fishnet installed

This commit is contained in:
2023-05-31 11:32:21 -04:00
parent 47b25269f1
commit a001fe1b04
1291 changed files with 126631 additions and 1 deletions

View File

@ -0,0 +1,179 @@
using FishNet.Broadcast;
using FishNet.Broadcast.Helping;
using FishNet.Managing.Logging;
using FishNet.Managing.Utility;
using FishNet.Object.Helping;
using FishNet.Serializing;
using FishNet.Serializing.Helping;
using FishNet.Transporting;
using FishNet.Utility.Extension;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Managing.Client
{
public sealed partial class ClientManager : MonoBehaviour
{
#region Private.
/// <summary>
/// Delegate to read received broadcasts.
/// </summary>
/// <param name="reader"></param>
private delegate void ServerBroadcastDelegate(PooledReader reader);
/// <summary>
/// Delegates for each key.
/// </summary>
private readonly Dictionary<ushort, HashSet<ServerBroadcastDelegate>> _broadcastHandlers = new Dictionary<ushort, HashSet<ServerBroadcastDelegate>>();
/// <summary>
/// Delegate targets for each key.
/// </summary>
private Dictionary<ushort, HashSet<(int, ServerBroadcastDelegate)>> _handlerTargets = new Dictionary<ushort, HashSet<(int, ServerBroadcastDelegate)>>();
#endregion
/// <summary>
/// Registers a method to call when a Broadcast arrives.
/// </summary>
/// <typeparam name="T">Type of broadcast being registered.</typeparam>
/// <param name="handler">Method to call.</param>
public void RegisterBroadcast<T>(Action<T> handler) where T : struct, IBroadcast
{
ushort key = typeof(T).FullName.GetStableHash16();
/* Create delegate and add for
* handler method. */
HashSet<ServerBroadcastDelegate> handlers;
if (!_broadcastHandlers.TryGetValueIL2CPP(key, out handlers))
{
handlers = new HashSet<ServerBroadcastDelegate>();
_broadcastHandlers.Add(key, handlers);
}
ServerBroadcastDelegate del = CreateBroadcastDelegate(handler);
handlers.Add(del);
/* Add hashcode of target for handler.
* This is so we can unregister the target later. */
int handlerHashCode = handler.GetHashCode();
HashSet<(int, ServerBroadcastDelegate)> targetHashCodes;
if (!_handlerTargets.TryGetValueIL2CPP(key, out targetHashCodes))
{
targetHashCodes = new HashSet<(int, ServerBroadcastDelegate)>();
_handlerTargets.Add(key, targetHashCodes);
}
targetHashCodes.Add((handlerHashCode, del));
}
/// <summary>
/// Unregisters a method call from a Broadcast type.
/// </summary>
/// <typeparam name="T">Type of broadcast being unregistered.</typeparam>
/// <param name="handler">Method to unregister.</param>
public void UnregisterBroadcast<T>(Action<T> handler) where T : struct, IBroadcast
{
ushort key = BroadcastHelper.GetKey<T>();
/* If key is found for T then look for
* the appropriate handler to remove. */
if (_broadcastHandlers.TryGetValueIL2CPP(key, out HashSet<ServerBroadcastDelegate> handlers))
{
HashSet<(int, ServerBroadcastDelegate)> targetHashCodes;
if (_handlerTargets.TryGetValueIL2CPP(key, out targetHashCodes))
{
int handlerHashCode = handler.GetHashCode();
ServerBroadcastDelegate result = null;
foreach ((int targetHashCode, ServerBroadcastDelegate del) in targetHashCodes)
{
if (targetHashCode == handlerHashCode)
{
result = del;
targetHashCodes.Remove((targetHashCode, del));
break;
}
}
//If no more in targetHashCodes then remove from handlerTarget.
if (targetHashCodes.Count == 0)
_handlerTargets.Remove(key);
if (result != null)
handlers.Remove(result);
}
//If no more in handlers then remove broadcastHandlers.
if (handlers.Count == 0)
_broadcastHandlers.Remove(key);
}
}
/// <summary>
/// Creates a ServerBroadcastDelegate.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="handler"></param>
/// <param name="requireAuthentication"></param>
/// <returns></returns>
private ServerBroadcastDelegate CreateBroadcastDelegate<T>(Action<T> handler)
{
void LogicContainer(PooledReader reader)
{
T broadcast = reader.Read<T>();
handler?.Invoke(broadcast);
}
return LogicContainer;
}
/// <summary>
/// Parses a received broadcast.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ParseBroadcast(PooledReader reader, Channel channel)
{
ushort key = reader.ReadUInt16();
int dataLength = Packets.GetPacketLength((ushort)PacketId.Broadcast, reader, channel);
// try to invoke the handler for that message
if (_broadcastHandlers.TryGetValueIL2CPP(key, out HashSet<ServerBroadcastDelegate> handlers))
{
int readerStartPosition = reader.Position;
/* //muchlater resetting the position could be better by instead reading once and passing in
* the object to invoke with. */
foreach (ServerBroadcastDelegate handler in handlers)
{
reader.Position = readerStartPosition;
handler.Invoke(reader);
}
}
else
{
reader.Skip(dataLength);
}
}
/// <summary>
/// Sends a Broadcast to the server.
/// </summary>
/// <typeparam name="T">Type of broadcast to send.</typeparam>
/// <param name="message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
/// <param name="channel">Channel to send on.</param>
public void Broadcast<T>(T message, Channel channel = Channel.Reliable) where T : struct, IBroadcast
{
//Check local connection state.
if (!Started)
{
NetworkManager.LogWarning($"Cannot send broadcast to server because client is not active.");
return;
}
using (PooledWriter writer = WriterPool.GetWriter())
{
Broadcasts.WriteBroadcast<T>(writer, message, channel);
ArraySegment<byte> segment = writer.GetArraySegment();
NetworkManager.TransportManager.SendToServer((byte)channel, segment);
}
}
}
}

View File

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

View File

