fishnet installed
This commit is contained in:
@ -0,0 +1,522 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object;
|
||||
using FishNet.Observing;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Performance;
|
||||
using System.Collections.Generic;
|
||||
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;
|
||||
#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 observersCount = _timedNetworkObservers.Count;
|
||||
if (observersCount == 0)
|
||||
return;
|
||||
|
||||
ServerManager serverManager = base.NetworkManager.ServerManager;
|
||||
TransportManager transportManager = NetworkManager.TransportManager;
|
||||
/* Try to iterate all timed observers every half a second.
|
||||
* This value will increase as there's more observers. */
|
||||
int completionTicks = (base.NetworkManager.TimeManager.TickRate * 2);
|
||||
/* Multiply required ticks based on connection count and nob count. This will
|
||||
* reduce how quickly observers update slightly but will drastically
|
||||
* improve performance. */
|
||||
float tickMultiplier = 1f + (float)(
|
||||
(serverManager.Clients.Count * 0.005f) +
|
||||
(_timedNetworkObservers.Count * 0.0005f)
|
||||
);
|
||||
/* Add an additional iteration to prevent
|
||||
* 0 iterations */
|
||||
int iterations = (observersCount / (int)(completionTicks * tickMultiplier)) + 1;
|
||||
if (iterations > observersCount)
|
||||
iterations = observersCount;
|
||||
|
||||
PooledWriter everyoneWriter = WriterPool.GetWriter();
|
||||
PooledWriter ownerWriter = WriterPool.GetWriter();
|
||||
|
||||
//Index to perform a check on.
|
||||
int observerIndex = 0;
|
||||
foreach (NetworkConnection conn in serverManager.Clients.Values)
|
||||
{
|
||||
int cacheIndex = 0;
|
||||
using (PooledWriter largeWriter = WriterPool.GetWriter())
|
||||
{
|
||||
//Reset index to start on for every connection.
|
||||
observerIndex = 0;
|
||||
/* Run the number of calculated iterations.
|
||||
* This is spaced out over frames to prevent
|
||||
* fps spikes. */
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
observerIndex = _nextTimedObserversIndex + i;
|
||||
/* Compare actual collection size not cached value.
|
||||
* This is incase collection is modified during runtime. */
|
||||
if (observerIndex >= _timedNetworkObservers.Count)
|
||||
observerIndex -= _timedNetworkObservers.Count;
|
||||
|
||||
/* If still out of bounds something whack is going on.
|
||||
* Reset index and exit method. Let it sort itself out
|
||||
* next iteration. */
|
||||
if (observerIndex < 0 || observerIndex >= _timedNetworkObservers.Count)
|
||||
{
|
||||
_nextTimedObserversIndex = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
NetworkObject nob = _timedNetworkObservers[observerIndex];
|
||||
ObserverStateChange osc = nob.RebuildObservers(conn, true);
|
||||
if (osc == ObserverStateChange.Added)
|
||||
{
|
||||
everyoneWriter.Reset();
|
||||
ownerWriter.Reset();
|
||||
base.WriteSpawn_Server(nob, conn, everyoneWriter, ownerWriter);
|
||||
CacheObserverChange(nob, ref cacheIndex);
|
||||
}
|
||||
else if (osc == ObserverStateChange.Removed)
|
||||
{
|
||||
everyoneWriter.Reset();
|
||||
WriteDespawn(nob, nob.GetDefaultDespawnType(), everyoneWriter);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
/* Only use ownerWriter if an add, and if owner. Owner
|
||||
* doesn't matter if not being added because no owner specific
|
||||
* information would be included. */
|
||||
PooledWriter writerToUse = (osc == ObserverStateChange.Added && nob.Owner == conn) ?
|
||||
ownerWriter : everyoneWriter;
|
||||
|
||||
largeWriter.WriteArraySegment(writerToUse.GetArraySegment());
|
||||
}
|
||||
|
||||
if (largeWriter.Length > 0)
|
||||
{
|
||||
transportManager.SendToClient(
|
||||
(byte)Channel.Reliable,
|
||||
largeWriter.GetArraySegment(), conn);
|
||||
}
|
||||
|
||||
//Invoke spawn callbacks on nobs.
|
||||
for (int i = 0; i < cacheIndex; i++)
|
||||
_observerChangedObjectsCache[i].InvokePostOnServerStart(conn);
|
||||
}
|
||||
}
|
||||
|
||||
everyoneWriter.Dispose();
|
||||
ownerWriter.Dispose();
|
||||
_nextTimedObserversIndex = (observerIndex + 1);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Caches an observer change.
|
||||
/// </summary>
|
||||
/// <param name="cacheIndex"></param>
|
||||
private void CacheObserverChange(NetworkObject nob, ref int cacheIndex)
|
||||
{
|
||||
/* If this spawn would exceed cache size then
|
||||
* add instead of set value. */
|
||||
if (_observerChangedObjectsCache.Count <= cacheIndex)
|
||||
_observerChangedObjectsCache.Add(nob);
|
||||
else
|
||||
_observerChangedObjectsCache[cacheIndex] = nob;
|
||||
|
||||
cacheIndex++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a connection from observers without synchronizing changes.
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
private void RemoveFromObserversWithoutSynchronization(NetworkConnection connection)
|
||||
{
|
||||
int cacheIndex = 0;
|
||||
|
||||
foreach (NetworkObject nob in Spawned.Values)
|
||||
{
|
||||
if (nob.RemoveObserver(connection))
|
||||
CacheObserverChange(nob, ref cacheIndex);
|
||||
}
|
||||
|
||||
//Invoke despawn callbacks on nobs.
|
||||
for (int i = 0; i < cacheIndex; i++)
|
||||
_observerChangedObjectsCache[i].InvokeOnServerDespawn(connection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on all NetworkObjects for all connections.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers()
|
||||
{
|
||||
ListCache<NetworkObject> nobCache = GetOrderedSpawnedObjects();
|
||||
ListCache<NetworkConnection> connCache = ListCaches.GetNetworkConnectionCache();
|
||||
foreach (NetworkConnection conn in base.NetworkManager.ServerManager.Clients.Values)
|
||||
connCache.AddValue(conn);
|
||||
|
||||
RebuildObservers(nobCache, connCache);
|
||||
ListCaches.StoreCache(nobCache);
|
||||
ListCaches.StoreCache(connCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(NetworkObject[] nobs)
|
||||
{
|
||||
int count = nobs.Length;
|
||||
for (int i = 0; i < count; i++)
|
||||
RebuildObservers(nobs[i]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(List<NetworkObject> nobs)
|
||||
{
|
||||
int count = nobs.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
RebuildObservers(nobs[i]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(ListCache<NetworkObject> nobs)
|
||||
{
|
||||
int count = nobs.Written;
|
||||
List<NetworkObject> collection = nobs.Collection;
|
||||
for (int i = 0; i < count; i++)
|
||||
RebuildObservers(collection[i]);
|
||||
}
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects for connections.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(ListCache<NetworkObject> nobs, NetworkConnection conn)
|
||||
{
|
||||
RebuildObservers(nobs.Collection, conn, nobs.Written);
|
||||
}
|
||||
/// <summary>
|
||||
/// Rebuilds observers on NetworkObjects for connections.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(ListCache<NetworkObject> nobs, ListCache<NetworkConnection> conns)
|
||||
{
|
||||
int count = nobs.Written;
|
||||
List<NetworkObject> collection = nobs.Collection;
|
||||
for (int i = 0; i < count; i++)
|
||||
RebuildObservers(collection[i], conns);
|
||||
}
|
||||
/// <summary>
|
||||
/// Rebuilds observers on all objects for a connections.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(ListCache<NetworkConnection> connections)
|
||||
{
|
||||
int count = connections.Written;
|
||||
List<NetworkConnection> collection = connections.Collection;
|
||||
for (int i = 0; i < count; i++)
|
||||
RebuildObservers(collection[i]);
|
||||
}
|
||||
/// <summary>
|
||||
/// Rebuilds observers on all objects for connections.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(NetworkConnection[] connections)
|
||||
{
|
||||
int count = connections.Length;
|
||||
for (int i = 0; i < count; i++)
|
||||
RebuildObservers(connections[i]);
|
||||
}
|
||||
/// <summary>
|
||||
/// Rebuilds observers on all objects for connections.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(List<NetworkConnection> connections)
|
||||
{
|
||||
int count = connections.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
RebuildObservers(connections[i]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers on all NetworkObjects for a connection.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(NetworkConnection connection)
|
||||
{
|
||||
ListCache<NetworkObject> cache = GetOrderedSpawnedObjects();
|
||||
RebuildObservers(cache, connection);
|
||||
ListCaches.StoreCache(cache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all spawned objects with root objects first.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private ListCache<NetworkObject> GetOrderedSpawnedObjects()
|
||||
{
|
||||
ListCache<NetworkObject> cache = ListCaches.GetNetworkObjectCache();
|
||||
foreach (NetworkObject networkObject in Spawned.Values)
|
||||
{
|
||||
if (networkObject.IsNested)
|
||||
continue;
|
||||
|
||||
//Add nob and children recursively.
|
||||
AddChildNetworkObjects(networkObject);
|
||||
}
|
||||
|
||||
void AddChildNetworkObjects(NetworkObject n)
|
||||
{
|
||||
cache.AddValue(n);
|
||||
foreach (NetworkObject nob in n.ChildNetworkObjects)
|
||||
AddChildNetworkObjects(nob);
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers for a connection on NetworkObjects.
|
||||
/// </summary>
|
||||
/// <param name="nobs">NetworkObjects to rebuild.</param>
|
||||
/// <param name="connection">Connection to rebuild for.</param>
|
||||
/// <param name="count">Number of iterations to perform collection. Entire collection is iterated when value is -1.</param>
|
||||
public void RebuildObservers(IEnumerable<NetworkObject> nobs, NetworkConnection connection, int count = -1)
|
||||
{
|
||||
PooledWriter everyoneWriter = WriterPool.GetWriter();
|
||||
PooledWriter ownerWriter = WriterPool.GetWriter();
|
||||
|
||||
//If there's no limit on how many can be written set count to the maximum.
|
||||
if (count == -1)
|
||||
count = int.MaxValue;
|
||||
|
||||
int iterations;
|
||||
int observerCacheIndex;
|
||||
using (PooledWriter largeWriter = WriterPool.GetWriter())
|
||||
{
|
||||
iterations = 0;
|
||||
observerCacheIndex = 0;
|
||||
foreach (NetworkObject n in nobs)
|
||||
{
|
||||
iterations++;
|
||||
if (iterations > count)
|
||||
break;
|
||||
|
||||
//If observer state changed then write changes.
|
||||
ObserverStateChange osc = n.RebuildObservers(connection, false);
|
||||
if (osc == ObserverStateChange.Added)
|
||||
{
|
||||
everyoneWriter.Reset();
|
||||
ownerWriter.Reset();
|
||||
base.WriteSpawn_Server(n, connection, everyoneWriter, ownerWriter);
|
||||
CacheObserverChange(n, ref observerCacheIndex);
|
||||
}
|
||||
else if (osc == ObserverStateChange.Removed)
|
||||
{
|
||||
connection.LevelOfDetails.Remove(n);
|
||||
everyoneWriter.Reset();
|
||||
WriteDespawn(n, n.GetDefaultDespawnType(), everyoneWriter);
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
/* Only use ownerWriter if an add, and if owner. Owner //cleanup see if rebuild timed and this can be joined or reuse methods.
|
||||
* doesn't matter if not being added because no owner specific
|
||||
* information would be included. */
|
||||
PooledWriter writerToUse = (osc == ObserverStateChange.Added && n.Owner == connection) ?
|
||||
ownerWriter : everyoneWriter;
|
||||
|
||||
largeWriter.WriteArraySegment(writerToUse.GetArraySegment());
|
||||
}
|
||||
|
||||
if (largeWriter.Length > 0)
|
||||
{
|
||||
NetworkManager.TransportManager.SendToClient(
|
||||
(byte)Channel.Reliable,
|
||||
largeWriter.GetArraySegment(), connection);
|
||||
}
|
||||
}
|
||||
|
||||
//Dispose of writers created in this method.
|
||||
everyoneWriter.Dispose();
|
||||
ownerWriter.Dispose();
|
||||
|
||||
//Invoke spawn callbacks on nobs.
|
||||
for (int i = 0; i < observerCacheIndex; i++)
|
||||
_observerChangedObjectsCache[i].InvokePostOnServerStart(connection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers for connections on a NetworkObject.
|
||||
/// </summary>
|
||||
private void RebuildObservers(NetworkObject nob, ListCache<NetworkConnection> conns)
|
||||
{
|
||||
PooledWriter everyoneWriter = WriterPool.GetWriter();
|
||||
PooledWriter ownerWriter = WriterPool.GetWriter();
|
||||
|
||||
int written = conns.Written;
|
||||
for (int i = 0; i < written; i++)
|
||||
{
|
||||
NetworkConnection conn = conns.Collection[i];
|
||||
|
||||
everyoneWriter.Reset();
|
||||
ownerWriter.Reset();
|
||||
//If observer state changed then write changes.
|
||||
ObserverStateChange osc = nob.RebuildObservers(conn, false);
|
||||
if (osc == ObserverStateChange.Added)
|
||||
{
|
||||
base.WriteSpawn_Server(nob, conn, everyoneWriter, ownerWriter);
|
||||
}
|
||||
else if (osc == ObserverStateChange.Removed)
|
||||
{
|
||||
conn.LevelOfDetails.Remove(nob);
|
||||
WriteDespawn(nob, nob.GetDefaultDespawnType(), everyoneWriter);
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Only use ownerWriter if an add, and if owner. Owner
|
||||
* doesn't matter if not being added because no owner specific
|
||||
* information would be included. */
|
||||
PooledWriter writerToUse = (osc == ObserverStateChange.Added && nob.Owner == conn) ?
|
||||
ownerWriter : everyoneWriter;
|
||||
|
||||
if (writerToUse.Length > 0)
|
||||
{
|
||||
NetworkManager.TransportManager.SendToClient(
|
||||
(byte)Channel.Reliable,
|
||||
writerToUse.GetArraySegment(), conn);
|
||||
|
||||
//If a spawn is being sent.
|
||||
if (osc == ObserverStateChange.Added)
|
||||
nob.InvokePostOnServerStart(conn);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Dispose of writers created in this method.
|
||||
everyoneWriter.Dispose();
|
||||
ownerWriter.Dispose();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers for all connections for a NetworkObject.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(NetworkObject nob)
|
||||
{
|
||||
ListCache<NetworkConnection> cache = ListCaches.GetNetworkConnectionCache();
|
||||
foreach (NetworkConnection item in NetworkManager.ServerManager.Clients.Values)
|
||||
cache.AddValue(item);
|
||||
|
||||
RebuildObservers(nob, cache);
|
||||
ListCaches.StoreCache(cache);
|
||||
}
|
||||
/// <summary>
|
||||
/// Rebuilds observers for a connection on NetworkObject.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void RebuildObservers(NetworkObject nob, NetworkConnection conn)
|
||||
{
|
||||
ListCache<NetworkConnection> cache = ListCaches.GetNetworkConnectionCache();
|
||||
cache.AddValue(conn);
|
||||
|
||||
RebuildObservers(nob, cache);
|
||||
ListCaches.StoreCache(cache);
|
||||
}
|
||||
/// <summary>
|
||||
/// Rebuilds observers for connections on NetworkObject.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(NetworkObject networkObject, NetworkConnection[] connections)
|
||||
{
|
||||
ListCache<NetworkConnection> cache = ListCaches.GetNetworkConnectionCache();
|
||||
cache.AddValues(connections);
|
||||
RebuildObservers(networkObject, cache);
|
||||
ListCaches.StoreCache(cache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds observers for connections on NetworkObject.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void RebuildObservers(NetworkObject networkObject, List<NetworkConnection> connections)
|
||||
{
|
||||
ListCache<NetworkConnection> cache = ListCaches.GetNetworkConnectionCache();
|
||||
cache.AddValues(connections);
|
||||
RebuildObservers(networkObject, cache);
|
||||
ListCaches.StoreCache(cache);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02d93fa4a653dd64da0bb338b82f4740
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,45 @@
|
||||
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 ReplicateRpc.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void ParseReplicateRpc(PooledReader reader, NetworkConnection conn, Channel channel)
|
||||
{
|
||||
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
|
||||
int dataLength = Packets.GetPacketLength((ushort)PacketId.ServerRpc, reader, channel);
|
||||
|
||||
if (nb != null)
|
||||
nb.OnReplicateRpc(null, reader, conn, channel);
|
||||
else
|
||||
SkipDataLength((ushort)PacketId.ServerRpc, reader, dataLength);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cb6cef520a4ff44bb8c4814e566c5ff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
876
Assets/FishNet/Runtime/Managing/Server/Object/ServerObjects.cs
Normal file
876
Assets/FishNet/Runtime/Managing/Server/Object/ServerObjects.cs
Normal file
@ -0,0 +1,876 @@
|
||||
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 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 ListCache<NetworkObject> _spawnCache = new ListCache<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.Clear();
|
||||
_objectIdCache.Clear();
|
||||
base.NetworkManager.ClearClientsCollection(base.NetworkManager.ServerManager.Clients);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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. */
|
||||
ListCache<NetworkObject> cache = ListCaches.GetNetworkObjectCache();
|
||||
foreach (NetworkObject nob in connection.Objects)
|
||||
cache.AddValue(nob);
|
||||
|
||||
int written = cache.Written;
|
||||
List<NetworkObject> collection = cache.Collection;
|
||||
for (int i = 0; i < written; 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 (!collection[i].IsDeinitializing)
|
||||
base.NetworkManager.ServerManager.Despawn(collection[i]);
|
||||
}
|
||||
|
||||
ListCaches.StoreCache(cache);
|
||||
}
|
||||
#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 = DDOLFinder.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)
|
||||
{
|
||||
ListCache<NetworkObject> nobs;
|
||||
SceneFN.GetSceneNetworkObjects(s, false, out nobs);
|
||||
|
||||
bool isHost = base.NetworkManager.IsHost;
|
||||
|
||||
for (int i = 0; i < nobs.Written; i++)
|
||||
{
|
||||
NetworkObject nob = nobs.Collection[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListCaches.StoreCache(nobs);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
//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.ParentNetworkObject != null && !networkObject.ParentNetworkObject.IsSpawned)
|
||||
{
|
||||
base.NetworkManager.LogError($"{networkObject.name} cannot be spawned because it has a parent NetworkObject {networkObject.ParentNetworkObject} which is not spawned.");
|
||||
return;
|
||||
}
|
||||
|
||||
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.AddValue(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. */
|
||||
ListCache<NetworkObject> spawnCacheCopy = ListCaches.GetNetworkObjectCache();
|
||||
spawnCacheCopy.AddValues(_spawnCache);
|
||||
_spawnCache.Reset();
|
||||
//Also rebuild observers for the object so it spawns for others.
|
||||
RebuildObservers(spawnCacheCopy);
|
||||
|
||||
/* 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 = spawnCacheCopy.Written;
|
||||
List<NetworkObject> collection = spawnCacheCopy.Collection;
|
||||
for (int i = 0; i < count; i++)
|
||||
collection[i].SetRenderersVisible(networkObject.Observers.Contains(NetworkManager.ClientManager.Connection));
|
||||
}
|
||||
|
||||
ListCaches.StoreCache(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);
|
||||
nob = base.GetSceneNetworkObject(sceneId);
|
||||
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, 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.GetReader(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.Dispose();
|
||||
}
|
||||
|
||||
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.GetWriter();
|
||||
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;
|
||||
|
||||
ListCache<int> intCache = ListCaches.GetIntCache();
|
||||
|
||||
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.AddValue(kvp.Key);
|
||||
|
||||
count++;
|
||||
if (count == iterations)
|
||||
break;
|
||||
}
|
||||
|
||||
//Remove cached entries.
|
||||
List<int> collection = intCache.Collection;
|
||||
int cCount = collection.Count;
|
||||
for (int i = 0; i < cCount; i++)
|
||||
RecentlyDespawnedIds.Remove(collection[i]);
|
||||
|
||||
ListCaches.StoreCache(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.GetWriter();
|
||||
WriteDespawn(nob, despawnType, everyoneWriter);
|
||||
|
||||
ArraySegment<byte> despawnSegment = everyoneWriter.GetArraySegment();
|
||||
|
||||
//Add observers to a list cache.
|
||||
ListCache<NetworkConnection> cache = ListCaches.GetNetworkConnectionCache();
|
||||
cache.Reset();
|
||||
cache.AddValues(nob.Observers);
|
||||
int written = cache.Written;
|
||||
for (int i = 0; i < written; i++)
|
||||
{
|
||||
//Invoke ondespawn and send despawn.
|
||||
NetworkConnection conn = cache.Collection[i];
|
||||
nob.InvokeOnServerDespawn(conn);
|
||||
NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, despawnSegment, conn);
|
||||
//Remove from observers.
|
||||
//nob.Observers.Remove(conn);
|
||||
}
|
||||
|
||||
everyoneWriter.Dispose();
|
||||
ListCaches.StoreCache(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
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5e7f3005cbc7924f99819311c58651a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user