405 lines
15 KiB
C#
405 lines
15 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Controls which clients can see and get messages for an object.
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
[AddComponentMenu("FishNet/Component/NetworkObserver")]
|
|
public sealed class NetworkObserver : MonoBehaviour
|
|
{
|
|
#region Types.
|
|
/// <summary>
|
|
/// How ObserverManager conditions are used.
|
|
/// </summary>
|
|
public enum ConditionOverrideType
|
|
{
|
|
/// <summary>
|
|
/// Keep current conditions, add new conditions from manager.
|
|
/// </summary>
|
|
AddMissing = 1,
|
|
/// <summary>
|
|
/// Replace current conditions with manager conditions.
|
|
/// </summary>
|
|
UseManager = 2,
|
|
/// <summary>
|
|
/// Keep current conditions, ignore manager conditions.
|
|
/// </summary>
|
|
IgnoreManager = 3,
|
|
}
|
|
#endregion
|
|
|
|
#region Serialized.
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
[Tooltip("How ObserverManager conditions are used.")]
|
|
[SerializeField]
|
|
private ConditionOverrideType _overrideType = ConditionOverrideType.IgnoreManager;
|
|
/// <summary>
|
|
/// How ObserverManager conditions are used.
|
|
/// </summary>
|
|
public ConditionOverrideType OverrideType
|
|
{
|
|
get => _overrideType;
|
|
internal set => _overrideType = value;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
[Tooltip("True to update visibility for clientHost based on if they are an observer or not.")]
|
|
[FormerlySerializedAs("_setHostVisibility")]
|
|
[SerializeField]
|
|
private bool _updateHostVisibility = true;
|
|
/// <summary>
|
|
/// True to update visibility for clientHost based on if they are an observer or not.
|
|
/// </summary>
|
|
public bool UpdateHostVisibility
|
|
{
|
|
get => _updateHostVisibility;
|
|
private set => _updateHostVisibility = value;
|
|
}
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
[Tooltip("Conditions connections must met to be added as an observer. Multiple conditions may be used.")]
|
|
[SerializeField]
|
|
internal List<ObserverCondition> _observerConditions = new List<ObserverCondition>();
|
|
/// <summary>
|
|
/// Conditions connections must met to be added as an observer. Multiple conditions may be used.
|
|
/// </summary>
|
|
public IReadOnlyList<ObserverCondition> ObserverConditions => _observerConditions;
|
|
[APIExclude]
|
|
#if MIRROR
|
|
public List<ObserverCondition> ObserverConditionsInternal
|
|
#else
|
|
internal List<ObserverCondition> ObserverConditionsInternal
|
|
#endif
|
|
{
|
|
get => _observerConditions;
|
|
set => _observerConditions = value;
|
|
}
|
|
#endregion
|
|
|
|
#region Private.
|
|
/// <summary>
|
|
/// Conditions under this component which are timed.
|
|
/// </summary>
|
|
private List<ObserverCondition> _timedConditions = new List<ObserverCondition>();
|
|
/// <summary>
|
|
/// Connections which have all non-timed conditions met.
|
|
/// </summary>
|
|
private HashSet<NetworkConnection> _nonTimedMet = new HashSet<NetworkConnection>();
|
|
/// <summary>
|
|
/// NetworkObject this belongs to.
|
|
/// </summary>
|
|
private NetworkObject _networkObject;
|
|
/// <summary>
|
|
/// Becomes true when registered with ServerObjects as Timed observers.
|
|
/// </summary>
|
|
private bool _registeredAsTimed;
|
|
/// <summary>
|
|
/// True if already pre-initialized.
|
|
/// </summary>
|
|
private bool _preintiialized;
|
|
/// <summary>
|
|
/// True if ParentNetworkObject was visible last iteration.
|
|
/// This value will also be true if there is no ParentNetworkObject.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes this script for use.
|
|
/// </summary>
|
|
/// <param name="networkManager"></param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a condition if found within Conditions.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public ObserverCondition GetObserverCondition<T>() 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns ObserverStateChange by comparing conditions for a connection.
|
|
/// </summary>
|
|
/// <returns>True if added to Observers.</returns>
|
|
[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<ObserverCondition> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers timed observer conditions.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unregisters timed conditions.
|
|
/// </summary>
|
|
private void UnregisterTimedConditions()
|
|
{
|
|
if (_timedConditions.Count == 0)
|
|
return;
|
|
if (!_registeredAsTimed)
|
|
return;
|
|
|
|
_registeredAsTimed = false;
|
|
_networkObject.NetworkManager.ServerManager.Objects.RemoveTimedNetworkObserver(_networkObject);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an ObserverStateChange when a condition fails.
|
|
/// </summary>
|
|
/// <param name="currentlyAdded"></param>
|
|
/// <returns></returns>
|
|
private ObserverStateChange ReturnFailedCondition(bool currentlyAdded)
|
|
{
|
|
if (currentlyAdded)
|
|
return ObserverStateChange.Removed;
|
|
else
|
|
return ObserverStateChange.Unchanged;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns an ObserverStateChange when all conditions pass.
|
|
/// </summary>
|
|
/// <param name="currentlyAdded"></param>
|
|
/// <returns></returns>
|
|
private ObserverStateChange ReturnPassedConditions(bool currentlyAdded)
|
|
{
|
|
if (currentlyAdded)
|
|
return ObserverStateChange.Unchanged;
|
|
else
|
|
return ObserverStateChange.Added;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when a remote client state changes with the server.
|
|
/// </summary>
|
|
private void ServerManager_OnRemoteConnectionState(NetworkConnection conn, RemoteConnectionStateArgs arg2)
|
|
{
|
|
if (arg2.ConnectionState == RemoteConnectionState.Stopped)
|
|
_nonTimedMet.Remove(conn);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a new value for UpdateHostVisibility.
|
|
/// This does not immediately update renderers.
|
|
/// You may need to combine with NetworkObject.SetRenderersVisible(bool).
|
|
/// </summary>
|
|
/// <param name="value">New value.</param>
|
|
public void SetUpdateHostVisibility(bool value)
|
|
{
|
|
//Unchanged.
|
|
if (value == UpdateHostVisibility)
|
|
return;
|
|
|
|
UpdateHostVisibility = value;
|
|
}
|
|
|
|
}
|
|
}
|