using FishNet.Connection; using FishNet.Documenting; using FishNet.Object; using FishNet.Transporting; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; using UnityEngine.Serialization; namespace FishNet.Observing { /// /// Controls which clients can see and get messages for an object. /// [DisallowMultipleComponent] [AddComponentMenu("FishNet/Component/NetworkObserver")] public sealed class NetworkObserver : MonoBehaviour { #region Types. /// /// How ObserverManager conditions are used. /// public enum ConditionOverrideType { /// /// Keep current conditions, add new conditions from manager. /// AddMissing = 1, /// /// Replace current conditions with manager conditions. /// UseManager = 2, /// /// Keep current conditions, ignore manager conditions. /// IgnoreManager = 3, } #endregion #region Serialized. /// /// /// [Tooltip("How ObserverManager conditions are used.")] [SerializeField] private ConditionOverrideType _overrideType = ConditionOverrideType.IgnoreManager; /// /// How ObserverManager conditions are used. /// public ConditionOverrideType OverrideType { get => _overrideType; internal set => _overrideType = value; } /// /// /// [Tooltip("True to update visibility for clientHost based on if they are an observer or not.")] [FormerlySerializedAs("_setHostVisibility")] [SerializeField] private bool _updateHostVisibility = true; /// /// True to update visibility for clientHost based on if they are an observer or not. /// public bool UpdateHostVisibility { get => _updateHostVisibility; private set => _updateHostVisibility = value; } /// /// /// [Tooltip("Conditions connections must met to be added as an observer. Multiple conditions may be used.")] [SerializeField] internal List _observerConditions = new List(); /// /// Conditions connections must met to be added as an observer. Multiple conditions may be used. /// public IReadOnlyList ObserverConditions => _observerConditions; [APIExclude] #if MIRROR public List ObserverConditionsInternal #else internal List ObserverConditionsInternal #endif { get => _observerConditions; set => _observerConditions = value; } #endregion #region Private. /// /// Conditions under this component which are timed. /// private List _timedConditions = new List(); /// /// Connections which have all non-timed conditions met. /// private HashSet _nonTimedMet = new HashSet(); /// /// NetworkObject this belongs to. /// private NetworkObject _networkObject; /// /// Becomes true when registered with ServerObjects as Timed observers. /// private bool _registeredAsTimed; /// /// True if already pre-initialized. /// private bool _preintiialized; /// /// True if ParentNetworkObject was visible last iteration. /// This value will also be true if there is no ParentNetworkObject. /// private bool _lastParentVisible; #endregion private void OnEnable() { if (_networkObject != null && _networkObject.IsServer) RegisterTimedConditions(); } private void OnDisable() { if (_networkObject != null && _networkObject.IsDeinitializing) { _lastParentVisible = false; _nonTimedMet.Clear(); UnregisterTimedConditions(); } } private void OnDestroy() { if (_networkObject != null) UnregisterTimedConditions(); } internal void Deinitialize() { if (_networkObject != null && _networkObject.IsDeinitializing) { _networkObject.ServerManager.OnRemoteConnectionState -= ServerManager_OnRemoteConnectionState; UnregisterTimedConditions(); } } /// /// Initializes this script for use. /// /// internal void PreInitialize(NetworkObject networkObject) { if (!_preintiialized) { _preintiialized = true; _networkObject = networkObject; bool ignoringManager = (OverrideType == ConditionOverrideType.IgnoreManager); //Check to override SetHostVisibility. if (!ignoringManager) UpdateHostVisibility = networkObject.ObserverManager.UpdateHostVisibility; bool observerFound = false; for (int i = 0; i < _observerConditions.Count; i++) { if (_observerConditions[i] != null) { observerFound = true; /* Make an instance of each condition so values are * not overwritten when the condition exist more than * once in the scene. Double edged sword of using scriptable * objects for conditions. */ _observerConditions[i] = _observerConditions[i].Clone(); ObserverCondition oc = _observerConditions[i]; oc.InitializeOnce(_networkObject); //If timed also register as containing timed conditions. if (oc.Timed()) _timedConditions.Add(oc); } else { _observerConditions.RemoveAt(i); i--; } } //No observers specified, do not need to take further action. if (!observerFound) return; _networkObject.ServerManager.OnRemoteConnectionState += ServerManager_OnRemoteConnectionState; } RegisterTimedConditions(); } /// /// Returns a condition if found within Conditions. /// /// public ObserverCondition GetObserverCondition() where T : ObserverCondition { /* Do not bother setting local variables, * condition collections aren't going to be long * enough to make doing so worth while. */ System.Type conditionType = typeof(T); for (int i = 0; i < _observerConditions.Count; i++) { if (_observerConditions[i].GetType() == conditionType) return _observerConditions[i]; } //Fall through, not found. return null; } /// /// Returns ObserverStateChange by comparing conditions for a connection. /// /// True if added to Observers. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ObserverStateChange RebuildObservers(NetworkConnection connection, bool timedOnly) { bool currentlyAdded = (_networkObject.Observers.Contains(connection)); //True if all conditions are met. bool allConditionsMet = true; //Only need to check beyond this if conditions exist. if (_observerConditions.Count > 0) { /* If cnnection is owner then they can see the object. */ bool notOwner = (connection != _networkObject.Owner); /* Only check conditions if not owner. Owner will always * have visibility. */ if (notOwner) { bool parentVisible = true; if (_networkObject.ParentNetworkObject != null) { parentVisible = _networkObject.ParentNetworkObject.Observers.Contains(connection); /* If parent is visible but was not previously * then unset timedOnly to make sure all conditions * are checked again. This ensures that the _nonTimedMet * collection is updated. */ if (parentVisible && !_lastParentVisible) timedOnly = false; _lastParentVisible = parentVisible; } //If parent is not visible no further checks are required. if (!parentVisible) { allConditionsMet = false; } //Parent is visible, perform checks. else { //True if connection starts with meeting non-timed conditions. bool startNonTimedMet = _nonTimedMet.Contains(connection); /* If a timed update an1d nonTimed * have not been met then there's * no reason to check timed. */ if (timedOnly && !startNonTimedMet) { allConditionsMet = false; } else { //Becomes true if a non-timed condition fails. bool nonTimedMet = true; List collection = (timedOnly) ? _timedConditions : _observerConditions; for (int i = 0; i < collection.Count; i++) { ObserverCondition condition = collection[i]; /* If any observer returns removed then break * from loop and return removed. If one observer has * removed then there's no reason to iterate * the rest. * * A condition is automatically met if it's not enabled. */ bool notProcessed = false; bool conditionMet = (!condition.GetIsEnabled() || condition.ConditionMet(connection, currentlyAdded, out notProcessed)); if (notProcessed) conditionMet = currentlyAdded; //Condition not met. if (!conditionMet) { allConditionsMet = false; if (!condition.Timed()) nonTimedMet = false; break; } } //If all conditions are being checked and nonTimedMet has updated. if (!timedOnly && (startNonTimedMet != nonTimedMet)) { if (nonTimedMet) _nonTimedMet.Add(connection); else _nonTimedMet.Remove(connection); } } } } } //If all conditions met. if (allConditionsMet) return ReturnPassedConditions(currentlyAdded); else return ReturnFailedCondition(currentlyAdded); } /// /// Registers timed observer conditions. /// private void RegisterTimedConditions() { if (_timedConditions.Count == 0) return; //Already registered or no timed conditions. if (_registeredAsTimed) return; _registeredAsTimed = true; _networkObject.NetworkManager.ServerManager.Objects.AddTimedNetworkObserver(_networkObject); } /// /// Unregisters timed conditions. /// private void UnregisterTimedConditions() { if (_timedConditions.Count == 0) return; if (!_registeredAsTimed) return; _registeredAsTimed = false; _networkObject.NetworkManager.ServerManager.Objects.RemoveTimedNetworkObserver(_networkObject); } /// /// Returns an ObserverStateChange when a condition fails. /// /// /// private ObserverStateChange ReturnFailedCondition(bool currentlyAdded) { if (currentlyAdded) return ObserverStateChange.Removed; else return ObserverStateChange.Unchanged; } /// /// Returns an ObserverStateChange when all conditions pass. /// /// /// private ObserverStateChange ReturnPassedConditions(bool currentlyAdded) { if (currentlyAdded) return ObserverStateChange.Unchanged; else return ObserverStateChange.Added; } /// /// Called when a remote client state changes with the server. /// private void ServerManager_OnRemoteConnectionState(NetworkConnection conn, RemoteConnectionStateArgs arg2) { if (arg2.ConnectionState == RemoteConnectionState.Stopped) _nonTimedMet.Remove(conn); } /// /// Sets a new value for UpdateHostVisibility. /// This does not immediately update renderers. /// You may need to combine with NetworkObject.SetRenderersVisible(bool). /// /// New value. public void SetUpdateHostVisibility(bool value) { //Unchanged. if (value == UpdateHostVisibility) return; UpdateHostVisibility = value; } } }