Initial Commit
This commit is contained in:
parent
53eb92e9af
commit
270ab7d11f
15341 changed files with 700234 additions and 0 deletions
|
@ -0,0 +1,956 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using UnityEngine;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using MLAPI.Configuration;
|
||||
using MLAPI.Logging;
|
||||
using MLAPI.Messaging;
|
||||
using MLAPI.NetworkVariable;
|
||||
using MLAPI.Profiling;
|
||||
using MLAPI.Reflection;
|
||||
using MLAPI.Serialization;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Spawning;
|
||||
using MLAPI.Transports;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace MLAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// The base class to override to write network code. Inherits MonoBehaviour
|
||||
/// </summary>
|
||||
public abstract class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal enum __NExec
|
||||
#else
|
||||
[Obsolete("Please do not use, will no longer be exposed in the future versions (framework internal)")]
|
||||
public enum __NExec
|
||||
#endif
|
||||
{
|
||||
None = 0,
|
||||
Server = 1,
|
||||
Client = 2
|
||||
}
|
||||
|
||||
#pragma warning disable 414
|
||||
[NonSerialized]
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal __NExec __nexec = __NExec.None;
|
||||
#else
|
||||
[Obsolete("Please do not use, will no longer be exposed in the future versions (framework internal)")]
|
||||
public __NExec __nexec = __NExec.None;
|
||||
#endif
|
||||
#pragma warning restore 414
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal NetworkSerializer __beginSendServerRpc(ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
|
||||
#else
|
||||
[Obsolete("Please do not use, will no longer be exposed in the future versions (framework internal)")]
|
||||
public NetworkSerializer __beginSendServerRpc(ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
|
||||
#endif
|
||||
{
|
||||
PooledNetworkWriter writer;
|
||||
|
||||
var rpcQueueContainer = NetworkManager.Singleton.RpcQueueContainer;
|
||||
var isUsingBatching = rpcQueueContainer.IsUsingBatching();
|
||||
var transportChannel = rpcDelivery == RpcDelivery.Reliable ? NetworkChannel.ReliableRpc : NetworkChannel.UnreliableRpc;
|
||||
|
||||
if (IsHost)
|
||||
{
|
||||
writer = rpcQueueContainer.BeginAddQueueItemToFrame(RpcQueueContainer.QueueItemType.ServerRpc, Time.realtimeSinceStartup, transportChannel,
|
||||
NetworkManager.Singleton.ServerClientId, null, RpcQueueHistoryFrame.QueueFrameType.Inbound, serverRpcParams.Send.UpdateStage);
|
||||
|
||||
if (!isUsingBatching)
|
||||
{
|
||||
writer.WriteByte(NetworkConstants.SERVER_RPC); // MessageType
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer = rpcQueueContainer.BeginAddQueueItemToFrame(RpcQueueContainer.QueueItemType.ServerRpc, Time.realtimeSinceStartup, transportChannel,
|
||||
NetworkManager.Singleton.ServerClientId, null, RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
|
||||
if (!isUsingBatching)
|
||||
{
|
||||
writer.WriteByte(NetworkConstants.SERVER_RPC); // MessageType
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteUInt64Packed(NetworkObjectId); // NetworkObjectId
|
||||
writer.WriteUInt16Packed(NetworkBehaviourId); // NetworkBehaviourId
|
||||
writer.WriteByte((byte)serverRpcParams.Send.UpdateStage); // NetworkUpdateStage
|
||||
|
||||
return writer.Serializer;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal void __endSendServerRpc(NetworkSerializer serializer, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
|
||||
#else
|
||||
[Obsolete("Please do not use, will no longer be exposed in the future versions (framework internal)")]
|
||||
public void __endSendServerRpc(NetworkSerializer serializer, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
|
||||
#endif
|
||||
{
|
||||
if (serializer == null) return;
|
||||
|
||||
var rpcQueueContainer = NetworkManager.Singleton.RpcQueueContainer;
|
||||
if (IsHost)
|
||||
{
|
||||
rpcQueueContainer.EndAddQueueItemToFrame(serializer.Writer, RpcQueueHistoryFrame.QueueFrameType.Inbound, serverRpcParams.Send.UpdateStage);
|
||||
}
|
||||
else
|
||||
{
|
||||
rpcQueueContainer.EndAddQueueItemToFrame(serializer.Writer, RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal NetworkSerializer __beginSendClientRpc(ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
||||
#else
|
||||
[Obsolete("Please do not use, will no longer be exposed in the future versions (framework internal)")]
|
||||
public NetworkSerializer __beginSendClientRpc(ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
||||
#endif
|
||||
{
|
||||
PooledNetworkWriter writer;
|
||||
|
||||
// This will start a new queue item entry and will then return the writer to the current frame's stream
|
||||
var rpcQueueContainer = NetworkManager.Singleton.RpcQueueContainer;
|
||||
var isUsingBatching = rpcQueueContainer.IsUsingBatching();
|
||||
var transportChannel = rpcDelivery == RpcDelivery.Reliable ? NetworkChannel.ReliableRpc : NetworkChannel.UnreliableRpc;
|
||||
|
||||
ulong[] ClientIds = clientRpcParams.Send.TargetClientIds ?? NetworkManager.Singleton.ConnectedClientsList.Select(c => c.ClientId).ToArray();
|
||||
if (clientRpcParams.Send.TargetClientIds != null && clientRpcParams.Send.TargetClientIds.Length == 0)
|
||||
{
|
||||
ClientIds = NetworkManager.Singleton.ConnectedClientsList.Select(c => c.ClientId).ToArray();
|
||||
}
|
||||
|
||||
//NOTES ON BELOW CHANGES:
|
||||
//The following checks for IsHost and whether the host client id is part of the clients to recieve the RPC
|
||||
//Is part of a patch-fix to handle looping back RPCs into the next frame's inbound queue.
|
||||
//!!! This code is temporary and will change (soon) when NetworkSerializer can be configured for mutliple NetworkWriters!!!
|
||||
var ContainsServerClientId = ClientIds.Contains(NetworkManager.Singleton.ServerClientId);
|
||||
if (IsHost && ContainsServerClientId)
|
||||
{
|
||||
//Always write to the next frame's inbound queue
|
||||
writer = rpcQueueContainer.BeginAddQueueItemToFrame(RpcQueueContainer.QueueItemType.ClientRpc, Time.realtimeSinceStartup, transportChannel,
|
||||
NetworkManager.Singleton.ServerClientId, null, RpcQueueHistoryFrame.QueueFrameType.Inbound, clientRpcParams.Send.UpdateStage);
|
||||
|
||||
//Handle sending to the other clients, if so the above notes explain why this code is here (a temporary patch-fix)
|
||||
if (ClientIds.Length > 1)
|
||||
{
|
||||
//Set the loopback frame
|
||||
rpcQueueContainer.SetLoopBackFrameItem(clientRpcParams.Send.UpdateStage);
|
||||
|
||||
//Switch to the outbound queue
|
||||
writer = rpcQueueContainer.BeginAddQueueItemToFrame(RpcQueueContainer.QueueItemType.ClientRpc, Time.realtimeSinceStartup, transportChannel, NetworkObjectId,
|
||||
ClientIds, RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
|
||||
|
||||
if (!isUsingBatching)
|
||||
{
|
||||
writer.WriteByte(NetworkConstants.CLIENT_RPC); // MessageType
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isUsingBatching)
|
||||
{
|
||||
writer.WriteByte(NetworkConstants.CLIENT_RPC); // MessageType
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer = rpcQueueContainer.BeginAddQueueItemToFrame(RpcQueueContainer.QueueItemType.ClientRpc, Time.realtimeSinceStartup, transportChannel, NetworkObjectId,
|
||||
ClientIds, RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
|
||||
|
||||
if (!isUsingBatching)
|
||||
{
|
||||
writer.WriteByte(NetworkConstants.CLIENT_RPC); // MessageType
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteUInt64Packed(NetworkObjectId); // NetworkObjectId
|
||||
writer.WriteUInt16Packed(NetworkBehaviourId); // NetworkBehaviourId
|
||||
writer.WriteByte((byte)clientRpcParams.Send.UpdateStage); // NetworkUpdateStage
|
||||
|
||||
return writer.Serializer;
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal void __endSendClientRpc(NetworkSerializer serializer, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
||||
#else
|
||||
[Obsolete("Please do not use, will no longer be exposed in the future versions (framework internal)")]
|
||||
public void __endSendClientRpc(NetworkSerializer serializer, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
||||
#endif
|
||||
{
|
||||
if (serializer == null) return;
|
||||
|
||||
var rpcQueueContainer = NetworkManager.Singleton.RpcQueueContainer;
|
||||
|
||||
if (IsHost)
|
||||
{
|
||||
ulong[] ClientIds = clientRpcParams.Send.TargetClientIds ?? NetworkManager.Singleton.ConnectedClientsList.Select(c => c.ClientId).ToArray();
|
||||
if (clientRpcParams.Send.TargetClientIds != null && clientRpcParams.Send.TargetClientIds.Length == 0)
|
||||
{
|
||||
ClientIds = NetworkManager.Singleton.ConnectedClientsList.Select(c => c.ClientId).ToArray();
|
||||
}
|
||||
|
||||
var ContainsServerClientId = ClientIds.Contains(NetworkManager.Singleton.ServerClientId);
|
||||
if (ContainsServerClientId && ClientIds.Length == 1)
|
||||
{
|
||||
rpcQueueContainer.EndAddQueueItemToFrame(serializer.Writer, RpcQueueHistoryFrame.QueueFrameType.Inbound, clientRpcParams.Send.UpdateStage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
rpcQueueContainer.EndAddQueueItemToFrame(serializer.Writer, RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkManager that owns this NetworkBehaviour instance
|
||||
/// </summary>
|
||||
public NetworkManager NetworkManager => NetworkObject.NetworkManager;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is the the personal clients player object
|
||||
/// </summary>
|
||||
public bool IsLocalPlayer => NetworkObject.IsLocalPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is owned by the local player or if the object is the local player object
|
||||
/// </summary>
|
||||
public bool IsOwner => NetworkObject.IsOwner;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as server
|
||||
/// </summary>
|
||||
protected static bool IsServer => IsRunning && NetworkManager.Singleton.IsServer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as client
|
||||
/// </summary>
|
||||
protected bool IsClient => IsRunning && NetworkManager.Singleton.IsClient;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if we are executing as Host, I.E Server and Client
|
||||
/// </summary>
|
||||
protected bool IsHost => IsRunning && NetworkManager.Singleton.IsHost;
|
||||
|
||||
private static bool IsRunning => NetworkManager.Singleton != null && NetworkManager.Singleton.IsListening;
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not the object has a owner
|
||||
/// </summary>
|
||||
public bool IsOwnedByServer => NetworkObject.IsOwnedByServer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkObject that owns this NetworkBehaviour instance
|
||||
/// </summary>
|
||||
public NetworkObject NetworkObject
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_NetworkObject == null)
|
||||
{
|
||||
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
||||
}
|
||||
|
||||
if (m_NetworkObject == null)
|
||||
{
|
||||
throw new NullReferenceException($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
|
||||
}
|
||||
|
||||
return m_NetworkObject;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not this NetworkBehaviour instance has a NetworkObject owner.
|
||||
/// </summary>
|
||||
public bool HasNetworkObject
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_NetworkObject == null)
|
||||
{
|
||||
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
||||
}
|
||||
|
||||
return m_NetworkObject != null;
|
||||
}
|
||||
}
|
||||
|
||||
private NetworkObject m_NetworkObject = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour
|
||||
/// </summary>
|
||||
public ulong NetworkObjectId => NetworkObject.NetworkObjectId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject
|
||||
/// </summary>
|
||||
public ushort NetworkBehaviourId => NetworkObject.GetNetworkBehaviourOrderIndex(this);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a the NetworkBehaviour with a given BehaviourId for the current NetworkObject
|
||||
/// </summary>
|
||||
/// <param name="behaviourId">The behaviourId to return</param>
|
||||
/// <returns>Returns NetworkBehaviour with given behaviourId</returns>
|
||||
protected NetworkBehaviour GetNetworkBehaviour(ushort behaviourId)
|
||||
{
|
||||
return NetworkObject.GetNetworkBehaviourAtOrderIndex(behaviourId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ClientId that owns the NetworkObject
|
||||
/// </summary>
|
||||
public ulong OwnerClientId => NetworkObject.OwnerClientId;
|
||||
|
||||
internal bool NetworkStartInvoked = false;
|
||||
internal bool InternalNetworkStartInvoked = false;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the network tick at the NetworkBehaviourUpdate time
|
||||
/// This allows sending NetworkVariables not more often than once per network tick, regardless of the update rate
|
||||
/// </summary>
|
||||
public static ushort CurrentTick { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when message handlers are ready to be registered and the network is setup
|
||||
/// </summary>
|
||||
public virtual void NetworkStart() { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when message handlers are ready to be registered and the network is setup. Provides a Payload if it was provided
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream containing the spawn payload</param>
|
||||
public virtual void NetworkStart(Stream stream)
|
||||
{
|
||||
NetworkStart();
|
||||
}
|
||||
|
||||
internal void InternalNetworkStart()
|
||||
{
|
||||
InitializeVariables();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when the local client gains ownership of this object
|
||||
/// </summary>
|
||||
public virtual void OnGainedOwnership() { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets called when we loose ownership of this object
|
||||
/// </summary>
|
||||
public virtual void OnLostOwnership() { }
|
||||
|
||||
#region NetworkVariable
|
||||
|
||||
private bool m_VarInit = false;
|
||||
|
||||
private readonly List<HashSet<int>> m_ChannelMappedNetworkVariableIndexes = new List<HashSet<int>>();
|
||||
private readonly List<NetworkChannel> m_ChannelsForNetworkVariableGroups = new List<NetworkChannel>();
|
||||
internal readonly List<INetworkVariable> NetworkVariableFields = new List<INetworkVariable>();
|
||||
|
||||
private static HashSet<NetworkObject> s_Touched = new HashSet<NetworkObject>();
|
||||
private static Dictionary<Type, FieldInfo[]> s_FieldTypes = new Dictionary<Type, FieldInfo[]>();
|
||||
|
||||
private static FieldInfo[] GetFieldInfoForType(Type type)
|
||||
{
|
||||
if (!s_FieldTypes.ContainsKey(type))
|
||||
{
|
||||
s_FieldTypes.Add(type, GetFieldInfoForTypeRecursive(type));
|
||||
}
|
||||
|
||||
return s_FieldTypes[type];
|
||||
}
|
||||
|
||||
private static FieldInfo[] GetFieldInfoForTypeRecursive(Type type, List<FieldInfo> list = null)
|
||||
{
|
||||
if (list == null)
|
||||
{
|
||||
list = new List<FieldInfo>();
|
||||
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
}
|
||||
else
|
||||
{
|
||||
list.AddRange(type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
}
|
||||
|
||||
if (type.BaseType != null && type.BaseType != typeof(NetworkBehaviour))
|
||||
{
|
||||
return GetFieldInfoForTypeRecursive(type.BaseType, list);
|
||||
}
|
||||
|
||||
return list.OrderBy(x => x.Name, StringComparer.Ordinal).ToArray();
|
||||
}
|
||||
|
||||
internal void InitializeVariables()
|
||||
{
|
||||
if (m_VarInit) return;
|
||||
m_VarInit = true;
|
||||
|
||||
FieldInfo[] sortedFields = GetFieldInfoForType(GetType());
|
||||
|
||||
for (int i = 0; i < sortedFields.Length; i++)
|
||||
{
|
||||
Type fieldType = sortedFields[i].FieldType;
|
||||
|
||||
if (fieldType.HasInterface(typeof(INetworkVariable)))
|
||||
{
|
||||
INetworkVariable instance = (INetworkVariable)sortedFields[i].GetValue(this);
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
instance = (INetworkVariable)Activator.CreateInstance(fieldType, true);
|
||||
sortedFields[i].SetValue(this, instance);
|
||||
}
|
||||
|
||||
instance.SetNetworkBehaviour(this);
|
||||
NetworkVariableFields.Add(instance);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Create index map for channels
|
||||
var firstLevelIndex = new Dictionary<NetworkChannel, int>();
|
||||
int secondLevelCounter = 0;
|
||||
|
||||
for (int i = 0; i < NetworkVariableFields.Count; i++)
|
||||
{
|
||||
NetworkChannel networkChannel = NetworkVariableFields[i].GetChannel();
|
||||
|
||||
if (!firstLevelIndex.ContainsKey(networkChannel))
|
||||
{
|
||||
firstLevelIndex.Add(networkChannel, secondLevelCounter);
|
||||
m_ChannelsForNetworkVariableGroups.Add(networkChannel);
|
||||
secondLevelCounter++;
|
||||
}
|
||||
|
||||
if (firstLevelIndex[networkChannel] >= m_ChannelMappedNetworkVariableIndexes.Count)
|
||||
{
|
||||
m_ChannelMappedNetworkVariableIndexes.Add(new HashSet<int>());
|
||||
}
|
||||
|
||||
m_ChannelMappedNetworkVariableIndexes[firstLevelIndex[networkChannel]].Add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
private static ProfilerMarker s_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}");
|
||||
#endif
|
||||
|
||||
internal static void NetworkBehaviourUpdate()
|
||||
{
|
||||
// Do not execute NetworkBehaviourUpdate more than once per network tick
|
||||
ushort tick = NetworkManager.Singleton.NetworkTickSystem.GetTick();
|
||||
if (tick == CurrentTick)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentTick = tick;
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_NetworkBehaviourUpdate.Begin();
|
||||
#endif
|
||||
try
|
||||
{
|
||||
if (IsServer)
|
||||
{
|
||||
s_Touched.Clear();
|
||||
for (int i = 0; i < NetworkManager.Singleton.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
var client = NetworkManager.Singleton.ConnectedClientsList[i];
|
||||
var spawnedObjs = NetworkSpawnManager.SpawnedObjectsList;
|
||||
s_Touched.UnionWith(spawnedObjs);
|
||||
foreach (var sobj in spawnedObjs)
|
||||
{
|
||||
// Sync just the variables for just the objects this client sees
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now, reset all the no-longer-dirty variables
|
||||
foreach (var sobj in s_Touched)
|
||||
{
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// when client updates the sever, it tells it about all its objects
|
||||
foreach (var sobj in NetworkSpawnManager.SpawnedObjectsList)
|
||||
{
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].VariableUpdate(NetworkManager.Singleton.ServerClientId);
|
||||
}
|
||||
}
|
||||
|
||||
// Now, reset all the no-longer-dirty variables
|
||||
foreach (var sobj in NetworkSpawnManager.SpawnedObjectsList)
|
||||
{
|
||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||
{
|
||||
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_NetworkBehaviourUpdate.End();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal void PreNetworkVariableWrite()
|
||||
{
|
||||
// reset our "which variables got written" data
|
||||
m_NetworkVariableIndexesToReset.Clear();
|
||||
m_NetworkVariableIndexesToResetSet.Clear();
|
||||
}
|
||||
|
||||
internal void PostNetworkVariableWrite()
|
||||
{
|
||||
// mark any variables we wrote as no longer dirty
|
||||
for (int i = 0; i < m_NetworkVariableIndexesToReset.Count; i++)
|
||||
{
|
||||
NetworkVariableFields[m_NetworkVariableIndexesToReset[i]].ResetDirty();
|
||||
}
|
||||
}
|
||||
|
||||
internal void VariableUpdate(ulong clientId)
|
||||
{
|
||||
if (!m_VarInit) InitializeVariables();
|
||||
|
||||
PreNetworkVariableWrite();
|
||||
NetworkVariableUpdate(clientId);
|
||||
}
|
||||
|
||||
private readonly List<int> m_NetworkVariableIndexesToReset = new List<int>();
|
||||
private readonly HashSet<int> m_NetworkVariableIndexesToResetSet = new HashSet<int>();
|
||||
|
||||
private void NetworkVariableUpdate(ulong clientId)
|
||||
{
|
||||
if (!CouldHaveDirtyNetworkVariables()) return;
|
||||
|
||||
for (int j = 0; j < m_ChannelMappedNetworkVariableIndexes.Count; j++)
|
||||
{
|
||||
using (var buffer = PooledNetworkBuffer.Get())
|
||||
{
|
||||
using (var writer = PooledNetworkWriter.Get(buffer))
|
||||
{
|
||||
writer.WriteUInt64Packed(NetworkObjectId);
|
||||
writer.WriteUInt16Packed(NetworkObject.GetNetworkBehaviourOrderIndex(this));
|
||||
|
||||
// Write the current tick frame
|
||||
// todo: this is currently done per channel, per tick. The snapshot system might improve on this
|
||||
writer.WriteUInt16Packed(CurrentTick);
|
||||
|
||||
bool writtenAny = false;
|
||||
for (int k = 0; k < NetworkVariableFields.Count; k++)
|
||||
{
|
||||
if (!m_ChannelMappedNetworkVariableIndexes[j].Contains(k))
|
||||
{
|
||||
// This var does not belong to the currently iterating channel group.
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
writer.WriteUInt16Packed(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteBool(false);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isDirty = NetworkVariableFields[k].IsDirty(); // cache this here. You never know what operations users will do in the dirty methods
|
||||
|
||||
// if I'm dirty AND a client, write (server always has all permissions)
|
||||
// if I'm dirty AND the server AND the client can read me, send.
|
||||
bool shouldWrite = isDirty && (!IsServer || NetworkVariableFields[k].CanClientRead(clientId));
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (!shouldWrite)
|
||||
{
|
||||
writer.WriteUInt16Packed(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteBool(shouldWrite);
|
||||
}
|
||||
|
||||
if (shouldWrite)
|
||||
{
|
||||
writtenAny = true;
|
||||
|
||||
// write the network tick at which this NetworkVariable was modified remotely
|
||||
// this will allow lag-compensation
|
||||
writer.WriteUInt16Packed(NetworkVariableFields[k].RemoteTick);
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
using (var varBuffer = PooledNetworkBuffer.Get())
|
||||
{
|
||||
NetworkVariableFields[k].WriteDelta(varBuffer);
|
||||
varBuffer.PadBuffer();
|
||||
|
||||
writer.WriteUInt16Packed((ushort)varBuffer.Length);
|
||||
buffer.CopyFrom(varBuffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkVariableFields[k].WriteDelta(buffer);
|
||||
}
|
||||
|
||||
if (!m_NetworkVariableIndexesToResetSet.Contains(k))
|
||||
{
|
||||
m_NetworkVariableIndexesToResetSet.Add(k);
|
||||
m_NetworkVariableIndexesToReset.Add(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (writtenAny)
|
||||
{
|
||||
InternalMessageSender.Send(clientId, NetworkConstants.NETWORK_VARIABLE_DELTA, m_ChannelsForNetworkVariableGroups[j], buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CouldHaveDirtyNetworkVariables()
|
||||
{
|
||||
// TODO: There should be a better way by reading one dirty variable vs. 'n'
|
||||
for (int i = 0; i < NetworkVariableFields.Count; i++)
|
||||
{
|
||||
if (NetworkVariableFields[i].IsDirty()) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static void HandleNetworkVariableDeltas(List<INetworkVariable> networkVariableList, Stream stream, ulong clientId, NetworkBehaviour logInstance)
|
||||
{
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
// read the remote network tick at which this variable was written.
|
||||
ushort remoteTick = reader.ReadUInt16Packed();
|
||||
|
||||
for (int i = 0; i < networkVariableList.Count; i++)
|
||||
{
|
||||
ushort varSize = 0;
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
varSize = reader.ReadUInt16Packed();
|
||||
|
||||
if (varSize == 0) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!reader.ReadBool()) continue;
|
||||
}
|
||||
|
||||
if (IsServer && !networkVariableList[i].CanClientWrite(clientId))
|
||||
{
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Client wrote to {nameof(NetworkVariable)} without permission. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
NetworkLog.LogError($"[{networkVariableList[i].GetType().Name}]");
|
||||
}
|
||||
|
||||
stream.Position += varSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
//This client wrote somewhere they are not allowed. This is critical
|
||||
//We can't just skip this field. Because we don't actually know how to dummy read
|
||||
//That is, we don't know how many bytes to skip. Because the interface doesn't have a
|
||||
//Read that gives us the value. Only a Read that applies the value straight away
|
||||
//A dummy read COULD be added to the interface for this situation, but it's just being too nice.
|
||||
//This is after all a developer fault. A critical error should be fine.
|
||||
// - TwoTen
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogError($"Client wrote to {nameof(NetworkVariable)} without permission. No more variables can be read. This is critical. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
NetworkLog.LogError($"[{networkVariableList[i].GetType().Name}]");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// read the local network tick at which this variable was written.
|
||||
// if this var was updated from our machine, this local tick will be locally valid
|
||||
ushort localTick = reader.ReadUInt16Packed();
|
||||
|
||||
long readStartPos = stream.Position;
|
||||
|
||||
networkVariableList[i].ReadDelta(stream, IsServer, localTick, remoteTick);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.NetworkVarDeltas);
|
||||
|
||||
ProfilerStatManager.NetworkVarsRcvd.Record();
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
(stream as NetworkBuffer).SkipPadBits();
|
||||
|
||||
if (stream.Position > (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var delta read too far. {stream.Position - (readStartPos + varSize)} bytes. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
}
|
||||
|
||||
stream.Position = readStartPos + varSize;
|
||||
}
|
||||
else if (stream.Position < (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var delta read too little. {(readStartPos + varSize) - stream.Position} bytes. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
}
|
||||
|
||||
stream.Position = readStartPos + varSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void HandleNetworkVariableUpdate(List<INetworkVariable> networkVariableList, Stream stream, ulong clientId, NetworkBehaviour logInstance)
|
||||
{
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
for (int i = 0; i < networkVariableList.Count; i++)
|
||||
{
|
||||
ushort varSize = 0;
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
varSize = reader.ReadUInt16Packed();
|
||||
|
||||
if (varSize == 0) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!reader.ReadBool()) continue;
|
||||
}
|
||||
|
||||
if (IsServer && !networkVariableList[i].CanClientWrite(clientId))
|
||||
{
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Client wrote to {nameof(NetworkVariable)} without permission. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
}
|
||||
|
||||
stream.Position += varSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
//This client wrote somewhere they are not allowed. This is critical
|
||||
//We can't just skip this field. Because we don't actually know how to dummy read
|
||||
//That is, we don't know how many bytes to skip. Because the interface doesn't have a
|
||||
//Read that gives us the value. Only a Read that applies the value straight away
|
||||
//A dummy read COULD be added to the interface for this situation, but it's just being too nice.
|
||||
//This is after all a developer fault. A critical error should be fine.
|
||||
// - TwoTen
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogError($"Client wrote to {nameof(NetworkVariable)} without permission. No more variables can be read. This is critical. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
long readStartPos = stream.Position;
|
||||
|
||||
networkVariableList[i].ReadField(stream, NetworkTickSystem.NoTick, NetworkTickSystem.NoTick);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.NetworkVarUpdates);
|
||||
|
||||
ProfilerStatManager.NetworkVarsRcvd.Record();
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (stream is NetworkBuffer networkBuffer)
|
||||
{
|
||||
networkBuffer.SkipPadBits();
|
||||
}
|
||||
|
||||
if (stream.Position > (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var update read too far. {stream.Position - (readStartPos + varSize)} bytes. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
}
|
||||
|
||||
stream.Position = readStartPos + varSize;
|
||||
}
|
||||
else if (stream.Position < (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var update read too little. {(readStartPos + varSize) - stream.Position} bytes. => {(logInstance != null ? ($"{nameof(NetworkObjectId)}: {logInstance.NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {logInstance.NetworkObject.GetNetworkBehaviourOrderIndex(logInstance)} - VariableIndex: {i}") : string.Empty)}");
|
||||
}
|
||||
|
||||
stream.Position = readStartPos + varSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal static void WriteNetworkVariableData(List<INetworkVariable> networkVariableList, Stream stream, ulong clientId)
|
||||
{
|
||||
if (networkVariableList.Count == 0) return;
|
||||
|
||||
using (var writer = PooledNetworkWriter.Get(stream))
|
||||
{
|
||||
for (int j = 0; j < networkVariableList.Count; j++)
|
||||
{
|
||||
bool canClientRead = networkVariableList[j].CanClientRead(clientId);
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (!canClientRead)
|
||||
{
|
||||
writer.WriteUInt16Packed(0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteBool(canClientRead);
|
||||
}
|
||||
|
||||
if (canClientRead)
|
||||
{
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
using (var varBuffer = PooledNetworkBuffer.Get())
|
||||
{
|
||||
networkVariableList[j].WriteField(varBuffer);
|
||||
varBuffer.PadBuffer();
|
||||
|
||||
writer.WriteUInt16Packed((ushort)varBuffer.Length);
|
||||
varBuffer.CopyTo(stream);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
networkVariableList[j].WriteField(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SetNetworkVariableData(List<INetworkVariable> networkVariableList, Stream stream)
|
||||
{
|
||||
if (networkVariableList.Count == 0) return;
|
||||
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
for (int j = 0; j < networkVariableList.Count; j++)
|
||||
{
|
||||
ushort varSize = 0;
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
varSize = reader.ReadUInt16Packed();
|
||||
|
||||
if (varSize == 0) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!reader.ReadBool()) continue;
|
||||
}
|
||||
|
||||
long readStartPos = stream.Position;
|
||||
|
||||
networkVariableList[j].ReadField(stream, NetworkTickSystem.NoTick, NetworkTickSystem.NoTick);
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
||||
{
|
||||
if (stream is NetworkBuffer networkBuffer)
|
||||
{
|
||||
networkBuffer.SkipPadBits();
|
||||
}
|
||||
|
||||
if (stream.Position > (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var data read too far. {stream.Position - (readStartPos + varSize)} bytes.");
|
||||
}
|
||||
|
||||
stream.Position = readStartPos + varSize;
|
||||
}
|
||||
else if (stream.Position < (readStartPos + varSize))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"Var data read too little. {(readStartPos + varSize) - stream.Position} bytes.");
|
||||
}
|
||||
|
||||
stream.Position = readStartPos + varSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local instance of a object with a given NetworkId
|
||||
/// </summary>
|
||||
/// <param name="networkId"></param>
|
||||
/// <returns></returns>
|
||||
protected NetworkObject GetNetworkObject(ulong networkId) => NetworkSpawnManager.SpawnedObjects.ContainsKey(networkId) ? NetworkSpawnManager.SpawnedObjects[networkId] : null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c8ea6ec00590bd44a983c228bcaee727
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 593a2fe42fa9d37498c96f9a383b6521
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,571 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using MLAPI.Configuration;
|
||||
using MLAPI.Exceptions;
|
||||
using MLAPI.Hashing;
|
||||
using MLAPI.Logging;
|
||||
using MLAPI.Messaging;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Spawning;
|
||||
using MLAPI.Transports;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MLAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// A component used to identify that a GameObject in the network
|
||||
/// </summary>
|
||||
[AddComponentMenu("MLAPI/NetworkObject", -99)]
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class NetworkObject : MonoBehaviour
|
||||
{
|
||||
private void OnValidate()
|
||||
{
|
||||
// Set this so the hash can be serialized on Scene objects. For prefabs, they are generated at runtime.
|
||||
ValidateHash();
|
||||
}
|
||||
|
||||
internal void ValidateHash()
|
||||
{
|
||||
if (string.IsNullOrEmpty(PrefabHashGenerator))
|
||||
{
|
||||
PrefabHashGenerator = gameObject.name;
|
||||
}
|
||||
|
||||
PrefabHash = PrefabHashGenerator.GetStableHash64();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetworkManager that owns this NetworkObject instance
|
||||
/// </summary>
|
||||
public NetworkManager NetworkManager => NetworkManager.Singleton;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique Id of this object that is synced across the network
|
||||
/// </summary>
|
||||
public ulong NetworkObjectId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ClientId of the owner of this NetworkObject
|
||||
/// </summary>
|
||||
public ulong OwnerClientId
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OwnerClientIdInternal == null)
|
||||
{
|
||||
return NetworkManager.Singleton != null ? NetworkManager.Singleton.ServerClientId : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return OwnerClientIdInternal.Value;
|
||||
}
|
||||
}
|
||||
internal set
|
||||
{
|
||||
if (NetworkManager.Singleton != null && value == NetworkManager.Singleton.ServerClientId)
|
||||
{
|
||||
OwnerClientIdInternal = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
OwnerClientIdInternal = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal ulong? OwnerClientIdInternal = null;
|
||||
|
||||
/// <summary>
|
||||
/// InstanceId is the id that is unique to the object and scene for a scene object when UsePrefabSync is false.
|
||||
/// If UsePrefabSync is true or if it's used on non scene objects, this has no effect.
|
||||
/// Should not be set manually
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
[SerializeField]
|
||||
public ulong NetworkInstanceId;
|
||||
|
||||
/// <summary>
|
||||
/// The Prefab unique hash. This should not be set my the user but rather changed by editing the PrefabHashGenerator.
|
||||
/// It has to be the same for all instances of a prefab
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
[SerializeField]
|
||||
public ulong PrefabHash;
|
||||
|
||||
/// <summary>
|
||||
/// The generator used to change the PrefabHash. This should be set the same for all instances of a prefab.
|
||||
/// It has to be unique in relation to other prefabs
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public string PrefabHashGenerator;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the object will always be replicated as root on clients and the parent will be ignored.
|
||||
/// </summary>
|
||||
public bool AlwaysReplicateAsRoot;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if this object is a player object
|
||||
/// </summary>
|
||||
public bool IsPlayerObject { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is the the personal clients player object
|
||||
/// </summary>
|
||||
public bool IsLocalPlayer => NetworkManager.Singleton != null && IsPlayerObject && OwnerClientId == NetworkManager.Singleton.LocalClientId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is owned by the local player or if the object is the local player object
|
||||
/// </summary>
|
||||
public bool IsOwner => NetworkManager.Singleton != null && OwnerClientId == NetworkManager.Singleton.LocalClientId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not the object is owned by anyone
|
||||
/// </summary>
|
||||
public bool IsOwnedByServer => NetworkManager.Singleton != null && OwnerClientId == NetworkManager.Singleton.ServerClientId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object has yet been spawned across the network
|
||||
/// </summary>
|
||||
public bool IsSpawned { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets if the object is a SceneObject, null if it's not yet spawned but is a scene object.
|
||||
/// </summary>
|
||||
public bool? IsSceneObject { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not the object should be automatically removed when the scene is unloaded.
|
||||
/// </summary>
|
||||
public bool DestroyWithScene { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for checking visibility
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId to check visibility for</param>
|
||||
public delegate bool VisibilityDelegate(ulong clientId);
|
||||
|
||||
/// <summary>
|
||||
/// Delegate invoked when the MLAPI needs to know if the object should be visible to a client, if null it will assume true
|
||||
/// </summary>
|
||||
public VisibilityDelegate CheckObjectVisibility = null;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for checking spawn options
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId to check spawn options for</param>
|
||||
public delegate bool SpawnDelegate(ulong clientId);
|
||||
|
||||
/// <summary>
|
||||
/// Delegate invoked when the MLAPI needs to know if it should include the transform when spawning the object, if null it will assume true
|
||||
/// </summary>
|
||||
public SpawnDelegate IncludeTransformWhenSpawning = null;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to destroy this object if it's owner is destroyed.
|
||||
/// If false, the objects ownership will be given to the server.
|
||||
/// </summary>
|
||||
public bool DontDestroyWithOwner;
|
||||
|
||||
internal readonly HashSet<ulong> m_Observers = new HashSet<ulong>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns Observers enumerator
|
||||
/// </summary>
|
||||
/// <returns>Observers enumerator</returns>
|
||||
public HashSet<ulong>.Enumerator GetObservers()
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
return m_Observers.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this object is visible to a specific client
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId of the client</param>
|
||||
/// <returns>True if the client knows about the object</returns>
|
||||
public bool IsNetworkVisibleTo(ulong clientId)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
return m_Observers.Contains(clientId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a previously hidden object to a client
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client to show the object to</param>
|
||||
/// <param name="payload">An optional payload to send as part of the spawn</param>
|
||||
public void NetworkShow(ulong clientId, Stream payload = null)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
if (!NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
throw new NotServerException("Only server can change visibility");
|
||||
}
|
||||
|
||||
if (m_Observers.Contains(clientId))
|
||||
{
|
||||
throw new VisibilityChangeException("The object is already visible");
|
||||
}
|
||||
|
||||
// Send spawn call
|
||||
m_Observers.Add(clientId);
|
||||
|
||||
NetworkSpawnManager.SendSpawnCallForObject(clientId, this, payload);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shows a list of previously hidden objects to a client
|
||||
/// </summary>
|
||||
/// <param name="networkObjects">The objects to show</param>
|
||||
/// <param name="clientId">The client to show the objects to</param>
|
||||
/// <param name="payload">An optional payload to send as part of the spawns</param>
|
||||
public static void NetworkShow(List<NetworkObject> networkObjects, ulong clientId, Stream payload = null)
|
||||
{
|
||||
if (!NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
throw new NotServerException("Only server can change visibility");
|
||||
}
|
||||
|
||||
// Do the safety loop first to prevent putting the MLAPI in an invalid state.
|
||||
for (int i = 0; i < networkObjects.Count; i++)
|
||||
{
|
||||
if (!networkObjects[i].IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
if (networkObjects[i].m_Observers.Contains(clientId))
|
||||
{
|
||||
throw new VisibilityChangeException($"{nameof(NetworkObject)} with NetworkId: {networkObjects[i].NetworkObjectId} is already visible");
|
||||
}
|
||||
}
|
||||
|
||||
using (var buffer = PooledNetworkBuffer.Get())
|
||||
using (var writer = PooledNetworkWriter.Get(buffer))
|
||||
{
|
||||
writer.WriteUInt16Packed((ushort)networkObjects.Count);
|
||||
|
||||
for (int i = 0; i < networkObjects.Count; i++)
|
||||
{
|
||||
// Send spawn call
|
||||
networkObjects[i].m_Observers.Add(clientId);
|
||||
|
||||
NetworkSpawnManager.WriteSpawnCallForObject(buffer, clientId, networkObjects[i], payload);
|
||||
}
|
||||
|
||||
InternalMessageSender.Send(clientId, NetworkConstants.ADD_OBJECTS, NetworkChannel.Internal, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides a object from a specific client
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client to hide the object for</param>
|
||||
public void NetworkHide(ulong clientId)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
if (!NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
throw new NotServerException("Only server can change visibility");
|
||||
}
|
||||
|
||||
if (!m_Observers.Contains(clientId))
|
||||
{
|
||||
throw new VisibilityChangeException("The object is already hidden");
|
||||
}
|
||||
|
||||
if (clientId == NetworkManager.Singleton.ServerClientId)
|
||||
{
|
||||
throw new VisibilityChangeException("Cannot hide an object from the server");
|
||||
}
|
||||
|
||||
|
||||
// Send destroy call
|
||||
m_Observers.Remove(clientId);
|
||||
|
||||
using (var buffer = PooledNetworkBuffer.Get())
|
||||
using (var writer = PooledNetworkWriter.Get(buffer))
|
||||
{
|
||||
writer.WriteUInt64Packed(NetworkObjectId);
|
||||
|
||||
InternalMessageSender.Send(clientId, NetworkConstants.DESTROY_OBJECT, NetworkChannel.Internal, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides a list of objects from a client
|
||||
/// </summary>
|
||||
/// <param name="networkObjects">The objects to hide</param>
|
||||
/// <param name="clientId">The client to hide the objects from</param>
|
||||
public static void NetworkHide(List<NetworkObject> networkObjects, ulong clientId)
|
||||
{
|
||||
if (!NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
throw new NotServerException("Only server can change visibility");
|
||||
}
|
||||
|
||||
if (clientId == NetworkManager.Singleton.ServerClientId)
|
||||
{
|
||||
throw new VisibilityChangeException("Cannot hide an object from the server");
|
||||
}
|
||||
|
||||
// Do the safety loop first to prevent putting the MLAPI in an invalid state.
|
||||
for (int i = 0; i < networkObjects.Count; i++)
|
||||
{
|
||||
if (!networkObjects[i].IsSpawned)
|
||||
{
|
||||
throw new SpawnStateException("Object is not spawned");
|
||||
}
|
||||
|
||||
if (!networkObjects[i].m_Observers.Contains(clientId))
|
||||
{
|
||||
throw new VisibilityChangeException($"{nameof(NetworkObject)} with {nameof(NetworkObjectId)}: {networkObjects[i].NetworkObjectId} is already hidden");
|
||||
}
|
||||
}
|
||||
|
||||
using (var buffer = PooledNetworkBuffer.Get())
|
||||
using (var writer = PooledNetworkWriter.Get(buffer))
|
||||
{
|
||||
writer.WriteUInt16Packed((ushort)networkObjects.Count);
|
||||
|
||||
for (int i = 0; i < networkObjects.Count; i++)
|
||||
{
|
||||
// Send destroy call
|
||||
networkObjects[i].m_Observers.Remove(clientId);
|
||||
|
||||
writer.WriteUInt64Packed(networkObjects[i].NetworkObjectId);
|
||||
}
|
||||
|
||||
InternalMessageSender.Send(clientId, NetworkConstants.DESTROY_OBJECTS, NetworkChannel.Internal, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (NetworkManager.Singleton != null)
|
||||
{
|
||||
NetworkSpawnManager.OnDestroyObject(NetworkObjectId, false);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SpawnInternal(Stream spawnPayload, bool destroyWithScene , ulong? ownerClientId, bool playerObject)
|
||||
{
|
||||
if (!NetworkManager.Singleton.IsListening)
|
||||
{
|
||||
throw new NotListeningException($"{nameof(NetworkManager)} isn't listening, start a server or host before spawning objects.");
|
||||
}
|
||||
|
||||
if (!NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
throw new NotServerException($"Only server can spawn {nameof(NetworkObject)}s");
|
||||
}
|
||||
|
||||
if (spawnPayload != null) spawnPayload.Position = 0;
|
||||
|
||||
NetworkSpawnManager.SpawnNetworkObjectLocally(this, NetworkSpawnManager.GetNetworkObjectId(), false, playerObject, ownerClientId, spawnPayload, spawnPayload != null, spawnPayload == null ? 0 : (int)spawnPayload.Length, false, destroyWithScene);
|
||||
|
||||
for (int i = 0; i < NetworkManager.Singleton.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
if (m_Observers.Contains(NetworkManager.Singleton.ConnectedClientsList[i].ClientId))
|
||||
{
|
||||
NetworkSpawnManager.SendSpawnCallForObject(NetworkManager.Singleton.ConnectedClientsList[i].ClientId, this, spawnPayload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns this GameObject across the network. Can only be called from the Server
|
||||
/// </summary>
|
||||
/// <param name="spawnPayload">The writer containing the spawn payload</param>
|
||||
/// <param name="destroyWithScene">Should the object be destroyd when the scene is changed</param>
|
||||
public void Spawn(Stream spawnPayload = null, bool destroyWithScene = false)
|
||||
{
|
||||
SpawnInternal(spawnPayload, destroyWithScene, null, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an object across the network with a given owner. Can only be called from server
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId to own the object</param>
|
||||
/// <param name="spawnPayload">The writer containing the spawn payload</param>
|
||||
/// <param name="destroyWithScene">Should the object be destroyd when the scene is changed</param>
|
||||
public void SpawnWithOwnership(ulong clientId, Stream spawnPayload = null, bool destroyWithScene = false)
|
||||
{
|
||||
SpawnInternal(spawnPayload, destroyWithScene, clientId, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an object across the network and makes it the player object for the given client
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId whos player object this is</param>
|
||||
/// <param name="spawnPayload">The writer containing the spawn payload</param>
|
||||
/// <param name="destroyWithScene">Should the object be destroyd when the scene is changed</param>
|
||||
public void SpawnAsPlayerObject(ulong clientId, Stream spawnPayload = null, bool destroyWithScene = false)
|
||||
{
|
||||
SpawnInternal(spawnPayload, destroyWithScene, clientId, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns this GameObject and destroys it for other clients. This should be used if the object should be kept on the server
|
||||
/// </summary>
|
||||
public void Despawn(bool destroy = false)
|
||||
{
|
||||
NetworkSpawnManager.DespawnObject(this, destroy);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes all ownership of an object from any client. Can only be called from server
|
||||
/// </summary>
|
||||
public void RemoveOwnership()
|
||||
{
|
||||
NetworkSpawnManager.RemoveOwnership(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the owner of the object. Can only be called from server
|
||||
/// </summary>
|
||||
/// <param name="newOwnerClientId">The new owner clientId</param>
|
||||
public void ChangeOwnership(ulong newOwnerClientId)
|
||||
{
|
||||
NetworkSpawnManager.ChangeOwnership(this, newOwnerClientId);
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourOnLostOwnership()
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].OnLostOwnership();
|
||||
}
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourOnGainedOwnership()
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].OnGainedOwnership();
|
||||
}
|
||||
}
|
||||
|
||||
internal void ResetNetworkStartInvoked()
|
||||
{
|
||||
if (ChildNetworkBehaviours != null)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].NetworkStartInvoked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void InvokeBehaviourNetworkSpawn(Stream stream)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
//We check if we are it's NetworkObject owner incase a NetworkObject exists as a child of our NetworkObject
|
||||
if (!ChildNetworkBehaviours[i].NetworkStartInvoked)
|
||||
{
|
||||
if (!ChildNetworkBehaviours[i].InternalNetworkStartInvoked)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InternalNetworkStart();
|
||||
ChildNetworkBehaviours[i].InternalNetworkStartInvoked = true;
|
||||
}
|
||||
|
||||
ChildNetworkBehaviours[i].NetworkStart(stream);
|
||||
ChildNetworkBehaviours[i].NetworkStartInvoked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<NetworkBehaviour> m_ChildNetworkBehaviours;
|
||||
|
||||
internal List<NetworkBehaviour> ChildNetworkBehaviours
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ChildNetworkBehaviours != null)
|
||||
{
|
||||
return m_ChildNetworkBehaviours;
|
||||
}
|
||||
|
||||
m_ChildNetworkBehaviours = new List<NetworkBehaviour>();
|
||||
var networkBehaviours = GetComponentsInChildren<NetworkBehaviour>(true);
|
||||
for (int i = 0; i < networkBehaviours.Length; i++)
|
||||
{
|
||||
if (networkBehaviours[i].NetworkObject == this)
|
||||
{
|
||||
m_ChildNetworkBehaviours.Add(networkBehaviours[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return m_ChildNetworkBehaviours;
|
||||
}
|
||||
}
|
||||
|
||||
internal void WriteNetworkVariableData(Stream stream, ulong clientId)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InitializeVariables();
|
||||
NetworkBehaviour.WriteNetworkVariableData(ChildNetworkBehaviours[i].NetworkVariableFields, stream, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetNetworkVariableData(Stream stream)
|
||||
{
|
||||
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
ChildNetworkBehaviours[i].InitializeVariables();
|
||||
NetworkBehaviour.SetNetworkVariableData(ChildNetworkBehaviours[i].NetworkVariableFields, stream);
|
||||
}
|
||||
}
|
||||
|
||||
internal ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour instance)
|
||||
{
|
||||
for (ushort i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||
{
|
||||
if (ChildNetworkBehaviours[i] == instance)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
internal NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index)
|
||||
{
|
||||
if (index >= ChildNetworkBehaviours.Count)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
|
||||
{
|
||||
NetworkLog.LogError($"Behaviour index was out of bounds. Did you mess up the order of your {nameof(NetworkBehaviour)}s?");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return ChildNetworkBehaviours[index];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d5a57f767e5e46a458fc5d3c628d0cbb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,83 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MLAPI
|
||||
{
|
||||
// todo: This is a pretty minimal tick system. It will be improved in the future
|
||||
// It currently relies on Time.unscaledTime and, as such, will start suffering
|
||||
// numerical precision issues after 2^23 ticks have passed (float have 23 bits mantissa)
|
||||
// For future releases, we'll need to improve on this, probably by leveraging FixedUpdate
|
||||
|
||||
public class NetworkTickSystem : INetworkUpdateSystem, IDisposable
|
||||
{
|
||||
private const float k_DefaultTickIntervalSec = 0.05f; // Defaults to 20 ticks second
|
||||
private readonly float m_TickIntervalSec; // Duration of a tick in seconds
|
||||
private int m_NetworkTickCount; // How many network ticks have passed?
|
||||
|
||||
// special value to indicate "No tick information"
|
||||
public const ushort NoTick = ushort.MaxValue;
|
||||
|
||||
// Number of ticks over which the tick number wraps back to 0
|
||||
public const ushort TickPeriod = NoTick - 1;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// Defaults to k_DefaultTickIntervalSec if no tick duration is specified
|
||||
/// </summary>
|
||||
/// <param name="tickIntervalSec">Duration of a network tick</param>
|
||||
public NetworkTickSystem(float tickIntervalSec = k_DefaultTickIntervalSec)
|
||||
{
|
||||
this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
|
||||
|
||||
//Assure we don't specify a value less than or equal to zero for tick frequency
|
||||
m_TickIntervalSec = (tickIntervalSec <= 0f) ? k_DefaultTickIntervalSec : tickIntervalSec;
|
||||
|
||||
// ticks might not start at 0, so let's update right away at construction
|
||||
UpdateNetworkTick();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.UnregisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetTick
|
||||
/// Gets the current network tick (non-fractional, wrapping around)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ushort GetTick()
|
||||
{
|
||||
return (ushort)(m_NetworkTickCount % TickPeriod);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetNetworkTime
|
||||
/// Network time is calculated from m_NetworkTickCount and m_TickIntervalSec (tick frequency)
|
||||
/// </summary>
|
||||
/// <returns>Network Time</returns>
|
||||
public float GetNetworkTime()
|
||||
{
|
||||
return m_NetworkTickCount * m_TickIntervalSec;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UpdateNetworkTick
|
||||
/// Called each network loop update during the PreUpdate stage
|
||||
/// </summary>
|
||||
private void UpdateNetworkTick()
|
||||
{
|
||||
m_NetworkTickCount = (int)(Time.unscaledTime / m_TickIntervalSec);
|
||||
}
|
||||
|
||||
public void NetworkUpdate(NetworkUpdateStage updateStage)
|
||||
{
|
||||
switch (updateStage)
|
||||
{
|
||||
case NetworkUpdateStage.EarlyUpdate:
|
||||
UpdateNetworkTick();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d802d09776f224e44af7dc71f09779d6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,398 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.LowLevel;
|
||||
using UnityEngine.PlayerLoop;
|
||||
|
||||
namespace MLAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the required interface of a network update system being executed by the network update loop.
|
||||
/// </summary>
|
||||
public interface INetworkUpdateSystem
|
||||
{
|
||||
void NetworkUpdate(NetworkUpdateStage updateStage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines network update stages being executed by the network update loop.
|
||||
/// </summary>
|
||||
public enum NetworkUpdateStage : byte
|
||||
{
|
||||
Initialization = 1,
|
||||
EarlyUpdate = 2,
|
||||
FixedUpdate = 3,
|
||||
PreUpdate = 4,
|
||||
Update = 0, // Default
|
||||
PreLateUpdate = 5,
|
||||
PostLateUpdate = 6
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the network update loop injected into low-level player loop in Unity.
|
||||
/// </summary>
|
||||
public static class NetworkUpdateLoop
|
||||
{
|
||||
private static Dictionary<NetworkUpdateStage, HashSet<INetworkUpdateSystem>> s_UpdateSystem_Sets;
|
||||
private static Dictionary<NetworkUpdateStage, INetworkUpdateSystem[]> s_UpdateSystem_Arrays;
|
||||
private const int k_UpdateSystem_InitialArrayCapacity = 1024;
|
||||
|
||||
static NetworkUpdateLoop()
|
||||
{
|
||||
s_UpdateSystem_Sets = new Dictionary<NetworkUpdateStage, HashSet<INetworkUpdateSystem>>();
|
||||
s_UpdateSystem_Arrays = new Dictionary<NetworkUpdateStage, INetworkUpdateSystem[]>();
|
||||
|
||||
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
|
||||
{
|
||||
s_UpdateSystem_Sets.Add(updateStage, new HashSet<INetworkUpdateSystem>());
|
||||
s_UpdateSystem_Arrays.Add(updateStage, new INetworkUpdateSystem[k_UpdateSystem_InitialArrayCapacity]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a network update system to be executed in all network update stages.
|
||||
/// </summary>
|
||||
public static void RegisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
|
||||
{
|
||||
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
|
||||
{
|
||||
RegisterNetworkUpdate(updateSystem, updateStage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a network update system to be executed in a specific network update stage.
|
||||
/// </summary>
|
||||
public static void RegisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update)
|
||||
{
|
||||
var sysSet = s_UpdateSystem_Sets[updateStage];
|
||||
if (!sysSet.Contains(updateSystem))
|
||||
{
|
||||
sysSet.Add(updateSystem);
|
||||
|
||||
int setLen = sysSet.Count;
|
||||
var sysArr = s_UpdateSystem_Arrays[updateStage];
|
||||
int arrLen = sysArr.Length;
|
||||
|
||||
if (setLen > arrLen)
|
||||
{
|
||||
// double capacity
|
||||
sysArr = s_UpdateSystem_Arrays[updateStage] = new INetworkUpdateSystem[arrLen *= 2];
|
||||
}
|
||||
|
||||
sysSet.CopyTo(sysArr);
|
||||
|
||||
if (setLen < arrLen)
|
||||
{
|
||||
// null terminator
|
||||
sysArr[setLen] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a network update system from all network update stages.
|
||||
/// </summary>
|
||||
public static void UnregisterAllNetworkUpdates(this INetworkUpdateSystem updateSystem)
|
||||
{
|
||||
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
|
||||
{
|
||||
UnregisterNetworkUpdate(updateSystem, updateStage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a network update system from a specific network update stage.
|
||||
/// </summary>
|
||||
public static void UnregisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update)
|
||||
{
|
||||
var sysSet = s_UpdateSystem_Sets[updateStage];
|
||||
if (sysSet.Contains(updateSystem))
|
||||
{
|
||||
sysSet.Remove(updateSystem);
|
||||
|
||||
int setLen = sysSet.Count;
|
||||
var sysArr = s_UpdateSystem_Arrays[updateStage];
|
||||
int arrLen = sysArr.Length;
|
||||
|
||||
sysSet.CopyTo(sysArr);
|
||||
|
||||
if (setLen < arrLen)
|
||||
{
|
||||
// null terminator
|
||||
sysArr[setLen] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current network update stage being executed.
|
||||
/// </summary>
|
||||
public static NetworkUpdateStage UpdateStage;
|
||||
|
||||
private static void RunNetworkUpdateStage(NetworkUpdateStage updateStage)
|
||||
{
|
||||
UpdateStage = updateStage;
|
||||
|
||||
var sysArr = s_UpdateSystem_Arrays[updateStage];
|
||||
int arrLen = sysArr.Length;
|
||||
for (int curIdx = 0; curIdx < arrLen; curIdx++)
|
||||
{
|
||||
var curSys = sysArr[curIdx];
|
||||
if (curSys == null)
|
||||
{
|
||||
// null terminator
|
||||
break;
|
||||
}
|
||||
|
||||
curSys.NetworkUpdate(updateStage);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkInitialization
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkInitialization),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.Initialization)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkEarlyUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkEarlyUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.EarlyUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkFixedUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkFixedUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.FixedUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkPreUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkPreUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PreUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.Update)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkPreLateUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkPreLateUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PreLateUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NetworkPostLateUpdate
|
||||
{
|
||||
public static PlayerLoopSystem CreateLoopSystem()
|
||||
{
|
||||
return new PlayerLoopSystem
|
||||
{
|
||||
type = typeof(NetworkPostLateUpdate),
|
||||
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PostLateUpdate)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void Initialize()
|
||||
{
|
||||
UnregisterLoopSystems();
|
||||
RegisterLoopSystems();
|
||||
}
|
||||
|
||||
private enum LoopSystemPosition
|
||||
{
|
||||
After,
|
||||
Before
|
||||
}
|
||||
|
||||
private static bool TryAddLoopSystem(ref PlayerLoopSystem parentLoopSystem, PlayerLoopSystem childLoopSystem, Type anchorSystemType, LoopSystemPosition loopSystemPosition)
|
||||
{
|
||||
int systemPosition = -1;
|
||||
if (anchorSystemType != null)
|
||||
{
|
||||
for (int i = 0; i < parentLoopSystem.subSystemList.Length; i++)
|
||||
{
|
||||
var subsystem = parentLoopSystem.subSystemList[i];
|
||||
if (subsystem.type == anchorSystemType)
|
||||
{
|
||||
systemPosition = loopSystemPosition == LoopSystemPosition.After ? i + 1 : i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
systemPosition = loopSystemPosition == LoopSystemPosition.After ? parentLoopSystem.subSystemList.Length : 0;
|
||||
}
|
||||
|
||||
if (systemPosition == -1) return false;
|
||||
|
||||
var newSubsystemList = new PlayerLoopSystem[parentLoopSystem.subSystemList.Length + 1];
|
||||
|
||||
// begin = systemsBefore + systemsAfter
|
||||
// + systemsBefore
|
||||
if (systemPosition > 0) Array.Copy(parentLoopSystem.subSystemList, newSubsystemList, systemPosition);
|
||||
// + childSystem
|
||||
newSubsystemList[systemPosition] = childLoopSystem;
|
||||
// + systemsAfter
|
||||
if (systemPosition < parentLoopSystem.subSystemList.Length) Array.Copy(parentLoopSystem.subSystemList, systemPosition, newSubsystemList, systemPosition + 1, parentLoopSystem.subSystemList.Length - systemPosition);
|
||||
// end = systemsBefore + childSystem + systemsAfter
|
||||
|
||||
parentLoopSystem.subSystemList = newSubsystemList;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryRemoveLoopSystem(ref PlayerLoopSystem parentLoopSystem, Type childSystemType)
|
||||
{
|
||||
int systemPosition = -1;
|
||||
for (int i = 0; i < parentLoopSystem.subSystemList.Length; i++)
|
||||
{
|
||||
var subsystem = parentLoopSystem.subSystemList[i];
|
||||
if (subsystem.type == childSystemType)
|
||||
{
|
||||
systemPosition = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (systemPosition == -1) return false;
|
||||
|
||||
var newSubsystemList = new PlayerLoopSystem[parentLoopSystem.subSystemList.Length - 1];
|
||||
|
||||
// begin = systemsBefore + childSystem + systemsAfter
|
||||
// + systemsBefore
|
||||
if (systemPosition > 0) Array.Copy(parentLoopSystem.subSystemList, newSubsystemList, systemPosition);
|
||||
// + systemsAfter
|
||||
if (systemPosition < parentLoopSystem.subSystemList.Length - 1) Array.Copy(parentLoopSystem.subSystemList, systemPosition + 1, newSubsystemList, systemPosition, parentLoopSystem.subSystemList.Length - systemPosition - 1);
|
||||
// end = systemsBefore + systemsAfter
|
||||
|
||||
parentLoopSystem.subSystemList = newSubsystemList;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void RegisterLoopSystems()
|
||||
{
|
||||
var rootPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
|
||||
|
||||
for (int i = 0; i < rootPlayerLoop.subSystemList.Length; i++)
|
||||
{
|
||||
ref var currentSystem = ref rootPlayerLoop.subSystemList[i];
|
||||
|
||||
if (currentSystem.type == typeof(Initialization))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkInitialization.CreateLoopSystem(), null, LoopSystemPosition.After);
|
||||
}
|
||||
else if (currentSystem.type == typeof(EarlyUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkEarlyUpdate.CreateLoopSystem(), typeof(EarlyUpdate.ScriptRunDelayedStartupFrame), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(FixedUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkFixedUpdate.CreateLoopSystem(), typeof(FixedUpdate.ScriptRunBehaviourFixedUpdate), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(PreUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkPreUpdate.CreateLoopSystem(), typeof(PreUpdate.PhysicsUpdate), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(Update))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkUpdate.CreateLoopSystem(), typeof(Update.ScriptRunBehaviourUpdate), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(PreLateUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkPreLateUpdate.CreateLoopSystem(), typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate), LoopSystemPosition.Before);
|
||||
}
|
||||
else if (currentSystem.type == typeof(PostLateUpdate))
|
||||
{
|
||||
TryAddLoopSystem(ref currentSystem, NetworkPostLateUpdate.CreateLoopSystem(), typeof(PostLateUpdate.PlayerSendFrameComplete), LoopSystemPosition.After);
|
||||
}
|
||||
}
|
||||
|
||||
PlayerLoop.SetPlayerLoop(rootPlayerLoop);
|
||||
}
|
||||
|
||||
internal static void UnregisterLoopSystems()
|
||||
{
|
||||
var rootPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
|
||||
|
||||
for (int i = 0; i < rootPlayerLoop.subSystemList.Length; i++)
|
||||
{
|
||||
ref var currentSystem = ref rootPlayerLoop.subSystemList[i];
|
||||
|
||||
if (currentSystem.type == typeof(Initialization))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkInitialization));
|
||||
}
|
||||
else if (currentSystem.type == typeof(EarlyUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkEarlyUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(FixedUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkFixedUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(PreUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(Update))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(PreLateUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreLateUpdate));
|
||||
}
|
||||
else if (currentSystem.type == typeof(PostLateUpdate))
|
||||
{
|
||||
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPostLateUpdate));
|
||||
}
|
||||
}
|
||||
|
||||
PlayerLoop.SetPlayerLoop(rootPlayerLoop);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0cd9c24b9acfd4e82a71c795f37235c3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue