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;
}
}
}