using FishNet.Component.Observing; using FishNet.Connection; using FishNet.Managing; using FishNet.Managing.Timing; using FishNet.Observing; using System; using System.Collections.Generic; using GameKit.Dependencies.Utilities; using UnityEngine; namespace FishNet.Object { public partial class NetworkObject : MonoBehaviour { #region Public. /// /// Called when the clientHost gains or loses visibility of this object. /// Boolean value will be true if clientHost has visibility. /// public event HostVisibilityUpdatedDelegate OnHostVisibilityUpdated; /// /// /// /// True if clientHost was known to have visibility of the object prior to this invoking. /// True if the clientHost now has visibility of the object. public delegate void HostVisibilityUpdatedDelegate(bool prevVisible, bool nextVisible); /// /// Called when this NetworkObject losses all observers or gains observers while previously having none. /// public event Action OnObserversActive; /// /// NetworkObserver on this object. /// [HideInInspector] public NetworkObserver NetworkObserver = null; /// /// Clients which can see and get messages from this NetworkObject. /// [HideInInspector] public HashSet Observers = new(); #endregion #region Internal. /// /// Current HashGrid entry this belongs to. /// internal GridEntry HashGridEntry; /// /// Last tick an observer was added. /// internal uint ObserverAddedTick = TimeManager.UNSET_TICK; #endregion #region Private. /// /// True if NetworkObserver has been initialized. /// private bool _networkObserverInitiliazed = false; /// /// Found renderers on the NetworkObject and it's children. This is only used as clientHost to hide non-observers objects. /// [System.NonSerialized] private List _renderers; /// /// True if renderers have been looked up. /// private bool _renderersPopulated; /// /// Last visibility value for clientHost on this object. /// private bool _lastClientHostVisibility; /// /// HashGrid for this object. /// private HashGrid _hashGrid; /// /// Next time this object may update it's position for HashGrid. /// private float _nextHashGridUpdateTime; /// /// True if this gameObject is static. /// private bool _isStatic; /// /// Current grid position. /// private Vector2Int _hashGridPosition = HashGrid.UnsetGridPosition; #endregion /// /// Updates Objects positions in the HashGrid for this Networkmanager. /// internal void UpdateForNetworkObject(bool force) { if (_hashGrid == null) return; if (_isStatic) return; float unscaledTime = Time.unscaledTime; //Not enough time has passed to update. if (!force && unscaledTime < _nextHashGridUpdateTime) return; const float updateInterval = 1f; _nextHashGridUpdateTime = unscaledTime + updateInterval; Vector2Int newPosition = _hashGrid.GetHashGridPosition(this); if (newPosition != _hashGridPosition) { _hashGridPosition = newPosition; HashGridEntry = _hashGrid.GetGridEntry(newPosition); } } /// /// Updates cached renderers used to managing clientHost visibility. /// /// True to also update visibility if clientHost. public void UpdateRenderers(bool updateVisibility = true) { InitializeRendererCollection(force: true, updateVisibility); } /// /// Sets the renderer visibility for clientHost. /// /// True if renderers are to be visibile. /// True to skip blocking checks. public void SetRenderersVisible(bool visible, bool force = false) { if (!force && !NetworkObserver.UpdateHostVisibility) return; UpdateRenderVisibility(visible); } /// /// Updates visibilites on renders without checks. /// /// private void UpdateRenderVisibility(bool visible) { InitializeRendererCollection(force: false, updateVisibility: false); List rs = _renderers; for (int i = 0; i < rs.Count; i++) { Renderer r = rs[i]; if (r == null) { _renderers.RemoveAt(i); i--; } else { r.enabled = visible; } } if (OnHostVisibilityUpdated != null) OnHostVisibilityUpdated.Invoke(_lastClientHostVisibility, visible); _lastClientHostVisibility = visible; } /// /// If needed Renderers collection is initialized and populated. /// private void InitializeRendererCollection(bool force, bool updateVisibility) { if (!force && _renderersPopulated) return; List cache = CollectionCaches.RetrieveList(); GetComponentsInChildren(includeInactive: true, cache); _renderers = new(); foreach (Renderer r in cache) { if (r.enabled) _renderers.Add(r); } CollectionCaches.Store(cache); /* Intentionally set before event call. This is to prevent * a potential endless loop should the user make another call * to this objects renderer API from the event, resulting in * the population repeating. */ _renderersPopulated = true; if (updateVisibility) UpdateRenderVisibility(_lastClientHostVisibility); } /// /// Adds the default NetworkObserver conditions using the ObserverManager. /// private void AddDefaultNetworkObserverConditions() { if (_networkObserverInitiliazed) return; NetworkObserver = NetworkManager.ObserverManager.AddDefaultConditions(this); } /// /// Removes a connection from observers for this object returning if the connection was removed. /// /// internal bool RemoveObserver(NetworkConnection connection) { int startCount = Observers.Count; bool removed = Observers.Remove(connection); if (removed) TryInvokeOnObserversActive(startCount); return removed; } /// /// Adds the connection to observers if conditions are met. /// /// /// True if added to Observers. internal ObserverStateChange RebuildObservers(NetworkConnection connection, bool timedOnly) { //If not a valid connection. if (!connection.IsValid) { NetworkManager.LogWarning($"An invalid connection was used when rebuilding observers."); return ObserverStateChange.Unchanged; } //Valid not not active. else if (!connection.IsActive) { /* Just remove from observers since connection isn't active * and return unchanged because nothing should process * given the connection isnt active. */ Observers.Remove(connection); return ObserverStateChange.Unchanged; } else if (IsDeinitializing) { /* If object is deinitializing it's either being despawned * this frame or it's not spawned. If we've made it this far, * it's most likely being despawned. */ return ObserverStateChange.Unchanged; } //Update hashgrid if needed. UpdateForNetworkObject(!timedOnly); int startCount = Observers.Count; ObserverStateChange osc = NetworkObserver.RebuildObservers(connection, timedOnly); if (osc == ObserverStateChange.Added) Observers.Add(connection); else if (osc == ObserverStateChange.Removed) Observers.Remove(connection); if (osc != ObserverStateChange.Unchanged) TryInvokeOnObserversActive(startCount); return osc; } /// /// Invokes OnObserversActive if observers are now 0 but previously were not, or if was previously 0 but now has observers. /// /// private void TryInvokeOnObserversActive(int startCount) { if (TimeManager != null) ObserverAddedTick = TimeManager.LocalTick; if (OnObserversActive != null) { if ((Observers.Count > 0 && startCount == 0) || Observers.Count == 0 && startCount > 0) OnObserversActive.Invoke(this); } } /// /// Resets this object to starting values. /// private void ResetState_Observers(bool asServer) { //As server or client it's safe to reset this value. ObserverAddedTick = TimeManager.UNSET_TICK; } } }