Initial Commit
This commit is contained in:
parent
53eb92e9af
commit
270ab7d11f
15341 changed files with 700234 additions and 0 deletions
|
@ -0,0 +1,10 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER && UNITY_EDITOR
|
||||
[assembly: InternalsVisibleTo("Unity.Multiplayer.MLAPI.Editor.CodeGen")]
|
||||
#endif
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[assembly: InternalsVisibleTo("Unity.Multiplayer.MLAPI.EditorTests")]
|
||||
[assembly: InternalsVisibleTo("Unity.Multiplayer.MLAPI.RuntimeTests")]
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c9fd74adf4a0f6e479be3978543dc6a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f2ef964afcae91248b2298b479ed1b53
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Queue with a fixed size
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the queue</typeparam>
|
||||
public sealed class FixedQueue<T>
|
||||
{
|
||||
private readonly T[] m_Queue;
|
||||
private int m_QueueCount = 0;
|
||||
private int m_QueueStart;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of enqueued objects
|
||||
/// </summary>
|
||||
public int Count => m_QueueCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at a given virtual index
|
||||
/// </summary>
|
||||
/// <param name="index">The virtual index to get the item from</param>
|
||||
/// <returns>The element at the virtual index</returns>
|
||||
public T this[int index] => m_Queue[(m_QueueStart + index) % m_Queue.Length];
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new FixedQueue with a given size
|
||||
/// </summary>
|
||||
/// <param name="maxSize">The size of the queue</param>
|
||||
public FixedQueue(int maxSize)
|
||||
{
|
||||
m_Queue = new T[maxSize];
|
||||
m_QueueStart = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enqueues an object
|
||||
/// </summary>
|
||||
/// <param name="t"></param>
|
||||
/// <returns></returns>
|
||||
public bool Enqueue(T t)
|
||||
{
|
||||
m_Queue[(m_QueueStart + m_QueueCount) % m_Queue.Length] = t;
|
||||
if (++m_QueueCount > m_Queue.Length)
|
||||
{
|
||||
--m_QueueCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dequeues an object
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public T Dequeue()
|
||||
{
|
||||
if (--m_QueueCount == -1) throw new IndexOutOfRangeException("Cannot dequeue empty queue!");
|
||||
T res = m_Queue[m_QueueStart];
|
||||
m_QueueStart = (m_QueueStart + 1) % m_Queue.Length;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at a given virtual index
|
||||
/// </summary>
|
||||
/// <param name="index">The virtual index to get the item from</param>
|
||||
/// <returns>The element at the virtual index</returns>
|
||||
public T ElementAt(int index) => m_Queue[(m_QueueStart + index) % m_Queue.Length];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a8514b4eca0c7044d9b92faf9407ec93
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d3cc7700dfd03ee4397858710461d179
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,25 @@
|
|||
namespace MLAPI.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the length of a var int encoded hash
|
||||
/// Note that the HashSize does not say anything about the actual final output due to the var int encoding
|
||||
/// It just says how many bytes the maximum will be
|
||||
/// </summary>
|
||||
public enum HashSize
|
||||
{
|
||||
/// <summary>
|
||||
/// Two byte hash
|
||||
/// </summary>
|
||||
VarIntTwoBytes,
|
||||
|
||||
/// <summary>
|
||||
/// Four byte hash
|
||||
/// </summary>
|
||||
VarIntFourBytes,
|
||||
|
||||
/// <summary>
|
||||
/// Eight byte hash
|
||||
/// </summary>
|
||||
VarIntEightBytes
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5ae94548754e0a0409da85c0e3235bb4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,354 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using MLAPI.Transports;
|
||||
using MLAPI.Hashing;
|
||||
using MLAPI.Serialization;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace MLAPI.Configuration
|
||||
{
|
||||
[Serializable]
|
||||
internal class NullableBoolSerializable
|
||||
{
|
||||
[SerializeField]
|
||||
public ulong Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The configuration object used to start server, client and hosts
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The protocol version. Different versions doesn't talk to each other.
|
||||
/// </summary>
|
||||
[Tooltip("Use this to make two builds incompatible with each other")]
|
||||
public ushort ProtocolVersion = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The transport hosts the sever uses
|
||||
/// </summary>
|
||||
[Tooltip("The NetworkTransport to use")]
|
||||
public NetworkTransport NetworkTransport = null;
|
||||
|
||||
/// <summary>
|
||||
/// A list of SceneNames that can be used during networked games.
|
||||
/// </summary>
|
||||
[Tooltip("The Scenes that can be switched to by the server")]
|
||||
public List<string> RegisteredScenes = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not runtime scene changes should be allowed and expected.
|
||||
/// If this is true, clients with different initial configurations will not work together.
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not runtime scene changes should be allowed and expected.\n " +
|
||||
"If this is true, clients with different initial configurations will not work together.")]
|
||||
public bool AllowRuntimeSceneChanges = false;
|
||||
|
||||
/// <summary>
|
||||
/// A list of spawnable prefabs
|
||||
/// </summary>
|
||||
[Tooltip("The prefabs that can be spawned across the network")]
|
||||
[FormerlySerializedAs("NetworkedPrefabs")]
|
||||
public List<NetworkPrefab> NetworkPrefabs = new List<NetworkPrefab>();
|
||||
|
||||
/// <summary>
|
||||
/// The default player prefab
|
||||
/// </summary>
|
||||
[SerializeReference]
|
||||
internal NullableBoolSerializable PlayerPrefabHash;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not a player object should be created by default. This value can be overriden on a case by case basis with ConnectionApproval.
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not a player object should be created by default. This value can be overriden on a case by case basis with ConnectionApproval.")]
|
||||
public bool CreatePlayerPrefab = true;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of times per second the receive queue is emptied and all messages inside are processed.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of times per second the receive queue is emptied from pending incoming messages")]
|
||||
public int ReceiveTickrate = 64;
|
||||
|
||||
/// <summary>
|
||||
/// Duration in seconds between network ticks.
|
||||
/// </summary>
|
||||
[Tooltip("Duration in seconds between network ticks")]
|
||||
public float NetworkTickIntervalSec = 0.050f;
|
||||
|
||||
/// <summary>
|
||||
/// The max amount of messages to process per ReceiveTickrate. This is to prevent flooding.
|
||||
/// </summary>
|
||||
[Tooltip("The maximum amount of Receive events to poll per Receive tick. This is to prevent flooding and freezing on the server")]
|
||||
public int MaxReceiveEventsPerTickRate = 500;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of times per second internal frame events will occur, e.g. send checking.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of times per second the internal event loop will run. This includes for example NetworkVariable checking.")]
|
||||
public int EventTickrate = 64;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of seconds to wait for handshake to complete before timing out a client
|
||||
/// </summary>
|
||||
[Tooltip("The amount of seconds to wait for the handshake to complete before the client times out")]
|
||||
public int ClientConnectionBufferTimeout = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to use connection approval
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not to force clients to be approved before they connect")]
|
||||
public bool ConnectionApproval = false;
|
||||
|
||||
/// <summary>
|
||||
/// The data to send during connection which can be used to decide on if a client should get accepted
|
||||
/// </summary>
|
||||
[Tooltip("The connection data sent along with connection requests")]
|
||||
public byte[] ConnectionData = new byte[0];
|
||||
|
||||
/// <summary>
|
||||
/// If your logic uses the NetworkTime, this should probably be turned off. If however it's needed to maximize accuracy, this is recommended to be turned on
|
||||
/// </summary>
|
||||
[Tooltip("Enable this to resync the NetworkTime after the initial sync")]
|
||||
public bool EnableTimeResync = false;
|
||||
|
||||
/// <summary>
|
||||
/// If time resync is turned on, this specifies the interval between syncs in seconds.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of seconds between resyncs of NetworkTime, if enabled")]
|
||||
public int TimeResyncInterval = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to enable the NetworkVariable system. This system runs in the Update loop and will degrade performance, but it can be a huge convenience.
|
||||
/// Only turn it off if you have no need for the NetworkVariable system.
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not to enable the NetworkVariable system")]
|
||||
public bool EnableNetworkVariable = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to ensure that NetworkVariables can be read even if a client accidentally writes where its not allowed to. This costs some CPU and bandwdith.
|
||||
/// </summary>
|
||||
[Tooltip("Ensures that NetworkVariables can be read even if a client accidental writes where its not allowed to. This will cost some CPU time and bandwidth")]
|
||||
public bool EnsureNetworkVariableLengthSafety = false;
|
||||
|
||||
/// <summary>
|
||||
/// Enables scene management. This will allow network scene switches and automatic scene diff corrections upon connect.
|
||||
/// SoftSynced scene objects wont work with this disabled. That means that disabling SceneManagement also enables PrefabSync.
|
||||
/// </summary>
|
||||
[Tooltip("Enables scene management. This will allow network scene switches and automatic scene diff corrections upon connect.\n" +
|
||||
"SoftSynced scene objects wont work with this disabled. That means that disabling SceneManagement also enables PrefabSync.")]
|
||||
public bool EnableSceneManagement = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the MLAPI should check for differences in the prefabs at connection.
|
||||
/// If you dynamically add prefabs at runtime, turn this OFF
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not the MLAPI should check for differences in the prefab lists at connection")]
|
||||
public bool ForceSamePrefabs = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, all NetworkObjects need to be prefabs and all scene objects will be replaced on server side which causes all serialization to be lost. Useful for multi project setups
|
||||
/// If false, Only non scene objects have to be prefabs. Scene objects will be matched using their PrefabInstanceId which can be precomputed globally for a scene at build time. Useful for single projects
|
||||
/// </summary>
|
||||
[Tooltip("If true, all NetworkObjects need to be prefabs and all scene objects will be replaced on server side which causes all serialization to be lost. Useful for multi project setups\n" +
|
||||
"If false, Only non scene objects have to be prefabs. Scene objects will be matched using their PrefabInstanceId which can be precomputed globally for a scene at build time. Useful for single projects")]
|
||||
public bool UsePrefabSync = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, NetworkIds will be reused after the NetworkIdRecycleDelay.
|
||||
/// </summary>
|
||||
[Tooltip("If true, NetworkIds will be reused after the NetworkIdRecycleDelay")]
|
||||
public bool RecycleNetworkIds = true;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of seconds a NetworkId has to be unused in order for it to be reused.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of seconds a NetworkId has to unused in order for it to be reused")]
|
||||
public float NetworkIdRecycleDelay = 120f;
|
||||
|
||||
/// <summary>
|
||||
/// Decides how many bytes to use for Rpc messaging. Leave this to 2 bytes unless you are facing hash collisions
|
||||
/// </summary>
|
||||
[Tooltip("The maximum amount of bytes to use for RPC messages. Leave this to 2 unless you are facing hash collisions")]
|
||||
public HashSize RpcHashSize = HashSize.VarIntTwoBytes;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of seconds to wait on all clients to load requested scene before the SwitchSceneProgress onComplete callback, that waits for all clients to complete loading, is called anyway.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of seconds to wait for all clients to load a requested scene")]
|
||||
public int LoadSceneTimeOut = 120;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not message buffering should be enabled. This will resolve most out of order messages during spawn.
|
||||
/// </summary>
|
||||
[Tooltip("Whether or not message buffering should be enabled. This will resolve most out of order messages during spawn")]
|
||||
public bool EnableMessageBuffering = true;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of time a message should be buffered for without being consumed. If it is not consumed within this time, it will be dropped")]
|
||||
public float MessageBufferTimeout = 20f;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to enable network logs.
|
||||
/// </summary>
|
||||
public bool EnableNetworkLogs = true;
|
||||
|
||||
private void Sort()
|
||||
{
|
||||
RegisteredScenes.Sort(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a base64 encoded version of the config
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string ToBase64()
|
||||
{
|
||||
NetworkConfig config = this;
|
||||
using (var buffer = PooledNetworkBuffer.Get())
|
||||
using (var writer = PooledNetworkWriter.Get(buffer))
|
||||
{
|
||||
writer.WriteUInt16Packed(config.ProtocolVersion);
|
||||
writer.WriteUInt16Packed((ushort)config.RegisteredScenes.Count);
|
||||
|
||||
for (int i = 0; i < config.RegisteredScenes.Count; i++)
|
||||
{
|
||||
writer.WriteString(config.RegisteredScenes[i]);
|
||||
}
|
||||
|
||||
writer.WriteInt32Packed(config.ReceiveTickrate);
|
||||
writer.WriteInt32Packed(config.MaxReceiveEventsPerTickRate);
|
||||
writer.WriteInt32Packed(config.EventTickrate);
|
||||
writer.WriteInt32Packed(config.ClientConnectionBufferTimeout);
|
||||
writer.WriteBool(config.ConnectionApproval);
|
||||
writer.WriteInt32Packed(config.LoadSceneTimeOut);
|
||||
writer.WriteBool(config.EnableTimeResync);
|
||||
writer.WriteBool(config.EnsureNetworkVariableLengthSafety);
|
||||
writer.WriteBits((byte)config.RpcHashSize, 2);
|
||||
writer.WriteBool(ForceSamePrefabs);
|
||||
writer.WriteBool(UsePrefabSync);
|
||||
writer.WriteBool(EnableSceneManagement);
|
||||
writer.WriteBool(RecycleNetworkIds);
|
||||
writer.WriteSinglePacked(NetworkIdRecycleDelay);
|
||||
writer.WriteBool(EnableNetworkVariable);
|
||||
writer.WriteBool(AllowRuntimeSceneChanges);
|
||||
writer.WriteBool(EnableNetworkLogs);
|
||||
buffer.PadBuffer();
|
||||
|
||||
return Convert.ToBase64String(buffer.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the NetworkConfig data with that from a base64 encoded version
|
||||
/// </summary>
|
||||
/// <param name="base64">The base64 encoded version</param>
|
||||
public void FromBase64(string base64)
|
||||
{
|
||||
NetworkConfig config = this;
|
||||
byte[] binary = Convert.FromBase64String(base64);
|
||||
using (var buffer = new NetworkBuffer(binary))
|
||||
using (var reader = PooledNetworkReader.Get(buffer))
|
||||
{
|
||||
config.ProtocolVersion = reader.ReadUInt16Packed();
|
||||
|
||||
ushort sceneCount = reader.ReadUInt16Packed();
|
||||
config.RegisteredScenes.Clear();
|
||||
|
||||
for (int i = 0; i < sceneCount; i++)
|
||||
{
|
||||
config.RegisteredScenes.Add(reader.ReadString().ToString());
|
||||
}
|
||||
|
||||
config.ReceiveTickrate = reader.ReadInt32Packed();
|
||||
config.MaxReceiveEventsPerTickRate = reader.ReadInt32Packed();
|
||||
config.EventTickrate = reader.ReadInt32Packed();
|
||||
config.ClientConnectionBufferTimeout = reader.ReadInt32Packed();
|
||||
config.ConnectionApproval = reader.ReadBool();
|
||||
config.LoadSceneTimeOut = reader.ReadInt32Packed();
|
||||
config.EnableTimeResync = reader.ReadBool();
|
||||
config.EnsureNetworkVariableLengthSafety = reader.ReadBool();
|
||||
config.RpcHashSize = (HashSize)reader.ReadBits(2);
|
||||
config.ForceSamePrefabs = reader.ReadBool();
|
||||
config.UsePrefabSync = reader.ReadBool();
|
||||
config.EnableSceneManagement = reader.ReadBool();
|
||||
config.RecycleNetworkIds = reader.ReadBool();
|
||||
config.NetworkIdRecycleDelay = reader.ReadSinglePacked();
|
||||
config.EnableNetworkVariable = reader.ReadBool();
|
||||
config.AllowRuntimeSceneChanges = reader.ReadBool();
|
||||
config.EnableNetworkLogs = reader.ReadBool();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private ulong? m_ConfigHash = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a SHA256 hash of parts of the NetworkConfig instance
|
||||
/// </summary>
|
||||
/// <param name="cache"></param>
|
||||
/// <returns></returns>
|
||||
public ulong GetConfig(bool cache = true)
|
||||
{
|
||||
if (m_ConfigHash != null && cache) return m_ConfigHash.Value;
|
||||
|
||||
Sort();
|
||||
|
||||
using (var buffer = PooledNetworkBuffer.Get())
|
||||
using (var writer = PooledNetworkWriter.Get(buffer))
|
||||
{
|
||||
writer.WriteUInt16Packed(ProtocolVersion);
|
||||
writer.WriteString(NetworkConstants.PROTOCOL_VERSION);
|
||||
|
||||
if (EnableSceneManagement && !AllowRuntimeSceneChanges)
|
||||
{
|
||||
for (int i = 0; i < RegisteredScenes.Count; i++)
|
||||
{
|
||||
writer.WriteString(RegisteredScenes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (ForceSamePrefabs)
|
||||
{
|
||||
var sortedPrefabList = NetworkPrefabs.OrderBy(x => x.Hash).ToList();
|
||||
for (int i = 0; i < sortedPrefabList.Count; i++)
|
||||
{
|
||||
writer.WriteUInt64Packed(sortedPrefabList[i].Hash);
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteBool(EnableNetworkVariable);
|
||||
writer.WriteBool(ForceSamePrefabs);
|
||||
writer.WriteBool(UsePrefabSync);
|
||||
writer.WriteBool(EnableSceneManagement);
|
||||
writer.WriteBool(EnsureNetworkVariableLengthSafety);
|
||||
writer.WriteBits((byte)RpcHashSize, 2);
|
||||
buffer.PadBuffer();
|
||||
|
||||
if (cache)
|
||||
{
|
||||
m_ConfigHash = buffer.ToArray().GetStableHash64();
|
||||
return m_ConfigHash.Value;
|
||||
}
|
||||
|
||||
return buffer.ToArray().GetStableHash64();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares a SHA256 hash with the current NetworkConfig instances hash
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <returns></returns>
|
||||
public bool CompareConfig(ulong hash)
|
||||
{
|
||||
return hash == GetConfig();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1b056e7faa4d1cb4aac7bc304c765c3b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,66 @@
|
|||
namespace MLAPI.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// A static class containing MLAPI constants
|
||||
/// </summary>
|
||||
internal static class NetworkConstants
|
||||
{
|
||||
internal const string PROTOCOL_VERSION = "13.0.0";
|
||||
|
||||
internal const byte CONNECTION_REQUEST = 3;
|
||||
internal const byte CONNECTION_APPROVED = 4;
|
||||
internal const byte ADD_OBJECT = 5;
|
||||
internal const byte DESTROY_OBJECT = 6;
|
||||
internal const byte SWITCH_SCENE = 7;
|
||||
internal const byte CLIENT_SWITCH_SCENE_COMPLETED = 8;
|
||||
internal const byte CHANGE_OWNER = 9;
|
||||
internal const byte ADD_OBJECTS = 10;
|
||||
internal const byte TIME_SYNC = 11;
|
||||
internal const byte NETWORK_VARIABLE_DELTA = 12;
|
||||
internal const byte NETWORK_VARIABLE_UPDATE = 13;
|
||||
internal const byte UNNAMED_MESSAGE = 20;
|
||||
internal const byte DESTROY_OBJECTS = 21;
|
||||
internal const byte NAMED_MESSAGE = 22;
|
||||
internal const byte SERVER_LOG = 23;
|
||||
internal const byte SERVER_RPC = 30;
|
||||
internal const byte CLIENT_RPC = 31;
|
||||
internal const byte INVALID = 32;
|
||||
|
||||
internal static readonly string[] MESSAGE_NAMES =
|
||||
{
|
||||
"", // 0
|
||||
"",
|
||||
"",
|
||||
"CONNECTION_REQUEST",
|
||||
"CONNECTION_APPROVED",
|
||||
"ADD_OBJECT",
|
||||
"DESTROY_OBJECT",
|
||||
"SWITCH_SCENE",
|
||||
"CLIENT_SWITCH_SCENE_COMPLETED",
|
||||
"CHANGE_OWNER",
|
||||
"ADD_OBJECTS",
|
||||
"TIME_SYNC",
|
||||
"NETWORK_VARIABLE_DELTA",
|
||||
"NETWORK_VARIABLE_UPDATE",
|
||||
"",
|
||||
"",
|
||||
"", // 16
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"UNNAMED_MESSAGE",
|
||||
"DESTROY_OBJECTS",
|
||||
"NAMED_MESSAGE",
|
||||
"SERVER_LOG",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"SERVER_RPC",
|
||||
"CLIENT_RPC",
|
||||
"INVALID" // 32
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dae82900f88f6bb4a90c29d431f2b45a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,52 @@
|
|||
using System;
|
||||
using MLAPI.Logging;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MLAPI.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// A class that represents a NetworkPrefab
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkPrefab
|
||||
{
|
||||
internal ulong Hash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Prefab == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} is not assigned");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
var networkObject = Prefab.GetComponent<NetworkObject>();
|
||||
if (networkObject == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkPrefab)} {Prefab.name} does not have a {nameof(NetworkObject)}");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
return networkObject.PrefabHash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The gameobject of the prefab
|
||||
/// </summary>
|
||||
public GameObject Prefab;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this is a playerPrefab
|
||||
/// </summary>
|
||||
public bool PlayerPrefab;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 10e0511afda4e7743b2cd7c9cf95e0ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c7155f1167eb96846aab2132b37c2815
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,25 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MLAPI.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A NetworkClient
|
||||
/// </summary>
|
||||
public class NetworkClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The ClientId of the NetworkClient
|
||||
/// </summary>
|
||||
public ulong ClientId;
|
||||
|
||||
/// <summary>
|
||||
/// The PlayerObject of the Client
|
||||
/// </summary>
|
||||
public NetworkObject PlayerObject;
|
||||
|
||||
/// <summary>
|
||||
/// The NetworkObject's owned by this Client
|
||||
/// </summary>
|
||||
public readonly List<NetworkObject> OwnedObjects = new List<NetworkObject>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 23ca4d14911834b41a761c62fb23773e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,29 @@
|
|||
namespace MLAPI.Connection
|
||||
{
|
||||
/// <summary>
|
||||
/// A class representing a client that is currently in the process of connecting
|
||||
/// </summary>
|
||||
public class PendingClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The ClientId of the client
|
||||
/// </summary>
|
||||
public ulong ClientId;
|
||||
|
||||
/// <summary>
|
||||
/// The state of the connection process for the client
|
||||
/// </summary>
|
||||
public State ConnectionState;
|
||||
|
||||
/// <summary>
|
||||
/// The states of a connection
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
/// <summary>
|
||||
/// Client is in the process of doing the connection handshake
|
||||
/// </summary>
|
||||
PendingConnection
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2ffbc5b303de84a4196e24f503752218
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: eb92f4010a5924b408aca753b55bd5ae
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -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:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2643fec0e8509fd409e5aceeba931323
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when the operation can only be done on the server
|
||||
/// </summary>
|
||||
public class NetworkConfigurationException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a NetworkConfigurationException
|
||||
/// </summary>
|
||||
public NetworkConfigurationException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NetworkConfigurationException with a message
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
public NetworkConfigurationException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NetworkConfigurationException with a message and a inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
/// <param name="inner">The inner exception</param>
|
||||
public NetworkConfigurationException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f50fae9dd20afbd438766cc330b188a4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when the operation require NetworkManager to be listening.
|
||||
/// </summary>
|
||||
public class NotListeningException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a NotListeningException
|
||||
/// </summary>
|
||||
public NotListeningException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NotListeningException with a message
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
public NotListeningException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NotListeningException with a message and a inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
/// <param name="inner">The inner exception</param>
|
||||
public NotListeningException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dc58b328162929140aeb42e6a4a9354b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when the operation can only be done on the server
|
||||
/// </summary>
|
||||
public class NotServerException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a NotServerException
|
||||
/// </summary>
|
||||
public NotServerException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NotServerException with a message
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
public NotServerException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a NotServerException with a message and a inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
/// <param name="inner">The inner exception</param>
|
||||
public NotServerException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 751e55ce1418ecd4e9b08fef9d8e9591
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when an object is not yet spawned
|
||||
/// </summary>
|
||||
public class SpawnStateException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a SpawnStateException
|
||||
/// </summary>
|
||||
public SpawnStateException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a SpawnStateException with a message
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
public SpawnStateException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a SpawnStateException with a message and a inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
/// <param name="inner">The inner exception</param>
|
||||
public SpawnStateException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
|
||||
public class InvalidChannelException : Exception
|
||||
{
|
||||
public InvalidChannelException(string message) : base(message) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 70c2a851ae78c6e49b34c7e4ca46305f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception thrown when a visibility change fails
|
||||
/// </summary>
|
||||
public class VisibilityChangeException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a VisibilityChangeException
|
||||
/// </summary>
|
||||
public VisibilityChangeException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a VisibilityChangeException with a message
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
public VisibilityChangeException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a VisibilityChangeException with a message and a inner exception
|
||||
/// </summary>
|
||||
/// <param name="message">The exception message</param>
|
||||
/// <param name="inner">The inner exception</param>
|
||||
public VisibilityChangeException(string message, Exception inner) : base(message, inner) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 85d754bbea7d74a48918ebe05833e7ba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e0b5218305f20f9419d00c4ab2adabef
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,153 @@
|
|||
namespace MLAPI.Hashing
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for getting hashes
|
||||
/// </summary>
|
||||
internal static class HashCode
|
||||
{
|
||||
private const uint k_FNV_offset_basis32 = 2166136261;
|
||||
private const uint k_FNV_prime32 = 16777619;
|
||||
|
||||
private const ulong k_FNV_offset_basis64 = 14695981039346656037;
|
||||
private const ulong k_FNV_prime64 = 1099511628211;
|
||||
|
||||
/// <summary>
|
||||
/// non cryptographic stable hash code,
|
||||
/// it will always return the same hash for the same
|
||||
/// string.
|
||||
///
|
||||
/// This is simply an implementation of FNV-1 32 bit xor folded to 16 bit
|
||||
/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
||||
/// </summary>
|
||||
/// <returns>The stable hash32.</returns>
|
||||
/// <param name="txt">Text.</param>
|
||||
internal static ushort GetStableHash16(this string txt)
|
||||
{
|
||||
uint hash32 = txt.GetStableHash32();
|
||||
|
||||
return (ushort)((hash32 >> 16) ^ hash32);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// non cryptographic stable hash code,
|
||||
/// it will always return the same hash for the same
|
||||
/// string.
|
||||
///
|
||||
/// This is simply an implementation of FNV-1 32 bit
|
||||
/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
||||
/// </summary>
|
||||
/// <returns>The stable hash32.</returns>
|
||||
/// <param name="txt">Text.</param>
|
||||
internal static uint GetStableHash32(this string txt)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
uint hash = k_FNV_offset_basis32;
|
||||
for (int i = 0; i < txt.Length; i++)
|
||||
{
|
||||
uint ch = txt[i];
|
||||
hash = hash * k_FNV_prime32;
|
||||
hash = hash ^ ch;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// non cryptographic stable hash code,
|
||||
/// it will always return the same hash for the same
|
||||
/// string.
|
||||
///
|
||||
/// This is simply an implementation of FNV-1 64 bit
|
||||
/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
||||
/// </summary>
|
||||
/// <returns>The stable hash32.</returns>
|
||||
/// <param name="txt">Text.</param>
|
||||
internal static ulong GetStableHash64(this string txt)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
ulong hash = k_FNV_offset_basis64;
|
||||
for (int i = 0; i < txt.Length; i++)
|
||||
{
|
||||
ulong ch = txt[i];
|
||||
hash = hash * k_FNV_prime64;
|
||||
hash = hash ^ ch;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// non cryptographic stable hash code,
|
||||
/// it will always return the same hash for the same
|
||||
/// string.
|
||||
///
|
||||
/// This is simply an implementation of FNV-1 32 bit xor folded to 16 bit
|
||||
/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
||||
/// </summary>
|
||||
/// <returns>The stable hash32.</returns>
|
||||
/// <param name="bytes">Text.</param>
|
||||
internal static ushort GetStableHash16(this byte[] bytes)
|
||||
{
|
||||
uint hash32 = bytes.GetStableHash32();
|
||||
|
||||
return (ushort)((hash32 >> 16) ^ hash32);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// non cryptographic stable hash code,
|
||||
/// it will always return the same hash for the same
|
||||
/// string.
|
||||
///
|
||||
/// This is simply an implementation of FNV-1 32 bit
|
||||
/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
||||
/// </summary>
|
||||
/// <returns>The stable hash32.</returns>
|
||||
/// <param name="bytes">Text.</param>
|
||||
internal static uint GetStableHash32(this byte[] bytes)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
uint hash = k_FNV_offset_basis32;
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
uint bt = bytes[i];
|
||||
hash = hash * k_FNV_prime32;
|
||||
hash = hash ^ bt;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// non cryptographic stable hash code,
|
||||
/// it will always return the same hash for the same
|
||||
/// string.
|
||||
///
|
||||
/// This is simply an implementation of FNV-1 64 bit
|
||||
/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
||||
/// </summary>
|
||||
/// <returns>The stable hash32.</returns>
|
||||
/// <param name="bytes">Text.</param>
|
||||
internal static ulong GetStableHash64(this byte[] bytes)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
ulong hash = k_FNV_offset_basis64;
|
||||
for (int i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
ulong bt = bytes[i];
|
||||
hash = hash * k_FNV_prime64;
|
||||
hash = hash ^ bt;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ad3d6d0472ba64241ab7b3a8f1f0ca93
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: eb2e0b0239cebdd44b534f09be21f1e6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,28 @@
|
|||
namespace MLAPI.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Log level
|
||||
/// </summary>
|
||||
public enum LogLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Developer logging level, most verbose
|
||||
/// </summary>
|
||||
Developer,
|
||||
|
||||
/// <summary>
|
||||
/// Normal logging level, medium verbose
|
||||
/// </summary>
|
||||
Normal,
|
||||
|
||||
/// <summary>
|
||||
/// Error logging level, very quiet
|
||||
/// </summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// Nothing logging level, no logging will be done
|
||||
/// </summary>
|
||||
Nothing
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b97845a27cb50d44da22aae51800abfe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,86 @@
|
|||
using MLAPI.Configuration;
|
||||
using MLAPI.Messaging;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Transports;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MLAPI.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for logging
|
||||
/// </summary>
|
||||
public static class NetworkLog
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current log level.
|
||||
/// </summary>
|
||||
/// <value>The current log level.</value>
|
||||
internal static LogLevel CurrentLogLevel => NetworkManager.Singleton == null ? LogLevel.Normal : NetworkManager.Singleton.LogLevel;
|
||||
|
||||
// MLAPI internal logging
|
||||
internal static void LogInfo(string message) => Debug.Log($"[MLAPI] {message}");
|
||||
internal static void LogWarning(string message) => Debug.LogWarning($"[MLAPI] {message}");
|
||||
internal static void LogError(string message) => Debug.LogError($"[MLAPI] {message}");
|
||||
|
||||
/// <summary>
|
||||
/// Logs an info log locally and on the server if possible.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log</param>
|
||||
public static void LogInfoServer(string message) => LogServer(message, LogType.Info);
|
||||
|
||||
/// <summary>
|
||||
/// Logs a warning log locally and on the server if possible.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log</param>
|
||||
public static void LogWarningServer(string message) => LogServer(message, LogType.Warning);
|
||||
|
||||
/// <summary>
|
||||
/// Logs an error log locally and on the server if possible.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log</param>
|
||||
public static void LogErrorServer(string message) => LogServer(message, LogType.Error);
|
||||
|
||||
private static void LogServer(string message, LogType logType)
|
||||
{
|
||||
// Get the sender of the local log
|
||||
ulong localId = NetworkManager.Singleton != null ? NetworkManager.Singleton.LocalClientId : 0;
|
||||
|
||||
switch (logType)
|
||||
{
|
||||
case LogType.Info:
|
||||
LogInfoServerLocal(message, localId);
|
||||
break;
|
||||
case LogType.Warning:
|
||||
LogWarningServerLocal(message, localId);
|
||||
break;
|
||||
case LogType.Error:
|
||||
LogErrorServerLocal(message, localId);
|
||||
break;
|
||||
}
|
||||
|
||||
if (NetworkManager.Singleton != null && !NetworkManager.Singleton.IsServer && NetworkManager.Singleton.NetworkConfig.EnableNetworkLogs)
|
||||
{
|
||||
using (var buffer = PooledNetworkBuffer.Get())
|
||||
using (var writer = PooledNetworkWriter.Get(buffer))
|
||||
{
|
||||
writer.WriteByte((byte)logType);
|
||||
writer.WriteStringPacked(message);
|
||||
|
||||
InternalMessageSender.Send(NetworkManager.Singleton.ServerClientId, NetworkConstants.SERVER_LOG, NetworkChannel.Internal, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void LogInfoServerLocal(string message, ulong sender) => Debug.Log($"[MLAPI_SERVER Sender={sender}] {message}");
|
||||
internal static void LogWarningServerLocal(string message, ulong sender) => Debug.LogWarning($"[MLAPI_SERVER Sender={sender}] {message}");
|
||||
internal static void LogErrorServerLocal(string message, ulong sender) => Debug.LogError($"[MLAPI_SERVER Sender={sender}] {message}");
|
||||
|
||||
internal enum LogType
|
||||
{
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2b33d7f8ddec3b140a4ff09659174e8d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 563c514253a555b48a68707decc5c088
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5b96fb2c0fcfc0845a71925f9375f3a5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,104 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Transports;
|
||||
using Unity.Profiling;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MLAPI.Messaging.Buffering
|
||||
{
|
||||
internal static class BufferManager
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
private static ProfilerMarker s_CleanBuffer = new ProfilerMarker($"{nameof(BufferManager)}.{nameof(CleanBuffer)}");
|
||||
#endif
|
||||
|
||||
private static Dictionary<ulong, Queue<BufferedMessage>> s_BufferQueues = new Dictionary<ulong, Queue<BufferedMessage>>();
|
||||
|
||||
internal struct BufferedMessage
|
||||
{
|
||||
internal ulong SenderClientId;
|
||||
internal NetworkChannel NetworkChannel;
|
||||
internal PooledNetworkBuffer NetworkBuffer;
|
||||
internal float ReceiveTime;
|
||||
internal float BufferTime;
|
||||
}
|
||||
|
||||
internal static Queue<BufferedMessage> ConsumeBuffersForNetworkId(ulong networkId)
|
||||
{
|
||||
if (s_BufferQueues.ContainsKey(networkId))
|
||||
{
|
||||
Queue<BufferedMessage> message = s_BufferQueues[networkId];
|
||||
|
||||
s_BufferQueues.Remove(networkId);
|
||||
|
||||
return message;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RecycleConsumedBufferedMessage(BufferedMessage message)
|
||||
{
|
||||
message.NetworkBuffer.Dispose();
|
||||
}
|
||||
|
||||
internal static void BufferMessageForNetworkId(ulong networkId, ulong senderClientId, NetworkChannel networkChannel, float receiveTime, ArraySegment<byte> payload)
|
||||
{
|
||||
if (!s_BufferQueues.ContainsKey(networkId))
|
||||
{
|
||||
s_BufferQueues.Add(networkId, new Queue<BufferedMessage>());
|
||||
}
|
||||
|
||||
Queue<BufferedMessage> queue = s_BufferQueues[networkId];
|
||||
|
||||
var payloadBuffer = PooledNetworkBuffer.Get();
|
||||
payloadBuffer.Write(payload.Array, payload.Offset, payload.Count);
|
||||
payloadBuffer.Position = 0;
|
||||
|
||||
queue.Enqueue(new BufferedMessage()
|
||||
{
|
||||
BufferTime = Time.realtimeSinceStartup,
|
||||
NetworkChannel = networkChannel,
|
||||
NetworkBuffer = payloadBuffer,
|
||||
ReceiveTime = receiveTime,
|
||||
SenderClientId = senderClientId
|
||||
});
|
||||
}
|
||||
|
||||
private static List<ulong> s_KeysToDestroy = new List<ulong>();
|
||||
|
||||
internal static void CleanBuffer()
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_CleanBuffer.Begin();
|
||||
#endif
|
||||
foreach (var pair in s_BufferQueues)
|
||||
{
|
||||
while (pair.Value.Count > 0 && Time.realtimeSinceStartup - pair.Value.Peek().BufferTime >= NetworkManager.Singleton.NetworkConfig.MessageBufferTimeout)
|
||||
{
|
||||
BufferedMessage message = pair.Value.Dequeue();
|
||||
|
||||
RecycleConsumedBufferedMessage(message);
|
||||
}
|
||||
|
||||
if (pair.Value.Count == 0)
|
||||
{
|
||||
s_KeysToDestroy.Add(pair.Key);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < s_KeysToDestroy.Count; i++)
|
||||
{
|
||||
s_BufferQueues.Remove(s_KeysToDestroy[i]);
|
||||
}
|
||||
|
||||
s_KeysToDestroy.Clear();
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_CleanBuffer.End();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6c9a727fa87a3a54092293945f782ada
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
using MLAPI.Transports;
|
||||
|
||||
namespace MLAPI.Messaging.Buffering
|
||||
{
|
||||
internal struct PreBufferPreset
|
||||
{
|
||||
public byte MessageType;
|
||||
public bool AllowBuffer;
|
||||
public ulong ClientId;
|
||||
public NetworkChannel NetworkChannel;
|
||||
public float ReceiveTime;
|
||||
public ArraySegment<byte> Data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ab6c3ab10d7bdb048a8c1cea7e8950b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,226 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using MLAPI.Configuration;
|
||||
using MLAPI.Logging;
|
||||
using MLAPI.Serialization;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Hashing;
|
||||
using MLAPI.Profiling;
|
||||
using MLAPI.Transports;
|
||||
|
||||
namespace MLAPI.Messaging
|
||||
{
|
||||
/// <summary>
|
||||
/// The manager class to manage custom messages, note that this is different from the NetworkManager custom messages.
|
||||
/// These are named and are much easier to use.
|
||||
/// </summary>
|
||||
public static class CustomMessagingManager
|
||||
{
|
||||
#region Unnamed
|
||||
|
||||
/// <summary>
|
||||
/// Delegate used for incoming unnamed messages
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId that sent the message</param>
|
||||
/// <param name="stream">The stream containing the message data</param>
|
||||
public delegate void UnnamedMessageDelegate(ulong clientId, Stream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked when unnamed messages arrive
|
||||
/// </summary>
|
||||
public static event UnnamedMessageDelegate OnUnnamedMessage;
|
||||
|
||||
internal static void InvokeUnnamedMessage(ulong clientId, Stream stream) => OnUnnamedMessage?.Invoke(clientId, stream);
|
||||
|
||||
/// <summary>
|
||||
/// Sends unnamed message to a list of clients
|
||||
/// </summary>
|
||||
/// <param name="clientIds">The clients to send to, sends to everyone if null</param>
|
||||
/// <param name="buffer">The message stream containing the data</param>
|
||||
/// <param name="networkChannel">The channel to send the data on</param>
|
||||
public static void SendUnnamedMessage(List<ulong> clientIds, NetworkBuffer buffer, NetworkChannel networkChannel = NetworkChannel.Internal)
|
||||
{
|
||||
if (!NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error) NetworkLog.LogWarning("Can not send unnamed messages to multiple users as a client");
|
||||
return;
|
||||
}
|
||||
|
||||
InternalMessageSender.Send(NetworkConstants.UNNAMED_MESSAGE, networkChannel, clientIds, buffer);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.UnnamedMessageSent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a unnamed message to a specific client
|
||||
/// </summary>
|
||||
/// <param name="clientId">The client to send the message to</param>
|
||||
/// <param name="buffer">The message stream containing the data</param>
|
||||
/// <param name="networkChannel">The channel tos end the data on</param>
|
||||
public static void SendUnnamedMessage(ulong clientId, NetworkBuffer buffer, NetworkChannel networkChannel = NetworkChannel.Internal)
|
||||
{
|
||||
InternalMessageSender.Send(clientId, NetworkConstants.UNNAMED_MESSAGE, networkChannel, buffer);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.UnnamedMessageSent);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Named
|
||||
|
||||
/// <summary>
|
||||
/// Delegate used to handle named messages
|
||||
/// </summary>
|
||||
public delegate void HandleNamedMessageDelegate(ulong sender, Stream payload);
|
||||
|
||||
private static Dictionary<ulong, HandleNamedMessageDelegate> s_NamedMessageHandlers16 = new Dictionary<ulong, HandleNamedMessageDelegate>();
|
||||
private static Dictionary<ulong, HandleNamedMessageDelegate> s_NamedMessageHandlers32 = new Dictionary<ulong, HandleNamedMessageDelegate>();
|
||||
private static Dictionary<ulong, HandleNamedMessageDelegate> s_NamedMessageHandlers64 = new Dictionary<ulong, HandleNamedMessageDelegate>();
|
||||
|
||||
internal static void InvokeNamedMessage(ulong hash, ulong sender, Stream stream)
|
||||
{
|
||||
if (NetworkManager.Singleton == null)
|
||||
{
|
||||
// We dont know what size to use. Try every (more collision prone)
|
||||
if (s_NamedMessageHandlers16.ContainsKey(hash))
|
||||
{
|
||||
s_NamedMessageHandlers16[hash](sender, stream);
|
||||
}
|
||||
|
||||
if (s_NamedMessageHandlers32.ContainsKey(hash))
|
||||
{
|
||||
s_NamedMessageHandlers32[hash](sender, stream);
|
||||
}
|
||||
|
||||
if (s_NamedMessageHandlers64.ContainsKey(hash))
|
||||
{
|
||||
s_NamedMessageHandlers64[hash](sender, stream);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only check the right size.
|
||||
if (NetworkManager.Singleton.NetworkConfig.RpcHashSize == HashSize.VarIntTwoBytes)
|
||||
{
|
||||
if (s_NamedMessageHandlers16.ContainsKey(hash))
|
||||
{
|
||||
s_NamedMessageHandlers16[hash](sender, stream);
|
||||
}
|
||||
}
|
||||
else if (NetworkManager.Singleton.NetworkConfig.RpcHashSize == HashSize.VarIntFourBytes)
|
||||
{
|
||||
if (s_NamedMessageHandlers32.ContainsKey(hash))
|
||||
{
|
||||
s_NamedMessageHandlers32[hash](sender, stream);
|
||||
}
|
||||
}
|
||||
else if (NetworkManager.Singleton.NetworkConfig.RpcHashSize == HashSize.VarIntEightBytes)
|
||||
{
|
||||
if (s_NamedMessageHandlers64.ContainsKey(hash))
|
||||
{
|
||||
s_NamedMessageHandlers64[hash](sender, stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a named message handler delegate.
|
||||
/// </summary>
|
||||
/// <param name="name">Name of the message.</param>
|
||||
/// <param name="callback">The callback to run when a named message is received.</param>
|
||||
public static void RegisterNamedMessageHandler(string name, HandleNamedMessageDelegate callback)
|
||||
{
|
||||
s_NamedMessageHandlers16[name.GetStableHash16()] = callback;
|
||||
s_NamedMessageHandlers32[name.GetStableHash32()] = callback;
|
||||
s_NamedMessageHandlers64[name.GetStableHash64()] = callback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a named message handler.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the message.</param>
|
||||
public static void UnregisterNamedMessageHandler(string name)
|
||||
{
|
||||
s_NamedMessageHandlers16.Remove(name.GetStableHash16());
|
||||
s_NamedMessageHandlers32.Remove(name.GetStableHash32());
|
||||
s_NamedMessageHandlers64.Remove(name.GetStableHash64());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a named message
|
||||
/// </summary>
|
||||
/// <param name="name">The message name to send</param>
|
||||
/// <param name="clientId">The client to send the message to</param>
|
||||
/// <param name="stream">The message stream containing the data</param>
|
||||
/// <param name="networkChannel">The channel to send the data on</param>
|
||||
public static void SendNamedMessage(string name, ulong clientId, Stream stream, NetworkChannel networkChannel = NetworkChannel.Internal)
|
||||
{
|
||||
ulong hash = 0;
|
||||
switch (NetworkManager.Singleton.NetworkConfig.RpcHashSize)
|
||||
{
|
||||
case HashSize.VarIntTwoBytes:
|
||||
hash = name.GetStableHash16();
|
||||
break;
|
||||
case HashSize.VarIntFourBytes:
|
||||
hash = name.GetStableHash32();
|
||||
break;
|
||||
case HashSize.VarIntEightBytes:
|
||||
hash = name.GetStableHash64();
|
||||
break;
|
||||
}
|
||||
|
||||
using (var messageBuffer = PooledNetworkBuffer.Get())
|
||||
using (var writer = PooledNetworkWriter.Get(messageBuffer))
|
||||
{
|
||||
writer.WriteUInt64Packed(hash);
|
||||
|
||||
messageBuffer.CopyFrom(stream);
|
||||
|
||||
InternalMessageSender.Send(clientId, NetworkConstants.NAMED_MESSAGE, networkChannel, messageBuffer);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.NamedMessageSent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends the named message
|
||||
/// </summary>
|
||||
/// <param name="name">The message name to send</param>
|
||||
/// <param name="clientIds">The clients to send to, sends to everyone if null</param>
|
||||
/// <param name="stream">The message stream containing the data</param>
|
||||
/// <param name="networkChannel">The channel to send the data on</param>
|
||||
public static void SendNamedMessage(string name, List<ulong> clientIds, Stream stream, NetworkChannel networkChannel = NetworkChannel.Internal)
|
||||
{
|
||||
ulong hash = 0;
|
||||
switch (NetworkManager.Singleton.NetworkConfig.RpcHashSize)
|
||||
{
|
||||
case HashSize.VarIntTwoBytes:
|
||||
hash = name.GetStableHash16();
|
||||
break;
|
||||
case HashSize.VarIntFourBytes:
|
||||
hash = name.GetStableHash32();
|
||||
break;
|
||||
case HashSize.VarIntEightBytes:
|
||||
hash = name.GetStableHash64();
|
||||
break;
|
||||
}
|
||||
|
||||
using (var messageBuffer = PooledNetworkBuffer.Get())
|
||||
using (var writer = PooledNetworkWriter.Get(messageBuffer))
|
||||
{
|
||||
writer.WriteUInt64Packed(hash);
|
||||
|
||||
messageBuffer.CopyFrom(stream);
|
||||
|
||||
if (!NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Error) NetworkLog.LogWarning("Can not send named messages to multiple users as a client");
|
||||
return;
|
||||
}
|
||||
|
||||
InternalMessageSender.Send(NetworkConstants.NAMED_MESSAGE, networkChannel, clientIds, messageBuffer);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.NamedMessageSent);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 15c647dad40a44d46886dca112cfd524
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,642 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using MLAPI.Connection;
|
||||
using MLAPI.Logging;
|
||||
using MLAPI.SceneManagement;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Spawning;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.SceneManagement;
|
||||
using System.Collections.Generic;
|
||||
using MLAPI.Configuration;
|
||||
using MLAPI.Messaging.Buffering;
|
||||
using MLAPI.Profiling;
|
||||
using MLAPI.Serialization;
|
||||
using Unity.Profiling;
|
||||
|
||||
namespace MLAPI.Messaging
|
||||
{
|
||||
internal static class InternalMessageHandler
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
private static ProfilerMarker s_HandleConnectionRequest = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleConnectionRequest)}");
|
||||
private static ProfilerMarker s_HandleConnectionApproved = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleConnectionApproved)}");
|
||||
private static ProfilerMarker s_HandleAddObject = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleAddObject)}");
|
||||
private static ProfilerMarker s_HandleDestroyObject = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleDestroyObject)}");
|
||||
private static ProfilerMarker s_HandleSwitchScene = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleSwitchScene)}");
|
||||
private static ProfilerMarker s_HandleClientSwitchSceneCompleted = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleClientSwitchSceneCompleted)}");
|
||||
private static ProfilerMarker s_HandleChangeOwner = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleChangeOwner)}");
|
||||
private static ProfilerMarker s_HandleAddObjects = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleAddObjects)}");
|
||||
private static ProfilerMarker s_HandleDestroyObjects = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleDestroyObjects)}");
|
||||
private static ProfilerMarker s_HandleTimeSync = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleTimeSync)}");
|
||||
private static ProfilerMarker s_HandleNetworkVariableDelta = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleNetworkVariableDelta)}");
|
||||
private static ProfilerMarker s_HandleNetworkVariableUpdate = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleNetworkVariableUpdate)}");
|
||||
private static ProfilerMarker s_HandleUnnamedMessage = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleUnnamedMessage)}");
|
||||
private static ProfilerMarker s_HandleNamedMessage = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleNamedMessage)}");
|
||||
private static ProfilerMarker s_HandleNetworkLog = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(HandleNetworkLog)}");
|
||||
private static ProfilerMarker s_RpcReceiveQueueItemServerRpc = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(RpcReceiveQueueItem)}.{nameof(RpcQueueContainer.QueueItemType.ServerRpc)}");
|
||||
private static ProfilerMarker s_RpcReceiveQueueItemClientRpc = new ProfilerMarker($"{nameof(InternalMessageHandler)}.{nameof(RpcReceiveQueueItem)}.{nameof(RpcQueueContainer.QueueItemType.ClientRpc)}");
|
||||
#endif
|
||||
|
||||
internal static void HandleConnectionRequest(ulong clientId, Stream stream)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleConnectionRequest.Begin();
|
||||
#endif
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
ulong configHash = reader.ReadUInt64Packed();
|
||||
if (!NetworkManager.Singleton.NetworkConfig.CompareConfig(configHash))
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkConfig)} mismatch. The configuration between the server and client does not match");
|
||||
}
|
||||
|
||||
NetworkManager.Singleton.DisconnectClient(clientId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.ConnectionApproval)
|
||||
{
|
||||
byte[] connectionBuffer = reader.ReadByteArray();
|
||||
NetworkManager.Singleton.InvokeConnectionApproval(connectionBuffer, clientId, (createPlayerObject, playerPrefabHash, approved, position, rotation) => { NetworkManager.Singleton.HandleApproval(clientId, createPlayerObject, playerPrefabHash, approved, position, rotation); });
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkManager.Singleton.HandleApproval(clientId, NetworkManager.Singleton.NetworkConfig.CreatePlayerPrefab, null, true, null, null);
|
||||
}
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleConnectionRequest.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleConnectionApproved(ulong clientId, Stream stream, float receiveTime)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleConnectionApproved.Begin();
|
||||
#endif
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
NetworkManager.Singleton.LocalClientId = reader.ReadUInt64Packed();
|
||||
|
||||
uint sceneIndex = 0;
|
||||
Guid sceneSwitchProgressGuid = new Guid();
|
||||
|
||||
if (NetworkManager.Singleton.NetworkConfig.EnableSceneManagement)
|
||||
{
|
||||
sceneIndex = reader.ReadUInt32Packed();
|
||||
sceneSwitchProgressGuid = new Guid(reader.ReadByteArray());
|
||||
}
|
||||
|
||||
bool sceneSwitch = NetworkManager.Singleton.NetworkConfig.EnableSceneManagement && NetworkSceneManager.HasSceneMismatch(sceneIndex);
|
||||
|
||||
float netTime = reader.ReadSinglePacked();
|
||||
NetworkManager.Singleton.UpdateNetworkTime(clientId, netTime, receiveTime, true);
|
||||
|
||||
NetworkManager.Singleton.ConnectedClients.Add(NetworkManager.Singleton.LocalClientId, new NetworkClient { ClientId = NetworkManager.Singleton.LocalClientId });
|
||||
|
||||
|
||||
void DelayedSpawnAction(Stream continuationStream)
|
||||
{
|
||||
using (var continuationReader = PooledNetworkReader.Get(continuationStream))
|
||||
{
|
||||
if (!NetworkManager.Singleton.NetworkConfig.EnableSceneManagement || NetworkManager.Singleton.NetworkConfig.UsePrefabSync)
|
||||
{
|
||||
NetworkSpawnManager.DestroySceneObjects();
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkSpawnManager.ClientCollectSoftSyncSceneObjectSweep(null);
|
||||
}
|
||||
|
||||
uint objectCount = continuationReader.ReadUInt32Packed();
|
||||
for (int i = 0; i < objectCount; i++)
|
||||
{
|
||||
bool isPlayerObject = continuationReader.ReadBool();
|
||||
ulong networkId = continuationReader.ReadUInt64Packed();
|
||||
ulong ownerId = continuationReader.ReadUInt64Packed();
|
||||
bool hasParent = continuationReader.ReadBool();
|
||||
ulong? parentNetworkId = null;
|
||||
|
||||
if (hasParent)
|
||||
{
|
||||
parentNetworkId = continuationReader.ReadUInt64Packed();
|
||||
}
|
||||
|
||||
ulong prefabHash;
|
||||
ulong instanceId;
|
||||
bool softSync;
|
||||
|
||||
if (!NetworkManager.Singleton.NetworkConfig.EnableSceneManagement || NetworkManager.Singleton.NetworkConfig.UsePrefabSync)
|
||||
{
|
||||
softSync = false;
|
||||
instanceId = 0;
|
||||
prefabHash = continuationReader.ReadUInt64Packed();
|
||||
}
|
||||
else
|
||||
{
|
||||
softSync = continuationReader.ReadBool();
|
||||
|
||||
if (softSync)
|
||||
{
|
||||
instanceId = continuationReader.ReadUInt64Packed();
|
||||
prefabHash = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
prefabHash = continuationReader.ReadUInt64Packed();
|
||||
instanceId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Vector3? pos = null;
|
||||
Quaternion? rot = null;
|
||||
if (continuationReader.ReadBool())
|
||||
{
|
||||
pos = new Vector3(continuationReader.ReadSinglePacked(), continuationReader.ReadSinglePacked(), continuationReader.ReadSinglePacked());
|
||||
rot = Quaternion.Euler(continuationReader.ReadSinglePacked(), continuationReader.ReadSinglePacked(), continuationReader.ReadSinglePacked());
|
||||
}
|
||||
|
||||
var networkObject = NetworkSpawnManager.CreateLocalNetworkObject(softSync, instanceId, prefabHash, parentNetworkId, pos, rot);
|
||||
NetworkSpawnManager.SpawnNetworkObjectLocally(networkObject, networkId, softSync, isPlayerObject, ownerId, continuationStream, false, 0, true, false);
|
||||
|
||||
Queue<BufferManager.BufferedMessage> bufferQueue = BufferManager.ConsumeBuffersForNetworkId(networkId);
|
||||
|
||||
// Apply buffered messages
|
||||
if (bufferQueue != null)
|
||||
{
|
||||
while (bufferQueue.Count > 0)
|
||||
{
|
||||
BufferManager.BufferedMessage message = bufferQueue.Dequeue();
|
||||
NetworkManager.Singleton.HandleIncomingData(message.SenderClientId, message.NetworkChannel, new ArraySegment<byte>(message.NetworkBuffer.GetBuffer(), (int)message.NetworkBuffer.Position, (int)message.NetworkBuffer.Length), message.ReceiveTime, false);
|
||||
BufferManager.RecycleConsumedBufferedMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NetworkSpawnManager.CleanDiffedSceneObjects();
|
||||
NetworkManager.Singleton.IsConnectedClient = true;
|
||||
NetworkManager.Singleton.InvokeOnClientConnectedCallback(NetworkManager.Singleton.LocalClientId);
|
||||
}
|
||||
}
|
||||
|
||||
if (sceneSwitch)
|
||||
{
|
||||
UnityAction<Scene, Scene> onSceneLoaded = null;
|
||||
|
||||
var continuationBuffer = new NetworkBuffer();
|
||||
continuationBuffer.CopyUnreadFrom(stream);
|
||||
continuationBuffer.Position = 0;
|
||||
|
||||
void OnSceneLoadComplete()
|
||||
{
|
||||
SceneManager.activeSceneChanged -= onSceneLoaded;
|
||||
NetworkSceneManager.IsSpawnedObjectsPendingInDontDestroyOnLoad = false;
|
||||
DelayedSpawnAction(continuationBuffer);
|
||||
}
|
||||
|
||||
onSceneLoaded = (oldScene, newScene) => { OnSceneLoadComplete(); };
|
||||
SceneManager.activeSceneChanged += onSceneLoaded;
|
||||
NetworkSceneManager.OnFirstSceneSwitchSync(sceneIndex, sceneSwitchProgressGuid);
|
||||
}
|
||||
else
|
||||
{
|
||||
DelayedSpawnAction(stream);
|
||||
}
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleConnectionApproved.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleAddObject(ulong clientId, Stream stream)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleAddObject.Begin();
|
||||
#endif
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
bool isPlayerObject = reader.ReadBool();
|
||||
ulong networkId = reader.ReadUInt64Packed();
|
||||
ulong ownerId = reader.ReadUInt64Packed();
|
||||
bool hasParent = reader.ReadBool();
|
||||
ulong? parentNetworkId = null;
|
||||
|
||||
if (hasParent)
|
||||
{
|
||||
parentNetworkId = reader.ReadUInt64Packed();
|
||||
}
|
||||
|
||||
ulong prefabHash;
|
||||
ulong instanceId;
|
||||
bool softSync;
|
||||
|
||||
if (!NetworkManager.Singleton.NetworkConfig.EnableSceneManagement || NetworkManager.Singleton.NetworkConfig.UsePrefabSync)
|
||||
{
|
||||
softSync = false;
|
||||
instanceId = 0;
|
||||
prefabHash = reader.ReadUInt64Packed();
|
||||
}
|
||||
else
|
||||
{
|
||||
softSync = reader.ReadBool();
|
||||
|
||||
if (softSync)
|
||||
{
|
||||
instanceId = reader.ReadUInt64Packed();
|
||||
prefabHash = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
prefabHash = reader.ReadUInt64Packed();
|
||||
instanceId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Vector3? pos = null;
|
||||
Quaternion? rot = null;
|
||||
if (reader.ReadBool())
|
||||
{
|
||||
pos = new Vector3(reader.ReadSinglePacked(), reader.ReadSinglePacked(), reader.ReadSinglePacked());
|
||||
rot = Quaternion.Euler(reader.ReadSinglePacked(), reader.ReadSinglePacked(), reader.ReadSinglePacked());
|
||||
}
|
||||
|
||||
bool hasPayload = reader.ReadBool();
|
||||
int payLoadLength = hasPayload ? reader.ReadInt32Packed() : 0;
|
||||
|
||||
var networkObject = NetworkSpawnManager.CreateLocalNetworkObject(softSync, instanceId, prefabHash, parentNetworkId, pos, rot);
|
||||
NetworkSpawnManager.SpawnNetworkObjectLocally(networkObject, networkId, softSync, isPlayerObject, ownerId, stream, hasPayload, payLoadLength, true, false);
|
||||
|
||||
Queue<BufferManager.BufferedMessage> bufferQueue = BufferManager.ConsumeBuffersForNetworkId(networkId);
|
||||
|
||||
// Apply buffered messages
|
||||
if (bufferQueue != null)
|
||||
{
|
||||
while (bufferQueue.Count > 0)
|
||||
{
|
||||
BufferManager.BufferedMessage message = bufferQueue.Dequeue();
|
||||
NetworkManager.Singleton.HandleIncomingData(message.SenderClientId, message.NetworkChannel, new ArraySegment<byte>(message.NetworkBuffer.GetBuffer(), (int)message.NetworkBuffer.Position, (int)message.NetworkBuffer.Length), message.ReceiveTime, false);
|
||||
BufferManager.RecycleConsumedBufferedMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleAddObject.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleDestroyObject(ulong clientId, Stream stream)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleDestroyObject.Begin();
|
||||
#endif
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
ulong networkId = reader.ReadUInt64Packed();
|
||||
NetworkSpawnManager.OnDestroyObject(networkId, true);
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleDestroyObject.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleSwitchScene(ulong clientId, Stream stream)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleSwitchScene.Begin();
|
||||
#endif
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
uint sceneIndex = reader.ReadUInt32Packed();
|
||||
Guid switchSceneGuid = new Guid(reader.ReadByteArray());
|
||||
|
||||
var objectBuffer = new NetworkBuffer();
|
||||
objectBuffer.CopyUnreadFrom(stream);
|
||||
objectBuffer.Position = 0;
|
||||
|
||||
NetworkSceneManager.OnSceneSwitch(sceneIndex, switchSceneGuid, objectBuffer);
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleSwitchScene.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleClientSwitchSceneCompleted(ulong clientId, Stream stream)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleClientSwitchSceneCompleted.Begin();
|
||||
#endif
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
NetworkSceneManager.OnClientSwitchSceneCompleted(clientId, new Guid(reader.ReadByteArray()));
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleClientSwitchSceneCompleted.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleChangeOwner(ulong clientId, Stream stream)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleChangeOwner.Begin();
|
||||
#endif
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
ulong networkId = reader.ReadUInt64Packed();
|
||||
ulong ownerClientId = reader.ReadUInt64Packed();
|
||||
|
||||
if (NetworkSpawnManager.SpawnedObjects[networkId].OwnerClientId == NetworkManager.Singleton.LocalClientId)
|
||||
{
|
||||
//We are current owner.
|
||||
NetworkSpawnManager.SpawnedObjects[networkId].InvokeBehaviourOnLostOwnership();
|
||||
}
|
||||
|
||||
if (ownerClientId == NetworkManager.Singleton.LocalClientId)
|
||||
{
|
||||
//We are new owner.
|
||||
NetworkSpawnManager.SpawnedObjects[networkId].InvokeBehaviourOnGainedOwnership();
|
||||
}
|
||||
|
||||
NetworkSpawnManager.SpawnedObjects[networkId].OwnerClientId = ownerClientId;
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleChangeOwner.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleAddObjects(ulong clientId, Stream stream)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleAddObjects.Begin();
|
||||
#endif
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
ushort objectCount = reader.ReadUInt16Packed();
|
||||
|
||||
for (int i = 0; i < objectCount; i++)
|
||||
{
|
||||
HandleAddObject(clientId, stream);
|
||||
}
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleAddObjects.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleDestroyObjects(ulong clientId, Stream stream)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleDestroyObjects.Begin();
|
||||
#endif
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
ushort objectCount = reader.ReadUInt16Packed();
|
||||
|
||||
for (int i = 0; i < objectCount; i++)
|
||||
{
|
||||
HandleDestroyObject(clientId, stream);
|
||||
}
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleDestroyObjects.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleTimeSync(ulong clientId, Stream stream, float receiveTime)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleTimeSync.Begin();
|
||||
#endif
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
float netTime = reader.ReadSinglePacked();
|
||||
NetworkManager.Singleton.UpdateNetworkTime(clientId, netTime, receiveTime);
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleTimeSync.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleNetworkVariableDelta(ulong clientId, Stream stream, Action<ulong, PreBufferPreset> bufferCallback, PreBufferPreset bufferPreset)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleNetworkVariableDelta.Begin();
|
||||
#endif
|
||||
if (!NetworkManager.Singleton.NetworkConfig.EnableNetworkVariable)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_DELTA)} received but {nameof(NetworkConfig.EnableNetworkVariable)} is false");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
ulong networkObjectId = reader.ReadUInt64Packed();
|
||||
ushort networkBehaviourIndex = reader.ReadUInt16Packed();
|
||||
|
||||
if (NetworkSpawnManager.SpawnedObjects.ContainsKey(networkObjectId))
|
||||
{
|
||||
NetworkBehaviour instance = NetworkSpawnManager.SpawnedObjects[networkObjectId].GetNetworkBehaviourAtOrderIndex(networkBehaviourIndex);
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_DELTA)} message received for a non-existent behaviour. {nameof(networkObjectId)}: {networkObjectId}, {nameof(networkBehaviourIndex)}: {networkBehaviourIndex}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkBehaviour.HandleNetworkVariableDeltas(instance.NetworkVariableFields, stream, clientId, instance);
|
||||
}
|
||||
}
|
||||
else if (NetworkManager.Singleton.IsServer || !NetworkManager.Singleton.NetworkConfig.EnableMessageBuffering)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_DELTA)} message received for a non-existent object with {nameof(networkObjectId)}: {networkObjectId}. This delta was lost.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_DELTA)} message received for a non-existent object with {nameof(networkObjectId)}: {networkObjectId}. This delta will be buffered and might be recovered.");
|
||||
}
|
||||
|
||||
bufferCallback(networkObjectId, bufferPreset);
|
||||
}
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleNetworkVariableDelta.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleNetworkVariableUpdate(ulong clientId, Stream stream, Action<ulong, PreBufferPreset> bufferCallback, PreBufferPreset bufferPreset)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleNetworkVariableUpdate.Begin();
|
||||
#endif
|
||||
if (!NetworkManager.Singleton.NetworkConfig.EnableNetworkVariable)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_UPDATE)} update received but {nameof(NetworkConfig.EnableNetworkVariable)} is false");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
ulong networkObjectId = reader.ReadUInt64Packed();
|
||||
ushort networkBehaviourIndex = reader.ReadUInt16Packed();
|
||||
|
||||
if (NetworkSpawnManager.SpawnedObjects.ContainsKey(networkObjectId))
|
||||
{
|
||||
var networkBehaviour = NetworkSpawnManager.SpawnedObjects[networkObjectId].GetNetworkBehaviourAtOrderIndex(networkBehaviourIndex);
|
||||
|
||||
if (networkBehaviour == null)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_UPDATE)} message received for a non-existent behaviour. {nameof(networkObjectId)}: {networkObjectId}, {nameof(networkBehaviourIndex)}: {networkBehaviourIndex}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NetworkBehaviour.HandleNetworkVariableUpdate(networkBehaviour.NetworkVariableFields, stream, clientId, networkBehaviour);
|
||||
}
|
||||
}
|
||||
else if (NetworkManager.Singleton.IsServer || !NetworkManager.Singleton.NetworkConfig.EnableMessageBuffering)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_UPDATE)} message received for a non-existent object with {nameof(networkObjectId)}: {networkObjectId}. This delta was lost.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
||||
{
|
||||
NetworkLog.LogWarning($"{nameof(NetworkConstants.NETWORK_VARIABLE_UPDATE)} message received for a non-existent object with {nameof(networkObjectId)}: {networkObjectId}. This delta will be buffered and might be recovered.");
|
||||
}
|
||||
|
||||
bufferCallback(networkObjectId, bufferPreset);
|
||||
}
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleNetworkVariableUpdate.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the stream to a PerformanceQueueItem and adds it to the receive queue
|
||||
/// </summary>
|
||||
/// <param name="clientId"></param>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="receiveTime"></param>
|
||||
internal static void RpcReceiveQueueItem(ulong clientId, Stream stream, float receiveTime, RpcQueueContainer.QueueItemType queueItemType)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer && clientId == NetworkManager.Singleton.ServerClientId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ProfilerStatManager.RpcsRcvd.Record();
|
||||
PerformanceDataManager.Increment(ProfilerConstants.RpcReceived);
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
switch (queueItemType)
|
||||
{
|
||||
case RpcQueueContainer.QueueItemType.ServerRpc:
|
||||
s_RpcReceiveQueueItemServerRpc.Begin();
|
||||
break;
|
||||
case RpcQueueContainer.QueueItemType.ClientRpc:
|
||||
s_RpcReceiveQueueItemClientRpc.Begin();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
var rpcQueueContainer = NetworkManager.Singleton.RpcQueueContainer;
|
||||
rpcQueueContainer.AddQueueItemToInboundFrame(queueItemType, receiveTime, clientId, (NetworkBuffer)stream);
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
switch (queueItemType)
|
||||
{
|
||||
case RpcQueueContainer.QueueItemType.ServerRpc:
|
||||
s_RpcReceiveQueueItemServerRpc.End();
|
||||
break;
|
||||
case RpcQueueContainer.QueueItemType.ClientRpc:
|
||||
s_RpcReceiveQueueItemClientRpc.End();
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleUnnamedMessage(ulong clientId, Stream stream)
|
||||
{
|
||||
PerformanceDataManager.Increment(ProfilerConstants.UnnamedMessageReceived);
|
||||
ProfilerStatManager.UnnamedMessage.Record();
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleUnnamedMessage.Begin();
|
||||
#endif
|
||||
CustomMessagingManager.InvokeUnnamedMessage(clientId, stream);
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleUnnamedMessage.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleNamedMessage(ulong clientId, Stream stream)
|
||||
{
|
||||
PerformanceDataManager.Increment(ProfilerConstants.NamedMessageReceived);
|
||||
ProfilerStatManager.NamedMessage.Record();
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleNamedMessage.Begin();
|
||||
#endif
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
ulong hash = reader.ReadUInt64Packed();
|
||||
|
||||
CustomMessagingManager.InvokeNamedMessage(hash, clientId, stream);
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleNamedMessage.End();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static void HandleNetworkLog(ulong clientId, Stream stream)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleNetworkLog.Begin();
|
||||
#endif
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
NetworkLog.LogType logType = (NetworkLog.LogType)reader.ReadByte();
|
||||
string message = reader.ReadStringPacked();
|
||||
|
||||
switch (logType)
|
||||
{
|
||||
case NetworkLog.LogType.Info:
|
||||
NetworkLog.LogInfoServerLocal(message, clientId);
|
||||
break;
|
||||
case NetworkLog.LogType.Warning:
|
||||
NetworkLog.LogWarningServerLocal(message, clientId);
|
||||
break;
|
||||
case NetworkLog.LogType.Error:
|
||||
NetworkLog.LogErrorServerLocal(message, clientId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_HandleNetworkLog.End();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1b0dde7481361454ab22f5fca90c4c24
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,118 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MLAPI.Configuration;
|
||||
using MLAPI.Internal;
|
||||
using MLAPI.Profiling;
|
||||
using MLAPI.Serialization;
|
||||
using MLAPI.Transports;
|
||||
|
||||
namespace MLAPI.Messaging
|
||||
{
|
||||
internal static class InternalMessageSender
|
||||
{
|
||||
internal static void Send(ulong clientId, byte messageType, NetworkChannel networkChannel, NetworkBuffer messageBuffer)
|
||||
{
|
||||
messageBuffer.PadBuffer();
|
||||
|
||||
if (NetworkManager.Singleton.IsServer && clientId == NetworkManager.Singleton.ServerClientId) return;
|
||||
|
||||
using (var buffer = MessagePacker.WrapMessage(messageType, messageBuffer))
|
||||
{
|
||||
NetworkProfiler.StartEvent(TickType.Send, (uint)buffer.Length, networkChannel, NetworkConstants.MESSAGE_NAMES[messageType]);
|
||||
|
||||
NetworkManager.Singleton.NetworkConfig.NetworkTransport.Send(clientId, new ArraySegment<byte>(buffer.GetBuffer(), 0, (int)buffer.Length), networkChannel);
|
||||
ProfilerStatManager.BytesSent.Record((int)buffer.Length);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.ByteSent, (int)buffer.Length);
|
||||
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
NetworkProfiler.EndEvent();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Send(byte messageType, NetworkChannel networkChannel, NetworkBuffer messageBuffer)
|
||||
{
|
||||
messageBuffer.PadBuffer();
|
||||
|
||||
using (var buffer = MessagePacker.WrapMessage(messageType, messageBuffer))
|
||||
{
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
NetworkProfiler.StartEvent(TickType.Send, (uint)buffer.Length, networkChannel, NetworkConstants.MESSAGE_NAMES[messageType]);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < NetworkManager.Singleton.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer && NetworkManager.Singleton.ConnectedClientsList[i].ClientId == NetworkManager.Singleton.ServerClientId) continue;
|
||||
|
||||
NetworkManager.Singleton.NetworkConfig.NetworkTransport.Send(NetworkManager.Singleton.ConnectedClientsList[i].ClientId, new ArraySegment<byte>(buffer.GetBuffer(), 0, (int)buffer.Length), networkChannel);
|
||||
ProfilerStatManager.BytesSent.Record((int)buffer.Length);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.ByteSent, (int)buffer.Length);
|
||||
}
|
||||
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
NetworkProfiler.EndEvent();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Send(byte messageType, NetworkChannel networkChannel, List<ulong> clientIds, NetworkBuffer messageBuffer)
|
||||
{
|
||||
if (clientIds == null)
|
||||
{
|
||||
Send(messageType, networkChannel, messageBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
messageBuffer.PadBuffer();
|
||||
|
||||
using (var buffer = MessagePacker.WrapMessage(messageType, messageBuffer))
|
||||
{
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
NetworkProfiler.StartEvent(TickType.Send, (uint)buffer.Length, networkChannel, NetworkConstants.MESSAGE_NAMES[messageType]);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < clientIds.Count; i++)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer && clientIds[i] == NetworkManager.Singleton.ServerClientId) continue;
|
||||
|
||||
NetworkManager.Singleton.NetworkConfig.NetworkTransport.Send(clientIds[i], new ArraySegment<byte>(buffer.GetBuffer(), 0, (int)buffer.Length), networkChannel);
|
||||
ProfilerStatManager.BytesSent.Record((int)buffer.Length);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.ByteSent, (int)buffer.Length);
|
||||
}
|
||||
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
NetworkProfiler.EndEvent();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
internal static void Send(byte messageType, NetworkChannel networkChannel, ulong clientIdToIgnore, NetworkBuffer messageBuffer)
|
||||
{
|
||||
messageBuffer.PadBuffer();
|
||||
|
||||
using (var buffer = MessagePacker.WrapMessage(messageType, messageBuffer))
|
||||
{
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
NetworkProfiler.StartEvent(TickType.Send, (uint)buffer.Length, networkChannel, NetworkConstants.MESSAGE_NAMES[messageType]);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < NetworkManager.Singleton.ConnectedClientsList.Count; i++)
|
||||
{
|
||||
if (NetworkManager.Singleton.ConnectedClientsList[i].ClientId == clientIdToIgnore ||
|
||||
(NetworkManager.Singleton.IsServer && NetworkManager.Singleton.ConnectedClientsList[i].ClientId == NetworkManager.Singleton.ServerClientId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NetworkManager.Singleton.NetworkConfig.NetworkTransport.Send(NetworkManager.Singleton.ConnectedClientsList[i].ClientId, new ArraySegment<byte>(buffer.GetBuffer(), 0, (int)buffer.Length), networkChannel);
|
||||
ProfilerStatManager.BytesSent.Record((int)buffer.Length);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.ByteSent, (int)buffer.Length);
|
||||
}
|
||||
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
NetworkProfiler.EndEvent();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ac70d2c412d5de844a7e2ebdc3fe5123
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,43 @@
|
|||
using MLAPI.Logging;
|
||||
using MLAPI.Serialization;
|
||||
using MLAPI.Configuration;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
|
||||
namespace MLAPI.Internal
|
||||
{
|
||||
internal static class MessagePacker
|
||||
{
|
||||
// This method is responsible for unwrapping a message, that is extracting the messagebody.
|
||||
internal static NetworkBuffer UnwrapMessage(NetworkBuffer inputBuffer, out byte messageType)
|
||||
{
|
||||
using (var inputHeaderReader = PooledNetworkReader.Get(inputBuffer))
|
||||
{
|
||||
if (inputBuffer.Length < 1)
|
||||
{
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) NetworkLog.LogError("The incoming message was too small");
|
||||
messageType = NetworkConstants.INVALID;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
messageType = inputHeaderReader.ReadByteDirect();
|
||||
|
||||
// The input stream is now ready to be read from. It's "safe" and has the correct position
|
||||
return inputBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
internal static NetworkBuffer WrapMessage(byte messageType, NetworkBuffer messageBody)
|
||||
{
|
||||
var outStream = PooledNetworkBuffer.Get();
|
||||
|
||||
using (var outWriter = PooledNetworkWriter.Get(outStream))
|
||||
{
|
||||
outWriter.WriteByte(messageType);
|
||||
outStream.Write(messageBody.GetBuffer(), 0, (int)messageBody.Length);
|
||||
}
|
||||
|
||||
return outStream;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3611708e433eef74c9d8dfb6c311d61e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Messaging
|
||||
{
|
||||
/// <summary>
|
||||
/// RPC delivery types
|
||||
/// </summary>
|
||||
public enum RpcDelivery
|
||||
{
|
||||
/// <summary>
|
||||
/// Reliable delivery
|
||||
/// </summary>
|
||||
Reliable = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Unreliable delivery
|
||||
/// </summary>
|
||||
Unreliable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Represents the common base class for Rpc attributes.</para>
|
||||
/// </summary>
|
||||
public abstract class RpcAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of RPC delivery method
|
||||
/// </summary>
|
||||
public RpcDelivery Delivery = RpcDelivery.Reliable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Marks a method as ServerRpc.</para>
|
||||
/// <para>A ServerRpc marked method will be fired by a client but executed on the server.</para>
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class ServerRpcAttribute : RpcAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether or not the ServerRpc should only be run if executed by the owner of the object
|
||||
/// </summary>
|
||||
public bool RequireOwnership = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Marks a method as ClientRpc.</para>
|
||||
/// <para>A ClientRpc marked method will be fired by the server but executed on clients.</para>
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class ClientRpcAttribute : RpcAttribute { }
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ac745c8131b811e4887a83fa9e65b4af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,225 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Serialization;
|
||||
using MLAPI.Configuration;
|
||||
using MLAPI.Profiling;
|
||||
using MLAPI.Transports;
|
||||
|
||||
namespace MLAPI.Messaging
|
||||
{
|
||||
internal class RpcBatcher
|
||||
{
|
||||
public class SendStream
|
||||
{
|
||||
public NetworkChannel NetworkChannel;
|
||||
public PooledNetworkBuffer Buffer;
|
||||
public PooledNetworkWriter Writer;
|
||||
public bool IsEmpty = true;
|
||||
|
||||
public SendStream()
|
||||
{
|
||||
Buffer = PooledNetworkBuffer.Get();
|
||||
Writer = PooledNetworkWriter.Get(Buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Stores the stream of batched RPC to send to each client, by ClientId
|
||||
private readonly Dictionary<ulong, SendStream> k_SendDict = new Dictionary<ulong, SendStream>();
|
||||
|
||||
// Used to store targets, internally
|
||||
private ulong[] m_TargetList = new ulong[0];
|
||||
|
||||
// Used to mark longer lengths. Works because we can't have zero-sized messages
|
||||
private const byte k_LongLenMarker = 0;
|
||||
|
||||
private void PushLength(int length, ref PooledNetworkWriter writer)
|
||||
{
|
||||
// If length is single byte we write it
|
||||
if (length < 256)
|
||||
{
|
||||
writer.WriteByte((byte)length); // write the amounts of bytes that are coming up
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise we write a two-byte length
|
||||
writer.WriteByte(k_LongLenMarker); // mark larger size
|
||||
writer.WriteByte((byte)(length % 256)); // write the length modulo 256
|
||||
writer.WriteByte((byte)(length / 256)); // write the length divided by 256
|
||||
}
|
||||
}
|
||||
|
||||
private int PopLength(in NetworkBuffer messageBuffer)
|
||||
{
|
||||
int read = messageBuffer.ReadByte();
|
||||
// if we read a non-zero value, we have a single byte length
|
||||
// or a -1 error we can return
|
||||
if (read != k_LongLenMarker)
|
||||
{
|
||||
return read;
|
||||
}
|
||||
|
||||
// otherwise, a two-byte length follows. We'll read in len1, len2
|
||||
int len1 = messageBuffer.ReadByte();
|
||||
if (len1 < 0)
|
||||
{
|
||||
// pass errors back to caller
|
||||
return len1;
|
||||
}
|
||||
|
||||
int len2 = messageBuffer.ReadByte();
|
||||
if (len2 < 0)
|
||||
{
|
||||
// pass errors back to caller
|
||||
return len2;
|
||||
}
|
||||
|
||||
return len1 + len2 * 256;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FillTargetList
|
||||
/// Fills a list with the ClientId's an item is targeted to
|
||||
/// </summary>
|
||||
/// <param name="queueItem">the FrameQueueItem we want targets for</param>
|
||||
/// <param name="networkIdList">the list to fill</param>
|
||||
private static void FillTargetList(in RpcFrameQueueItem queueItem, ref ulong[] networkIdList)
|
||||
{
|
||||
switch (queueItem.QueueItemType)
|
||||
{
|
||||
// todo: revisit .resize() and .ToArry() usage, for performance
|
||||
case RpcQueueContainer.QueueItemType.ServerRpc:
|
||||
Array.Resize(ref networkIdList, 1);
|
||||
networkIdList[0] = queueItem.NetworkId;
|
||||
break;
|
||||
default:
|
||||
// todo: consider the implications of default usage of queueItem.clientIds
|
||||
case RpcQueueContainer.QueueItemType.ClientRpc:
|
||||
// copy the list
|
||||
networkIdList = queueItem.ClientNetworkIds.ToArray();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// QueueItem
|
||||
/// Add a FrameQueueItem to be sent
|
||||
/// </summary>queueItem
|
||||
/// <param name="queueItem">the threshold in bytes</param>
|
||||
public void QueueItem(in RpcFrameQueueItem queueItem)
|
||||
{
|
||||
FillTargetList(queueItem, ref m_TargetList);
|
||||
|
||||
foreach (ulong clientId in m_TargetList)
|
||||
{
|
||||
if (!k_SendDict.ContainsKey(clientId))
|
||||
{
|
||||
// todo: consider what happens if many clients join and leave the game consecutively
|
||||
// we probably need a cleanup mechanism at some point
|
||||
k_SendDict[clientId] = new SendStream();
|
||||
}
|
||||
|
||||
if (k_SendDict[clientId].IsEmpty)
|
||||
{
|
||||
k_SendDict[clientId].IsEmpty = false;
|
||||
k_SendDict[clientId].NetworkChannel = queueItem.NetworkChannel;
|
||||
|
||||
switch (queueItem.QueueItemType)
|
||||
{
|
||||
// 8 bits are used for the message type, which is an NetworkConstants
|
||||
case RpcQueueContainer.QueueItemType.ServerRpc:
|
||||
k_SendDict[clientId].Writer.WriteByte(NetworkConstants.SERVER_RPC); // MessageType
|
||||
break;
|
||||
case RpcQueueContainer.QueueItemType.ClientRpc:
|
||||
k_SendDict[clientId].Writer.WriteByte(NetworkConstants.CLIENT_RPC); // MessageType
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// write the amounts of bytes that are coming up
|
||||
PushLength(queueItem.MessageData.Count, ref k_SendDict[clientId].Writer);
|
||||
|
||||
// write the message to send
|
||||
k_SendDict[clientId].Writer.WriteBytes(queueItem.MessageData.Array, queueItem.MessageData.Count, queueItem.MessageData.Offset);
|
||||
|
||||
ProfilerStatManager.BytesSent.Record(queueItem.MessageData.Count);
|
||||
ProfilerStatManager.RpcsSent.Record();
|
||||
PerformanceDataManager.Increment(ProfilerConstants.ByteSent, queueItem.MessageData.Count);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.RpcSent);
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void SendCallbackType(ulong clientId, SendStream messageStream);
|
||||
|
||||
public delegate void ReceiveCallbackType(NetworkBuffer messageStream, RpcQueueContainer.QueueItemType messageType, ulong clientId, float receiveTime);
|
||||
|
||||
/// <summary>
|
||||
/// SendItems
|
||||
/// Send any batch of RPC that are of length above threshold
|
||||
/// </summary>
|
||||
/// <param name="thresholdBytes"> the threshold in bytes</param>
|
||||
/// <param name="sendCallback"> the function to call for sending the batch</param>
|
||||
public void SendItems(int thresholdBytes, SendCallbackType sendCallback)
|
||||
{
|
||||
foreach (KeyValuePair<ulong, SendStream> entry in k_SendDict)
|
||||
{
|
||||
if (!entry.Value.IsEmpty)
|
||||
{
|
||||
// read the queued message
|
||||
int length = (int)k_SendDict[entry.Key].Buffer.Length;
|
||||
|
||||
if (length >= thresholdBytes)
|
||||
{
|
||||
sendCallback(entry.Key, entry.Value);
|
||||
// clear the batch that was sent from the SendDict
|
||||
entry.Value.Buffer.SetLength(0);
|
||||
entry.Value.Buffer.Position = 0;
|
||||
entry.Value.IsEmpty = true;
|
||||
ProfilerStatManager.RpcBatchesSent.Record();
|
||||
PerformanceDataManager.Increment(ProfilerConstants.RpcBatchesSent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ReceiveItems
|
||||
/// Process the messageStream and call the callback with individual RPC messages
|
||||
/// </summary>
|
||||
/// <param name="messageBuffer"> the messageStream containing the batched RPC</param>
|
||||
/// <param name="receiveCallback"> the callback to call has type int f(message, type, clientId, time) </param>
|
||||
/// <param name="messageType"> the message type to pass back to callback</param>
|
||||
/// <param name="clientId"> the clientId to pass back to callback</param>
|
||||
/// <param name="receiveTime"> the packet receive time to pass back to callback</param>
|
||||
public void ReceiveItems(in NetworkBuffer messageBuffer, ReceiveCallbackType receiveCallback, RpcQueueContainer.QueueItemType messageType, ulong clientId, float receiveTime)
|
||||
{
|
||||
using (var copy = PooledNetworkBuffer.Get())
|
||||
{
|
||||
do
|
||||
{
|
||||
// read the length of the next RPC
|
||||
int rpcSize = PopLength(messageBuffer);
|
||||
if (rpcSize < 0)
|
||||
{
|
||||
// abort if there's an error reading lengths
|
||||
return;
|
||||
}
|
||||
|
||||
// copy what comes after current stream position
|
||||
long position = messageBuffer.Position;
|
||||
copy.SetLength(rpcSize);
|
||||
copy.Position = 0;
|
||||
Buffer.BlockCopy(messageBuffer.GetBuffer(), (int)position, copy.GetBuffer(), 0, rpcSize);
|
||||
|
||||
receiveCallback(copy, messageType, clientId, receiveTime);
|
||||
|
||||
// seek over the RPC
|
||||
// RPCReceiveQueueItem peeks at content, it doesn't advance
|
||||
messageBuffer.Seek(rpcSize, SeekOrigin.Current);
|
||||
} while (messageBuffer.Position < messageBuffer.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 51882ca216b244e40873fb93a17ee153
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
|
||||
namespace MLAPI.Messaging
|
||||
{
|
||||
public struct ServerRpcSendParams
|
||||
{
|
||||
public NetworkUpdateStage UpdateStage;
|
||||
}
|
||||
|
||||
public struct ServerRpcReceiveParams
|
||||
{
|
||||
public NetworkUpdateStage UpdateStage;
|
||||
public ulong SenderClientId;
|
||||
}
|
||||
|
||||
public struct ServerRpcParams
|
||||
{
|
||||
public ServerRpcSendParams Send;
|
||||
public ServerRpcReceiveParams Receive;
|
||||
}
|
||||
|
||||
public struct ClientRpcSendParams
|
||||
{
|
||||
public NetworkUpdateStage UpdateStage;
|
||||
public ulong[] TargetClientIds;
|
||||
}
|
||||
|
||||
public struct ClientRpcReceiveParams
|
||||
{
|
||||
public NetworkUpdateStage UpdateStage;
|
||||
}
|
||||
|
||||
public struct ClientRpcParams
|
||||
{
|
||||
public ClientRpcSendParams Send;
|
||||
public ClientRpcReceiveParams Receive;
|
||||
}
|
||||
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
// RuntimeAccessModifiersILPP will make this `public`
|
||||
internal struct __RpcParams
|
||||
#else
|
||||
[Obsolete("Please do not use, will no longer be exposed in the future versions (framework internal)")]
|
||||
public struct __RpcParams
|
||||
#endif
|
||||
{
|
||||
public ServerRpcParams Server;
|
||||
public ClientRpcParams Client;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: accabc5a65e8a45dda9207fac37d1b24
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1bd9e643489acaa4eaaa62e3b10859e3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using MLAPI.Transports;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
|
||||
namespace MLAPI.Messaging
|
||||
{
|
||||
/// <summary>
|
||||
/// FrameQueueItem
|
||||
/// Container structure for RPCs written to the Queue Frame
|
||||
/// Used for both Inbound and Outbound RPCs
|
||||
/// NOTE: This structure will change in the near future and is in a state of flux.
|
||||
/// This will include removing specific properties or changing property types
|
||||
/// </summary>
|
||||
internal struct RpcFrameQueueItem
|
||||
{
|
||||
public NetworkUpdateStage UpdateStage;
|
||||
public RpcQueueContainer.QueueItemType QueueItemType;
|
||||
public ulong NetworkId; //Sender's network Identifier
|
||||
public NetworkChannel NetworkChannel;
|
||||
public ulong[] ClientNetworkIds; //Server invoked Client RPCs only
|
||||
public long StreamSize;
|
||||
public float Timestamp;
|
||||
public PooledNetworkWriter NetworkWriter;
|
||||
public PooledNetworkReader NetworkReader;
|
||||
public PooledNetworkBuffer NetworkBuffer;
|
||||
public ArraySegment<byte> MessageData;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0f20d27472604314b9c3d9c2662fd97c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,753 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MLAPI.Serialization;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Profiling;
|
||||
using MLAPI.Transports;
|
||||
|
||||
namespace MLAPI.Messaging
|
||||
{
|
||||
/// <summary>
|
||||
/// RpcQueueContainer
|
||||
/// Handles the management of an Rpc Queue
|
||||
/// </summary>
|
||||
internal class RpcQueueContainer : INetworkUpdateSystem
|
||||
{
|
||||
private const int k_MinQueueHistory = 2; //We need a minimum of 2 queue history buffers in order to properly handle looping back Rpcs when a host
|
||||
|
||||
public enum QueueItemType
|
||||
{
|
||||
ServerRpc,
|
||||
ClientRpc,
|
||||
CreateObject, //MLAPI Constant *** We need to determine if these belong here ***
|
||||
DestroyObject, //MLAPI Constant
|
||||
|
||||
None //Indicates end of frame
|
||||
}
|
||||
|
||||
public enum RpcQueueProcessingTypes
|
||||
{
|
||||
Send,
|
||||
Receive,
|
||||
}
|
||||
|
||||
// Inbound and Outbound QueueHistoryFrames
|
||||
private readonly Dictionary<RpcQueueHistoryFrame.QueueFrameType, Dictionary<int, Dictionary<NetworkUpdateStage, RpcQueueHistoryFrame>>> QueueHistory =
|
||||
new Dictionary<RpcQueueHistoryFrame.QueueFrameType, Dictionary<int, Dictionary<NetworkUpdateStage, RpcQueueHistoryFrame>>>();
|
||||
|
||||
private RpcQueueProcessor m_RpcQueueProcessor;
|
||||
|
||||
private uint m_OutboundFramesProcessed;
|
||||
private uint m_InboundFramesProcessed;
|
||||
private uint m_MaxFrameHistory;
|
||||
private int m_InboundStreamBufferIndex;
|
||||
private int m_OutBoundStreamBufferIndex;
|
||||
private bool m_IsTestingEnabled;
|
||||
private bool m_ProcessUpdateStagesExternally;
|
||||
private bool m_IsNotUsingBatching;
|
||||
|
||||
public bool IsUsingBatching()
|
||||
{
|
||||
return !m_IsNotUsingBatching;
|
||||
}
|
||||
|
||||
public void EnableBatchedRpcs(bool isbatchingEnabled)
|
||||
{
|
||||
m_IsNotUsingBatching = !isbatchingEnabled;
|
||||
}
|
||||
|
||||
// INetworkUpdateSystem
|
||||
public void NetworkUpdate(NetworkUpdateStage updateStage)
|
||||
{
|
||||
ProcessAndFlushRpcQueue(RpcQueueProcessingTypes.Receive, updateStage);
|
||||
|
||||
if (updateStage == NetworkUpdateStage.PostLateUpdate)
|
||||
{
|
||||
ProcessAndFlushRpcQueue(RpcQueueProcessingTypes.Send, updateStage);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetStreamBufferFrameCount
|
||||
/// Returns how many frames have been processed (Inbound/Outbound)
|
||||
/// </summary>
|
||||
/// <param name="queueType"></param>
|
||||
/// <returns>number of frames procssed</returns>
|
||||
public uint GetStreamBufferFrameCount(RpcQueueHistoryFrame.QueueFrameType queueType)
|
||||
{
|
||||
return queueType == RpcQueueHistoryFrame.QueueFrameType.Inbound ? m_InboundFramesProcessed : m_OutboundFramesProcessed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AddToInternalMLAPISendQueue
|
||||
/// NSS-TODO: This will need to be removed once we determine how we want to handle specific
|
||||
/// internal MLAPI commands relative to RPCS.
|
||||
/// Example: An network object is destroyed via server side (internal mlapi) command, but prior to this several RPCs are invoked for the to be destroyed object (Client RPC)
|
||||
/// If both the DestroyObject internal mlapi command and the ClientRPCs are received in the same frame but the internal mlapi DestroyObject command is processed prior to the
|
||||
/// RPCs being invoked then the object won't exist and additional warnings will be logged that the object no longer exists.
|
||||
/// The vices versa scenario (create and then RPCs sent) is an unlikely/improbable scenario, but just in case added the CreateObject to this special case scenario.
|
||||
///
|
||||
/// To avoid the DestroyObject scenario, the internal MLAPI commands (DestroyObject and CreateObject) are always invoked after RPCs.
|
||||
/// </summary>
|
||||
/// <param name="queueItem">item to add to the internal MLAPI queue</param>
|
||||
public void AddToInternalMLAPISendQueue(RpcFrameQueueItem queueItem)
|
||||
{
|
||||
m_RpcQueueProcessor.QueueInternalMLAPICommand(queueItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ProcessAndFlushRPCQueue
|
||||
/// Will process the RPC queue and then move to the next available frame
|
||||
/// </summary>
|
||||
/// <param name="queueType"></param>
|
||||
public void ProcessAndFlushRpcQueue(RpcQueueProcessingTypes queueType, NetworkUpdateStage currentUpdateStage)
|
||||
{
|
||||
if (m_RpcQueueProcessor == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (queueType)
|
||||
{
|
||||
case RpcQueueProcessingTypes.Receive:
|
||||
{
|
||||
m_RpcQueueProcessor.ProcessReceiveQueue(currentUpdateStage);
|
||||
break;
|
||||
}
|
||||
case RpcQueueProcessingTypes.Send:
|
||||
{
|
||||
m_RpcQueueProcessor.ProcessSendQueue();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetCurrentFrame
|
||||
/// Gets the current frame for the Inbound or Outbound queue
|
||||
/// </summary>
|
||||
/// <param name="qType"></param>
|
||||
/// <returns>QueueHistoryFrame</returns>
|
||||
public RpcQueueHistoryFrame GetCurrentFrame(RpcQueueHistoryFrame.QueueFrameType qType, NetworkUpdateStage currentUpdateStage)
|
||||
{
|
||||
if (QueueHistory.ContainsKey(qType))
|
||||
{
|
||||
int StreamBufferIndex = GetStreamBufferIndex(qType);
|
||||
|
||||
if (QueueHistory[qType].ContainsKey(StreamBufferIndex))
|
||||
{
|
||||
if (QueueHistory[qType][StreamBufferIndex].ContainsKey(currentUpdateStage))
|
||||
{
|
||||
return QueueHistory[qType][StreamBufferIndex][currentUpdateStage];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetStreamBufferIndex
|
||||
/// Returns the queue type's current stream buffer index
|
||||
/// </summary>
|
||||
/// <param name="queueType"></param>
|
||||
/// <returns></returns>
|
||||
private int GetStreamBufferIndex(RpcQueueHistoryFrame.QueueFrameType queueType)
|
||||
{
|
||||
return queueType == RpcQueueHistoryFrame.QueueFrameType.Inbound ? m_InboundStreamBufferIndex : m_OutBoundStreamBufferIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AdvanceFrameHistory
|
||||
/// Progresses the current frame to the next QueueHistoryFrame for the QueueHistoryFrame.QueueFrameType.
|
||||
/// All other frames other than the current frame is considered the live rollback history
|
||||
/// </summary>
|
||||
/// <param name="queueType"></param>
|
||||
public void AdvanceFrameHistory(RpcQueueHistoryFrame.QueueFrameType queueType)
|
||||
{
|
||||
int StreamBufferIndex = GetStreamBufferIndex(queueType);
|
||||
|
||||
if (!QueueHistory.ContainsKey(queueType))
|
||||
{
|
||||
UnityEngine.Debug.LogError($"You must initialize the {nameof(RpcQueueContainer)} before using MLAPI!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!QueueHistory[queueType].ContainsKey(StreamBufferIndex))
|
||||
{
|
||||
UnityEngine.Debug.LogError($"{nameof(RpcQueueContainer)} {queueType} queue stream buffer index out of range! [{StreamBufferIndex}]");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
foreach (KeyValuePair<NetworkUpdateStage, RpcQueueHistoryFrame> queueHistoryByUpdates in QueueHistory[queueType][StreamBufferIndex])
|
||||
{
|
||||
var rpcQueueHistoryItem = queueHistoryByUpdates.Value;
|
||||
|
||||
//This only gets reset when we advanced to next frame (do not reset this in the ResetQueueHistoryFrame)
|
||||
rpcQueueHistoryItem.HasLoopbackData = false;
|
||||
|
||||
if (rpcQueueHistoryItem.QueueItemOffsets.Count > 0)
|
||||
{
|
||||
if (queueType == RpcQueueHistoryFrame.QueueFrameType.Inbound)
|
||||
{
|
||||
ProfilerStatManager.RpcInQueueSize.Record((int)rpcQueueHistoryItem.TotalSize);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.RpcInQueueSize, (int)rpcQueueHistoryItem.TotalSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProfilerStatManager.RpcOutQueueSize.Record((int)rpcQueueHistoryItem.TotalSize);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.RpcOutQueueSize, (int)rpcQueueHistoryItem.TotalSize);
|
||||
}
|
||||
}
|
||||
|
||||
ResetQueueHistoryFrame(rpcQueueHistoryItem);
|
||||
IncrementAndSetQueueHistoryFrame(rpcQueueHistoryItem);
|
||||
}
|
||||
|
||||
//Roll to the next stream buffer
|
||||
StreamBufferIndex++;
|
||||
|
||||
//If we have hit our maximum history, roll back over to the first one
|
||||
if (StreamBufferIndex >= m_MaxFrameHistory)
|
||||
{
|
||||
StreamBufferIndex = 0;
|
||||
}
|
||||
|
||||
if (queueType == RpcQueueHistoryFrame.QueueFrameType.Inbound)
|
||||
{
|
||||
m_InboundStreamBufferIndex = StreamBufferIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_OutBoundStreamBufferIndex = StreamBufferIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IncrementAndSetQueueHistoryFrame
|
||||
/// Increments and sets frame count for this queue frame
|
||||
/// </summary>
|
||||
/// <param name="rpcQueueFrame">QueueHistoryFrame to be reset</param>
|
||||
private void IncrementAndSetQueueHistoryFrame(RpcQueueHistoryFrame rpcQueueFrame)
|
||||
{
|
||||
if (rpcQueueFrame.GetQueueFrameType() == RpcQueueHistoryFrame.QueueFrameType.Inbound)
|
||||
{
|
||||
m_InboundFramesProcessed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_OutboundFramesProcessed++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ResetQueueHistoryFrame
|
||||
/// Resets the queue history frame passed to this method
|
||||
/// </summary>
|
||||
/// <param name="rpcQueueFrame">QueueHistoryFrame to be reset</param>
|
||||
private static void ResetQueueHistoryFrame(RpcQueueHistoryFrame rpcQueueFrame)
|
||||
{
|
||||
//If we are dirt and have loopback data then don't clear this frame
|
||||
if (rpcQueueFrame.IsDirty && !rpcQueueFrame.HasLoopbackData)
|
||||
{
|
||||
rpcQueueFrame.TotalSize = 0;
|
||||
rpcQueueFrame.QueueItemOffsets.Clear();
|
||||
rpcQueueFrame.QueueBuffer.Position = 0;
|
||||
rpcQueueFrame.MarkCurrentStreamPosition();
|
||||
rpcQueueFrame.IsDirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AddQueueItemToInboundFrame
|
||||
/// Adds an RPC queue item to the outbound frame
|
||||
/// </summary>
|
||||
/// <param name="qItemType">type of rpc (client or server)</param>
|
||||
/// <param name="timeStamp">when it was received</param>
|
||||
/// <param name="sourceNetworkId">who sent the rpc</param>
|
||||
/// <param name="message">the message being received</param>
|
||||
internal void AddQueueItemToInboundFrame(QueueItemType qItemType, float timeStamp, ulong sourceNetworkId, NetworkBuffer message)
|
||||
{
|
||||
long originalPosition = message.Position;
|
||||
|
||||
NetworkUpdateStage updateStage;
|
||||
|
||||
using (var reader = PooledNetworkReader.Get(message))
|
||||
{
|
||||
var longValue = reader.ReadUInt64Packed(); // NetworkObjectId (temporary, we reset position just below)
|
||||
var shortValue = reader.ReadUInt16Packed(); // NetworkBehaviourId (temporary, we reset position just below)
|
||||
updateStage = (NetworkUpdateStage)reader.ReadByteDirect();
|
||||
}
|
||||
|
||||
message.Position = originalPosition;
|
||||
var rpcQueueHistoryItem = GetQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType.Inbound, updateStage);
|
||||
rpcQueueHistoryItem.IsDirty = true;
|
||||
|
||||
long StartPosition = rpcQueueHistoryItem.QueueBuffer.Position;
|
||||
|
||||
//Write the packed version of the queueItem to our current queue history buffer
|
||||
rpcQueueHistoryItem.QueueWriter.WriteUInt16((ushort)qItemType);
|
||||
rpcQueueHistoryItem.QueueWriter.WriteSingle(timeStamp);
|
||||
rpcQueueHistoryItem.QueueWriter.WriteUInt64(sourceNetworkId);
|
||||
|
||||
//Inbound we copy the entire packet and store the position offset
|
||||
long streamSize = message.Length;
|
||||
rpcQueueHistoryItem.QueueWriter.WriteInt64(streamSize);
|
||||
rpcQueueHistoryItem.QueueWriter.WriteInt64(message.Position);
|
||||
rpcQueueHistoryItem.QueueWriter.WriteBytes(message.GetBuffer(), streamSize);
|
||||
|
||||
//Add the packed size to the offsets for parsing over various entries
|
||||
rpcQueueHistoryItem.QueueItemOffsets.Add((uint)rpcQueueHistoryItem.QueueBuffer.Position);
|
||||
|
||||
//Calculate the packed size based on stream progression
|
||||
rpcQueueHistoryItem.TotalSize += (uint)(rpcQueueHistoryItem.QueueBuffer.Position - StartPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SetLoopBackFrameItem
|
||||
/// ***Temporary fix for host mode loopback RPC writer work-around
|
||||
/// Sets the next frame inbond buffer as the loopback queue history frame in the current frame's outbound buffer
|
||||
/// </summary>
|
||||
/// <param name="updateStage"></param>
|
||||
public void SetLoopBackFrameItem(NetworkUpdateStage updateStage)
|
||||
{
|
||||
//Get the next frame's inbound queue history frame
|
||||
var loopbackHistoryframe = GetQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType.Inbound, updateStage, true);
|
||||
|
||||
//Get the current frame's outbound queue history frame
|
||||
var rpcQueueHistoryItem = GetQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate, false);
|
||||
|
||||
if (rpcQueueHistoryItem != null)
|
||||
{
|
||||
rpcQueueHistoryItem.LoopbackHistoryFrame = loopbackHistoryframe;
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError("Could not find the outbound QueueHistoryFrame!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetLoopBackWriter
|
||||
/// Gets the loop back writer for the history frame (if one exists)
|
||||
/// ***Temporary fix for host mode loopback RPC writer work-around
|
||||
/// </summary>
|
||||
/// <param name="queueFrameType"></param>
|
||||
/// <param name="updateStage"></param>
|
||||
/// <returns></returns>
|
||||
public RpcQueueHistoryFrame GetLoopBackHistoryFrame(RpcQueueHistoryFrame.QueueFrameType queueFrameType, NetworkUpdateStage updateStage)
|
||||
{
|
||||
return GetQueueHistoryFrame(queueFrameType, updateStage, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BeginAddQueueItemToOutboundFrame
|
||||
/// Adds a queue item to the outbound queue frame
|
||||
/// </summary>
|
||||
/// <param name="qItemType">type of rpc (client or server)</param>
|
||||
/// <param name="timeStamp">when it was scheduled to be sent</param>
|
||||
/// <param name="networkChannel">the channel to send it on</param>
|
||||
/// <param name="sourceNetworkId">who is sending the rpc</param>
|
||||
/// <param name="targetNetworkIds">who the rpc is being sent to</param>
|
||||
/// <returns></returns>
|
||||
public PooledNetworkWriter BeginAddQueueItemToFrame(QueueItemType qItemType, float timeStamp, NetworkChannel networkChannel, ulong sourceNetworkId, ulong[] targetNetworkIds,
|
||||
RpcQueueHistoryFrame.QueueFrameType queueFrameType, NetworkUpdateStage updateStage)
|
||||
{
|
||||
bool getNextFrame = NetworkManager.Singleton.IsHost && queueFrameType == RpcQueueHistoryFrame.QueueFrameType.Inbound;
|
||||
|
||||
var rpcQueueHistoryItem = GetQueueHistoryFrame(queueFrameType, updateStage, getNextFrame);
|
||||
rpcQueueHistoryItem.IsDirty = true;
|
||||
|
||||
//Write the packed version of the queueItem to our current queue history buffer
|
||||
rpcQueueHistoryItem.QueueWriter.WriteUInt16((ushort)qItemType);
|
||||
rpcQueueHistoryItem.QueueWriter.WriteSingle(timeStamp);
|
||||
rpcQueueHistoryItem.QueueWriter.WriteUInt64(sourceNetworkId);
|
||||
|
||||
if (queueFrameType != RpcQueueHistoryFrame.QueueFrameType.Inbound)
|
||||
{
|
||||
rpcQueueHistoryItem.QueueWriter.WriteByte((byte)networkChannel);
|
||||
|
||||
if (targetNetworkIds != null && targetNetworkIds.Length != 0)
|
||||
{
|
||||
//In the event the host is one of the networkIds, for outbound we want to ignore it (at this spot only!!)
|
||||
//Get a count of clients we are going to send to (and write into the buffer)
|
||||
var numberOfClients = 0;
|
||||
for (int i = 0; i < targetNetworkIds.Length; i++)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsHost && targetNetworkIds[i] == NetworkManager.Singleton.ServerClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
numberOfClients++;
|
||||
}
|
||||
|
||||
//Write our total number of clients
|
||||
rpcQueueHistoryItem.QueueWriter.WriteInt32(numberOfClients);
|
||||
|
||||
//Now write the cliend ids
|
||||
for (int i = 0; i < targetNetworkIds.Length; i++)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsHost && targetNetworkIds[i] == NetworkManager.Singleton.ServerClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
rpcQueueHistoryItem.QueueWriter.WriteUInt64(targetNetworkIds[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rpcQueueHistoryItem.QueueWriter.WriteInt32(0);
|
||||
}
|
||||
}
|
||||
|
||||
//Mark where we started in the stream to later determine the actual RPC message size (position before writing RPC message vs position after write has completed)
|
||||
rpcQueueHistoryItem.MarkCurrentStreamPosition();
|
||||
|
||||
//Write a filler dummy size of 0 to hold this position in order to write to it once the RPC is done writing.
|
||||
rpcQueueHistoryItem.QueueWriter.WriteInt64(0);
|
||||
|
||||
if (NetworkManager.Singleton.IsHost && queueFrameType == RpcQueueHistoryFrame.QueueFrameType.Inbound)
|
||||
{
|
||||
if (!IsUsingBatching())
|
||||
{
|
||||
rpcQueueHistoryItem.QueueWriter.WriteInt64(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
rpcQueueHistoryItem.QueueWriter.WriteInt64(0);
|
||||
}
|
||||
|
||||
rpcQueueHistoryItem.HasLoopbackData = true; //The only case for this is when it is the Host
|
||||
}
|
||||
|
||||
//Return the writer to the invoking method.
|
||||
return rpcQueueHistoryItem.QueueWriter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EndAddQueueItemToOutboundFrame
|
||||
/// Signifies the end of this outbound RPC.
|
||||
/// We store final MSG size and track the total current frame queue size
|
||||
/// </summary>
|
||||
/// <param name="writer">writer that was used</param>
|
||||
public void EndAddQueueItemToFrame(NetworkWriter writer, RpcQueueHistoryFrame.QueueFrameType queueFrameType, NetworkUpdateStage updateStage)
|
||||
{
|
||||
bool getNextFrame = NetworkManager.Singleton.IsHost && queueFrameType == RpcQueueHistoryFrame.QueueFrameType.Inbound;
|
||||
|
||||
var rpcQueueHistoryItem = GetQueueHistoryFrame(queueFrameType, updateStage, getNextFrame);
|
||||
var loopBackHistoryFrame = rpcQueueHistoryItem.LoopbackHistoryFrame;
|
||||
|
||||
var pbWriter = (PooledNetworkWriter)writer;
|
||||
if (pbWriter != rpcQueueHistoryItem.QueueWriter && !getNextFrame)
|
||||
{
|
||||
UnityEngine.Debug.LogError($"{nameof(RpcQueueContainer)} {queueFrameType} passed writer is not the same as the current {nameof(PooledNetworkWriter)} for the {queueFrameType}!");
|
||||
}
|
||||
|
||||
//The total size of the frame is the last known position of the stream
|
||||
rpcQueueHistoryItem.TotalSize = (uint)rpcQueueHistoryItem.QueueBuffer.Position;
|
||||
|
||||
long CurrentPosition = rpcQueueHistoryItem.QueueBuffer.Position;
|
||||
ulong BitPosition = rpcQueueHistoryItem.QueueBuffer.BitPosition;
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
//>>>> REPOSITIONING STREAM TO RPC MESSAGE SIZE LOCATION <<<<
|
||||
//////////////////////////////////////////////////////////////
|
||||
rpcQueueHistoryItem.QueueBuffer.Position = rpcQueueHistoryItem.GetCurrentMarkedPosition();
|
||||
|
||||
long MSGOffset = 8;
|
||||
if (getNextFrame && IsUsingBatching())
|
||||
{
|
||||
MSGOffset += 8;
|
||||
}
|
||||
|
||||
//subtracting 8 byte to account for the value of the size of the RPC
|
||||
long MSGSize = (long)(rpcQueueHistoryItem.TotalSize - (rpcQueueHistoryItem.GetCurrentMarkedPosition() + MSGOffset));
|
||||
|
||||
if (MSGSize > 0)
|
||||
{
|
||||
//Write the actual size of the RPC message
|
||||
rpcQueueHistoryItem.QueueWriter.WriteInt64(MSGSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogWarning("MSGSize of < zero detected!! Setting message size to zero!");
|
||||
rpcQueueHistoryItem.QueueWriter.WriteInt64(0);
|
||||
}
|
||||
|
||||
if (loopBackHistoryFrame != null)
|
||||
{
|
||||
if (MSGSize > 0)
|
||||
{
|
||||
//Point to where the size of the message is stored
|
||||
loopBackHistoryFrame.QueueBuffer.Position = loopBackHistoryFrame.GetCurrentMarkedPosition();
|
||||
|
||||
//Write the actual size of the RPC message
|
||||
loopBackHistoryFrame.QueueWriter.WriteInt64(MSGSize);
|
||||
|
||||
if (!IsUsingBatching())
|
||||
{
|
||||
//Write the offset for the header info copied
|
||||
loopBackHistoryFrame.QueueWriter.WriteInt64(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Write the offset for the header info copied
|
||||
loopBackHistoryFrame.QueueWriter.WriteInt64(0);
|
||||
}
|
||||
|
||||
//Write RPC data
|
||||
loopBackHistoryFrame.QueueWriter.WriteBytes(rpcQueueHistoryItem.QueueBuffer.GetBuffer(), MSGSize, (int)rpcQueueHistoryItem.QueueBuffer.Position);
|
||||
|
||||
//Set the total size for this stream
|
||||
loopBackHistoryFrame.TotalSize = (uint)loopBackHistoryFrame.QueueBuffer.Position;
|
||||
|
||||
//Add the total size to the offsets for parsing over various entries
|
||||
loopBackHistoryFrame.QueueItemOffsets.Add((uint)loopBackHistoryFrame.QueueBuffer.Position);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogWarning("[LoopBack] MSGSize of < zero detected!! Setting message size to zero!");
|
||||
//Write the actual size of the RPC message
|
||||
loopBackHistoryFrame.QueueWriter.WriteInt64(0);
|
||||
}
|
||||
|
||||
rpcQueueHistoryItem.LoopbackHistoryFrame = null;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
//<<<< REPOSITIONING STREAM BACK TO THE CURRENT TAIL >>>>
|
||||
//////////////////////////////////////////////////////////////
|
||||
rpcQueueHistoryItem.QueueBuffer.Position = CurrentPosition;
|
||||
rpcQueueHistoryItem.QueueBuffer.BitPosition = BitPosition;
|
||||
|
||||
//Add the packed size to the offsets for parsing over various entries
|
||||
rpcQueueHistoryItem.QueueItemOffsets.Add((uint)rpcQueueHistoryItem.QueueBuffer.Position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetQueueHistoryFrame
|
||||
/// Gets the current queue history frame (inbound or outbound)
|
||||
/// </summary>
|
||||
/// <param name="frameType">inbound or outbound</param>
|
||||
/// <returns>QueueHistoryFrame or null</returns>
|
||||
public RpcQueueHistoryFrame GetQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType frameType, NetworkUpdateStage updateStage, bool getNextFrame = false)
|
||||
{
|
||||
int StreamBufferIndex = GetStreamBufferIndex(frameType);
|
||||
|
||||
//We want to write into the future/next frame
|
||||
if (getNextFrame)
|
||||
{
|
||||
StreamBufferIndex++;
|
||||
|
||||
//If we have hit our maximum history, roll back over to the first one
|
||||
if (StreamBufferIndex >= m_MaxFrameHistory)
|
||||
{
|
||||
StreamBufferIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (!QueueHistory.ContainsKey(frameType))
|
||||
{
|
||||
UnityEngine.Debug.LogError("You must initialize the RPCQueueManager before using MLAPI!");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!QueueHistory[frameType].ContainsKey(StreamBufferIndex))
|
||||
{
|
||||
UnityEngine.Debug.LogError($"{nameof(RpcQueueContainer)} {frameType} queue stream buffer index out of range! [{StreamBufferIndex}]");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!QueueHistory[frameType][StreamBufferIndex].ContainsKey(updateStage))
|
||||
{
|
||||
UnityEngine.Debug.LogError($"{nameof(RpcQueueContainer)} {updateStage} update type does not exist!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return QueueHistory[frameType][StreamBufferIndex][updateStage];
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
/// <summary>
|
||||
/// LoopbackSendFrame
|
||||
/// Will copy the contents of the current outbound QueueHistoryFrame to the current inbound QueueHistoryFrame
|
||||
/// [NSS]: Leaving this here in the event a portion of this code is useful for doing Batch testing
|
||||
/// </summary>
|
||||
public void LoopbackSendFrame()
|
||||
{
|
||||
//If we do not have loop back or testing mode enabled then ignore the call
|
||||
if (m_IsTestingEnabled)
|
||||
{
|
||||
var rpcQueueHistoryItemOutbound = GetQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
|
||||
if (rpcQueueHistoryItemOutbound.QueueItemOffsets.Count > 0)
|
||||
{
|
||||
//Reset inbound queues based on update stage
|
||||
foreach (NetworkUpdateStage netUpdateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
|
||||
{
|
||||
var rpcQueueHistoryItemInbound = GetQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType.Inbound, netUpdateStage);
|
||||
ResetQueueHistoryFrame(rpcQueueHistoryItemInbound);
|
||||
}
|
||||
|
||||
var pooledNetworkBuffer = PooledNetworkBuffer.Get();
|
||||
var rpcFrameQueueItem = rpcQueueHistoryItemOutbound.GetFirstQueueItem();
|
||||
|
||||
while (rpcFrameQueueItem.QueueItemType != QueueItemType.None)
|
||||
{
|
||||
pooledNetworkBuffer.SetLength(rpcFrameQueueItem.StreamSize);
|
||||
pooledNetworkBuffer.Position = 0;
|
||||
byte[] pooledNetworkStreamArray = pooledNetworkBuffer.GetBuffer();
|
||||
Buffer.BlockCopy(rpcFrameQueueItem.MessageData.Array ?? Array.Empty<byte>(), rpcFrameQueueItem.MessageData.Offset, pooledNetworkStreamArray, 0, (int)rpcFrameQueueItem.StreamSize);
|
||||
|
||||
if (!IsUsingBatching())
|
||||
{
|
||||
pooledNetworkBuffer.Position = 1;
|
||||
}
|
||||
|
||||
AddQueueItemToInboundFrame(rpcFrameQueueItem.QueueItemType, UnityEngine.Time.realtimeSinceStartup, rpcFrameQueueItem.NetworkId, pooledNetworkBuffer);
|
||||
rpcFrameQueueItem = rpcQueueHistoryItemOutbound.GetNextQueueItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initialize
|
||||
/// This should be called during primary initialization period (typically during NetworkManager's Start method)
|
||||
/// This will allocate [maxFrameHistory] + [1 currentFrame] number of PooledNetworkBuffers and keep them open until the session ends
|
||||
/// Note: For zero frame history set maxFrameHistory to zero
|
||||
/// </summary>
|
||||
/// <param name="maxFrameHistory"></param>
|
||||
public void Initialize(uint maxFrameHistory)
|
||||
{
|
||||
ClearParameters();
|
||||
|
||||
m_RpcQueueProcessor = new RpcQueueProcessor();
|
||||
m_MaxFrameHistory = maxFrameHistory + k_MinQueueHistory;
|
||||
|
||||
if (!QueueHistory.ContainsKey(RpcQueueHistoryFrame.QueueFrameType.Inbound))
|
||||
{
|
||||
QueueHistory.Add(RpcQueueHistoryFrame.QueueFrameType.Inbound, new Dictionary<int, Dictionary<NetworkUpdateStage, RpcQueueHistoryFrame>>());
|
||||
}
|
||||
|
||||
if (!QueueHistory.ContainsKey(RpcQueueHistoryFrame.QueueFrameType.Outbound))
|
||||
{
|
||||
QueueHistory.Add(RpcQueueHistoryFrame.QueueFrameType.Outbound, new Dictionary<int, Dictionary<NetworkUpdateStage, RpcQueueHistoryFrame>>());
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_MaxFrameHistory; i++)
|
||||
{
|
||||
if (!QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Outbound].ContainsKey(i))
|
||||
{
|
||||
QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Outbound].Add(i, new Dictionary<NetworkUpdateStage, RpcQueueHistoryFrame>());
|
||||
var queueHistoryFrame = new RpcQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
|
||||
queueHistoryFrame.QueueBuffer = PooledNetworkBuffer.Get();
|
||||
queueHistoryFrame.QueueBuffer.Position = 0;
|
||||
queueHistoryFrame.QueueWriter = PooledNetworkWriter.Get(queueHistoryFrame.QueueBuffer);
|
||||
queueHistoryFrame.QueueReader = PooledNetworkReader.Get(queueHistoryFrame.QueueBuffer);
|
||||
queueHistoryFrame.QueueItemOffsets = new List<uint>();
|
||||
|
||||
//For now all outbound, we will always have a single update in which they are processed (LATEUPDATE)
|
||||
QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Outbound][i].Add(NetworkUpdateStage.PostLateUpdate, queueHistoryFrame);
|
||||
}
|
||||
|
||||
if (!QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Inbound].ContainsKey(i))
|
||||
{
|
||||
QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Inbound].Add(i, new Dictionary<NetworkUpdateStage, RpcQueueHistoryFrame>());
|
||||
|
||||
//For inbound, we create a queue history frame per update stage
|
||||
foreach (NetworkUpdateStage netUpdateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
|
||||
{
|
||||
var rpcQueueHistoryFrame = new RpcQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType.Inbound, netUpdateStage);
|
||||
rpcQueueHistoryFrame.QueueBuffer = PooledNetworkBuffer.Get();
|
||||
rpcQueueHistoryFrame.QueueBuffer.Position = 0;
|
||||
rpcQueueHistoryFrame.QueueWriter = PooledNetworkWriter.Get(rpcQueueHistoryFrame.QueueBuffer);
|
||||
rpcQueueHistoryFrame.QueueReader = PooledNetworkReader.Get(rpcQueueHistoryFrame.QueueBuffer);
|
||||
rpcQueueHistoryFrame.QueueItemOffsets = new List<uint>();
|
||||
QueueHistory[RpcQueueHistoryFrame.QueueFrameType.Inbound][i].Add(netUpdateStage, rpcQueueHistoryFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//As long as this instance is using the pre-defined update stages
|
||||
if (!m_ProcessUpdateStagesExternally)
|
||||
{
|
||||
//Register with the network update loop system
|
||||
this.RegisterAllNetworkUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTestingState(bool enabled)
|
||||
{
|
||||
m_IsTestingEnabled = enabled;
|
||||
}
|
||||
|
||||
public bool IsTesting()
|
||||
{
|
||||
return m_IsTestingEnabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the stream indices and frames process properties
|
||||
/// </summary>
|
||||
private void ClearParameters()
|
||||
{
|
||||
m_InboundStreamBufferIndex = 0;
|
||||
m_OutBoundStreamBufferIndex = 0;
|
||||
m_OutboundFramesProcessed = 0;
|
||||
m_InboundFramesProcessed = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shutdown
|
||||
/// Flushes the internal messages
|
||||
/// Removes itself from the network update loop
|
||||
/// Disposes readers, writers, clears the queue history, and resets any parameters
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
{
|
||||
//As long as this instance is using the pre-defined update stages
|
||||
if (!m_ProcessUpdateStagesExternally)
|
||||
{
|
||||
//Remove ourself from the network loop update system
|
||||
this.UnregisterAllNetworkUpdates();
|
||||
}
|
||||
|
||||
//We need to make sure all internal messages (i.e. object destroy) are sent
|
||||
m_RpcQueueProcessor.InternalMessagesSendAndFlush();
|
||||
|
||||
//Dispose of any readers and writers
|
||||
foreach (var queueHistorySection in QueueHistory)
|
||||
{
|
||||
foreach (var queueHistoryItemByStage in queueHistorySection.Value)
|
||||
{
|
||||
foreach (var queueHistoryItem in queueHistoryItemByStage.Value)
|
||||
{
|
||||
queueHistoryItem.Value.QueueWriter?.Dispose();
|
||||
queueHistoryItem.Value.QueueReader?.Dispose();
|
||||
queueHistoryItem.Value.QueueBuffer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Clear history and parameters
|
||||
QueueHistory.Clear();
|
||||
|
||||
ClearParameters();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RpcQueueContainer - Constructor
|
||||
/// </summary>
|
||||
/// <param name="processInternally">determines if it handles processing internally or if it will be done externally</param>
|
||||
/// <param name="isLoopBackEnabled">turns loopback on or off (primarily debugging purposes)</param>
|
||||
public RpcQueueContainer(bool processExternally)
|
||||
{
|
||||
m_ProcessUpdateStagesExternally = processExternally;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
fileFormatVersion: 2
|
||||
<<<<<<< HEAD:com.unity.multiplayer.mlapi/Runtime/Core/Experimental/NetworkRPCQueueManager.cs.meta
|
||||
guid: 5191aef41bff11b479a8c81d51d2a08d
|
||||
=======
|
||||
guid: e2c328a4aae6b492f8999cd803e20bb2
|
||||
>>>>>>> origin/experimental-stdrpcapi:com.unity.multiplayer.mlapi/Editor/CodeGen/MLAPIRuntimeILPP.cs.meta
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,251 @@
|
|||
using System.Collections.Generic;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Transports;
|
||||
|
||||
namespace MLAPI.Messaging
|
||||
{
|
||||
/// <summary>
|
||||
/// QueueHistoryFrame
|
||||
/// Used by the RpcQueueContainer to hold queued RPCs
|
||||
/// All queued Rpcs end up in a PooledNetworkBuffer within a QueueHistoryFrame instance.
|
||||
/// </summary>
|
||||
public class RpcQueueHistoryFrame
|
||||
{
|
||||
public enum QueueFrameType
|
||||
{
|
||||
Inbound,
|
||||
Outbound,
|
||||
}
|
||||
|
||||
public bool IsDirty; //Used to determine if this queue history frame has been reset (cleaned) yet
|
||||
public bool HasLoopbackData; //Used to determine if a dirt frame is dirty because rpcs are being looped back betwen HostClient and HostServer
|
||||
public uint TotalSize;
|
||||
public List<uint> QueueItemOffsets;
|
||||
|
||||
public PooledNetworkBuffer QueueBuffer;
|
||||
public PooledNetworkWriter QueueWriter;
|
||||
public RpcQueueHistoryFrame LoopbackHistoryFrame; //Temporary fix for Host mode loopback work around.
|
||||
|
||||
|
||||
public PooledNetworkReader QueueReader;
|
||||
|
||||
private int m_QueueItemOffsetIndex;
|
||||
private RpcFrameQueueItem m_CurrentQueueItem;
|
||||
private readonly QueueFrameType m_QueueFrameType;
|
||||
private int m_MaximumClients;
|
||||
private long m_CurrentStreamSizeMark;
|
||||
private NetworkUpdateStage m_StreamUpdateStage; //Update stage specific to RPCs (typically inbound has most potential for variation)
|
||||
private const int k_MaxStreamBounds = 131072;
|
||||
private const int k_MinStreamBounds = 0;
|
||||
|
||||
/// <summary>
|
||||
/// GetQueueFrameType
|
||||
/// Returns whether this is an inbound or outbound frame
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public QueueFrameType GetQueueFrameType()
|
||||
{
|
||||
return m_QueueFrameType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MarkCurrentStreamSize
|
||||
/// Marks the current size of the stream (used primarily for sanity checks)
|
||||
/// </summary>
|
||||
public void MarkCurrentStreamPosition()
|
||||
{
|
||||
if (QueueBuffer != null)
|
||||
{
|
||||
m_CurrentStreamSizeMark = QueueBuffer.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_CurrentStreamSizeMark = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the current position that was marked (to track size of RPC msg)
|
||||
/// </summary>
|
||||
/// <returns>m_CurrentStreamSizeMark</returns>
|
||||
public long GetCurrentMarkedPosition()
|
||||
{
|
||||
return m_CurrentStreamSizeMark;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetCurrentQueueItem
|
||||
/// Internal method to get the current Queue Item from the stream at its current position
|
||||
/// </summary>
|
||||
/// <returns>FrameQueueItem</returns>
|
||||
private RpcFrameQueueItem GetCurrentQueueItem()
|
||||
{
|
||||
//Write the packed version of the queueItem to our current queue history buffer
|
||||
m_CurrentQueueItem.QueueItemType = (RpcQueueContainer.QueueItemType)QueueReader.ReadUInt16();
|
||||
m_CurrentQueueItem.Timestamp = QueueReader.ReadSingle();
|
||||
m_CurrentQueueItem.NetworkId = QueueReader.ReadUInt64();
|
||||
|
||||
//Clear out any current value for the client ids
|
||||
m_CurrentQueueItem.ClientNetworkIds = new ulong[0];
|
||||
|
||||
//If outbound, determine if any client ids needs to be added
|
||||
if (m_QueueFrameType == QueueFrameType.Outbound)
|
||||
{
|
||||
//Outbound we care about both channel and clients
|
||||
m_CurrentQueueItem.NetworkChannel = (NetworkChannel)QueueReader.ReadByteDirect();
|
||||
int NumClients = QueueReader.ReadInt32();
|
||||
if (NumClients > 0 && NumClients < m_MaximumClients)
|
||||
{
|
||||
ulong[] clientIdArray = new ulong[NumClients];
|
||||
for (int i = 0; i < NumClients; i++)
|
||||
{
|
||||
clientIdArray[i] = QueueReader.ReadUInt64();
|
||||
}
|
||||
|
||||
if (m_CurrentQueueItem.ClientNetworkIds == null)
|
||||
{
|
||||
m_CurrentQueueItem.ClientNetworkIds = clientIdArray;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_CurrentQueueItem.ClientNetworkIds = clientIdArray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_CurrentQueueItem.UpdateStage = m_StreamUpdateStage;
|
||||
|
||||
//Get the stream size
|
||||
m_CurrentQueueItem.StreamSize = QueueReader.ReadInt64();
|
||||
|
||||
//Sanity checking for boundaries
|
||||
if (m_CurrentQueueItem.StreamSize < k_MaxStreamBounds && m_CurrentQueueItem.StreamSize > k_MinStreamBounds)
|
||||
{
|
||||
//Inbound and Outbound message streams are handled differently
|
||||
if (m_QueueFrameType == QueueFrameType.Inbound)
|
||||
{
|
||||
//Get our offset
|
||||
long Position = QueueReader.ReadInt64();
|
||||
|
||||
//Always make sure we are positioned at the start of the stream before we write
|
||||
m_CurrentQueueItem.NetworkBuffer.Position = 0;
|
||||
|
||||
//Write the entire message to the m_CurrentQueueItem stream (1 stream is re-used for all incoming RPCs)
|
||||
m_CurrentQueueItem.NetworkWriter.ReadAndWrite(QueueReader, m_CurrentQueueItem.StreamSize);
|
||||
|
||||
//Reset the position back to the offset so std rpc API can process the message properly
|
||||
//(i.e. minus the already processed header)
|
||||
m_CurrentQueueItem.NetworkBuffer.Position = Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Create a byte array segment for outbound sending
|
||||
m_CurrentQueueItem.MessageData = QueueReader.CreateArraySegment((int)m_CurrentQueueItem.StreamSize, (int)QueueBuffer.Position);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogWarning($"{nameof(m_CurrentQueueItem)}.{nameof(RpcFrameQueueItem.StreamSize)} exceeds allowed size ({k_MaxStreamBounds} vs {m_CurrentQueueItem.StreamSize})! Exiting from the current RpcQueue enumeration loop!");
|
||||
m_CurrentQueueItem.QueueItemType = RpcQueueContainer.QueueItemType.None;
|
||||
}
|
||||
|
||||
return m_CurrentQueueItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetNextQueueItem
|
||||
/// Handles getting the next queue item from this frame
|
||||
/// If none are remaining, then it returns a queue item type of NONE
|
||||
/// </summary>
|
||||
/// <returns>FrameQueueItem</returns>
|
||||
internal RpcFrameQueueItem GetNextQueueItem()
|
||||
{
|
||||
QueueBuffer.Position = QueueItemOffsets[m_QueueItemOffsetIndex];
|
||||
m_QueueItemOffsetIndex++;
|
||||
if (m_QueueItemOffsetIndex >= QueueItemOffsets.Count)
|
||||
{
|
||||
m_CurrentQueueItem.QueueItemType = RpcQueueContainer.QueueItemType.None;
|
||||
return m_CurrentQueueItem;
|
||||
}
|
||||
|
||||
return GetCurrentQueueItem();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GetFirstQueueItem
|
||||
/// Should be called the first time a queue item is pulled from a queue history frame.
|
||||
/// This will reset the frame's stream indices and add a new stream and stream writer to the m_CurrentQueueItem instance.
|
||||
/// </summary>
|
||||
/// <returns>FrameQueueItem</returns>
|
||||
internal RpcFrameQueueItem GetFirstQueueItem()
|
||||
{
|
||||
if (QueueBuffer.Position > 0)
|
||||
{
|
||||
m_QueueItemOffsetIndex = 0;
|
||||
QueueBuffer.Position = 0;
|
||||
|
||||
if (m_QueueFrameType == QueueFrameType.Inbound)
|
||||
{
|
||||
if (m_CurrentQueueItem.NetworkBuffer == null)
|
||||
{
|
||||
m_CurrentQueueItem.NetworkBuffer = PooledNetworkBuffer.Get();
|
||||
}
|
||||
|
||||
if (m_CurrentQueueItem.NetworkWriter == null)
|
||||
{
|
||||
m_CurrentQueueItem.NetworkWriter = PooledNetworkWriter.Get(m_CurrentQueueItem.NetworkBuffer);
|
||||
}
|
||||
|
||||
if (m_CurrentQueueItem.NetworkReader == null)
|
||||
{
|
||||
m_CurrentQueueItem.NetworkReader = PooledNetworkReader.Get(m_CurrentQueueItem.NetworkBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
return GetCurrentQueueItem();
|
||||
}
|
||||
|
||||
m_CurrentQueueItem.QueueItemType = RpcQueueContainer.QueueItemType.None;
|
||||
return m_CurrentQueueItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CloseQueue
|
||||
/// Should be called once all processing of the current frame is complete.
|
||||
/// This only closes the m_CurrentQueueItem's stream which is used as a "middle-man" (currently)
|
||||
/// for delivering the RPC message to the method requesting a queue item from a frame.
|
||||
/// </summary>
|
||||
public void CloseQueue()
|
||||
{
|
||||
if (m_CurrentQueueItem.NetworkWriter != null)
|
||||
{
|
||||
m_CurrentQueueItem.NetworkWriter.Dispose();
|
||||
m_CurrentQueueItem.NetworkWriter = null;
|
||||
}
|
||||
|
||||
if (m_CurrentQueueItem.NetworkReader != null)
|
||||
{
|
||||
m_CurrentQueueItem.NetworkReader.Dispose();
|
||||
m_CurrentQueueItem.NetworkReader = null;
|
||||
}
|
||||
|
||||
if (m_CurrentQueueItem.NetworkBuffer != null)
|
||||
{
|
||||
m_CurrentQueueItem.NetworkBuffer.Dispose();
|
||||
m_CurrentQueueItem.NetworkBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// QueueHistoryFrame Constructor
|
||||
/// </summary>
|
||||
/// <param name="queueType">type of queue history frame (Inbound/Outbound)</param>
|
||||
public RpcQueueHistoryFrame(QueueFrameType queueType, NetworkUpdateStage updateStage, int maxClients = 512)
|
||||
{
|
||||
m_MaximumClients = maxClients;
|
||||
m_QueueFrameType = queueType;
|
||||
m_CurrentQueueItem = new RpcFrameQueueItem();
|
||||
m_StreamUpdateStage = updateStage;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 179940fa53696f24a91ff34ce0439d47
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,261 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Profiling;
|
||||
using MLAPI.Configuration;
|
||||
using MLAPI.Profiling;
|
||||
|
||||
namespace MLAPI.Messaging
|
||||
{
|
||||
/// <summary>
|
||||
/// RpcQueueProcessing
|
||||
/// Handles processing of RpcQueues
|
||||
/// Inbound to invocation
|
||||
/// Outbound to send
|
||||
/// </summary>
|
||||
internal class RpcQueueProcessor
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
private static ProfilerMarker s_ProcessReceiveQueue = new ProfilerMarker($"{nameof(RpcQueueProcessor)}.{nameof(ProcessReceiveQueue)}");
|
||||
private static ProfilerMarker s_ProcessSendQueue = new ProfilerMarker($"{nameof(RpcQueueProcessor)}.{nameof(ProcessSendQueue)}");
|
||||
#endif
|
||||
|
||||
// Batcher object used to manage the RPC batching on the send side
|
||||
private readonly RpcBatcher m_RpcBatcher = new RpcBatcher();
|
||||
private const int k_BatchThreshold = 512;
|
||||
|
||||
//NSS-TODO: Need to determine how we want to handle all other MLAPI send types
|
||||
//Temporary place to keep internal MLAPI messages
|
||||
private readonly List<RpcFrameQueueItem> m_InternalMLAPISendQueue = new List<RpcFrameQueueItem>();
|
||||
|
||||
/// <summary>
|
||||
/// ProcessReceiveQueue
|
||||
/// Public facing interface method to start processing all RPCs in the current inbound frame
|
||||
/// </summary>
|
||||
public void ProcessReceiveQueue(NetworkUpdateStage currentStage)
|
||||
{
|
||||
bool advanceFrameHistory = false;
|
||||
var rpcQueueContainer = NetworkManager.Singleton.RpcQueueContainer;
|
||||
if (rpcQueueContainer != null)
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_ProcessReceiveQueue.Begin();
|
||||
#endif
|
||||
var currentFrame = rpcQueueContainer.GetQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType.Inbound, currentStage);
|
||||
var nextFrame = rpcQueueContainer.GetQueueHistoryFrame(RpcQueueHistoryFrame.QueueFrameType.Inbound, currentStage, true);
|
||||
if (nextFrame.IsDirty && nextFrame.HasLoopbackData)
|
||||
{
|
||||
advanceFrameHistory = true;
|
||||
}
|
||||
|
||||
if (currentFrame != null && currentFrame.IsDirty)
|
||||
{
|
||||
var currentQueueItem = currentFrame.GetFirstQueueItem();
|
||||
while (currentQueueItem.QueueItemType != RpcQueueContainer.QueueItemType.None)
|
||||
{
|
||||
advanceFrameHistory = true;
|
||||
|
||||
if (rpcQueueContainer.IsTesting())
|
||||
{
|
||||
Debug.Log($"RPC invoked during the {currentStage} update stage.");
|
||||
}
|
||||
|
||||
NetworkManager.InvokeRpc(currentQueueItem);
|
||||
ProfilerStatManager.RpcsQueueProc.Record();
|
||||
PerformanceDataManager.Increment(ProfilerConstants.RpcQueueProcessed);
|
||||
currentQueueItem = currentFrame.GetNextQueueItem();
|
||||
}
|
||||
|
||||
//We call this to dispose of the shared stream writer and stream
|
||||
currentFrame.CloseQueue();
|
||||
}
|
||||
|
||||
if (advanceFrameHistory)
|
||||
{
|
||||
rpcQueueContainer.AdvanceFrameHistory(RpcQueueHistoryFrame.QueueFrameType.Inbound);
|
||||
}
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_ProcessReceiveQueue.End();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ProcessSendQueue
|
||||
/// Called to send both performance RPC and internal messages and then flush the outbound queue
|
||||
/// </summary>
|
||||
public void ProcessSendQueue()
|
||||
{
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_ProcessSendQueue.Begin();
|
||||
#endif
|
||||
|
||||
RpcQueueSendAndFlush();
|
||||
|
||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||
s_ProcessSendQueue.End();
|
||||
#endif
|
||||
InternalMessagesSendAndFlush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// QueueInternalMLAPICommand
|
||||
/// Added this as an example of how to add internal messages to the outbound send queue
|
||||
/// </summary>
|
||||
/// <param name="queueItem">message queue item to add<</param>
|
||||
public void QueueInternalMLAPICommand(RpcFrameQueueItem queueItem)
|
||||
{
|
||||
m_InternalMLAPISendQueue.Add(queueItem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic Sending Method for Internal Messages
|
||||
/// TODO: Will need to open this up for discussion, but we will want to determine if this is how we want internal MLAPI command
|
||||
/// messages to be sent. We might want specific commands to occur during specific network update regions (see NetworkUpdate
|
||||
/// </summary>
|
||||
public void InternalMessagesSendAndFlush()
|
||||
{
|
||||
foreach (RpcFrameQueueItem queueItem in m_InternalMLAPISendQueue)
|
||||
{
|
||||
var PoolStream = queueItem.NetworkBuffer;
|
||||
if (NetworkManager.Singleton.IsListening)
|
||||
{
|
||||
switch (queueItem.QueueItemType)
|
||||
{
|
||||
case RpcQueueContainer.QueueItemType.CreateObject:
|
||||
{
|
||||
foreach (ulong clientId in queueItem.ClientNetworkIds)
|
||||
{
|
||||
InternalMessageSender.Send(clientId, NetworkConstants.ADD_OBJECT, queueItem.NetworkChannel, PoolStream);
|
||||
}
|
||||
|
||||
PerformanceDataManager.Increment(ProfilerConstants.RpcSent, queueItem.ClientNetworkIds.Length);
|
||||
ProfilerStatManager.RpcsSent.Record(queueItem.ClientNetworkIds.Length);
|
||||
break;
|
||||
}
|
||||
case RpcQueueContainer.QueueItemType.DestroyObject:
|
||||
{
|
||||
foreach (ulong clientId in queueItem.ClientNetworkIds)
|
||||
{
|
||||
InternalMessageSender.Send(clientId, NetworkConstants.DESTROY_OBJECT, queueItem.NetworkChannel, PoolStream);
|
||||
}
|
||||
|
||||
PerformanceDataManager.Increment(ProfilerConstants.RpcSent, queueItem.ClientNetworkIds.Length);
|
||||
ProfilerStatManager.RpcsSent.Record(queueItem.ClientNetworkIds.Length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PoolStream.Dispose();
|
||||
}
|
||||
|
||||
m_InternalMLAPISendQueue.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RPCQueueSendAndFlush
|
||||
/// Sends all RPC queue items in the current outbound frame
|
||||
/// </summary>
|
||||
private void RpcQueueSendAndFlush()
|
||||
{
|
||||
var advanceFrameHistory = false;
|
||||
var rpcQueueContainer = NetworkManager.Singleton.RpcQueueContainer;
|
||||
if (rpcQueueContainer != null)
|
||||
{
|
||||
var currentFrame = rpcQueueContainer.GetCurrentFrame(RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
|
||||
if (currentFrame != null)
|
||||
{
|
||||
var currentQueueItem = currentFrame.GetFirstQueueItem();
|
||||
while (currentQueueItem.QueueItemType != RpcQueueContainer.QueueItemType.None)
|
||||
{
|
||||
advanceFrameHistory = true;
|
||||
if (rpcQueueContainer.IsUsingBatching())
|
||||
{
|
||||
m_RpcBatcher.QueueItem(currentQueueItem);
|
||||
|
||||
m_RpcBatcher.SendItems(k_BatchThreshold, SendCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
SendFrameQueueItem(currentQueueItem);
|
||||
}
|
||||
|
||||
currentQueueItem = currentFrame.GetNextQueueItem();
|
||||
}
|
||||
|
||||
//If the size is < m_BatchThreshold then just send the messages
|
||||
if (advanceFrameHistory && rpcQueueContainer.IsUsingBatching())
|
||||
{
|
||||
m_RpcBatcher.SendItems(0, SendCallback);
|
||||
}
|
||||
}
|
||||
|
||||
//If we processed any RPCs, then advance to the next frame
|
||||
if (advanceFrameHistory)
|
||||
{
|
||||
rpcQueueContainer.AdvanceFrameHistory(RpcQueueHistoryFrame.QueueFrameType.Outbound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// SendCallback
|
||||
/// This is the callback from the batcher when it need to send a batch
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="clientId"> clientId to send to</param>
|
||||
/// <param name="sendStream"> the stream to send</param>
|
||||
private static void SendCallback(ulong clientId, RpcBatcher.SendStream sendStream)
|
||||
{
|
||||
var length = (int)sendStream.Buffer.Length;
|
||||
var bytes = sendStream.Buffer.GetBuffer();
|
||||
var sendBuffer = new ArraySegment<byte>(bytes, 0, length);
|
||||
|
||||
NetworkManager.Singleton.NetworkConfig.NetworkTransport.Send(clientId, sendBuffer, sendStream.NetworkChannel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SendFrameQueueItem
|
||||
/// Sends the RPC Queue Item to the specified destination
|
||||
/// </summary>
|
||||
/// <param name="queueItem">Information on what to send</param>
|
||||
private void SendFrameQueueItem(RpcFrameQueueItem queueItem)
|
||||
{
|
||||
switch (queueItem.QueueItemType)
|
||||
{
|
||||
case RpcQueueContainer.QueueItemType.ServerRpc:
|
||||
{
|
||||
NetworkManager.Singleton.NetworkConfig.NetworkTransport.Send(queueItem.NetworkId, queueItem.MessageData, queueItem.NetworkChannel);
|
||||
|
||||
//For each packet sent, we want to record how much data we have sent
|
||||
|
||||
PerformanceDataManager.Increment(ProfilerConstants.ByteSent, (int)queueItem.StreamSize);
|
||||
PerformanceDataManager.Increment(ProfilerConstants.RpcSent);
|
||||
ProfilerStatManager.BytesSent.Record((int)queueItem.StreamSize);
|
||||
ProfilerStatManager.RpcsSent.Record();
|
||||
break;
|
||||
}
|
||||
case RpcQueueContainer.QueueItemType.ClientRpc:
|
||||
{
|
||||
foreach (ulong clientid in queueItem.ClientNetworkIds)
|
||||
{
|
||||
NetworkManager.Singleton.NetworkConfig.NetworkTransport.Send(clientid, queueItem.MessageData, queueItem.NetworkChannel);
|
||||
|
||||
//For each packet sent, we want to record how much data we have sent
|
||||
PerformanceDataManager.Increment(ProfilerConstants.ByteSent, (int)queueItem.StreamSize);
|
||||
ProfilerStatManager.BytesSent.Record((int)queueItem.StreamSize);
|
||||
}
|
||||
|
||||
//For each client we send to, we want to record how many RPCs we have sent
|
||||
PerformanceDataManager.Increment(ProfilerConstants.RpcSent, queueItem.ClientNetworkIds.Length);
|
||||
ProfilerStatManager.RpcsSent.Record(queueItem.ClientNetworkIds.Length);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
fileFormatVersion: 2
|
||||
<<<<<<< HEAD:com.unity.multiplayer.mlapi/Runtime/Core/Experimental/NetworkProcessRPCQueue.cs.meta
|
||||
guid: ce454f612d3d587498b214111dde1766
|
||||
=======
|
||||
guid: 363e6284d2a3f4d0eb19b11f82c399f9
|
||||
>>>>>>> origin/experimental-stdrpcapi:com.unity.multiplayer.mlapi/Editor/CodeGen/ILPP.cs.meta
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 40a137fab0c0c6a46a2440d9a859755f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 614a7000210157648b54dd16914b97b5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,598 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Transports;
|
||||
|
||||
namespace MLAPI.NetworkVariable.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Event based NetworkVariable container for syncing Dictionaries
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type for the dictionary keys</typeparam>
|
||||
/// <typeparam name="TValue">The type for the dictionary values</typeparam>
|
||||
public class NetworkDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INetworkVariable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the last time the variable was synced
|
||||
/// </summary>
|
||||
public float LastSyncedTime { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The settings for this container
|
||||
/// </summary>
|
||||
public readonly NetworkVariableSettings Settings = new NetworkVariableSettings();
|
||||
|
||||
private readonly IDictionary<TKey, TValue> m_Dictionary = new Dictionary<TKey, TValue>();
|
||||
private NetworkBehaviour m_NetworkBehaviour;
|
||||
private readonly List<NetworkDictionaryEvent<TKey, TValue>> m_DirtyEvents = new List<NetworkDictionaryEvent<TKey, TValue>>();
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for dictionary changed event
|
||||
/// </summary>
|
||||
/// <param name="changeEvent">Struct containing information about the change event</param>
|
||||
public delegate void OnDictionaryChangedDelegate(NetworkDictionaryEvent<TKey, TValue> changeEvent);
|
||||
|
||||
/// <summary>
|
||||
/// The callback to be invoked when the dictionary gets changed
|
||||
/// </summary>
|
||||
public event OnDictionaryChangedDelegate OnDictionaryChanged;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkDictionary with the default value and settings
|
||||
/// </summary>
|
||||
public NetworkDictionary() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkDictionary with the default value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings to use for the NetworkDictionary</param>
|
||||
public NetworkDictionary(NetworkVariableSettings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkDictionary with a custom value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings to use for the NetworkDictionary</param>
|
||||
/// <param name="value">The initial value to use for the NetworkDictionary</param>
|
||||
public NetworkDictionary(NetworkVariableSettings settings, IDictionary<TKey, TValue> value)
|
||||
{
|
||||
Settings = settings;
|
||||
m_Dictionary = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkDictionary with a custom value and the default settings
|
||||
/// </summary>
|
||||
/// <param name="value">The initial value to use for the NetworkDictionary</param>
|
||||
public NetworkDictionary(IDictionary<TKey, TValue> value)
|
||||
{
|
||||
m_Dictionary = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResetDirty()
|
||||
{
|
||||
m_DirtyEvents.Clear();
|
||||
LastSyncedTime = NetworkManager.Singleton.NetworkTime;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkChannel GetChannel()
|
||||
{
|
||||
return Settings.SendNetworkChannel;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReadDelta(Stream stream, bool keepDirtyDelta, ushort localTick, ushort remoteTick)
|
||||
{
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
ushort deltaCount = reader.ReadUInt16Packed();
|
||||
for (int i = 0; i < deltaCount; i++)
|
||||
{
|
||||
NetworkDictionaryEvent<TKey, TValue>.EventType eventType = (NetworkDictionaryEvent<TKey, TValue>.EventType)reader.ReadBits(3);
|
||||
switch (eventType)
|
||||
{
|
||||
case NetworkDictionaryEvent<TKey, TValue>.EventType.Add:
|
||||
{
|
||||
TKey key = (TKey)reader.ReadObjectPacked(typeof(TKey));
|
||||
TValue value = (TValue)reader.ReadObjectPacked(typeof(TValue));
|
||||
m_Dictionary.Add(key, value);
|
||||
|
||||
if (OnDictionaryChanged != null)
|
||||
{
|
||||
OnDictionaryChanged(new NetworkDictionaryEvent<TKey, TValue>
|
||||
{
|
||||
Type = eventType,
|
||||
Key = key,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkDictionaryEvent<TKey, TValue>()
|
||||
{
|
||||
Type = eventType,
|
||||
Key = key,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkDictionaryEvent<TKey, TValue>.EventType.Remove:
|
||||
{
|
||||
TKey key = (TKey)reader.ReadObjectPacked(typeof(TKey));
|
||||
TValue value;
|
||||
m_Dictionary.TryGetValue(key, out value);
|
||||
m_Dictionary.Remove(key);
|
||||
|
||||
if (OnDictionaryChanged != null)
|
||||
{
|
||||
OnDictionaryChanged(new NetworkDictionaryEvent<TKey, TValue>
|
||||
{
|
||||
Type = eventType,
|
||||
Key = key,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkDictionaryEvent<TKey, TValue>()
|
||||
{
|
||||
Type = eventType,
|
||||
Key = key,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkDictionaryEvent<TKey, TValue>.EventType.RemovePair:
|
||||
{
|
||||
TKey key = (TKey)reader.ReadObjectPacked(typeof(TKey));
|
||||
TValue value = (TValue)reader.ReadObjectPacked(typeof(TValue));
|
||||
m_Dictionary.Remove(new KeyValuePair<TKey, TValue>(key, value));
|
||||
|
||||
if (OnDictionaryChanged != null)
|
||||
{
|
||||
OnDictionaryChanged(new NetworkDictionaryEvent<TKey, TValue>
|
||||
{
|
||||
Type = eventType,
|
||||
Key = key,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkDictionaryEvent<TKey, TValue>()
|
||||
{
|
||||
Type = eventType,
|
||||
Key = key,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkDictionaryEvent<TKey, TValue>.EventType.Clear:
|
||||
{
|
||||
//read nothing
|
||||
m_Dictionary.Clear();
|
||||
|
||||
if (OnDictionaryChanged != null)
|
||||
{
|
||||
OnDictionaryChanged(new NetworkDictionaryEvent<TKey, TValue>
|
||||
{
|
||||
Type = eventType
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkDictionaryEvent<TKey, TValue>
|
||||
{
|
||||
Type = eventType
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkDictionaryEvent<TKey, TValue>.EventType.Value:
|
||||
{
|
||||
TKey key = (TKey)reader.ReadObjectPacked(typeof(TKey));
|
||||
TValue value = (TValue)reader.ReadObjectPacked(typeof(TValue));
|
||||
|
||||
m_Dictionary[key] = value;
|
||||
|
||||
if (OnDictionaryChanged != null)
|
||||
{
|
||||
OnDictionaryChanged(new NetworkDictionaryEvent<TKey, TValue>
|
||||
{
|
||||
Type = eventType,
|
||||
Key = key,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkDictionaryEvent<TKey, TValue>()
|
||||
{
|
||||
Type = eventType,
|
||||
Key = key,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReadField(Stream stream, ushort localTick, ushort remoteTick)
|
||||
{
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
m_Dictionary.Clear();
|
||||
ushort entryCount = reader.ReadUInt16Packed();
|
||||
for (int i = 0; i < entryCount; i++)
|
||||
{
|
||||
TKey key = (TKey)reader.ReadObjectPacked(typeof(TKey));
|
||||
TValue value = (TValue)reader.ReadObjectPacked(typeof(TValue));
|
||||
m_Dictionary.Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetNetworkBehaviour(NetworkBehaviour behaviour)
|
||||
{
|
||||
m_NetworkBehaviour = behaviour;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return m_Dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteDelta(Stream stream)
|
||||
{
|
||||
using (var writer = PooledNetworkWriter.Get(stream))
|
||||
{
|
||||
writer.WriteUInt16Packed((ushort)m_DirtyEvents.Count);
|
||||
for (int i = 0; i < m_DirtyEvents.Count; i++)
|
||||
{
|
||||
writer.WriteBits((byte)m_DirtyEvents[i].Type, 3);
|
||||
switch (m_DirtyEvents[i].Type)
|
||||
{
|
||||
case NetworkDictionaryEvent<TKey, TValue>.EventType.Add:
|
||||
{
|
||||
writer.WriteObjectPacked(m_DirtyEvents[i].Key);
|
||||
writer.WriteObjectPacked(m_DirtyEvents[i].Value);
|
||||
}
|
||||
break;
|
||||
case NetworkDictionaryEvent<TKey, TValue>.EventType.Remove:
|
||||
{
|
||||
writer.WriteObjectPacked(m_DirtyEvents[i].Key);
|
||||
}
|
||||
break;
|
||||
case NetworkDictionaryEvent<TKey, TValue>.EventType.RemovePair:
|
||||
{
|
||||
writer.WriteObjectPacked(m_DirtyEvents[i].Key);
|
||||
writer.WriteObjectPacked(m_DirtyEvents[i].Value);
|
||||
}
|
||||
break;
|
||||
case NetworkDictionaryEvent<TKey, TValue>.EventType.Clear:
|
||||
{
|
||||
//write nothing
|
||||
}
|
||||
break;
|
||||
case NetworkDictionaryEvent<TKey, TValue>.EventType.Value:
|
||||
{
|
||||
writer.WriteObjectPacked(m_DirtyEvents[i].Key);
|
||||
writer.WriteObjectPacked(m_DirtyEvents[i].Value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteField(Stream stream)
|
||||
{
|
||||
using (var writer = PooledNetworkWriter.Get(stream))
|
||||
{
|
||||
writer.WriteUInt16Packed((ushort)m_Dictionary.Count);
|
||||
foreach (KeyValuePair<TKey, TValue> pair in m_Dictionary)
|
||||
{
|
||||
writer.WriteObjectPacked(pair.Key);
|
||||
writer.WriteObjectPacked(pair.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanClientWrite(ulong clientId)
|
||||
{
|
||||
switch (Settings.WritePermission)
|
||||
{
|
||||
case NetworkVariablePermission.Everyone:
|
||||
return true;
|
||||
case NetworkVariablePermission.ServerOnly:
|
||||
return false;
|
||||
case NetworkVariablePermission.OwnerOnly:
|
||||
return m_NetworkBehaviour.OwnerClientId == clientId;
|
||||
case NetworkVariablePermission.Custom:
|
||||
{
|
||||
if (Settings.WritePermissionCallback == null) return false;
|
||||
return Settings.WritePermissionCallback(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanClientRead(ulong clientId)
|
||||
{
|
||||
switch (Settings.ReadPermission)
|
||||
{
|
||||
case NetworkVariablePermission.Everyone:
|
||||
return true;
|
||||
case NetworkVariablePermission.ServerOnly:
|
||||
return false;
|
||||
case NetworkVariablePermission.OwnerOnly:
|
||||
return m_NetworkBehaviour.OwnerClientId == clientId;
|
||||
case NetworkVariablePermission.Custom:
|
||||
{
|
||||
if (Settings.ReadPermissionCallback == null) return false;
|
||||
return Settings.ReadPermissionCallback(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDirty()
|
||||
{
|
||||
if (m_DirtyEvents.Count == 0) return false;
|
||||
if (Settings.SendTickrate == 0) return true;
|
||||
if (Settings.SendTickrate < 0) return false;
|
||||
if (NetworkManager.Singleton.NetworkTime - LastSyncedTime >= (1f / Settings.SendTickrate)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get => m_Dictionary[key];
|
||||
set
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_Dictionary[key] = value;
|
||||
|
||||
NetworkDictionaryEvent<TKey, TValue> dictionaryEvent = new NetworkDictionaryEvent<TKey, TValue>()
|
||||
{
|
||||
Type = NetworkDictionaryEvent<TKey, TValue>.EventType.Value,
|
||||
Key = key,
|
||||
Value = value
|
||||
};
|
||||
|
||||
HandleAddDictionaryEvent(dictionaryEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<TKey> Keys => m_Dictionary.Keys;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<TValue> Values => m_Dictionary.Values;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => m_Dictionary.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsReadOnly => m_Dictionary.IsReadOnly;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_Dictionary.Add(key, value);
|
||||
|
||||
NetworkDictionaryEvent<TKey, TValue> dictionaryEvent = new NetworkDictionaryEvent<TKey, TValue>()
|
||||
{
|
||||
Type = NetworkDictionaryEvent<TKey, TValue>.EventType.Add,
|
||||
Key = key,
|
||||
Value = value
|
||||
};
|
||||
|
||||
HandleAddDictionaryEvent(dictionaryEvent);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_Dictionary.Add(item);
|
||||
|
||||
NetworkDictionaryEvent<TKey, TValue> dictionaryEvent = new NetworkDictionaryEvent<TKey, TValue>()
|
||||
{
|
||||
Type = NetworkDictionaryEvent<TKey, TValue>.EventType.Add,
|
||||
Key = item.Key,
|
||||
Value = item.Value
|
||||
};
|
||||
|
||||
HandleAddDictionaryEvent(dictionaryEvent);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_Dictionary.Clear();
|
||||
|
||||
NetworkDictionaryEvent<TKey, TValue> dictionaryEvent = new NetworkDictionaryEvent<TKey, TValue>()
|
||||
{
|
||||
Type = NetworkDictionaryEvent<TKey, TValue>.EventType.Clear
|
||||
};
|
||||
|
||||
HandleAddDictionaryEvent(dictionaryEvent);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return m_Dictionary.Contains(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
return m_Dictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
m_Dictionary.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return m_Dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer)
|
||||
m_Dictionary.Remove(key);
|
||||
|
||||
TValue value;
|
||||
m_Dictionary.TryGetValue(key, out value);
|
||||
|
||||
NetworkDictionaryEvent<TKey, TValue> dictionaryEvent = new NetworkDictionaryEvent<TKey, TValue>()
|
||||
{
|
||||
Type = NetworkDictionaryEvent<TKey, TValue>.EventType.Remove,
|
||||
Key = key,
|
||||
Value = value
|
||||
};
|
||||
|
||||
HandleAddDictionaryEvent(dictionaryEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_Dictionary.Remove(item);
|
||||
|
||||
NetworkDictionaryEvent<TKey, TValue> dictionaryEvent = new NetworkDictionaryEvent<TKey, TValue>()
|
||||
{
|
||||
Type = NetworkDictionaryEvent<TKey, TValue>.EventType.RemovePair,
|
||||
Key = item.Key,
|
||||
Value = item.Value
|
||||
};
|
||||
|
||||
HandleAddDictionaryEvent(dictionaryEvent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return m_Dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
private void HandleAddDictionaryEvent(NetworkDictionaryEvent<TKey, TValue> dictionaryEvent)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
if (NetworkManager.Singleton.ConnectedClients.Count > 0)
|
||||
{
|
||||
m_DirtyEvents.Add(dictionaryEvent);
|
||||
}
|
||||
|
||||
OnDictionaryChanged?.Invoke(dictionaryEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_DirtyEvents.Add(dictionaryEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public ushort RemoteTick
|
||||
{
|
||||
get
|
||||
{
|
||||
// todo: implement proper network tick for NetworkDictionary
|
||||
return NetworkTickSystem.NoTick;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Struct containing event information about changes to a NetworkDictionary.
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">The type for the dictionary key that the event is about</typeparam>
|
||||
/// <typeparam name="TValue">The type for the dictionary value that the event is about</typeparam>
|
||||
public struct NetworkDictionaryEvent<TKey, TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum representing the different operations available for triggering an event.
|
||||
/// </summary>
|
||||
public enum EventType
|
||||
{
|
||||
/// <summary>
|
||||
/// Add
|
||||
/// </summary>
|
||||
Add,
|
||||
|
||||
/// <summary>
|
||||
/// Remove
|
||||
/// </summary>
|
||||
Remove,
|
||||
|
||||
/// <summary>
|
||||
/// Remove pair
|
||||
/// </summary>
|
||||
RemovePair,
|
||||
|
||||
/// <summary>
|
||||
/// Clear
|
||||
/// </summary>
|
||||
Clear,
|
||||
|
||||
/// <summary>
|
||||
/// Value changed
|
||||
/// </summary>
|
||||
Value
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum representing the operation made to the dictionary.
|
||||
/// </summary>
|
||||
public EventType Type;
|
||||
|
||||
/// <summary>
|
||||
/// the key changed, added or removed if available.
|
||||
/// </summary>
|
||||
public TKey Key;
|
||||
|
||||
/// <summary>
|
||||
/// The value changed, added or removed if available.
|
||||
/// </summary>
|
||||
public TValue Value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 262db7b2db1128748a2e88dd7e20a764
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,602 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Transports;
|
||||
|
||||
namespace MLAPI.NetworkVariable.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Event based NetworkVariable container for syncing Lists
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type for the list</typeparam>
|
||||
public class NetworkList<T> : IList<T>, INetworkVariable
|
||||
{
|
||||
private readonly IList<T> m_List = new List<T>();
|
||||
private readonly List<NetworkListEvent<T>> m_DirtyEvents = new List<NetworkListEvent<T>>();
|
||||
private NetworkBehaviour m_NetworkBehaviour;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last time the variable was synced
|
||||
/// </summary>
|
||||
public float LastSyncedTime { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The settings for this container
|
||||
/// </summary>
|
||||
public readonly NetworkVariableSettings Settings = new NetworkVariableSettings();
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for list changed event
|
||||
/// </summary>
|
||||
/// <param name="changeEvent">Struct containing information about the change event</param>
|
||||
public delegate void OnListChangedDelegate(NetworkListEvent<T> changeEvent);
|
||||
|
||||
/// <summary>
|
||||
/// The callback to be invoked when the list gets changed
|
||||
/// </summary>
|
||||
public event OnListChangedDelegate OnListChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkList with the default value and settings
|
||||
/// </summary>
|
||||
public NetworkList() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkList with the default value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings to use for the NetworkList</param>
|
||||
public NetworkList(NetworkVariableSettings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkList with a custom value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings to use for the NetworkList</param>
|
||||
/// <param name="value">The initial value to use for the NetworkList</param>
|
||||
public NetworkList(NetworkVariableSettings settings, IList<T> value)
|
||||
{
|
||||
Settings = settings;
|
||||
m_List = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkList with a custom value and the default settings
|
||||
/// </summary>
|
||||
/// <param name="value">The initial value to use for the NetworkList</param>
|
||||
public NetworkList(IList<T> value)
|
||||
{
|
||||
m_List = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResetDirty()
|
||||
{
|
||||
m_DirtyEvents.Clear();
|
||||
LastSyncedTime = NetworkManager.Singleton.NetworkTime;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDirty()
|
||||
{
|
||||
if (m_DirtyEvents.Count == 0) return false;
|
||||
if (Settings.SendTickrate == 0) return true;
|
||||
if (Settings.SendTickrate < 0) return false;
|
||||
if (NetworkManager.Singleton.NetworkTime - LastSyncedTime >= (1f / Settings.SendTickrate)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkChannel GetChannel()
|
||||
{
|
||||
return Settings.SendNetworkChannel;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanClientWrite(ulong clientId)
|
||||
{
|
||||
switch (Settings.WritePermission)
|
||||
{
|
||||
case NetworkVariablePermission.Everyone:
|
||||
return true;
|
||||
case NetworkVariablePermission.ServerOnly:
|
||||
return false;
|
||||
case NetworkVariablePermission.OwnerOnly:
|
||||
return m_NetworkBehaviour.OwnerClientId == clientId;
|
||||
case NetworkVariablePermission.Custom:
|
||||
{
|
||||
if (Settings.WritePermissionCallback == null) return false;
|
||||
return Settings.WritePermissionCallback(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanClientRead(ulong clientId)
|
||||
{
|
||||
switch (Settings.ReadPermission)
|
||||
{
|
||||
case NetworkVariablePermission.Everyone:
|
||||
return true;
|
||||
case NetworkVariablePermission.ServerOnly:
|
||||
return false;
|
||||
case NetworkVariablePermission.OwnerOnly:
|
||||
return m_NetworkBehaviour.OwnerClientId == clientId;
|
||||
case NetworkVariablePermission.Custom:
|
||||
{
|
||||
if (Settings.ReadPermissionCallback == null) return false;
|
||||
return Settings.ReadPermissionCallback(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteDelta(Stream stream)
|
||||
{
|
||||
using (var writer = PooledNetworkWriter.Get(stream))
|
||||
{
|
||||
writer.WriteUInt16Packed((ushort)m_DirtyEvents.Count);
|
||||
for (int i = 0; i < m_DirtyEvents.Count; i++)
|
||||
{
|
||||
writer.WriteBits((byte)m_DirtyEvents[i].Type, 3);
|
||||
switch (m_DirtyEvents[i].Type)
|
||||
{
|
||||
case NetworkListEvent<T>.EventType.Add:
|
||||
{
|
||||
writer.WriteObjectPacked(m_DirtyEvents[i].Value); //BOX
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Insert:
|
||||
{
|
||||
writer.WriteInt32Packed(m_DirtyEvents[i].Index);
|
||||
writer.WriteObjectPacked(m_DirtyEvents[i].Value); //BOX
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Remove:
|
||||
{
|
||||
writer.WriteObjectPacked(m_DirtyEvents[i].Value); //BOX
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.RemoveAt:
|
||||
{
|
||||
writer.WriteInt32Packed(m_DirtyEvents[i].Index);
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Value:
|
||||
{
|
||||
writer.WriteInt32Packed(m_DirtyEvents[i].Index);
|
||||
writer.WriteObjectPacked(m_DirtyEvents[i].Value); //BOX
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Clear:
|
||||
{
|
||||
//Nothing has to be written
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteField(Stream stream)
|
||||
{
|
||||
using (var writer = PooledNetworkWriter.Get(stream))
|
||||
{
|
||||
writer.WriteUInt16Packed((ushort)m_List.Count);
|
||||
for (int i = 0; i < m_List.Count; i++)
|
||||
{
|
||||
writer.WriteObjectPacked(m_List[i]); //BOX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReadField(Stream stream, ushort localTick, ushort remoteTick)
|
||||
{
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
m_List.Clear();
|
||||
ushort count = reader.ReadUInt16Packed();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
m_List.Add((T)reader.ReadObjectPacked(typeof(T))); //BOX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReadDelta(Stream stream, bool keepDirtyDelta, ushort localTick, ushort remoteTick)
|
||||
{
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
ushort deltaCount = reader.ReadUInt16Packed();
|
||||
for (int i = 0; i < deltaCount; i++)
|
||||
{
|
||||
NetworkListEvent<T>.EventType eventType = (NetworkListEvent<T>.EventType)reader.ReadBits(3);
|
||||
switch (eventType)
|
||||
{
|
||||
case NetworkListEvent<T>.EventType.Add:
|
||||
{
|
||||
m_List.Add((T)reader.ReadObjectPacked(typeof(T))); //BOX
|
||||
|
||||
if (OnListChanged != null)
|
||||
{
|
||||
OnListChanged(new NetworkListEvent<T>
|
||||
{
|
||||
Type = eventType,
|
||||
Index = m_List.Count - 1,
|
||||
Value = m_List[m_List.Count - 1]
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
||||
{
|
||||
Type = eventType,
|
||||
Index = m_List.Count - 1,
|
||||
Value = m_List[m_List.Count - 1]
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Insert:
|
||||
{
|
||||
int index = reader.ReadInt32Packed();
|
||||
m_List.Insert(index, (T)reader.ReadObjectPacked(typeof(T))); //BOX
|
||||
|
||||
if (OnListChanged != null)
|
||||
{
|
||||
OnListChanged(new NetworkListEvent<T>
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = m_List[index]
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = m_List[index]
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Remove:
|
||||
{
|
||||
T value = (T)reader.ReadObjectPacked(typeof(T)); //BOX
|
||||
int index = m_List.IndexOf(value);
|
||||
m_List.RemoveAt(index);
|
||||
|
||||
if (OnListChanged != null)
|
||||
{
|
||||
OnListChanged(new NetworkListEvent<T>
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.RemoveAt:
|
||||
{
|
||||
int index = reader.ReadInt32Packed();
|
||||
T value = m_List[index];
|
||||
m_List.RemoveAt(index);
|
||||
|
||||
if (OnListChanged != null)
|
||||
{
|
||||
OnListChanged(new NetworkListEvent<T>
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Value:
|
||||
{
|
||||
int index = reader.ReadInt32Packed();
|
||||
T value = (T)reader.ReadObjectPacked(typeof(T)); //BOX
|
||||
if (index < m_List.Count) m_List[index] = value;
|
||||
|
||||
if (OnListChanged != null)
|
||||
{
|
||||
OnListChanged(new NetworkListEvent<T>
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkListEvent<T>.EventType.Clear:
|
||||
{
|
||||
//Read nothing
|
||||
m_List.Clear();
|
||||
|
||||
if (OnListChanged != null)
|
||||
{
|
||||
OnListChanged(new NetworkListEvent<T>
|
||||
{
|
||||
Type = eventType,
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
||||
{
|
||||
Type = eventType
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetNetworkBehaviour(NetworkBehaviour behaviour)
|
||||
{
|
||||
m_NetworkBehaviour = behaviour;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return m_List.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)m_List).GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Add(T item)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_List.Add(item);
|
||||
|
||||
NetworkListEvent<T> listEvent = new NetworkListEvent<T>()
|
||||
{
|
||||
Type = NetworkListEvent<T>.EventType.Add,
|
||||
Value = item,
|
||||
Index = m_List.Count - 1
|
||||
};
|
||||
|
||||
HandleAddListEvent(listEvent);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_List.Clear();
|
||||
|
||||
NetworkListEvent<T> listEvent = new NetworkListEvent<T>()
|
||||
{
|
||||
Type = NetworkListEvent<T>.EventType.Clear
|
||||
};
|
||||
|
||||
HandleAddListEvent(listEvent);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return m_List.Contains(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
m_List.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(T item)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_List.Remove(item);
|
||||
|
||||
NetworkListEvent<T> listEvent = new NetworkListEvent<T>()
|
||||
{
|
||||
Type = NetworkListEvent<T>.EventType.Remove,
|
||||
Value = item
|
||||
};
|
||||
|
||||
HandleAddListEvent(listEvent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => m_List.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsReadOnly => m_List.IsReadOnly;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
return m_List.IndexOf(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_List.Insert(index, item);
|
||||
|
||||
NetworkListEvent<T> listEvent = new NetworkListEvent<T>()
|
||||
{
|
||||
Type = NetworkListEvent<T>.EventType.Insert,
|
||||
Index = index,
|
||||
Value = item
|
||||
};
|
||||
|
||||
HandleAddListEvent(listEvent);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_List.RemoveAt(index);
|
||||
|
||||
NetworkListEvent<T> listEvent = new NetworkListEvent<T>()
|
||||
{
|
||||
Type = NetworkListEvent<T>.EventType.RemoveAt,
|
||||
Index = index
|
||||
};
|
||||
|
||||
HandleAddListEvent(listEvent);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public T this[int index]
|
||||
{
|
||||
get => m_List[index];
|
||||
set
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer)
|
||||
m_List[index] = value;
|
||||
|
||||
NetworkListEvent<T> listEvent = new NetworkListEvent<T>()
|
||||
{
|
||||
Type = NetworkListEvent<T>.EventType.Value,
|
||||
Index = index,
|
||||
Value = value
|
||||
};
|
||||
|
||||
HandleAddListEvent(listEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleAddListEvent(NetworkListEvent<T> listEvent)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer)
|
||||
{
|
||||
if (NetworkManager.Singleton.ConnectedClients.Count > 0)
|
||||
{
|
||||
m_DirtyEvents.Add(listEvent);
|
||||
}
|
||||
|
||||
OnListChanged?.Invoke(listEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_DirtyEvents.Add(listEvent);
|
||||
}
|
||||
}
|
||||
|
||||
public ushort RemoteTick
|
||||
{
|
||||
get
|
||||
{
|
||||
// todo: implement proper network tick for NetworkList
|
||||
return NetworkTickSystem.NoTick;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Struct containing event information about changes to a NetworkList.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type for the list that the event is about</typeparam>
|
||||
public struct NetworkListEvent<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum representing the different operations available for triggering an event.
|
||||
/// </summary>
|
||||
public enum EventType
|
||||
{
|
||||
/// <summary>
|
||||
/// Add
|
||||
/// </summary>
|
||||
Add,
|
||||
|
||||
/// <summary>
|
||||
/// Insert
|
||||
/// </summary>
|
||||
Insert,
|
||||
|
||||
/// <summary>
|
||||
/// Remove
|
||||
/// </summary>
|
||||
Remove,
|
||||
|
||||
/// <summary>
|
||||
/// Remove at
|
||||
/// </summary>
|
||||
RemoveAt,
|
||||
|
||||
/// <summary>
|
||||
/// Value changed
|
||||
/// </summary>
|
||||
Value,
|
||||
|
||||
/// <summary>
|
||||
/// Clear
|
||||
/// </summary>
|
||||
Clear
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum representing the operation made to the list.
|
||||
/// </summary>
|
||||
public EventType Type;
|
||||
|
||||
/// <summary>
|
||||
/// The value changed, added or removed if available.
|
||||
/// </summary>
|
||||
public T Value;
|
||||
|
||||
/// <summary>
|
||||
/// the index changed, added or removed if available
|
||||
/// </summary>
|
||||
public int Index;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 42fb86f9389141347856337e3c62ad80
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,548 @@
|
|||
#if !NET35
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Transports;
|
||||
|
||||
namespace MLAPI.NetworkVariable.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Event based NetworkVariable container for syncing Sets
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type for the set</typeparam>
|
||||
public class NetworkSet<T> : ISet<T>, INetworkVariable
|
||||
{
|
||||
private readonly ISet<T> m_Set = new HashSet<T>();
|
||||
private readonly List<NetworkSetEvent<T>> m_DirtyEvents = new List<NetworkSetEvent<T>>();
|
||||
private NetworkBehaviour m_NetworkBehaviour;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the last time the variable was synced
|
||||
/// </summary>
|
||||
public float LastSyncedTime { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The settings for this container
|
||||
/// </summary>
|
||||
public readonly NetworkVariableSettings Settings = new NetworkVariableSettings();
|
||||
|
||||
/// <summary>
|
||||
/// Delegate type for set changed event
|
||||
/// </summary>
|
||||
/// <param name="changeEvent">Struct containing information about the change event</param>
|
||||
public delegate void OnSetChangedDelegate(NetworkSetEvent<T> changeEvent);
|
||||
|
||||
/// <summary>
|
||||
/// The callback to be invoked when the set gets changed
|
||||
/// </summary>
|
||||
public event OnSetChangedDelegate OnSetChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkSet with the default value and settings
|
||||
/// </summary>
|
||||
public NetworkSet() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkSet with the default value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings to use for the NetworkList</param>
|
||||
public NetworkSet(NetworkVariableSettings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkSet with a custom value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings to use for the NetworkSet</param>
|
||||
/// <param name="value">The initial value to use for the NetworkSet</param>
|
||||
public NetworkSet(NetworkVariableSettings settings, ISet<T> value)
|
||||
{
|
||||
Settings = settings;
|
||||
m_Set = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkSet with a custom value and the default settings
|
||||
/// </summary>
|
||||
/// <param name="value">The initial value to use for the NetworkList</param>
|
||||
public NetworkSet(ISet<T> value)
|
||||
{
|
||||
m_Set = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResetDirty()
|
||||
{
|
||||
m_DirtyEvents.Clear();
|
||||
LastSyncedTime = NetworkManager.Singleton.NetworkTime;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDirty()
|
||||
{
|
||||
if (m_DirtyEvents.Count == 0) return false;
|
||||
if (Settings.SendTickrate == 0) return true;
|
||||
if (Settings.SendTickrate < 0) return false;
|
||||
if (NetworkManager.Singleton.NetworkTime - LastSyncedTime >= (1f / Settings.SendTickrate)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkChannel GetChannel()
|
||||
{
|
||||
return Settings.SendNetworkChannel;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanClientWrite(ulong clientId)
|
||||
{
|
||||
switch (Settings.WritePermission)
|
||||
{
|
||||
case NetworkVariablePermission.Everyone:
|
||||
return true;
|
||||
case NetworkVariablePermission.ServerOnly:
|
||||
return false;
|
||||
case NetworkVariablePermission.OwnerOnly:
|
||||
return m_NetworkBehaviour.OwnerClientId == clientId;
|
||||
case NetworkVariablePermission.Custom:
|
||||
{
|
||||
if (Settings.WritePermissionCallback == null) return false;
|
||||
return Settings.WritePermissionCallback(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanClientRead(ulong clientId)
|
||||
{
|
||||
switch (Settings.ReadPermission)
|
||||
{
|
||||
case NetworkVariablePermission.Everyone:
|
||||
return true;
|
||||
case NetworkVariablePermission.ServerOnly:
|
||||
return false;
|
||||
case NetworkVariablePermission.OwnerOnly:
|
||||
return m_NetworkBehaviour.OwnerClientId == clientId;
|
||||
case NetworkVariablePermission.Custom:
|
||||
{
|
||||
if (Settings.ReadPermissionCallback == null) return false;
|
||||
return Settings.ReadPermissionCallback(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteDelta(Stream stream)
|
||||
{
|
||||
using (var writer = PooledNetworkWriter.Get(stream))
|
||||
{
|
||||
writer.WriteUInt16Packed((ushort)m_DirtyEvents.Count);
|
||||
for (int i = 0; i < m_DirtyEvents.Count; i++)
|
||||
{
|
||||
writer.WriteBits((byte)m_DirtyEvents[i].Type, 2);
|
||||
|
||||
switch (m_DirtyEvents[i].Type)
|
||||
{
|
||||
case NetworkSetEvent<T>.EventType.Add:
|
||||
{
|
||||
writer.WriteObjectPacked(m_DirtyEvents[i].Value); //BOX
|
||||
}
|
||||
break;
|
||||
case NetworkSetEvent<T>.EventType.Remove:
|
||||
{
|
||||
writer.WriteObjectPacked(m_DirtyEvents[i].Value); //BOX
|
||||
}
|
||||
break;
|
||||
case NetworkSetEvent<T>.EventType.Clear:
|
||||
{
|
||||
//Nothing has to be written
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteField(Stream stream)
|
||||
{
|
||||
using (var writer = PooledNetworkWriter.Get(stream))
|
||||
{
|
||||
writer.WriteUInt16Packed((ushort)m_Set.Count);
|
||||
|
||||
foreach (T value in m_Set)
|
||||
{
|
||||
writer.WriteObjectPacked(value); //BOX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReadField(Stream stream, ushort localTick, ushort remoteTick)
|
||||
{
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
m_Set.Clear();
|
||||
ushort count = reader.ReadUInt16Packed();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
m_Set.Add((T)reader.ReadObjectPacked(typeof(T))); //BOX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReadDelta(Stream stream, bool keepDirtyDelta, ushort localTick, ushort remoteTick)
|
||||
{
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
ushort deltaCount = reader.ReadUInt16Packed();
|
||||
for (int i = 0; i < deltaCount; i++)
|
||||
{
|
||||
NetworkSetEvent<T>.EventType eventType = (NetworkSetEvent<T>.EventType)reader.ReadBits(2);
|
||||
switch (eventType)
|
||||
{
|
||||
case NetworkSetEvent<T>.EventType.Add:
|
||||
{
|
||||
T value = (T)reader.ReadObjectPacked(typeof(T)); //BOX
|
||||
m_Set.Add(value);
|
||||
|
||||
if (OnSetChanged != null)
|
||||
{
|
||||
OnSetChanged(new NetworkSetEvent<T>
|
||||
{
|
||||
Type = eventType,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkSetEvent<T>()
|
||||
{
|
||||
Type = eventType,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkSetEvent<T>.EventType.Remove:
|
||||
{
|
||||
T value = (T)reader.ReadObjectPacked(typeof(T)); //BOX
|
||||
m_Set.Remove(value);
|
||||
|
||||
if (OnSetChanged != null)
|
||||
{
|
||||
OnSetChanged(new NetworkSetEvent<T>
|
||||
{
|
||||
Type = eventType,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkSetEvent<T>()
|
||||
{
|
||||
Type = eventType,
|
||||
Value = value
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NetworkSetEvent<T>.EventType.Clear:
|
||||
{
|
||||
//Read nothing
|
||||
m_Set.Clear();
|
||||
|
||||
if (OnSetChanged != null)
|
||||
{
|
||||
OnSetChanged(new NetworkSetEvent<T>
|
||||
{
|
||||
Type = eventType,
|
||||
});
|
||||
}
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
m_DirtyEvents.Add(new NetworkSetEvent<T>()
|
||||
{
|
||||
Type = eventType
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetNetworkBehaviour(NetworkBehaviour behaviour)
|
||||
{
|
||||
m_NetworkBehaviour = behaviour;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return m_Set.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return m_Set.GetEnumerator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void ICollection<T>.Add(T item)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_Set.Add(item);
|
||||
|
||||
NetworkSetEvent<T> setEvent = new NetworkSetEvent<T>()
|
||||
{
|
||||
Type = NetworkSetEvent<T>.EventType.Add,
|
||||
Value = item
|
||||
};
|
||||
m_DirtyEvents.Add(setEvent);
|
||||
|
||||
if (NetworkManager.Singleton.IsServer && OnSetChanged != null)
|
||||
OnSetChanged(setEvent);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ExceptWith(IEnumerable<T> other)
|
||||
{
|
||||
foreach (T value in other)
|
||||
{
|
||||
if (m_Set.Contains(value))
|
||||
{
|
||||
Remove(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void IntersectWith(IEnumerable<T> other)
|
||||
{
|
||||
HashSet<T> otherSet = new HashSet<T>(other);
|
||||
|
||||
foreach (T value in m_Set)
|
||||
{
|
||||
if (!otherSet.Contains(value))
|
||||
{
|
||||
Remove(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsProperSubsetOf(IEnumerable<T> other)
|
||||
{
|
||||
return m_Set.IsProperSubsetOf(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsProperSupersetOf(IEnumerable<T> other)
|
||||
{
|
||||
return m_Set.IsProperSupersetOf(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSubsetOf(IEnumerable<T> other)
|
||||
{
|
||||
return m_Set.IsSubsetOf(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsSupersetOf(IEnumerable<T> other)
|
||||
{
|
||||
return m_Set.IsSupersetOf(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Overlaps(IEnumerable<T> other)
|
||||
{
|
||||
return m_Set.Overlaps(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SetEquals(IEnumerable<T> other)
|
||||
{
|
||||
return m_Set.SetEquals(other);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SymmetricExceptWith(IEnumerable<T> other)
|
||||
{
|
||||
foreach (T value in other)
|
||||
{
|
||||
if (m_Set.Contains(value))
|
||||
{
|
||||
Remove(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_Set.Add(value);
|
||||
|
||||
NetworkSetEvent<T> setEvent = new NetworkSetEvent<T>()
|
||||
{
|
||||
Type = NetworkSetEvent<T>.EventType.Add,
|
||||
Value = value
|
||||
};
|
||||
m_DirtyEvents.Add(setEvent);
|
||||
|
||||
if (NetworkManager.Singleton.IsServer && OnSetChanged != null)
|
||||
OnSetChanged(setEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UnionWith(IEnumerable<T> other)
|
||||
{
|
||||
foreach (T value in other)
|
||||
{
|
||||
if (!m_Set.Contains(value))
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_Set.Add(value);
|
||||
|
||||
NetworkSetEvent<T> setEvent = new NetworkSetEvent<T>()
|
||||
{
|
||||
Type = NetworkSetEvent<T>.EventType.Add,
|
||||
Value = value
|
||||
};
|
||||
m_DirtyEvents.Add(setEvent);
|
||||
|
||||
if (NetworkManager.Singleton.IsServer && OnSetChanged != null)
|
||||
OnSetChanged(setEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
bool ISet<T>.Add(T item)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_Set.Add(item);
|
||||
|
||||
NetworkSetEvent<T> setEvent = new NetworkSetEvent<T>()
|
||||
{
|
||||
Type = NetworkSetEvent<T>.EventType.Add,
|
||||
Value = item
|
||||
};
|
||||
m_DirtyEvents.Add(setEvent);
|
||||
|
||||
if (NetworkManager.Singleton.IsServer && OnSetChanged != null)
|
||||
OnSetChanged(setEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Clear()
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_Set.Clear();
|
||||
|
||||
NetworkSetEvent<T> setEvent = new NetworkSetEvent<T>()
|
||||
{
|
||||
Type = NetworkSetEvent<T>.EventType.Clear
|
||||
};
|
||||
m_DirtyEvents.Add(setEvent);
|
||||
|
||||
if (NetworkManager.Singleton.IsServer && OnSetChanged != null)
|
||||
OnSetChanged(setEvent);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return m_Set.Contains(item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
m_Set.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Remove(T item)
|
||||
{
|
||||
if (NetworkManager.Singleton.IsServer) m_Set.Remove(item);
|
||||
|
||||
NetworkSetEvent<T> setEvent = new NetworkSetEvent<T>()
|
||||
{
|
||||
Type = NetworkSetEvent<T>.EventType.Remove,
|
||||
Value = item
|
||||
};
|
||||
m_DirtyEvents.Add(setEvent);
|
||||
|
||||
if (NetworkManager.Singleton.IsServer && OnSetChanged != null)
|
||||
OnSetChanged(setEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Count => m_Set.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsReadOnly => m_Set.IsReadOnly;
|
||||
|
||||
public ushort RemoteTick
|
||||
{
|
||||
get
|
||||
{
|
||||
// todo: implement proper network tick for NetworkSet
|
||||
return NetworkTickSystem.NoTick;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Struct containing event information about changes to a NetworkSet.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type for the set that the event is about</typeparam>
|
||||
public struct NetworkSetEvent<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Enum representing the different operations available for triggering an event.
|
||||
/// </summary>
|
||||
public enum EventType
|
||||
{
|
||||
/// <summary>
|
||||
/// Add
|
||||
/// </summary>
|
||||
Add,
|
||||
|
||||
/// <summary>
|
||||
/// Remove
|
||||
/// </summary>
|
||||
Remove,
|
||||
|
||||
/// <summary>
|
||||
/// Clear
|
||||
/// </summary>
|
||||
Clear
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum representing the operation made to the set.
|
||||
/// </summary>
|
||||
public EventType Type;
|
||||
|
||||
/// <summary>
|
||||
/// The value changed, added or removed if available.
|
||||
/// </summary>
|
||||
public T Value;
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 245b489a4e4f2174fbdeb27b9cdee07b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,82 @@
|
|||
using System.IO;
|
||||
using MLAPI.Transports;
|
||||
|
||||
namespace MLAPI.NetworkVariable
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for network value containers
|
||||
/// </summary>
|
||||
public interface INetworkVariable
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the name of the channel to be used for syncing
|
||||
/// </summary>
|
||||
/// <returns>The name of the channel to be used for syncing</returns>
|
||||
NetworkChannel GetChannel();
|
||||
|
||||
/// <summary>
|
||||
/// Resets the dirty state and marks the variable as synced / clean
|
||||
/// </summary>
|
||||
void ResetDirty();
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not the container is dirty
|
||||
/// </summary>
|
||||
/// <returns>Whether or not the container is dirty</returns>
|
||||
bool IsDirty();
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not a specific client can write to the varaible
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId of the remote client</param>
|
||||
/// <returns>Whether or not the client can write to the variable</returns>
|
||||
bool CanClientWrite(ulong clientId);
|
||||
|
||||
/// <summary>
|
||||
/// Gets Whether or not a specific client can read to the varaible
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId of the remote client</param>
|
||||
/// <returns>Whether or not the client can read to the variable</returns>
|
||||
bool CanClientRead(ulong clientId);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the dirty changes, that is, the changes since the variable was last dirty, to the writer
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to write the dirty changes to</param>
|
||||
void WriteDelta(Stream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the complete state of the variable to the writer
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to write the state to</param>
|
||||
void WriteField(Stream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Reads the complete state from the reader and applies it
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to read the state from</param>
|
||||
/// <param name="localTick">The local network tick at which this var was written, on the machine it was written </param>
|
||||
/// <param name="remoteTick">The remote network tick at which this var was sent by the host </param>
|
||||
void ReadField(Stream stream, ushort localTick, ushort remoteTick);
|
||||
|
||||
/// <summary>
|
||||
/// Reads delta from the reader and applies them to the internal value
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to read the delta from</param>
|
||||
/// <param name="keepDirtyDelta">Whether or not the delta should be kept as dirty or consumed</param>
|
||||
/// <param name="localTick">The local network tick at which this var was written, on the machine it was written </param>
|
||||
/// <param name="remoteTick">The remote network tick at which this var was sent by the host </param>
|
||||
void ReadDelta(Stream stream, bool keepDirtyDelta, ushort localTick, ushort remoteTick);
|
||||
|
||||
/// <summary>
|
||||
/// Sets NetworkBehaviour the container belongs to.
|
||||
/// </summary>
|
||||
/// <param name="behaviour">The behaviour the container behaves to</param>
|
||||
void SetNetworkBehaviour(NetworkBehaviour behaviour);
|
||||
|
||||
/// <summary>
|
||||
/// Accessor for the RemoteTick stored in the networkVariable, list, set or dictionary
|
||||
/// </summary>
|
||||
ushort RemoteTick { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 87f1fd4778c2dab4b8bc02c738cade25
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,585 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.IO;
|
||||
using System;
|
||||
using MLAPI.Serialization.Pooled;
|
||||
using MLAPI.Transports;
|
||||
|
||||
namespace MLAPI.NetworkVariable
|
||||
{
|
||||
/// <summary>
|
||||
/// A variable that can be synchronized over the network.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariable<T> : INetworkVariable
|
||||
{
|
||||
/// <summary>
|
||||
/// The settings for this var
|
||||
/// </summary>
|
||||
public readonly NetworkVariableSettings Settings = new NetworkVariableSettings();
|
||||
|
||||
/// <summary>
|
||||
/// The last time the variable was written to locally
|
||||
/// </summary>
|
||||
public ushort LocalTick { get; internal set; }
|
||||
/// <summary>
|
||||
/// The last time the variable was written to remotely. Uses the remote timescale
|
||||
/// </summary>
|
||||
public ushort RemoteTick { get; internal set; }
|
||||
/// <summary>
|
||||
/// Delegate type for value changed event
|
||||
/// </summary>
|
||||
/// <param name="previousValue">The value before the change</param>
|
||||
/// <param name="newValue">The new value</param>
|
||||
public delegate void OnValueChangedDelegate(T previousValue, T newValue);
|
||||
/// <summary>
|
||||
/// The callback to be invoked when the value gets changed
|
||||
/// </summary>
|
||||
public OnValueChangedDelegate OnValueChanged;
|
||||
|
||||
private NetworkBehaviour m_NetworkBehaviour;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with the default value and settings
|
||||
/// </summary>
|
||||
public NetworkVariable() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with the default value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings to use for the NetworkVariable</param>
|
||||
public NetworkVariable(NetworkVariableSettings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with a custom value and custom settings
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings to use for the NetworkVariable</param>
|
||||
/// <param name="value">The initial value to use for the NetworkVariable</param>
|
||||
public NetworkVariable(NetworkVariableSettings settings, T value)
|
||||
{
|
||||
Settings = settings;
|
||||
m_InternalValue = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a NetworkVariable with a custom value and the default settings
|
||||
/// </summary>
|
||||
/// <param name="value">The initial value to use for the NetworkVariable</param>
|
||||
public NetworkVariable(T value)
|
||||
{
|
||||
m_InternalValue = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private T m_InternalValue;
|
||||
|
||||
/// <summary>
|
||||
/// The value of the NetworkVariable container
|
||||
/// </summary>
|
||||
public T Value
|
||||
{
|
||||
get => m_InternalValue;
|
||||
set
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(m_InternalValue, value)) return;
|
||||
|
||||
// Setter is assumed to be called locally, by game code.
|
||||
// When used by the host, it is its responsibility to set the RemoteTick
|
||||
RemoteTick = NetworkTickSystem.NoTick;
|
||||
|
||||
m_IsDirty = true;
|
||||
T previousValue = m_InternalValue;
|
||||
m_InternalValue = value;
|
||||
OnValueChanged?.Invoke(previousValue, m_InternalValue);
|
||||
}
|
||||
}
|
||||
|
||||
private bool m_IsDirty = false;
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether or not the variable needs to be delta synced
|
||||
/// </summary>
|
||||
public void SetDirty(bool isDirty)
|
||||
{
|
||||
m_IsDirty = isDirty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsDirty()
|
||||
{
|
||||
return m_IsDirty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ResetDirty()
|
||||
{
|
||||
m_IsDirty = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanClientRead(ulong clientId)
|
||||
{
|
||||
switch (Settings.ReadPermission)
|
||||
{
|
||||
case NetworkVariablePermission.Everyone:
|
||||
return true;
|
||||
case NetworkVariablePermission.ServerOnly:
|
||||
return false;
|
||||
case NetworkVariablePermission.OwnerOnly:
|
||||
return m_NetworkBehaviour.OwnerClientId == clientId;
|
||||
case NetworkVariablePermission.Custom:
|
||||
{
|
||||
if (Settings.ReadPermissionCallback == null) return false;
|
||||
return Settings.ReadPermissionCallback(clientId);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the variable to the writer
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to write the value to</param>
|
||||
public void WriteDelta(Stream stream)
|
||||
{
|
||||
WriteField(stream);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanClientWrite(ulong clientId)
|
||||
{
|
||||
switch (Settings.WritePermission)
|
||||
{
|
||||
case NetworkVariablePermission.Everyone:
|
||||
return true;
|
||||
case NetworkVariablePermission.ServerOnly:
|
||||
return false;
|
||||
case NetworkVariablePermission.OwnerOnly:
|
||||
return m_NetworkBehaviour.OwnerClientId == clientId;
|
||||
case NetworkVariablePermission.Custom:
|
||||
{
|
||||
if (Settings.WritePermissionCallback == null) return false;
|
||||
return Settings.WritePermissionCallback(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads value from the reader and applies it
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to read the value from</param>
|
||||
/// <param name="keepDirtyDelta">Whether or not the container should keep the dirty delta, or mark the delta as consumed</param>
|
||||
public void ReadDelta(Stream stream, bool keepDirtyDelta, ushort localTick, ushort remoteTick)
|
||||
{
|
||||
// todo: This allows the host-returned value to be set back to an old value
|
||||
// this will need to be adjusted to check if we're have a most recent value
|
||||
LocalTick = localTick;
|
||||
RemoteTick = remoteTick;
|
||||
|
||||
using (var reader = PooledNetworkReader.Get(stream))
|
||||
{
|
||||
T previousValue = m_InternalValue;
|
||||
m_InternalValue = (T)reader.ReadObjectPacked(typeof(T));
|
||||
|
||||
if (keepDirtyDelta) m_IsDirty = true;
|
||||
|
||||
OnValueChanged?.Invoke(previousValue, m_InternalValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetNetworkBehaviour(NetworkBehaviour behaviour)
|
||||
{
|
||||
m_NetworkBehaviour = behaviour;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReadField(Stream stream, ushort localTick, ushort remoteTick)
|
||||
{
|
||||
ReadDelta(stream, false, localTick, remoteTick);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteField(Stream stream)
|
||||
{
|
||||
// Store the local tick at which this NetworkVariable was modified
|
||||
LocalTick = NetworkBehaviour.CurrentTick;
|
||||
using (var writer = PooledNetworkWriter.Get(stream))
|
||||
{
|
||||
writer.WriteObjectPacked(m_InternalValue); //BOX
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkChannel GetChannel()
|
||||
{
|
||||
return Settings.SendNetworkChannel;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds strings and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableString : NetworkVariable<string>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableString() : base(string.Empty) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableString(NetworkVariableSettings settings) : base(settings, string.Empty) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableString(string value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableString(NetworkVariableSettings settings, string value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds bools and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableBool : NetworkVariable<bool>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableBool() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableBool(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableBool(bool value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableBool(NetworkVariableSettings settings, bool value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds bytes and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableByte : NetworkVariable<byte>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableByte() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableByte(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableByte(byte value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableByte(NetworkVariableSettings settings, byte value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds sbytes and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableSByte : NetworkVariable<sbyte>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableSByte() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableSByte(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableSByte(sbyte value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableSByte(NetworkVariableSettings settings, sbyte value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds ushorts and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableUShort : NetworkVariable<ushort>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableUShort() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableUShort(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableUShort(ushort value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableUShort(NetworkVariableSettings settings, ushort value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds shorts and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableShort : NetworkVariable<short>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableShort() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableShort(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableShort(short value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableShort(NetworkVariableSettings settings, short value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds uints and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableUInt : NetworkVariable<uint>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableUInt() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableUInt(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableUInt(uint value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableUInt(NetworkVariableSettings settings, uint value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds ints and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableInt : NetworkVariable<int>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableInt() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableInt(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableInt(int value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableInt(NetworkVariableSettings settings, int value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds ulongs and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableULong : NetworkVariable<ulong>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableULong() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableULong(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableULong(ulong value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableULong(NetworkVariableSettings settings, ulong value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds longs and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableLong : NetworkVariable<long>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableLong() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableLong(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableLong(long value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableLong(NetworkVariableSettings settings, long value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds floats and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableFloat : NetworkVariable<float>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableFloat() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableFloat(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableFloat(float value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableFloat(NetworkVariableSettings settings, float value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds doubles and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableDouble : NetworkVariable<double>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableDouble() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableDouble(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableDouble(double value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableDouble(NetworkVariableSettings settings, double value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds vector2s and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableVector2 : NetworkVariable<Vector2>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableVector2() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableVector2(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableVector2(Vector2 value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableVector2(NetworkVariableSettings settings, Vector2 value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds vector3s and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableVector3 : NetworkVariable<Vector3>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableVector3() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableVector3(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableVector3(Vector3 value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableVector3(NetworkVariableSettings settings, Vector3 value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds vector4s and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableVector4 : NetworkVariable<Vector4>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableVector4() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableVector4(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableVector4(Vector4 value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableVector4(NetworkVariableSettings settings, Vector4 value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds colors and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableColor : NetworkVariable<Color>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableColor() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableColor(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableColor(Color value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableColor(NetworkVariableSettings settings, Color value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds color32s and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableColor32 : NetworkVariable<Color32>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableColor32() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableColor32(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableColor32(Color32 value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableColor32(NetworkVariableSettings settings, Color32 value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds rays and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableRay : NetworkVariable<Ray>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableRay() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableRay(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableRay(Ray value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableRay(NetworkVariableSettings settings, Ray value) : base(settings, value) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NetworkVariable that holds quaternions and support serialization
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class NetworkVariableQuaternion : NetworkVariable<Quaternion>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableQuaternion() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableQuaternion(NetworkVariableSettings settings) : base(settings) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableQuaternion(Quaternion value) : base(value) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetworkVariableQuaternion(NetworkVariableSettings settings, Quaternion value) : base(settings, value) { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0f6bd4b05b09e604986db16876d2cd4f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,28 @@
|
|||
namespace MLAPI.NetworkVariable
|
||||
{
|
||||
/// <summary>
|
||||
/// Permission type
|
||||
/// </summary>
|
||||
public enum NetworkVariablePermission
|
||||
{
|
||||
/// <summary>
|
||||
/// Everyone
|
||||
/// </summary>
|
||||
Everyone,
|
||||
|
||||
/// <summary>
|
||||
/// Server-only operation
|
||||
/// </summary>
|
||||
ServerOnly,
|
||||
|
||||
/// <summary>
|
||||
/// Owner-ownly
|
||||
/// </summary>
|
||||
OwnerOnly,
|
||||
|
||||
/// <summary>
|
||||
/// Custom delegate
|
||||
/// </summary>
|
||||
Custom
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bc753a77be3d7e846b7bb2612a67ac62
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,50 @@
|
|||
using MLAPI.Transports;
|
||||
|
||||
namespace MLAPI.NetworkVariable
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate type for permission checking
|
||||
/// </summary>
|
||||
/// <param name="clientId">The clientId whose permissions to check</param>
|
||||
public delegate bool NetworkVariablePermissionsDelegate(ulong clientId);
|
||||
|
||||
/// <summary>
|
||||
/// The settings class used by the build in NetworkVar implementations
|
||||
/// </summary>
|
||||
public class NetworkVariableSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the write permissions for this var
|
||||
/// </summary>
|
||||
public NetworkVariablePermission WritePermission = NetworkVariablePermission.ServerOnly;
|
||||
/// <summary>
|
||||
/// Defines the read permissions for this var
|
||||
/// </summary>
|
||||
public NetworkVariablePermission ReadPermission = NetworkVariablePermission.Everyone;
|
||||
/// <summary>
|
||||
/// The delegate used to evaluate write permission when the "Custom" mode is used
|
||||
/// </summary>
|
||||
public NetworkVariablePermissionsDelegate WritePermissionCallback = null;
|
||||
/// <summary>
|
||||
/// The delegate used to evaluate read permission when the "Custom" mode is used
|
||||
/// </summary>
|
||||
public NetworkVariablePermissionsDelegate ReadPermissionCallback = null;
|
||||
/// <summary>
|
||||
/// The maximum times per second this var will be synced.
|
||||
/// A value of 0 will cause the variable to sync as soon as possible after being changed.
|
||||
/// A value of less than 0 will cause the variable to sync only at once at spawn and not update again.
|
||||
/// </summary>
|
||||
public float SendTickrate = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the channel to use for this variable.
|
||||
/// Variables with different channels will be split into different packets
|
||||
/// </summary>
|
||||
public NetworkChannel SendNetworkChannel = NetworkChannel.DefaultMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new NetworkVariableSettings instance
|
||||
/// </summary>
|
||||
public NetworkVariableSettings() { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 06315503f26247d4aaa786326d12ecdc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0fb2af5a23f5eba4eba7151395239c81
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MLAPI.Profiling
|
||||
{
|
||||
public interface ITransportProfilerData
|
||||
{
|
||||
void BeginNewTick();
|
||||
IReadOnlyDictionary<string, int> GetTransportProfilerData();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0c016323c9d0c4e8990d946f8f3d6ce2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,142 @@
|
|||
using System.Collections.Generic;
|
||||
using MLAPI.Collections;
|
||||
using MLAPI.Configuration;
|
||||
using MLAPI.Transports;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MLAPI.Profiling
|
||||
{
|
||||
/// <summary>
|
||||
/// NetworkProfiler for profiling network traffic
|
||||
/// </summary>
|
||||
public static class NetworkProfiler
|
||||
{
|
||||
/// <summary>
|
||||
/// The ticks that has been recorded
|
||||
/// </summary>
|
||||
public static FixedQueue<ProfilerTick> Ticks { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the profiler is recording data
|
||||
/// </summary>
|
||||
public static bool IsRunning { get; private set; }
|
||||
|
||||
private static int s_TickHistory = 1024;
|
||||
private static int s_EventIdCounter = 0;
|
||||
private static ProfilerTick s_CurrentTick;
|
||||
|
||||
/// <summary>
|
||||
/// Starts recording data for the Profiler
|
||||
/// </summary>
|
||||
/// <param name="historyLength">The amount of ticks to keep in memory</param>
|
||||
public static void Start(int historyLength)
|
||||
{
|
||||
if (IsRunning) return;
|
||||
s_EventIdCounter = 0;
|
||||
Ticks = new FixedQueue<ProfilerTick>(historyLength);
|
||||
s_TickHistory = historyLength;
|
||||
s_CurrentTick = null;
|
||||
IsRunning = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops recording data
|
||||
/// </summary>
|
||||
public static void Stop()
|
||||
{
|
||||
Ticks = null; //leave to GC
|
||||
s_CurrentTick = null; //leave to GC
|
||||
IsRunning = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops recording data and fills the buffer with the recorded ticks and returns the length;
|
||||
/// </summary>
|
||||
/// <param name="tickBuffer">The buffer to fill with the ticks</param>
|
||||
/// <returns>The number of ticks recorded</returns>
|
||||
public static int Stop(ref ProfilerTick[] tickBuffer)
|
||||
{
|
||||
if (!IsRunning) return 0;
|
||||
int iteration = Ticks.Count > tickBuffer.Length ? tickBuffer.Length : Ticks.Count;
|
||||
for (int i = 0; i < iteration; i++) tickBuffer[i] = Ticks[i];
|
||||
|
||||
Ticks = null; //leave to GC
|
||||
s_CurrentTick = null; //leave to GC
|
||||
IsRunning = false;
|
||||
|
||||
return iteration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops recording data and fills the buffer with the recorded ticks and returns the length;
|
||||
/// </summary>
|
||||
/// <param name="tickBuffer">The buffer to fill with the ticks</param>
|
||||
/// <returns>The number of ticks recorded</returns>
|
||||
public static int Stop(ref List<ProfilerTick> tickBuffer)
|
||||
{
|
||||
if (!IsRunning) return 0;
|
||||
|
||||
int iteration = Ticks.Count > tickBuffer.Count ? tickBuffer.Count : Ticks.Count;
|
||||
for (int i = 0; i < iteration; i++) tickBuffer[i] = Ticks[i];
|
||||
|
||||
Ticks = null; //leave to GC
|
||||
s_CurrentTick = null; //leave to GC
|
||||
IsRunning = false;
|
||||
|
||||
return iteration;
|
||||
}
|
||||
|
||||
internal static void StartTick(TickType type)
|
||||
{
|
||||
if (!IsRunning) return;
|
||||
if (Ticks.Count == s_TickHistory)
|
||||
{
|
||||
Ticks.Dequeue();
|
||||
}
|
||||
|
||||
var tick = new ProfilerTick()
|
||||
{
|
||||
Type = type,
|
||||
Frame = Time.frameCount,
|
||||
EventId = s_EventIdCounter
|
||||
};
|
||||
s_EventIdCounter++;
|
||||
Ticks.Enqueue(tick);
|
||||
s_CurrentTick = tick;
|
||||
}
|
||||
|
||||
internal static void EndTick()
|
||||
{
|
||||
if (!IsRunning) return;
|
||||
if (s_CurrentTick == null) return;
|
||||
s_CurrentTick = null;
|
||||
}
|
||||
|
||||
internal static void StartEvent(TickType eventType, uint bytes, NetworkChannel networkChannel, byte messageType)
|
||||
{
|
||||
if (!IsRunning) return;
|
||||
if (s_CurrentTick == null) return;
|
||||
|
||||
string messageName = messageType < NetworkConstants.MESSAGE_NAMES.Length ? NetworkConstants.MESSAGE_NAMES[messageType] : "INVALID_MESSAGE_TYPE";
|
||||
|
||||
string channelName = networkChannel.ToString();
|
||||
s_CurrentTick.StartEvent(eventType, bytes, channelName, messageName);
|
||||
}
|
||||
|
||||
internal static void StartEvent(TickType eventType, uint bytes, NetworkChannel networkChannel, string messageName)
|
||||
{
|
||||
if (!IsRunning) return;
|
||||
if (s_CurrentTick == null) return;
|
||||
|
||||
string channelName = networkChannel.ToString();
|
||||
s_CurrentTick.StartEvent(eventType, bytes, channelName, messageName);
|
||||
}
|
||||
|
||||
internal static void EndEvent()
|
||||
{
|
||||
if (!IsRunning) return;
|
||||
if (s_CurrentTick == null) return;
|
||||
s_CurrentTick.EndEvent();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1009676d0b4d5ea4ba2a8532b573ad32
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MLAPI.Profiling
|
||||
{
|
||||
internal static class PerformanceDataManager
|
||||
{
|
||||
private static PerformanceTickData s_ProfilerData = new PerformanceTickData();
|
||||
private static int s_TickId;
|
||||
|
||||
internal static void BeginNewTick()
|
||||
{
|
||||
s_TickId = Math.Max(s_TickId, 0);
|
||||
s_ProfilerData.Reset();
|
||||
s_ProfilerData.TickId = s_TickId++;
|
||||
}
|
||||
|
||||
internal static void Increment(string fieldName, int count = 1)
|
||||
{
|
||||
s_ProfilerData.Increment(fieldName, count);
|
||||
}
|
||||
|
||||
internal static void AddTransportData(IReadOnlyDictionary<string, int> transportProfilerData)
|
||||
{
|
||||
s_ProfilerData.AddNonDuplicateData(transportProfilerData);
|
||||
}
|
||||
|
||||
internal static PerformanceTickData GetData()
|
||||
{
|
||||
return s_ProfilerData;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue