315 lines
12 KiB
C#
315 lines
12 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Additional options for managing the observer system.
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
[AddComponentMenu("FishNet/Manager/ObserverManager")]
|
|
public sealed class ObserverManager : MonoBehaviour
|
|
{
|
|
#region Internal.
|
|
/// <summary>
|
|
/// Current index to use for level of detail based on tick.
|
|
/// </summary>
|
|
internal byte LevelOfDetailIndex { get; private set; }
|
|
#endregion
|
|
|
|
#region Serialized.
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
[Tooltip("True to use the NetworkLOD system.")]
|
|
[SerializeField]
|
|
private bool _useNetworkLod;
|
|
/// <summary>
|
|
/// True to use the NetworkLOD system.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
internal bool GetUseNetworkLod() => _useNetworkLod;
|
|
/// <summary>
|
|
/// Distance for each level of detal.
|
|
/// </summary>
|
|
internal List<float> GetLevelOfDetailDistances() => (_useNetworkLod) ? _levelOfDetailDistances : _singleLevelOfDetailDistances;
|
|
[Tooltip("Distance for each level of detal.")]
|
|
[SerializeField]
|
|
private List<float> _levelOfDetailDistances = new List<float>();
|
|
/// <summary>
|
|
/// Returned when network LOD is off. Value contained is one level of detail with max distance.
|
|
/// </summary>
|
|
private List<float> _singleLevelOfDetailDistances = new List<float>() { float.MaxValue };
|
|
/// <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("Default observer conditions for networked objects.")]
|
|
[SerializeField]
|
|
private List<ObserverCondition> _defaultConditions = new List<ObserverCondition>();
|
|
#endregion
|
|
|
|
#region Private.
|
|
/// <summary>
|
|
/// NetworkManager on object.
|
|
/// </summary>
|
|
private NetworkManager _networkManager;
|
|
/// <summary>
|
|
/// Intervals for each level of detail.
|
|
/// </summary>
|
|
private uint[] _levelOfDetailIntervals;
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Initializes this script for use.
|
|
/// </summary>
|
|
/// <param name="manager"></param>
|
|
internal void InitializeOnce_Internal(NetworkManager manager)
|
|
{
|
|
_networkManager = manager;
|
|
ValidateLevelOfDetails();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a new value for UpdateHostVisibility.
|
|
/// </summary>
|
|
/// <param name="value">New value.</param>
|
|
/// <param name="updateType">Which objects to update.</param>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds default observer conditions to nob and returns the NetworkObserver used.
|
|
/// </summary>
|
|
internal NetworkObserver AddDefaultConditions(NetworkObject nob)
|
|
{
|
|
bool isGlobal = (nob.IsGlobal && !nob.IsSceneObject);
|
|
bool obsAdded;
|
|
|
|
NetworkObserver result;
|
|
if (!nob.TryGetComponent<NetworkObserver>(out result))
|
|
{
|
|
obsAdded = true;
|
|
result = nob.gameObject.AddComponent<NetworkObserver>();
|
|
}
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the tick interval to use for a lod level.
|
|
/// </summary>
|
|
/// <param name="lodLevel"></param>
|
|
/// <returns></returns>
|
|
public byte GetLevelOfDetailInterval(byte lodLevel)
|
|
{
|
|
if (LevelOfDetailIndex == 0)
|
|
return 1;
|
|
|
|
return (byte)System.Math.Pow(2, lodLevel);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates and sets the current level of detail index for the tick.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates that level of detail intervals are proper.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
} |