StationObscurum/Assets/FishNet/Runtime/Managing/Client/Object/ObjectCaching.cs

605 lines
27 KiB
C#

using FishNet.Connection;
using FishNet.Managing.Logging;
using FishNet.Managing.Object;
using FishNet.Object;
using FishNet.Object.Helping;
using FishNet.Serializing;
using FishNet.Utility.Extension;
using FishNet.Utility.Performance;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Scripting;
namespace FishNet.Managing.Client
{
/// <summary>
/// Information about cached network objects.
/// </summary>
internal class ClientObjectCache
{
#region Types.
public enum CacheSearchType
{
Any = 0,
Spawning = 1,
Despawning = 2
}
#endregion
#region Internal.
/// <summary>
/// Objets which are being spawned during iteration.
/// </summary>
internal Dictionary<int, NetworkObject> SpawningObjects = new Dictionary<int, NetworkObject>();
#endregion
#region Private.
/// <summary>
/// Cached objects buffer. Contains spawns and despawns.
/// </summary>
private ListCache<CachedNetworkObject> _cachedObjects = new ListCache<CachedNetworkObject>();
/// <summary>
/// NetworkObjects which have been spawned already during the current iteration.
/// </summary>
private HashSet<NetworkObject> _iteratedSpawns = new HashSet<NetworkObject>();
/// <summary>
/// Despawns which are occurring the same tick as their spawn.
/// </summary>
private HashSet<int> _conflictingDespawns = new HashSet<int>();
/// <summary>
/// ClientObjects reference.
/// </summary>
private ClientObjects _clientObjects;
/// <summary>
/// NetworkManager for this cache.
/// </summary>
private NetworkManager _networkManager;
/// <summary>
/// True if logged the warning about despawning on the same tick as the spawn.
/// This exist to prevent excessive spam of the warning.
/// </summary>
private bool _loggedSameTickWarning;
/// <summary>
/// True if initializeOrder was not default for any spawned objects.
/// </summary>
private bool _initializeOrderChanged;
#endregion
public ClientObjectCache(ClientObjects cobs, NetworkManager networkManager)
{
_clientObjects = cobs;
_networkManager = networkManager;
}
/// <summary>
/// Returns a NetworkObject found in spawned cache using objectId.
/// </summary>
/// <param name="objectId"></param>
/// <returns></returns>
public NetworkObject GetInCached(int objectId, CacheSearchType searchType)
{
int count = _cachedObjects.Written;
List<CachedNetworkObject> collection = _cachedObjects.Collection;
for (int i = 0; i < count; i++)
{
CachedNetworkObject cnob = collection[i];
if (cnob.ObjectId == objectId)
{
//Any condition always returns.
if (searchType == CacheSearchType.Any)
return cnob.NetworkObject;
bool spawning = (searchType == CacheSearchType.Spawning);
bool spawnAction = (cnob.Action == CachedNetworkObject.ActionType.Spawn);
if (spawning == spawnAction)
return cnob.NetworkObject;
else
return null;
}
}
//Fall through.
return null;
}
/// <summary>
/// Initializes for a spawned NetworkObject.
/// </summary>
/// <param name="nob"></param>
/// <param name="syncValues"></param>
/// <param name="manager"></param>
public void AddSpawn(NetworkManager manager, ushort collectionId, int objectId, sbyte initializeOrder, int ownerId, SpawnType ost, byte componentIndex, int rootObjectId, int? parentObjectId, byte? parentComponentIndex
, int? prefabId, Vector3? localPosition, Quaternion? localRotation, Vector3? localScale, ulong sceneId, ArraySegment<byte> rpcLinks, ArraySegment<byte> syncValues)
{
//Set if initialization order has changed.
_initializeOrderChanged |= (initializeOrder != 0);
CachedNetworkObject cnob = null;
//If order has not changed then add normally.
if (!_initializeOrderChanged)
{
cnob = _cachedObjects.AddReference();
}
//Otherwise see if values need to be sorted.
else
{
/* Spawns will be ordered at the end of their nearest order.
* If spawns arrived with Id order of 5, 7, 2 then the result
* would be as shown below...
* Id 5 / order -5
* Id 7 / order -5
* Id 2 / order 0
* Not as if the values were inserted first such as...
* Id 7 / order -5
* Id 5 / order -5
* Id 2 / order 0
* This is to prevent the likeliness of child nobs being out of order
* as well to preserve user spawn order if they spawned multiple
* objects the same which, with the same order. */
int written = _cachedObjects.Written;
for (int i = 0; i < written; i++)
{
CachedNetworkObject item = _cachedObjects.Collection[i];
/* If item order is larger then that means
* initializeOrder has reached the last entry
* of its value. Insert just before item index. */
if (initializeOrder < item.InitializeOrder)
{
cnob = _cachedObjects.InsertReference(i);
break;
}
}
//If here and cnob is null then it was not inserted; add to end.
if (cnob == null)
cnob = _cachedObjects.AddReference();
}
cnob.InitializeSpawn(manager, collectionId, objectId, initializeOrder, ownerId, ost, componentIndex, rootObjectId, parentObjectId, parentComponentIndex
, prefabId, localPosition, localRotation, localScale, sceneId, rpcLinks, syncValues);
}
public void AddDespawn(int objectId, DespawnType despawnType)
{
CachedNetworkObject cnob = _cachedObjects.AddReference();
cnob.InitializeDespawn(objectId, despawnType);
}
/// <summary>
/// Iterates any written objects.
/// </summary>
public void Iterate()
{
int written = _cachedObjects.Written;
if (written == 0)
return;
try
{
//Indexes which have already been processed.
HashSet<int> processedIndexes = new HashSet<int>();
List<CachedNetworkObject> collection = _cachedObjects.Collection;
_conflictingDespawns.Clear();
/* The next iteration will set rpclinks,
* synctypes, and so on. */
for (int i = 0; i < written; i++)
{
/* An index may already be processed if it was pushed ahead.
* This can occur if a nested object spawn exists but the root
* object has not spawned yet. In this situation the root spawn is
* found and performed first. */
if (processedIndexes.Contains(i))
continue;
CachedNetworkObject cnob = collection[i];
bool spawn = (cnob.Action == CachedNetworkObject.ActionType.Spawn);
/* See if nested, and if so check if root is already spawned.
* If parent is not spawned then find it and process the parent first. */
if (spawn)
{
/* When an object is nested or has a parent it is
* dependent upon either the root of nested, or the parent,
* being spawned to setup properly.
*
* When either of these are true check spawned objects first
* to see if the objects exist. If not check if they are appearing
* later in the cache. Root or parent objects can appear later
* in the cache depending on the order of which observers are rebuilt.
* While it is possible to have the server ensure spawns always send
* root/parents first, that's a giant can of worms that's not worth getting into.
* Not only are there many scenarios to cover, but it also puts more work
* on the server. It's more effective to have the client handle the sorting. */
//Nested.
if (cnob.IsNested || cnob.HasParent)
{
bool nested = cnob.IsNested;
//It's not possible to be nested and have a parent. Set the Id to look for based on if nested or parented.
int targetObjectId = (nested) ? cnob.RootObjectId : cnob.ParentObjectId.Value;
NetworkObject nob = GetSpawnedObject(targetObjectId);
//If not spawned yet.
if (nob == null)
{
bool found = false;
string errMsg;
for (int z = (i + 1); z < written; z++)
{
CachedNetworkObject zCnob = collection[z];
if (zCnob.ObjectId == targetObjectId)
{
found = true;
if (cnob.Action != CachedNetworkObject.ActionType.Spawn)
{
errMsg = (nested)
? $"ObjectId {targetObjectId} was found for a nested spawn, but ActionType is not spawn. ComponentIndex {cnob.ComponentIndex} will not be spawned."
: $"ObjectId {targetObjectId} was found for a parented spawn, but ActionType is not spawn. ObjectId {cnob.ObjectId} will not be spawned.";
_networkManager.LogError(errMsg);
break;
}
else
{
ProcessObject(zCnob, true, z);
break;
}
}
}
//Root nob could not be found.
if (!found)
{
errMsg = (nested)
? $"ObjectId {targetObjectId} could not be found for a nested spawn. ComponentIndex {cnob.ComponentIndex} will not be spawned."
: $"ObjectId {targetObjectId} was found for a parented spawn. ObjectId {cnob.ObjectId} will not be spawned.";
_networkManager.LogError(errMsg);
}
}
}
}
ProcessObject(cnob, spawn, i);
}
void ProcessObject(CachedNetworkObject cnob, bool spawn, int index)
{
processedIndexes.Add(index);
if (spawn)
{
if (cnob.IsSceneObject)
cnob.NetworkObject = _clientObjects.GetSceneNetworkObject(cnob.SceneId);
else if (cnob.IsNested)
cnob.NetworkObject = _clientObjects.GetNestedNetworkObject(cnob);
else
cnob.NetworkObject = _clientObjects.GetInstantiatedNetworkObject(cnob);
/* Apply transform changes but only if not host.
* These would have already been applied server side. */
if (!_networkManager.IsHost && cnob.NetworkObject != null)
{
Transform t = cnob.NetworkObject.transform;
_clientObjects.GetTransformProperties(cnob.LocalPosition, cnob.LocalRotation, cnob.LocalScale, t, out Vector3 pos, out Quaternion rot, out Vector3 scale);
t.SetLocalPositionRotationAndScale(pos, rot, scale);
}
}
else
{
cnob.NetworkObject = _clientObjects.GetSpawnedNetworkObject(cnob);
/* Do not log unless not nested. Nested nobs sometimes
* could be destroyed if parent was first. */
if (!_networkManager.IsHost && cnob.NetworkObject == null && !cnob.IsNested)
_networkManager.Log($"NetworkObject for ObjectId of {cnob.ObjectId} was found null. Unable to despawn object. This may occur if a nested NetworkObject had it's parent object unexpectedly destroyed. This incident is often safe to ignore.");
}
NetworkObject nob = cnob.NetworkObject;
//No need to error here, the other Gets above would have.
if (nob == null)
return;
if (spawn)
{
//If not also server then object also has to be preinitialized.
if (!_networkManager.IsServer)
{
int ownerId = cnob.OwnerId;
//If local client is owner then use localconnection reference.
NetworkConnection localConnection = _networkManager.ClientManager.Connection;
NetworkConnection owner;
//If owner is self.
if (ownerId == localConnection.ClientId)
{
owner = localConnection;
}
else
{
/* If owner cannot be found then share owners
* is disabled */
if (!_networkManager.ClientManager.Clients.TryGetValueIL2CPP(ownerId, out owner))
owner = NetworkManager.EmptyConnection;
}
nob.Preinitialize_Internal(_networkManager, cnob.ObjectId, owner, false);
}
_clientObjects.AddToSpawned(cnob.NetworkObject, false);
SpawningObjects.Add(cnob.ObjectId, cnob.NetworkObject);
IterateSpawn(cnob);
_iteratedSpawns.Add(cnob.NetworkObject);
/* Enable networkObject here if client only.
* This is to ensure Awake fires in the same order
* as InitializeOrder settings. There is no need
* to perform this action if server because server
* would have already spawned in order. */
if (!_networkManager.IsServer && cnob.NetworkObject != null)
cnob.NetworkObject.gameObject.SetActive(true);
}
else
{
/* If spawned already this iteration then the nob
* must be initialized so that the start/stop cycles
* complete normally. Otherwise, the despawn callbacks will
* fire immediately while the start callbacks will run after all
* spawns have been iterated.
* The downside to this is that synctypes
* for spawns later in this iteration will not be initialized
* yet, and if the nob being spawned/despawned references
* those synctypes the values will be default.
*
* The alternative is to delay the despawning until after
* all spawns are iterated, but that will break the order
* reliability. This is unfortunately a lose/lose situation so
* the best we can do is let the user know the risk. */
if (_iteratedSpawns.Contains(cnob.NetworkObject))
{
if (!_loggedSameTickWarning)
{
_loggedSameTickWarning = true;
_networkManager.LogWarning($"NetworkObject {cnob.NetworkObject.name} is being despawned on the same tick it's spawned." +
$" When this occurs SyncTypes will not be set on other objects during the time of this despawn." +
$" In result, if NetworkObject {cnob.NetworkObject.name} is referencing a SyncType of another object being spawned this tick, the returned values will be default.");
}
_conflictingDespawns.Add(cnob.ObjectId);
cnob.NetworkObject.gameObject.SetActive(true);
cnob.NetworkObject.Initialize(false, true);
}
//Now being initialized, despawn the object.
IterateDespawn(cnob);
}
}
/* Activate the objects after all data
* has been synchronized. This will apply synctypes. */
for (int i = 0; i < written; i++)
{
CachedNetworkObject cnob = collection[i];
if (cnob.Action == CachedNetworkObject.ActionType.Spawn && cnob.NetworkObject != null)
{
/* Apply syncTypes. It's very important to do this after all
* spawns have been processed and added to the manager.Objects collection.
* Otherwise, the synctype may reference an object spawning the same tick
* and the result would be null due to said object not being in spawned.
*
* At this time the NetworkObject is not initialized so by calling
* OnSyncType the changes are cached to invoke callbacks after initialization,
* not during the time of this action. */
foreach (NetworkBehaviour nb in cnob.NetworkObject.NetworkBehaviours)
{
PooledReader reader = cnob.SyncValuesReader;
//SyncVars.
int length = reader.ReadInt32();
nb.OnSyncType(reader, length, false);
//SyncObjects
length = reader.ReadInt32();
nb.OnSyncType(reader, length, true);
}
/* Only continue with the initialization if it wasn't initialized
* early to prevent a despawn conflict. */
bool canInitialize = (!_conflictingDespawns.Contains(cnob.ObjectId) || !_iteratedSpawns.Contains(cnob.NetworkObject));
if (canInitialize)
cnob.NetworkObject.Initialize(false, false);
}
}
//Invoke synctype callbacks.
for (int i = 0; i < written; i++)
{
CachedNetworkObject cnob = collection[i];
if (cnob.Action == CachedNetworkObject.ActionType.Spawn && cnob.NetworkObject != null)
cnob.NetworkObject.InvokeSyncTypeCallbacks(false);
}
}
finally
{
//Once all have been iterated reset.
Reset();
}
}
/// <summary>
/// Initializes an object on clients and spawns the NetworkObject.
/// </summary>
/// <param name="cnob"></param>
private void IterateSpawn(CachedNetworkObject cnob)
{
/* All nob spawns have been added to spawned before
* they are processed. This ensures they will be found if
* anything is referencing them before/after initialization. */
/* However, they have to be added again here should an ItereteDespawn
* had removed them. This can occur if an object is set to be spawned,
* thus added to spawned before iterations, then a despawn runs which
* removes it from spawn. */
_clientObjects.AddToSpawned(cnob.NetworkObject, false);
_clientObjects.ApplyRpcLinks(cnob.NetworkObject, cnob.RpcLinkReader);
}
/// <summary>
/// Deinitializes an object on clients and despawns the NetworkObject.
/// </summary>
/// <param name="cnob"></param>
private void IterateDespawn(CachedNetworkObject cnob)
{
_clientObjects.Despawn(cnob.NetworkObject, cnob.DespawnType, false);
}
/// <summary>
/// Returns a NetworkObject found in spawn cache, or Spawned.
/// </summary>
/// <param name="objectId"></param>
internal NetworkObject GetSpawnedObject(int objectId)
{
NetworkObject result;
//If not found in Spawning then check Spawned.
if (!SpawningObjects.TryGetValue(objectId, out result))
{
Dictionary<int, NetworkObject> spawned = (_networkManager.IsHost) ?
_networkManager.ServerManager.Objects.Spawned
: _networkManager.ClientManager.Objects.Spawned;
spawned.TryGetValue(objectId, out result);
}
return result;
}
/// <summary>
/// Resets cache.
/// </summary>
public void Reset()
{
_initializeOrderChanged = false;
_cachedObjects.Reset();
_iteratedSpawns.Clear();
SpawningObjects.Clear();
}
}
/// <summary>
/// A cached network object which exist in world but has not been Initialized yet.
/// </summary>
[Preserve]
internal class CachedNetworkObject
{
#region Types.
public enum ActionType
{
Unset = 0,
Spawn = 1,
Despawn = 2,
}
#endregion
/// <summary>
/// True if cached object is nested.
/// </summary>
public bool IsNested => (ComponentIndex > 0);
/// <summary>
/// True if a scene object.
/// </summary>
public bool IsSceneObject => (SceneId > 0);
/// <summary>
/// True if this object has a parent.
/// </summary>
public bool HasParent => (ParentObjectId != null);
/// <summary>
/// True if the parent object is a NetworkBehaviour.
/// </summary>
public bool ParentIsNetworkBehaviour => (HasParent && (ParentComponentIndex != null));
public ushort CollectionId;
public int ObjectId;
public sbyte InitializeOrder;
public int OwnerId;
public SpawnType SpawnType;
public DespawnType DespawnType;
public byte ComponentIndex;
public int RootObjectId;
public int? ParentObjectId;
public byte? ParentComponentIndex;
public int? PrefabId;
public Vector3? LocalPosition;
public Quaternion? LocalRotation;
public Vector3? LocalScale;
public ulong SceneId;
public ArraySegment<byte> RpcLinks;
public ArraySegment<byte> SyncValues;
/// <summary>
/// True if spawning.
/// </summary>
public ActionType Action { get; private set; }
/// <summary>
/// Cached NetworkObject.
/// </summary>
#pragma warning disable 0649
public NetworkObject NetworkObject;
/// <summary>
/// Reader containing rpc links for the network object.
/// </summary>
public PooledReader RpcLinkReader { get; private set; }
/// <summary>
/// Reader containing sync values for the network object.
/// </summary>
public PooledReader SyncValuesReader { get; private set; }
#pragma warning restore 0649
public void InitializeSpawn(NetworkManager manager, ushort collectionId, int objectId, sbyte initializeOrder, int ownerId, SpawnType objectSpawnType, byte componentIndex, int rootObjectId, int? parentObjectId, byte? parentComponentIndex
, int? prefabId, Vector3? localPosition, Quaternion? localRotation, Vector3? localScale, ulong sceneId, ArraySegment<byte> rpcLinks, ArraySegment<byte> syncValues)
{
ResetValues();
Action = ActionType.Spawn;
CollectionId = collectionId;
ObjectId = objectId;
InitializeOrder = initializeOrder;
OwnerId = ownerId;
SpawnType = objectSpawnType;
ComponentIndex = componentIndex;
RootObjectId = rootObjectId;
ParentObjectId = parentObjectId;
ParentComponentIndex = parentComponentIndex;
PrefabId = prefabId;
LocalPosition = localPosition;
LocalRotation = localRotation;
LocalScale = localScale;
SceneId = sceneId;
RpcLinks = rpcLinks;
SyncValues = syncValues;
RpcLinkReader = ReaderPool.GetReader(rpcLinks, manager);
SyncValuesReader = ReaderPool.GetReader(syncValues, manager);
}
/// <summary>
/// Initializes for a despawned NetworkObject.
/// </summary>
/// <param name="nob"></param>
public void InitializeDespawn(int objectId, DespawnType despawnType)
{
ResetValues();
Action = ActionType.Despawn;
DespawnType = despawnType;
ObjectId = objectId;
}
/// <summary>
/// Resets values which could malform identify the cached object.
/// </summary>
private void ResetValues()
{
NetworkObject = null;
}
~CachedNetworkObject()
{
if (RpcLinkReader != null)
RpcLinkReader.Dispose();
if (SyncValuesReader != null)
SyncValuesReader.Dispose();
}
}
}