using FishNet.Connection; //remove on 2023/01/01 move to correct folder. using FishNet.Object; using FishNet.Observing; using FishNet.Utility.Constant; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; using UnityEngine.Serialization; [assembly: InternalsVisibleTo(UtilityConstants.DEMOS_ASSEMBLY_NAME)] namespace FishNet.Managing.Observing { /// /// Additional options for managing the observer system. /// [DisallowMultipleComponent] [AddComponentMenu("FishNet/Manager/ObserverManager")] public sealed class ObserverManager : MonoBehaviour { #region Internal. /// /// Current index to use for level of detail based on tick. /// internal byte LevelOfDetailIndex { get; private set; } #endregion #region Serialized. /// /// /// [Tooltip("True to use the NetworkLOD system.")] [SerializeField] private bool _useNetworkLod; /// /// True to use the NetworkLOD system. /// /// internal bool GetUseNetworkLod() => _useNetworkLod; /// /// Distance for each level of detal. /// internal List GetLevelOfDetailDistances() => (_useNetworkLod) ? _levelOfDetailDistances : _singleLevelOfDetailDistances; [Tooltip("Distance for each level of detal.")] [SerializeField] private List _levelOfDetailDistances = new List(); /// /// Returned when network LOD is off. Value contained is one level of detail with max distance. /// private List _singleLevelOfDetailDistances = new List() { float.MaxValue }; /// /// /// [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("Default observer conditions for networked objects.")] [SerializeField] private List _defaultConditions = new List(); #endregion #region Private. /// /// NetworkManager on object. /// private NetworkManager _networkManager; /// /// Intervals for each level of detail. /// private uint[] _levelOfDetailIntervals; #endregion /// /// Initializes this script for use. /// /// internal void InitializeOnce_Internal(NetworkManager manager) { _networkManager = manager; ValidateLevelOfDetails(); } /// /// Sets a new value for UpdateHostVisibility. /// /// New value. /// Which objects to update. public void SetUpdateHostVisibility(bool value, HostVisibilityUpdateTypes updateType) { //Unchanged. if (value == UpdateHostVisibility) return; /* Update even if server state is not known. * The setting should be updated so when the server * does start spawned objects have latest setting. */ if (HostVisibilityUpdateContains(updateType, HostVisibilityUpdateTypes.Manager)) UpdateHostVisibility = value; /* If to update spawned as well then update all networkobservers * with the setting and also update renderers. */ if (_networkManager.IsServer && HostVisibilityUpdateContains(updateType, HostVisibilityUpdateTypes.Spawned)) { NetworkConnection clientConn = _networkManager.ClientManager.Connection; foreach (NetworkObject n in _networkManager.ServerManager.Objects.Spawned.Values) { n.NetworkObserver.SetUpdateHostVisibility(value); //Only check to update renderers if clientHost. If not client then clientConn won't be active. if (clientConn.IsActive) n.SetRenderersVisible(n.Observers.Contains(clientConn), true); } } bool HostVisibilityUpdateContains(HostVisibilityUpdateTypes whole, HostVisibilityUpdateTypes part) { return (whole & part) == part; } } /// /// Adds default observer conditions to nob and returns the NetworkObserver used. /// internal NetworkObserver AddDefaultConditions(NetworkObject nob) { bool isGlobal = (nob.IsGlobal && !nob.IsSceneObject); bool obsAdded; NetworkObserver result; if (!nob.TryGetComponent(out result)) { obsAdded = true; result = nob.gameObject.AddComponent(); } else { obsAdded = false; } /* NetworkObserver is null and there are no * conditions to add. Nothing will change by adding * the NetworkObserver component so exit early. */ if (!obsAdded && _defaultConditions.Count == 0) return result; //If the NetworkObserver component was just added. if (obsAdded) { /* Global nobs do not need a NetworkObserver. * Ultimately, a global NetworkObject is one without * any conditions. */ if (isGlobal) return result; //If there are no conditions then there's nothing to add. if (_defaultConditions.Count == 0) return result; /* If here then there not a global networkobject and there are conditions to use. * Since the NetworkObserver is being added fresh, set OverrideType to UseManager * so that the NetworkObserver is populated with the manager conditions. */ result.OverrideType = NetworkObserver.ConditionOverrideType.UseManager; } //NetworkObject has a NetworkObserver already on it. else { //If global the NetworkObserver has to be cleared and set to ignore manager. if (isGlobal) { result.ObserverConditionsInternal.Clear(); result.OverrideType = NetworkObserver.ConditionOverrideType.IgnoreManager; } } //If ignoring manager then use whatever is already configured. if (result.OverrideType == NetworkObserver.ConditionOverrideType.IgnoreManager) { //Do nothing. } //If using manager then replace all with conditions. else if (result.OverrideType == NetworkObserver.ConditionOverrideType.UseManager) { result.ObserverConditionsInternal.Clear(); AddMissing(result); } //Adding only new. else if (result.OverrideType == NetworkObserver.ConditionOverrideType.AddMissing) { AddMissing(result); } void AddMissing(NetworkObserver networkObserver) { int count = _defaultConditions.Count; for (int i = 0; i < count; i++) { ObserverCondition oc = _defaultConditions[i]; if (!networkObserver.ObserverConditionsInternal.Contains(oc)) networkObserver.ObserverConditionsInternal.Add(oc); } } return result; } /// /// Gets the tick interval to use for a lod level. /// /// /// public byte GetLevelOfDetailInterval(byte lodLevel) { if (LevelOfDetailIndex == 0) return 1; return (byte)System.Math.Pow(2, lodLevel); } /// /// Calculates and sets the current level of detail index for the tick. /// internal void CalculateLevelOfDetail(uint tick) { int count = GetLevelOfDetailDistances().Count; for (int i = (count - 1); i > 0; i--) { uint interval = _levelOfDetailIntervals[i]; if (tick % interval == 0) { LevelOfDetailIndex = (byte)i; return; } } //If here then index is 0 and interval is every tick. LevelOfDetailIndex = 0; } /// /// Validates that level of detail intervals are proper. /// private void ValidateLevelOfDetails() { if (!_useNetworkLod) return; //No distances specified. if (_levelOfDetailDistances == null || _levelOfDetailDistances.Count == 0) { if (_networkManager != null) { _networkManager.LogWarning("Level of detail distances contains no entries. NetworkLOD has been disabled."); _useNetworkLod = false; } return; } //Make sure every distance is larger than the last. float lastDistance = float.MinValue; foreach (float dist in _levelOfDetailDistances) { if (dist <= 0f || dist <= lastDistance) { if (_networkManager != null) { _networkManager.LogError($"Level of detail distances must be greater than 0f, and each distance larger than the previous. NetworkLOD has been disabled."); _useNetworkLod = false; } return; } lastDistance = dist; } int maxEntries = 8; //Too many distances. if (_levelOfDetailDistances.Count > maxEntries) { _networkManager?.LogWarning("There can be a maximum of 8 level of detail distances. Entries beyond this quantity have been discarded."); while (_levelOfDetailDistances.Count > maxEntries) _levelOfDetailDistances.RemoveAt(_levelOfDetailDistances.Count - 1); } if (Application.isPlaying) { //Build intervals and sqr distances. int count = _levelOfDetailDistances.Count; _levelOfDetailIntervals = new uint[count]; for (int i = (count - 1); i > 0; i--) { uint power = (uint)Mathf.Pow(2, i); _levelOfDetailIntervals[i] = power; } //Sqr for (int i = 0; i < count; i++) { float dist = _levelOfDetailDistances[i]; dist *= dist; _levelOfDetailDistances[i] = dist; } } } } }