@ -0,0 +1,197 @@
using FishNet.Managing.Timing;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Transporting;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.Managing.Client
{
public sealed partial class ClientManager : MonoBehaviour
{
#region Internal.
/// <summary>
/// How many ticks between each LOD update.
/// </summary>
public uint LevelOfDetailInterval => NetworkManager.TimeManager.TimeToTicks(0.5d, TickRounding.RoundUp);
#endregion
#region Private.
/// <summary>
/// Positions of the player objects.
/// </summary>
private List<Vector3> _objectsPositionsCache = new List<Vector3>();
/// <summary>
/// Next index within Spawned to update the LOD on.
/// </summary>
private int _nextLodNobIndex;
#endregion
/// <summary>
/// Sends a level of update if conditions are met.
/// </summary>
/// <param name="forceFullUpdate">True to force send a full update immediately.
/// This may be useful when teleporting clients.
/// This must be called by on the server by using ServerManager.ForceLodUpdate(NetworkConnection).
/// </param>
internal void SendLodUpdate(bool forceFullUpdate)
{
if (!Connection.Authenticated)
return;
NetworkManager nm = NetworkManager;
if (forceFullUpdate)
{
nm.LogError($"ForceFullUpdate is not yet implemented. Setting this true should not be possible.");
return;
}
if (!nm.ObserverManager.GetUseNetworkLod())
return;
//Interval check.
uint localTick = nm.TimeManager.LocalTick;
uint intervalRequirement = LevelOfDetailInterval;
bool intervalMet = ((localTick - Connection.LastLevelOfDetailUpdate) >= intervalRequirement);
if (!forceFullUpdate && !intervalMet)
return;
//Set next tick.
Connection.LastLevelOfDetailUpdate = localTick;
List<NetworkObject> localClientSpawned = nm.ClientManager.Objects.LocalClientSpawned;
int spawnedCount = localClientSpawned.Count;
if (spawnedCount == 0)
return;
//Rebuild position cache for players objects.
_objectsPositionsCache.Clear();
foreach (NetworkObject playerObjects in Connection.Objects)
_objectsPositionsCache.Add(playerObjects.transform.position);
/* Set the maximum number of entries per send.
* Each send is going to be approximately 3 bytes
* but sometimes can be 4. Calculate based off the maximum
* possible bytes. */
//int mtu = NetworkManager.TransportManager.GetMTU((byte)Channel.Reliable);
const int estimatedMaximumIterations = ( 400 / 4);
/* Aim to process all objects over at most 10 seconds.
* To reduce the number of packets sent objects are
* calculated ~twice a second. This means if the client had
* 1000 objects visible to them they would need to process
* 100 objects a second, so 50 objects every half a second.
* This should be no problem even on slower mobile devices. */
int iterations;
//Normal update.
if (!forceFullUpdate)
{
iterations = Mathf.Min(spawnedCount, estimatedMaximumIterations);
}
//Force does a full update.
else
{
_nextLodNobIndex = 0;
iterations = spawnedCount;
}
//Cache a few more things.
Dictionary<NetworkObject, byte> currentLods = Connection.LevelOfDetails;
List<float> lodDistances = NetworkManager.ObserverManager.GetLevelOfDetailDistances();
//Index to use next is too high so reset it.
if (_nextLodNobIndex >= spawnedCount)
_nextLodNobIndex = 0;
int nobIndex = _nextLodNobIndex;
PooledWriter tmpWriter = WriterPool.GetWriter(1000);
int written = 0;
//Only check if player has objects.
if (_objectsPositionsCache.Count > 0)
{
for (int i = 0; i < iterations; i++)
{
NetworkObject nob = localClientSpawned[nobIndex];
//Somehow went null. Can occur perhaps if client destroys objects between ticks maybe.
if (nob == null)
{
IncreaseObjectIndex();
continue;
}
//Only check objects not owned by the local client.
if (!nob.IsOwner && !nob.IsDeinitializing)
{
Vector3 nobPosition = nob.transform.position;
float closestDistance = float.MaxValue;
foreach (Vector3 objPosition in _objectsPositionsCache)
{
float dist = Vector3.SqrMagnitude(nobPosition - objPosition);
if (dist < closestDistance)
closestDistance = dist;
}
//If not within any distances then max lod will be used, the value below.
byte lod = (byte)(lodDistances.Count - 1);
for (byte z = 0; z < lodDistances.Count; z++)
{
//Distance is within range of this lod.
if (closestDistance <= lodDistances[z])
{
lod = z;
break;
}
}
bool changed;
/* See if value changed. Value is changed
* if it's not the same of old or if
* the nob has not yet been added to the
* level of details collection.
* Even if a forced update only delta
* needs to send. */
if (currentLods.TryGetValue(nob, out byte oldLod))
changed = (oldLod != lod);
else
changed = true;
//If changed then set new value and write.
if (changed)
{
currentLods[nob] = lod;
tmpWriter.WriteNetworkObjectId(nob.ObjectId);
tmpWriter.WriteByte(lod);
written++;
}
}
IncreaseObjectIndex();
void IncreaseObjectIndex()
{
nobIndex++;
if (nobIndex >= spawnedCount)
nobIndex = 0;
}
}
}
//Set next lod index to current nob index.
_nextLodNobIndex = nobIndex;
/* Send using the reliable channel since
* we are using deltas. This is also why
* updates are sent larger chunked twice a second rather
* than smaller chunks regularly. */
PooledWriter writer = WriterPool.GetWriter(1000);
writer.WritePacketId(PacketId.NetworkLODUpdate);
writer.WriteInt32(written);
writer.WriteArraySegment(tmpWriter.GetArraySegment());
NetworkManager.TransportManager.SendToServer((byte)Channel.Reliable, writer.GetArraySegment(), true);
//Dispose writers.
writer.DisposeLength();
tmpWriter.DisposeLength();
}
}
}

View File

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

View File

@ -0,0 +1,503 @@
using FishNet.Connection;
using FishNet.Managing.Debugging;
using FishNet.Managing.Logging;
using FishNet.Managing.Server;
using FishNet.Managing.Transporting;
using FishNet.Serializing;
using FishNet.Transporting;
using FishNet.Utility.Extension;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.Managing.Client
{
/// <summary>
/// A container for local client data and actions.
/// </summary>
[DisallowMultipleComponent]
[AddComponentMenu("FishNet/Manager/ClientManager")]
public sealed partial class ClientManager : MonoBehaviour
{
#region Public.
/// <summary>
/// Called after local client has authenticated.
/// </summary>
public event Action OnAuthenticated;
/// <summary>
/// Called after the local client connection state changes.
/// </summary>
public event Action<ClientConnectionStateArgs> OnClientConnectionState;
/// <summary>
/// Called when a client other than self connects.
/// This is only available when using ServerManager.ShareIds.
/// </summary>
public event Action<RemoteConnectionStateArgs> OnRemoteConnectionState;
/// <summary>
/// True if the client connection is connected to the server.
/// </summary>
public bool Started { get; private set; }
/// <summary>
/// NetworkConnection the local client is using to send data to the server.
/// </summary>
public NetworkConnection Connection = NetworkManager.EmptyConnection;
/// <summary>
/// Handling and information for objects known to the local client.
/// </summary>
public ClientObjects Objects { get; private set; }
/// <summary>
/// All currently connected clients. This field only contains data while ServerManager.ShareIds is enabled.
/// </summary>
public Dictionary<int, NetworkConnection> Clients = new Dictionary<int, NetworkConnection>();
/// <summary>
/// NetworkManager for client.
/// </summary>
[HideInInspector]
public NetworkManager NetworkManager { get; private set; }
#endregion
#region Serialized.
/// <summary>
/// True to automatically set the frame rate when the client connects.
/// </summary>
[Tooltip("True to automatically set the frame rate when the client connects.")]
[SerializeField]
private bool _changeFrameRate = true;
/// <summary>
///
/// </summary>
[Tooltip("Maximum frame rate the client may run at. When as host this value runs at whichever is higher between client and server.")]
[Range(1, NetworkManager.MAXIMUM_FRAMERATE)]
[SerializeField]
private ushort _frameRate = NetworkManager.MAXIMUM_FRAMERATE;
/// <summary>
/// Maximum frame rate the client may run at. When as host this value runs at whichever is higher between client and server.
/// </summary>
internal ushort FrameRate => (_changeFrameRate) ? _frameRate : (ushort)0;
#endregion
#region Private.
/// <summary>
/// Used to read splits.
/// </summary>
private SplitReader _splitReader = new SplitReader();
#if UNITY_EDITOR || DEVELOPMENT_BUILD
/// <summary>
/// Logs data about parser to help debug.
/// </summary>
private ParseLogger _parseLogger = new ParseLogger();
#endif
#endregion
private void OnDestroy()
{
Objects?.SubscribeToSceneLoaded(false);
}
/// <summary>
/// Initializes this script for use.
/// </summary>
/// <param name="manager"></param>
internal void InitializeOnce_Internal(NetworkManager manager)
{
NetworkManager = manager;
Objects = new ClientObjects(manager);
Objects.SubscribeToSceneLoaded(true);
/* Unsubscribe before subscribing.
* Shouldn't be an issue but better safe than sorry. */
SubscribeToEvents(false);
SubscribeToEvents(true);
//Listen for client connections from server.
RegisterBroadcast<ClientConnectionChangeBroadcast>(OnClientConnectionBroadcast);
RegisterBroadcast<ConnectedClientsBroadcast>(OnConnectedClientsBroadcast);
}
/// <summary>
/// Called when the server sends a connection state change for any client.
/// </summary>
/// <param name="args"></param>
private void OnClientConnectionBroadcast(ClientConnectionChangeBroadcast args)
{
//If connecting invoke after added to clients, otherwise invoke before removed.
RemoteConnectionStateArgs rcs = new RemoteConnectionStateArgs((args.Connected) ? RemoteConnectionState.Started : RemoteConnectionState.Stopped, args.Id, -1);
if (args.Connected)
{
Clients[args.Id] = new NetworkConnection(NetworkManager, args.Id, false);
OnRemoteConnectionState?.Invoke(rcs);
}
else
{
OnRemoteConnectionState?.Invoke(rcs);
if (Clients.TryGetValue(args.Id, out NetworkConnection c))
{
c.Dispose();
Clients.Remove(args.Id);
}
}
}
/// <summary>
/// Called when the server sends all currently connected clients.
/// </summary>
/// <param name="args"></param>
private void OnConnectedClientsBroadcast(ConnectedClientsBroadcast args)
{
NetworkManager.ClearClientsCollection(Clients);
List<int> collection = args.ListCache.Collection;// args.Ids;
//No connected clients except self.
if (collection == null)
return;
int count = collection.Count;
for (int i = 0; i < count; i++)
{
int id = collection[i];
Clients[id] = new NetworkConnection(NetworkManager, id, false);
}
}
/// <summary>
/// Changes subscription status to transport.
/// </summary>
/// <param name="subscribe"></param>
private void SubscribeToEvents(bool subscribe)
{
if (NetworkManager == null || NetworkManager.TransportManager == null || NetworkManager.TransportManager.Transport == null)
return;
if (subscribe)
{
NetworkManager.TransportManager.OnIterateIncomingEnd += TransportManager_OnIterateIncomingEnd;
NetworkManager.TransportManager.Transport.OnClientReceivedData += Transport_OnClientReceivedData;
NetworkManager.TransportManager.Transport.OnClientConnectionState += Transport_OnClientConnectionState;
}
else
{
NetworkManager.TransportManager.OnIterateIncomingEnd -= TransportManager_OnIterateIncomingEnd;
NetworkManager.TransportManager.Transport.OnClientReceivedData -= Transport_OnClientReceivedData;
NetworkManager.TransportManager.Transport.OnClientConnectionState -= Transport_OnClientConnectionState;
}
}
/// <summary>
/// Stops the local client connection.
/// </summary>
public void StopConnection()
{
NetworkManager.TransportManager.Transport.StopConnection(false);
}
/// <summary>
/// Starts the local client connection.
/// </summary>
public void StartConnection()
{
NetworkManager.TransportManager.Transport.StartConnection(false);
}
/// <summary>
/// Sets the transport address and starts the local client connection.
/// </summary>
public void StartConnection(string address)
{
StartConnection(address, NetworkManager.TransportManager.Transport.GetPort());
}
/// <summary>
/// Sets the transport address and port, and starts the local client connection.
/// </summary>
public void StartConnection(string address, ushort port)
{
NetworkManager.TransportManager.Transport.SetClientAddress(address);
NetworkManager.TransportManager.Transport.SetPort(port);
StartConnection();
}
/// <summary>
/// Called when a connection state changes for the local client.
/// </summary>
/// <param name="args"></param>
private void Transport_OnClientConnectionState(ClientConnectionStateArgs args)
{
LocalConnectionState state = args.ConnectionState;
Started = (state == LocalConnectionState.Started);
Objects.OnClientConnectionState(args);
//Clear connection after so objects can update using current Connection value.
if (!Started)
{
Connection = NetworkManager.EmptyConnection;
NetworkManager.ClearClientsCollection(Clients);
}
if (NetworkManager.CanLog(LoggingType.Common))
{
Transport t = NetworkManager.TransportManager.GetTransport(args.TransportIndex);
string tName = (t == null) ? "Unknown" : t.GetType().Name;
Debug.Log($"Local client is {state.ToString().ToLower()} for {tName}.");
}
NetworkManager.UpdateFramerate();
OnClientConnectionState?.Invoke(args);
}
/// <summary>
/// Called when a socket receives data.
/// </summary>
private void Transport_OnClientReceivedData(ClientReceivedDataArgs args)
{
args.Data = NetworkManager.TransportManager.ProcessIntermediateIncoming(args.Data, true);
ParseReceived(args);
}
/// <summary>
/// Called after IterateIncoming has completed.
/// </summary>
private void TransportManager_OnIterateIncomingEnd(bool server)
{
/* Should the last packet received be a spawn or despawn
* then the cache won't yet be iterated because it only
* iterates when a packet is anything but those two. Because
* of such if any object caches did come in they must be iterated
* at the end of the incoming cycle. This isn't as clean as I'd
* like but it does ensure there will be no missing network object
* references on spawned objects. */
if (Started && !server)
Objects.IterateObjectCache();
}
/// <summary>
/// Parses received data.
/// </summary>
private void ParseReceived(ClientReceivedDataArgs args)
{
#if UNITY_EDITOR || DEVELOPMENT_BUILD
_parseLogger.Reset();
#endif
ArraySegment<byte> segment = args.Data;
NetworkManager.StatisticsManager.NetworkTraffic.LocalClientReceivedData((ulong)segment.Count);
if (segment.Count <= TransportManager.TICK_BYTES)
return;
PacketId packetId = PacketId.Unset;
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
try
{
#endif
using (PooledReader reader = ReaderPool.GetReader(segment, NetworkManager))
{
NetworkManager.TimeManager.LastPacketTick = reader.ReadUInt32(AutoPackType.Unpacked);
/* This is a special condition where a message may arrive split.
* When this occurs buffer each packet until all packets are
* received. */
if (reader.PeekPacketId() == PacketId.Split)
{
//Skip packetId.
reader.ReadPacketId();
int expectedMessages;
_splitReader.GetHeader(reader, out expectedMessages);
_splitReader.Write(NetworkManager.TimeManager.LastPacketTick, reader, expectedMessages);
/* If fullMessage returns 0 count then the split
* has not written fully yet. Otherwise, if there is
* data within then reinitialize reader with the
* full message. */
ArraySegment<byte> fullMessage = _splitReader.GetFullMessage();
if (fullMessage.Count == 0)
return;
//Initialize reader with full message.
reader.Initialize(fullMessage, NetworkManager);
}
while (reader.Remaining > 0)
{
packetId = reader.ReadPacketId();
#if UNITY_EDITOR || DEVELOPMENT_BUILD
_parseLogger.AddPacket(packetId);
#endif
bool spawnOrDespawn = (packetId == PacketId.ObjectSpawn || packetId == PacketId.ObjectDespawn);
/* Length of data. Only available if using unreliable. Unreliable packets
* can arrive out of order which means object orientated messages such as RPCs may
* arrive after the object for which they target has already been destroyed. When this happens
* on lesser solutions they just dump the entire packet. However, since FishNet batches data.
* it's very likely a packet will contain more than one packetId. With this mind, length is
* sent as well so if any reason the data does have to be dumped it will only be dumped for
* that single packetId but not the rest. Broadcasts don't need length either even if unreliable
* because they are not object bound. */
//Is spawn or despawn; cache packet.
if (spawnOrDespawn)
{
if (packetId == PacketId.ObjectSpawn)
Objects.CacheSpawn(reader);
else if (packetId == PacketId.ObjectDespawn)
Objects.CacheDespawn(reader);
}
//Not spawn or despawn.
else
{
/* Iterate object cache should any of the
* incoming packets rely on it. Objects
* in cache will always be received before any messages
* that use them. */
Objects.IterateObjectCache();
//Then process packet normally.
if ((ushort)packetId >= NetworkManager.StartingRpcLinkIndex)
{
Objects.ParseRpcLink(reader, (ushort)packetId, args.Channel);
}
else if (packetId == PacketId.Reconcile)
{
Objects.ParseReconcileRpc(reader, args.Channel);
}
else if (packetId == PacketId.ObserversRpc)
{
Objects.ParseObserversRpc(reader, args.Channel);
}
else if (packetId == PacketId.TargetRpc)
{
Objects.ParseTargetRpc(reader, args.Channel);
}
else if (packetId == PacketId.Broadcast)
{
ParseBroadcast(reader, args.Channel);
}
else if (packetId == PacketId.PingPong)
{
ParsePingPong(reader);
}
else if (packetId == PacketId.SyncVar)
{
Objects.ParseSyncType(reader, false, args.Channel);
}
else if (packetId == PacketId.SyncObject)
{
Objects.ParseSyncType(reader, true, args.Channel);
}
else if (packetId == PacketId.PredictedSpawnResult)
{
Objects.ParsePredictedSpawnResult(reader);
}
else if (packetId == PacketId.TimingUpdate)
{
NetworkManager.TimeManager.ParseTimingUpdate();
}
else if (packetId == PacketId.OwnershipChange)
{
Objects.ParseOwnershipChange(reader);
}
else if (packetId == PacketId.Authenticated)
{
ParseAuthenticated(reader);
}
else if (packetId == PacketId.Disconnect)
{
reader.Clear();
StopConnection();
}
else
{
NetworkManager.LogError($"Client received an unhandled PacketId of {(ushort)packetId}. Remaining data has been purged.");
#if UNITY_EDITOR || DEVELOPMENT_BUILD
_parseLogger.Print(NetworkManager);
#endif
return;
}
}
}
/* Iterate cache when reader is emptied.
* This is incase the last packet received
* was a spawned, which wouldn't trigger
* the above iteration. There's no harm
* in doing this check multiple times as there's
* an exit early check. */
Objects.IterateObjectCache();
}
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
}
catch (Exception e)
{
if (NetworkManager.CanLog(LoggingType.Error))
Debug.LogError($"Client encountered an error while parsing data for packetId {packetId}. Message: {e.Message}.");
}
#endif
}
/// <summary>
/// Parses a PingPong packet.
/// </summary>
/// <param name="reader"></param>
private void ParsePingPong(PooledReader reader)
{
uint clientTick = reader.ReadUInt32(AutoPackType.Unpacked);
NetworkManager.TimeManager.ModifyPing(clientTick);
}
/// <summary>
/// Parses a received connectionId. This is received before client receives connection state change.
/// </summary>
/// <param name="reader"></param>
private void ParseAuthenticated(PooledReader reader)
{
NetworkManager networkManager = NetworkManager;
int connectionId = reader.ReadNetworkConnectionId();
//If only a client then make a new connection.
if (!networkManager.IsServer)
{
Clients.TryGetValueIL2CPP(connectionId, out Connection);
}
/* If also the server then use the servers connection
* for the connectionId. This is to resolve host problems
* where LocalConnection for client differs from the server Connection
* reference, which results in different field values. */
else
{
if (networkManager.ServerManager.Clients.TryGetValueIL2CPP(connectionId, out NetworkConnection conn))
{
Connection = conn;
}
else
{
networkManager.LogError($"Unable to lookup LocalConnection for {connectionId} as host.");
Connection = new NetworkConnection(networkManager, connectionId, false);
}
}
//If predicted spawning is enabled also get reserved Ids.
if (NetworkManager.PredictionManager.GetAllowPredictedSpawning())
{
byte count = reader.ReadByte();
Queue<int> q = Connection.PredictedObjectIds;
for (int i = 0; i < count; i++)
q.Enqueue(reader.ReadNetworkObjectId());
}
/* Set the TimeManager tick to lastReceivedTick.
* This still doesn't account for latency but
* it's the best we can do until the client gets
* a ping response. */
networkManager.TimeManager.Tick = networkManager.TimeManager.LastPacketTick;
//Mark as authenticated.
Connection.ConnectionAuthenticated();
OnAuthenticated?.Invoke();
/* Register scene objects for all scenes
* after being authenticated. This is done after
* authentication rather than when the connection
* is started because if also as server an online
* scene may already be loaded on server, but not
* for client. This means the sceneLoaded unity event
* won't fire, and since client isn't authenticated
* at the connection start phase objects won't be added. */
Objects.RegisterAndDespawnSceneObjects();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aca43cf6f20e77c4f8fcc078fd85081f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 934151ddc3910094daef3552e81ecf24
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,46 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
namespace FishNet.Managing.Client.Editing
{
[CustomEditor(typeof(ClientManager), true)]
[CanEditMultipleObjects]
public class ClientManagerEditor : Editor
{
private SerializedProperty _changeFrameRate;
private SerializedProperty _frameRate;
protected virtual void OnEnable()
{
_changeFrameRate = serializedObject.FindProperty("_changeFrameRate");
_frameRate = serializedObject.FindProperty("_frameRate");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((ClientManager)target), typeof(ClientManager), false);
GUI.enabled = true;
EditorGUILayout.PropertyField(_changeFrameRate);
if (_changeFrameRate.boolValue)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_frameRate);
EditorGUI.indentLevel--;
}
EditorGUILayout.Space();
serializedObject.ApplyModifiedProperties();
}
}
}
#endif

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cd638699438c5194ca93b15a5121d0a8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,87 @@
using FishNet.Managing.Logging;
using FishNet.Managing.Object;
using FishNet.Managing.Utility;
using FishNet.Object;
using FishNet.Object.Helping;
using FishNet.Serializing;
using FishNet.Transporting;
using FishNet.Utility.Extension;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Managing.Client
{
/// <summary>
/// Handles objects and information about objects for the local client. See ManagedObjects for inherited options.
/// </summary>
public partial class ClientObjects : ManagedObjects
{
#region Private.
/// <summary>
/// RPCLinks of currently spawned objects.
/// </summary>
private Dictionary<ushort, RpcLink> _rpcLinks = new Dictionary<ushort, RpcLink>();
#endregion
/// <summary>
/// Parses a received RPCLink.
/// </summary>
/// <param name="reader"></param>
/// <param name="index"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ParseRpcLink(PooledReader reader, ushort index, Channel channel)
{
int dataLength = Packets.GetPacketLength(ushort.MaxValue, reader, channel);
//Link index isn't stored.
if (!_rpcLinks.TryGetValueIL2CPP(index, out RpcLink link))
{
SkipDataLength(index, reader, dataLength);
return;
}
else
//Found NetworkObject for link.
if (Spawned.TryGetValueIL2CPP(link.ObjectId, out NetworkObject nob))
{
NetworkBehaviour nb = nob.NetworkBehaviours[link.ComponentIndex];
if (link.RpcType == RpcType.Target)
nb.OnTargetRpc(link.RpcHash, reader, channel);
else if (link.RpcType == RpcType.Observers)
nb.OnObserversRpc(link.RpcHash, reader, channel);
else if (link.RpcType == RpcType.Reconcile)
nb.OnReconcileRpc(link.RpcHash, reader, channel);
}
//Could not find NetworkObject.
else
{
SkipDataLength(index, reader, dataLength, link.ObjectId);
}
}
/// <summary>
/// Sets link to rpcLinks key linkIndex.
/// </summary>
/// <param name="linkIndex"></param>
/// <param name="link"></param>
internal void SetRpcLink(ushort linkIndex, RpcLink link)
{
_rpcLinks[linkIndex] = link;
}
/// <summary>
/// Removes link index keys from rpcLinks.
/// </summary>
internal void RemoveLinkIndexes(List<ushort> values)
{
if (values == null)
return;
for (int i = 0; i < values.Count; i++)
_rpcLinks.Remove(values[i]);
}
}
}

View File

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

View File

@ -0,0 +1,688 @@
using FishNet.Connection;
using FishNet.Documenting;
using FishNet.Managing.Logging;
using FishNet.Managing.Object;
using FishNet.Managing.Server;
using FishNet.Managing.Utility;
using FishNet.Object;
using FishNet.Object.Helping;
using FishNet.Serializing;
using FishNet.Transporting;
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.Client
{
/// <summary>
/// Handles objects and information about objects for the local client. See ManagedObjects for inherited options.
/// </summary>
public partial class ClientObjects : ManagedObjects
{
#region Private.
/// <summary>
/// NetworkObjects which are cached to be spawned or despawned.
/// </summary>
private ClientObjectCache _objectCache;
#endregion
internal ClientObjects(NetworkManager networkManager)
{
base.Initialize(networkManager);
_objectCache = new ClientObjectCache(this, networkManager);
}
/// <summary>
/// Called when a connection state changes for the local server.
/// </summary>
internal void OnServerConnectionState(ServerConnectionStateArgs args)
{
//Nothing needs to be done if started.
if (args.ConnectionState == LocalConnectionState.Started)
return;
/* If not started and client is active then deinitialize
* client objects first. This will let the deinit calls
* perform before the server destroys them. Ideally this
* would be done when the user shows intent to shutdown
* the server, but realistically planning for server socket
* drops is a much more universal solution.
*
* Calling StopConnection on the client will set it's local state
* to Stopping which will result in a deinit. */
if (NetworkManager.IsClient)
base.NetworkManager.ClientManager.StopConnection();
}
/// <summary>
/// Called when the connection state changes for the local client.
/// </summary>
/// <param name="args"></param>
internal void OnClientConnectionState(ClientConnectionStateArgs args)
{
/* If new state is not started then reset
* environment. */
if (args.ConnectionState != LocalConnectionState.Started)
{
_objectCache.Reset();
//If not server then deinitialize normally.
if (!base.NetworkManager.IsServer)
{
base.DespawnWithoutSynchronization(false);
}
//Otherwise invoke stop callbacks only for client side.
else
{
foreach (NetworkObject n in Spawned.Values)
n.InvokeStopCallbacks(false);
}
/* Clear spawned and scene objects as they will be rebuilt.
* Spawned would have already be cleared if DespawnSpawned
* was called but it won't hurt anything clearing an empty collection. */
base.Spawned.Clear();
base.SceneObjects.Clear();
base.LocalClientSpawned.Clear();
}
}
/// <summary>
/// Called when a scene is loaded.
/// </summary>
/// <param name="s"></param>
/// <param name="arg1"></param>
[APIExclude]
protected internal override void SceneManager_sceneLoaded(Scene s, LoadSceneMode arg1)
{
base.SceneManager_sceneLoaded(s, arg1);
if (!base.NetworkManager.IsClient)
return;
/* When a scene first loads for a client it should disable
* all network objects in that scene. The server will send
* spawn messages once it's aware client has loaded the scene. */
RegisterAndDespawnSceneObjects(s);
}
/// <summary>
/// Sends a predicted spawn to the server.
/// </summary>
internal void PredictedSpawn(NetworkObject networkObject, NetworkConnection ownerConnection)
{
//No more Ids to use.
Queue<int> predictedObjectIds = NetworkManager.ClientManager.Connection.PredictedObjectIds;
if (predictedObjectIds.Count == 0)
{
NetworkManager.LogError($"Predicted spawn for object {networkObject.name} failed because no more predicted ObjectIds remain. This usually occurs when the client is spawning excessively before the server can respond. Increasing ReservedObjectIds within the ServerManager component or reducing spawn rate could prevent this problem.");
return;
}
networkObject.PreinitializePredictedObject_Client(base.NetworkManager, predictedObjectIds.Dequeue(), ownerConnection, base.NetworkManager.ClientManager.Connection);
NetworkManager.ClientManager.Objects.AddToSpawned(networkObject, false);
networkObject.Initialize(false, true);
PooledWriter writer = WriterPool.GetWriter();
WriteSpawn(networkObject, writer);
base.NetworkManager.TransportManager.SendToServer((byte)Channel.Reliable, writer.GetArraySegment());
writer.Dispose();
}
/// <summary>
/// Writes a predicted spawn.
/// </summary>
/// <param name="nob"></param>
public void WriteSpawn(NetworkObject nob, Writer writer)
{
PooledWriter headerWriter = WriterPool.GetWriter();
headerWriter.WritePacketId(PacketId.ObjectSpawn);
headerWriter.WriteNetworkObjectForSpawn(nob);
headerWriter.WriteNetworkConnection(nob.Owner);
bool sceneObject = nob.IsSceneObject;
//Write type of spawn.
SpawnType st = SpawnType.Unset;
if (sceneObject)
st |= SpawnType.Scene;
else
st |= (nob.IsGlobal) ? SpawnType.InstantiatedGlobal : SpawnType.Instantiated;
headerWriter.WriteByte((byte)st);
//ComponentIndex for the nob. 0 is root but more appropriately there's a IsNested boolean as shown above.
headerWriter.WriteByte(nob.ComponentIndex);
//Properties on the transform which diff from serialized value.
base.WriteChangedTransformProperties(nob, sceneObject, false, headerWriter);
/* Writing a scene object. */
if (sceneObject)
{
headerWriter.WriteUInt64(nob.SceneId, AutoPackType.Unpacked);
}
/* Writing a spawned object. */
else
{
//Nested predicted spawning will be added later.
headerWriter.WriteByte((byte)SpawnParentType.Unset);
headerWriter.WriteNetworkObjectId(nob.PrefabId);
}
writer.WriteBytes(headerWriter.GetBuffer(), 0, headerWriter.Length);
//If allowed to write synctypes.
if (nob.AllowPredictedSyncTypes)
{
PooledWriter tempWriter = WriterPool.GetWriter();
WriteSyncTypes(writer, tempWriter, SyncTypeWriteType.All);
void WriteSyncTypes(Writer finalWriter, PooledWriter tWriter, SyncTypeWriteType writeType)
{
tWriter.Reset();
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
nb.WriteSyncTypesForSpawn(tWriter, writeType);
finalWriter.WriteBytesAndSize(tWriter.GetBuffer(), 0, tWriter.Length);
}
tempWriter.Dispose();
}
//Dispose of writers created in this method.
headerWriter.Dispose();
}
/// <summary>
/// Sends a predicted despawn to the server.
/// </summary>
internal void PredictedDespawn(NetworkObject networkObject)
{
PooledWriter writer = WriterPool.GetWriter();
WriteDepawn(networkObject, writer);
base.NetworkManager.TransportManager.SendToServer((byte)Channel.Reliable, writer.GetArraySegment());
writer.Dispose();
//Deinitialize after writing despawn so all the right data is sent.
networkObject.DeinitializePredictedObject_Client();
}
/// <summary>
/// Writes a predicted despawn.
/// </summary>
public void WriteDepawn(NetworkObject nob, Writer writer)
{
writer.WritePacketId(PacketId.ObjectDespawn);
writer.WriteNetworkObject(nob);
}
/// <summary>
/// Registers NetworkObjects in all scenes and despawns them.
/// </summary>
internal void RegisterAndDespawnSceneObjects()
{
for (int i = 0; i < SceneManager.sceneCount; i++)
RegisterAndDespawnSceneObjects(SceneManager.GetSceneAt(i));
}
/// <summary>
/// Adds NetworkObjects within s to SceneObjects, and despawns them.
/// </summary>
/// <param name="s"></param>
private void RegisterAndDespawnSceneObjects(Scene s)
{
ListCache<NetworkObject> nobs;
SceneFN.GetSceneNetworkObjects(s, false, out nobs);
for (int i = 0; i < nobs.Written; i++)
{
NetworkObject nob = nobs.Collection[i];
if (!nob.IsSceneObject)
continue;
base.UpdateNetworkBehavioursForSceneObject(nob, false);
if (nob.IsNetworked && nob.IsNetworked)
{
base.AddToSceneObjects(nob);
//Only run if not also server, as this already ran on server.
if (!base.NetworkManager.IsServer)
nob.gameObject.SetActive(false);
}
}
ListCaches.StoreCache(nobs);
}
/// <summary>
/// Called when a NetworkObject runs Deactivate.
/// </summary>
/// <param name="nob"></param>
internal override void NetworkObjectUnexpectedlyDestroyed(NetworkObject nob, bool asServer)
{
nob.RemoveClientRpcLinkIndexes();
base.NetworkObjectUnexpectedlyDestroyed(nob, asServer);
}
/// <summary>
/// Parses an OwnershipChange packet.
/// </summary>
/// <param name="reader"></param>
internal void ParseOwnershipChange(PooledReader reader)
{
NetworkObject nob = reader.ReadNetworkObject();
NetworkConnection newOwner = reader.ReadNetworkConnection();
if (nob != null)
nob.GiveOwnership(newOwner, false);
else
NetworkManager.LogWarning($"NetworkBehaviour could not be found when trying to parse OwnershipChange packet.");
}
/// <summary>
/// Parses a received syncVar.
/// </summary>
/// <param name="reader"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ParseSyncType(PooledReader reader, bool isSyncObject, Channel channel)
{
//cleanup this is unique to synctypes where length comes first.
//this will change once I tidy up synctypes.
ushort packetId = (isSyncObject) ? (ushort)PacketId.SyncObject : (ushort)PacketId.SyncVar;
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
int dataLength = Packets.GetPacketLength(packetId, reader, channel);
if (nb != null)
{
/* Length of data to be read for syncvars.
* This is important because syncvars are never
* a set length and data must be read through completion.
* The only way to know where completion of syncvar is, versus
* when another packet starts is by including the length. */
if (dataLength > 0)
nb.OnSyncType(reader, dataLength, isSyncObject);
}
else
{
SkipDataLength(packetId, reader, dataLength);
}
}
/// <summary>
/// Parses a
/// </summary>
/// <param name="reader"></param>
internal void ParsePredictedSpawnResult(Reader reader)
{
int usedObjectId = reader.ReadNetworkObjectId();
bool success = reader.ReadBoolean();
if (success)
{
int nextObjectId = reader.ReadNetworkObjectId();
if (nextObjectId != NetworkObject.UNSET_OBJECTID_VALUE)
NetworkManager.ClientManager.Connection.PredictedObjectIds.Enqueue(nextObjectId);
}
}
/// <summary>
/// Parses a ReconcileRpc.
/// </summary>
/// <param name="reader"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ParseReconcileRpc(PooledReader reader, Channel channel)
{
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
int dataLength = Packets.GetPacketLength((ushort)PacketId.Reconcile, reader, channel);
if (nb != null)
nb.OnReconcileRpc(null, reader, channel);
else
SkipDataLength((ushort)PacketId.ObserversRpc, reader, dataLength);
}
/// <summary>
/// Parses an ObserversRpc.
/// </summary>
/// <param name="reader"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ParseObserversRpc(PooledReader reader, Channel channel)
{
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
int dataLength = Packets.GetPacketLength((ushort)PacketId.ObserversRpc, reader, channel);
if (nb != null)
nb.OnObserversRpc(null, reader, channel);
else
SkipDataLength((ushort)PacketId.ObserversRpc, reader, dataLength);
}
/// <summary>
/// Parses a TargetRpc.
/// </summary>
/// <param name="reader"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ParseTargetRpc(PooledReader reader, Channel channel)
{
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
int dataLength = Packets.GetPacketLength((ushort)PacketId.TargetRpc, reader, channel);
if (nb != null)
nb.OnTargetRpc(null, reader, channel);
else
SkipDataLength((ushort)PacketId.TargetRpc, reader, dataLength);
}
/// <summary>
/// Caches a received spawn to be processed after all spawns and despawns are received for the tick.
/// </summary>
/// <param name="reader"></param>
internal void CacheSpawn(PooledReader reader)
{
sbyte initializeOrder;
ushort collectionId;
int objectId = reader.ReadNetworkObjectForSpawn(out initializeOrder, out collectionId, out _);
int ownerId = reader.ReadNetworkConnectionId();
SpawnType st = (SpawnType)reader.ReadByte();
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);
bool nested = SpawnTypeEnum.Contains(st, SpawnType.Nested);
int rootObjectId = (nested) ? reader.ReadNetworkObjectId() : 0;
bool sceneObject = SpawnTypeEnum.Contains(st, SpawnType.Scene);
int? parentObjectId = null;
byte? parentComponentIndex = null;
int? prefabId = null;
ulong sceneId = 0;
if (sceneObject)
ReadSceneObject(reader, out sceneId);
else
ReadSpawnedObject(reader, out parentObjectId, out parentComponentIndex, out prefabId);
ArraySegment<byte> rpcLinks = reader.ReadArraySegmentAndSize();
ArraySegment<byte> syncValues = reader.ReadArraySegmentAndSize();
/* If the objectId can be found as already spawned then check if it's predicted.
* Should the spawn be predicted then no need to continue. Later on however
* we may want to apply synctypes.
*
* Only check if not server, since if server the client doesnt need
* to predicted spawn. */
if (!base.NetworkManager.IsServerOnly && base.Spawned.TryGetValue(objectId, out NetworkObject nob))
{
//If not predicted the nob should not be in spawned.
if (!nob.PredictedSpawner.IsValid)
{
NetworkManager.LogError($"Received a spawn objectId of {objectId} which was already found in spawned, and was not predicted.");
}
//Everything is proper, apply RPC links.
else
{
PooledReader linkReader = ReaderPool.GetReader(rpcLinks, NetworkManager);
ApplyRpcLinks(nob, linkReader);
linkReader.Dispose();
}
//No further initialization needed when predicting.
return;
}
_objectCache.AddSpawn(base.NetworkManager, collectionId, objectId, initializeOrder, ownerId, st, componentIndex, rootObjectId, parentObjectId, parentComponentIndex, prefabId, localPosition, localRotation, localScale, sceneId, rpcLinks, syncValues);
}
/// <summary>
/// Caches a received despawn to be processed after all spawns and despawns are received for the tick.
/// </summary>
/// <param name="reader"></param>
internal void CacheDespawn(PooledReader reader)
{
DespawnType despawnType;
int objectId = reader.ReadNetworkObjectForDepawn(out despawnType);
_objectCache.AddDespawn(objectId, despawnType);
}
/// <summary>
/// Iterates object cache which contains spawn and despawn messages.
/// Parses the packets within the cache and ensures objects are spawned and despawned before their sync values are applied.
/// This ensures there is no chance a sync value is referencing a spawned object which does not exist yet due to it normally being spawned later in the cache.
/// </summary>
internal void IterateObjectCache()
{
_objectCache.Iterate();
}
/// <summary>
/// Gets a nested NetworkObject within it's root.
/// </summary>
/// <param name="cnob"></param>
/// <returns></returns>
internal NetworkObject GetNestedNetworkObject(CachedNetworkObject cnob)
{
NetworkObject rootNob;
int rootObjectId = cnob.RootObjectId;
byte componentIndex = cnob.ComponentIndex;
/* Spawns are processed after all spawns come in,
* this ensures no reference race conditions. Turns out because of this
* the parentNob may be in cache and not actually spawned, if it was spawned the same packet
* as this one. So when not found in the spawned collection try to
* find it in Spawning before throwing. */
rootNob = _objectCache.GetSpawnedObject(rootObjectId);
//If still null, that's not good.
if (rootNob == null)
{
NetworkManager.LogError($"Nested spawned object with componentIndex of {componentIndex} and a parentId of {rootObjectId} could not be spawned because parent was not found.");
return null;
}
NetworkObject nob = null;
List<NetworkObject> childNobs = rootNob.ChildNetworkObjects;
//Find nob with component index.
for (int i = 0; i < childNobs.Count; i++)
{
if (childNobs[i].ComponentIndex == componentIndex)
{
nob = childNobs[i];
break;
}
}
//If child nob was not found.
if (nob == null)
{
NetworkManager.LogError($"Nested spawned object with componentIndex of {componentIndex} could not be found as a child NetworkObject of {rootNob.name}.");
return null;
}
return nob;
}
/// <summary>
/// Applies RPCLinks to a NetworkObject.
/// </summary>
internal void ApplyRpcLinks(NetworkObject nob, Reader reader)
{
List<ushort> rpcLinkIndexes = new List<ushort>();
//Apply rpcLinks.
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
{
int length = reader.ReadInt32();
int readerStart = reader.Position;
while (reader.Position - readerStart < length)
{
//Index of RpcLink.
ushort linkIndex = reader.ReadUInt16();
RpcLink link = new RpcLink(nob.ObjectId, nb.ComponentIndex,
//RpcHash.
reader.ReadUInt16(),
//ObserverRpc.
(RpcType)reader.ReadByte());
//Add to links.
SetRpcLink(linkIndex, link);
rpcLinkIndexes.Add(linkIndex);
}
}
nob.SetRpcLinkIndexes(rpcLinkIndexes);
}
/// <summary>
/// Instantiates a NetworkObject if required and sets transform values.
/// </summary>
internal NetworkObject GetInstantiatedNetworkObject(CachedNetworkObject cnob)
{
if (cnob.PrefabId == null)
{
NetworkManager.LogError($"PrefabId for {cnob.ObjectId} is null. Object will not spawn.");
return null;
}
NetworkManager networkManager = base.NetworkManager;
int prefabId = cnob.PrefabId.Value;
NetworkObject result;
if (prefabId == NetworkObject.UNSET_OBJECTID_VALUE)
{
NetworkManager.LogError($"Spawned object has an invalid prefabId. Make sure all objects which are being spawned over the network are within SpawnableObjects on the NetworkManager.");
return null;
}
ushort collectionId = cnob.CollectionId;
//PrefabObjects to get the prefab from.
PrefabObjects prefabObjects = networkManager.GetPrefabObjects<PrefabObjects>(collectionId, false);
//Not found for collectionId > 0. This means the user likely did not setup the collection on client.
if (prefabObjects == null && collectionId > 0)
{
networkManager.LogError($"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.");
return null;
}
//Only instantiate if not host.
if (!networkManager.IsHost)
{
Transform parentTransform = null;
bool hasParent = (cnob.ParentObjectId != null);
//Set parentTransform if there's a parent object.
if (hasParent)
{
int objectId = cnob.ParentObjectId.Value;
NetworkObject nob = _objectCache.GetSpawnedObject(objectId);
if (nob == null)
{
NetworkObject prefab = prefabObjects.GetObject(false, prefabId);
networkManager.LogError($"NetworkObject not found for ObjectId {objectId}. Prefab {prefab.name} will be instantiated without parent synchronization.");
}
else
{
//If parent object is a network behaviour then find the component.
if (cnob.ParentIsNetworkBehaviour)
{
byte componentIndex = cnob.ComponentIndex;
NetworkBehaviour nb = nob.GetNetworkBehaviour(componentIndex, false);
if (nb != null)
{
parentTransform = nb.transform;
}
else
{
NetworkObject prefab = prefabObjects.GetObject(false, prefabId);
networkManager.LogError($"NetworkBehaviour on index {componentIndex} could nto be found within NetworkObject {nob.name} with ObjectId {objectId}. Prefab {prefab.name} will be instantiated without parent synchronization.");
}
}
//The networkObject is the parent.
else
{
parentTransform = nob.transform;
}
}
}
result = networkManager.GetPooledInstantiated(prefabId, collectionId, false);
Transform t = result.transform;
t.SetParent(parentTransform, true);
//Only need to set IsGlobal also if not host.
bool isGlobal = SpawnTypeEnum.Contains(cnob.SpawnType, SpawnType.InstantiatedGlobal);
result.SetIsGlobal(isGlobal);
}
//If host then find server instantiated object.
else
{
ServerObjects so = networkManager.ServerManager.Objects;
if (!so.Spawned.TryGetValueIL2CPP(cnob.ObjectId, out result))
result = so.GetFromPending(cnob.ObjectId);
if (result == null)
networkManager.LogError($"ObjectId {cnob.ObjectId} could not be found in Server spawned, nor Server pending despawn.");
}
return result;
}
/// <summary>
/// Gets a NetworkObject from Spawned, or object cache.
/// </summary>
/// <param name="cnob"></param>
/// <returns></returns>
internal NetworkObject GetSpawnedNetworkObject(CachedNetworkObject cnob)
{
NetworkObject nob;
//Try checking already spawned objects first.
if (base.Spawned.TryGetValueIL2CPP(cnob.ObjectId, out nob))
{
return nob;
}
/* If not found in already spawned objects see if
* the networkObject is in the objectCache. It's possible the despawn
* came immediately or shortly after the spawn message, before
* the object has been initialized. */
else
{
nob = _objectCache.GetInCached(cnob.ObjectId, ClientObjectCache.CacheSearchType.Any);
/* Nob may be null if it's a child object being despawned, and the
* parent despawn already occurred. */
return nob;
}
}
/// <summary>
/// Finishes reading a scene object.
/// </summary>
private void ReadSceneObject(PooledReader reader, out ulong sceneId)
{
sceneId = reader.ReadUInt64(AutoPackType.Unpacked);
}
/// <summary>
/// Finishes reading a spawned object, and instantiates the object.
/// </summary>
private void ReadSpawnedObject(PooledReader reader, out int? parentObjectId, out byte? parentComponentIndex, out int? prefabId)
{
//Parent.
SpawnParentType spt = (SpawnParentType)reader.ReadByte();
//Defaults.
parentObjectId = null;
parentComponentIndex = null;
if (spt == SpawnParentType.NetworkObject)
{
int objectId = reader.ReadNetworkObjectId();
if (objectId != NetworkObject.UNSET_OBJECTID_VALUE)
parentObjectId = objectId;
}
else if (spt == SpawnParentType.NetworkBehaviour)
{
reader.ReadNetworkBehaviour(out int objectId, out byte componentIndex);
if (objectId != NetworkObject.UNSET_OBJECTID_VALUE)
{
parentObjectId = objectId;
parentComponentIndex = componentIndex;
}
}
prefabId = (ushort)reader.ReadNetworkObjectId();
}
}
}

View File

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

View File

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

View File

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