mirror of
https://github.com/Dadechin/Unity-WebSocket.git
synced 2025-07-04 04:14:34 +00:00
507 lines
20 KiB
C#
507 lines
20 KiB
C#
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
|
#define DEVELOPMENT
|
|
#endif
|
|
using FishNet.Component.Observing;
|
|
using FishNet.Connection;
|
|
using FishNet.Managing.Logging;
|
|
using FishNet.Managing.Utility;
|
|
using FishNet.Object;
|
|
using FishNet.Serializing;
|
|
using FishNet.Transporting;
|
|
using FishNet.Utility.Extension;
|
|
using GameKit.Dependencies.Utilities;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Security.Cryptography;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
|
|
namespace FishNet.Managing.Object
|
|
{
|
|
public abstract partial class ManagedObjects
|
|
{
|
|
#region Public.
|
|
/// <summary>
|
|
/// NetworkObjects which are currently active.
|
|
/// </summary>
|
|
public Dictionary<int, NetworkObject> Spawned = new();
|
|
#endregion
|
|
|
|
#region Protected.
|
|
/// <summary>
|
|
/// Returns the next ObjectId to use.
|
|
/// </summary>
|
|
protected internal virtual bool GetNextNetworkObjectId(out int nextNetworkObjectId)
|
|
{
|
|
nextNetworkObjectId = NetworkObject.UNSET_OBJECTID_VALUE;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// NetworkManager handling this.
|
|
/// </summary>
|
|
protected NetworkManager NetworkManager { get; private set; }
|
|
/// <summary>
|
|
/// Objects in currently loaded scenes. These objects can be active or inactive.
|
|
/// Key is the objectId while value is the object. Key is not the same as NetworkObject.ObjectId.
|
|
/// </summary>
|
|
protected Dictionary<ulong, NetworkObject> SceneObjects_Internal = new();
|
|
/// <summary>
|
|
/// Objects in currently loaded scenes. These objects can be active or inactive.
|
|
/// Key is the objectId while value is the object. Key is not the same as NetworkObject.ObjectId.
|
|
/// </summary>
|
|
public IReadOnlyDictionary<ulong, NetworkObject> SceneObjects => SceneObjects_Internal;
|
|
#endregion
|
|
|
|
#region Private.
|
|
/// <summary>
|
|
/// Cached HashGrid. Will be null if not used.
|
|
/// </summary>
|
|
private HashGrid _hashGrid;
|
|
#endregion
|
|
|
|
protected virtual void Initialize(NetworkManager manager)
|
|
{
|
|
NetworkManager = manager;
|
|
manager.TryGetInstance(out _hashGrid);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribes to SceneManager.SceneLoaded event.
|
|
/// </summary>
|
|
/// <param name="subscribe"></param>
|
|
internal void SubscribeToSceneLoaded(bool subscribe)
|
|
{
|
|
if (subscribe)
|
|
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
|
|
else
|
|
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when a scene is loaded.
|
|
/// </summary>
|
|
/// <param name="s"></param>
|
|
/// <param name="arg1"></param>
|
|
protected internal virtual void SceneManager_sceneLoaded(Scene s, LoadSceneMode arg1) { }
|
|
|
|
/// <summary>
|
|
/// Called when a NetworkObject runs Deactivate.
|
|
/// </summary>
|
|
/// <param name="nob"></param>
|
|
internal virtual void NetworkObjectDestroyed(NetworkObject nob, bool asServer)
|
|
{
|
|
if (nob == null)
|
|
return;
|
|
|
|
RemoveFromSpawned(nob, fromOnDestroy: true, asServer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a NetworkedObject from spawned.
|
|
/// </summary>
|
|
protected virtual void RemoveFromSpawned(NetworkObject nob, bool fromOnDestroy, bool asServer)
|
|
{
|
|
Spawned.Remove(nob.ObjectId);
|
|
//Do the same with SceneObjects.
|
|
if (fromOnDestroy && nob.IsSceneObject)
|
|
RemoveFromSceneObjects(nob);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Despawns a NetworkObject.
|
|
/// </summary>
|
|
internal virtual void Despawn(NetworkObject nob, DespawnType despawnType, bool asServer)
|
|
{
|
|
if (nob == null)
|
|
{
|
|
NetworkManager.LogWarning($"Cannot despawn a null NetworkObject.");
|
|
return;
|
|
}
|
|
|
|
/* If not asServer and the object is not initialized on client
|
|
* then it likely is already despawned. This bit of code should
|
|
* never be reached as checks should be placed before-hand. */
|
|
if (!asServer && !nob.IsClientInitialized)
|
|
{
|
|
NetworkManager.LogError($"Object {nob.ToString()} is already despawned. Please report this error.");
|
|
return;
|
|
}
|
|
|
|
|
|
//True if should be destroyed, false if deactivated.
|
|
bool destroy = false;
|
|
bool wasRemovedFromPending = false;
|
|
|
|
/* Only modify object state if asServer,
|
|
* or !asServer and not host. This is so clients, when acting as
|
|
* host, don't destroy objects they lost observation of. */
|
|
|
|
/* Nested prefabs can never be destroyed. Only check to
|
|
* destroy if not nested. By nested prefab, this means the object
|
|
* despawning is part of another prefab that is also a spawned
|
|
* network object. */
|
|
if (!nob.IsNested)
|
|
{
|
|
//If as server.
|
|
if (asServer)
|
|
{
|
|
//Scene object.
|
|
if (!nob.IsSceneObject)
|
|
{
|
|
/* If client-host has visibility
|
|
* then disable and wait for client-host to get destroy
|
|
* message. Otherwise destroy immediately. */
|
|
if (nob.Observers.Contains(NetworkManager.ClientManager.Connection))
|
|
NetworkManager.ServerManager.Objects.AddToPending(nob);
|
|
else
|
|
destroy = true;
|
|
}
|
|
}
|
|
//Not as server.
|
|
else
|
|
{
|
|
bool isServer = NetworkManager.IsServerStarted;
|
|
//Only check to destroy if not a scene object.
|
|
if (!nob.IsSceneObject)
|
|
{
|
|
/* If was removed from pending then also destroy.
|
|
* Pending objects are ones that exist on the server
|
|
* side only to await destruction from client side.
|
|
* Objects can also be destroyed if server is not
|
|
* active. */
|
|
wasRemovedFromPending = NetworkManager.ServerManager.Objects.RemoveFromPending(nob);
|
|
destroy = (!isServer || wasRemovedFromPending);
|
|
}
|
|
}
|
|
}
|
|
|
|
TryUnsetParent();
|
|
/* If this had a parent set at runtime then
|
|
* unset parent before checks are completed.
|
|
* If we did not do this then this nob would
|
|
* just be disabled beneath its runtime parent,
|
|
* when it should be pooled separately or destroyed. */
|
|
void TryUnsetParent()
|
|
{
|
|
if (!asServer || wasRemovedFromPending)
|
|
{
|
|
if (nob.RuntimeParentNetworkBehaviour != null)
|
|
{
|
|
nob.UnsetParent();
|
|
/* DespawnType also has to be updated to use default
|
|
* for the networkObject since this despawn is happening
|
|
* automatically. */
|
|
despawnType = nob.GetDefaultDespawnType();
|
|
}
|
|
}
|
|
}
|
|
|
|
nob.SetIsDestroying(despawnType);
|
|
//Deinitialize to invoke callbacks.
|
|
nob.Deinitialize(asServer);
|
|
//Remove from match condition only if server.
|
|
if (asServer)
|
|
MatchCondition.RemoveFromMatchWithoutRebuild(nob, NetworkManager);
|
|
RemoveFromSpawned(nob, false, asServer);
|
|
|
|
//If to destroy.
|
|
if (destroy)
|
|
{
|
|
if (despawnType == DespawnType.Destroy)
|
|
UnityEngine.Object.Destroy(nob.gameObject);
|
|
else
|
|
NetworkManager.StorePooledInstantiated(nob, asServer);
|
|
}
|
|
/* If to potentially disable instead of destroy.
|
|
* This is such as something is despawning server side
|
|
* but a clientHost is present, or if a scene object. */
|
|
else
|
|
{
|
|
//If as server.
|
|
if (asServer)
|
|
{
|
|
/* If not clientHost the object can be disabled.
|
|
*
|
|
* Also, if clientHost and clientHost is not an observer, the object
|
|
* can be disabled. */
|
|
//If not clientHost then the object can be disabled.
|
|
if (!NetworkManager.IsClientStarted || !nob.Observers.Contains(NetworkManager.ClientManager.Connection))
|
|
nob.gameObject.SetActive(false);
|
|
}
|
|
//Not as server.
|
|
else
|
|
{
|
|
//If the server is not active then the object can be disabled.
|
|
if (!NetworkManager.IsServerStarted)
|
|
{
|
|
nob.gameObject.SetActive(false);
|
|
}
|
|
//If also server then checks must be done.
|
|
else
|
|
{
|
|
/* Object is still spawned on the server side. This means
|
|
* the clientHost likely lost visibility. When this is the case
|
|
* update clientHost renderers. */
|
|
if (NetworkManager.ServerManager.Objects.Spawned.ContainsKey(nob.ObjectId))
|
|
nob.SetRenderersVisible(false);
|
|
/* No longer spawned on the server, can
|
|
* deactivate on the client. */
|
|
else
|
|
nob.gameObject.SetActive(false);
|
|
}
|
|
}
|
|
|
|
/* Also despawn child objects.
|
|
* This only must be done when not destroying
|
|
* as destroying would result in the despawn being
|
|
* forced.
|
|
*
|
|
* Only run if asServer as well. The server will send
|
|
* individual despawns for each child. */
|
|
if (asServer)
|
|
{
|
|
List<NetworkObject> childNobs = nob.GetNetworkObjects(GetNetworkObjectOption.AllNested);
|
|
foreach (NetworkObject childNob in childNobs)
|
|
{
|
|
if (childNob != null && !childNob.IsDeinitializing)
|
|
Despawn(childNob, despawnType, asServer: true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a prefab, not to be mistaken for initializing a spawned object.
|
|
/// </summary>
|
|
/// <param name="prefab">Prefab to initialize.</param>
|
|
/// <param name="index">Index within spawnable prefabs.</param>
|
|
public static void InitializePrefab(NetworkObject prefab, int index, ushort? collectionId = null)
|
|
{
|
|
const int invalidIndex = -1;
|
|
if (index == invalidIndex)
|
|
{
|
|
Debug.LogError($"An index of {invalidIndex} cannot be assigned as a PrefabId for {prefab.name}.");
|
|
return;
|
|
}
|
|
|
|
if (prefab == null)
|
|
return;
|
|
|
|
prefab.PrefabId = (ushort)index;
|
|
if (collectionId != null)
|
|
prefab.SpawnableCollectionId = collectionId.Value;
|
|
|
|
prefab.SetInitializedValues(null, force: true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Despawns Spawned NetworkObjects. Scene objects will be disabled, others will be destroyed.
|
|
/// </summary>
|
|
internal virtual void DespawnWithoutSynchronization(bool recursive, bool asServer)
|
|
{
|
|
foreach (NetworkObject nob in Spawned.Values)
|
|
{
|
|
if (nob == null)
|
|
continue;
|
|
|
|
DespawnWithoutSynchronization(nob, recursive, asServer, nob.GetDefaultDespawnType(), removeFromSpawned: false);
|
|
}
|
|
|
|
Spawned.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Despawns a network object.
|
|
/// </summary>
|
|
/// <param name="nob"></param>
|
|
protected virtual void DespawnWithoutSynchronization(NetworkObject nob, bool recursive, bool asServer, DespawnType despawnType, bool removeFromSpawned)
|
|
{
|
|
#if FISHNET_STABLE_RECURSIVE_DESPAWNS
|
|
recursive = false;
|
|
#endif
|
|
|
|
GetNetworkObjectOption getOption = (recursive) ? GetNetworkObjectOption.All : GetNetworkObjectOption.Self;
|
|
List<NetworkObject> allNobs = nob.GetNetworkObjects(getOption);
|
|
|
|
//True if can deactivate or destroy.
|
|
bool canCleanup = (asServer || !NetworkManager.IsServerStarted);
|
|
|
|
foreach (NetworkObject lNob in allNobs)
|
|
{
|
|
lNob.SetIsDestroying(despawnType);
|
|
lNob.Deinitialize(asServer);
|
|
|
|
if (canCleanup && removeFromSpawned)
|
|
RemoveFromSpawned(lNob, fromOnDestroy: false, asServer);
|
|
}
|
|
|
|
/* Only need to check the first nob. If it's stored, deactivated,
|
|
* or destroyed, the rest will follow. */
|
|
if (canCleanup)
|
|
{
|
|
NetworkObject firstNob = allNobs[0];
|
|
|
|
if (firstNob.IsSceneObject || firstNob.IsInitializedNested)
|
|
{
|
|
firstNob.gameObject.SetActive(value: false);
|
|
}
|
|
else
|
|
{
|
|
if (despawnType == DespawnType.Destroy)
|
|
UnityEngine.Object.Destroy(firstNob.gameObject);
|
|
else
|
|
NetworkManager.StorePooledInstantiated(firstNob, asServer);
|
|
}
|
|
}
|
|
|
|
CollectionCaches<NetworkObject>.Store(allNobs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a NetworkObject to Spawned.
|
|
/// </summary>
|
|
internal virtual void AddToSpawned(NetworkObject nob, bool asServer)
|
|
{
|
|
Spawned[nob.ObjectId] = nob;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a NetworkObject to SceneObjects.
|
|
/// </summary>
|
|
protected internal void AddToSceneObjects(NetworkObject nob)
|
|
{
|
|
SceneObjects_Internal[nob.SceneId] = nob;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a NetworkObject from SceneObjects.
|
|
/// </summary>
|
|
/// <param name="nob"></param>
|
|
protected internal void RemoveFromSceneObjects(NetworkObject nob)
|
|
{
|
|
SceneObjects_Internal.Remove(nob.SceneId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a NetworkObject from SceneObjects.
|
|
/// </summary>
|
|
/// <param name="nob"></param>
|
|
protected internal void RemoveFromSceneObjects(ulong sceneId)
|
|
{
|
|
SceneObjects_Internal.Remove(sceneId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds a NetworkObject within Spawned.
|
|
/// </summary>
|
|
/// <param name="objectId"></param>
|
|
/// <returns></returns>
|
|
protected internal NetworkObject GetSpawnedNetworkObject(int objectId)
|
|
{
|
|
NetworkObject r;
|
|
if (!Spawned.TryGetValueIL2CPP(objectId, out r))
|
|
NetworkManager.LogError($"Spawned NetworkObject not found for ObjectId {objectId}.");
|
|
|
|
return r;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to skip data length for a packet.
|
|
/// </summary>
|
|
/// <param name="packetId"></param>
|
|
/// <param name="reader"></param>
|
|
/// <param name="dataLength"></param>
|
|
protected internal void SkipDataLength(ushort packetId, PooledReader reader, int dataLength, int rpcLinkObjectId = -1)
|
|
{
|
|
/* -1 means length wasn't set, which would suggest a reliable packet.
|
|
* Object should never be missing for reliable packets since spawns
|
|
* and despawns are reliable in order. */
|
|
if (dataLength == (int)MissingObjectPacketLength.Reliable)
|
|
{
|
|
string msg;
|
|
bool isRpcLink = (packetId >= NetworkManager.StartingRpcLinkIndex);
|
|
if (isRpcLink)
|
|
{
|
|
msg = (rpcLinkObjectId == -1) ? $"RPCLink of Id {(PacketId)packetId} could not be found. Remaining data will be purged." : $"ObjectId {rpcLinkObjectId} for RPCLink {(PacketId)packetId} could not be found.";
|
|
}
|
|
else
|
|
{
|
|
msg = $"NetworkBehaviour could not be found for packetId {(PacketId)packetId}. Remaining data will be purged.";
|
|
}
|
|
|
|
/* Default logging for server is errors only. Use error on client and warning
|
|
* on servers to reduce chances of allocation attacks. */
|
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR || !UNITY_SERVER
|
|
NetworkManager.LogError(msg);
|
|
#else
|
|
NetworkManager.LogWarning(msg);
|
|
#endif
|
|
reader.Clear();
|
|
}
|
|
/* If length is known then is unreliable packet. It's possible
|
|
* this packetId arrived before or after the object was spawned/destroyed.
|
|
* Skip past the data for this packet and use rest in reader. With non-linked
|
|
* RPCs length is sent before object information. */
|
|
else if (dataLength >= 0)
|
|
{
|
|
reader.Skip(Math.Min(dataLength, reader.Remaining));
|
|
}
|
|
/* -2 indicates the length is very long. Don't even try saving
|
|
* the packet, user shouldn't be sending this much data over unreliable. */
|
|
else if (dataLength == (int)MissingObjectPacketLength.PurgeRemaiming)
|
|
{
|
|
reader.Clear();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a ReplicateRpc.
|
|
/// </summary>
|
|
internal void ParseReplicateRpc(PooledReader reader, NetworkConnection conn, Channel channel)
|
|
{
|
|
#if DEVELOPMENT
|
|
NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining, out string rpcInformation, out uint expectedReadAmount);
|
|
#endif
|
|
|
|
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
|
|
int dataLength = Packets.GetPacketLength((ushort)PacketId.ServerRpc, reader, channel);
|
|
if (nb != null && nb.IsSpawned)
|
|
nb.OnReplicateRpc(null, reader, conn, channel);
|
|
else
|
|
SkipDataLength((ushort)PacketId.ServerRpc, reader, dataLength);
|
|
|
|
#if DEVELOPMENT
|
|
NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: false, NetworkManager, reader, startReaderRemaining, rpcInformation, expectedReadAmount, channel);
|
|
#endif
|
|
}
|
|
|
|
#if DEVELOPMENT
|
|
/// <summary>
|
|
/// Checks to write a scene object's details into a writer.
|
|
/// </summary>
|
|
protected void CheckWriteSceneObjectDetails(NetworkObject nob, Writer w)
|
|
{
|
|
//Check to write additional information if a scene object.
|
|
if (NetworkManager.DebugManager.WriteSceneObjectDetails)
|
|
{
|
|
w.WriteString(nob.gameObject.scene.name);
|
|
w.WriteString(nob.gameObject.name);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks to read a scene object's details and populates values if read was successful.
|
|
/// </summary>
|
|
protected void CheckReadSceneObjectDetails(Reader r, ref string sceneName, ref string objectName)
|
|
{
|
|
if (NetworkManager.DebugManager.WriteSceneObjectDetails)
|
|
{
|
|
sceneName = r.ReadStringAllocated();
|
|
objectName = r.ReadStringAllocated();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
} |