[Add] FishNet
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b8fdb465dad5174893b51876d9cc64c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,133 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Broadcast.Helping;
|
||||
using FishNet.Managing.Utility;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Transporting;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using FishNet.Managing.Transporting;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Client
|
||||
{
|
||||
public sealed partial class ClientManager : MonoBehaviour
|
||||
{
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Handler for registered broadcasts.
|
||||
/// </summary>
|
||||
private readonly Dictionary<ushort, BroadcastHandlerBase> _broadcastHandlers = new();
|
||||
#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, Channel> handler) where T : struct, IBroadcast
|
||||
{
|
||||
if (handler == null)
|
||||
{
|
||||
NetworkManager.LogError($"Broadcast cannot be registered because handler is null. This may occur when trying to register to objects which require initialization, such as events.");
|
||||
return;
|
||||
}
|
||||
|
||||
ushort key = BroadcastExtensions.GetKey<T>();
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
NetworkManager.SetBroadcastName<T>(key);
|
||||
#endif
|
||||
|
||||
// Create new IBroadcastHandler if needed.
|
||||
BroadcastHandlerBase bhs;
|
||||
if (!_broadcastHandlers.TryGetValueIL2CPP(key, out bhs))
|
||||
{
|
||||
bhs = new ServerBroadcastHandler<T>();
|
||||
_broadcastHandlers.Add(key, bhs);
|
||||
}
|
||||
// Register handler to IBroadcastHandler.
|
||||
bhs.RegisterHandler(handler);
|
||||
}
|
||||
|
||||
/// <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, Channel> handler) where T : struct, IBroadcast
|
||||
{
|
||||
ushort key = BroadcastExtensions.GetKey<T>();
|
||||
if (_broadcastHandlers.TryGetValueIL2CPP(key, out BroadcastHandlerBase bhs))
|
||||
bhs.UnregisterHandler(handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a received broadcast.
|
||||
/// </summary>
|
||||
private void ParseBroadcast(PooledReader reader, Channel channel)
|
||||
{
|
||||
int readerPositionAfterDebug = reader.Position;
|
||||
|
||||
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 BroadcastHandlerBase bhs))
|
||||
{
|
||||
bhs.InvokeHandlers(reader, channel);
|
||||
}
|
||||
else
|
||||
{
|
||||
reader.Skip(dataLength);
|
||||
}
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
{
|
||||
string broadcastName = NetworkManager.GetBroadcastName(key);
|
||||
_networkTrafficStatistics.AddInboundPacketIdData(PacketId.Broadcast, broadcastName, reader.Position - readerPositionAfterDebug + TransportManager.PACKETID_LENGTH, gameObject: null, asServer: false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
BroadcastsSerializers.WriteBroadcast(NetworkManager, writer, message, ref channel);
|
||||
ArraySegment<byte> segment = writer.GetArraySegment();
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
{
|
||||
ushort key = BroadcastExtensions.GetKey<T>();
|
||||
string broadcastName = NetworkManager.GetBroadcastName(key);
|
||||
|
||||
/* Do not include packetId length -- its written in the 'WriteBroadcast' method. */
|
||||
_networkTrafficStatistics.AddOutboundPacketIdData(PacketId.Broadcast, broadcastName, writer.Length, gameObject: null, asServer: false);
|
||||
}
|
||||
#endif
|
||||
|
||||
NetworkManager.TransportManager.SendToServer((byte)channel, segment);
|
||||
writer.Store();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97ef4be4cfcc6a54d80a65a5c8d325d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Client/ClientManager.Broadcast.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,6 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Client
|
||||
{
|
||||
public sealed partial class ClientManager : MonoBehaviour { }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa61343b56a8f4942b8e6c998ed319ba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Client/ClientManager.QOL.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,777 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Debugging;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Transporting.Multipass;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FishNet.Managing.Statistic;
|
||||
using Unity.Profiling;
|
||||
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>
|
||||
/// This is set true if the server has notified the client it is using a development build.
|
||||
/// Value is set before authentication.
|
||||
/// </summary>
|
||||
public bool IsServerDevelopment { get; private set; }
|
||||
/// <summary>
|
||||
/// Called after local client has authenticated.
|
||||
/// </summary>
|
||||
public event Action OnAuthenticated;
|
||||
/// <summary>
|
||||
/// Called when the local client connection to the server has timed out.
|
||||
/// This is called immediately before disconnecting.
|
||||
/// </summary>
|
||||
public event Action OnClientTimeOut;
|
||||
/// <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>
|
||||
/// Called when the server sends all currently connected clients.
|
||||
/// This is only available when using ServerManager.ShareIds.
|
||||
/// </summary>
|
||||
public event Action<ConnectedClientsArgs> OnConnectedClients;
|
||||
/// <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();
|
||||
/// <summary>
|
||||
/// NetworkManager for client.
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
public NetworkManager NetworkManager { get; private set; }
|
||||
#endregion
|
||||
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// What platforms to enable remote server timeout.
|
||||
/// </summary>
|
||||
[Tooltip("What platforms to enable remote server timeout.")]
|
||||
[SerializeField]
|
||||
private RemoteTimeoutType _remoteServerTimeout = RemoteTimeoutType.Development;
|
||||
/// <summary>
|
||||
/// How long in seconds server must go without sending any packets before the local client disconnects. This is independent of any transport settings.
|
||||
/// </summary>
|
||||
[Tooltip("How long in seconds server must go without sending any packets before the local client disconnects. This is independent of any transport settings.")]
|
||||
[Range(1, ServerManager.MAXIMUM_REMOTE_CLIENT_TIMEOUT_DURATION)]
|
||||
[SerializeField]
|
||||
private ushort _remoteServerTimeoutDuration = 60;
|
||||
|
||||
/// <summary>
|
||||
/// Sets timeout settings. Can be used at runtime.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void SetRemoteServerTimeout(RemoteTimeoutType timeoutType, ushort duration)
|
||||
{
|
||||
_remoteServerTimeout = timeoutType;
|
||||
duration = (ushort)Mathf.Clamp(duration, 1, ServerManager.MAXIMUM_REMOTE_CLIENT_TIMEOUT_DURATION);
|
||||
_remoteServerTimeoutDuration = duration;
|
||||
}
|
||||
|
||||
// todo add remote server timeout (see ServerManager.RemoteClientTimeout).
|
||||
/// <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>
|
||||
/// 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;
|
||||
[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>
|
||||
/// Sets the maximum frame rate the client may run at. Calling this method will enable ChangeFrameRate.
|
||||
/// </summary>
|
||||
/// <param name = "value">New value.</param>
|
||||
public void SetFrameRate(ushort value)
|
||||
{
|
||||
_frameRate = (ushort)Mathf.Clamp(value, 0, NetworkManager.MAXIMUM_FRAMERATE);
|
||||
_changeFrameRate = true;
|
||||
if (NetworkManager != null)
|
||||
NetworkManager.UpdateFramerate();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Last unscaled time client got a packet.
|
||||
/// </summary>
|
||||
private float _lastPacketTime;
|
||||
/// <summary>
|
||||
/// Used to read splits.
|
||||
/// </summary>
|
||||
private SplitReader _splitReader = new();
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
private NetworkTrafficStatistics _networkTrafficStatistics;
|
||||
#endregion
|
||||
|
||||
#region Private Profiler Markers
|
||||
private static readonly ProfilerMarker _pm_OnPostTick = new("ClientManager.TimeManager_OnPostTick()");
|
||||
private static readonly ProfilerMarker _pm_Transport_OnClientConnectionState = new("ClientManager.Transport_OnClientConnectionState(ClientConnectionStateArgs)");
|
||||
private static readonly ProfilerMarker _pm_Transport_OnClientReceivedData = new("ClientManager.Transport_OnClientReceivedData(ClientReceivedDataArgs)");
|
||||
private static readonly ProfilerMarker _pm_TransportManager_OnIterateIncomingEnd = new("ClientManager.TransportManager_OnIterateIncomingEnd(bool)");
|
||||
private static readonly ProfilerMarker _pm_ParseReceived = new("ClientManager.ParseReceived(ClientReceivedDataArgs)");
|
||||
private static readonly ProfilerMarker _pm_ParseReader = new("ClientManager.ParseReader(PooledReader, Channel, bool)");
|
||||
private static readonly ProfilerMarker _pm_ParseReader_ReadPacketId = new("ClientManager.ParseReader.ReadPacketId()");
|
||||
private static readonly ProfilerMarker _pm_ParseReader_HandlePacket = new("ClientManager.ParseReader.HandlePacket()");
|
||||
private static readonly ProfilerMarker _pm_ParseReader_StateUpdate = new("ClientManager.ParseReader.StateUpdate()");
|
||||
private static readonly ProfilerMarker _pm_ParseReader_Broadcast = new("ClientManager.ParseReader.Broadcast()");
|
||||
private static readonly ProfilerMarker _pm_ParseReader_PingPong = new("ClientManager.ParseReader.PingPong()");
|
||||
private static readonly ProfilerMarker _pm_ParseReader_TimingUpdate = new("ClientManager.ParseReader.TimingUpdate()");
|
||||
private static readonly ProfilerMarker _pm_ParseReader_Authenticated = new("ClientManager.ParseReader.Authenticated()");
|
||||
private static readonly ProfilerMarker _pm_ParseReader_Disconnect = new("ClientManager.ParseReader.Disconnect()");
|
||||
private static readonly ProfilerMarker _pm_ParseReader_Version = new("ClientManager.ParseReader.Version()");
|
||||
#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;
|
||||
|
||||
manager.StatisticsManager.TryGetNetworkTrafficStatistics(out _networkTrafficStatistics);
|
||||
|
||||
Objects = new(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, Channel channel)
|
||||
{
|
||||
// If connecting invoke after added to clients, otherwise invoke before removed.
|
||||
RemoteConnectionStateArgs rcs = new(args.Connected ? RemoteConnectionState.Started : RemoteConnectionState.Stopped, args.Id, -1);
|
||||
|
||||
if (args.Connected)
|
||||
{
|
||||
Clients[args.Id] = new(NetworkManager, args.Id, -1, false);
|
||||
OnRemoteConnectionState?.Invoke(rcs);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnRemoteConnectionState?.Invoke(rcs);
|
||||
if (Clients.TryGetValue(args.Id, out NetworkConnection c))
|
||||
{
|
||||
c.ResetState();
|
||||
Clients.Remove(args.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the server sends all currently connected clients.
|
||||
/// </summary>
|
||||
/// <param name = "args"></param>
|
||||
private void OnConnectedClientsBroadcast(ConnectedClientsBroadcast args, Channel channel)
|
||||
{
|
||||
NetworkManager.ClearClientsCollection(Clients);
|
||||
|
||||
List<int> collection = args.Values;
|
||||
// No connected clients except self.
|
||||
if (collection == null)
|
||||
{
|
||||
collection = new();
|
||||
}
|
||||
// Other clients.
|
||||
else
|
||||
{
|
||||
int count = collection.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int id = collection[i];
|
||||
Clients[id] = new(NetworkManager, id, -1, false);
|
||||
}
|
||||
}
|
||||
|
||||
OnConnectedClients?.Invoke(new(collection));
|
||||
}
|
||||
|
||||
/// <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;
|
||||
NetworkManager.TimeManager.OnPostTick += TimeManager_OnPostTick;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.TransportManager.OnIterateIncomingEnd -= TransportManager_OnIterateIncomingEnd;
|
||||
NetworkManager.TransportManager.Transport.OnClientReceivedData -= Transport_OnClientReceivedData;
|
||||
NetworkManager.TransportManager.Transport.OnClientConnectionState -= Transport_OnClientConnectionState;
|
||||
NetworkManager.TimeManager.OnPostTick -= TimeManager_OnPostTick;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the transport index being used for the local client.
|
||||
/// If only one transport is used this will return 0. If Multipass is being used this will return the client's transport in multipass.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int GetTransportIndex()
|
||||
{
|
||||
if (NetworkManager.TransportManager.Transport is Multipass mp)
|
||||
return mp.ClientTransport.Index;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops the local client connection.
|
||||
/// </summary>
|
||||
public bool StopConnection()
|
||||
{
|
||||
return NetworkManager.TransportManager.Transport.StopConnection(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the local client connection.
|
||||
/// </summary>
|
||||
public bool StartConnection()
|
||||
{
|
||||
return NetworkManager.TransportManager.Transport.StartConnection(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the transport address and starts the local client connection.
|
||||
/// </summary>
|
||||
public bool StartConnection(string address)
|
||||
{
|
||||
NetworkManager.TransportManager.Transport.SetClientAddress(address);
|
||||
return StartConnection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the transport address and port, and starts the local client connection.
|
||||
/// </summary>
|
||||
public bool StartConnection(string address, ushort port)
|
||||
{
|
||||
NetworkManager.TransportManager.Transport.SetClientAddress(address);
|
||||
NetworkManager.TransportManager.Transport.SetPort(port);
|
||||
return StartConnection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a connection state changes for the local client.
|
||||
/// </summary>
|
||||
/// <param name = "args"></param>
|
||||
private void Transport_OnClientConnectionState(ClientConnectionStateArgs args)
|
||||
{
|
||||
using (_pm_Transport_OnClientConnectionState.Auto())
|
||||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastPacketTime = Time.unscaledTime;
|
||||
// Send version.
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
writer.WritePacketIdUnpacked(PacketId.Version);
|
||||
writer.WriteString(NetworkManager.FISHNET_VERSION);
|
||||
NetworkManager.TransportManager.SendToServer((byte)Channel.Reliable, writer.GetArraySegment());
|
||||
WriterPool.Store(writer);
|
||||
}
|
||||
|
||||
if (NetworkManager.CanLog(LoggingType.Common))
|
||||
{
|
||||
Transport t = NetworkManager.TransportManager.GetTransport(args.TransportIndex);
|
||||
string tName = t == null ? "Unknown" : t.GetType().Name;
|
||||
string socketInformation = string.Empty;
|
||||
if (state == LocalConnectionState.Starting)
|
||||
socketInformation = $" Server IP is {t.GetClientAddress()}, port is {t.GetPort()}.";
|
||||
NetworkManager.Log($"Local client is {state.ToString().ToLower()} for {tName}.{socketInformation}");
|
||||
}
|
||||
|
||||
NetworkManager.UpdateFramerate();
|
||||
OnClientConnectionState?.Invoke(args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a socket receives data.
|
||||
/// </summary>
|
||||
private void Transport_OnClientReceivedData(ClientReceivedDataArgs args)
|
||||
{
|
||||
using (_pm_Transport_OnClientReceivedData.Auto())
|
||||
{
|
||||
ParseReceived(args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after IterateIncoming has completed.
|
||||
/// </summary>
|
||||
private void TransportManager_OnIterateIncomingEnd(bool server)
|
||||
{
|
||||
using (_pm_TransportManager_OnIterateIncomingEnd.Auto())
|
||||
{
|
||||
/* 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)
|
||||
{
|
||||
using (_pm_ParseReceived.Auto())
|
||||
{
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.PacketBundleReceived(asServer: false);
|
||||
#endif
|
||||
|
||||
_lastPacketTime = Time.unscaledTime;
|
||||
|
||||
ArraySegment<byte> segment;
|
||||
if (NetworkManager.TransportManager.HasIntermediateLayer)
|
||||
segment = NetworkManager.TransportManager.ProcessIntermediateIncoming(args.Data, true);
|
||||
else
|
||||
segment = args.Data;
|
||||
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddInboundSocketData((ulong)segment.Count, asServer: false);
|
||||
|
||||
if (segment.Count <= TransportManager.UNPACKED_TICK_LENGTH)
|
||||
return;
|
||||
|
||||
PooledReader reader = ReaderPool.Retrieve(segment, NetworkManager, Reader.DataSource.Server);
|
||||
TimeManager tm = NetworkManager.TimeManager;
|
||||
tm.LastPacketTick.Update(reader.ReadTickUnpacked(), EstimatedTick.OldTickOption.Discard, false);
|
||||
ParseReader(reader, args.Channel);
|
||||
ReaderPool.Store(reader);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ParseReader(PooledReader reader, Channel channel, bool print = false)
|
||||
{
|
||||
using (_pm_ParseReader.Auto())
|
||||
{
|
||||
PacketId packetId = PacketId.Unset;
|
||||
#if !DEVELOPMENT
|
||||
try
|
||||
{
|
||||
#endif
|
||||
Reader.DataSource dataSource = Reader.DataSource.Server;
|
||||
/* 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)
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
NetworkManager.PacketIdHistory.ReceivedPacket(PacketId.Split, packetFromServer: true);
|
||||
#endif
|
||||
// Skip packetId.
|
||||
reader.ReadPacketId();
|
||||
int expectedMessages;
|
||||
_splitReader.GetHeader(reader, out expectedMessages);
|
||||
_splitReader.Write(NetworkManager.TimeManager.LastPacketTick.LastRemoteTick, 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;
|
||||
|
||||
reader.Initialize(fullMessage, NetworkManager, dataSource);
|
||||
}
|
||||
|
||||
while (reader.Remaining > 0)
|
||||
{
|
||||
using (_pm_ParseReader_ReadPacketId.Auto())
|
||||
{
|
||||
packetId = reader.ReadPacketId();
|
||||
#if DEVELOPMENT
|
||||
NetworkManager.PacketIdHistory.ReceivedPacket(packetId, packetFromServer: true);
|
||||
// if (!NetworkManager.IsServerStarted)
|
||||
// print = true;
|
||||
// if (print)
|
||||
// {
|
||||
// if (packetId == PacketId.ObserversRpc)
|
||||
// Debug.Log($"PacketId {packetId} - Remaining {reader.Remaining}.");
|
||||
// else
|
||||
// Debug.LogWarning($"PacketId {packetId} - Remaining {reader.Remaining}.");
|
||||
// }
|
||||
// print = false;
|
||||
#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.ReadSpawn(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();
|
||||
using (_pm_ParseReader_HandlePacket.Auto())
|
||||
{
|
||||
// Then process packet normally.
|
||||
if ((ushort)packetId >= NetworkManager.StartingRpcLinkIndex)
|
||||
{
|
||||
Objects.ParseRpcLink(reader, (ushort)packetId, channel);
|
||||
}
|
||||
else if (packetId == PacketId.StateUpdate)
|
||||
{
|
||||
using (_pm_ParseReader_StateUpdate.Auto())
|
||||
{
|
||||
NetworkManager.PredictionManager.ParseStateUpdate(reader, channel);
|
||||
}
|
||||
}
|
||||
else if (packetId == PacketId.Replicate)
|
||||
{
|
||||
Objects.ParseReplicateRpc(reader, null, channel);
|
||||
}
|
||||
else if (packetId == PacketId.Reconcile)
|
||||
{
|
||||
Objects.ParseReconcileRpc(reader, channel);
|
||||
}
|
||||
else if (packetId == PacketId.ObserversRpc)
|
||||
{
|
||||
Objects.ParseObserversRpc(reader, channel);
|
||||
}
|
||||
else if (packetId == PacketId.TargetRpc)
|
||||
{
|
||||
Objects.ParseTargetRpc(reader, channel);
|
||||
}
|
||||
else if (packetId == PacketId.Broadcast)
|
||||
{
|
||||
using (_pm_ParseReader_Broadcast.Auto())
|
||||
{
|
||||
ParseBroadcast(reader, channel);
|
||||
}
|
||||
}
|
||||
else if (packetId == PacketId.PingPong)
|
||||
{
|
||||
using (_pm_ParseReader_PingPong.Auto())
|
||||
{
|
||||
ParsePingPong(reader);
|
||||
}
|
||||
}
|
||||
else if (packetId == PacketId.SyncType)
|
||||
{
|
||||
Objects.ParseSyncType(reader, channel);
|
||||
}
|
||||
else if (packetId == PacketId.PredictedSpawnResult)
|
||||
{
|
||||
Objects.ParsePredictedSpawnResult(reader);
|
||||
}
|
||||
else if (packetId == PacketId.TimingUpdate)
|
||||
{
|
||||
using (_pm_ParseReader_TimingUpdate.Auto())
|
||||
{
|
||||
NetworkManager.TimeManager.ParseTimingUpdate(reader);
|
||||
}
|
||||
}
|
||||
else if (packetId == PacketId.OwnershipChange)
|
||||
{
|
||||
Objects.ParseOwnershipChange(reader);
|
||||
}
|
||||
else if (packetId == PacketId.Authenticated)
|
||||
{
|
||||
using (_pm_ParseReader_Authenticated.Auto())
|
||||
{
|
||||
ParseAuthenticated(reader);
|
||||
}
|
||||
}
|
||||
else if (packetId == PacketId.Disconnect)
|
||||
{
|
||||
using (_pm_ParseReader_Disconnect.Auto())
|
||||
{
|
||||
reader.Clear();
|
||||
StopConnection();
|
||||
}
|
||||
}
|
||||
else if (packetId == PacketId.Version)
|
||||
{
|
||||
using (_pm_ParseReader_Version.Auto())
|
||||
{
|
||||
ParseVersion(reader);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.LogError($"Client received an unhandled PacketId of {(ushort)packetId} on channel {channel}. Remaining data has been purged.");
|
||||
#if DEVELOPMENT
|
||||
NetworkManager.LogError(NetworkManager.PacketIdHistory.GetReceivedPacketIds(packetsFromServer: true));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEVELOPMENT
|
||||
if (print)
|
||||
Debug.Log($"Reader remaining {reader.Remaining}");
|
||||
#endif
|
||||
}
|
||||
|
||||
/* 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 !DEVELOPMENT
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
NetworkManager.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)
|
||||
{
|
||||
int readerPositionAfterDebug = reader.Position;
|
||||
|
||||
uint clientTick = reader.ReadTickUnpacked();
|
||||
NetworkManager.TimeManager.ModifyPing(clientTick);
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddInboundPacketIdData(PacketId.PingPong, string.Empty, reader.Position - readerPositionAfterDebug + TransportManager.PACKETID_LENGTH, gameObject: null, asServer: false);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a Version packet.
|
||||
/// </summary>
|
||||
/// <param name = "reader"></param>
|
||||
private void ParseVersion(PooledReader reader)
|
||||
{
|
||||
IsServerDevelopment = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
/// <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();
|
||||
|
||||
bool isServerStarted = networkManager.IsServerStarted;
|
||||
|
||||
// If only a client then make a new connection.
|
||||
if (!isServerStarted)
|
||||
{
|
||||
Clients.TryGetValueIL2CPP(connectionId, out Connection);
|
||||
/* This is bad and should never happen unless the connection is dropping
|
||||
* while receiving authenticated. Would have to be a crazy race condition
|
||||
* but with the network anything is possible. */
|
||||
if (Connection == null)
|
||||
{
|
||||
NetworkManager.LogWarning($"Client connection could not be found while parsing authenticated status. This usually occurs when the client is receiving a packet immediately before losing connection.");
|
||||
Connection = new(networkManager, connectionId, GetTransportIndex(), false);
|
||||
}
|
||||
}
|
||||
/* 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(networkManager, connectionId, GetTransportIndex(), false);
|
||||
}
|
||||
}
|
||||
|
||||
// If predicted spawning is enabled also get reserved Ids.
|
||||
if (NetworkManager.ServerManager.GetAllowPredictedSpawning())
|
||||
{
|
||||
int count = (int)reader.ReadSignedPackedWhole();
|
||||
Queue<int> q = Connection.PredictedObjectIds;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
/* If host then just read Ids, but do not
|
||||
* enqueue as server side already did so. */
|
||||
int id = reader.ReadNetworkObjectId();
|
||||
if (!isServerStarted)
|
||||
q.Enqueue(id);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
if (!networkManager.IsServerStarted)
|
||||
networkManager.TimeManager.Tick = networkManager.TimeManager.LastPacketTick.LastRemoteTick;
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the TimeManager calls OnPostTick.
|
||||
/// </summary>
|
||||
private void TimeManager_OnPostTick()
|
||||
{
|
||||
using (_pm_OnPostTick.Auto())
|
||||
{
|
||||
CheckServerTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to timeout client connections.
|
||||
/// </summary>
|
||||
private void CheckServerTimeout()
|
||||
{
|
||||
/* Not connected or host. There should be no way
|
||||
* for server to drop and client not know about it as host.
|
||||
* This would mean a game crash or force close in which
|
||||
* the client would be gone as well anyway. */
|
||||
if (!Started || NetworkManager.IsServerStarted)
|
||||
return;
|
||||
if (_remoteServerTimeout == RemoteTimeoutType.Disabled)
|
||||
return;
|
||||
#if DEVELOPMENT
|
||||
// If development but not set to development return.
|
||||
if (_remoteServerTimeout != RemoteTimeoutType.Development)
|
||||
return;
|
||||
#endif
|
||||
// Wait two timing intervals to give packets a chance to come through.
|
||||
if (NetworkManager.SceneManager.IsIteratingQueue(2f))
|
||||
return;
|
||||
|
||||
/* ServerManager version only checks every so often
|
||||
* to perform iterations over time so the checks are not
|
||||
* impactful on the CPU. The client however can check every tick
|
||||
* since it's simple math. */
|
||||
if (Time.unscaledTime - _lastPacketTime > _remoteServerTimeoutDuration)
|
||||
{
|
||||
OnClientTimeOut?.Invoke();
|
||||
NetworkManager.Log($"Server has timed out. You can modify this feature on the ClientManager component.");
|
||||
StopConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aca43cf6f20e77c4f8fcc078fd85081f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Client/ClientManager.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 311ccd7ecfc60694bb8a40a3af3a76f6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,57 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Client.Editing
|
||||
{
|
||||
[CustomEditor(typeof(ClientManager), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class ClientManagerEditor : Editor
|
||||
{
|
||||
private SerializedProperty _remoteServerTimeout;
|
||||
private SerializedProperty _remoteServerTimeoutDuration;
|
||||
private SerializedProperty _changeFrameRate;
|
||||
private SerializedProperty _frameRate;
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_remoteServerTimeout = serializedObject.FindProperty(nameof(_remoteServerTimeout));
|
||||
_remoteServerTimeoutDuration = serializedObject.FindProperty(nameof(_remoteServerTimeoutDuration));
|
||||
_changeFrameRate = serializedObject.FindProperty(nameof(_changeFrameRate));
|
||||
_frameRate = serializedObject.FindProperty(nameof(_frameRate));
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((ClientManager)target), typeof(ClientManager), false);
|
||||
GUI.enabled = true;
|
||||
|
||||
EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.PropertyField(_remoteServerTimeout);
|
||||
if ((RemoteTimeoutType)_remoteServerTimeout.intValue != RemoteTimeoutType.Disabled)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_remoteServerTimeoutDuration, new GUIContent("Timeout"));
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(_changeFrameRate);
|
||||
if (_changeFrameRate.boolValue)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_frameRate);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a652d51a1efa8a442966e885e2736599
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Client/Editor/ClientManagerEditor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3956bb4fb9de7644d9070720d0e5c4c7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,100 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Managing.Utility;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Collections.Generic;
|
||||
|
||||
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();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Parses a received RPCLink.
|
||||
/// </summary>
|
||||
/// <param name = "reader"></param>
|
||||
/// <param name = "index"></param>
|
||||
internal void ParseRpcLink(PooledReader reader, ushort index, Channel channel)
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining, out string rpcInformation, out uint expectedReadAmount);
|
||||
#endif
|
||||
int readerStartAfterDebug = reader.Position;
|
||||
|
||||
int dataLength;
|
||||
// Link index isn't stored.
|
||||
if (!_rpcLinks.TryGetValueIL2CPP(index, out RpcLink link))
|
||||
{
|
||||
dataLength = Packets.GetPacketLength(ushort.MaxValue, reader, channel);
|
||||
SkipDataLength(index, reader, dataLength);
|
||||
}
|
||||
// Found NetworkObject for link.
|
||||
else if (Spawned.TryGetValueIL2CPP(link.ObjectId, out NetworkObject nob))
|
||||
{
|
||||
// Still call GetPacketLength to remove any extra bytes at the front of the reader.
|
||||
NetworkBehaviour nb = nob.NetworkBehaviours[link.ComponentIndex];
|
||||
if (link.RpcPacketId == PacketId.TargetRpc)
|
||||
{
|
||||
Packets.GetPacketLength((ushort)PacketId.TargetRpc, reader, channel);
|
||||
nb.ReadTargetRpc(readerStartAfterDebug, fromRpcLink: true, link.RpcHash, reader, channel);
|
||||
}
|
||||
else if (link.RpcPacketId == PacketId.ObserversRpc)
|
||||
{
|
||||
Packets.GetPacketLength((ushort)PacketId.ObserversRpc, reader, channel);
|
||||
nb.ReadObserversRpc(readerStartAfterDebug, fromRpcLink: true, link.RpcHash, reader, channel);
|
||||
}
|
||||
else if (link.RpcPacketId == PacketId.Reconcile)
|
||||
{
|
||||
Packets.GetPacketLength((ushort)PacketId.Reconcile, reader, channel);
|
||||
nb.OnReconcileRpc(readerStartAfterDebug, link.RpcHash, reader, channel);
|
||||
}
|
||||
}
|
||||
// Could not find NetworkObject.
|
||||
else
|
||||
{
|
||||
dataLength = Packets.GetPacketLength(index, reader, channel);
|
||||
SkipDataLength(index, reader, dataLength, link.ObjectId);
|
||||
}
|
||||
|
||||
#if DEVELOPMENT
|
||||
NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: true, NetworkManager, reader, startReaderRemaining, rpcInformation, expectedReadAmount, channel);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a20cc3f399aa614c931c9b45205937b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.RpcLinks.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,788 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
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 GameKit.Dependencies.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using FishNet.Serializing.Helping;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Unity.Profiling;
|
||||
|
||||
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
|
||||
|
||||
#region Private Profiler Markers
|
||||
private static readonly ProfilerMarker _pm_ParseOwnershipChange = new("ClientObjects.ParseOwnershipChange(PooledReader)");
|
||||
private static readonly ProfilerMarker _pm_ParseSyncType = new("ClientObjects.ParseSyncType(PooledReader, Channel)");
|
||||
private static readonly ProfilerMarker _pm_ParsePredictedSpawnResult = new("ClientObjects.ParsePredictedSpawnResult(PooledReader)");
|
||||
private static readonly ProfilerMarker _pm_ParseReconcileRpc = new("ClientObjects.ParseReconcileRpc(PooledReader, Channel)");
|
||||
private static readonly ProfilerMarker _pm_ParseObserversRpc = new("ClientObjects.ParseObserversRpc(PooledReader, Channel)");
|
||||
private static readonly ProfilerMarker _pm_ParseTargetRpc = new("ClientObjects.ParseTargetRpc(PooledReader, Channel)");
|
||||
private static readonly ProfilerMarker _pm_ReadSpawn = new("ClientObjects.ReadSpawn(PooledReader)");
|
||||
private static readonly ProfilerMarker _pm_CacheDespawn = new("ClientObjects.CacheDespawn(PooledReader)");
|
||||
private static readonly ProfilerMarker _pm_IterateObjectCache = new("ClientObjects.IterateObjectCache()");
|
||||
#endregion
|
||||
|
||||
internal ClientObjects(NetworkManager networkManager)
|
||||
{
|
||||
base.Initialize(networkManager);
|
||||
_objectCache = new(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. */
|
||||
|
||||
/* Only perform this step if the transport being stopped
|
||||
* is the one which client is connected to. */
|
||||
if (NetworkManager.IsClientStarted && args.TransportIndex == NetworkManager.ClientManager.GetTransportIndex())
|
||||
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 (!NetworkManager.IsServerStarted)
|
||||
{
|
||||
base.DespawnWithoutSynchronization(recursive: true, asServer: false);
|
||||
}
|
||||
// Otherwise invoke stop callbacks only for client side.
|
||||
else
|
||||
{
|
||||
foreach (NetworkObject n in Spawned.Values)
|
||||
{
|
||||
if (!n.CanDeinitialize(asServer: false))
|
||||
continue;
|
||||
|
||||
n.InvokeStopCallbacks(false, true);
|
||||
n.SetInitializedStatus(false, 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.ClearSpawnedCollectionAndInvoke();
|
||||
SceneObjects_Internal.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 (!NetworkManager.IsClientStarted)
|
||||
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>
|
||||
/// Adds a NetworkObject to Spawned.
|
||||
/// </summary>
|
||||
internal override void AddToSpawned(NetworkObject nob, bool asServer)
|
||||
{
|
||||
base.AddToSpawned(nob, asServer);
|
||||
// If being added as client and is also server.
|
||||
if (NetworkManager.IsServerStarted)
|
||||
nob.SetRenderersVisible(true);
|
||||
}
|
||||
|
||||
/// <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.TryPeek(out int objectId))
|
||||
{
|
||||
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.");
|
||||
StoreNetworkObject();
|
||||
return;
|
||||
}
|
||||
|
||||
networkObject.InitializePredictedObject_Client(NetworkManager, objectId, ownerConnection, NetworkManager.ClientManager.Connection);
|
||||
NetworkManager.ClientManager.Objects.AddToSpawned(networkObject, false);
|
||||
networkObject.Initialize(asServer: false, invokeSyncTypeCallbacks: true);
|
||||
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
if (WriteSpawn(networkObject, writer, connection: null))
|
||||
{
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (NetworkTrafficStatistics != null)
|
||||
NetworkTrafficStatistics.AddOutboundPacketIdData(PacketId.ObjectSpawn, string.Empty, writer.Length, networkObject.gameObject, asServer: false);
|
||||
#endif
|
||||
NetworkManager.TransportManager.SendToServer((byte)Channel.Reliable, writer.GetArraySegment());
|
||||
//Also dequeue entry, since we only peeked it earlier.
|
||||
predictedObjectIds.Dequeue();
|
||||
}
|
||||
else
|
||||
{
|
||||
StoreNetworkObject();
|
||||
}
|
||||
|
||||
void StoreNetworkObject()
|
||||
{
|
||||
networkObject.SetIsDestroying();
|
||||
networkObject.Deinitialize(asServer: false);
|
||||
|
||||
NetworkManager.StorePooledOrDestroyInstantiated(networkObject, asServer: false);
|
||||
}
|
||||
|
||||
writer.Store();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a predicted despawn to the server.
|
||||
/// </summary>
|
||||
internal void PredictedDespawn(NetworkObject networkObject)
|
||||
{
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
WriteDepawn(networkObject, writer);
|
||||
NetworkManager.TransportManager.SendToServer((byte)Channel.Reliable, writer.GetArraySegment());
|
||||
writer.Store();
|
||||
|
||||
base.Despawn(networkObject, networkObject.GetDefaultDespawnType(), asServer: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a predicted despawn.
|
||||
/// </summary>
|
||||
public void WriteDepawn(NetworkObject nob, Writer writer)
|
||||
{
|
||||
writer.WritePacketIdUnpacked(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)
|
||||
{
|
||||
List<NetworkObject> nobs = CollectionCaches<NetworkObject>.RetrieveList();
|
||||
Scenes.GetSceneNetworkObjects(s, firstOnly: false, errorOnDuplicates: true, ignoreUnsetSceneIds: true, result: ref nobs);
|
||||
|
||||
bool isServerStarted = NetworkManager.IsServerStarted;
|
||||
|
||||
int nobsCount = nobs.Count;
|
||||
for (int i = 0; i < nobsCount; i++)
|
||||
{
|
||||
NetworkObject nob = nobs[i];
|
||||
if (!nob.IsSceneObject)
|
||||
continue;
|
||||
|
||||
//Only set initialized values if not server, as server would have already done so.
|
||||
if (!isServerStarted)
|
||||
nob.SetInitializedValues(parentNob: null, ignoreSerializedTimestamp: false);
|
||||
|
||||
if (nob.GetIsNetworked())
|
||||
{
|
||||
AddToSceneObjects(nob);
|
||||
//Only run if not also server, as this already ran on server.
|
||||
if (!NetworkManager.IsServerStarted)
|
||||
nob.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(nobs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a NetworkObject runs Deactivate.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
internal override void NetworkObjectDestroyed(NetworkObject nob, bool asServer)
|
||||
{
|
||||
nob.RemoveClientRpcLinkIndexes();
|
||||
base.NetworkObjectDestroyed(nob, asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses an OwnershipChange packet.
|
||||
/// </summary>
|
||||
/// <param name = "reader"></param>
|
||||
internal void ParseOwnershipChange(PooledReader reader)
|
||||
{
|
||||
using (_pm_ParseOwnershipChange.Auto())
|
||||
{
|
||||
NetworkObject nob = reader.ReadNetworkObject();
|
||||
NetworkConnection newOwner = reader.ReadNetworkConnection();
|
||||
if (nob != null && nob.IsSpawned)
|
||||
nob.GiveOwnership(newOwner, asServer: false, recursive: false);
|
||||
else
|
||||
NetworkManager.LogWarning($"NetworkBehaviour could not be found when trying to parse OwnershipChange packet.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a received SyncType.
|
||||
/// </summary>
|
||||
/// <param name = "reader"></param>
|
||||
internal void ParseSyncType(PooledReader reader, Channel channel)
|
||||
{
|
||||
using (_pm_ParseSyncType.Auto())
|
||||
{
|
||||
int readerPositionAfterDebug = reader.Position;
|
||||
|
||||
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
|
||||
int length = (int)ReservedLengthWriter.ReadLength(reader, NetworkBehaviour.SYNCTYPE_RESERVE_BYTES);
|
||||
|
||||
if (nb != null && nb.IsSpawned)
|
||||
{
|
||||
/* 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 (length > 0)
|
||||
nb.ReadSyncType(readerPositionAfterDebug, reader, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
SkipDataLength((ushort)PacketId.SyncType, reader, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a
|
||||
/// </summary>
|
||||
/// <param name = "reader"></param>
|
||||
internal void ParsePredictedSpawnResult(PooledReader reader)
|
||||
{
|
||||
using (_pm_ParsePredictedSpawnResult.Auto())
|
||||
{
|
||||
int readerPositionAfterDebug = reader.Position;
|
||||
|
||||
bool success = reader.ReadBoolean();
|
||||
int usedObjectId = reader.ReadNetworkObjectId();
|
||||
int nextObjectId = reader.ReadNetworkObjectId();
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (NetworkTrafficStatistics != null)
|
||||
NetworkTrafficStatistics.AddInboundPacketIdData(PacketId.PredictedSpawnResult, string.Empty, reader.Position - readerPositionAfterDebug + Transporting.TransportManager.PACKETID_LENGTH, gameObject: null, asServer: false);
|
||||
#endif
|
||||
|
||||
if (nextObjectId != NetworkObject.UNSET_OBJECTID_VALUE)
|
||||
NetworkManager.ClientManager.Connection.PredictedObjectIds.Enqueue(nextObjectId);
|
||||
|
||||
//Server would not allow the predicted spawn.
|
||||
if (!success)
|
||||
{
|
||||
if (Spawned.TryGetValueIL2CPP(usedObjectId, out NetworkObject nob))
|
||||
{
|
||||
//TODO support pooling. This first requires a rework of the initialization / clientHost message system.
|
||||
nob.SetIsDestroying(DespawnType.Destroy);
|
||||
UnityEngine.Object.Destroy(nob.gameObject);
|
||||
//nob.Deinitialize(asServer: false);
|
||||
//NetworkManager.StorePooledInstantiated(nob, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a ReconcileRpc.
|
||||
/// </summary>
|
||||
/// <param name = "reader"></param>
|
||||
internal void ParseReconcileRpc(PooledReader reader, Channel channel)
|
||||
{
|
||||
using (_pm_ParseReconcileRpc.Auto())
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int readerRemainingAfterLength, out string rpcInformation, out uint expectedReadAmount);
|
||||
#endif
|
||||
int readerStartAfterDebug = reader.Position;
|
||||
|
||||
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
|
||||
int dataLength = Packets.GetPacketLength((ushort)PacketId.Reconcile, reader, channel);
|
||||
|
||||
if (nb != null && nb.IsSpawned)
|
||||
nb.OnReconcileRpc(readerStartAfterDebug, hash: null, reader, channel);
|
||||
else
|
||||
SkipDataLength((ushort)PacketId.ObserversRpc, reader, dataLength);
|
||||
|
||||
#if DEVELOPMENT
|
||||
NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: false, NetworkManager, reader, readerRemainingAfterLength, rpcInformation, expectedReadAmount, channel);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses an ObserversRpc.
|
||||
/// </summary>
|
||||
/// <param name = "reader"></param>
|
||||
internal void ParseObserversRpc(PooledReader reader, Channel channel)
|
||||
{
|
||||
using (_pm_ParseObserversRpc.Auto())
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining, out string rpcInformation, out uint expectedReadAmount);
|
||||
#endif
|
||||
int readerStartAfterDebug = reader.Position;
|
||||
|
||||
NetworkBehaviour nb = reader.ReadNetworkBehaviour(logException: false);
|
||||
int dataLength = Packets.GetPacketLength((ushort)PacketId.ObserversRpc, reader, channel);
|
||||
if (nb != null && nb.IsSpawned)
|
||||
{
|
||||
nb.ReadObserversRpc(readerStartAfterDebug, fromRpcLink: false, hash: 0, reader, channel);
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.Log($"NetworkBehaviour not found for an ObserverRpc. Rpc data will be discarded.");
|
||||
SkipDataLength((ushort)PacketId.ObserversRpc, reader, dataLength);
|
||||
}
|
||||
|
||||
#if DEVELOPMENT
|
||||
NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: false, NetworkManager, reader, startReaderRemaining, rpcInformation, expectedReadAmount, channel);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a TargetRpc.
|
||||
/// </summary>
|
||||
/// <param name = "reader"></param>
|
||||
internal void ParseTargetRpc(PooledReader reader, Channel channel)
|
||||
{
|
||||
using (_pm_ParseTargetRpc.Auto())
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining, out string rpcInformation, out uint expectedReadAmount);
|
||||
#endif
|
||||
int readerStartAfterDebug = reader.Position;
|
||||
|
||||
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
|
||||
int dataLength = Packets.GetPacketLength((ushort)PacketId.TargetRpc, reader, channel);
|
||||
|
||||
if (nb != null && nb.IsSpawned)
|
||||
nb.ReadTargetRpc(readerStartAfterDebug, fromRpcLink: false, hash: 0, 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>
|
||||
internal void ReadSpawn(PooledReader reader)
|
||||
{
|
||||
using (_pm_ReadSpawn.Auto())
|
||||
{
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
int readerPositionAfterDebug = reader.Position;
|
||||
#endif
|
||||
|
||||
SpawnType st = (SpawnType)reader.ReadUInt8Unpacked();
|
||||
|
||||
bool sceneObject = st.FastContains(SpawnType.Scene);
|
||||
|
||||
ReadNestedSpawnIds(reader, st, out byte? nobComponentId, out int? parentObjectId, out byte? parentComponentId, _objectCache.ReadSpawningObjects);
|
||||
|
||||
//NeworkObject and owner information.
|
||||
int objectId = reader.ReadNetworkObjectForSpawn(out int initializeOrder, out ushort collectionId);
|
||||
int ownerId = reader.ReadNetworkConnectionId();
|
||||
//Read transform values which differ from serialized values.
|
||||
Vector3? localPosition;
|
||||
Quaternion? localRotation;
|
||||
Vector3? localScale;
|
||||
ReadTransformProperties(reader, out localPosition, out localRotation, out localScale);
|
||||
|
||||
int prefabId = 0;
|
||||
ulong sceneId = 0;
|
||||
string sceneName = string.Empty;
|
||||
string objectName = string.Empty;
|
||||
|
||||
if (sceneObject)
|
||||
{
|
||||
ReadSceneObjectId(reader, out sceneId);
|
||||
#if DEVELOPMENT
|
||||
if (NetworkManager.ClientManager.IsServerDevelopment)
|
||||
CheckReadSceneObjectDetails(reader, ref sceneName, ref objectName);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
prefabId = reader.ReadNetworkObjectId();
|
||||
}
|
||||
|
||||
ArraySegment<byte> payload = ReadPayload(reader);
|
||||
ArraySegment<byte> rpcLinks = ReadRpcLinks(reader);
|
||||
ArraySegment<byte> syncTypes = ReadSyncTypesForSpawn(reader);
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (NetworkTrafficStatistics != null)
|
||||
NetworkTrafficStatistics.AddInboundPacketIdData(PacketId.ObjectSpawn, string.Empty, reader.Position - readerPositionAfterDebug + Transporting.TransportManager.PACKETID_LENGTH, gameObject: null, asServer: false);
|
||||
#endif
|
||||
|
||||
bool isPredictedSpawner = st.FastContains(SpawnType.IsPredictedSpawner);
|
||||
|
||||
//If found in spawn already.
|
||||
if (Spawned.TryGetValue(objectId, out NetworkObject nob))
|
||||
{
|
||||
/* If not server then extra checks must be done. Client should never
|
||||
* receive spawn messages for already spawned objects, unless they locally
|
||||
* predicted spawned the object. */
|
||||
if (!NetworkManager.IsServerStarted)
|
||||
{
|
||||
//Not predicted spawner.
|
||||
if (!st.FastContains(SpawnType.IsPredictedSpawner))
|
||||
{
|
||||
NetworkManager.LogWarning($"Received a spawn objectId of {objectId} which was already found in spawned, and was not predicted. This sometimes may occur on clientHost when the server destroys an object unexpectedly before the clientHost gets the spawn message.");
|
||||
}
|
||||
//Is predicted spawner.
|
||||
else
|
||||
{
|
||||
PooledReader segmentReader = ReaderPool.Retrieve(ArraySegment<byte>.Empty, NetworkManager);
|
||||
|
||||
//RpcLinks.
|
||||
segmentReader.Initialize(rpcLinks, NetworkManager, Reader.DataSource.Server);
|
||||
ApplyRpcLinks(nob, segmentReader);
|
||||
|
||||
//Payload.
|
||||
segmentReader.Initialize(payload, NetworkManager, Reader.DataSource.Server);
|
||||
ReadPayload(sender: null, nob, segmentReader, segmentReader.Length);
|
||||
|
||||
//SyncTypes.
|
||||
segmentReader.Initialize(syncTypes, NetworkManager, Reader.DataSource.Server);
|
||||
ApplySyncTypesForSpawn(nob, segmentReader);
|
||||
}
|
||||
|
||||
/* Nob isn't added to spawn if predicted spawner.
|
||||
* We only wanted to read and apply initial data from the server. */
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If predicted spawner and not in spawned then simply exit early.
|
||||
* The predicted spawner destroyed the object locally. */
|
||||
if (isPredictedSpawner)
|
||||
return;
|
||||
}
|
||||
|
||||
_objectCache.AddSpawn(NetworkManager, collectionId, objectId, initializeOrder, ownerId, st, nobComponentId, parentObjectId, parentComponentId, prefabId, localPosition, localRotation, localScale, sceneId, sceneName, objectName, payload, rpcLinks, syncTypes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
using (_pm_CacheDespawn.Auto())
|
||||
{
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
int readerPositionAfterDebug = reader.Position;
|
||||
#endif
|
||||
|
||||
DespawnType despawnType;
|
||||
int objectId = reader.ReadNetworkObjectForDespawn(out despawnType);
|
||||
_objectCache.AddDespawn(objectId, despawnType);
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (NetworkTrafficStatistics != null)
|
||||
NetworkTrafficStatistics.AddInboundPacketIdData(PacketId.ObjectDespawn, string.Empty, reader.Position - readerPositionAfterDebug + Transporting.TransportManager.PACKETID_LENGTH, gameObject: null, asServer: false);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <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()
|
||||
{
|
||||
using (_pm_IterateObjectCache.Auto())
|
||||
{
|
||||
_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.ParentObjectId.Value;
|
||||
byte componentIndex = cnob.ComponentId.Value;
|
||||
|
||||
/* 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)
|
||||
{
|
||||
//Only log if not clientHost.
|
||||
if (!NetworkManager.IsServerStarted)
|
||||
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.InitializedNestedNetworkObjects;
|
||||
|
||||
//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)
|
||||
{
|
||||
//Only log if not clientHost.
|
||||
if (!NetworkManager.IsServerStarted)
|
||||
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.
|
||||
/// This method will exit early if reader is null.
|
||||
/// </summary>
|
||||
internal void ApplyRpcLinks(NetworkObject nob, PooledReader reader)
|
||||
{
|
||||
if (reader == null)
|
||||
return;
|
||||
|
||||
List<ushort> rpcLinkIndexes = new();
|
||||
|
||||
while (reader.Remaining > 0)
|
||||
{
|
||||
byte componentId = reader.ReadNetworkBehaviourId();
|
||||
ushort count = reader.ReadUInt16Unpacked();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
//Index of RpcLink.
|
||||
ushort linkIndex = reader.ReadUInt16Unpacked();
|
||||
RpcLink link = new(nob.ObjectId, componentId,
|
||||
//RpcHash.
|
||||
reader.ReadUInt16Unpacked(),
|
||||
//packetId for rpc.
|
||||
reader.ReadPacketId());
|
||||
//Add to links.
|
||||
SetRpcLink(linkIndex, link);
|
||||
rpcLinkIndexes.Add(linkIndex);
|
||||
}
|
||||
}
|
||||
|
||||
nob.SetRpcLinkIndexes(rpcLinkIndexes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies initial SyncTypes to a NetworkObject.
|
||||
/// A null reader will exit the method early.
|
||||
/// </summary>
|
||||
internal void ApplySyncTypesForSpawn(NetworkObject nob, PooledReader reader)
|
||||
{
|
||||
if (reader == null)
|
||||
return;
|
||||
/* 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. */
|
||||
List<NetworkBehaviour> behaviours = nob.NetworkBehaviours;
|
||||
|
||||
while (reader.Remaining > 0)
|
||||
{
|
||||
byte behaviourId = reader.ReadUInt8Unpacked();
|
||||
behaviours[behaviourId].ReadSyncTypesForSpawn(reader);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 = 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.IsHostStarted)
|
||||
{
|
||||
Transform parentTransform = null;
|
||||
//Set parentTransform if there's a parent object.
|
||||
if (cnob.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
|
||||
{
|
||||
byte componentIndex = cnob.ComponentId.Value;
|
||||
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 not be found within NetworkObject {nob.name} with ObjectId {objectId}. Prefab {prefab.name} will be instantiated without parent synchronization.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ObjectPoolRetrieveOption retrieveOptions = ObjectPoolRetrieveOption.MakeActive | ObjectPoolRetrieveOption.LocalSpace;
|
||||
result = networkManager.GetPooledInstantiated(prefabId, collectionId, retrieveOptions, parentTransform, cnob.Position, cnob.Rotation, cnob.Scale, asServer: false);
|
||||
|
||||
//Only need to set IsGlobal also if not host.
|
||||
bool isGlobal = cnob.SpawnType.FastContains(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.LogWarning($"ObjectId {cnob.ObjectId} could not be found in Server spawned, nor Server pending despawn. This may occur as clientHost when objects are destroyed before the client receives a despawn packet. In most cases this may be ignored.");
|
||||
}
|
||||
|
||||
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 (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. */
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: da027fa27b0c0994ebfa317968862970
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Client/Object/ClientObjects.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,654 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Utility.Extension;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
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>
|
||||
/// Objects which are being spawned during iteration.
|
||||
/// </summary>
|
||||
internal Dictionary<int, NetworkObject> IteratedSpawningObjects = new();
|
||||
/// <summary>
|
||||
/// ObjectIds which have been read this tick.
|
||||
/// </summary>
|
||||
internal HashSet<int> ReadSpawningObjects = new();
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Cached objects buffer. Contains spawns and despawns.
|
||||
/// </summary>
|
||||
private List<CachedNetworkObject> _cachedObjects = new();
|
||||
/// <summary>
|
||||
/// NetworkObjects which have been spawned already during the current iteration.
|
||||
/// </summary>
|
||||
private HashSet<NetworkObject> _iteratedSpawns = new();
|
||||
/// <summary>
|
||||
/// Despawns which are occurring the same tick as their spawn.
|
||||
/// </summary>
|
||||
private HashSet<int> _conflictingDespawns = new();
|
||||
/// <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.Count;
|
||||
List<CachedNetworkObject> collection = _cachedObjects;
|
||||
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>
|
||||
public void AddSpawn(NetworkManager manager, ushort collectionId, int objectId, int initializeOrder, int ownerId, SpawnType ost, byte? nobComponentId, int? parentObjectId, byte? parentComponentId, int? prefabId, Vector3? localPosition, Quaternion? localRotation, Vector3? localScale, ulong sceneId, string sceneName, string objectName, ArraySegment<byte> payload, 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 = ResettableObjectCaches<CachedNetworkObject>.Retrieve();
|
||||
_cachedObjects.Add(cnob);
|
||||
}
|
||||
// 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.Count;
|
||||
for (int i = 0; i < written; i++)
|
||||
{
|
||||
CachedNetworkObject item = _cachedObjects[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 = ResettableObjectCaches<CachedNetworkObject>.Retrieve();
|
||||
_cachedObjects.Insert(i, cnob);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//If here and cnob is null then it was not inserted; add to end.
|
||||
if (cnob == null)
|
||||
{
|
||||
cnob = ResettableObjectCaches<CachedNetworkObject>.Retrieve();
|
||||
_cachedObjects.Add(cnob);
|
||||
}
|
||||
}
|
||||
|
||||
cnob.InitializeSpawn(manager, collectionId, objectId, initializeOrder, ownerId, ost, nobComponentId, parentObjectId, parentComponentId, prefabId, localPosition, localRotation, localScale, sceneId, sceneName, objectName, payload, rpcLinks, syncValues);
|
||||
|
||||
ReadSpawningObjects.Add(objectId);
|
||||
}
|
||||
|
||||
public void AddDespawn(int objectId, DespawnType despawnType)
|
||||
{
|
||||
CachedNetworkObject cnob = ResettableObjectCaches<CachedNetworkObject>.Retrieve();
|
||||
_cachedObjects.Add(cnob);
|
||||
cnob.InitializeDespawn(objectId, despawnType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates any written objects.
|
||||
/// </summary>
|
||||
public void Iterate()
|
||||
{
|
||||
int written = _cachedObjects.Count;
|
||||
if (written == 0)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
//Indexes which have already been processed.
|
||||
HashSet<int> processedIndexes = new();
|
||||
List<CachedNetworkObject> collection = _cachedObjects;
|
||||
_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.HasParent)
|
||||
{
|
||||
bool nested = cnob.IsInitializedNested;
|
||||
//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 = cnob.ParentObjectId.Value;
|
||||
NetworkObject nob = GetSpawnedObject(targetObjectId);
|
||||
//If not spawned yet.
|
||||
if (nob == null)
|
||||
{
|
||||
bool isClientHost = _networkManager.IsServerStarted;
|
||||
|
||||
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)
|
||||
{
|
||||
if (!isClientHost)
|
||||
{
|
||||
errMsg = nested ? $"ObjectId {targetObjectId} was found for a nested spawn, but ActionType is not spawn. ComponentIndex {cnob.ComponentId} 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. Only log if not clientHost.
|
||||
if (!found)
|
||||
{
|
||||
if (!isClientHost)
|
||||
{
|
||||
errMsg = nested ? $"ObjectId {targetObjectId} could not be found for a nested spawn. ComponentIndex {cnob.ComponentId} 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 the NetworkObject is null on lookup then something happened in the retrieval. Exit early.
|
||||
* This can be normal on clientHost when client side gets packets late. When
|
||||
* clientHost this will fail silently.*/
|
||||
|
||||
if (spawn)
|
||||
{
|
||||
if (cnob.IsSceneObject)
|
||||
{
|
||||
cnob.NetworkObject = _clientObjects.GetSceneNetworkObject(cnob.SceneId, cnob.SceneName, cnob.ObjectName);
|
||||
if (cnob.NetworkObject != null)
|
||||
SetParentAndTransformProperties(cnob);
|
||||
}
|
||||
//Is nested in a prefab.
|
||||
else if (cnob.IsInitializedNested)
|
||||
{
|
||||
cnob.NetworkObject = _clientObjects.GetNestedNetworkObject(cnob);
|
||||
if (cnob.NetworkObject != null)
|
||||
cnob.NetworkObject.transform.SetLocalPositionRotationAndScale(cnob.Position, cnob.Rotation, cnob.Scale);
|
||||
}
|
||||
/* Not sceneObject or initializedNested. Could still be runtime
|
||||
* nested but this also requires instantiation. The instantiation process
|
||||
* handles parenting and position. */
|
||||
else
|
||||
{
|
||||
cnob.NetworkObject = _clientObjects.GetInstantiatedNetworkObject(cnob);
|
||||
//Parenting and transform is done during the instantiation process.
|
||||
}
|
||||
}
|
||||
//Despawn.
|
||||
else
|
||||
{
|
||||
cnob.NetworkObject = _clientObjects.GetSpawnedNetworkObject(cnob);
|
||||
|
||||
/* //TODO cache recent predicted despawns for up to a few seconds.
|
||||
* If not found and the Id is in recently predicted despawned then simply
|
||||
* do nothing. Otherwise log missing Id. */
|
||||
// /* Do not log unless not nested. Nested nobs sometimes
|
||||
// * could be destroyed if parent was first. */
|
||||
// if (!_networkManager.IsHostStarted && cnob.NetworkObject == null && !cnob.IsInitializedNested)
|
||||
// _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)
|
||||
{
|
||||
NetworkConnection owner;
|
||||
int objectId;
|
||||
//If not server then initialize by using lookups.
|
||||
if (!_networkManager.IsServerStarted)
|
||||
{
|
||||
objectId = cnob.ObjectId;
|
||||
int ownerId = cnob.OwnerId;
|
||||
//If local client is owner then use localconnection reference.
|
||||
NetworkConnection localConnection = _networkManager.ClientManager.Connection;
|
||||
//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;
|
||||
}
|
||||
}
|
||||
//Otherwise initialize using server values.
|
||||
else
|
||||
{
|
||||
owner = nob.Owner;
|
||||
objectId = nob.ObjectId;
|
||||
}
|
||||
|
||||
//Preinitialize client side.
|
||||
nob.InitializeEarly(_networkManager, objectId, owner, false);
|
||||
//Read payload.
|
||||
if (cnob.PayloadReader != null)
|
||||
_networkManager.ClientManager.Objects.ReadPayload(NetworkManager.EmptyConnection, nob, cnob.PayloadReader, cnob.PayloadReader.Length);
|
||||
|
||||
_clientObjects.AddToSpawned(cnob.NetworkObject, false);
|
||||
IteratedSpawningObjects.Add(cnob.ObjectId, cnob.NetworkObject);
|
||||
/* Fixes https://github.com/FirstGearGames/FishNet/issues/323
|
||||
* The redundancy may have been caused by a rework. It would seem
|
||||
* IterateSpawn was always running after the above lines, and not
|
||||
* from anywhere else. So there's no reason we cannot inline it
|
||||
* here. */
|
||||
_clientObjects.ApplyRpcLinks(cnob.NetworkObject, cnob.RpcLinkReader);
|
||||
//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.IsServerStarted && 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. */
|
||||
NetworkObject n = cnob.NetworkObject;
|
||||
if (_iteratedSpawns.Contains(n))
|
||||
{
|
||||
// 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);
|
||||
n.gameObject.SetActive(true);
|
||||
n.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)
|
||||
{
|
||||
_clientObjects.ApplySyncTypesForSpawn(cnob.NetworkObject, cnob.SyncTypesReader);
|
||||
|
||||
/* 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.InvokeOnStartSyncTypeCallbacks(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
//Once all have been iterated reset.
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets parent using information on a CachedNetworkObject then applies transform properties.
|
||||
/// </summary>
|
||||
/// <param name = "cnob"></param>
|
||||
private void SetParentAndTransformProperties(CachedNetworkObject cnob)
|
||||
{
|
||||
if (!_networkManager.IsHostStarted && cnob.NetworkObject != null)
|
||||
{
|
||||
//Apply runtime parent if needed.
|
||||
if (cnob.HasParent)
|
||||
{
|
||||
if (_networkManager.ClientManager.Objects.Spawned.TryGetValueIL2CPP(cnob.ParentObjectId.Value, out NetworkObject parentNob))
|
||||
{
|
||||
//If parented to the NOB directly.
|
||||
if (!cnob.ParentComponentId.HasValue)
|
||||
cnob.NetworkObject.SetParent(parentNob);
|
||||
//Parented to a NB.
|
||||
else
|
||||
cnob.NetworkObject.SetParent(parentNob.NetworkBehaviours[cnob.ParentComponentId.Value]);
|
||||
}
|
||||
else
|
||||
{
|
||||
_networkManager.Log($"Parent NetworkObject Id {cnob.ParentObjectId} could not be found in spawned. NetworkObject {cnob.NetworkObject} will not have it's parent set.");
|
||||
}
|
||||
|
||||
//cnob.NetworkObject.transform.SetLocalPositionRotationAndScale(cnob.Position, cnob.Rotation, cnob.Scale);
|
||||
}
|
||||
|
||||
// else
|
||||
// {
|
||||
// cnob.NetworkObject.transform.SetWorldPositionRotationAndScale(cnob.Position, cnob.Rotation, cnob.Scale);
|
||||
// }
|
||||
cnob.NetworkObject.transform.SetLocalPositionRotationAndScale(cnob.Position, cnob.Rotation, cnob.Scale);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 (!IteratedSpawningObjects.TryGetValue(objectId, out result))
|
||||
{
|
||||
IReadOnlyDictionary<int, NetworkObject> spawned = _networkManager.IsHostStarted ? _networkManager.ServerManager.Objects.Spawned : _networkManager.ClientManager.Objects.Spawned;
|
||||
spawned.TryGetValue(objectId, out result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets cache.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_initializeOrderChanged = false;
|
||||
foreach (CachedNetworkObject item in _cachedObjects)
|
||||
ResettableObjectCaches<CachedNetworkObject>.Store(item);
|
||||
|
||||
_cachedObjects.Clear();
|
||||
_iteratedSpawns.Clear();
|
||||
IteratedSpawningObjects.Clear();
|
||||
ReadSpawningObjects.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A cached network object which exist in world but has not been Initialized yet.
|
||||
/// </summary>
|
||||
[Preserve]
|
||||
internal class CachedNetworkObject : IResettable
|
||||
{
|
||||
#region Types.
|
||||
public enum ActionType
|
||||
{
|
||||
Unset = 0,
|
||||
Spawn = 1,
|
||||
Despawn = 2
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// True if cached object is nested during initialization.
|
||||
/// </summary>
|
||||
public bool IsInitializedNested => ComponentId > 0;
|
||||
/// <summary>
|
||||
/// True if a scene object.
|
||||
/// </summary>
|
||||
public bool IsSceneObject => SceneId != NetworkObject.UNSET_SCENEID_VALUE;
|
||||
/// <summary>
|
||||
/// True if this object has a parent.
|
||||
/// </summary>
|
||||
public bool HasParent => ParentObjectId != null && ParentComponentId != null;
|
||||
public ushort CollectionId;
|
||||
public int ObjectId;
|
||||
public int InitializeOrder;
|
||||
public int OwnerId;
|
||||
public SpawnType SpawnType;
|
||||
public DespawnType DespawnType;
|
||||
public byte? ComponentId;
|
||||
public int? ParentObjectId;
|
||||
public byte? ParentComponentId;
|
||||
public int? PrefabId;
|
||||
public Vector3? Position;
|
||||
public Quaternion? Rotation;
|
||||
public Vector3? Scale;
|
||||
public ulong SceneId;
|
||||
public string SceneName = string.Empty;
|
||||
public string ObjectName = string.Empty;
|
||||
/// <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 payload for the NetworkObject behaviours.
|
||||
/// </summary>
|
||||
public PooledReader PayloadReader;
|
||||
/// <summary>
|
||||
/// Reader containing rpc links for the NetworkObject.
|
||||
/// </summary>
|
||||
public PooledReader RpcLinkReader;
|
||||
/// <summary>
|
||||
/// Reader containing sync values for the NetworkObject.
|
||||
/// </summary>
|
||||
public PooledReader SyncTypesReader;
|
||||
#pragma warning restore 0649
|
||||
|
||||
public void InitializeSpawn(NetworkManager manager, ushort collectionId, int objectId, int initializeOrder, int ownerId, SpawnType objectSpawnType, byte? nobComponentId, int? parentObjectId, byte? parentComponentId, int? prefabId, Vector3? position, Quaternion? rotation, Vector3? scale, ulong sceneId, string sceneName, string objectName, ArraySegment<byte> payload, ArraySegment<byte> rpcLinks, ArraySegment<byte> syncTypes)
|
||||
{
|
||||
ResetState();
|
||||
Action = ActionType.Spawn;
|
||||
CollectionId = collectionId;
|
||||
ObjectId = objectId;
|
||||
InitializeOrder = initializeOrder;
|
||||
OwnerId = ownerId;
|
||||
SpawnType = objectSpawnType;
|
||||
ComponentId = nobComponentId;
|
||||
ParentObjectId = parentObjectId;
|
||||
ParentComponentId = parentComponentId;
|
||||
PrefabId = prefabId;
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
SceneId = sceneId;
|
||||
SceneName = sceneName;
|
||||
ObjectName = objectName;
|
||||
|
||||
if (payload.Count > 0)
|
||||
PayloadReader = ReaderPool.Retrieve(payload, manager);
|
||||
if (rpcLinks.Count > 0)
|
||||
RpcLinkReader = ReaderPool.Retrieve(rpcLinks, manager);
|
||||
if (syncTypes.Count > 0)
|
||||
SyncTypesReader = ReaderPool.Retrieve(syncTypes, manager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes for a despawned NetworkObject.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
public void InitializeDespawn(int objectId, DespawnType despawnType)
|
||||
{
|
||||
ResetState();
|
||||
Action = ActionType.Despawn;
|
||||
DespawnType = despawnType;
|
||||
ObjectId = objectId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets values which could malform identify the cached object.
|
||||
/// </summary>
|
||||
public void ResetState()
|
||||
{
|
||||
SceneName = string.Empty;
|
||||
ObjectName = string.Empty;
|
||||
NetworkObject = null;
|
||||
|
||||
ReaderPool.StoreAndDefault(ref PayloadReader);
|
||||
ReaderPool.StoreAndDefault(ref RpcLinkReader);
|
||||
ReaderPool.StoreAndDefault(ref SyncTypesReader);
|
||||
}
|
||||
|
||||
public void InitializeState() { }
|
||||
|
||||
~CachedNetworkObject()
|
||||
{
|
||||
NetworkObject = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54cb2af8ab4557d479acb7fed98bd0c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Client/Object/ObjectCaching.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fdb708ebb7c8be48bd50bb6fb36299e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Debugging
|
||||
{
|
||||
/// <summary>
|
||||
/// A container for debugging.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("FishNet/Manager/DebugManager")]
|
||||
public class DebugManager : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// True to write additional information about scene objects being sent in spawn messages. This is primarily used to resolve sceneId not found errors.
|
||||
/// </summary>
|
||||
[Tooltip("True to write additional information about scene objects being sent in spawn messages. This is primarily used to resolve sceneId not found errors.")]
|
||||
public bool WriteSceneObjectDetails;
|
||||
/// <summary>
|
||||
/// True to validate written versus read length of Rpcs. Errors will be thrown if read length is not equal to written length.
|
||||
/// </summary>
|
||||
[Tooltip("True to validate written versus read length of Rpcs. Errors will be thrown if read length is not equal to written length.")]
|
||||
public bool ValidateRpcLengths;
|
||||
/// <summary>
|
||||
/// True to disable RpcLinks for Observer RPCs.
|
||||
/// </summary>
|
||||
[Tooltip("True to disable RpcLinks for Observer RPCs.")]
|
||||
public bool DisableObserversRpcLinks;
|
||||
/// <summary>
|
||||
/// True to disable RpcLinks for Target RPCs.
|
||||
/// </summary>
|
||||
[Tooltip("True to disable RpcLinks for Target RPCs.")]
|
||||
public bool DisableTargetRpcLinks;
|
||||
/// <summary>
|
||||
/// True to disable RpcLinks for Server RPCs.
|
||||
/// </summary>
|
||||
[Tooltip("True to disable RpcLinks for Server RPCs.")]
|
||||
public bool DisableServerRpcLinks;
|
||||
/// <summary>
|
||||
/// True to disable RpcLinks for Replicate RPCs.
|
||||
/// </summary>
|
||||
[Tooltip("True to disable RpcLinks for Replicate RPCs.")]
|
||||
public bool DisableReplicateRpcLinks;
|
||||
/// <summary>
|
||||
/// True to disable RpcLinks for Reconcile RPCs.
|
||||
/// </summary>
|
||||
[Tooltip("True to disable RpcLinks for Reconcile RPCs.")]
|
||||
public bool DisableReconcileRpcLinks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d0962ead4b02a34aae248fccce671ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Debugging/DebugManager.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f59bd8d1da17e3c49b541b79d1adfbf9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
#if UNITY_EDITOR
|
||||
using FishNet.Managing.Debugging;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using LayoutTools = GameKit.Dependencies.Utilities.EditorGuiLayoutTools;
|
||||
|
||||
namespace FishNet.Managing.Editing
|
||||
{
|
||||
[CustomEditor(typeof(DebugManager))]
|
||||
public class DebugManagerEditor : Editor
|
||||
{
|
||||
private SerializedProperty _writeSceneObjectDetails;
|
||||
private SerializedProperty _validateRpcLengths;
|
||||
private SerializedProperty _disableObserversRpcLinks;
|
||||
private SerializedProperty _disableTargetRpcLinks;
|
||||
private SerializedProperty _disableServerRpcLinks;
|
||||
private SerializedProperty _disableReplicateRpcLinks;
|
||||
private SerializedProperty _disableReconcileRpcLinks;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_writeSceneObjectDetails = serializedObject.FindProperty(nameof(_writeSceneObjectDetails).MemberToPascalCase());
|
||||
_validateRpcLengths = serializedObject.FindProperty(nameof(_validateRpcLengths).MemberToPascalCase());
|
||||
_disableObserversRpcLinks = serializedObject.FindProperty(nameof(_disableObserversRpcLinks).MemberToPascalCase());
|
||||
_disableTargetRpcLinks = serializedObject.FindProperty(nameof(_disableTargetRpcLinks).MemberToPascalCase());
|
||||
_disableServerRpcLinks = serializedObject.FindProperty(nameof(_disableServerRpcLinks).MemberToPascalCase());
|
||||
_disableReplicateRpcLinks = serializedObject.FindProperty(nameof(_disableReplicateRpcLinks).MemberToPascalCase());
|
||||
_disableReconcileRpcLinks = serializedObject.FindProperty(nameof(_disableReconcileRpcLinks).MemberToPascalCase());
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
DebugManager DebugManager = (DebugManager)target;
|
||||
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour(DebugManager), typeof(DebugManager), false);
|
||||
GUI.enabled = true;
|
||||
|
||||
LayoutTools.AddHelpBox("Debug features will only be run in Unity Editor, and development builds. Enabling debug features will increase bandwidth consumption and likely create garbage allocations.", MessageType.Warning);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Detail Writing", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
LayoutTools.AddPropertyField(_writeSceneObjectDetails, "Scene Objects");
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Packet Validation", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
LayoutTools.AddPropertyField(_validateRpcLengths, "Rpc Lengths");
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("Disable RpcLinks", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
LayoutTools.AddPropertyField(_disableObserversRpcLinks, "ObserversRpcs");
|
||||
LayoutTools.AddPropertyField(_disableTargetRpcLinks, "TargetRpcs");
|
||||
LayoutTools.AddPropertyField(_disableServerRpcLinks, "ServerRpcs");
|
||||
LayoutTools.AddPropertyField(_disableReplicateRpcLinks, "ReplicateRpcs");
|
||||
LayoutTools.AddPropertyField(_disableReconcileRpcLinks, "ReconcileRpcs");
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6de332294b6706489cb1de30b878ac7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Debugging/Editor/DebugManagerEditor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,103 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
#if DEVELOPMENT
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using FishNet.Transporting.Tugboat;
|
||||
|
||||
namespace FishNet.Managing.Debugging
|
||||
{
|
||||
internal class PacketIdHistory
|
||||
{
|
||||
/// <summary>
|
||||
/// Last several non-split packetIds to be received on the client.
|
||||
/// </summary>
|
||||
private readonly Queue<PacketId> _serverPacketsReceived = new();
|
||||
/// <summary>
|
||||
/// Last several non-split packetIds to be received on the server.
|
||||
/// </summary>
|
||||
private readonly Queue<PacketId> _clientPacketsReceived = new();
|
||||
/// <summary>
|
||||
/// StringBuilder to limit garbage allocation.
|
||||
/// </summary>
|
||||
private static StringBuilder _stringBuilder = new();
|
||||
/// <summary>
|
||||
/// Maximum number of packets allowed to be queued.
|
||||
/// </summary>
|
||||
private const int PACKET_COUNT = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Resets data.
|
||||
/// </summary>
|
||||
internal void ResetState(bool packetsFromServer)
|
||||
{
|
||||
if (packetsFromServer)
|
||||
_serverPacketsReceived.Clear();
|
||||
else
|
||||
_clientPacketsReceived.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a packet to data.
|
||||
/// </summary>
|
||||
internal void ReceivedPacket(PacketId pId, bool packetFromServer)
|
||||
{
|
||||
Queue<PacketId> queue = packetFromServer ? _serverPacketsReceived : _clientPacketsReceived;
|
||||
|
||||
queue.Enqueue(pId);
|
||||
|
||||
while (queue.Count > PACKET_COUNT)
|
||||
queue.Dequeue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints current data.
|
||||
/// </summary>
|
||||
internal string GetReceivedPacketIds(bool packetsFromServer, bool resetReceived = false)
|
||||
{
|
||||
string packetOriginTxt = packetsFromServer ? "from Server" : "from Client";
|
||||
|
||||
_stringBuilder.Clear();
|
||||
Queue<PacketId> queue = GetQueue(packetsFromServer);
|
||||
|
||||
_stringBuilder.AppendLine($"The last {queue.Count} packets to arrive {packetOriginTxt} are:");
|
||||
foreach (PacketId item in queue)
|
||||
_stringBuilder.AppendLine($"{item.ToString()}");
|
||||
|
||||
// Attach nob information.
|
||||
_stringBuilder.Append($"The last parsed NetworkObject is ");
|
||||
NetworkObject lastNob = Reader.LastNetworkObject;
|
||||
if (lastNob != null)
|
||||
_stringBuilder.Append($"Id {lastNob.ObjectId} on gameObject {lastNob.name}");
|
||||
else
|
||||
_stringBuilder.Append("Unset");
|
||||
|
||||
// Attach nb information.
|
||||
_stringBuilder.Append($", and NetworkBehaviour ");
|
||||
NetworkBehaviour lastNb = Reader.LastNetworkBehaviour;
|
||||
if (lastNb == null)
|
||||
_stringBuilder.Append("Unset");
|
||||
else
|
||||
_stringBuilder.Append($"{lastNb.GetType().Name}");
|
||||
|
||||
_stringBuilder.Append(".");
|
||||
|
||||
if (resetReceived)
|
||||
ResetState(packetsFromServer);
|
||||
|
||||
return _stringBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns which packet queue to use.
|
||||
/// </summary>
|
||||
private Queue<PacketId> GetQueue(bool packetsFromServer) => packetsFromServer ? _serverPacketsReceived : _clientPacketsReceived;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afc241e869d97a44f8339510586dce73
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Debugging/PacketIdHistory.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13b772a9b225b1343bbb9313504db84f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
#if UNITY_EDITOR
|
||||
using FishNet.Managing.Object;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Editing
|
||||
{
|
||||
[CustomEditor(typeof(NetworkManager))]
|
||||
public class NetworkManagerEditor : Editor
|
||||
{
|
||||
private SerializedProperty _logging;
|
||||
private SerializedProperty _refreshDefaultPrefabs;
|
||||
private SerializedProperty _runInBackground;
|
||||
private SerializedProperty _dontDestroyOnLoad;
|
||||
private SerializedProperty _persistence;
|
||||
private SerializedProperty _spawnablePrefabs;
|
||||
private SerializedProperty _objectPool;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_logging = serializedObject.FindProperty("_logging");
|
||||
_refreshDefaultPrefabs = serializedObject.FindProperty("_refreshDefaultPrefabs");
|
||||
_runInBackground = serializedObject.FindProperty("_runInBackground");
|
||||
_dontDestroyOnLoad = serializedObject.FindProperty("_dontDestroyOnLoad");
|
||||
_persistence = serializedObject.FindProperty("_persistence");
|
||||
_spawnablePrefabs = serializedObject.FindProperty("_spawnablePrefabs");
|
||||
_objectPool = serializedObject.FindProperty("_objectPool");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
NetworkManager networkManager = (NetworkManager)target;
|
||||
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour(networkManager), typeof(NetworkManager), false);
|
||||
GUI.enabled = true;
|
||||
|
||||
// EditorGUILayout.BeginVertical(GUI.skin.box);
|
||||
// EditorGUILayout.EndVertical();
|
||||
|
||||
|
||||
EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_runInBackground);
|
||||
EditorGUILayout.PropertyField(_dontDestroyOnLoad);
|
||||
EditorGUILayout.PropertyField(_persistence);
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUILayout.LabelField("Logging", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_logging);
|
||||
EditorGUILayout.Space();
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUILayout.LabelField("Prefabs", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_spawnablePrefabs);
|
||||
EditorGUILayout.PropertyField(_objectPool);
|
||||
EditorGUILayout.PropertyField(_refreshDefaultPrefabs);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUILayout.Space();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e8e16b3e97106a4980b954c56d7bbc5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Editor/NetworkManagerEditor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cb1c158bca76a644b857ca01845204b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 980bce33e6d8c5247bedb2ddc2310b77
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
#if UNITY_EDITOR
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using UnityEditor;
|
||||
|
||||
namespace FishNet.Managing.Logging.Editing
|
||||
{
|
||||
[CustomEditor(typeof(LevelLoggingConfiguration), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class LevelLoggingConfigurationEditor : Editor
|
||||
{
|
||||
private SerializedProperty _isEnabled;
|
||||
private SerializedProperty _addLocalTick;
|
||||
private SerializedProperty _addTimestamps;
|
||||
private SerializedProperty _enableTimestampsInEditor;
|
||||
private SerializedProperty _developmentLogging;
|
||||
private SerializedProperty _guiLogging;
|
||||
private SerializedProperty _headlessLogging;
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_isEnabled = serializedObject.FindProperty(nameof(_isEnabled).MemberToPascalCase());
|
||||
|
||||
_addLocalTick = serializedObject.FindProperty(nameof(_addLocalTick));
|
||||
|
||||
_addTimestamps = serializedObject.FindProperty(nameof(_addTimestamps));
|
||||
_enableTimestampsInEditor = serializedObject.FindProperty(nameof(_enableTimestampsInEditor));
|
||||
|
||||
_developmentLogging = serializedObject.FindProperty(nameof(_developmentLogging));
|
||||
_guiLogging = serializedObject.FindProperty(nameof(_guiLogging));
|
||||
_headlessLogging = serializedObject.FindProperty(nameof(_headlessLogging));
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.PropertyField(_isEnabled);
|
||||
|
||||
if (_isEnabled.boolValue == false)
|
||||
return;
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.PropertyField(_addLocalTick);
|
||||
|
||||
EditorGUILayout.PropertyField(_addTimestamps);
|
||||
if (_addTimestamps.boolValue == true)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_enableTimestampsInEditor);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(_developmentLogging);
|
||||
EditorGUILayout.PropertyField(_guiLogging);
|
||||
EditorGUILayout.PropertyField(_headlessLogging);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b815d3a75a4bcc54b907a07e83c7b7cb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Logging/Editor/LevelLoggingConfigurationEditor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,194 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Documenting;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using FishNet.Managing.Timing;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration ScriptableObject specifying which data to log. Used in conjuction with NetworkManager.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "New LevelLoggingConfiguration", menuName = "FishNet/Logging/Level Logging Configuration")]
|
||||
public class LevelLoggingConfiguration : LoggingConfiguration
|
||||
{
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// True to add localtick to logs.
|
||||
/// </summary>
|
||||
[Tooltip("True to add localtick to logs.")]
|
||||
[SerializeField]
|
||||
private bool _addLocalTick;
|
||||
/// <summary>
|
||||
/// True to add timestamps to logs.
|
||||
/// </summary>
|
||||
[Tooltip("True to add timestamps to logs.")]
|
||||
[SerializeField]
|
||||
private bool _addTimestamps = true;
|
||||
/// <summary>
|
||||
/// True to add timestamps when in editor. False to only include timestamps in builds.
|
||||
/// </summary>
|
||||
[Tooltip("True to add timestamps when in editor. False to only include timestamps in builds.")]
|
||||
[SerializeField]
|
||||
private bool _enableTimestampsInEditor;
|
||||
/// <summary>
|
||||
/// Type of logging to use for development builds and editor.
|
||||
/// </summary>
|
||||
[Tooltip("Type of logging to use for development builds and editor.")]
|
||||
[SerializeField]
|
||||
private LoggingType _developmentLogging = LoggingType.Common;
|
||||
/// <summary>
|
||||
/// Type of logging to use for GUI builds.
|
||||
/// </summary>
|
||||
[Tooltip("Type of logging to use for GUI builds.")]
|
||||
[SerializeField]
|
||||
private LoggingType _guiLogging = LoggingType.Warning;
|
||||
/// <summary>
|
||||
/// Type of logging to use for headless builds.
|
||||
/// </summary>
|
||||
[Tooltip("Type of logging to use for headless builds.")]
|
||||
[SerializeField]
|
||||
private LoggingType _headlessLogging = LoggingType.Error;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// True when initialized.
|
||||
/// </summary>
|
||||
private bool _initialized;
|
||||
/// <summary>
|
||||
/// Highest type which can be logged.
|
||||
/// </summary>
|
||||
private LoggingType _highestLoggingType = LoggingType.Off;
|
||||
/// <summary>
|
||||
/// Sequential stringbuilder for performance.
|
||||
/// </summary>
|
||||
private static StringBuilder _stringBuilder = new();
|
||||
#endregion
|
||||
|
||||
[APIExclude]
|
||||
public void LoggingConstructor(bool loggingEnabled, LoggingType development, LoggingType gui, LoggingType headless)
|
||||
{
|
||||
IsEnabled = loggingEnabled;
|
||||
_developmentLogging = development;
|
||||
_guiLogging = gui;
|
||||
_headlessLogging = headless;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes script for use.
|
||||
/// </summary>
|
||||
/// <param name = "manager"></param>
|
||||
public override void InitializeOnce()
|
||||
{
|
||||
byte currentHighest = (byte)LoggingType.Off;
|
||||
#if UNITY_SERVER
|
||||
currentHighest = Math.Max(currentHighest, (byte)_headlessLogging);
|
||||
#elif DEVELOPMENT
|
||||
currentHighest = Math.Max(currentHighest, (byte)_developmentLogging);
|
||||
#else
|
||||
currentHighest = Math.Max(currentHighest, (byte)_guiLogging);
|
||||
#endif
|
||||
_highestLoggingType = (LoggingType)currentHighest;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if can log for loggingType.
|
||||
/// </summary>
|
||||
/// <param name = "loggingType">Type of logging being filtered.</param>
|
||||
/// <returns></returns>
|
||||
public override bool CanLog(LoggingType loggingType)
|
||||
{
|
||||
if (!IsEnabled)
|
||||
return false;
|
||||
|
||||
if (!_initialized)
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
if (Application.isPlaying)
|
||||
NetworkManagerExtensions.LogError("CanLog called before being initialized.");
|
||||
else
|
||||
return true;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
return (byte)loggingType <= (byte)_highestLoggingType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a common value if can log.
|
||||
/// </summary>
|
||||
public override void Log(string value)
|
||||
{
|
||||
if (CanLog(LoggingType.Common))
|
||||
Debug.Log(AddSettingsToLog(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a warning value if can log.
|
||||
/// </summary>
|
||||
public override void LogWarning(string value)
|
||||
{
|
||||
if (CanLog(LoggingType.Warning))
|
||||
Debug.LogWarning(AddSettingsToLog(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an error value if can log.
|
||||
/// </summary>
|
||||
public override void LogError(string value)
|
||||
{
|
||||
if (CanLog(LoggingType.Error))
|
||||
Debug.LogError(AddSettingsToLog(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clones this logging configuration.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override LoggingConfiguration Clone()
|
||||
{
|
||||
LevelLoggingConfiguration copy = CreateInstance<LevelLoggingConfiguration>();
|
||||
copy.LoggingConstructor(IsEnabled, _developmentLogging, _guiLogging, _headlessLogging);
|
||||
copy._addTimestamps = _addTimestamps;
|
||||
copy._addLocalTick = _addLocalTick;
|
||||
copy._enableTimestampsInEditor = _enableTimestampsInEditor;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds onto logging message if settings are enabled to.
|
||||
/// </summary>
|
||||
private string AddSettingsToLog(string value)
|
||||
{
|
||||
_stringBuilder.Clear();
|
||||
|
||||
|
||||
if (_addTimestamps && (!Application.isEditor || _enableTimestampsInEditor))
|
||||
_stringBuilder.Append($"[{DateTime.Now:yyyy.MM.dd HH:mm:ss}] ");
|
||||
|
||||
if (_addLocalTick)
|
||||
{
|
||||
TimeManager tm = InstanceFinder.TimeManager;
|
||||
uint tick = tm == null ? TimeManager.UNSET_TICK : tm.LocalTick;
|
||||
_stringBuilder.Append($"LocalTick [{tick}] ");
|
||||
}
|
||||
|
||||
// If anything was added onto string builder then add value, and set value to string builder.
|
||||
if (_stringBuilder.Length > 0)
|
||||
{
|
||||
_stringBuilder.Append(value);
|
||||
value = _stringBuilder.ToString();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 925fc096350b81f4f82f4fe4ac0c4dda
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Logging/LevelLoggingConfiguration.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,61 @@
|
||||
using FishNet.Documenting;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Base for logging configurations.
|
||||
/// </summary>
|
||||
public abstract class LoggingConfiguration : ScriptableObject
|
||||
{
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// True to use logging features. False to disable all logging.
|
||||
/// </summary>
|
||||
[Tooltip("True to use logging features. False to disable all logging.")]
|
||||
public bool IsEnabled = true;
|
||||
[Obsolete("Use IsEnabled.")] // Remove V5
|
||||
public bool LoggingEnabled
|
||||
{
|
||||
get => IsEnabled;
|
||||
set => IsEnabled = value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes script for use.
|
||||
/// </summary>
|
||||
/// <param name = "manager"></param>
|
||||
public virtual void InitializeOnce() { }
|
||||
|
||||
/// <summary>
|
||||
/// True if can log for loggingType.
|
||||
/// </summary>
|
||||
/// <param name = "loggingType">Type of logging being filtered.</param>
|
||||
/// <returns></returns>
|
||||
public abstract bool CanLog(LoggingType loggingType);
|
||||
|
||||
/// <summary>
|
||||
/// Logs a common value if can log.
|
||||
/// </summary>
|
||||
public abstract void Log(string value);
|
||||
|
||||
/// <summary>
|
||||
/// Logs a warning value if can log.
|
||||
/// </summary>
|
||||
public abstract void LogWarning(string value);
|
||||
|
||||
/// <summary>
|
||||
/// Logs an error value if can log.
|
||||
/// </summary>
|
||||
public abstract void LogError(string value);
|
||||
|
||||
/// <summary>
|
||||
/// Clones this logging configuration.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract LoggingConfiguration Clone();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 438d7e99b7655114891d4fa6e9f68c7d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Logging/LoggingConfiguration.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,25 @@
|
||||
namespace FishNet.Managing.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of logging being filtered.
|
||||
/// </summary>
|
||||
public enum LoggingType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Disable logging.
|
||||
/// </summary>
|
||||
Off = 0,
|
||||
/// <summary>
|
||||
/// Only log errors.
|
||||
/// </summary>
|
||||
Error = 1,
|
||||
/// <summary>
|
||||
/// Log warnings and errors.
|
||||
/// </summary>
|
||||
Warning = 2,
|
||||
/// <summary>
|
||||
/// Log all common activities, warnings, and errors.
|
||||
/// </summary>
|
||||
Common = 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8bf0a7ab3f60fe44984fcfd16d8fe7b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Logging/LoggingType.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,180 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Logging;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing
|
||||
{
|
||||
public partial class NetworkManager : MonoBehaviour
|
||||
{
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// Logging configuration to use. When empty default logging settings will be used.
|
||||
/// </summary>
|
||||
[Tooltip("Logging configuration to use. When empty default logging settings will be used.")]
|
||||
[SerializeField]
|
||||
private LoggingConfiguration _logging;
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
private const string ERROR_LOGGING_PREFIX = "Error - ";
|
||||
private const string WARNING_LOGGING_PREFIX = "Warning - ";
|
||||
private const string COMMON_LOGGING_PREFIX = "Log - ";
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes logging settings.
|
||||
/// </summary>
|
||||
private void InitializeLogging()
|
||||
{
|
||||
if (_logging == null)
|
||||
_logging = ScriptableObject.CreateInstance<LevelLoggingConfiguration>();
|
||||
else
|
||||
_logging = _logging.Clone();
|
||||
|
||||
_logging.InitializeOnce();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if can log for loggingType.
|
||||
/// </summary>
|
||||
/// <param name = "loggingType">Type of logging being filtered.</param>
|
||||
/// <returns></returns>
|
||||
internal bool InternalCanLog(LoggingType loggingType)
|
||||
{
|
||||
return _logging.CanLog(loggingType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a common log, should logging settings permit it.
|
||||
/// </summary>
|
||||
internal void InternalLog(string value)
|
||||
{
|
||||
_logging.Log(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a log using the loggingType, should logging settings permit it.
|
||||
/// </summary>
|
||||
internal void InternalLog(LoggingType loggingType, string value)
|
||||
{
|
||||
if (loggingType == LoggingType.Common)
|
||||
_logging.Log(value);
|
||||
else if (loggingType == LoggingType.Warning)
|
||||
_logging.LogWarning(value);
|
||||
else if (loggingType == LoggingType.Error)
|
||||
_logging.LogError(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a warning log, should logging settings permit it.
|
||||
/// </summary>
|
||||
internal void InternalLogWarning(string value)
|
||||
{
|
||||
_logging.LogWarning(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an error log, should logging settings permit it.
|
||||
/// </summary>
|
||||
internal void InternalLogError(string value)
|
||||
{
|
||||
_logging.LogError(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static class NetworkManagerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// True if can log for loggingType.
|
||||
/// </summary>
|
||||
internal static bool CanLog(this NetworkManager networkManager, LoggingType loggingType)
|
||||
{
|
||||
if (GetNetworkManager(ref networkManager))
|
||||
return networkManager.InternalCanLog(loggingType);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a log using the loggingType, should logging settings permit it.
|
||||
/// </summary>
|
||||
public static void Log(this NetworkManager networkManager, LoggingType loggingType, string value)
|
||||
{
|
||||
if (loggingType == LoggingType.Common)
|
||||
networkManager.Log(value);
|
||||
else if (loggingType == LoggingType.Warning)
|
||||
networkManager.LogWarning(value);
|
||||
else if (loggingType == LoggingType.Error)
|
||||
networkManager.LogError(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a common log, should logging settings permit it.
|
||||
/// </summary>
|
||||
public static void Log(this NetworkManager networkManager, string message)
|
||||
{
|
||||
if (GetNetworkManager(ref networkManager))
|
||||
networkManager.InternalLog(message);
|
||||
else
|
||||
Debug.Log(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs a warning log, should logging settings permit it.
|
||||
/// </summary>
|
||||
public static void LogWarning(this NetworkManager networkManager, string message)
|
||||
{
|
||||
if (GetNetworkManager(ref networkManager))
|
||||
networkManager.InternalLogWarning(message);
|
||||
else
|
||||
Debug.LogWarning(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs an error log, should logging settings permit it.
|
||||
/// </summary>
|
||||
public static void LogError(this NetworkManager networkManager, string message)
|
||||
{
|
||||
if (GetNetworkManager(ref networkManager))
|
||||
networkManager.InternalLogError(message);
|
||||
else
|
||||
Debug.LogError(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a NetworkManager, first using a preferred option.
|
||||
/// </summary>
|
||||
/// <returns>True if a NetworkManager was found.</returns>
|
||||
private static bool GetNetworkManager(ref NetworkManager preferredNm)
|
||||
{
|
||||
if (preferredNm != null)
|
||||
return true;
|
||||
|
||||
preferredNm = InstanceFinder.NetworkManager;
|
||||
return preferredNm != null;
|
||||
}
|
||||
|
||||
#region Backwards compatibility.
|
||||
/// <summary>
|
||||
/// Performs a common log, should logging settings permit it.
|
||||
/// </summary>
|
||||
public static void Log(string msg) => Log(null, msg);
|
||||
|
||||
/// <summary>
|
||||
/// Performs a warning log, should logging settings permit it.
|
||||
/// </summary>
|
||||
public static void LogWarning(string msg) => LogWarning(null, msg);
|
||||
|
||||
/// <summary>
|
||||
/// Performs an error log, should logging settings permit it.
|
||||
/// </summary>
|
||||
public static void LogError(string msg) => LogError(null, msg);
|
||||
|
||||
/// <summary>
|
||||
/// True if can log for loggingType.
|
||||
/// </summary>
|
||||
public static bool CanLog(LoggingType lt) => CanLog(null, lt);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4200d74f31ee8144fb606ce88ad1b747
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/NetworkManager.Logging.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,148 @@
|
||||
using FishNet.Object;
|
||||
using FishNet.Utility.Performance;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing
|
||||
{
|
||||
public sealed partial class NetworkManager : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns an instantiated or pooled object using supplied values. When a value is not specified it uses default values to the prefab or NetworkManager.
|
||||
/// </summary>
|
||||
public NetworkObject GetPooledInstantiated(NetworkObject prefab, Transform parent, bool asServer) => GetPooledInstantiated(prefab.PrefabId, prefab.SpawnableCollectionId, ObjectPoolRetrieveOption.MakeActive, parent, position: null, rotation: null, scale: null, asServer);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instantiated or pooled object using supplied values. When a value is not specified it uses default values to the prefab or NetworkManager.
|
||||
/// </summary>
|
||||
public NetworkObject GetPooledInstantiated(NetworkObject prefab, bool asServer) => GetPooledInstantiated(prefab.PrefabId, prefab.SpawnableCollectionId, ObjectPoolRetrieveOption.MakeActive, parent: null, position: null, rotation: null, scale: null, asServer);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instantiated or pooled object using supplied values. When a value is not specified it uses default values to the prefab or NetworkManager.
|
||||
/// </summary>
|
||||
public NetworkObject GetPooledInstantiated(NetworkObject prefab, Vector3 position, Quaternion rotation, bool asServer) => GetPooledInstantiated(prefab.PrefabId, prefab.SpawnableCollectionId, ObjectPoolRetrieveOption.MakeActive, parent: null, position, rotation, scale: null, asServer);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instantiated or pooled object using supplied values. When a value is not specified it uses default values to the prefab or NetworkManager.
|
||||
/// </summary>
|
||||
public NetworkObject GetPooledInstantiated(GameObject prefab, bool asServer)
|
||||
{
|
||||
if (SetPrefabInformation(prefab, out _, out int prefabId, out ushort collectionId))
|
||||
return GetPooledInstantiated(prefabId, collectionId, ObjectPoolRetrieveOption.MakeActive, parent: null, position: null, rotation: null, scale: null, asServer);
|
||||
// Fallthrough, failure.
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instantiated or pooled object using supplied values. When a value is not specified it uses default values to the prefab or NetworkManager.
|
||||
/// </summary>
|
||||
public NetworkObject GetPooledInstantiated(GameObject prefab, Transform parent, bool asServer)
|
||||
{
|
||||
if (SetPrefabInformation(prefab, out _, out int prefabId, out ushort collectionId))
|
||||
return GetPooledInstantiated(prefabId, collectionId, ObjectPoolRetrieveOption.MakeActive, parent, position: null, rotation: null, scale: null, asServer);
|
||||
// Fallthrough, failure.
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instantiated or pooled object using supplied values. When a value is not specified it uses default values to the prefab or NetworkManager.
|
||||
/// </summary>
|
||||
public NetworkObject GetPooledInstantiated(GameObject prefab, Vector3 position, Quaternion rotation, bool asServer)
|
||||
{
|
||||
if (SetPrefabInformation(prefab, out _, out int prefabId, out ushort collectionId))
|
||||
return GetPooledInstantiated(prefabId, collectionId, ObjectPoolRetrieveOption.MakeActive, parent: null, position, rotation, scale: null, asServer);
|
||||
// Fallthrough, failure.
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instantiated or pooled object using supplied values. When a value is not specified it uses default values to the prefab or NetworkManager.
|
||||
/// </summary>
|
||||
public NetworkObject GetPooledInstantiated(NetworkObject prefab, Vector3 position, Quaternion rotation, Transform parent, bool asServer) => GetPooledInstantiated(prefab.PrefabId, prefab.SpawnableCollectionId, ObjectPoolRetrieveOption.MakeActive, parent, position, rotation, scale: null, asServer);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instantiated or pooled object using supplied values. When a value is not specified it uses default values to the prefab or NetworkManager.
|
||||
/// </summary>
|
||||
public NetworkObject GetPooledInstantiated(GameObject prefab, Vector3 position, Quaternion rotation, Transform parent, bool asServer)
|
||||
{
|
||||
if (SetPrefabInformation(prefab, out _, out int prefabId, out ushort collectionId))
|
||||
return GetPooledInstantiated(prefabId, collectionId, ObjectPoolRetrieveOption.MakeActive, parent, position, rotation, scale: null, asServer);
|
||||
// Fallthrough, failure.
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instantiated or pooled object using supplied values. When a value is not specified it uses default values to the prefab or NetworkManager.
|
||||
/// </summary>
|
||||
public NetworkObject GetPooledInstantiated(int prefabId, ushort collectionId, bool asServer) => GetPooledInstantiated(prefabId, collectionId, ObjectPoolRetrieveOption.MakeActive, parent: null, position: null, rotation: null, scale: null, asServer: asServer);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instantiated or pooled object using supplied values. When a value is not specified it uses default values to the prefab or NetworkManager.
|
||||
/// </summary>
|
||||
public NetworkObject GetPooledInstantiated(int prefabId, ushort collectionId, Vector3 position, Quaternion rotation, bool asServer) => GetPooledInstantiated(prefabId, collectionId, ObjectPoolRetrieveOption.MakeActive, parent: null, position, rotation, scale: null, asServer);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instantiated or pooled object using supplied values. When a value is not specified it uses default values to the prefab or NetworkManager.
|
||||
/// </summary>
|
||||
/// <param name = "makeActive">True to make the NetworkObject active if not already. Using false will not prevent an object from activating via instantation, but rather indicates to not set active manually prior to returning a NetworkObject.</param>
|
||||
[Obsolete("Use GetPooledInstantiated(int, ushort, RetrieveOption, parent, Vector3?, Quaternion? Vector3?, bool) instead.")] // Remove in V5
|
||||
public NetworkObject GetPooledInstantiated(int prefabId, ushort collectionId, Transform parent, Vector3? position, Quaternion? rotation, Vector3? scale, bool makeActive, bool asServer) => _objectPool.RetrieveObject(prefabId, collectionId, parent, position, rotation, scale, makeActive, asServer);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an instantiated or pooled object using supplied values. When a value is not specified it uses default values to the prefab or NetworkManager.
|
||||
/// </summary>
|
||||
public NetworkObject GetPooledInstantiated(int prefabId, ushort collectionId, ObjectPoolRetrieveOption options, Transform parent, Vector3? position, Quaternion? rotation, Vector3? scale, bool asServer) => _objectPool.RetrieveObject(prefabId, collectionId, options, parent, position, rotation, scale, asServer);
|
||||
|
||||
/// <summary>
|
||||
/// Stores an instantied object.
|
||||
/// </summary>
|
||||
/// <param name = "instantiated">Object which was instantiated.</param>
|
||||
/// <param name = "asServer">True to store for the server.</param>
|
||||
public void StorePooledInstantiated(NetworkObject instantiated, bool asServer) => _objectPool.StoreObject(instantiated, asServer);
|
||||
|
||||
/// <summary>
|
||||
/// Stores a NetworkObject if it has pooling enabled, otherwise destroys it.
|
||||
/// </summary>
|
||||
/// <param name = "instantiated">Object which was instantiated.</param>
|
||||
/// <param name = "asServer">True to store for the server.</param>
|
||||
public void StorePooledOrDestroyInstantiated(NetworkObject instantiated, bool asServer)
|
||||
{
|
||||
if (instantiated.GetDefaultDespawnType() == DespawnType.Destroy)
|
||||
{
|
||||
Destroy(instantiated.gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
_objectPool.StoreObject(instantiated, asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a number of objects and adds them to the pool.
|
||||
/// </summary>
|
||||
/// <param name = "prefab">Prefab to cache.</param>
|
||||
/// <param name = "count">Quantity to spawn.</param>
|
||||
/// <param name = "asServer">True if storing prefabs for the server collection. This is only applicable when using DualPrefabObjects.</param>
|
||||
public void CacheObjects(NetworkObject prefab, int count, bool asServer) => _objectPool.CacheObjects(prefab, count, asServer);
|
||||
|
||||
/// <summary>
|
||||
/// Outputs a prefab, along with it's Id and collectionId. Returns if the information could be found.
|
||||
/// </summary>
|
||||
private bool SetPrefabInformation(GameObject prefab, out NetworkObject nob, out int prefabId, out ushort collectionId)
|
||||
{
|
||||
if (!prefab.TryGetComponent(out nob))
|
||||
{
|
||||
prefabId = 0;
|
||||
collectionId = 0;
|
||||
InternalLogError($"NetworkObject was not found on {prefab}. An instantiated NetworkObject cannot be returned.");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
prefabId = nob.PrefabId;
|
||||
collectionId = nob.SpawnableCollectionId;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5f425d13e7c7d648971c4d6c85af23b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/NetworkManager.ObjectPooling.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,15 @@
|
||||
using FishNet.Component.ColliderRollback;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing
|
||||
{
|
||||
public sealed partial class NetworkManager : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// RollbackManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public RollbackManager RollbackManager { get; private set; }
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb7fdb186794b674788273f3b211ec5b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/NetworkManager.Pro.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,343 @@
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Object;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using UnityComponent = UnityEngine.Component;
|
||||
|
||||
namespace FishNet.Managing
|
||||
{
|
||||
public partial class NetworkManager : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
#region Obsoletes
|
||||
[Obsolete("Use IsClientOnlyStarted. Note the difference between IsClientOnlyInitialized and IsClientOnlyStarted.")]
|
||||
public bool IsClientOnly => IsClientOnlyStarted;
|
||||
[Obsolete("Use IsServerOnlyStarted. Note the difference between IsServerOnlyInitialized and IsServerOnlyStarted.")]
|
||||
public bool IsServerOnly => IsServerOnlyStarted;
|
||||
[Obsolete("Use IsHostStarted. Note the difference between IsHostInitialized and IsHostStarted.")]
|
||||
public bool IsHost => IsHostStarted;
|
||||
[Obsolete("Use IsClientStarted. Note the difference between IsClientInitialized and IsClientStarted.")]
|
||||
public bool IsClient => IsClientStarted;
|
||||
[Obsolete("Use IsServerStarted. Note the difference between IsServerInitialized and IsServerStarted.")]
|
||||
public bool IsServer => IsServerStarted;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// True if server is started.
|
||||
/// </summary>
|
||||
public bool IsServerStarted => ServerManager.Started;
|
||||
/// <summary>
|
||||
/// True if only the server is started.
|
||||
/// </summary>
|
||||
public bool IsServerOnlyStarted => IsServerStarted && !IsClientStarted;
|
||||
/// <summary>
|
||||
/// True if the client is authenticated.
|
||||
/// </summary>
|
||||
public bool IsClientStarted => ClientManager.Started && ClientManager.Connection.IsAuthenticated;
|
||||
/// <summary>
|
||||
/// True if only the client is authenticated.
|
||||
/// </summary>
|
||||
public bool IsClientOnlyStarted => !IsServerStarted && IsClientStarted;
|
||||
/// <summary>
|
||||
/// True if client is authenticated, and the server is started.
|
||||
/// </summary>
|
||||
public bool IsHostStarted => IsServerStarted && IsClientStarted;
|
||||
/// <summary>
|
||||
/// True if client nor server are started.
|
||||
/// </summary>
|
||||
public bool IsOffline => !IsServerStarted && !IsClientStarted;
|
||||
#endregion
|
||||
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[Tooltip("Collection to use for spawnable objects.")]
|
||||
[SerializeField]
|
||||
private PrefabObjects _spawnablePrefabs;
|
||||
/// <summary>
|
||||
/// Collection to use for spawnable objects.
|
||||
/// </summary>
|
||||
public PrefabObjects SpawnablePrefabs
|
||||
{
|
||||
get => _spawnablePrefabs;
|
||||
set => _spawnablePrefabs = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
private Dictionary<ushort, PrefabObjects> _runtimeSpawnablePrefabs = new();
|
||||
/// <summary>
|
||||
/// Collection to use for spawnable objects added at runtime, such as addressables.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<ushort, PrefabObjects> RuntimeSpawnablePrefabs => _runtimeSpawnablePrefabs;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Delegates waiting to be invoked when a component is registered.
|
||||
/// </summary>
|
||||
private Dictionary<string, List<Action<UnityComponent>>> _pendingInvokes = new();
|
||||
/// <summary>
|
||||
/// Currently registered components.
|
||||
/// </summary>
|
||||
private Dictionary<string, UnityComponent> _registeredComponents = new();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets the PrefabObjects to use for spawnableCollectionId.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type of PrefabObjects to return. This is also used to create an instance of type when createIfMissing is true.</typeparam>
|
||||
/// <param name = "spawnableCollectionId">Id to use. 0 will return the configured SpawnablePrefabs.</param>
|
||||
/// <param name = "createIfMissing">True to create and assign a PrefabObjects if missing for the collectionId.</param>
|
||||
/// <returns></returns>
|
||||
public PrefabObjects GetPrefabObjects<T>(ushort spawnableCollectionId, bool createIfMissing) where T : PrefabObjects
|
||||
{
|
||||
if (spawnableCollectionId == 0)
|
||||
{
|
||||
if (createIfMissing)
|
||||
{
|
||||
InternalLogError($"SpawnableCollectionId cannot be 0 when create missing is true.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return SpawnablePrefabs;
|
||||
}
|
||||
}
|
||||
|
||||
PrefabObjects po;
|
||||
if (!_runtimeSpawnablePrefabs.TryGetValue(spawnableCollectionId, out po))
|
||||
{
|
||||
// Do not create missing, return null for not found.
|
||||
if (!createIfMissing)
|
||||
return null;
|
||||
|
||||
po = ScriptableObject.CreateInstance<T>();
|
||||
po.SetCollectionId(spawnableCollectionId);
|
||||
_runtimeSpawnablePrefabs[spawnableCollectionId] = po;
|
||||
}
|
||||
|
||||
return po;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the PrefabObjects collection from memory.
|
||||
/// This should only be called after you properly disposed of it's contents properly.
|
||||
/// </summary>
|
||||
/// <param name = "spawnableCollectionId">CollectionId to remove.</param>
|
||||
/// <returns>True if collection was found and removed.</returns>
|
||||
public bool RemoveSpawnableCollection(ushort spawnableCollectionId)
|
||||
{
|
||||
return _runtimeSpawnablePrefabs.Remove(spawnableCollectionId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index a prefab uses. Can be used in conjuction with GetPrefab.
|
||||
/// </summary>
|
||||
/// <param name = "prefab"></param>
|
||||
/// <param name = "asServer">True if to get from the server collection.</param>
|
||||
/// <returns>Returns index if found, and -1 if not found.</returns>
|
||||
public int GetPrefabIndex(GameObject prefab, bool asServer)
|
||||
{
|
||||
int count = SpawnablePrefabs.GetObjectCount();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
GameObject go = SpawnablePrefabs.GetObject(asServer, i).gameObject;
|
||||
if (go == prefab)
|
||||
return i;
|
||||
}
|
||||
|
||||
// Fall through, not found.
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a prefab with prefabId.
|
||||
/// This method will bypass object pooling.
|
||||
/// </summary>
|
||||
/// <param name = "prefabId">PrefabId to get.</param>
|
||||
/// <param name = "asServer">True if getting the prefab asServer.</param>
|
||||
public NetworkObject GetPrefab(int prefabId, bool asServer)
|
||||
{
|
||||
return SpawnablePrefabs.GetObject(asServer, prefabId);
|
||||
}
|
||||
|
||||
#region Registered components
|
||||
/// <summary>
|
||||
/// Invokes an action when a specified component becomes registered. Action will invoke immediately if already registered.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Component type.</typeparam>
|
||||
/// <param name = "handler">Action to invoke.</param>
|
||||
public void RegisterInvokeOnInstance<T>(Action<UnityComponent> handler) where T : UnityComponent
|
||||
{
|
||||
T result;
|
||||
// If not found yet make a pending invoke.
|
||||
if (!TryGetInstance(out result))
|
||||
{
|
||||
string tName = GetInstanceName<T>();
|
||||
List<Action<UnityComponent>> handlers;
|
||||
if (!_pendingInvokes.TryGetValue(tName, out handlers))
|
||||
{
|
||||
handlers = new();
|
||||
_pendingInvokes[tName] = handlers;
|
||||
}
|
||||
|
||||
handlers.Add(handler);
|
||||
}
|
||||
// Already exist, invoke right away.
|
||||
else
|
||||
{
|
||||
handler.Invoke(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an action to be invokes when a specified component becomes registered.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Component type.</typeparam>
|
||||
/// <param name = "handler">Action to invoke.</param>
|
||||
public void UnregisterInvokeOnInstance<T>(Action<UnityComponent> handler) where T : UnityComponent
|
||||
{
|
||||
string tName = GetInstanceName<T>();
|
||||
List<Action<UnityComponent>> handlers;
|
||||
if (!_pendingInvokes.TryGetValue(tName, out handlers))
|
||||
return;
|
||||
|
||||
handlers.Remove(handler);
|
||||
// Do not remove pending to prevent garbage collection later from recreation.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if an instance exists for type.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type to check.</typeparam>
|
||||
/// <returns></returns>
|
||||
public bool HasInstance<T>() where T : UnityComponent
|
||||
{
|
||||
return TryGetInstance<T>(out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns class of type from registered instances.
|
||||
/// A warning will display if not found.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type to get.</typeparam>
|
||||
/// <returns></returns>
|
||||
public T GetInstance<T>() where T : UnityComponent
|
||||
{
|
||||
T result;
|
||||
if (TryGetInstance(out result))
|
||||
return result;
|
||||
else
|
||||
InternalLogWarning($"Component {GetInstanceName<T>()} is not registered. To avoid this warning use TryGetInstance(T).");
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns class of type from registered instances.
|
||||
/// </summary>
|
||||
/// <param name = "component">Outputted component.</param>
|
||||
/// <typeparam name = "T">Type to get.</typeparam>
|
||||
/// <returns>True if was able to get instance.</returns>
|
||||
public bool TryGetInstance<T>(out T result) where T : UnityComponent
|
||||
{
|
||||
string tName = GetInstanceName<T>();
|
||||
if (_registeredComponents.TryGetValue(tName, out UnityComponent v))
|
||||
{
|
||||
result = (T)v;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new component to this NetworkManager.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type to register.</typeparam>
|
||||
/// <param name = "component">Reference of the component being registered.</param>
|
||||
/// <param name = "replace">True to replace existing references.</param>
|
||||
public void RegisterInstance<T>(T component, bool replace = true) where T : UnityComponent
|
||||
{
|
||||
string tName = GetInstanceName<T>();
|
||||
if (_registeredComponents.ContainsKey(tName) && !replace)
|
||||
{
|
||||
InternalLogWarning($"Component {tName} is already registered.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_registeredComponents[tName] = component;
|
||||
RemoveNullPendingDelegates();
|
||||
// If in pending invokes also send these out.
|
||||
if (_pendingInvokes.TryGetValue(tName, out List<Action<UnityComponent>> dels))
|
||||
{
|
||||
for (int i = 0; i < dels.Count; i++)
|
||||
dels[i].Invoke(component);
|
||||
/* Clear delegates but do not remove dictionary entry
|
||||
* to prevent list from being re-initialized. */
|
||||
dels.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to registers a new component to this NetworkManager.
|
||||
/// This will not register the instance if another already exists.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type to register.</typeparam>
|
||||
/// <param name = "component">Reference of the component being registered.</param>
|
||||
/// <returns>True if was able to register, false if an instance is already registered.</returns>
|
||||
public bool TryRegisterInstance<T>(T component) where T : UnityComponent
|
||||
{
|
||||
string tName = GetInstanceName<T>();
|
||||
if (_registeredComponents.ContainsKey(tName))
|
||||
return false;
|
||||
else
|
||||
RegisterInstance(component, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a component from this NetworkManager.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type to unregister.</typeparam>
|
||||
public void UnregisterInstance<T>() where T : UnityComponent
|
||||
{
|
||||
string tName = GetInstanceName<T>();
|
||||
_registeredComponents.Remove(tName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes delegates from pending invokes when may have gone missing.
|
||||
/// </summary>
|
||||
private void RemoveNullPendingDelegates()
|
||||
{
|
||||
foreach (List<Action<UnityComponent>> handlers in _pendingInvokes.Values)
|
||||
{
|
||||
for (int i = 0; i < handlers.Count; i++)
|
||||
{
|
||||
if (handlers[i] == null)
|
||||
{
|
||||
handlers.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name to use for T.
|
||||
/// </summary>
|
||||
private string GetInstanceName<T>()
|
||||
{
|
||||
return typeof(T).FullName;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5761633dda73e7447a3a41b87354d06e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/NetworkManager.QOL.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,650 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
#if UNITY_EDITOR
|
||||
using FishNet.Editing.PrefabCollectionGenerator;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Client;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Managing.Transporting;
|
||||
using UnityEngine;
|
||||
using FishNet.Managing.Scened;
|
||||
using FishNet.Object;
|
||||
using FishNet.Documenting;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using FishNet.Managing.Observing;
|
||||
using System.Linq;
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Managing.Debugging;
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Managing.Statistic;
|
||||
using FishNet.Utility.Performance;
|
||||
using FishNet.Component.ColliderRollback;
|
||||
using FishNet.Component.Transforming.Beta;
|
||||
using FishNet.Configuring;
|
||||
using FishNet.Configuring.EditorCloning;
|
||||
using FishNet.Managing.Predicting;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
|
||||
namespace FishNet.Managing
|
||||
{
|
||||
/// <summary>
|
||||
/// Acts as a container for all things related to your networking session.
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(short.MinValue)]
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("FishNet/Manager/NetworkManager")]
|
||||
public sealed partial class NetworkManager : MonoBehaviour
|
||||
{
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// How to persist with multiple NetworkManagers.
|
||||
/// </summary>
|
||||
public enum PersistenceType
|
||||
{
|
||||
/// <summary>
|
||||
/// Destroy any new NetworkManagers.
|
||||
/// </summary>
|
||||
DestroyNewest,
|
||||
/// <summary>
|
||||
/// Destroy previous NetworkManager when a new NetworkManager occurs.
|
||||
/// </summary>
|
||||
DestroyOldest,
|
||||
/// <summary>
|
||||
/// Allow multiple NetworkManagers, do not destroy any automatically.
|
||||
/// </summary>
|
||||
AllowMultiple
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// True if this instance of the NetworkManager is initialized.
|
||||
/// </summary>
|
||||
public bool Initialized { get; private set; }
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
private static List<NetworkManager> _instances = new();
|
||||
/// <summary>
|
||||
/// Currently initialized NetworkManagers.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<NetworkManager> Instances
|
||||
{
|
||||
get
|
||||
{
|
||||
/* Remove null instances of NetworkManager.
|
||||
* This shouldn't happen because instances are removed
|
||||
* OnDestroy but none the less something is causing
|
||||
* it. */
|
||||
for (int i = 0; i < _instances.Count; i++)
|
||||
{
|
||||
if (_instances[i] == null)
|
||||
{
|
||||
_instances.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
return _instances;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// PredictionManager for this NetworkManager.
|
||||
/// </summary>
|
||||
internal PredictionManager PredictionManager { get; private set; }
|
||||
/// <summary>
|
||||
/// ServerManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public ServerManager ServerManager { get; private set; }
|
||||
/// <summary>
|
||||
/// ClientManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public ClientManager ClientManager { get; private set; }
|
||||
/// <summary>
|
||||
/// TransportManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public TransportManager TransportManager { get; private set; }
|
||||
/// <summary>
|
||||
/// TimeManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public TimeManager TimeManager { get; private set; }
|
||||
/// <summary>
|
||||
/// SceneManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public SceneManager SceneManager { get; private set; }
|
||||
/// <summary>
|
||||
/// ObserverManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public ObserverManager ObserverManager { get; private set; }
|
||||
#if FISHNET_THREADED_TICKSMOOTHERS
|
||||
/// <summary>
|
||||
/// TickSmoothingManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public TickSmoothingManager TickSmoothingManager { get; private set; }
|
||||
#endif
|
||||
/// <summary>
|
||||
/// DebugManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public DebugManager DebugManager { get; private set; }
|
||||
/// <summary>
|
||||
/// StatisticsManager for this NetworkManager.
|
||||
/// </summary>
|
||||
public StatisticsManager StatisticsManager { get; private set; }
|
||||
/// <summary>
|
||||
/// An empty connection reference. Used when a connection cannot be found to prevent object creation.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public static NetworkConnection EmptyConnection { get; private set; } = new();
|
||||
#endregion
|
||||
|
||||
#region Internal.
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
/// <summary>
|
||||
/// Names of broadcasts.
|
||||
/// Key: Broadcast key.
|
||||
/// Value: Name.
|
||||
/// </summary>
|
||||
/// <remarks>Only broadcast which were registered at least once will be found within this collection. This collection is never cleared.</remarks>
|
||||
private Dictionary<ushort, string> _broadcastNames = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a broadcast name by key.
|
||||
/// </summary>
|
||||
internal string GetBroadcastName(ushort key)
|
||||
{
|
||||
if (_broadcastNames.TryGetValueIL2CPP(key, out string result))
|
||||
return result;
|
||||
|
||||
const string notFoundName = "Unregistered Broadcasts";
|
||||
|
||||
return notFoundName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a broadcast name to be used with GetBroadcastName.
|
||||
/// </summary>
|
||||
internal void SetBroadcastName<T>(ushort key) where T : struct, IBroadcast
|
||||
{
|
||||
// Avoid allocation by checking if key already exist.
|
||||
if (!_broadcastNames.ContainsKey(key))
|
||||
_broadcastNames[key] = typeof(T).Name;
|
||||
}
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Starting index for RpcLinks.
|
||||
/// </summary>
|
||||
internal static ushort StartingRpcLinkIndex;
|
||||
#if DEVELOPMENT
|
||||
/// <summary>
|
||||
/// Logs data about parser to help debug.
|
||||
/// </summary>
|
||||
internal PacketIdHistory PacketIdHistory = new();
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Timestamp when the first NetworkManager instance was launched.
|
||||
/// </summary>
|
||||
internal static long LaunchTimestamp;
|
||||
#endregion
|
||||
|
||||
#region Serialized.
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// True to refresh the DefaultPrefabObjects collection whenever the editor enters play mode. This is an attempt to alleviate the DefaultPrefabObjects scriptable object not refreshing when using multiple editor applications such as ParrelSync.
|
||||
/// </summary>
|
||||
[Tooltip("True to refresh the DefaultPrefabObjects collection whenever the editor enters play mode. This is an attempt to alleviate the DefaultPrefabObjects scriptable object not refreshing when using multiple editor applications such as ParrelSync.")]
|
||||
[SerializeField]
|
||||
private bool _refreshDefaultPrefabs = false;
|
||||
#endif
|
||||
/// <summary>
|
||||
/// True to have your application run while in the background.
|
||||
/// </summary>
|
||||
[Tooltip("True to have your application run while in the background.")]
|
||||
[SerializeField]
|
||||
private bool _runInBackground = true;
|
||||
/// <summary>
|
||||
/// True to make this instance DontDestroyOnLoad. This is typical if you only want one NetworkManager.
|
||||
/// </summary>
|
||||
[Tooltip("True to make this instance DontDestroyOnLoad. This is typical if you only want one NetworkManager.")]
|
||||
[SerializeField]
|
||||
private bool _dontDestroyOnLoad = true;
|
||||
/// <summary>
|
||||
/// Object pool to use for this NetworkManager. Value may be null.
|
||||
/// </summary>
|
||||
public ObjectPool ObjectPool => _objectPool;
|
||||
[Tooltip("Object pool to use for this NetworkManager. Value may be null.")]
|
||||
[SerializeField]
|
||||
private ObjectPool _objectPool;
|
||||
/// <summary>
|
||||
/// How to persist when other NetworkManagers are introduced.
|
||||
/// </summary>
|
||||
[Tooltip("How to persist when other NetworkManagers are introduced.")]
|
||||
[SerializeField]
|
||||
private PersistenceType _persistence = PersistenceType.DestroyNewest;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Value of Application.RunInBackground before starting any network connection.
|
||||
/// </summary>
|
||||
/// <remarks>A null value indicates not yet set.</remarks>
|
||||
private bool? _offlineApplicationRunInBackground;
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// Version of this release.
|
||||
/// </summary>
|
||||
public const string FISHNET_VERSION = "4.6.22";
|
||||
/// <summary>
|
||||
/// Maximum framerate allowed.
|
||||
/// </summary>
|
||||
internal const ushort MAXIMUM_FRAMERATE = 500;
|
||||
/// <summary>
|
||||
/// Timestamp to use when value is not set.
|
||||
/// </summary>
|
||||
internal const long UNSET_LAUNCH_TIMESTAMP = 0;
|
||||
/// <summary>
|
||||
/// Value to use when the launch timestamp is calculated, but happens to be the unset value.
|
||||
/// </summary>
|
||||
private const long REPAIR_LAUNCH_TIMESTAMP_CONFLICT_VALUE = UNSET_LAUNCH_TIMESTAMP + 1;
|
||||
#endregion
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
InitializeLogging();
|
||||
if (!ValidateSpawnablePrefabs(true))
|
||||
return;
|
||||
|
||||
if (StartingRpcLinkIndex == 0)
|
||||
StartingRpcLinkIndex = (ushort)(Enums.GetHighestValue<PacketId>() + 1);
|
||||
|
||||
if (!CanPersist())
|
||||
return;
|
||||
|
||||
// If is the first instance then set launch timestamp.
|
||||
if (_instances.Count == 0)
|
||||
{
|
||||
LaunchTimestamp = DateTime.Now.ToBinary();
|
||||
// What are the odds fo this happening!
|
||||
if (LaunchTimestamp == UNSET_LAUNCH_TIMESTAMP)
|
||||
LaunchTimestamp = REPAIR_LAUNCH_TIMESTAMP_CONFLICT_VALUE;
|
||||
}
|
||||
|
||||
bool isDefaultPrefabs = SpawnablePrefabs != null && SpawnablePrefabs is DefaultPrefabObjects;
|
||||
CloneChecker.IsMultiplayerClone(out EditorCloneType cloneType);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/* If first instance then force
|
||||
* default prefabs to repopulate.
|
||||
* This is only done in editor because
|
||||
* cloning tools sometimes don't synchronize
|
||||
* scriptable object changes, which is what
|
||||
* the default prefabs is. */
|
||||
|
||||
/* There is an issue with Unity Multiplayer where if a prefab
|
||||
* is modified in the main editor with a multiplayer window open,
|
||||
* the modified prefab becomes null in the collection. Because of this,
|
||||
* we must re-generate on the cloned window. */
|
||||
if (isDefaultPrefabs && (cloneType == EditorCloneType.UnityMultiplayer || (_refreshDefaultPrefabs && _instances.Count == 0)))
|
||||
{
|
||||
Generator.IgnorePostProcess = true;
|
||||
Debug.Log("DefaultPrefabCollection is being refreshed.");
|
||||
Generator.GenerateFull(initializeAdded: false);
|
||||
Generator.IgnorePostProcess = false;
|
||||
}
|
||||
#endif
|
||||
// If default prefabs then also make a new instance and sort them.
|
||||
if (isDefaultPrefabs)
|
||||
{
|
||||
DefaultPrefabObjects originalDpo = (DefaultPrefabObjects)SpawnablePrefabs;
|
||||
// If not editor then a new instance must be made and sorted.
|
||||
DefaultPrefabObjects instancedDpo = ScriptableObject.CreateInstance<DefaultPrefabObjects>();
|
||||
instancedDpo.AddObjects(originalDpo.Prefabs.ToList(), checkForDuplicates: false, initializeAdded: false);
|
||||
instancedDpo.Sort();
|
||||
SpawnablePrefabs = instancedDpo;
|
||||
}
|
||||
|
||||
if (TryGetComponent<NetworkObject>(out _))
|
||||
InternalLogError($"NetworkObject component found on the NetworkManager object {gameObject.name}. This is not allowed and will cause problems. Remove the NetworkObject component from this object.");
|
||||
|
||||
SpawnablePrefabs.InitializePrefabRange(0);
|
||||
SpawnablePrefabs.SetCollectionId(0);
|
||||
|
||||
SetDontDestroyOnLoad();
|
||||
SetRunInBackground();
|
||||
DebugManager = GetOrCreateComponent<DebugManager>();
|
||||
TransportManager = GetOrCreateComponent<TransportManager>();
|
||||
|
||||
ServerManager = GetOrCreateComponent<ServerManager>();
|
||||
ClientManager = GetOrCreateComponent<ClientManager>();
|
||||
TimeManager = GetOrCreateComponent<TimeManager>();
|
||||
SceneManager = GetOrCreateComponent<SceneManager>();
|
||||
ObserverManager = GetOrCreateComponent<ObserverManager>();
|
||||
#if THREADED_TICKSMOOTHERS
|
||||
TickSmoothingManager = GetOrCreateComponent<TickSmoothingManager>();
|
||||
#endif
|
||||
RollbackManager = GetOrCreateComponent<RollbackManager>();
|
||||
PredictionManager = GetOrCreateComponent<PredictionManager>();
|
||||
StatisticsManager = GetOrCreateComponent<StatisticsManager>();
|
||||
if (_objectPool == null)
|
||||
_objectPool = GetOrCreateComponent<DefaultObjectPool>();
|
||||
|
||||
InitializeComponents();
|
||||
|
||||
_instances.Add(this);
|
||||
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
ServerManager.StartForHeadless();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_instances.Remove(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes components. To be called after all components are added.
|
||||
/// </summary>
|
||||
private void InitializeComponents()
|
||||
{
|
||||
TimeManager.InitializeOnce_Internal(this);
|
||||
TimeManager.OnLateUpdate += TimeManager_OnLateUpdate;
|
||||
TransportManager.InitializeOnce_Internal(this);
|
||||
|
||||
/* There is no need to unsubscribe to either of the connection
|
||||
* state events below since components
|
||||
* will be destroyed with the NetworkManager. */
|
||||
|
||||
ClientManager.InitializeOnce_Internal(this);
|
||||
ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState;
|
||||
|
||||
ServerManager.InitializeOnce_Internal(this);
|
||||
ServerManager.OnServerConnectionState += ServerManager_OnServerConnectionState;
|
||||
|
||||
SceneManager.InitializeOnce_Internal(this);
|
||||
ObserverManager.InitializeOnce_Internal(this);
|
||||
#if THREADED_TICKSMOOTHERS
|
||||
TickSmoothingManager.InitializeOnce_Internal(this);
|
||||
#endif
|
||||
RollbackManager.InitializeOnce_Internal(this);
|
||||
PredictionManager.InitializeOnce(this);
|
||||
StatisticsManager.InitializeOnce_Internal(this);
|
||||
_objectPool.InitializeOnce(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the local server connection changes.
|
||||
/// </summary>
|
||||
private void ServerManager_OnServerConnectionState(ServerConnectionStateArgs obj) => UpdateRunInBackgroundIfApplicable();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the local client connection changes.
|
||||
/// </summary>
|
||||
private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs obj) => UpdateRunInBackgroundIfApplicable();
|
||||
|
||||
/// <summary>
|
||||
/// Updates the frame rate based on server and client status.
|
||||
/// </summary>
|
||||
internal void UpdateFramerate()
|
||||
{
|
||||
bool clientStarted = ClientManager.Started;
|
||||
bool serverStarted = ServerManager.Started;
|
||||
|
||||
int frameRate = 0;
|
||||
// If both client and server are started then use whichever framerate is higher.
|
||||
if (clientStarted && serverStarted)
|
||||
frameRate = Math.Max(ServerManager.FrameRate, ClientManager.FrameRate);
|
||||
else if (clientStarted)
|
||||
frameRate = ClientManager.FrameRate;
|
||||
else if (serverStarted)
|
||||
frameRate = ServerManager.FrameRate;
|
||||
|
||||
/* Make sure framerate isn't set to max on server.
|
||||
* If it is then default to tick rate. If framerate is
|
||||
* less than tickrate then also set to tickrate. */
|
||||
#if UNITY_SERVER && !UNITY_EDITOR
|
||||
ushort minimumServerFramerate = (ushort)(TimeManager.TickRate + 15);
|
||||
if (frameRate == MAXIMUM_FRAMERATE)
|
||||
frameRate = minimumServerFramerate;
|
||||
else if (frameRate < TimeManager.TickRate)
|
||||
frameRate = minimumServerFramerate;
|
||||
#endif
|
||||
// If there is a framerate to set.
|
||||
if (frameRate > 0)
|
||||
Application.targetFrameRate = frameRate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when MonoBehaviours call LateUpdate.
|
||||
/// </summary>
|
||||
private void TimeManager_OnLateUpdate()
|
||||
{
|
||||
/* Some reason runinbackground becomes unset
|
||||
* or the setting goes ignored some times when it's set
|
||||
* in awake. Rather than try to fix or care why Unity
|
||||
* does this just set it in LateUpdate(or Update). */
|
||||
SetRunInBackground();
|
||||
// Let's object pooler do regular work.
|
||||
_objectPool.LateUpdate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if this NetworkManager can initialize.
|
||||
/// </summary>
|
||||
/// <param name="instanceRetained">True if this instance will be retained/kept.</param>
|
||||
/// <returns></returns>
|
||||
private bool CanPersist()
|
||||
{
|
||||
/* If allow multiple then any number of
|
||||
* NetworkManagers are allowed. Don't
|
||||
* automatically destroy any. */
|
||||
if (_persistence == PersistenceType.AllowMultiple)
|
||||
return true;
|
||||
|
||||
List<NetworkManager> instances = Instances.ToList();
|
||||
// This is the first instance, it may initialize.
|
||||
if (instances.Count == 0)
|
||||
return true;
|
||||
|
||||
// First instance of NM.
|
||||
NetworkManager firstInstance = instances[0];
|
||||
|
||||
// If to destroy the oldest.
|
||||
if (_persistence == PersistenceType.DestroyOldest)
|
||||
{
|
||||
InternalLog($"NetworkManager on object {firstInstance.name} is being destroyed due to persistence type {_persistence}. A NetworkManager instance has been created on {gameObject.name}.");
|
||||
DestroyImmediate(firstInstance.gameObject);
|
||||
// This being the new one will persist, allow initialization.
|
||||
return true;
|
||||
}
|
||||
|
||||
// If to destroy the newest.
|
||||
if (_persistence == PersistenceType.DestroyNewest)
|
||||
{
|
||||
InternalLog($"NetworkManager on object {gameObject.name} is being destroyed due to persistence type {_persistence}. A NetworkManager instance already exist on {firstInstance.name}.");
|
||||
DestroyImmediate(gameObject);
|
||||
// This one is being destroyed because its the newest.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Unhandled.
|
||||
InternalLog($"Persistence type of {_persistence} is unhandled on {gameObject.name}. Initialization will not proceed, and this NetworkManager instance will be destroyed.");
|
||||
DestroyImmediate(gameObject);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates SpawnablePrefabs field and returns if validated successfully.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool ValidateSpawnablePrefabs(bool print)
|
||||
{
|
||||
// If null and object is in a scene.
|
||||
if (SpawnablePrefabs == null && !string.IsNullOrEmpty(gameObject.scene.name))
|
||||
{
|
||||
// First try to fetch the file, only if editor and not in play mode.
|
||||
#if UNITY_EDITOR
|
||||
if (!ApplicationState.IsPlaying())
|
||||
{
|
||||
SpawnablePrefabs = Generator.GetDefaultPrefabObjects();
|
||||
if (SpawnablePrefabs != null)
|
||||
{
|
||||
Debug.Log($"SpawnablePrefabs was set to DefaultPrefabObjects automatically on object {gameObject.name} in scene {gameObject.scene.name}.");
|
||||
EditorUtility.SetDirty(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// Always throw an error as this would cause failure.
|
||||
if (print)
|
||||
Debug.LogError($"SpawnablePrefabs is null on {gameObject.name}. Select the NetworkManager in scene {gameObject.scene.name} and choose a prefabs file. Choosing DefaultPrefabObjects will automatically populate prefabs for you.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets DontDestroyOnLoad if configured to.
|
||||
/// </summary>
|
||||
private void SetDontDestroyOnLoad()
|
||||
{
|
||||
if (_dontDestroyOnLoad)
|
||||
DontDestroyOnLoad(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets Application.runInBackground to runInBackground.
|
||||
/// </summary>
|
||||
private void SetRunInBackground()
|
||||
{
|
||||
Application.runInBackground = _runInBackground;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a component, creating and adding it if it does not exist.
|
||||
/// </summary>
|
||||
/// <param name = "presetValue">Value which may already be set. When not null this is returned instead.</param>
|
||||
private T GetOrCreateComponent<T>(T presetValue = null) where T : UnityEngine.Component
|
||||
{
|
||||
// If already set then return set value.
|
||||
if (presetValue != null)
|
||||
return presetValue;
|
||||
|
||||
if (gameObject.TryGetComponent(out T result))
|
||||
return result;
|
||||
else
|
||||
return gameObject.AddComponent<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears a client collection after disposing of the NetworkConnections.
|
||||
/// </summary>
|
||||
/// <param name = "clients"></param>
|
||||
internal void ClearClientsCollection(Dictionary<int, NetworkConnection> clients, int transportIndex = -1)
|
||||
{
|
||||
// True to dispose all connections.
|
||||
bool disposeAll = transportIndex < 0;
|
||||
List<int> cache = CollectionCaches<int>.RetrieveList();
|
||||
|
||||
/* Only reset NetworkConnections if server is also not started.
|
||||
* Otherwise, this would reset connections for the server side
|
||||
* as well. */
|
||||
bool canResetState = !IsServerStarted;
|
||||
|
||||
foreach (KeyValuePair<int, NetworkConnection> kvp in clients)
|
||||
{
|
||||
NetworkConnection value = kvp.Value;
|
||||
// If to check transport index.
|
||||
if (!disposeAll)
|
||||
{
|
||||
if (value.TransportIndex == transportIndex)
|
||||
{
|
||||
cache.Add(kvp.Key);
|
||||
if (canResetState)
|
||||
value.ResetState();
|
||||
}
|
||||
}
|
||||
// Not using transport index, no check required.
|
||||
else
|
||||
{
|
||||
if (canResetState)
|
||||
value.ResetState();
|
||||
}
|
||||
}
|
||||
|
||||
// If all are being disposed the collection can be cleared.
|
||||
if (disposeAll)
|
||||
{
|
||||
clients.Clear();
|
||||
}
|
||||
// Otherwise, only remove those which were disposed.
|
||||
else
|
||||
{
|
||||
foreach (int item in cache)
|
||||
clients.Remove(item);
|
||||
}
|
||||
|
||||
CollectionCaches<int>.Store(cache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates Application.RunInBackground to NetworkManager setting when connected, and application setting when not connected.
|
||||
/// </summary>
|
||||
private void UpdateRunInBackgroundIfApplicable()
|
||||
{
|
||||
// Not configured to update values.
|
||||
if (!_runInBackground)
|
||||
return;
|
||||
|
||||
bool anythingStarted = ServerManager.IsAnyServerStarted() || ClientManager.Started;
|
||||
|
||||
// Check to set values.
|
||||
if (anythingStarted)
|
||||
{
|
||||
// Already set.
|
||||
if (_offlineApplicationRunInBackground != null)
|
||||
return;
|
||||
|
||||
//Update run in background after caching current value.
|
||||
_offlineApplicationRunInBackground = Application.runInBackground;
|
||||
Application.runInBackground = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Already unset.
|
||||
if (_offlineApplicationRunInBackground == null)
|
||||
return;
|
||||
|
||||
//Update run in background then unset cached value.
|
||||
Application.runInBackground = _offlineApplicationRunInBackground.Value;
|
||||
_offlineApplicationRunInBackground = null;
|
||||
}
|
||||
}
|
||||
|
||||
#region Editor.
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
if (SpawnablePrefabs == null)
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
ValidateSpawnablePrefabs(true);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2c95dfde7d73b54dbbdc23155d35d36
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/NetworkManager.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0cc8067328d24b4ba59a946ec4b8622
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
using FishNet.Object;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
/// <summary>
|
||||
/// When using dual prefabs, defines which prefab to spawn for server, and which for clients.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct DualPrefab
|
||||
{
|
||||
public NetworkObject Server;
|
||||
public NetworkObject Client;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76840b2b810d8fc45aeccef03122763c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/DualPrefab.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,525 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using System;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using FishNet.Serializing.Helping;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
public abstract partial class ManagedObjects
|
||||
{
|
||||
#region Consts.
|
||||
/// <summary>
|
||||
/// Number of bytes to reserve for a predicted spawn length.
|
||||
/// </summary>
|
||||
internal const byte PREDICTED_SPAWN_BYTES = 2;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Reads and outputs a transforms values.
|
||||
/// </summary>
|
||||
protected void ReadTransformProperties(Reader reader, out Vector3? localPosition, out Quaternion? localRotation, out Vector3? localScale)
|
||||
{
|
||||
// Read changed.
|
||||
TransformPropertiesFlag tpf = (TransformPropertiesFlag)reader.ReadUInt8Unpacked();
|
||||
// Position.
|
||||
if (tpf.FastContains(TransformPropertiesFlag.Position))
|
||||
localPosition = reader.ReadVector3();
|
||||
else
|
||||
localPosition = null;
|
||||
// Rotation.
|
||||
if (tpf.FastContains(TransformPropertiesFlag.Rotation))
|
||||
localRotation = reader.ReadQuaternion(NetworkManager.ServerManager.SpawnPacking.Rotation);
|
||||
else
|
||||
localRotation = null;
|
||||
// Scale.
|
||||
if (tpf.FastContains(TransformPropertiesFlag.Scale))
|
||||
localScale = reader.ReadVector3();
|
||||
else
|
||||
localScale = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a spawn to a client or server.
|
||||
/// If connection is not null the spawn is sent ot a client, otherwise it will be considered a predicted spawn.
|
||||
/// </summary>
|
||||
/// <returns>True if spawn was written.</returns>
|
||||
internal bool WriteSpawn(NetworkObject nob, PooledWriter writer, NetworkConnection connection)
|
||||
{
|
||||
writer.WritePacketIdUnpacked(PacketId.ObjectSpawn);
|
||||
|
||||
ReservedLengthWriter asClientReservedWriter = ReservedWritersExtensions.Retrieve();
|
||||
bool predictedSpawn = connection == null;
|
||||
|
||||
if (predictedSpawn)
|
||||
asClientReservedWriter.Initialize(writer, PREDICTED_SPAWN_BYTES);
|
||||
|
||||
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;
|
||||
|
||||
if (connection == nob.PredictedSpawner)
|
||||
st |= SpawnType.IsPredictedSpawner;
|
||||
|
||||
// Call before writing SpawnType so nested can be appended to it if needed.
|
||||
PooledWriter nestedWriter = WriteNestedSpawn(nob, ref st);
|
||||
|
||||
writer.WriteUInt8Unpacked((byte)st);
|
||||
// Write parent here if writer for parent is valid.
|
||||
if (nestedWriter != null)
|
||||
{
|
||||
writer.WriteArraySegment(nestedWriter.GetArraySegment());
|
||||
WriterPool.Store(nestedWriter);
|
||||
}
|
||||
|
||||
writer.WriteSpawnedNetworkObject(nob);
|
||||
writer.WriteNetworkConnection(nob.Owner);
|
||||
|
||||
// Properties on the transform which diff from serialized value.
|
||||
WriteChangedTransformProperties(nob, sceneObject, writer);
|
||||
|
||||
/* Writing a scene object. */
|
||||
if (sceneObject)
|
||||
{
|
||||
writer.WriteUInt64Unpacked(nob.SceneId);
|
||||
#if DEVELOPMENT
|
||||
CheckWriteSceneObjectDetails(nob, writer);
|
||||
#endif
|
||||
}
|
||||
/* Writing a spawned object. */
|
||||
else
|
||||
{
|
||||
writer.WriteNetworkObjectId(nob.PrefabId);
|
||||
}
|
||||
|
||||
NetworkConnection payloadSender = predictedSpawn ? NetworkManager.EmptyConnection : connection;
|
||||
WritePayload(payloadSender, nob, writer);
|
||||
|
||||
/* RPCLinks and SyncTypes are ONLY written by the server.
|
||||
* Although not necessary, both sides will write the length
|
||||
* to keep the reading of spawns consistent. */
|
||||
WriteRpcLinks(nob, writer);
|
||||
WriteSyncTypesForSpawn(nob, writer, connection);
|
||||
|
||||
bool canWrite;
|
||||
// Need to validate predicted spawn length.
|
||||
if (predictedSpawn)
|
||||
{
|
||||
int maxContentLength;
|
||||
if (PREDICTED_SPAWN_BYTES == 2)
|
||||
{
|
||||
maxContentLength = ushort.MaxValue;
|
||||
}
|
||||
else
|
||||
#pragma warning disable CS0162 // Unreachable code detected
|
||||
{
|
||||
NetworkManager.LogError($"Unhandled spawn bytes value of {PREDICTED_SPAWN_BYTES}.");
|
||||
maxContentLength = 0;
|
||||
}
|
||||
#pragma warning restore CS0162 // Unreachable code detected
|
||||
|
||||
// Too much content; this really should absolutely never happen.
|
||||
canWrite = asClientReservedWriter.Length <= maxContentLength;
|
||||
if (!canWrite)
|
||||
NetworkManager.LogError($"A single predicted spawns may not exceed {maxContentLength} bytes in length. Written length is {asClientReservedWriter.Length}. Predicted spawn for {nob.name} will be despawned immediately.");
|
||||
// Not too large.
|
||||
else
|
||||
asClientReservedWriter.WriteLength();
|
||||
}
|
||||
|
||||
// Not predicted, server can always write.
|
||||
else
|
||||
{
|
||||
canWrite = true;
|
||||
}
|
||||
|
||||
asClientReservedWriter.Store();
|
||||
return canWrite;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes RPCLinks for a NetworkObject.
|
||||
/// </summary>
|
||||
protected void WriteRpcLinks(NetworkObject nob, PooledWriter writer)
|
||||
{
|
||||
ReservedLengthWriter rw = ReservedWritersExtensions.Retrieve();
|
||||
|
||||
rw.Initialize(writer, NetworkBehaviour.RPCLINK_RESERVED_BYTES);
|
||||
|
||||
if (NetworkManager.IsServerStarted)
|
||||
{
|
||||
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
|
||||
nb.WriteRpcLinks(writer);
|
||||
}
|
||||
|
||||
rw.WriteLength();
|
||||
|
||||
rw.Store();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads RpcLinks from a spawn into an arraySegment.
|
||||
/// </summary>
|
||||
protected ArraySegment<byte> ReadRpcLinks(PooledReader reader)
|
||||
{
|
||||
uint segmentSize = ReservedLengthWriter.ReadLength(reader, NetworkBehaviour.RPCLINK_RESERVED_BYTES);
|
||||
return reader.ReadArraySegment((int)segmentSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes SyncTypes for a NetworkObject.
|
||||
/// </summary>
|
||||
protected void WriteSyncTypesForSpawn(NetworkObject nob, PooledWriter writer, NetworkConnection connection)
|
||||
{
|
||||
ReservedLengthWriter rw = ReservedWritersExtensions.Retrieve();
|
||||
|
||||
// SyncTypes.
|
||||
rw.Initialize(writer, NetworkBehaviour.SYNCTYPE_RESERVE_BYTES);
|
||||
|
||||
if (NetworkManager.IsServerStarted)
|
||||
{
|
||||
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
|
||||
nb.WriteSyncTypesForSpawn(writer, connection);
|
||||
}
|
||||
|
||||
rw.WriteLength();
|
||||
rw.Store();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads SyncTypes from a spawn into an arraySegment.
|
||||
/// </summary>
|
||||
protected ArraySegment<byte> ReadSyncTypesForSpawn(PooledReader reader)
|
||||
{
|
||||
uint segmentSize = ReservedLengthWriter.ReadLength(reader, NetworkBehaviour.SYNCTYPE_RESERVE_BYTES);
|
||||
return reader.ReadArraySegment((int)segmentSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writers a nested spawn and returns writer used.
|
||||
/// If nested was not written null is returned.
|
||||
/// </summary>
|
||||
internal PooledWriter WriteNestedSpawn(NetworkObject nob, ref SpawnType st)
|
||||
{
|
||||
// Check to write parent behaviour or nob.
|
||||
NetworkBehaviour parentNb;
|
||||
Transform t = nob.transform.parent;
|
||||
if (t != null)
|
||||
{
|
||||
parentNb = nob.CurrentParentNetworkBehaviour;
|
||||
/* Check for a NetworkObject if there is no NetworkBehaviour.
|
||||
* There is a small chance the parent object will only contain
|
||||
* a NetworkObject. */
|
||||
if (parentNb == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
// No parent.
|
||||
else
|
||||
{
|
||||
if (!parentNb.IsSpawned)
|
||||
{
|
||||
NetworkManager.LogWarning($"Parent {t.name} is not spawned. {nob.name} will not have it's parent sent in the spawn message.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
st |= SpawnType.Nested;
|
||||
PooledWriter writer = WriterPool.Retrieve();
|
||||
writer.WriteUInt8Unpacked(nob.ComponentIndex);
|
||||
writer.WriteNetworkBehaviour(parentNb);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
}
|
||||
// CurrentNetworkBehaviour is not set.
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If flags indicate there is a nested spawn the objectId and NetworkBehaviourId are output.
|
||||
/// Otherwise, output value sare set to null.
|
||||
/// </summary>
|
||||
internal void ReadNestedSpawnIds(PooledReader reader, SpawnType st, out byte? nobComponentIndex, out int? parentObjectId, out byte? parentComponentIndex, HashSet<int> readSpawningObjects = null)
|
||||
{
|
||||
if (st.FastContains(SpawnType.Nested))
|
||||
{
|
||||
nobComponentIndex = reader.ReadUInt8Unpacked();
|
||||
reader.ReadNetworkBehaviour(out int objectId, out byte componentIndex, readSpawningObjects);
|
||||
if (objectId != NetworkObject.UNSET_OBJECTID_VALUE)
|
||||
{
|
||||
parentObjectId = objectId;
|
||||
parentComponentIndex = componentIndex;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall through, not nested.
|
||||
nobComponentIndex = null;
|
||||
parentObjectId = null;
|
||||
parentComponentIndex = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finishes reading a scene object.
|
||||
/// </summary>
|
||||
protected void ReadSceneObjectId(PooledReader reader, out ulong sceneId)
|
||||
{
|
||||
sceneId = reader.ReadUInt64Unpacked();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes changed transform proeprties to writer.
|
||||
/// </summary>
|
||||
protected void WriteChangedTransformProperties(NetworkObject nob, bool sceneObject, Writer headerWriter)
|
||||
{
|
||||
/* Write changed transform properties. */
|
||||
TransformPropertiesFlag tpf;
|
||||
/* If a scene object or nested during initialization then
|
||||
* write changes compared to initialized values. */
|
||||
if (sceneObject || nob.InitializedParentNetworkBehaviour != null)
|
||||
{
|
||||
tpf = nob.GetTransformChanges(nob.SerializedTransformProperties);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This should not be possible when spawning non-nested.
|
||||
if (nob.PrefabId == NetworkObject.UNSET_PREFABID_VALUE)
|
||||
{
|
||||
NetworkManager.LogWarning($"NetworkObject {nob.ToString()} unexpectedly has an unset PrefabId while it's not nested. Please report this warning.");
|
||||
tpf = TransformPropertiesFlag.Everything;
|
||||
}
|
||||
else
|
||||
{
|
||||
PrefabObjects po = NetworkManager.GetPrefabObjects<PrefabObjects>(nob.SpawnableCollectionId, false);
|
||||
tpf = nob.GetTransformChanges(po.GetObject(asServer: true, nob.PrefabId).gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
headerWriter.WriteUInt8Unpacked((byte)tpf);
|
||||
// If properties have changed.
|
||||
if (tpf != TransformPropertiesFlag.Unset)
|
||||
{
|
||||
// Write any changed properties.
|
||||
if (tpf.FastContains(TransformPropertiesFlag.Position))
|
||||
headerWriter.WriteVector3(nob.transform.localPosition);
|
||||
if (tpf.FastContains(TransformPropertiesFlag.Rotation))
|
||||
headerWriter.WriteQuaternion(nob.transform.localRotation, NetworkManager.ServerManager.SpawnPacking.Rotation);
|
||||
if (tpf.FastContains(TransformPropertiesFlag.Scale))
|
||||
headerWriter.WriteVector3(nob.transform.localScale);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a despawn.
|
||||
/// </summary>
|
||||
protected void WriteDespawn(NetworkObject nob, DespawnType despawnType, Writer everyoneWriter)
|
||||
{
|
||||
everyoneWriter.WritePacketIdUnpacked(PacketId.ObjectDespawn);
|
||||
everyoneWriter.WriteNetworkObjectForDespawn(nob, despawnType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a scene NetworkObject and sets transform values.
|
||||
/// </summary>
|
||||
internal NetworkObject GetSceneNetworkObject(ulong sceneId, string sceneName, string objectName)
|
||||
{
|
||||
NetworkObject nob;
|
||||
SceneObjects_Internal.TryGetValueIL2CPP(sceneId, out nob);
|
||||
// If found in scene objects.
|
||||
if (nob == null)
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
string missingObjectDetails = sceneName == string.Empty ? "For more information on the missing object add DebugManager to your NetworkManager and enable WriteSceneObjectDetails" : $"Scene containing the object is '{sceneName}', object name is '{objectName}";
|
||||
NetworkManager.LogError($"SceneId of {sceneId} not found in SceneObjects. {missingObjectDetails}. This may occur if your scene differs between client and server, if client does not have the scene loaded, or if networked scene objects do not have a SceneCondition. See ObserverManager in the documentation for more on conditions.");
|
||||
#else
|
||||
NetworkManager.LogError($"SceneId of {sceneId} not found in SceneObjects. This may occur if your scene differs between client and server, if client does not have the scene loaded, or if networked scene objects do not have a SceneCondition. See ObserverManager in the documentation for more on conditions.");
|
||||
#endif
|
||||
}
|
||||
|
||||
return nob;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if a NetworkObject meets basic criteria for being predicted spawned.
|
||||
/// </summary>
|
||||
/// <param name = "reader">If not null reader will be cleared on error.</param>
|
||||
/// <returns></returns>
|
||||
protected bool CanPredictedSpawn(NetworkObject nob, NetworkConnection spawner, bool asServer, Reader reader = null)
|
||||
{
|
||||
// Does not allow predicted spawning.
|
||||
if (!nob.AllowPredictedSpawning)
|
||||
{
|
||||
if (asServer)
|
||||
spawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {spawner.ClientId} tried to spawn an object {nob.name} which does not support predicted spawning.");
|
||||
else
|
||||
NetworkManager.LogError($"Object {nob.name} does not support predicted spawning. Add a PredictedSpawn component to the object and configure appropriately.");
|
||||
|
||||
if (reader != null)
|
||||
reader.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// // Parenting is not yet supported.
|
||||
// if (nob.CurrentParentNetworkBehaviour != null)
|
||||
// {
|
||||
// if (asServer)
|
||||
// spawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {spawner.ClientId} tried to spawn an object that is not root.");
|
||||
// else
|
||||
// NetworkManager.LogError($"Predicted spawning as a child is not supported.");
|
||||
//
|
||||
// if (reader != null)
|
||||
// reader.Clear();
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// Nested nobs not yet supported.
|
||||
if (nob.InitializedNestedNetworkObjects.Count > 0)
|
||||
{
|
||||
if (asServer)
|
||||
spawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {spawner.ClientId} tried to spawn an object {nob.name} which has nested NetworkObjects.");
|
||||
else
|
||||
NetworkManager.LogError($"Predicted spawning prefabs which contain nested NetworkObjects is not yet supported but will be in a later release.");
|
||||
|
||||
if (reader != null)
|
||||
reader.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if a NetworkObject meets basic criteria for being predicted despawned.
|
||||
/// </summary>
|
||||
/// <param name = "reader">If not null reader will be cleared on error.</param>
|
||||
/// <returns></returns>
|
||||
protected bool CanPredictedDespawn(NetworkObject nob, NetworkConnection despawner, bool asServer, Reader reader = null)
|
||||
{
|
||||
// Does not allow predicted spawning.
|
||||
if (!nob.AllowPredictedDespawning)
|
||||
{
|
||||
if (asServer)
|
||||
despawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {despawner.ClientId} tried to despawn an object {nob.name} which does not support predicted despawning.");
|
||||
else
|
||||
NetworkManager.LogError($"Object {nob.name} does not support predicted despawning. Modify the PredictedSpawn component settings to allow predicted despawning.");
|
||||
|
||||
reader?.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
////Parenting is not yet supported.
|
||||
// if (nob.transform.parent != null)
|
||||
// {
|
||||
// if (asServer)
|
||||
// despawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {despawner.ClientId} tried to despawn an object that is not root.");
|
||||
// else
|
||||
// NetworkManager.LogError($"Predicted despawning as a child is not supported.");
|
||||
|
||||
// reader?.Clear();
|
||||
// return false;
|
||||
// }
|
||||
// Nested nobs not yet supported.
|
||||
if (nob.InitializedNestedNetworkObjects.Count > 0)
|
||||
{
|
||||
if (asServer)
|
||||
despawner.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"Connection {despawner.ClientId} tried to despawn an object {nob.name} which has nested NetworkObjects.");
|
||||
else
|
||||
NetworkManager.LogError($"Predicted despawning prefabs which contain nested NetworkObjects is not yet supported but will be in a later release.");
|
||||
|
||||
reader?.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Blocked by PredictedSpawn settings or user logic.
|
||||
if ((asServer && !nob.PredictedSpawn.OnTryDespawnServer(despawner)) || (!asServer && !nob.PredictedSpawn.OnTryDespawnClient()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a payload for a NetworkObject.
|
||||
/// </summary>
|
||||
internal void ReadPayload(NetworkConnection sender, NetworkObject nob, PooledReader reader, int? payloadLength = null)
|
||||
{
|
||||
if (!payloadLength.HasValue)
|
||||
payloadLength = (int)ReservedLengthWriter.ReadLength(reader, NetworkBehaviour.PAYLOAD_RESERVE_BYTES);
|
||||
// If there is a payload.
|
||||
if (payloadLength > 0)
|
||||
{
|
||||
if (nob != null)
|
||||
{
|
||||
foreach (NetworkBehaviour networkBehaviour in nob.NetworkBehaviours)
|
||||
networkBehaviour.ReadPayload(sender, reader);
|
||||
}
|
||||
// NetworkObject could be null if payload is for a predicted spawn.
|
||||
else
|
||||
{
|
||||
reader.Skip((int)payloadLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the payload returning it as an arraySegment.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal ArraySegment<byte> ReadPayload(PooledReader reader)
|
||||
{
|
||||
int payloadLength = (int)ReservedLengthWriter.ReadLength(reader, NetworkBehaviour.PAYLOAD_RESERVE_BYTES);
|
||||
return reader.ReadArraySegment(payloadLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// /Writers a payload for a NetworkObject.
|
||||
/// </summary>
|
||||
protected void WritePayload(NetworkConnection sender, NetworkObject nob, PooledWriter writer)
|
||||
{
|
||||
ReservedLengthWriter rw = ReservedWritersExtensions.Retrieve();
|
||||
|
||||
rw.Initialize(writer, NetworkBehaviour.PAYLOAD_RESERVE_BYTES);
|
||||
|
||||
foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
|
||||
nb.WritePayload(sender, writer);
|
||||
|
||||
rw.WriteLength();
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// Writes a payload for a NetworkObject.
|
||||
// /// </summary>
|
||||
// protected ArraySegment<byte> ReadPayload(PooledReader reader)
|
||||
// {
|
||||
// PooledWriter nbWriter = WriterPool.Retrieve();
|
||||
// foreach (NetworkBehaviour nb in nob.NetworkBehaviours)
|
||||
// {
|
||||
// nbWriter.Reset();
|
||||
// nb.WritePayload(conn, nbWriter);
|
||||
// if (nbWriter.Length > 0)
|
||||
// {
|
||||
// writer.WriteUInt8Unpacked(nb.ComponentIndex);
|
||||
// writer.WriteArraySegment(nbWriter.GetArraySegment());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fcb759226359ad48926ff17cbf0ec6d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/ManagedObjects.Spawning.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,573 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Component.Observing;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Utility;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using FishNet.Managing.Statistic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
public abstract partial class ManagedObjects
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// NetworkObjects which are currently active.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<int, NetworkObject> Spawned => _spawned;
|
||||
private Dictionary<int, NetworkObject> _spawned = new();
|
||||
/// <summary>
|
||||
/// Invoked when an object is added to Spawned.
|
||||
/// </summary>
|
||||
public event OnSpawnedChanged OnSpawnedAdd;
|
||||
/// <summary>
|
||||
/// Invoked when an object is removed from Spawned.
|
||||
/// </summary>
|
||||
public event OnSpawnedChanged OnSpawnedRemove;
|
||||
/// <summary>
|
||||
/// Invoked when Spawned is cleared.
|
||||
/// </summary>
|
||||
public event Action OnSpawnedClear;
|
||||
/// <summary>
|
||||
/// Delegate for when there is change to Spawned.
|
||||
/// </summary>
|
||||
public delegate void OnSpawnedChanged(int objectId, NetworkObject networkObject);
|
||||
#endregion
|
||||
|
||||
#region Private Profiler Markers
|
||||
private static readonly ProfilerMarker _pm_ParseReplicateRpc =
|
||||
new("ManagedObjects.ParseReplicateRpc(PooledReader, NetworkConnection, Channel)");
|
||||
#endregion
|
||||
|
||||
#region Protected.
|
||||
/// <summary>
|
||||
/// Returns the next ObjectId to use.
|
||||
/// </summary>
|
||||
protected internal virtual bool GetNextNetworkObjectId(out int nextNetworkObjectId)
|
||||
{
|
||||
nextNetworkObjectId = NetworkObject.UNSET_OBJECTID_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NetworkManager handling this.
|
||||
/// </summary>
|
||||
protected NetworkManager NetworkManager { get; private set; }
|
||||
/// <summary>
|
||||
/// Objects in currently loaded scenes. These objects can be active or inactive.
|
||||
/// Key is the objectId while value is the object. Key is not the same as NetworkObject.ObjectId.
|
||||
/// </summary>
|
||||
protected Dictionary<ulong, NetworkObject> SceneObjects_Internal = new();
|
||||
/// <summary>
|
||||
/// Objects in currently loaded scenes. These objects can be active or inactive.
|
||||
/// Key is the objectId while value is the object. Key is not the same as NetworkObject.ObjectId.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<ulong, NetworkObject> SceneObjects => SceneObjects_Internal;
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[NonSerialized] protected NetworkTrafficStatistics NetworkTrafficStatistics;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called to add an object to Spawned.
|
||||
/// </summary>
|
||||
protected void AddtoSpawnedCollectionAndInvoke(NetworkObject nob)
|
||||
{
|
||||
_spawned[nob.ObjectId] = nob;
|
||||
OnSpawnedAdd?.Invoke(nob.ObjectId, nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to remove an object from Spawned.
|
||||
/// </summary>
|
||||
protected void RemoveFromSpawnedCollectionAndInvoke(NetworkObject nob)
|
||||
{
|
||||
if (_spawned.Remove(nob.ObjectId))
|
||||
OnSpawnedRemove?.Invoke(nob.ObjectId, nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to clear Spawned.
|
||||
/// </summary>
|
||||
protected void ClearSpawnedCollectionAndInvoke()
|
||||
{
|
||||
_spawned.Clear();
|
||||
OnSpawnedClear?.Invoke();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Cached HashGrid. Will be null if not used.
|
||||
/// </summary>
|
||||
private HashGrid _hashGrid;
|
||||
#endregion
|
||||
|
||||
protected virtual void Initialize(NetworkManager manager)
|
||||
{
|
||||
NetworkManager = manager;
|
||||
manager.StatisticsManager.TryGetNetworkTrafficStatistics(out NetworkTrafficStatistics);
|
||||
|
||||
manager.TryGetInstance(out _hashGrid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to SceneManager.SceneLoaded event.
|
||||
/// </summary>
|
||||
/// <param name = "subscribe"></param>
|
||||
internal void SubscribeToSceneLoaded(bool subscribe)
|
||||
{
|
||||
if (subscribe)
|
||||
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
|
||||
else
|
||||
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a scene is loaded.
|
||||
/// </summary>
|
||||
/// <param name = "s"></param>
|
||||
/// <param name = "arg1"></param>
|
||||
protected internal virtual void SceneManager_sceneLoaded(Scene s, LoadSceneMode arg1) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when a NetworkObject runs Deactivate.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
internal virtual void NetworkObjectDestroyed(NetworkObject nob, bool asServer)
|
||||
{
|
||||
if (nob == null)
|
||||
return;
|
||||
|
||||
RemoveFromSpawned(nob, fromOnDestroy: true, asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a NetworkedObject from spawned.
|
||||
/// </summary>
|
||||
protected virtual void RemoveFromSpawned(NetworkObject nob, bool fromOnDestroy, bool asServer)
|
||||
{
|
||||
RemoveFromSpawnedCollectionAndInvoke(nob);
|
||||
|
||||
// Do the same with SceneObjects.
|
||||
if (fromOnDestroy && nob.IsSceneObject)
|
||||
RemoveFromSceneObjects(nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns a NetworkObject.
|
||||
/// </summary>
|
||||
internal virtual void Despawn(NetworkObject nob, DespawnType despawnType, bool asServer)
|
||||
{
|
||||
if (nob == null)
|
||||
{
|
||||
NetworkManager.LogWarning($"Cannot despawn a null NetworkObject.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* If not asServer and the object is not initialized on client
|
||||
* then it likely is already despawned. This bit of code should
|
||||
* never be reached as checks should be placed before-hand. */
|
||||
if (!asServer && !nob.IsClientInitialized)
|
||||
{
|
||||
NetworkManager.LogError($"Object {nob.ToString()} is already despawned. Please report this error.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// True if should be destroyed, false if deactivated.
|
||||
bool destroy = false;
|
||||
bool wasRemovedFromPending = false;
|
||||
|
||||
/* Only modify object state if asServer,
|
||||
* or !asServer and not host. This is so clients, when acting as
|
||||
* host, don't destroy objects they lost observation of. */
|
||||
|
||||
/* Nested prefabs can never be destroyed. Only check to
|
||||
* destroy if not nested. By nested prefab, this means the object
|
||||
* despawning is part of another prefab that is also a spawned
|
||||
* network object. */
|
||||
if (!nob.IsNested)
|
||||
{
|
||||
// If as server.
|
||||
if (asServer)
|
||||
{
|
||||
// Scene object.
|
||||
if (!nob.IsSceneObject)
|
||||
{
|
||||
/* If client-host has visibility
|
||||
* then disable and wait for client-host to get destroy
|
||||
* message. Otherwise destroy immediately. */
|
||||
if (nob.Observers.Contains(NetworkManager.ClientManager.Connection))
|
||||
NetworkManager.ServerManager.Objects.AddToPending(nob);
|
||||
else
|
||||
destroy = true;
|
||||
}
|
||||
}
|
||||
// Not as server.
|
||||
else
|
||||
{
|
||||
bool isServer = NetworkManager.IsServerStarted;
|
||||
// Only check to destroy if not a scene object.
|
||||
if (!nob.IsSceneObject)
|
||||
{
|
||||
/* If was removed from pending then also destroy.
|
||||
* Pending objects are ones that exist on the server
|
||||
* side only to await destruction from client side.
|
||||
* Objects can also be destroyed if server is not
|
||||
* active. */
|
||||
wasRemovedFromPending = NetworkManager.ServerManager.Objects.RemoveFromPending(nob);
|
||||
destroy = !isServer || wasRemovedFromPending;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TryUnsetParent();
|
||||
|
||||
/* If this had a parent set at runtime then
|
||||
* unset parent before checks are completed.
|
||||
* If we did not do this then this nob would
|
||||
* just be disabled beneath its runtime parent,
|
||||
* when it should be pooled separately or destroyed. */
|
||||
void TryUnsetParent()
|
||||
{
|
||||
if (!asServer || wasRemovedFromPending)
|
||||
{
|
||||
if (nob.RuntimeParentNetworkBehaviour != null)
|
||||
{
|
||||
nob.UnsetParent();
|
||||
/* DespawnType also has to be updated to use default
|
||||
* for the networkObject since this despawn is happening
|
||||
* automatically. */
|
||||
despawnType = nob.GetDefaultDespawnType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nob.SetIsDestroying(despawnType);
|
||||
// Deinitialize to invoke callbacks.
|
||||
nob.Deinitialize(asServer);
|
||||
// Remove from match condition only if server.
|
||||
if (asServer)
|
||||
MatchCondition.RemoveFromMatchWithoutRebuild(nob, NetworkManager);
|
||||
RemoveFromSpawned(nob, false, asServer);
|
||||
|
||||
// If to destroy.
|
||||
if (destroy)
|
||||
{
|
||||
if (despawnType == DespawnType.Destroy)
|
||||
UnityEngine.Object.Destroy(nob.gameObject);
|
||||
else
|
||||
NetworkManager.StorePooledInstantiated(nob, asServer);
|
||||
}
|
||||
/* If to potentially disable instead of destroy.
|
||||
* This is such as something is despawning server side
|
||||
* but a clientHost is present, or if a scene object. */
|
||||
else
|
||||
{
|
||||
// If as server.
|
||||
if (asServer)
|
||||
{
|
||||
/* If not clientHost the object can be disabled.
|
||||
*
|
||||
* Also, if clientHost and clientHost is not an observer, the object
|
||||
* can be disabled. */
|
||||
// If not clientHost then the object can be disabled.
|
||||
if (!NetworkManager.IsClientStarted || !nob.Observers.Contains(NetworkManager.ClientManager.Connection))
|
||||
nob.gameObject.SetActive(false);
|
||||
}
|
||||
// Not as server.
|
||||
else
|
||||
{
|
||||
// If the server is not active then the object can be disabled.
|
||||
if (!NetworkManager.IsServerStarted)
|
||||
{
|
||||
nob.gameObject.SetActive(false);
|
||||
}
|
||||
// If also server then checks must be done.
|
||||
else
|
||||
{
|
||||
/* Object is still spawned on the server side. This means
|
||||
* the clientHost likely lost visibility. When this is the case
|
||||
* update clientHost renderers. */
|
||||
if (NetworkManager.ServerManager.Objects.Spawned.ContainsKey(nob.ObjectId))
|
||||
nob.SetRenderersVisible(false);
|
||||
/* No longer spawned on the server, can
|
||||
* deactivate on the client. */
|
||||
else
|
||||
nob.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
/* Also despawn child objects.
|
||||
* This only must be done when not destroying
|
||||
* as destroying would result in the despawn being
|
||||
* forced.
|
||||
*
|
||||
* Only run if asServer as well. The server will send
|
||||
* individual despawns for each child. */
|
||||
if (asServer)
|
||||
{
|
||||
List<NetworkObject> childNobs = nob.GetNetworkObjects(GetNetworkObjectOption.AllNested);
|
||||
foreach (NetworkObject childNob in childNobs)
|
||||
{
|
||||
if (childNob != null && !childNob.IsDeinitializing)
|
||||
Despawn(childNob, despawnType, asServer: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a prefab, not to be mistaken for initializing a spawned object.
|
||||
/// </summary>
|
||||
/// <param name = "prefab">Prefab to initialize.</param>
|
||||
/// <param name = "index">Index within spawnable prefabs.</param>
|
||||
public static void InitializePrefab(NetworkObject prefab, int index, ushort? collectionId = null)
|
||||
{
|
||||
const int invalidIndex = -1;
|
||||
if (index == invalidIndex)
|
||||
{
|
||||
Debug.LogError($"An index of {invalidIndex} cannot be assigned as a PrefabId for {prefab.name}.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefab == null)
|
||||
return;
|
||||
|
||||
prefab.PrefabId = (ushort)index;
|
||||
if (collectionId != null)
|
||||
prefab.SpawnableCollectionId = collectionId.Value;
|
||||
|
||||
prefab.SetInitializedValues(null, ignoreSerializedTimestamp: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns Spawned NetworkObjects. Scene objects will be disabled, others will be destroyed.
|
||||
/// </summary>
|
||||
internal virtual void DespawnWithoutSynchronization(bool recursive, bool asServer)
|
||||
{
|
||||
foreach (NetworkObject nob in Spawned.Values)
|
||||
{
|
||||
if (nob == null)
|
||||
continue;
|
||||
|
||||
DespawnWithoutSynchronization(nob, recursive, asServer, nob.GetDefaultDespawnType(), removeFromSpawned: false);
|
||||
}
|
||||
|
||||
ClearSpawnedCollectionAndInvoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns a network object.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
protected virtual void DespawnWithoutSynchronization(NetworkObject nob, bool recursive, bool asServer, DespawnType despawnType, bool removeFromSpawned)
|
||||
{
|
||||
#if FISHNET_STABLE_RECURSIVE_DESPAWNS
|
||||
recursive = false;
|
||||
#endif
|
||||
|
||||
GetNetworkObjectOption getOption = recursive ? GetNetworkObjectOption.All : GetNetworkObjectOption.Self;
|
||||
List<NetworkObject> allNobs = nob.GetNetworkObjects(getOption);
|
||||
|
||||
// True if can deactivate or destroy.
|
||||
bool canCleanup = asServer || !NetworkManager.IsServerStarted;
|
||||
|
||||
foreach (NetworkObject lNob in allNobs)
|
||||
{
|
||||
lNob.SetIsDestroying(despawnType);
|
||||
lNob.Deinitialize(asServer);
|
||||
|
||||
if (canCleanup && removeFromSpawned)
|
||||
RemoveFromSpawned(lNob, fromOnDestroy: false, asServer);
|
||||
}
|
||||
|
||||
/* Only need to check the first nob. If it's stored, deactivated,
|
||||
* or destroyed, the rest will follow. */
|
||||
if (canCleanup)
|
||||
{
|
||||
NetworkObject firstNob = allNobs[0];
|
||||
|
||||
if (firstNob.IsSceneObject || firstNob.IsInitializedNested)
|
||||
{
|
||||
firstNob.gameObject.SetActive(value: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (despawnType == DespawnType.Destroy)
|
||||
UnityEngine.Object.Destroy(firstNob.gameObject);
|
||||
else
|
||||
NetworkManager.StorePooledInstantiated(firstNob, asServer);
|
||||
}
|
||||
}
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(allNobs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a NetworkObject to Spawned.
|
||||
/// </summary>
|
||||
internal virtual void AddToSpawned(NetworkObject nob, bool asServer)
|
||||
{
|
||||
AddtoSpawnedCollectionAndInvoke(nob);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a NetworkObject to SceneObjects.
|
||||
/// </summary>
|
||||
protected internal void AddToSceneObjects(NetworkObject nob)
|
||||
{
|
||||
SceneObjects_Internal[nob.SceneId] = nob;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a NetworkObject from SceneObjects.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
protected internal void RemoveFromSceneObjects(NetworkObject nob)
|
||||
{
|
||||
SceneObjects_Internal.Remove(nob.SceneId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a NetworkObject from SceneObjects.
|
||||
/// </summary>
|
||||
/// <param name = "nob"></param>
|
||||
protected internal void RemoveFromSceneObjects(ulong sceneId)
|
||||
{
|
||||
SceneObjects_Internal.Remove(sceneId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a NetworkObject within Spawned.
|
||||
/// </summary>
|
||||
/// <param name = "objectId"></param>
|
||||
/// <returns></returns>
|
||||
protected internal NetworkObject GetSpawnedNetworkObject(int objectId)
|
||||
{
|
||||
NetworkObject r;
|
||||
if (!_spawned.TryGetValueIL2CPP(objectId, out r))
|
||||
NetworkManager.LogError($"Spawned NetworkObject not found for ObjectId {objectId}.");
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to skip data length for a packet.
|
||||
/// </summary>
|
||||
/// <param name = "packetId"></param>
|
||||
/// <param name = "reader"></param>
|
||||
/// <param name = "dataLength"></param>
|
||||
protected internal void SkipDataLength(ushort packetId, PooledReader reader, int dataLength, int rpcLinkObjectId = -1)
|
||||
{
|
||||
/* -1 means length wasn't set, which would suggest a reliable packet.
|
||||
* Object should never be missing for reliable packets since spawns
|
||||
* and despawns are reliable in order. */
|
||||
if (dataLength == (int)MissingObjectPacketLength.Reliable)
|
||||
{
|
||||
string msg;
|
||||
bool isRpcLink = packetId >= NetworkManager.StartingRpcLinkIndex;
|
||||
if (isRpcLink)
|
||||
{
|
||||
msg = rpcLinkObjectId == -1 ? $"RPCLink of Id {(PacketId)packetId} could not be found. Remaining data will be purged." : $"ObjectId {rpcLinkObjectId} for RPCLink {(PacketId)packetId} could not be found.";
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = $"NetworkBehaviour could not be found for packetId {(PacketId)packetId}. Remaining data will be purged.";
|
||||
}
|
||||
|
||||
/* Default logging for server is errors only. Use error on client and warning
|
||||
* on servers to reduce chances of allocation attacks. */
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR || !UNITY_SERVER
|
||||
NetworkManager.LogError(msg);
|
||||
#else
|
||||
NetworkManager.LogWarning(msg);
|
||||
#endif
|
||||
reader.Clear();
|
||||
}
|
||||
/* If length is known then is unreliable packet. It's possible
|
||||
* this packetId arrived before or after the object was spawned/destroyed.
|
||||
* Skip past the data for this packet and use rest in reader. With non-linked
|
||||
* RPCs length is sent before object information. */
|
||||
else if (dataLength >= 0)
|
||||
{
|
||||
reader.Skip(Math.Min(dataLength, reader.Remaining));
|
||||
}
|
||||
/* -2 indicates the length is very long. Don't even try saving
|
||||
* the packet, user shouldn't be sending this much data over unreliable. */
|
||||
else if (dataLength == (int)MissingObjectPacketLength.PurgeRemaiming)
|
||||
{
|
||||
reader.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a ReplicateRpc.
|
||||
/// </summary>
|
||||
internal void ParseReplicateRpc(PooledReader reader, NetworkConnection conn, Channel channel)
|
||||
{
|
||||
using (_pm_ParseReplicateRpc.Auto())
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
NetworkBehaviour.ReadDebugForValidatedRpc(NetworkManager, reader, out int startReaderRemaining,
|
||||
out string rpcInformation, out uint expectedReadAmount);
|
||||
#endif
|
||||
int readerStartAfterDebug = reader.Position;
|
||||
|
||||
NetworkBehaviour nb = reader.ReadNetworkBehaviour();
|
||||
int dataLength = Packets.GetPacketLength((ushort)PacketId.ServerRpc, reader, channel);
|
||||
if (nb != null && nb.IsSpawned)
|
||||
nb.OnReplicateRpc(readerStartAfterDebug, hash: null, reader, conn, channel);
|
||||
else
|
||||
SkipDataLength((ushort)PacketId.ServerRpc, reader, dataLength);
|
||||
|
||||
#if DEVELOPMENT
|
||||
NetworkBehaviour.TryPrintDebugForValidatedRpc(fromRpcLink: false, NetworkManager, reader,
|
||||
startReaderRemaining, rpcInformation, expectedReadAmount, channel);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if DEVELOPMENT
|
||||
/// <summary>
|
||||
/// Checks to write a scene object's details into a writer.
|
||||
/// </summary>
|
||||
protected void CheckWriteSceneObjectDetails(NetworkObject nob, Writer w)
|
||||
{
|
||||
// Check to write additional information if a scene object.
|
||||
if (NetworkManager.DebugManager.WriteSceneObjectDetails)
|
||||
{
|
||||
w.WriteString(nob.gameObject.scene.name);
|
||||
w.WriteString(nob.gameObject.name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to read a scene object's details and populates values if read was successful.
|
||||
/// </summary>
|
||||
protected void CheckReadSceneObjectDetails(Reader r, ref string sceneName, ref string objectName)
|
||||
{
|
||||
if (NetworkManager.DebugManager.WriteSceneObjectDetails)
|
||||
{
|
||||
sceneName = r.ReadStringAllocated();
|
||||
objectName = r.ReadStringAllocated();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1363007244792145846afddc31ac12c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/ManagedObjects.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,42 @@
|
||||
using FishNet.Documenting;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
[System.Flags]
|
||||
internal enum SpawnType : byte
|
||||
{
|
||||
Unset = 0,
|
||||
/// <summary>
|
||||
/// Is nested beneath a NetworkBehaviour.
|
||||
/// </summary>
|
||||
Nested = 1,
|
||||
/// <summary>
|
||||
/// Is a scene object.
|
||||
/// </summary>
|
||||
Scene = 2,
|
||||
/// <summary>
|
||||
/// Instantiate into active scene.
|
||||
/// </summary>
|
||||
Instantiated = 4,
|
||||
/// <summary>
|
||||
/// Instantiate into the global scene.
|
||||
/// </summary>
|
||||
InstantiatedGlobal = 8,
|
||||
/// <summary>
|
||||
/// Indicates the receiver is the predicted spawner.
|
||||
/// </summary>
|
||||
IsPredictedSpawner = 16
|
||||
}
|
||||
|
||||
[APIExclude]
|
||||
internal static class SpawnTypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns if whole contains part.
|
||||
/// </summary>
|
||||
public static bool FastContains(this SpawnType whole, SpawnType part)
|
||||
{
|
||||
return (whole & part) == part;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed15edf5a1a100d45b05f6adace574cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/ObjectSpawnType.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f3c9013358962e4786b12d363c7a3fc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,133 @@
|
||||
using FishNet.Documenting;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Text;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using FishNet.Object;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
[APIExclude]
|
||||
// [CreateAssetMenu(fileName = "New DefaultPrefabObjects", menuName = "FishNet/Spawnable Prefabs/Default Prefab Objects")]
|
||||
public class DefaultPrefabObjects : SinglePrefabObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for version rebuilding.
|
||||
/// </summary>
|
||||
private StringBuilder _stringBuilder = new();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets asset path hashes for prefabs starting at index, or if missing.
|
||||
/// </summary
|
||||
/// <return>Returns true if one or more NetworkObjects were updated.</return>
|
||||
internal bool SetAssetPathHashes(int index)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
bool dirtied = false;
|
||||
int count = base.GetObjectCount();
|
||||
|
||||
if (count == 0)
|
||||
return false;
|
||||
|
||||
if (index < 0 || index >= count)
|
||||
{
|
||||
Debug.LogError($"Index {index} is out of range when trying to set asset path hashes. Collection length is {count}. Defaulf prefabs may need to be rebuilt.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
NetworkObject n = Prefabs[i];
|
||||
if (i < index)
|
||||
continue;
|
||||
|
||||
string pathAndName = $"{AssetDatabase.GetAssetPath(n.gameObject)}{n.gameObject.name}".Trim().ToLowerInvariant();
|
||||
|
||||
_stringBuilder.Clear();
|
||||
foreach (char c in pathAndName)
|
||||
{
|
||||
if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
|
||||
_stringBuilder.Append(c);
|
||||
}
|
||||
|
||||
ulong hashcode = _stringBuilder.ToString().GetStableHashU64();
|
||||
// Already set.
|
||||
if (n.AssetPathHash == hashcode)
|
||||
continue;
|
||||
|
||||
n.SetAssetPathHash(hashcode);
|
||||
EditorUtility.SetDirty(n);
|
||||
dirtied = true;
|
||||
}
|
||||
|
||||
//Check for conflicts.
|
||||
Dictionary<ulong, string> hashesAndPaths = new();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
NetworkObject n = Prefabs[i];
|
||||
|
||||
string pathAndName = $"{AssetDatabase.GetAssetPath(n.gameObject)}{n.gameObject.name}";
|
||||
|
||||
if (hashesAndPaths.TryGetValueIL2CPP(n.AssetPathHash, out string path))
|
||||
{
|
||||
Debug.LogError($"Assets {pathAndName} and {path} have the same assetPath hash of {n.AssetPathHash}. Please modify the prefab name of either to resolve.");
|
||||
dirtied = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
hashesAndPaths.Add(n.AssetPathHash, pathAndName);
|
||||
}
|
||||
}
|
||||
|
||||
return dirtied;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sorts prefabs by name and path hashcode.
|
||||
/// </summary>
|
||||
internal void Sort()
|
||||
{
|
||||
if (base.GetObjectCount() == 0)
|
||||
return;
|
||||
|
||||
Dictionary<ulong, NetworkObject> hashcodesAndNobs = new();
|
||||
List<ulong> hashcodes = new();
|
||||
|
||||
bool error = false;
|
||||
foreach (NetworkObject n in Prefabs)
|
||||
{
|
||||
hashcodes.Add(n.AssetPathHash);
|
||||
// If hashcode is 0 something is wrong
|
||||
if (n.AssetPathHash == 0)
|
||||
{
|
||||
error = true;
|
||||
Debug.LogError($"AssetPathHash is not set for GameObject {n.name}.");
|
||||
}
|
||||
hashcodesAndNobs.Add(n.AssetPathHash, n);
|
||||
}
|
||||
// An error occured, no reason to continue.
|
||||
if (error)
|
||||
{
|
||||
Debug.LogError($"One or more NetworkObject prefabs did not have their AssetPathHash set. This usually occurs when a prefab cannot be saved. Check the specified prefabs for missing scripts or serialization errors and correct them, then use Fish-Networking -> Refresh Default Prefabs.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Once all hashes have been made re-add them to prefabs sorted.
|
||||
hashcodes.Sort();
|
||||
// Build to a new list using sorted hashcodes.
|
||||
List<NetworkObject> sortedNobs = new();
|
||||
foreach (ulong hc in hashcodes)
|
||||
sortedNobs.Add(hashcodesAndNobs[hc]);
|
||||
|
||||
base.Clear();
|
||||
base.AddObjects(sortedNobs, checkForDuplicates: false, initializeAdded: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ad70174b079c2f4ebc7931d3dd1af6f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/PrefabObjects/DefaultPrefabObjects.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,130 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
// document
|
||||
[APIExclude]
|
||||
[CreateAssetMenu(fileName = "New DualPrefabObjects", menuName = "FishNet/Spawnable Prefabs/Dual Prefab Objects")]
|
||||
public class DualPrefabObjects : PrefabObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[Tooltip("Prefabs which may be spawned.")]
|
||||
[SerializeField]
|
||||
private List<DualPrefab> _prefabs = new();
|
||||
/// <summary>
|
||||
/// Prefabs which may be spawned.
|
||||
/// </summary>
|
||||
public IReadOnlyList<DualPrefab> Prefabs => _prefabs;
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
_prefabs.Clear();
|
||||
}
|
||||
|
||||
public override int GetObjectCount()
|
||||
{
|
||||
return _prefabs.Count;
|
||||
}
|
||||
|
||||
public override NetworkObject GetObject(bool asServer, int id)
|
||||
{
|
||||
if (id < 0 || id >= _prefabs.Count)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"PrefabId {id} is out of range.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
DualPrefab dp = _prefabs[id];
|
||||
NetworkObject nob = asServer ? dp.Server : dp.Client;
|
||||
if (nob == null)
|
||||
{
|
||||
string lookupSide = asServer ? "server" : "client";
|
||||
NetworkManagerExtensions.LogError($"Prefab for {lookupSide} on id {id} is null ");
|
||||
}
|
||||
|
||||
return nob;
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveNull()
|
||||
{
|
||||
for (int i = 0; i < _prefabs.Count; i++)
|
||||
{
|
||||
if (_prefabs[i].Server == null || _prefabs[i].Client == null)
|
||||
{
|
||||
_prefabs.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddObject(DualPrefab dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
AddObjects(new DualPrefab[] { dualPrefab }, checkForDuplicates, initializeAdded);
|
||||
}
|
||||
|
||||
public override void AddObjects(List<DualPrefab> dualPrefabs, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
AddObjects(dualPrefabs.ToArray(), checkForDuplicates, initializeAdded);
|
||||
}
|
||||
|
||||
public override void AddObjects(DualPrefab[] dualPrefabs, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
if (!checkForDuplicates)
|
||||
{
|
||||
_prefabs.AddRange(dualPrefabs);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (DualPrefab dp in dualPrefabs)
|
||||
AddUniqueNetworkObjects(dp);
|
||||
}
|
||||
|
||||
if (initializeAdded && Application.isPlaying)
|
||||
InitializePrefabRange(0);
|
||||
}
|
||||
|
||||
private void AddUniqueNetworkObjects(DualPrefab dp)
|
||||
{
|
||||
for (int i = 0; i < _prefabs.Count; i++)
|
||||
{
|
||||
if (_prefabs[i].Server == dp.Server && _prefabs[i].Client == dp.Client)
|
||||
return;
|
||||
}
|
||||
|
||||
_prefabs.Add(dp);
|
||||
}
|
||||
|
||||
public override void InitializePrefabRange(int startIndex)
|
||||
{
|
||||
for (int i = startIndex; i < _prefabs.Count; i++)
|
||||
{
|
||||
ManagedObjects.InitializePrefab(_prefabs[i].Server, i, CollectionId);
|
||||
ManagedObjects.InitializePrefab(_prefabs[i].Client, i, CollectionId);
|
||||
}
|
||||
}
|
||||
|
||||
#region Unused.
|
||||
public override void AddObject(NetworkObject networkObject, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"Single prefabs are not supported with DualPrefabObjects. Make a SinglePrefabObjects asset instead.");
|
||||
}
|
||||
|
||||
public override void AddObjects(List<NetworkObject> networkObjects, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"Single prefabs are not supported with DualPrefabObjects. Make a SinglePrefabObjects asset instead.");
|
||||
}
|
||||
|
||||
public override void AddObjects(NetworkObject[] networkObjects, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"Single prefabs are not supported with DualPrefabObjects. Make a SinglePrefabObjects asset instead.");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4b890523e001c74a9a2bf0d6340e5f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/PrefabObjects/DualPrefabObjects.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,34 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
// document
|
||||
[APIExclude]
|
||||
public abstract class PrefabObjects : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// CollectionId for this PrefabObjects.
|
||||
/// </summary>
|
||||
public ushort CollectionId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets CollectionIdValue.
|
||||
/// </summary>
|
||||
internal void SetCollectionId(ushort id) => CollectionId = id;
|
||||
|
||||
public abstract void Clear();
|
||||
public abstract int GetObjectCount();
|
||||
public abstract NetworkObject GetObject(bool asServer, int id);
|
||||
public abstract void RemoveNull();
|
||||
public abstract void AddObject(NetworkObject networkObject, bool checkForDuplicates = false, bool initializeAdded = true);
|
||||
public abstract void AddObjects(List<NetworkObject> networkObjects, bool checkForDuplicates = false, bool initializeAdded = true);
|
||||
public abstract void AddObjects(NetworkObject[] networkObjects, bool checkForDuplicates = false, bool initializeAdded = true);
|
||||
public abstract void AddObject(DualPrefab dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true);
|
||||
public abstract void AddObjects(List<DualPrefab> dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true);
|
||||
public abstract void AddObjects(DualPrefab[] dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true);
|
||||
public abstract void InitializePrefabRange(int startIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5a7beb0d6ee75a4fb1f058eb3e2640a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/PrefabObjects/PrefabObjects.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,125 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Object
|
||||
{
|
||||
// document
|
||||
[APIExclude]
|
||||
[CreateAssetMenu(fileName = "New SinglePrefabObjects", menuName = "FishNet/Spawnable Prefabs/Single Prefab Objects")]
|
||||
public class SinglePrefabObjects : PrefabObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[Tooltip("Prefabs which may be spawned.")]
|
||||
[SerializeField]
|
||||
private List<NetworkObject> _prefabs = new();
|
||||
/// <summary>
|
||||
/// Prefabs which may be spawned.
|
||||
/// </summary>
|
||||
public IReadOnlyList<NetworkObject> Prefabs => _prefabs;
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
_prefabs.Clear();
|
||||
}
|
||||
|
||||
public override int GetObjectCount()
|
||||
{
|
||||
return _prefabs.Count;
|
||||
}
|
||||
|
||||
public override NetworkObject GetObject(bool asServer, int id)
|
||||
{
|
||||
if (id < 0 || id >= _prefabs.Count)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"PrefabId {id} is out of range.");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkObject nob = _prefabs[id];
|
||||
if (nob == null)
|
||||
NetworkManagerExtensions.LogError($"Prefab on id {id} is null.");
|
||||
|
||||
return nob;
|
||||
}
|
||||
}
|
||||
|
||||
public override void RemoveNull()
|
||||
{
|
||||
for (int i = 0; i < _prefabs.Count; i++)
|
||||
{
|
||||
if (_prefabs[i] == null)
|
||||
{
|
||||
_prefabs.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddObject(NetworkObject networkObject, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
if (!checkForDuplicates)
|
||||
_prefabs.Add(networkObject);
|
||||
else
|
||||
AddUniqueNetworkObject(networkObject);
|
||||
|
||||
if (initializeAdded && Application.isPlaying)
|
||||
InitializePrefabRange(0);
|
||||
}
|
||||
|
||||
public override void AddObjects(List<NetworkObject> networkObjects, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
if (!checkForDuplicates)
|
||||
{
|
||||
_prefabs.AddRange(networkObjects);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (NetworkObject nob in networkObjects)
|
||||
AddUniqueNetworkObject(nob);
|
||||
}
|
||||
|
||||
if (initializeAdded && Application.isPlaying)
|
||||
InitializePrefabRange(0);
|
||||
}
|
||||
|
||||
public override void AddObjects(NetworkObject[] networkObjects, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
AddObjects(networkObjects.ToList(), checkForDuplicates, initializeAdded);
|
||||
}
|
||||
|
||||
private void AddUniqueNetworkObject(NetworkObject nob)
|
||||
{
|
||||
if (!_prefabs.Contains(nob))
|
||||
_prefabs.Add(nob);
|
||||
}
|
||||
|
||||
public override void InitializePrefabRange(int startIndex)
|
||||
{
|
||||
for (int i = startIndex; i < _prefabs.Count; i++)
|
||||
ManagedObjects.InitializePrefab(_prefabs[i], i, CollectionId);
|
||||
}
|
||||
|
||||
#region Unused.
|
||||
public override void AddObject(DualPrefab dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"Dual prefabs are not supported with SinglePrefabObjects. Make a DualPrefabObjects asset instead.");
|
||||
}
|
||||
|
||||
public override void AddObjects(List<DualPrefab> dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"Dual prefabs are not supported with SinglePrefabObjects. Make a DualPrefabObjects asset instead.");
|
||||
}
|
||||
|
||||
public override void AddObjects(DualPrefab[] dualPrefab, bool checkForDuplicates = false, bool initializeAdded = true)
|
||||
{
|
||||
NetworkManagerExtensions.LogError($"Dual prefabs are not supported with SinglePrefabObjects. Make a DualPrefabObjects asset instead.");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4489d77032a81ef42b0067acf2737d4d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/PrefabObjects/SinglePrefabObjects.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,11 @@
|
||||
// namespace FishNet.Managing.Object // Remove in V5
|
||||
// {
|
||||
// public enum SpawnParentType : byte
|
||||
// {
|
||||
// Unset = 0,
|
||||
// NetworkObject = 1,
|
||||
// NetworkBehaviour = 2
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbace351ced9ff94eb294dbb2e1d6a75
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Object/SpawnParentType.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66e179864f87850459121eef3b80e0d9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3549ac1479ae014986f98b9f63e3011
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Observing.Editing
|
||||
{
|
||||
[CustomEditor(typeof(ObserverManager), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class ObserverManagerEditor : Editor
|
||||
{
|
||||
private SerializedProperty _updateHostVisibility;
|
||||
private SerializedProperty _maximumTimedObserversDuration;
|
||||
private SerializedProperty _defaultConditions;
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_updateHostVisibility = serializedObject.FindProperty(nameof(_updateHostVisibility));
|
||||
_maximumTimedObserversDuration = serializedObject.FindProperty(nameof(_maximumTimedObserversDuration));
|
||||
_defaultConditions = serializedObject.FindProperty(nameof(_defaultConditions));
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((ObserverManager)target), typeof(ObserverManager), false);
|
||||
GUI.enabled = true;
|
||||
|
||||
EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.PropertyField(_updateHostVisibility);
|
||||
if (_maximumTimedObserversDuration.floatValue < 1d)
|
||||
EditorGUILayout.HelpBox("Using low values may reduce server performance while under load.", MessageType.Warning);
|
||||
EditorGUILayout.PropertyField(_maximumTimedObserversDuration);
|
||||
EditorGUILayout.PropertyField(_defaultConditions);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86e21ffc228c07d4891b6e74080b8c90
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Observing/Editor/ObserverManagerEditor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,217 @@
|
||||
using FishNet.Component.Observing;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Object;
|
||||
using FishNet.Observing;
|
||||
using FishNet.Utility;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.DEMOS_ASSEMBLY_NAME)]
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.TEST_ASSEMBLY_NAME)]
|
||||
|
||||
namespace FishNet.Managing.Observing
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional options for managing the observer system.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("FishNet/Manager/ObserverManager")]
|
||||
public sealed class ObserverManager : MonoBehaviour
|
||||
{
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// True to update visibility for clientHost based on if they are an observer or not.
|
||||
/// </summary>
|
||||
public bool UpdateHostVisibility
|
||||
{
|
||||
get => _updateHostVisibility;
|
||||
private set => _updateHostVisibility = value;
|
||||
}
|
||||
[Tooltip("True to update visibility for clientHost based on if they are an observer or not.")]
|
||||
[SerializeField]
|
||||
private bool _updateHostVisibility = true;
|
||||
/// <summary>
|
||||
/// Maximum duration the server will take to update timed observer conditions as server load increases. Lower values will result in timed conditions being checked quicker at the cost of performance..
|
||||
/// </summary>
|
||||
public float MaximumTimedObserversDuration
|
||||
{
|
||||
get => _maximumTimedObserversDuration;
|
||||
private set => _maximumTimedObserversDuration = value;
|
||||
}
|
||||
[Tooltip("Maximum duration the server will take to update timed observer conditions as server load increases. Lower values will result in timed conditions being checked quicker at the cost of performance.")]
|
||||
[SerializeField]
|
||||
[Range(MINIMUM_TIMED_OBSERVERS_DURATION, MAXIMUM_TIMED_OBSERVERS_DURATION)]
|
||||
private float _maximumTimedObserversDuration = 10f;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the MaximumTimedObserversDuration value.
|
||||
/// </summary>
|
||||
/// <param name = "value">New maximum duration to update timed observers over.</param>
|
||||
public void SetMaximumTimedObserversDuration(float value) => MaximumTimedObserversDuration = System.Math.Clamp(value, MINIMUM_TIMED_OBSERVERS_DURATION, MAXIMUM_TIMED_OBSERVERS_DURATION);
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
[Tooltip("Default observer conditions for networked objects.")]
|
||||
[SerializeField]
|
||||
private List<ObserverCondition> _defaultConditions = new();
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// NetworkManager on object.
|
||||
/// </summary>
|
||||
private NetworkManager _networkManager;
|
||||
#endregion
|
||||
|
||||
#region Consts.
|
||||
/// <summary>
|
||||
/// Minimum time allowed for timed observers to rebuild.
|
||||
/// </summary>
|
||||
private const float MINIMUM_TIMED_OBSERVERS_DURATION = 0.1f;
|
||||
/// <summary>
|
||||
/// Maxmimum time allowed for timed observers to rebuild.
|
||||
/// </summary>
|
||||
private const float MAXIMUM_TIMED_OBSERVERS_DURATION = 20f;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this script for use.
|
||||
/// </summary>
|
||||
/// <param name = "manager"></param>
|
||||
internal void InitializeOnce_Internal(NetworkManager manager)
|
||||
{
|
||||
_networkManager = manager;
|
||||
// Update the current value to itself so it becomes clamped. This is just to protect against the user manually setting it outside clamp somehow.
|
||||
SetMaximumTimedObserversDuration(MaximumTimedObserversDuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a new value for UpdateHostVisibility.
|
||||
/// </summary>
|
||||
/// <param name = "value">New value.</param>
|
||||
/// <param name = "updateType">Which objects to update.</param>
|
||||
public void SetUpdateHostVisibility(bool value, HostVisibilityUpdateTypes updateType)
|
||||
{
|
||||
// Unchanged.
|
||||
if (value == UpdateHostVisibility)
|
||||
return;
|
||||
|
||||
/* Update even if server state is not known.
|
||||
* The setting should be updated so when the server
|
||||
* does start spawned objects have latest setting. */
|
||||
if (HostVisibilityUpdateContains(updateType, HostVisibilityUpdateTypes.Manager))
|
||||
UpdateHostVisibility = value;
|
||||
|
||||
/* If to update spawned as well then update all networkobservers
|
||||
* with the setting and also update renderers. */
|
||||
if (_networkManager.IsServerStarted && HostVisibilityUpdateContains(updateType, HostVisibilityUpdateTypes.Spawned))
|
||||
{
|
||||
NetworkConnection clientConn = _networkManager.ClientManager.Connection;
|
||||
foreach (NetworkObject n in _networkManager.ServerManager.Objects.Spawned.Values)
|
||||
{
|
||||
n.NetworkObserver.SetUpdateHostVisibility(value);
|
||||
|
||||
// Only check to update renderers if clientHost. If not client then clientConn won't be active.
|
||||
if (clientConn.IsActive)
|
||||
n.SetRenderersVisible(n.Observers.Contains(clientConn), true);
|
||||
}
|
||||
}
|
||||
|
||||
bool HostVisibilityUpdateContains(HostVisibilityUpdateTypes whole, HostVisibilityUpdateTypes part)
|
||||
{
|
||||
return (whole & part) == part;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds default observer conditions to nob and returns the NetworkObserver used.
|
||||
/// </summary>
|
||||
internal NetworkObserver AddDefaultConditions(NetworkObject nob)
|
||||
{
|
||||
bool isGlobal = nob.IsGlobal && !nob.IsSceneObject;
|
||||
bool obsAdded;
|
||||
|
||||
NetworkObserver result;
|
||||
if (!nob.TryGetComponent(out result))
|
||||
{
|
||||
obsAdded = true;
|
||||
result = nob.gameObject.AddComponent<NetworkObserver>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If already setup by this manager then return.
|
||||
if (result.ConditionsSetByObserverManager)
|
||||
return result;
|
||||
|
||||
obsAdded = false;
|
||||
}
|
||||
|
||||
/* NetworkObserver is null and there are no
|
||||
* conditions to add. Nothing will change by adding
|
||||
* the NetworkObserver component so exit early. */
|
||||
if (!obsAdded && _defaultConditions.Count == 0)
|
||||
return result;
|
||||
|
||||
// If the NetworkObserver component was just added.
|
||||
if (obsAdded)
|
||||
{
|
||||
/* Global nobs do not need a NetworkObserver.
|
||||
* Ultimately, a global NetworkObject is one without
|
||||
* any conditions. */
|
||||
if (isGlobal)
|
||||
return result;
|
||||
// If there are no conditions then there's nothing to add.
|
||||
if (_defaultConditions.Count == 0)
|
||||
return result;
|
||||
/* If here then there not a global networkobject and there are conditions to use.
|
||||
* Since the NetworkObserver is being added fresh, set OverrideType to UseManager
|
||||
* so that the NetworkObserver is populated with the manager conditions. */
|
||||
result.OverrideType = NetworkObserver.ConditionOverrideType.UseManager;
|
||||
}
|
||||
// NetworkObject has a NetworkObserver already on it.
|
||||
else
|
||||
{
|
||||
// If global the NetworkObserver has to be cleared and set to ignore manager.
|
||||
if (isGlobal)
|
||||
{
|
||||
result.ObserverConditionsInternal.Clear();
|
||||
result.OverrideType = NetworkObserver.ConditionOverrideType.IgnoreManager;
|
||||
}
|
||||
}
|
||||
|
||||
// If ignoring manager then use whatever is already configured.
|
||||
if (result.OverrideType == NetworkObserver.ConditionOverrideType.IgnoreManager)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
// If using manager then replace all with conditions.
|
||||
else if (result.OverrideType == NetworkObserver.ConditionOverrideType.UseManager)
|
||||
{
|
||||
result.ObserverConditionsInternal.Clear();
|
||||
AddMissing(result);
|
||||
}
|
||||
// Adding only new.
|
||||
else if (result.OverrideType == NetworkObserver.ConditionOverrideType.AddMissing)
|
||||
{
|
||||
AddMissing(result);
|
||||
}
|
||||
|
||||
void AddMissing(NetworkObserver networkObserver)
|
||||
{
|
||||
int count = _defaultConditions.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ObserverCondition oc = _defaultConditions[i];
|
||||
if (!networkObserver.ObserverConditionsInternal.Contains(oc))
|
||||
networkObserver.ObserverConditionsInternal.Add(oc);
|
||||
}
|
||||
}
|
||||
|
||||
result.ConditionsSetByObserverManager = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d331f979d46e8e4a9fc90070c596d44
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Observing/ObserverManager.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd17b65ce1c2f3248980bdc8408c808e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5500daad38c4cd742b6f37bc59c91849
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,91 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Managing.Predicting.Editing
|
||||
{
|
||||
[CustomEditor(typeof(PredictionManager), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class PredictionManagerEditor : Editor
|
||||
{
|
||||
//Client.
|
||||
private SerializedProperty _reduceReconcilesWithFramerate;
|
||||
private SerializedProperty _minimumClientReconcileFramerate;
|
||||
private SerializedProperty _createLocalStates;
|
||||
private SerializedProperty _stateInterpolation;
|
||||
private SerializedProperty _stateOrder;
|
||||
|
||||
//Server.
|
||||
private SerializedProperty _dropExcessiveReplicates;
|
||||
private SerializedProperty _maximumServerReplicates;
|
||||
//private SerializedProperty _maximumConsumeCount;
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
//Client.
|
||||
_reduceReconcilesWithFramerate = serializedObject.FindProperty(nameof(_reduceReconcilesWithFramerate));
|
||||
_minimumClientReconcileFramerate = serializedObject.FindProperty(nameof(_minimumClientReconcileFramerate));
|
||||
_createLocalStates = serializedObject.FindProperty(nameof(_createLocalStates));
|
||||
_stateInterpolation = serializedObject.FindProperty(nameof(_stateInterpolation));
|
||||
_stateOrder = serializedObject.FindProperty(nameof(_stateOrder));
|
||||
|
||||
//Server.
|
||||
_dropExcessiveReplicates = serializedObject.FindProperty(nameof(_dropExcessiveReplicates));
|
||||
_maximumServerReplicates = serializedObject.FindProperty(nameof(_maximumServerReplicates));
|
||||
//_maximumConsumeCount = serializedObject.FindProperty(nameof(_maximumConsumeCount));
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour((PredictionManager)target), typeof(PredictionManager), false);
|
||||
GUI.enabled = true;
|
||||
|
||||
|
||||
EditorGUILayout.LabelField("Client", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.PropertyField(_reduceReconcilesWithFramerate);
|
||||
if (_reduceReconcilesWithFramerate.boolValue)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_minimumClientReconcileFramerate);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(_createLocalStates);
|
||||
|
||||
int interpolationValue = _stateInterpolation.intValue;
|
||||
if (interpolationValue == 0)
|
||||
EditorGUILayout.HelpBox(PredictionManager.ZERO_STATE_INTERPOLATION_MESSAGE, MessageType.Warning);
|
||||
else if (_stateOrder.intValue == (int)ReplicateStateOrder.Appended && interpolationValue < PredictionManager.MINIMUM_APPENDED_INTERPOLATION_RECOMMENDATION)
|
||||
EditorGUILayout.HelpBox(PredictionManager.LESS_THAN_MINIMUM_APPENDED_MESSAGE, MessageType.Warning);
|
||||
else if (_stateOrder.intValue == (int)ReplicateStateOrder.Inserted && interpolationValue < PredictionManager.MINIMUM_INSERTED_INTERPOLATION_RECOMMENDATION)
|
||||
EditorGUILayout.HelpBox(PredictionManager.LESS_THAN_MINIMUM_INSERTED_MESSAGE, MessageType.Warning);
|
||||
EditorGUILayout.PropertyField(_stateInterpolation);
|
||||
|
||||
EditorGUILayout.PropertyField(_stateOrder);
|
||||
EditorGUI.indentLevel--;
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.LabelField("Server", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
// EditorGUILayout.PropertyField(_serverInterpolation);
|
||||
EditorGUILayout.PropertyField(_dropExcessiveReplicates);
|
||||
EditorGUI.indentLevel++;
|
||||
if (_dropExcessiveReplicates.boolValue)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_maximumServerReplicates);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5382c1ab98f25c8439d23140d36651fe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Prediction/Editor/PredictionManagerEditor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,945 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Performance;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FishNet.Managing.Statistic;
|
||||
using Unity.Profiling;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace FishNet.Managing.Predicting
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional options for managing the observer system.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
[AddComponentMenu("FishNet/Manager/PredictionManager")]
|
||||
public sealed class PredictionManager : MonoBehaviour
|
||||
{
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// Responsible for throttling client reconcile amounts.
|
||||
/// </summary>
|
||||
private class ClientReconcileThrottler
|
||||
{
|
||||
/// <summary>
|
||||
/// Last time a reconcile ran.
|
||||
/// </summary>
|
||||
private float _lastReconcileUnscaledTime;
|
||||
/// <summary>
|
||||
/// Number of frames passed.
|
||||
/// </summary>
|
||||
private ushort _accumulatedFrames;
|
||||
/// <summary>
|
||||
/// Last evaluated frame rate.
|
||||
/// </summary>
|
||||
private ushort _evaluatedFramerate;
|
||||
/// <summary>
|
||||
/// Total delta time added by frames since the last reset.
|
||||
/// </summary>
|
||||
private float _accumulatedDeltaTime;
|
||||
/// <summary>
|
||||
/// Value to use when no frames are recorded.
|
||||
/// </summary>
|
||||
private const ushort UNSET_FRAME_COUNT = 0;
|
||||
|
||||
/// <summary>
|
||||
/// True if a reconcile can be performed and sets the next time a reconcile may occur if so.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool TryReconcile(ushort minimumFrameRate)
|
||||
{
|
||||
//No frames are set -- allow a reconcile.
|
||||
if (_evaluatedFramerate == UNSET_FRAME_COUNT)
|
||||
return ReturnTrueAndUpdateValues();
|
||||
|
||||
if (_evaluatedFramerate >= minimumFrameRate)
|
||||
return ReturnTrueAndUpdateValues();
|
||||
|
||||
/* If here then frames are not met. */
|
||||
|
||||
//Enough time has passed since last reconcile to run another.
|
||||
//Not enough time has passed.
|
||||
if (Time.fixedUnscaledTime - _lastReconcileUnscaledTime >= 0.25f)
|
||||
return ReturnTrueAndUpdateValues();
|
||||
|
||||
/* Reconcile will not be performed if here. */
|
||||
return false;
|
||||
|
||||
bool ReturnTrueAndUpdateValues()
|
||||
{
|
||||
_lastReconcileUnscaledTime = Time.unscaledTime;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds that a frame had occurred.
|
||||
/// </summary>
|
||||
public void AddFrame(float unscaledDeltaTime)
|
||||
{
|
||||
_accumulatedDeltaTime += unscaledDeltaTime;
|
||||
|
||||
if (_accumulatedFrames < ushort.MaxValue)
|
||||
_accumulatedFrames++;
|
||||
|
||||
/* Frames will only be updated every three seconds. */
|
||||
if (_accumulatedDeltaTime < 3f)
|
||||
return;
|
||||
|
||||
//Update evaluated frame rate.
|
||||
_evaluatedFramerate = _accumulatedFrames;
|
||||
|
||||
_accumulatedDeltaTime = 0f;
|
||||
_accumulatedFrames = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets current values.
|
||||
/// </summary>
|
||||
public void ResetState()
|
||||
{
|
||||
_accumulatedDeltaTime = 0f;
|
||||
_accumulatedFrames = UNSET_FRAME_COUNT;
|
||||
_lastReconcileUnscaledTime = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
internal class StatePacket : IResettable
|
||||
{
|
||||
public struct IncomingData
|
||||
{
|
||||
public ArraySegment<byte> Data;
|
||||
public readonly Channel Channel;
|
||||
|
||||
public IncomingData(ArraySegment<byte> data, Channel channel)
|
||||
{
|
||||
Data = data;
|
||||
Channel = channel;
|
||||
}
|
||||
}
|
||||
|
||||
public List<IncomingData> Data;
|
||||
public uint ClientTick;
|
||||
public uint ServerTick;
|
||||
|
||||
public void Update(ArraySegment<byte> data, uint clientTick, uint serverTick, Channel channel)
|
||||
{
|
||||
AddData(data, channel);
|
||||
ServerTick = serverTick;
|
||||
ClientTick = clientTick;
|
||||
}
|
||||
|
||||
public void AddData(ArraySegment<byte> data, Channel channel)
|
||||
{
|
||||
if (data.Array != null)
|
||||
Data.Add(new(data, channel));
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
for (int i = 0; i < Data.Count; i++)
|
||||
ByteArrayPool.Store(Data[i].Data.Array);
|
||||
|
||||
CollectionCaches<IncomingData>.StoreAndDefault(ref Data);
|
||||
}
|
||||
|
||||
public void InitializeState()
|
||||
{
|
||||
Data = CollectionCaches<IncomingData>.RetrieveList();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Called before performing a reconcile. Contains the client and server tick the reconcile is for.
|
||||
/// </summary>
|
||||
public event PreReconcileDel OnPreReconcile;
|
||||
|
||||
public delegate void PreReconcileDel(uint clientTick, uint serverTick);
|
||||
|
||||
/// <summary>
|
||||
/// Called when performing a reconcile.
|
||||
/// This is used internally to reconcile objects and does not guarantee your subscriptions to this event will process before or after internal components.
|
||||
/// </summary>
|
||||
public event ReconcileDel OnReconcile;
|
||||
|
||||
public delegate void ReconcileDel(uint clientTick, uint serverTick);
|
||||
|
||||
/// <summary>
|
||||
/// Called after performing a reconcile. Contains the client and server tick the reconcile is for.
|
||||
/// </summary>
|
||||
public event PostReconcileDel OnPostReconcile;
|
||||
|
||||
public delegate void PostReconcileDel(uint clientTick, uint serverTick);
|
||||
|
||||
/// <summary>
|
||||
/// Called before Physics SyncTransforms are run after a reconcile.
|
||||
/// This will only invoke if physics are set to TimeManager, within the TimeManager inspector.
|
||||
/// </summary>
|
||||
public event PrePhysicsSyncTransformDel OnPrePhysicsTransformSync;
|
||||
|
||||
public delegate void PrePhysicsSyncTransformDel(uint clientTick, uint serverTick);
|
||||
|
||||
/// <summary>
|
||||
/// Called after Physics SyncTransforms are run after a reconcile.
|
||||
/// This will only invoke if physics are set to TimeManager, within the TimeManager inspector.
|
||||
/// </summary>
|
||||
public event PostPhysicsSyncTransformDel OnPostPhysicsTransformSync;
|
||||
|
||||
public delegate void PostPhysicsSyncTransformDel(uint clientTick, uint serverTick);
|
||||
|
||||
public event PostPhysicsSyncTransformDel OnPostReconcileSyncTransforms;
|
||||
/// <summary>
|
||||
/// Called before physics is simulated when replaying a replicate method.
|
||||
/// </summary>
|
||||
public event PreReplicateReplayDel OnPreReplicateReplay;
|
||||
|
||||
public delegate void PreReplicateReplayDel(uint clientTick, uint serverTick);
|
||||
|
||||
/// <summary>
|
||||
/// Called when replaying a replication.
|
||||
/// This is called before physics are simulated.
|
||||
/// This is used internally to replay objects and does not guarantee your subscriptions to this event will process before or after internal components.
|
||||
/// </summary>
|
||||
internal event ReplicateReplayDel OnReplicateReplay;
|
||||
|
||||
public delegate void ReplicateReplayDel(uint clientTick, uint serverTick);
|
||||
|
||||
/// <summary>
|
||||
/// Called after physics is simulated when replaying a replicate method.
|
||||
/// </summary>
|
||||
public event PostReplicateReplayDel OnPostReplicateReplay;
|
||||
|
||||
public delegate void PostReplicateReplayDel(uint clientTick, uint serverTick);
|
||||
|
||||
/// <summary>
|
||||
/// True if client timing needs to be reduced. This is fine-tuning of the prediction system.
|
||||
/// </summary>
|
||||
internal bool ReduceClientTiming;
|
||||
/// <summary>
|
||||
/// True if prediction is currently reconciling. While reconciling run replicates will be replays.
|
||||
/// </summary>
|
||||
public bool IsReconciling { get; private set; }
|
||||
/// <summary>
|
||||
/// When not unset this is the current tick which local client is replaying authoritative inputs on.
|
||||
/// </summary>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public uint ClientReplayTick { get; private set; } = TimeManager.UNSET_TICK;
|
||||
/// <summary>
|
||||
/// When not unset this is the current tick which local client is replaying non-authoritative inputs on.
|
||||
/// </summary>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public uint ServerReplayTick { get; private set; } = TimeManager.UNSET_TICK;
|
||||
/// <summary>
|
||||
/// Local tick on the most recent performed reconcile.
|
||||
/// </summary>
|
||||
public uint ClientStateTick { get; private set; } = TimeManager.UNSET_TICK;
|
||||
/// <summary>
|
||||
/// Server tick on the most recent performed reconcile.
|
||||
/// </summary>
|
||||
public uint ServerStateTick { get; private set; } = TimeManager.UNSET_TICK;
|
||||
#endregion
|
||||
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// True to reduce reconciles when frame rate drops below a threshold.
|
||||
/// When frame rate drops below the specified value reconciles will be reduced to roughly 4-5 times a second.
|
||||
/// </summary>
|
||||
[Tooltip("True to reduce reconciles when frame rate drops below a threshold. When frame rate drops below the specified value reconciles will be reduced to roughly 4-5 times a second.")]
|
||||
[SerializeField]
|
||||
private bool _reduceReconcilesWithFramerate = true;
|
||||
/// <summary>
|
||||
/// Frame rate client must fall below to begin reducing how many reconciles the client runs locally.
|
||||
/// </summary>
|
||||
[Tooltip("Frame rate client must fall below to begin reducing how many reconciles the client runs locally.")]
|
||||
[Range(15, NetworkManager.MAXIMUM_FRAMERATE)]
|
||||
[SerializeField]
|
||||
private ushort _minimumClientReconcileFramerate = 50;
|
||||
/// <summary>
|
||||
/// True for the client to create local reconcile states. Enabling this feature allows reconciles to be sent less frequently and provides data to use for reconciles when packets are lost.
|
||||
/// </summary>
|
||||
internal bool CreateLocalStates => _createLocalStates;
|
||||
[FormerlySerializedAs("_localStates")]
|
||||
[Tooltip("True for the client to create local reconcile states. Enabling this feature allows reconciles to be sent less frequently and provides data to use for reconciles when packets are lost.")]
|
||||
[SerializeField]
|
||||
private bool _createLocalStates = true;
|
||||
/// <summary>
|
||||
/// How many states to try and hold in a buffer before running them. Larger values add resilience against network issues at the cost of running states later.
|
||||
/// </summary>
|
||||
public byte StateInterpolation => _stateInterpolation;
|
||||
[Tooltip("How many states to try and hold in a buffer before running them on clients. Larger values add resilience against network issues at the cost of running states later.")]
|
||||
[Range(0, MAXIMUM_PAST_INPUTS)]
|
||||
[FormerlySerializedAs("_redundancyCount")] // Remove on V5.
|
||||
[FormerlySerializedAs("_interpolation")] // Remove on V5.
|
||||
[SerializeField]
|
||||
private byte _stateInterpolation = 2;
|
||||
/// <summary>
|
||||
/// The order in which states are run. Future favors performance and does not depend upon reconciles, while Past favors accuracy but clients must reconcile every tick.
|
||||
/// </summary>
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public ReplicateStateOrder StateOrder => _stateOrder;
|
||||
[Tooltip("The order in which clients run states. Future favors performance and does not depend upon reconciles, while Past favors accuracy but clients must reconcile every tick.")]
|
||||
[SerializeField]
|
||||
private ReplicateStateOrder _stateOrder = ReplicateStateOrder.Appended;
|
||||
/// <summary>
|
||||
/// True if StateOrder is set to future.
|
||||
/// </summary>
|
||||
internal bool IsAppendedStateOrder => _stateOrder == ReplicateStateOrder.Appended;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current ReplicateStateOrder. This may be changed at runtime.
|
||||
/// Changing this value only affects the client which it is changed on.
|
||||
/// </summary>
|
||||
/// <param name = "stateOrder"></param>
|
||||
public void SetStateOrder(ReplicateStateOrder stateOrder)
|
||||
{
|
||||
// Server doesn't use state order, exit early if server.
|
||||
if (_networkManager.IsServerStarted)
|
||||
return;
|
||||
// Same as before, do nothing.
|
||||
if (stateOrder == _stateOrder)
|
||||
return;
|
||||
|
||||
_stateOrder = stateOrder;
|
||||
/* If the client is started and if the next
|
||||
* ReplicateStateOrder is Inserted then tell spawned objects
|
||||
* to move the current replicates queue into history. This
|
||||
* will let the data be used during reconcile.
|
||||
*
|
||||
* Since data is now in the history to work with reconciles, discard
|
||||
* it from replicates queue. */
|
||||
if (stateOrder == ReplicateStateOrder.Inserted && _networkManager.IsClientStarted)
|
||||
{
|
||||
foreach (NetworkObject item in _networkManager.ClientManager.Objects.Spawned.Values)
|
||||
item.EmptyReplicatesQueueIntoHistory();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True to drop replicates from clients which are being received excessively. This can help with attacks but may cause client to temporarily de-synchronize during connectivity issues.
|
||||
/// When false the server will hold at most up to 3 seconds worth of replicates, consuming multiple per tick to clear out the buffer quicker. This is good to ensure all inputs are executed but potentially could allow speed hacking.
|
||||
/// </summary>
|
||||
internal bool DropExcessiveReplicates => _dropExcessiveReplicates;
|
||||
[Tooltip("True to drop replicates from clients which are being received excessively. This can help with attacks but may cause client to temporarily de-synchronize during connectivity issues. When false the server will hold at most up to 3 seconds worth of replicates, consuming multiple per tick to clear out the buffer quicker. This is good to ensure all inputs are executed but potentially could allow speed hacking.")]
|
||||
[SerializeField]
|
||||
private bool _dropExcessiveReplicates = true;
|
||||
/// <summary>
|
||||
/// No more than this value of replicates should be stored as a buffer.
|
||||
/// </summary>
|
||||
internal ushort MaximumPastReplicates => (ushort)(_networkManager.TimeManager.TickRate * 5);
|
||||
[Tooltip("Maximum number of replicates a server can queue per object. Higher values will reduce the chance of dropped input when the client's connection is unstable, but will potentially add latency to the client's object both on the server and client.")]
|
||||
[SerializeField]
|
||||
private byte _maximumServerReplicates = 15;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the maximum number of replicates a server can queue per object.
|
||||
/// </summary>
|
||||
public void SetMaximumServerReplicates(byte value) => _maximumServerReplicates = (byte)Mathf.Clamp(value, MINIMUM_REPLICATE_QUEUE_SIZE, MAXIMUM_REPLICATE_QUEUE_SIZE);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of replicates a server can queue per object. Higher values will reduce the chance of dropped input when the client's connection is unstable, but will potentially add latency to the client's object both on the server and client.
|
||||
/// </summary>
|
||||
public byte GetMaximumServerReplicates() => _maximumServerReplicates;
|
||||
|
||||
/// <summary>
|
||||
/// Number of past inputs to send, which is also the number of times to resend final data.
|
||||
/// </summary>
|
||||
internal byte RedundancyCount => (byte)(_stateInterpolation + 1);
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Current reconcile state to use.
|
||||
/// </summary>
|
||||
// private StatePacket _reconcileState;
|
||||
private readonly BasicQueue<StatePacket> _reconcileStates = new();
|
||||
/// <summary>
|
||||
/// Look up to find states by their tick.
|
||||
/// Key: client LocalTick on the state.
|
||||
/// Value: StatePacket stored.
|
||||
/// </summary>
|
||||
private readonly Dictionary<uint, StatePacket> _stateLookups = new();
|
||||
/// <summary>
|
||||
/// Last ordered tick read for a reconcile state.
|
||||
/// </summary>
|
||||
private uint _lastOrderedReadReconcileTick;
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
private NetworkTrafficStatistics _networkTrafficStatistics;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private readonly ClientReconcileThrottler _clientReconcileThrottler = new();
|
||||
/// <summary>
|
||||
/// True if the client-side had subscribed to the TimeManager.
|
||||
/// </summary>
|
||||
private bool _clientSubscribedToTimeManager;
|
||||
/// <summary>
|
||||
/// NetworkManager used with this.
|
||||
/// </summary>
|
||||
private NetworkManager _networkManager;
|
||||
#endregion
|
||||
|
||||
#region Private Profiler Markers
|
||||
private static readonly ProfilerMarker _pm_OnLateUpdate = new("PredictionManager.TimeManager_OnLateUpdate()");
|
||||
private static readonly ProfilerMarker _pm_OnPreReconcile = new("PredictionManager.OnPreReconcile(uint, uint)");
|
||||
private static readonly ProfilerMarker _pm_OnReconcile = new("PredictionManager.OnReconcile(uint, uint)");
|
||||
private static readonly ProfilerMarker _pm_OnPrePhysicsTransformSync = new("PredictionManager.OnPrePhysicsTransformSync(uint, uint)");
|
||||
private static readonly ProfilerMarker _pm_PhysicsSyncTransforms = new("PredictionManager.Physics.SyncTransforms()");
|
||||
private static readonly ProfilerMarker _pm_Physics2DSyncTransforms = new("PredictionManager.Physics2D.SyncTransforms()");
|
||||
private static readonly ProfilerMarker _pm_OnPostPhysicsTransformSync = new("PredictionManager.OnPostPhysicsTransformSync(uint, uint)");
|
||||
private static readonly ProfilerMarker _pm_OnPostReconcileSyncTransforms = new("PredictionManager.OnPostReconcileSyncTransforms(uint, uint)");
|
||||
private static readonly ProfilerMarker _pm_OnPreReplicateReplay = new("PredictionManager.OnPreReplicateReplay(uint, uint)");
|
||||
private static readonly ProfilerMarker _pm_OnReplicateReplay = new("PredictionManager.OnReplicateReplay(uint, uint)");
|
||||
private static readonly ProfilerMarker _pm_OnPostReplicateReplay = new("PredictionManager.OnPostReplicateReplay(uint, uint)");
|
||||
private static readonly ProfilerMarker _pm_OnPostReconcile = new("PredictionManager.OnPostReconcile(uint, uint)");
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// Amount to reserve for the header of a state update.
|
||||
/// </summary>
|
||||
internal const int STATE_HEADER_RESERVE_LENGTH = TransportManager.PACKETID_LENGTH + TransportManager.UNPACKED_TICK_LENGTH + TransportManager.UNPACKED_SIZE_LENGTH;
|
||||
/// <summary>
|
||||
/// Minimum number of past inputs which can be sent.
|
||||
/// </summary>
|
||||
private const byte MINIMUM_PAST_INPUTS = 1;
|
||||
/// <summary>
|
||||
/// Maximum number of past inputs which can be sent.
|
||||
/// </summary>
|
||||
private const byte MAXIMUM_PAST_INPUTS = 5;
|
||||
/// <summary>
|
||||
/// Minimum amount of replicate queue size.
|
||||
/// </summary>
|
||||
private const byte MINIMUM_REPLICATE_QUEUE_SIZE = MINIMUM_PAST_INPUTS + 1;
|
||||
/// <summary>
|
||||
/// Maximum amount of replicate queue size.
|
||||
/// </summary>
|
||||
private const byte MAXIMUM_REPLICATE_QUEUE_SIZE = byte.MaxValue;
|
||||
/// <summary>
|
||||
/// Recommended state interpolation value when using appended state order.
|
||||
/// </summary>
|
||||
internal const int MINIMUM_APPENDED_INTERPOLATION_RECOMMENDATION = 2;
|
||||
/// <summary>
|
||||
/// Recommended state interpolation value when using inserted state order.
|
||||
/// </summary>
|
||||
internal const int MINIMUM_INSERTED_INTERPOLATION_RECOMMENDATION = 1;
|
||||
/// <summary>
|
||||
/// Message when state interpolation is 0.
|
||||
/// </summary>
|
||||
internal static readonly string ZERO_STATE_INTERPOLATION_MESSAGE = $"When interpolation is 0 the chances of de-synchronizations on non-owned objects is increased drastically.";
|
||||
/// <summary>
|
||||
/// Message when state interpolation is less than ideal for appended state order.
|
||||
/// </summary>
|
||||
internal static readonly string LESS_THAN_MINIMUM_APPENDED_MESSAGE = $"When using Appended StateOrder and an interpolation less than {MINIMUM_APPENDED_INTERPOLATION_RECOMMENDATION} the chances of de-synchronizations on non-owned objects is increased.";
|
||||
/// <summary>
|
||||
/// Message when state interpolation is less than ideal for inserted state order.
|
||||
/// </summary>
|
||||
internal static readonly string LESS_THAN_MINIMUM_INSERTED_MESSAGE = $"When using Inserted StateOrder and an interpolation less than {MINIMUM_INSERTED_INTERPOLATION_RECOMMENDATION} the chances of de-synchronizations on non-owned objects is increased.";
|
||||
#endregion
|
||||
|
||||
internal void InitializeOnce(NetworkManager manager)
|
||||
{
|
||||
_networkManager = manager;
|
||||
manager.StatisticsManager.TryGetNetworkTrafficStatistics(out _networkTrafficStatistics);
|
||||
|
||||
ValidateClampInterpolation();
|
||||
_networkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the local client connection state changes.
|
||||
/// </summary>
|
||||
private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs obj)
|
||||
{
|
||||
_clientReconcileThrottler.ResetState();
|
||||
_lastOrderedReadReconcileTick = 0;
|
||||
|
||||
//If state is started.
|
||||
if (obj.ConnectionState == LocalConnectionState.Started)
|
||||
SubscribeToTimeManager(subscribe: true, asServer: false);
|
||||
else
|
||||
SubscribeToTimeManager(subscribe: false, asServer: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps queued inputs to a valid value.
|
||||
/// </summary>
|
||||
private void ValidateClampInterpolation()
|
||||
{
|
||||
ushort startingValue = _stateInterpolation;
|
||||
// Check for setting if dropping.
|
||||
if (_dropExcessiveReplicates && _stateInterpolation > _maximumServerReplicates)
|
||||
_stateInterpolation = (byte)(_maximumServerReplicates - 1);
|
||||
|
||||
// If changed.
|
||||
if (_stateInterpolation != startingValue)
|
||||
_networkManager.Log($"Interpolation has been set to {_stateInterpolation}.");
|
||||
|
||||
// Check to warn if low value.
|
||||
if (_stateInterpolation == 0)
|
||||
_networkManager.LogWarning(ZERO_STATE_INTERPOLATION_MESSAGE);
|
||||
else if (_stateOrder == ReplicateStateOrder.Appended && _stateInterpolation < MINIMUM_APPENDED_INTERPOLATION_RECOMMENDATION)
|
||||
_networkManager.LogWarning(LESS_THAN_MINIMUM_APPENDED_MESSAGE);
|
||||
else if (_stateOrder == ReplicateStateOrder.Inserted && _stateInterpolation < MINIMUM_INSERTED_INTERPOLATION_RECOMMENDATION)
|
||||
_networkManager.LogWarning(LESS_THAN_MINIMUM_INSERTED_MESSAGE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes subscription to TimeManager.
|
||||
/// </summary>
|
||||
private void SubscribeToTimeManager(bool subscribe, bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
return;
|
||||
if (_networkManager == null)
|
||||
return;
|
||||
|
||||
if (subscribe == _clientSubscribedToTimeManager)
|
||||
return;
|
||||
|
||||
_clientSubscribedToTimeManager = subscribe;
|
||||
|
||||
if (subscribe)
|
||||
_networkManager.TimeManager.OnLateUpdate += TimeManager_OnLateUpdate;
|
||||
else
|
||||
_networkManager.TimeManager.OnLateUpdate -= TimeManager_OnLateUpdate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when late update fires on the TimeManager.
|
||||
/// </summary>
|
||||
private void TimeManager_OnLateUpdate()
|
||||
{
|
||||
using (_pm_OnLateUpdate.Auto())
|
||||
{
|
||||
/* Do not throttle nor count frames if scene manager has performed actions recently. */
|
||||
if (_networkManager.SceneManager.HasProcessedScenesRecently(timeFrame: 2f))
|
||||
{
|
||||
_clientReconcileThrottler.ResetState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_reduceReconcilesWithFramerate)
|
||||
_clientReconcileThrottler.AddFrame(Time.unscaledDeltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns client or server state tick for the current reconcile.
|
||||
/// </summary>
|
||||
/// <param name = "clientTick">True to return client state tick, false for servers.</param>
|
||||
/// <returns></returns>
|
||||
public uint GetReconcileStateTick(bool clientTick) => clientTick ? ClientStateTick : ServerStateTick;
|
||||
|
||||
/// <summary>
|
||||
/// Reconciles to received states.
|
||||
/// </summary>
|
||||
internal void ReconcileToStates()
|
||||
{
|
||||
if (!_networkManager.IsClientStarted)
|
||||
return;
|
||||
|
||||
if (_reconcileStates.Count == 0)
|
||||
return;
|
||||
|
||||
TimeManager tm = _networkManager.TimeManager;
|
||||
uint localTick = tm.LocalTick;
|
||||
uint lastLocalTickCompleted = localTick;
|
||||
|
||||
//Check the first state; if it cannot be run no need to continue.
|
||||
if (!CanStateBeReconciled(_reconcileStates.Peek()))
|
||||
return;
|
||||
|
||||
/* Discard reconciles which are not needed due to
|
||||
* repetitiveness. */
|
||||
StatePacket statePacket;
|
||||
|
||||
/* Since Inserted only runs inputs on reconciles
|
||||
* it must use the first state to ensure all
|
||||
* possible inputs are run, starting at the oldest (first state). */
|
||||
if (StateOrder == ReplicateStateOrder.Inserted)
|
||||
{
|
||||
statePacket = _reconcileStates.Dequeue();
|
||||
|
||||
int removeCount = 0;
|
||||
|
||||
//Start beyond the first state.
|
||||
for (int i = 0; i < _reconcileStates.Count; i++)
|
||||
{
|
||||
if (CanStateBeReconciled(_reconcileStates[i]))
|
||||
removeCount++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < removeCount; i++)
|
||||
DisposeOfStatePacket(_reconcileStates.Dequeue());
|
||||
}
|
||||
/* Appended order must reconcile the oldest state no-matter what.
|
||||
* This is for the scenario if a local player were to jump on perhaps tick 100,
|
||||
* then due to latency receive reconciles 99 100 and 101 at once, we must reconcile
|
||||
* to 99 should there be any corrections to the jump on 100. */
|
||||
else
|
||||
{
|
||||
/* The state to reconcile will always be
|
||||
* the first entry and removing ones not needed. */
|
||||
statePacket = _reconcileStates.Dequeue();
|
||||
}
|
||||
|
||||
bool CanStateBeReconciled(StatePacket lStatePacket)
|
||||
{
|
||||
if (lStatePacket == null)
|
||||
return false;
|
||||
|
||||
/* In order for the client to be able to reconcile itself
|
||||
* the clientTick must be <= the last tick run on the client (lastLocalTickCompleted).
|
||||
*
|
||||
* However, since spectated objects use StateInterpolation they cannot be reconciled
|
||||
* unless the clientTick is <= the last tick run on the client, and interpolation.
|
||||
* This ensures that the reconcile does not occur to a state of data which is
|
||||
* beyond the interpolated amount.
|
||||
* Eg:
|
||||
* If client received a spectated replicate on spectated tick 100 then the replicate
|
||||
* would not run until localTick 102. If the next client reconcile state were for 100 it would
|
||||
* pass because 100 is <= lastLocalTickCompleted. The spectated object would then reconcile
|
||||
* to the results of the server spectated tick 100, even though the client had not run that
|
||||
* spectated replicate locally yet. A correction would occur, and there would possibly be
|
||||
* graphical disturbance.
|
||||
*
|
||||
* This happens because clients and server run inputs immediately on
|
||||
* owned objects; however, on an object the server spectated, there may not be any desync,
|
||||
* but we must be considerate of all scenarios.
|
||||
*/
|
||||
bool isClientMet = lStatePacket.ClientTick < lastLocalTickCompleted;
|
||||
bool isServerMet = lStatePacket.ServerTick < _networkManager.TimeManager.LastPacketTick.Value() - StateInterpolation - 1;
|
||||
|
||||
return isClientMet && isServerMet;
|
||||
}
|
||||
|
||||
//If state is null no reconcile is available at this time.
|
||||
if (statePacket == null)
|
||||
return;
|
||||
|
||||
bool dropReconcile = false;
|
||||
uint clientTick = statePacket.ClientTick;
|
||||
uint serverTick = statePacket.ServerTick;
|
||||
|
||||
//Check to throttle reconciles.
|
||||
if (_reduceReconcilesWithFramerate && !_clientReconcileThrottler.TryReconcile(_minimumClientReconcileFramerate))
|
||||
dropReconcile = true;
|
||||
|
||||
if (!dropReconcile)
|
||||
{
|
||||
IsReconciling = true;
|
||||
|
||||
ClientStateTick = clientTick;
|
||||
/* This is the tick which the reconcile is for.
|
||||
* Since reconciles are performed after replicate, if
|
||||
* the replicate was on tick 100 then this reconcile is the state
|
||||
* on tick 100, after the replicate is performed. */
|
||||
ServerStateTick = serverTick;
|
||||
|
||||
// Have the reader get processed.
|
||||
foreach (StatePacket.IncomingData item in statePacket.Data)
|
||||
{
|
||||
PooledReader reader = ReaderPool.Retrieve(item.Data, _networkManager, Reader.DataSource.Server);
|
||||
_networkManager.ClientManager.ParseReader(reader, item.Channel);
|
||||
ReaderPool.Store(reader);
|
||||
}
|
||||
|
||||
bool timeManagerPhysics = tm.PhysicsMode == PhysicsMode.TimeManager;
|
||||
float tickDelta = (float)tm.TickDelta * _networkManager.TimeManager.GetPhysicsTimeScale();
|
||||
|
||||
using (_pm_OnPreReconcile.Auto())
|
||||
OnPreReconcile?.Invoke(ClientStateTick, ServerStateTick);
|
||||
using (_pm_OnReconcile.Auto())
|
||||
OnReconcile?.Invoke(ClientStateTick, ServerStateTick);
|
||||
|
||||
if (timeManagerPhysics)
|
||||
{
|
||||
using (_pm_OnPrePhysicsTransformSync.Auto())
|
||||
OnPrePhysicsTransformSync?.Invoke(ClientStateTick, ServerStateTick);
|
||||
|
||||
using (_pm_PhysicsSyncTransforms.Auto())
|
||||
Physics.SyncTransforms();
|
||||
using (_pm_Physics2DSyncTransforms.Auto())
|
||||
Physics2D.SyncTransforms();
|
||||
|
||||
using (_pm_OnPostPhysicsTransformSync.Auto())
|
||||
OnPostPhysicsTransformSync?.Invoke(ClientStateTick, ServerStateTick);
|
||||
}
|
||||
|
||||
using (_pm_OnPostReconcileSyncTransforms.Auto())
|
||||
OnPostReconcileSyncTransforms?.Invoke(ClientStateTick, ServerStateTick);
|
||||
|
||||
Physics.SyncTransforms();
|
||||
Physics2D.SyncTransforms();
|
||||
|
||||
/* Set first replicate to be the 1 tick
|
||||
* after reconcile. This is because reconcile calcs
|
||||
* should be performed after replicate has run.
|
||||
* In result object will reconcile to data AFTER
|
||||
* the replicate tick, and then run remaining replicates as replay.
|
||||
*
|
||||
* Replay up to localtick, excluding localtick. There will
|
||||
* be no input for localtick since reconcile runs before
|
||||
* OnTick. */
|
||||
ClientReplayTick = ClientStateTick + 1;
|
||||
ServerReplayTick = ServerStateTick + 1;
|
||||
|
||||
/* Only replay up to but excluding local tick.
|
||||
* This prevents client from running 1 local tick into the future
|
||||
* since the OnTick has not run yet.
|
||||
*
|
||||
* EG: if localTick is 100 replay will run up to 99, then OnTick
|
||||
* will fire for 100. */
|
||||
while (ClientReplayTick < lastLocalTickCompleted)
|
||||
{
|
||||
using (_pm_OnPreReplicateReplay.Auto())
|
||||
OnPreReplicateReplay?.Invoke(ClientReplayTick, ServerReplayTick);
|
||||
using (_pm_OnReplicateReplay.Auto())
|
||||
OnReplicateReplay?.Invoke(ClientReplayTick, ServerReplayTick);
|
||||
|
||||
if (timeManagerPhysics && tickDelta > 0f)
|
||||
{
|
||||
_networkManager.TimeManager.InvokeOnPhysicsSimulation(preSimulation: true, tickDelta);
|
||||
_networkManager.TimeManager.SimulatePhysics(tickDelta);
|
||||
_networkManager.TimeManager.InvokeOnPhysicsSimulation(preSimulation: false, tickDelta);
|
||||
}
|
||||
|
||||
using (_pm_OnPostReplicateReplay.Auto())
|
||||
OnPostReplicateReplay?.Invoke(ClientReplayTick, ServerReplayTick);
|
||||
|
||||
ClientReplayTick++;
|
||||
ServerReplayTick++;
|
||||
}
|
||||
|
||||
using (_pm_OnPostReconcile.Auto())
|
||||
OnPostReconcile?.Invoke(ClientStateTick, ServerStateTick);
|
||||
|
||||
ClientStateTick = TimeManager.UNSET_TICK;
|
||||
ServerStateTick = TimeManager.UNSET_TICK;
|
||||
ClientReplayTick = TimeManager.UNSET_TICK;
|
||||
ServerReplayTick = TimeManager.UNSET_TICK;
|
||||
IsReconciling = false;
|
||||
}
|
||||
|
||||
DisposeOfStatePacket(statePacket);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends written states for clients.
|
||||
/// </summary>
|
||||
internal void SendStateUpdate()
|
||||
{
|
||||
byte stateInterpolation = StateInterpolation;
|
||||
TransportManager tm = _networkManager.TransportManager;
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
int headersWritten = 0;
|
||||
#endif
|
||||
|
||||
foreach (NetworkConnection nc in _networkManager.ServerManager.Clients.Values)
|
||||
{
|
||||
uint lastReplicateTick;
|
||||
// If client has performed a replicate recently.
|
||||
if (!nc.ReplicateTick.IsUnset)
|
||||
{
|
||||
lastReplicateTick = nc.ReplicateTick.Value();
|
||||
}
|
||||
/* If not then use what is estimated to be the clients
|
||||
* current tick along with desired interpolation.
|
||||
* This should be just about the same as if the client used replicate recently. */
|
||||
else
|
||||
{
|
||||
uint ncLocalTick = nc.LocalTick.Value();
|
||||
uint interpolationDifference = (uint)stateInterpolation * 2;
|
||||
if (ncLocalTick < interpolationDifference)
|
||||
ncLocalTick = 0;
|
||||
|
||||
lastReplicateTick = ncLocalTick;
|
||||
}
|
||||
|
||||
foreach (PooledWriter writer in nc.PredictionStateWriters)
|
||||
{
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
headersWritten++;
|
||||
#endif
|
||||
|
||||
/* Packet is sent as follows...
|
||||
* PacketId.
|
||||
* LastReplicateTick of receiver.
|
||||
* Length of packet.
|
||||
* Data. */
|
||||
ArraySegment<byte> segment = writer.GetArraySegment();
|
||||
writer.Position = 0;
|
||||
writer.WritePacketIdUnpacked(PacketId.StateUpdate);
|
||||
writer.WriteTickUnpacked(lastReplicateTick);
|
||||
|
||||
/* Send the full length of the writer excluding
|
||||
* the reserve count of the header. The header reserve
|
||||
* count will always be the same so that can be parsed
|
||||
* off immediately upon receiving. */
|
||||
int dataLength = segment.Count - STATE_HEADER_RESERVE_LENGTH;
|
||||
// Write length.
|
||||
writer.WriteInt32Unpacked(dataLength);
|
||||
// Channel is defaulted to unreliable.
|
||||
Channel channel = Channel.Unreliable;
|
||||
// If a single state exceeds MTU it must be sent on reliable. This is extremely unlikely.
|
||||
_networkManager.TransportManager.CheckSetReliableChannel(segment.Count, ref channel);
|
||||
tm.SendToClient((byte)channel, segment, nc, splitLargeMessages: true);
|
||||
}
|
||||
|
||||
nc.StorePredictionStateWriters();
|
||||
}
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
{
|
||||
int written = STATE_HEADER_RESERVE_LENGTH * headersWritten;
|
||||
_networkTrafficStatistics.AddOutboundPacketIdData(PacketId.StateUpdate, string.Empty, written, gameObject: null, asServer: true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a received state update.
|
||||
/// </summary>
|
||||
internal void ParseStateUpdate(PooledReader reader, Channel channel)
|
||||
{
|
||||
uint lastRemoteTick = _networkManager.TimeManager.LastPacketTick.LastRemoteTick;
|
||||
// If server or state is older than another received state.
|
||||
if (_networkManager.IsServerStarted || lastRemoteTick < _lastOrderedReadReconcileTick)
|
||||
{
|
||||
/* If the server is receiving a state update it can
|
||||
* simply discard the data since the server will never
|
||||
* need to reset states. This can occur on the clientHost
|
||||
* side. */
|
||||
reader.ReadTickUnpacked();
|
||||
int payloadLength = reader.ReadInt32Unpacked();
|
||||
reader.Skip(payloadLength);
|
||||
|
||||
// if (!_networkManager.IsServerStarted)
|
||||
// Debug.Log($"Discarding state " + lastRemoteTick);
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastOrderedReadReconcileTick = lastRemoteTick;
|
||||
|
||||
RemoveExcessiveStates();
|
||||
|
||||
// LocalTick of this client the state is for.
|
||||
uint clientTick = reader.ReadTickUnpacked();
|
||||
// Length of packet.
|
||||
int payloadLength = reader.ReadInt32Unpacked();
|
||||
// Read data into array.
|
||||
byte[] arr = ByteArrayPool.Retrieve(payloadLength);
|
||||
reader.ReadUInt8Array(ref arr, payloadLength);
|
||||
// Make segment and store into states.
|
||||
ArraySegment<byte> segment = new(arr, 0, payloadLength);
|
||||
|
||||
/* See if an entry was already added for the clientTick. If so then
|
||||
* add onto the data. Otherwise, add a new state packet. */
|
||||
if (_stateLookups.TryGetValue(clientTick, out StatePacket sp1))
|
||||
{
|
||||
//Debug.Log($"Updating state " + clientTick);
|
||||
sp1.AddData(segment, channel);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Debug.Log($"Adding state " + clientTick);
|
||||
StatePacket sp2 = ResettableObjectCaches<StatePacket>.Retrieve();
|
||||
sp2.Update(segment, clientTick, lastRemoteTick, channel);
|
||||
_stateLookups[clientTick] = sp2;
|
||||
_reconcileStates.Enqueue(sp2);
|
||||
}
|
||||
}
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddInboundPacketIdData(PacketId.StateUpdate, string.Empty, STATE_HEADER_RESERVE_LENGTH, gameObject: null, asServer: false);
|
||||
#endif
|
||||
}
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Creates a local statePacket with no data other than ticks.
|
||||
// /// </summary>
|
||||
// internal void CreateLocalStateUpdate()
|
||||
// {
|
||||
// // Only to be called when there are no reconcile states available.
|
||||
// if (_reconcileStates.Count > 0)
|
||||
// return;
|
||||
// if (_networkManager.IsServerStarted)
|
||||
// return;
|
||||
// // Not yet received first state, cannot apply tick.
|
||||
// if (_lastStatePacketTick.IsUnset)
|
||||
// return;
|
||||
//
|
||||
// _lastStatePacketTick.AddTick(1);
|
||||
//
|
||||
// /* Update last read as well. If we've made it this far we won't be caring about states before this
|
||||
// * even if they come in late. */
|
||||
// _lastOrderedReadReconcileTick = _lastStatePacketTick.Server;
|
||||
//
|
||||
// StatePacket sp = ResettableObjectCaches<StatePacket>.Retrieve();
|
||||
// // Channel does not matter; it's only used to determine how data is parsed, data we don't have.
|
||||
// sp.Update(default, _lastStatePacketTick.Client, _lastStatePacketTick.Server, Channel.Unreliable);
|
||||
// _reconcileStates.Enqueue(sp);
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// Removes excessively stored state packets.
|
||||
/// </summary>
|
||||
private void RemoveExcessiveStates()
|
||||
{
|
||||
/* There should never really be more than queuedInputs so set
|
||||
* a limit a little beyond to prevent reconciles from building up.
|
||||
* This is more of a last result if something went terribly
|
||||
* wrong with the network. */
|
||||
int adjustedStateInterpolation = StateInterpolation * 4 + 2;
|
||||
/* If appending allow an additional stateInterpolation since
|
||||
* entries are not added into the past until they are run on the appended
|
||||
* queue for each networkObject. */
|
||||
if (IsAppendedStateOrder)
|
||||
adjustedStateInterpolation += StateInterpolation;
|
||||
int maxAllowedStates = Mathf.Max(adjustedStateInterpolation, 4);
|
||||
|
||||
while (_reconcileStates.Count > maxAllowedStates)
|
||||
{
|
||||
StatePacket oldSp = _reconcileStates.Dequeue();
|
||||
DisposeOfStatePacket(oldSp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of and cleans up everything related to a StatePacket.
|
||||
/// </summary>
|
||||
private void DisposeOfStatePacket(StatePacket sp)
|
||||
{
|
||||
uint clientTick = sp.ClientTick;
|
||||
_stateLookups.Remove(clientTick);
|
||||
ResettableObjectCaches<StatePacket>.Store(sp);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnValidate()
|
||||
{
|
||||
ValidateClampInterpolation();
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e08bb003fce297d4086cf8cba5aa459a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace FishNet.Managing.Predicting
|
||||
{
|
||||
public enum ReplicateStateOrder
|
||||
{
|
||||
/// <summary>
|
||||
/// On clients states are placed in the past and then run when reconciles occur.
|
||||
/// This ensures that the local client is running states at the exact same time the server had, resulting in the best possible outcome for prediction accuracy.
|
||||
/// To function properly however, clients must reconcile regularly to run past inputs which may cause performance loss on lower-end client devices.
|
||||
/// </summary>
|
||||
Inserted,
|
||||
/// <summary>
|
||||
/// On clients states are still placed in the past but rather than wait until a reconcile to run, they are also placed into a queue and run as they are received.
|
||||
/// This causes states to initially run out of tick alignment, while correcting during reconciles.
|
||||
/// However, due to states no longer depending on reconciles to be run reconciles may be sent less, and clients may run reconciles less, resulting in high performance gain especially among physics-based games.
|
||||
/// </summary>
|
||||
Appended
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 677aa6d3e4dab3743925f2c28646da09
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Prediction/StateOrder.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace FishNet.Managing
|
||||
{
|
||||
public enum RemoteTimeoutType
|
||||
{
|
||||
/// <summary>
|
||||
/// Disable this feature.
|
||||
/// </summary>
|
||||
Disabled = 0,
|
||||
/// <summary>
|
||||
/// Only enable in release builds.
|
||||
/// </summary>
|
||||
Release = 1,
|
||||
/// <summary>
|
||||
/// Enable in all builds and editor.
|
||||
/// </summary>
|
||||
Development = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b3df7f42da2f4f444952e23b5fa8aaa0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/RemoteTimeoutType.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36c52dde2f6aec644aa226d1a864b2b6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f210b76f529cc1040968dfa087adb628
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Documenting;
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
/// <summary>
|
||||
/// Sent when there are starting scenes for the client to load.
|
||||
/// </summary>
|
||||
public struct EmptyStartScenesBroadcast : IBroadcast { }
|
||||
|
||||
/// <summary>
|
||||
/// Sent to clients to load networked scenes.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public struct LoadScenesBroadcast : IBroadcast
|
||||
{
|
||||
public LoadQueueData QueueData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent to clients to unload networked scenes.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public struct UnloadScenesBroadcast : IBroadcast
|
||||
{
|
||||
public UnloadQueueData QueueData;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sent to server to indicate which scenes a client has loaded.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public struct ClientScenesLoadedBroadcast : IBroadcast
|
||||
{
|
||||
public SceneLookupData[] SceneLookupDatas;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 698a94b4f8664ac4ab108deae0ba3b7c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Scened/Broadcast/SceneBroadcasts.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,171 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnitySceneManager = UnityEngine.SceneManagement.SceneManager;
|
||||
using UnityScene = UnityEngine.SceneManagement.Scene;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
public class DefaultSceneProcessor : SceneProcessorBase
|
||||
{
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Currently active loading AsyncOperations.
|
||||
/// </summary>
|
||||
protected List<AsyncOperation> LoadingAsyncOperations = new();
|
||||
/// <summary>
|
||||
/// A collection of scenes used both for loading and unloading.
|
||||
/// </summary>
|
||||
protected List<UnityScene> Scenes = new();
|
||||
/// <summary>
|
||||
/// Current AsyncOperation being processed.
|
||||
/// </summary>
|
||||
protected AsyncOperation CurrentAsyncOperation;
|
||||
/// <summary>
|
||||
/// Last scene to load or begin loading.
|
||||
/// </summary>
|
||||
private UnityScene _lastLoadedScene;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Called when scene loading has begun.
|
||||
/// </summary>
|
||||
public override void LoadStart(LoadQueueData queueData)
|
||||
{
|
||||
base.LoadStart(queueData);
|
||||
ResetValues();
|
||||
}
|
||||
|
||||
public override void LoadEnd(LoadQueueData queueData)
|
||||
{
|
||||
base.LoadEnd(queueData);
|
||||
ResetValues();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets values for a fresh load or unload.
|
||||
/// </summary>
|
||||
private void ResetValues()
|
||||
{
|
||||
CurrentAsyncOperation = null;
|
||||
LoadingAsyncOperations.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when scene unloading has begun within an unload operation.
|
||||
/// </summary>
|
||||
/// <param name = "queueData"></param>
|
||||
public override void UnloadStart(UnloadQueueData queueData)
|
||||
{
|
||||
base.UnloadStart(queueData);
|
||||
Scenes.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin loading a scene using an async method.
|
||||
/// </summary>
|
||||
/// <param name = "sceneName">Scene name to load.</param>
|
||||
public override void BeginLoadAsync(string sceneName, UnityEngine.SceneManagement.LoadSceneParameters parameters)
|
||||
{
|
||||
AsyncOperation ao = UnitySceneManager.LoadSceneAsync(sceneName, parameters);
|
||||
LoadingAsyncOperations.Add(ao);
|
||||
|
||||
_lastLoadedScene = UnitySceneManager.GetSceneAt(UnitySceneManager.sceneCount - 1);
|
||||
|
||||
CurrentAsyncOperation = ao;
|
||||
CurrentAsyncOperation.allowSceneActivation = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Begin unloading a scene using an async method.
|
||||
/// </summary>
|
||||
/// <param name = "sceneName">Scene name to unload.</param>
|
||||
public override void BeginUnloadAsync(UnityScene scene)
|
||||
{
|
||||
CurrentAsyncOperation = UnitySceneManager.UnloadSceneAsync(scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if a scene load or unload percent is done.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override bool IsPercentComplete()
|
||||
{
|
||||
return GetPercentComplete() >= 0.9f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the progress on the current scene load or unload.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override float GetPercentComplete()
|
||||
{
|
||||
return CurrentAsyncOperation == null ? 1f : CurrentAsyncOperation.progress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the scene last loaded by the processor.
|
||||
/// </summary>
|
||||
/// <remarks>This is called after IsPercentComplete returns true.</remarks>
|
||||
public override UnityScene GetLastLoadedScene() => _lastLoadedScene;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a loaded scene.
|
||||
/// </summary>
|
||||
/// <param name = "scene">Scene loaded.</param>
|
||||
public override void AddLoadedScene(UnityScene scene)
|
||||
{
|
||||
base.AddLoadedScene(scene);
|
||||
Scenes.Add(scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns scenes which were loaded during a load operation.
|
||||
/// </summary>
|
||||
public override List<UnityScene> GetLoadedScenes()
|
||||
{
|
||||
return Scenes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates scenes which were loaded.
|
||||
/// </summary>
|
||||
public override void ActivateLoadedScenes()
|
||||
{
|
||||
for (int i = 0; i < LoadingAsyncOperations.Count; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadingAsyncOperations[i].allowSceneActivation = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
SceneManager.NetworkManager.LogError($"An error occured while activating scenes. {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if all asynchronized tasks are considered IsDone.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override IEnumerator AsyncsIsDone()
|
||||
{
|
||||
bool notDone;
|
||||
do
|
||||
{
|
||||
notDone = false;
|
||||
foreach (AsyncOperation ao in LoadingAsyncOperations)
|
||||
{
|
||||
if (!ao.isDone)
|
||||
{
|
||||
notDone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
yield return null;
|
||||
} while (notDone);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c6eacaa60569d947b383df03fff1ea3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Scened/DefaultSceneProcessor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dab62b7547b4c7244ac46c329804de12
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
using FishNet.Connection;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
/// <summary>
|
||||
/// Data container about a scene presence change for a client.
|
||||
/// </summary>
|
||||
public struct ClientPresenceChangeEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Scene on the server which the client's presence has changed.
|
||||
/// </summary>
|
||||
public Scene Scene;
|
||||
/// <summary>
|
||||
/// Connection to client.
|
||||
/// </summary>
|
||||
public NetworkConnection Connection;
|
||||
/// <summary>
|
||||
/// True if the client was added to the scene, false is removed.
|
||||
/// </summary>
|
||||
public bool Added;
|
||||
|
||||
internal ClientPresenceChangeEventArgs(Scene scene, NetworkConnection conn, bool added)
|
||||
{
|
||||
Scene = scene;
|
||||
Connection = conn;
|
||||
Added = added;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa91039d4ab1c6445af72881af122b0a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Scened/Events/ClientPresenceChangeEventArgs.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
/// <summary>
|
||||
/// Data container about a scene load start.
|
||||
/// </summary>
|
||||
public struct SceneLoadStartEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue data used by the current scene action.
|
||||
/// </summary>
|
||||
public readonly LoadQueueData QueueData;
|
||||
|
||||
internal SceneLoadStartEventArgs(LoadQueueData lqd)
|
||||
{
|
||||
QueueData = lqd;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data container about a scene load percent change.
|
||||
/// </summary>
|
||||
public struct SceneLoadPercentEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue data used by the current scene action.
|
||||
/// </summary>
|
||||
public readonly LoadQueueData QueueData;
|
||||
/// <summary>
|
||||
/// Percentage of change completion. 1f is equal to 100% complete.
|
||||
/// </summary>
|
||||
public readonly float Percent;
|
||||
|
||||
internal SceneLoadPercentEventArgs(LoadQueueData lqd, float percent)
|
||||
{
|
||||
QueueData = lqd;
|
||||
Percent = percent;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data container about a scene load end.
|
||||
/// </summary>
|
||||
public struct SceneLoadEndEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue data used by the current scene action.
|
||||
/// </summary>
|
||||
public readonly LoadQueueData QueueData;
|
||||
/// <summary>
|
||||
/// Scenes which were loaded.
|
||||
/// </summary>
|
||||
public readonly Scene[] LoadedScenes;
|
||||
/// <summary>
|
||||
/// Scenes which were skipped because they were already loaded.
|
||||
/// </summary>
|
||||
public readonly string[] SkippedSceneNames;
|
||||
/// <summary>
|
||||
/// Scenes which were unloaded.
|
||||
/// </summary>
|
||||
public readonly string[] UnloadedSceneNames;
|
||||
|
||||
internal SceneLoadEndEventArgs(LoadQueueData lqd, string[] skipped, Scene[] loaded, string[] unloadedSceneNames)
|
||||
{
|
||||
QueueData = lqd;
|
||||
SkippedSceneNames = skipped;
|
||||
LoadedScenes = loaded;
|
||||
UnloadedSceneNames = unloadedSceneNames;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86278568f8087de49b0908f148501993
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Scened/Events/LoadSceneEventArgs.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
/// <summary>
|
||||
/// Data container about a scene unload start.
|
||||
/// </summary>
|
||||
public struct SceneUnloadStartEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue data used by the current scene action.
|
||||
/// </summary>
|
||||
public readonly UnloadQueueData QueueData;
|
||||
|
||||
internal SceneUnloadStartEventArgs(UnloadQueueData sqd)
|
||||
{
|
||||
QueueData = sqd;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Data container about a scene unload end.
|
||||
/// </summary>
|
||||
public struct SceneUnloadEndEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue data used by the current scene action.
|
||||
/// </summary>
|
||||
public readonly UnloadQueueData QueueData;
|
||||
/// <summary>
|
||||
/// Scenes which were successfully unloaded.
|
||||
/// This collection may be populated with empty scenes depending on engine version.
|
||||
/// </summary>
|
||||
[Obsolete("Use UnloadedScenesV2.")] // Remove on V5. Rename UnloadedScenesV2 to UnloadedScenes.
|
||||
public List<Scene> UnloadedScenes;
|
||||
/// <summary>
|
||||
/// Scenes which were successfully unloaded.
|
||||
/// This contains information of the scene unloaded but may not contain scene references as some Unity versions discard that information after a scene is unloaded.
|
||||
/// </summary>
|
||||
public List<UnloadedScene> UnloadedScenesV2;
|
||||
|
||||
internal SceneUnloadEndEventArgs(UnloadQueueData sqd, List<Scene> unloadedScenes, List<UnloadedScene> newUnloadedScenes)
|
||||
{
|
||||
QueueData = sqd;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
UnloadedScenes = unloadedScenes;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
UnloadedScenesV2 = newUnloadedScenes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c24765fea85b564aa331b529f324f92
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Scened/Events/UnloadSceneEventArgs.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64830aa6b410d984a94143eb0c9a057e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace FishNet.Managing.Scened
|
||||
{
|
||||
/// <summary>
|
||||
/// Settings to apply when loading a scene.
|
||||
/// </summary>
|
||||
public class LoadOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// True if to automatically unload the loaded scenes when they are no longer being used by clients. This field only applies to scenes loaded for connections, not globally loaded scenes.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
public bool AutomaticallyUnload = true;
|
||||
/// <summary>
|
||||
/// False if to only load scenes which are not yet loaded. When true a scene may load multiple times; this is known as scene stacking. Only the server is able to stack scenes; clients will load a single instance. Global scenes cannot be stacked.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
public bool AllowStacking;
|
||||
/// <summary>
|
||||
/// LocalPhysics mode to use when loading this scene. Generally this will only be used when applying scene stacking. Only used by the server.
|
||||
/// https://docs.unity3d.com/ScriptReference/SceneManagement.LocalPhysicsMode.html
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
public LocalPhysicsMode LocalPhysics = LocalPhysicsMode.None;
|
||||
/// <summary>
|
||||
/// True to reload a scene if it's already loaded.
|
||||
/// This does not function yet.
|
||||
/// </summary>
|
||||
[System.Obsolete("This feature is not functional yet but will be at a later release.")]
|
||||
public bool ReloadScenes;
|
||||
/// <summary>
|
||||
/// True if scenes should be loaded using addressables. This field only exists for optional use so the user may know if their queue data is using addressables.
|
||||
/// </summary>
|
||||
public bool Addressables;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1614453d3786b2a4eb18b69297da7dc9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Managing/Scened/LoadUnloadDatas/LoadOptions.cs
|
||||
uploadId: 866910
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user