Add StickGame Assets

This commit is contained in:
Dzejkobik007
2024-03-24 22:21:16 +01:00
parent 5a5812c0c7
commit 6c8b523d1f
6643 changed files with 596260 additions and 0 deletions

View File

@@ -0,0 +1,562 @@
using FishNet.Component.Observing;
using FishNet.Connection;
using FishNet.Managing.Object;
using FishNet.Managing.Timing;
using FishNet.Object;
using FishNet.Observing;
using FishNet.Serializing;
using FishNet.Transporting;
using FishNet.Utility;
using FishNet.Utility.Performance;
using GameKit.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Managing.Server
{
public partial class ServerObjects : ManagedObjects
{
#region Private.
/// <summary>
/// Cache filled with objects which observers are being updated.
/// This is primarily used to invoke events after all observers are updated, rather than as each is updated.
/// </summary>
private List<NetworkObject> _observerChangedObjectsCache = new List<NetworkObject>(100);
/// <summary>
/// NetworkObservers which require regularly iteration.
/// </summary>
private List<NetworkObject> _timedNetworkObservers = new List<NetworkObject>();
/// <summary>
/// Index in TimedNetworkObservers to start on next cycle.
/// </summary>
private int _nextTimedObserversIndex;
/// <summary>
/// Used to write spawns for everyone. This writer will exclude owner only information.
/// </summary>
private PooledWriter _writer = new PooledWriter();
#endregion
/// <summary>
/// Called when MonoBehaviours call Update.
/// </summary>
private void Observers_OnUpdate()
{
UpdateTimedObservers();
}
/// <summary>
/// Progressively updates NetworkObservers with timed conditions.
/// </summary>
private void UpdateTimedObservers()
{
if (!base.NetworkManager.IsServer)
return;
//No point in updating if the timemanager isn't going to tick this frame.
if (!base.NetworkManager.TimeManager.FrameTicked)
return;
int networkObserversCount = _timedNetworkObservers.Count;
if (networkObserversCount == 0)
return;
/* Try to iterate all timed observers every half a second.
* This value will increase as there's more observers or timed conditions. */
double timeMultiplier = 1d + (float)((base.NetworkManager.ServerManager.Clients.Count * 0.005d) + (_timedNetworkObservers.Count * 0.0005d));
double completionTime = (0.5d * timeMultiplier);
uint completionTicks = base.NetworkManager.TimeManager.TimeToTicks(completionTime, TickRounding.RoundUp);
/* Iterations will be the number of objects
* to iterate to be have completed all objects by
* the end of completionTicks. */
int iterations = Mathf.CeilToInt((float)networkObserversCount / (float)completionTicks);
if (iterations > _timedNetworkObservers.Count)
iterations = _timedNetworkObservers.Count;
List<NetworkConnection> connCache = RetrieveAuthenticatedConnections();
//Build nob cache.
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList();
for (int i = 0; i < iterations; i++)
{
if (_nextTimedObserversIndex >= _timedNetworkObservers.Count)
_nextTimedObserversIndex = 0;
nobCache.Add(_timedNetworkObservers[_nextTimedObserversIndex++]);
}
RebuildObservers(nobCache, connCache, true);
CollectionCaches<NetworkConnection>.Store(connCache);
CollectionCaches<NetworkObject>.Store(nobCache);
}
/// <summary>
/// Indicates that a networkObserver component should be updated regularly. This is done automatically.
/// </summary>
/// <param name="networkObject">NetworkObject to be updated.</param>
public void AddTimedNetworkObserver(NetworkObject networkObject)
{
_timedNetworkObservers.Add(networkObject);
}
/// <summary>
/// Indicates that a networkObserver component no longer needs to be updated regularly. This is done automatically.
/// </summary>
/// <param name="networkObject">NetworkObject to be updated.</param>
public void RemoveTimedNetworkObserver(NetworkObject networkObject)
{
_timedNetworkObservers.Remove(networkObject);
}
/// <summary>
/// Gets all NetworkConnections which are authenticated.
/// </summary>
/// <returns></returns>
private List<NetworkConnection> RetrieveAuthenticatedConnections()
{
List<NetworkConnection> cache = CollectionCaches<NetworkConnection>.RetrieveList();
foreach (NetworkConnection item in NetworkManager.ServerManager.Clients.Values)
{
if (item.Authenticated)
cache.Add(item);
}
return cache;
}
/// <summary>
/// Gets all spawned objects with root objects first.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private List<NetworkObject> RetrieveOrderedSpawnedObjects()
{
List<NetworkObject> cache = CollectionCaches<NetworkObject>.RetrieveList();
bool initializationOrderChanged = false;
//First order root objects.
foreach (NetworkObject item in Spawned.Values)
OrderRootByInitializationOrder(item, cache, ref initializationOrderChanged);
OrderNestedByInitializationOrder(cache);
return cache;
}
/// <summary>
/// Orders a NetworkObject into a cache based on it's initialization order.
/// Only non-nested NetworkObjects will be added.
/// </summary>
/// <param name="nob">NetworkObject to check.</param>
/// <param name="cache">Cache to sort into.</param>
/// <param name="initializationOrderChanged">Boolean to indicate if initialization order is specified for one or more objects.</param>
private void OrderRootByInitializationOrder(NetworkObject nob, List<NetworkObject> cache, ref bool initializationOrderChanged)
{
if (nob.IsNested)
return;
sbyte currentItemInitOrder = nob.GetInitializeOrder();
initializationOrderChanged |= (currentItemInitOrder != 0);
int count = cache.Count;
/* If initialize order has not changed or count is
* 0 then add normally. */
if (!initializationOrderChanged || count == 0)
{
cache.Add(nob);
}
else
{
/* If current item init order is larger or equal than
* the last entry in copy then add to the end.
* Otherwise check where to add from the beginning. */
if (currentItemInitOrder >= cache[count - 1].GetInitializeOrder())
{
cache.Add(nob);
}
else
{
for (int i = 0; i < count; i++)
{
/* If item being sorted is lower than the one in already added.
* then insert it before the one already added. */
if (currentItemInitOrder <= cache[i].GetInitializeOrder())
{
cache.Insert(i, nob);
break;
}
}
}
}
}
/// <summary>
/// Orders nested NetworkObjects of cache by initialization order.
/// </summary>
/// <param name="cache">Cache to sort.</param>
private void OrderNestedByInitializationOrder(List<NetworkObject> cache)
{
//After everything is sorted by root only insert children.
for (int i = 0; i < cache.Count; i++)
{
NetworkObject nob = cache[i];
//Skip root.
if (nob.IsNested)
continue;
int startingIndex = i;
AddChildNetworkObjects(nob, ref startingIndex);
}
void AddChildNetworkObjects(NetworkObject n, ref int index)
{
foreach (NetworkObject childObject in n.ChildNetworkObjects)
{
cache.Insert(++index, childObject);
AddChildNetworkObjects(childObject, ref index);
}
}
}
/// <summary>
/// Removes a connection from observers without synchronizing changes.
/// </summary>
/// <param name="connection"></param>
private void RemoveFromObserversWithoutSynchronization(NetworkConnection connection)
{
List<NetworkObject> observerChangedObjectsCache = _observerChangedObjectsCache;
foreach (NetworkObject nob in Spawned.Values)
{
if (nob.RemoveObserver(connection))
observerChangedObjectsCache.Add(nob);
}
//Invoke despawn callbacks on nobs.
for (int i = 0; i < observerChangedObjectsCache.Count; i++)
observerChangedObjectsCache[i].InvokeOnServerDespawn(connection);
observerChangedObjectsCache.Clear();
}
/// <summary>
/// Rebuilds observers on all NetworkObjects for all connections.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(bool timedOnly = false)
{
List<NetworkObject> nobCache = RetrieveOrderedSpawnedObjects();
List<NetworkConnection> connCache = RetrieveAuthenticatedConnections();
RebuildObservers(nobCache, connCache, timedOnly);
CollectionCaches<NetworkObject>.Store(nobCache);
CollectionCaches<NetworkConnection>.Store(connCache);
}
/// <summary>
/// Rebuilds observers for all connections for a NetworkObject.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(NetworkObject nob, bool timedOnly = false)
{
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList(nob);
List<NetworkConnection> connCache = RetrieveAuthenticatedConnections();
RebuildObservers(nobCache, connCache, timedOnly);
CollectionCaches<NetworkObject>.Store(nobCache);
CollectionCaches<NetworkConnection>.Store(connCache);
}
/// <summary>
/// Rebuilds observers on all NetworkObjects for a connection.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(NetworkConnection connection, bool timedOnly = false)
{
List<NetworkObject> nobCache = RetrieveOrderedSpawnedObjects();
List<NetworkConnection> connCache = CollectionCaches<NetworkConnection>.RetrieveList(connection);
RebuildObservers(nobCache, connCache, timedOnly);
CollectionCaches<NetworkObject>.Store(nobCache);
CollectionCaches<NetworkConnection>.Store(connCache);
}
#region Obsolete RebuildObservers.
/// <summary>
/// Rebuilds observers on NetworkObjects.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Use RebuildObservers IList variant instead.")]
public void RebuildObservers(IEnumerable<NetworkObject> nobs, bool timedOnly = false)
{
List<NetworkConnection> conns = RetrieveAuthenticatedConnections();
RebuildObservers(nobs, conns, timedOnly);
CollectionCaches<NetworkConnection>.Store(conns);
}
/// <summary>
/// Rebuilds observers on all objects for connections.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Use RebuildObservers IList variant instead.")]
public void RebuildObservers(IEnumerable<NetworkConnection> connections, bool timedOnly = false)
{
List<NetworkObject> nobCache = RetrieveOrderedSpawnedObjects();
RebuildObservers(nobCache, connections, timedOnly);
CollectionCaches<NetworkObject>.Store(nobCache);
}
/// <summary>
/// Rebuilds observers on NetworkObjects for connections.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Use RebuildObservers IList variant instead.")]
public void RebuildObservers(IEnumerable<NetworkObject> nobs, NetworkConnection conn, bool timedOnly = false)
{
List<NetworkConnection> connCache = CollectionCaches<NetworkConnection>.RetrieveList(conn);
RebuildObservers(nobs, connCache, timedOnly);
CollectionCaches<NetworkConnection>.Store(connCache);
}
/// <summary>
/// Rebuilds observers for connections on NetworkObject.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Use RebuildObservers IList variant instead.")]
public void RebuildObservers(NetworkObject networkObject, IEnumerable<NetworkConnection> connections, bool timedOnly = false)
{
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList(networkObject);
RebuildObservers(nobCache, connections, timedOnly);
CollectionCaches<NetworkObject>.Store(nobCache);
}
/// <summary>
/// Rebuilds observers on NetworkObjects for connections.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Use RebuildObservers IList variant instead.")]
public void RebuildObservers(IEnumerable<NetworkObject> nobs, IEnumerable<NetworkConnection> conns, bool timedOnly = false)
{
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList();
foreach (NetworkConnection nc in conns)
{
_writer.Reset();
nobCache.Clear();
foreach (NetworkObject nob in nobs)
RebuildObservers(nob, nc, nobCache, timedOnly);
//Send if change.
if (_writer.Length > 0)
{
NetworkManager.TransportManager.SendToClient(
(byte)Channel.Reliable, _writer.GetArraySegment(), nc);
foreach (NetworkObject n in nobCache)
n.OnSpawnServer(nc);
}
}
CollectionCaches<NetworkObject>.Store(nobCache);
}
#endregion
/// <summary>
/// Rebuilds observers on NetworkObjects.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(IList<NetworkObject> nobs, bool timedOnly = false)
{
List<NetworkConnection> conns = RetrieveAuthenticatedConnections();
RebuildObservers(nobs, conns, timedOnly);
CollectionCaches<NetworkConnection>.Store(conns);
}
/// <summary>
/// Rebuilds observers on all objects for connections.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(IList<NetworkConnection> connections, bool timedOnly = false)
{
List<NetworkObject> nobCache = RetrieveOrderedSpawnedObjects();
RebuildObservers(nobCache, connections, timedOnly);
CollectionCaches<NetworkObject>.Store(nobCache);
}
/// <summary>
/// Rebuilds observers on NetworkObjects for connections.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(IList<NetworkObject> nobs, NetworkConnection conn, bool timedOnly = false)
{
List<NetworkConnection> connCache = CollectionCaches<NetworkConnection>.RetrieveList(conn);
RebuildObservers(nobs, connCache, timedOnly);
CollectionCaches<NetworkConnection>.Store(connCache);
}
/// <summary>
/// Rebuilds observers for connections on NetworkObject.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(NetworkObject networkObject, IList<NetworkConnection> connections, bool timedOnly = false)
{
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList(networkObject);
RebuildObservers(nobCache, connections, timedOnly);
CollectionCaches<NetworkObject>.Store(nobCache);
}
/// <summary>
/// Rebuilds observers on NetworkObjects for connections.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(IList<NetworkObject> nobs, IList<NetworkConnection> conns, bool timedOnly = false)
{
List<NetworkObject> nobCache = CollectionCaches<NetworkObject>.RetrieveList();
NetworkConnection nc;
int connsCount = conns.Count;
for (int i = 0; i < connsCount; i++)
{
_writer.Reset();
nobCache.Clear();
nc = conns[i];
int nobsCount = nobs.Count;
for (int z = 0; z < nobsCount; z++)
RebuildObservers(nobs[z], nc, nobCache, timedOnly);
//Send if change.
if (_writer.Length > 0)
{
NetworkManager.TransportManager.SendToClient(
(byte)Channel.Reliable, _writer.GetArraySegment(), nc);
foreach (NetworkObject n in nobCache)
n.OnSpawnServer(nc);
}
}
CollectionCaches<NetworkObject>.Store(nobCache);
}
/// <summary>
/// Rebuilds observers for a connection on NetworkObject.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RebuildObservers(NetworkObject nob, NetworkConnection conn, bool timedOnly = false)
{
if (ApplicationState.IsQuitting())
return;
_writer.Reset();
/* When not using a timed rebuild such as this connections must have
* hashgrid data rebuilt immediately. */
// if (!timedOnly)
conn.UpdateHashGridPositions(!timedOnly);
//If observer state changed then write changes.
ObserverStateChange osc = nob.RebuildObservers(conn, timedOnly);
if (osc == ObserverStateChange.Added)
{
base.WriteSpawn_Server(nob, conn, _writer);
}
else if (osc == ObserverStateChange.Removed)
{
if (conn.LevelOfDetails.TryGetValue(nob, out NetworkConnection.LevelOfDetailData lodData))
ObjectCaches<NetworkConnection.LevelOfDetailData>.Store(lodData);
conn.LevelOfDetails.Remove(nob);
WriteDespawn(nob, nob.GetDefaultDespawnType(), _writer);
}
else
{
return;
}
NetworkManager.TransportManager.SendToClient(
(byte)Channel.Reliable,
_writer.GetArraySegment(), conn);
/* If spawning then also invoke server
* start events, such as buffer last
* and onspawnserver. */
if (osc == ObserverStateChange.Added)
nob.OnSpawnServer(conn);
/* If there is change then also rebuild on any runtime children.
* This is to ensure runtime children have visibility updated
* in relation to parent.
*
* If here there is change. */
foreach (NetworkObject item in nob.RuntimeChildNetworkObjects)
RebuildObservers(item, conn, timedOnly);
}
/// <summary>
/// Rebuilds observers for a connection on NetworkObject.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void RebuildObservers(NetworkObject nob, NetworkConnection conn, List<NetworkObject> addedNobs, bool timedOnly = false)
{
if (ApplicationState.IsQuitting())
return;
/* When not using a timed rebuild such as this connections must have
* hashgrid data rebuilt immediately. */
//if (!timedOnly)
//conn.UpdateHashGridPositions(true);
conn.UpdateHashGridPositions(!timedOnly);
//If observer state changed then write changes.
ObserverStateChange osc = nob.RebuildObservers(conn, timedOnly);
if (osc == ObserverStateChange.Added)
{
base.WriteSpawn_Server(nob, conn, _writer);
addedNobs.Add(nob);
}
else if (osc == ObserverStateChange.Removed)
{
if (conn.LevelOfDetails.TryGetValue(nob, out NetworkConnection.LevelOfDetailData lodData))
ObjectCaches<NetworkConnection.LevelOfDetailData>.Store(lodData);
conn.LevelOfDetails.Remove(nob);
WriteDespawn(nob, nob.GetDefaultDespawnType(), _writer);
}
else
{
return;
}
/* If there is change then also rebuild on any runtime children.
* This is to ensure runtime children have visibility updated
* in relation to parent.
*
* If here there is change. */
foreach (NetworkObject item in nob.RuntimeChildNetworkObjects)
RebuildObservers(item, conn, addedNobs, timedOnly);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02d93fa4a653dd64da0bb338b82f4740
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,30 @@
using FishNet.Connection;
using FishNet.Managing.Object;
using FishNet.Managing.Utility;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Transporting;
using System.Runtime.CompilerServices;
namespace FishNet.Managing.Server
{
public partial class ServerObjects : ManagedObjects
{
/// <summary>
/// Parses a ServerRpc.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ParseServerRpc(PooledReader reader, NetworkConnection conn, Channel channel)
{
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
int dataLength = Packets.GetPacketLength((ushort)PacketId.ServerRpc, reader, channel);
if (nb != null)
nb.OnServerRpc(reader, conn, channel);
else
SkipDataLength((ushort)PacketId.ServerRpc, reader, dataLength);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3cb6cef520a4ff44bb8c4814e566c5ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,918 @@
#if UNITY_EDITOR || DEVELOPMENT_BUILD
#define DEVELOPMENT
#endif
using FishNet.Connection;
using FishNet.Managing.Logging;
using FishNet.Managing.Object;
using FishNet.Managing.Timing;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Transporting;
using FishNet.Utility;
using FishNet.Utility.Extension;
using FishNet.Utility.Performance;
using GameKit.Utilities;
using GameKit.Utilities.Types;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace FishNet.Managing.Server
{
/// <summary>
/// Handles objects and information about objects for the server. See ManagedObjects for inherited options.
/// </summary>
public partial class ServerObjects : ManagedObjects
{
#region Public.
/// <summary>
/// Called right before client objects are destroyed when a client disconnects.
/// </summary>
public event Action<NetworkConnection> OnPreDestroyClientObjects;
#endregion
#region Internal.
/// <summary>
/// Collection of NetworkObjects recently despawned.
/// Key: objectId.
/// Value: despawn tick.
/// This is used primarily to track if client is sending messages for recently despawned objects.
/// Objects are automatically removed after RECENTLY_DESPAWNED_DURATION seconds.
/// </summary>
internal Dictionary<int, uint> RecentlyDespawnedIds = new Dictionary<int, uint>();
#endregion
#region Private.
/// <summary>
/// Cached ObjectIds which may be used when exceeding available ObjectIds.
/// </summary>
private Queue<int> _objectIdCache = new Queue<int>();
/// <summary>
/// Returns the ObjectId cache.
/// </summary>
/// <returns></returns>
internal Queue<int> GetObjectIdCache() => _objectIdCache;
/// <summary>
/// NetworkBehaviours which have dirty SyncVars.
/// </summary>
private List<NetworkBehaviour> _dirtySyncVarBehaviours = new List<NetworkBehaviour>(20);
/// <summary>
/// NetworkBehaviours which have dirty SyncObjects.
/// </summary>
private List<NetworkBehaviour> _dirtySyncObjectBehaviours = new List<NetworkBehaviour>(20);
/// <summary>
/// Objects which need to be destroyed next tick.
/// This is needed when running as host so host client will get any final messages for the object before they're destroyed.
/// </summary>
private Dictionary<int, NetworkObject> _pendingDestroy = new Dictionary<int, NetworkObject>();
/// <summary>
/// Scenes which were loaded that need to be setup.
/// </summary>
private List<(int, Scene)> _loadedScenes = new List<(int frame, Scene scene)>();
/// <summary>
/// Cache of spawning objects, used for recursively spawning nested NetworkObjects.
/// </summary>
private List<NetworkObject> _spawnCache = new List<NetworkObject>();
/// <summary>
/// True if one or more scenes are currently loading through the SceneManager.
/// </summary>
private bool _scenesLoading;
/// <summary>
/// Number of ticks which must pass to clear a recently despawned.
/// </summary>
private uint _cleanRecentlyDespawnedMaxTicks => base.NetworkManager.TimeManager.TimeToTicks(30d, TickRounding.RoundUp);
#endregion
internal ServerObjects(NetworkManager networkManager)
{
base.Initialize(networkManager);
networkManager.SceneManager.OnLoadStart += SceneManager_OnLoadStart;
networkManager.SceneManager.OnActiveSceneSetInternal += SceneManager_OnActiveSceneSet;
networkManager.TimeManager.OnUpdate += TimeManager_OnUpdate;
}
/// <summary>
/// Called when MonoBehaviours call Update.
/// </summary>
private void TimeManager_OnUpdate()
{
if (!base.NetworkManager.IsServer)
{
_scenesLoading = false;
_loadedScenes.Clear();
return;
}
CleanRecentlyDespawned();
if (!_scenesLoading)
IterateLoadedScenes(false);
Observers_OnUpdate();
}
#region Checking dirty SyncTypes.
/// <summary>
/// Iterates NetworkBehaviours with dirty SyncTypes.
/// </summary>
internal void WriteDirtySyncTypes()
{
/* Tells networkbehaviours to check their
* dirty synctypes. */
IterateCollection(_dirtySyncVarBehaviours, false);
IterateCollection(_dirtySyncObjectBehaviours, true);
void IterateCollection(List<NetworkBehaviour> collection, bool isSyncObject)
{
for (int i = 0; i < collection.Count; i++)
{
bool dirtyCleared = collection[i].WriteDirtySyncTypes(isSyncObject);
if (dirtyCleared)
{
collection.RemoveAt(i);
i--;
}
}
}
}
/// <summary>
/// Sets that a NetworkBehaviour has a dirty syncVars.
/// </summary>
/// <param name="nb"></param>
internal void SetDirtySyncType(NetworkBehaviour nb, bool isSyncObject)
{
if (isSyncObject)
_dirtySyncObjectBehaviours.Add(nb);
else
_dirtySyncVarBehaviours.Add(nb);
}
#endregion
#region Connection Handling.
/// <summary>
/// Called when the connection state changes for the local server.
/// </summary>
/// <param name="args"></param>
internal void OnServerConnectionState(ServerConnectionStateArgs args)
{
//If server just connected.
if (args.ConnectionState == LocalConnectionState.Started)
{
/* If there's no servers started besides the one
* that just started then build Ids and setup scene objects. */
if (base.NetworkManager.ServerManager.OneServerStarted())
{
BuildObjectIdCache();
SetupSceneObjects();
}
}
//Server in anything but started state.
else
{
//If no servers are started then reset.
if (!base.NetworkManager.ServerManager.AnyServerStarted())
{
base.DespawnWithoutSynchronization(true);
base.SceneObjects_Internal.Clear();
_objectIdCache.Clear();
base.NetworkManager.ClearClientsCollection(base.NetworkManager.ServerManager.Clients);
}
//If at least one server is started then only clear for disconnecting server.
else
{
//Remove connections only for transportIndex.
base.NetworkManager.ClearClientsCollection(base.NetworkManager.ServerManager.Clients, args.TransportIndex);
}
}
}
/// <summary>
/// Called when a client disconnects.
/// </summary>
/// <param name="connection"></param>
internal void ClientDisconnected(NetworkConnection connection)
{
RemoveFromObserversWithoutSynchronization(connection);
OnPreDestroyClientObjects?.Invoke(connection);
/* A cache is made because the Objects
* collection would end up modified during
* iteration from removing ownership and despawning. */
List<NetworkObject> nobs = CollectionCaches<NetworkObject>.RetrieveList();
foreach (NetworkObject nob in connection.Objects)
nobs.Add(nob);
int nobsCount = nobs.Count;
for (int i = 0; i < nobsCount; i++)
{
/* Objects may already be deinitializing when a client disconnects
* because the root object could have been despawned first, and in result
* all child objects would have been recursively despawned.
*
* EG: object is:
* A (nob)
* B (nob)
*
* Both A and B are owned by the client so they will both be
* in collection. Should A despawn first B will recursively despawn
* from it. Then once that finishes and the next index of collection
* is run, which would B, the object B would have already been deinitialized. */
if (!nobs[i].IsDeinitializing)
base.NetworkManager.ServerManager.Despawn(nobs[i]);
}
CollectionCaches<NetworkObject>.Store(nobs);
}
#endregion
#region ObjectIds.
/// <summary>
/// Builds the ObjectId cache with all possible Ids.
/// </summary>
private void BuildObjectIdCache()
{
_objectIdCache.Clear();
/* Shuffle Ids to make it more difficult
* for clients to track spawned object
* count. */
List<int> shuffledCache = new List<int>();
//Ignore ushort.maxvalue as that indicates null.
for (int i = 0; i < (ushort.MaxValue - 1); i++)
shuffledCache.Add(i);
/* Only shuffle when NOT in editor and not
* development build.
* Debugging could be easier when Ids are ordered. */
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
shuffledCache.Shuffle();
#endif
//Add shuffled to objectIdCache.
//Build Id cache.
int cacheCount = shuffledCache.Count;
for (int i = 0; i < cacheCount; i++)
_objectIdCache.Enqueue(shuffledCache[i]);
}
/// <summary>
/// Caches a NetworkObject ObjectId.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CacheObjectId(NetworkObject nob)
{
if (nob.ObjectId != NetworkObject.UNSET_OBJECTID_VALUE)
CacheObjectId(nob.ObjectId);
}
/// <summary>
/// Adds an ObjectId to objectId cache.
/// </summary>
/// <param name="id"></param>
internal void CacheObjectId(int id)
{
_objectIdCache.Enqueue(id);
}
/// <summary>
/// Gets the next ObjectId to use for NetworkObjects.
/// </summary>
/// <returns></returns>
protected internal override int GetNextNetworkObjectId(bool errorCheck = true)
{
//Either something went wrong or user actually managed to spawn ~64K networked objects.
if (_objectIdCache.Count == 0)
{
base.NetworkManager.LogError($"No more available ObjectIds. How the heck did you manage to have {ushort.MaxValue} objects spawned at once?");
return -1;
}
else
{
return _objectIdCache.Dequeue();
}
}
#endregion
#region Initializing Objects In Scenes.
/// <summary>
/// Called when a scene load starts.
/// </summary>
private void SceneManager_OnLoadStart(Scened.SceneLoadStartEventArgs obj)
{
_scenesLoading = true;
}
/// <summary>
/// Called after the active scene has been scene, immediately after scene loads.
/// </summary>
private void SceneManager_OnActiveSceneSet()
{
_scenesLoading = false;
IterateLoadedScenes(true);
}
/// <summary>
/// Iterates loaded scenes and sets them up.
/// </summary>
/// <param name="ignoreFrameRestriction">True to ignore the frame restriction when iterating.</param>
internal void IterateLoadedScenes(bool ignoreFrameRestriction)
{
//Not started, clear loaded scenes.
if (!NetworkManager.ServerManager.Started)
_loadedScenes.Clear();
for (int i = 0; i < _loadedScenes.Count; i++)
{
(int frame, Scene scene) value = _loadedScenes[i];
if (ignoreFrameRestriction || (Time.frameCount > value.frame))
{
SetupSceneObjects(value.scene);
_loadedScenes.RemoveAt(i);
i--;
}
}
}
/// <summary>
/// Called when a scene loads on the server.
/// </summary>
/// <param name="s"></param>
/// <param name="arg1"></param>
protected internal override void SceneManager_sceneLoaded(Scene s, LoadSceneMode arg1)
{
base.SceneManager_sceneLoaded(s, arg1);
if (!NetworkManager.ServerManager.Started)
return;
//Add to loaded scenes so that they are setup next frame.
_loadedScenes.Add((Time.frameCount, s));
}
/// <summary>
/// Setup all NetworkObjects in scenes. Should only be called when server is active.
/// </summary>
protected internal void SetupSceneObjects()
{
for (int i = 0; i < SceneManager.sceneCount; i++)
SetupSceneObjects(SceneManager.GetSceneAt(i));
Scene ddolScene = DDOL.GetDDOL().gameObject.scene;
if (ddolScene.isLoaded)
SetupSceneObjects(ddolScene);
}
/// <summary>
/// Setup NetworkObjects in a scene. Should only be called when server is active.
/// </summary>
/// <param name="s"></param>
private void SetupSceneObjects(Scene s)
{
if (!s.IsValid())
return;
List<NetworkObject> sceneNobs = CollectionCaches<NetworkObject>.RetrieveList();
Scenes.GetSceneNetworkObjects(s, false, ref sceneNobs);
//Sort the nobs based on initialization order.
bool initializationOrderChanged = false;
List<NetworkObject> cache = CollectionCaches<NetworkObject>.RetrieveList();
foreach (NetworkObject item in sceneNobs)
OrderRootByInitializationOrder(item, cache, ref initializationOrderChanged);
OrderNestedByInitializationOrder(cache);
//Store sceneNobs.
CollectionCaches<NetworkObject>.Store(sceneNobs);
bool isHost = base.NetworkManager.IsHost;
int nobsCount = cache.Count;
for (int i = 0; i < nobsCount; i++)
{
NetworkObject nob = cache[i];
//Only setup if a scene object and not initialzied.
if (nob.IsNetworked && nob.IsSceneObject && nob.IsDeinitializing)
{
base.UpdateNetworkBehavioursForSceneObject(nob, true);
base.AddToSceneObjects(nob);
/* If was active in the editor (before hitting play), or currently active
* then PreInitialize without synchronizing to clients. There is no reason
* to synchronize to clients because the scene just loaded on server,
* which means clients are not yet in the scene. */
if (nob.ActiveDuringEdit || nob.gameObject.activeInHierarchy)
{
//If not host then object doesn't need to be spawned until a client joins.
if (!isHost)
SetupWithoutSynchronization(nob);
//Otherwise spawn object so observers update for clientHost.
else
SpawnWithoutChecks(nob);
}
}
}
CollectionCaches<NetworkObject>.Store(cache);
}
/// <summary>
/// Performs setup on a NetworkObject without synchronizing the actions to clients.
/// </summary>
/// <param name="objectId">Override ObjectId to use.</param>
private void SetupWithoutSynchronization(NetworkObject nob, NetworkConnection ownerConnection = null, int? objectId = null)
{
if (nob.IsNetworked)
{
if (objectId == null)
objectId = GetNextNetworkObjectId();
nob.Preinitialize_Internal(NetworkManager, objectId.Value, ownerConnection, true);
base.AddToSpawned(nob, true);
nob.gameObject.SetActive(true);
nob.Initialize(true, true);
}
}
#endregion
#region Spawning.
/// <summary>
/// Spawns an object over the network.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Spawn(NetworkObject networkObject, NetworkConnection ownerConnection = null, UnityEngine.SceneManagement.Scene scene = default)
{
//Default as false, will change if needed.
bool predictedSpawn = false;
if (networkObject == null)
{
base.NetworkManager.LogError($"Specified networkObject is null.");
return;
}
if (!NetworkManager.ServerManager.Started)
{
//Neither server nor client are started.
if (!NetworkManager.ClientManager.Started)
{
base.NetworkManager.LogWarning("Cannot spawn object because server nor client are active.");
return;
}
//Server has predicted spawning disabled.
if (!NetworkManager.PredictionManager.GetAllowPredictedSpawning())
{
base.NetworkManager.LogWarning("Cannot spawn object because server is not active and predicted spawning is not enabled.");
return;
}
//Various predicted spawn checks.
if (!base.CanPredictedSpawn(networkObject, NetworkManager.ClientManager.Connection, ownerConnection, false))
return;
predictedSpawn = true;
}
if (!networkObject.gameObject.scene.IsValid())
{
base.NetworkManager.LogError($"{networkObject.name} is a prefab. You must instantiate the prefab first, then use Spawn on the instantiated copy.");
return;
}
if (ownerConnection != null && ownerConnection.IsActive && !ownerConnection.LoadedStartScenes(!predictedSpawn))
{
base.NetworkManager.LogWarning($"{networkObject.name} was spawned but it's recommended to not spawn objects for connections until they have loaded start scenes. You can be notified when a connection loads start scenes by using connection.OnLoadedStartScenes on the connection, or SceneManager.OnClientLoadStartScenes.");
}
if (networkObject.IsSpawned)
{
base.NetworkManager.LogWarning($"{networkObject.name} is already spawned.");
return;
}
if (networkObject.CurrentParentNetworkObject != null && !networkObject.CurrentParentNetworkObject.IsSpawned)
{
base.NetworkManager.LogError($"{networkObject.name} cannot be spawned because it has a parent NetworkObject {networkObject.CurrentParentNetworkObject} which is not spawned.");
return;
}
/* If scene is specified make sure the object is root,
* and if not move it before network spawning. */
if (scene.IsValid())
{
if (networkObject.transform.parent != null)
{
base.NetworkManager.LogError($"{networkObject.name} cannot be moved to scene name {scene.name}, handle {scene.handle} because {networkObject.name} is not root and only root objects may be moved.");
return;
}
else
{
UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene);
}
}
if (predictedSpawn)
base.NetworkManager.ClientManager.Objects.PredictedSpawn(networkObject, ownerConnection);
else
SpawnWithoutChecks(networkObject, ownerConnection);
}
/// <summary>
/// Spawns networkObject without any checks.
/// </summary>
private void SpawnWithoutChecks(NetworkObject networkObject, NetworkConnection ownerConnection = null, int? objectId = null)
{
/* Setup locally without sending to clients.
* When observers are built for the network object
* during initialization spawn messages will
* be sent. */
networkObject.SetIsNetworked(true);
_spawnCache.Add(networkObject);
SetupWithoutSynchronization(networkObject, ownerConnection, objectId);
foreach (NetworkObject item in networkObject.ChildNetworkObjects)
{
/* Only spawn recursively if the nob state is unset.
* Unset indicates that the nob has not been */
if (item.gameObject.activeInHierarchy || item.State == NetworkObjectState.Spawned)
SpawnWithoutChecks(item, ownerConnection);
}
/* Copy to a new cache then reset _spawnCache
* just incase rebuilding observers would lead to
* more additions into _spawnCache. EG: rebuilding
* may result in additional objects being spawned
* for clients and if _spawnCache were not reset
* the same objects would be rebuilt again. This likely
* would not affect anything other than perf but who
* wants that. */
List<NetworkObject> spawnCacheCopy = CollectionCaches<NetworkObject>.RetrieveList();
spawnCacheCopy.AddRange(_spawnCache);
_spawnCache.Clear();
//Also rebuild observers for the object so it spawns for others.
RebuildObservers(spawnCacheCopy);
int spawnCacheCopyCount = spawnCacheCopy.Count;
/* If also client then we need to make sure the object renderers have correct visibility.
* Set visibility based on if the observers contains the clientHost connection. */
if (NetworkManager.IsClient)
{
int count = spawnCacheCopyCount;
for (int i = 0; i < count; i++)
spawnCacheCopy[i].SetRenderersVisible(networkObject.Observers.Contains(NetworkManager.ClientManager.Connection));
}
CollectionCaches<NetworkObject>.Store(spawnCacheCopy);
}
/// <summary>
/// Reads a predicted spawn.
/// </summary>
internal void ReadPredictedSpawn(PooledReader reader, NetworkConnection conn)
{
sbyte initializeOrder;
ushort collectionId;
int prefabId;
int objectId = reader.ReadNetworkObjectForSpawn(out initializeOrder, out collectionId, out _);
//If objectId is not within predicted ids for conn.
if (!conn.PredictedObjectIds.Contains(objectId))
{
reader.Clear();
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {conn.ClientId} used predicted spawning with a non-reserved objectId of {objectId}.");
return;
}
NetworkConnection owner = reader.ReadNetworkConnection();
SpawnType st = (SpawnType)reader.ReadByte();
//Not used at the moment.
byte componentIndex = reader.ReadByte();
//Read transform values which differ from serialized values.
Vector3? localPosition;
Quaternion? localRotation;
Vector3? localScale;
base.ReadTransformProperties(reader, out localPosition, out localRotation, out localScale);
NetworkObject nob;
bool isGlobal = false;
if (SpawnTypeEnum.Contains(st, SpawnType.Scene))
{
ulong sceneId = reader.ReadUInt64(AutoPackType.Unpacked);
#if DEVELOPMENT
string sceneName = string.Empty;
string objectName = string.Empty;
CheckReadSceneObjectDetails(reader, ref sceneName, ref objectName);
nob = base.GetSceneNetworkObject(sceneId, sceneName, objectName);
#else
nob = base.GetSceneNetworkObject(sceneId);
#endif
if (!base.CanPredictedSpawn(nob, conn, owner, true))
return;
}
else
{
//Not used right now.
SpawnParentType spt = (SpawnParentType)reader.ReadByte();
prefabId = reader.ReadNetworkObjectId();
//Invalid prefabId.
if (prefabId == NetworkObject.UNSET_PREFABID_VALUE)
{
reader.Clear();
conn.Kick(KickReason.UnusualActivity, LoggingType.Common, $"Spawned object has an invalid prefabId of {prefabId}. Make sure all objects which are being spawned over the network are within SpawnableObjects on the NetworkManager. Connection {conn.ClientId} will be kicked immediately.");
return;
}
PrefabObjects prefabObjects = NetworkManager.GetPrefabObjects<PrefabObjects>(collectionId, false);
//PrefabObjects not found.
if (prefabObjects == null)
{
reader.Clear();
conn.Kick(KickReason.UnusualActivity, LoggingType.Common, $"PrefabObjects collection is not found for CollectionId {collectionId}. Be sure to add your addressables NetworkObject prefabs to the collection on server and client before attempting to spawn them over the network. Connection {conn.ClientId} will be kicked immediately.");
return;
}
//Check if prefab allows predicted spawning.
NetworkObject nPrefab = prefabObjects.GetObject(true, prefabId);
if (!base.CanPredictedSpawn(nPrefab, conn, owner, true))
return;
nob = NetworkManager.GetPooledInstantiated(prefabId, collectionId, false);
isGlobal = SpawnTypeEnum.Contains(st, SpawnType.InstantiatedGlobal);
}
Transform t = nob.transform;
//Parenting predicted spawns is not supported yet.
t.SetParent(null, true);
base.GetTransformProperties(localPosition, localRotation, localScale, t, out Vector3 pos, out Quaternion rot, out Vector3 scale);
t.SetLocalPositionRotationAndScale(pos, rot, scale);
nob.SetIsGlobal(isGlobal);
//Initialize for prediction.
nob.InitializePredictedObject_Server(base.NetworkManager, conn);
/* Only read sync types if allowed for the object.
* If the client did happen to send synctypes while not allowed
* this will create a parse error on the server,
* resulting in the client being kicked. */
if (nob.AllowPredictedSyncTypes)
{
ArraySegment<byte> syncValues = reader.ReadArraySegmentAndSize();
PooledReader syncTypeReader = ReaderPool.Retrieve(syncValues, base.NetworkManager);
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
{
//SyncVars.
int length = syncTypeReader.ReadInt32();
nb.OnSyncType(syncTypeReader, length, false, true);
//SyncObjects
length = syncTypeReader.ReadInt32();
nb.OnSyncType(syncTypeReader, length, true, true);
}
syncTypeReader.Store();
}
SpawnWithoutChecks(nob, owner, objectId);
//Send the spawner a new reservedId.
WriteResponse(true);
//Writes a predicted spawn result to a client.
void WriteResponse(bool success)
{
PooledWriter writer = WriterPool.Retrieve();
writer.WritePacketId(PacketId.PredictedSpawnResult);
writer.WriteNetworkObjectId(nob.ObjectId);
writer.WriteBoolean(success);
if (success)
{
Queue<int> objectIdCache = NetworkManager.ServerManager.Objects.GetObjectIdCache();
//Write next objectId to use.
int invalidId = NetworkObject.UNSET_OBJECTID_VALUE;
int nextId = (objectIdCache.Count > 0) ? objectIdCache.Dequeue() : invalidId;
writer.WriteNetworkObjectId(nextId);
//If nextId is valid then also add it to spawners local cache.
if (nextId != invalidId)
conn.PredictedObjectIds.Enqueue(nextId);
////Update RPC links.
//foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
// nb.WriteRpcLinks(writer);
}
conn.SendToClient((byte)Channel.Reliable, writer.GetArraySegment());
}
}
#endregion
#region Despawning.
/// <summary>
/// Cleans recently despawned objects.
/// </summary>
private void CleanRecentlyDespawned()
{
//Only iterate if frame ticked to save perf.
if (!base.NetworkManager.TimeManager.FrameTicked)
return;
List<int> intCache = CollectionCaches<int>.RetrieveList();
uint requiredTicks = _cleanRecentlyDespawnedMaxTicks;
uint currentTick = base.NetworkManager.TimeManager.LocalTick;
//Iterate 20, or 5% of the collection, whichever is higher.
int iterations = Mathf.Max(20, (int)(RecentlyDespawnedIds.Count * 0.05f));
/* Given this is a dictionary there is no gaurantee which order objects are
* added. Because of this it's possible some objects may take much longer to
* be removed. This is okay so long as a consistent chunk of objects are removed
* at a time; eventually all objects will be iterated. */
int count = 0;
foreach (KeyValuePair<int, uint> kvp in RecentlyDespawnedIds)
{
long result = (currentTick - kvp.Value);
//If enough ticks have passed to remove.
if (result > requiredTicks)
intCache.Add(kvp.Key);
count++;
if (count == iterations)
break;
}
//Remove cached entries.
int cCount = intCache.Count;
for (int i = 0; i < cCount; i++)
RecentlyDespawnedIds.Remove(intCache[i]);
CollectionCaches<int>.Store(intCache);
}
/// <summary>
/// Returns if an objectId was recently despawned.
/// </summary>
/// <param name="objectId">ObjectId to check.</param>
/// <param name="ticks">Passed ticks to be within to be considered recently despawned.</param>
/// <returns>True if an objectId was despawned with specified number of ticks.</returns>
public bool RecentlyDespawned(int objectId, uint ticks)
{
uint despawnTick;
if (!RecentlyDespawnedIds.TryGetValue(objectId, out despawnTick))
return false;
return ((NetworkManager.TimeManager.LocalTick - despawnTick) <= ticks);
}
/// <summary>
/// Adds to objects pending destroy due to clientHost environment.
/// </summary>
/// <param name="nob"></param>
internal void AddToPending(NetworkObject nob)
{
_pendingDestroy[nob.ObjectId] = nob;
}
/// <summary>
/// Tries to removes objectId from PendingDestroy and returns if successful.
/// </summary>
internal bool RemoveFromPending(int objectId)
{
return _pendingDestroy.Remove(objectId);
}
/// <summary>
/// Returns a NetworkObject in PendingDestroy.
/// </summary>
internal NetworkObject GetFromPending(int objectId)
{
NetworkObject nob;
_pendingDestroy.TryGetValue(objectId, out nob);
return nob;
}
/// <summary>
/// Destroys NetworkObjects pending for destruction.
/// </summary>
internal void DestroyPending()
{
foreach (NetworkObject item in _pendingDestroy.Values)
{
if (item != null)
MonoBehaviour.Destroy(item.gameObject);
}
_pendingDestroy.Clear();
}
/// <summary>
/// Despawns an object over the network.
/// </summary>
internal override void Despawn(NetworkObject networkObject, DespawnType despawnType, bool asServer)
{
//Default as false, will change if needed.
bool predictedDespawn = false;
if (networkObject == null)
{
base.NetworkManager.LogWarning($"NetworkObject cannot be despawned because it is null.");
return;
}
if (networkObject.IsDeinitializing)
{
base.NetworkManager.LogWarning($"Object {networkObject.name} cannot be despawned because it is already deinitializing.");
return;
}
if (!NetworkManager.ServerManager.Started)
{
//Neither server nor client are started.
if (!NetworkManager.ClientManager.Started)
{
base.NetworkManager.LogWarning("Cannot despawn object because server nor client are active.");
return;
}
//Server has predicted spawning disabled.
if (!NetworkManager.PredictionManager.GetAllowPredictedSpawning())
{
base.NetworkManager.LogWarning("Cannot despawn object because server is not active and predicted spawning is not enabled.");
return;
}
//Various predicted despawn checks.
if (!base.CanPredictedDespawn(networkObject, NetworkManager.ClientManager.Connection, false))
return;
predictedDespawn = true;
}
if (!networkObject.gameObject.scene.IsValid())
{
base.NetworkManager.LogError($"{networkObject.name} is a prefab. You must instantiate the prefab first, then use Spawn on the instantiated copy.");
return;
}
if (predictedDespawn)
{
base.NetworkManager.ClientManager.Objects.PredictedDespawn(networkObject);
}
else
{
FinalizeDespawn(networkObject, despawnType);
RecentlyDespawnedIds[networkObject.ObjectId] = base.NetworkManager.TimeManager.LocalTick;
base.Despawn(networkObject, despawnType, asServer);
}
}
/// <summary>
/// Called when a NetworkObject is destroyed without being deactivated first.
/// </summary>
/// <param name="nob"></param>
internal override void NetworkObjectUnexpectedlyDestroyed(NetworkObject nob, bool asServer)
{
FinalizeDespawn(nob, DespawnType.Destroy);
base.NetworkObjectUnexpectedlyDestroyed(nob, asServer);
}
/// <summary>
/// Finalizes the despawn process. By the time this is called the object is considered unaccessible.
/// </summary>
/// <param name="nob"></param>
private void FinalizeDespawn(NetworkObject nob, DespawnType despawnType)
{
if (nob != null && nob.ObjectId != NetworkObject.UNSET_OBJECTID_VALUE)
{
nob.WriteDirtySyncTypes();
WriteDespawnAndSend(nob, despawnType);
CacheObjectId(nob);
}
}
/// <summary>
/// Writes a despawn and sends it to clients.
/// </summary>
/// <param name="nob"></param>
private void WriteDespawnAndSend(NetworkObject nob, DespawnType despawnType)
{
PooledWriter everyoneWriter = WriterPool.Retrieve();
WriteDespawn(nob, despawnType, everyoneWriter);
ArraySegment<byte> despawnSegment = everyoneWriter.GetArraySegment();
//Add observers to a list cache.
List<NetworkConnection> cache = CollectionCaches<NetworkConnection>.RetrieveList();
cache.AddRange(nob.Observers);
int cacheCount = cache.Count;
for (int i = 0; i < cacheCount; i++)
{
//Invoke ondespawn and send despawn.
NetworkConnection conn = cache[i];
nob.InvokeOnServerDespawn(conn);
NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, despawnSegment, conn);
//Remove from observers.
//nob.Observers.Remove(conn);
}
everyoneWriter.Store();
CollectionCaches<NetworkConnection>.Store(cache);
}
/// <summary>
/// Reads a predicted despawn.
/// </summary>
internal void ReadPredictedDespawn(Reader reader, NetworkConnection conn)
{
NetworkObject nob = reader.ReadNetworkObject();
//Maybe server destroyed the object so don't kick if null.
if (nob == null)
{
reader.Clear();
return;
}
//Does not allow predicted despawning.
if (!nob.AllowPredictedDespawning)
{
reader.Clear();
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {conn.ClientId} used predicted despawning for object {nob.name} when it does not support predicted despawning.");
}
//Despawn object.
nob.Despawn();
}
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d5e7f3005cbc7924f99819311c58651a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: