Skip to content

feat: Message Ordering #948

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0a4f1e6
Message ordering WIP
ShadauxCat Jun 18, 2021
6edda60
Converted all messages to the new flow, got all runtime tests passing…
ShadauxCat Jul 1, 2021
1905156
All tests fixed. Also fixed some other warnings that were being seen.
ShadauxCat Jul 1, 2021
0c34ce4
Merge branch 'develop' into feature/message_ordering. Still some fail…
ShadauxCat Jul 6, 2021
8af4b87
Fixed two major issues to correct all current tests:
ShadauxCat Jul 6, 2021
2f66fba
Fixed standards.py failures and added a warning when changing initial…
ShadauxCat Jul 7, 2021
c24834e
Address review feedback
ShadauxCat Jul 13, 2021
722b5ff
Back out accidental change to packages-lock.json
ShadauxCat Jul 13, 2021
956d996
Actually reverting packages-lock.json correctly
ShadauxCat Jul 13, 2021
d61a06e
Reverted change to .editorconfig, renamed a field in the rpc params
ShadauxCat Jul 14, 2021
c015bac
Fix naming violation
ShadauxCat Jul 15, 2021
6cc5f66
Realized MessageBatcher was bugged when sending messages on different…
ShadauxCat Jul 15, 2021
87fef25
Addressed some review feedback.
ShadauxCat Jul 21, 2021
9c279a0
Addressed some more review feedback.
ShadauxCat Jul 21, 2021
b26b8b5
Moved SpawnRpcDespawnInstanceHandler to its own file.
ShadauxCat Jul 21, 2021
f2fbd0a
Addressed further review feedback.
ShadauxCat Jul 22, 2021
6be7950
Fixed a missed rename reversion.
ShadauxCat Jul 22, 2021
c2eb4ef
Merge branch 'develop' into feature/message_ordering
ShadauxCat Jul 22, 2021
f62b3e0
Removed file that shouldn't have been committed.
ShadauxCat Jul 23, 2021
b71926b
- Removed a check for an error case that can't actually happen
ShadauxCat Jul 23, 2021
3c2176f
Fixed NetworkManagerMessageHandlerTests
ShadauxCat Jul 23, 2021
c362242
Fixed an issue created by my last change that would cause messages se…
ShadauxCat Jul 23, 2021
adb380f
Whoops, naming.
ShadauxCat Jul 23, 2021
1a58496
Increased timeout value in NetworkTransformTests
ShadauxCat Jul 23, 2021
a8e834b
Maybe set framerate back to the original value after the test, maybe …
ShadauxCat Jul 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions com.unity.multiplayer.mlapi/Editor/MLAPIProfilerModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ private class MLAPIModules
{
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcSent, m_Category = ProfilerCategory.Network.Name },
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcReceived, m_Category = ProfilerCategory.Network.Name },
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcBatchesSent, m_Category = ProfilerCategory.Network.Name },
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcBatchesReceived, m_Category = ProfilerCategory.Network.Name },
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcQueueProcessed, m_Category = ProfilerCategory.Network.Name },
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcInQueueSize, m_Category = ProfilerCategory.Network.Name },
new MLAPIProfilerCounter { m_Name = ProfilerConstants.RpcOutQueueSize, m_Category = ProfilerCategory.Network.Name },
};

private static List<MLAPIProfilerCounter> CreateOperationsCounters() => new List<MLAPIProfilerCounter>()
Expand Down
13 changes: 0 additions & 13 deletions com.unity.multiplayer.mlapi/Editor/NetworkManagerEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,6 @@ public class NetworkManagerEditor : UnityEditor.Editor
private SerializedProperty m_NetworkIdRecycleDelayProperty;
private SerializedProperty m_RpcHashSizeProperty;
private SerializedProperty m_LoadSceneTimeOutProperty;
private SerializedProperty m_EnableMessageBufferingProperty;
private SerializedProperty m_MessageBufferTimeoutProperty;

private ReorderableList m_NetworkPrefabsList;
private ReorderableList m_RegisteredScenesList;
Expand Down Expand Up @@ -109,8 +107,6 @@ private void Initialize()
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay");
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut");
m_EnableMessageBufferingProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableMessageBuffering");
m_MessageBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("MessageBufferTimeout");


ReloadTransports();
Expand Down Expand Up @@ -140,8 +136,6 @@ private void CheckNullProperties()
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay");
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut");
m_EnableMessageBufferingProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableMessageBuffering");
m_MessageBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("MessageBufferTimeout");
}

private void OnEnable()
Expand Down Expand Up @@ -318,13 +312,6 @@ public override void OnInspectorGUI()
EditorGUILayout.PropertyField(m_NetworkIdRecycleDelayProperty);
}

EditorGUILayout.PropertyField(m_EnableMessageBufferingProperty);

using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.EnableMessageBuffering))
{
EditorGUILayout.PropertyField(m_MessageBufferTimeoutProperty);
}

EditorGUILayout.LabelField("Bandwidth", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(m_RpcHashSizeProperty);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,6 @@ public class NetworkConfig
[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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,65 +5,6 @@ namespace MLAPI.Configuration
/// </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 ALL_CLIENTS_LOADED_SCENE = 14;
internal const byte PARENT_SYNC = 16;
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 SNAPSHOT_DATA = 25;
internal const byte SNAPSHOT_ACK = 26;
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",
"",
"ALL_CLIENTS_SWITCH_SCENE_COMPLETED",
"",
"PARENT_SYNC", // 16
"",
"",
"",
"UNNAMED_MESSAGE",
"DESTROY_OBJECTS",
"NAMED_MESSAGE",
"SERVER_LOG",
"",
"SNAPSHOT_DATA",
"SNAPSHOT_ACK",
"",
"",
"",
"SERVER_RPC",
"CLIENT_RPC",
"INVALID" // 32
};
internal const string PROTOCOL_VERSION = "14.0.0";
}
}
130 changes: 78 additions & 52 deletions com.unity.multiplayer.mlapi/Runtime/Core/NetworkBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ internal enum __RpcExecStage
Client = 2
}

private static void SetUpdateStage<T>(ref T param) where T: IHasUpdateStage
{
if (param.UpdateStage == NetworkUpdateStage.Unset)
{
param.UpdateStage = NetworkUpdateLoop.UpdateStage;

if (param.UpdateStage == NetworkUpdateStage.Initialization)
{
param.UpdateStage = NetworkUpdateStage.EarlyUpdate;
}
}
}

#pragma warning disable 414 // disable assigned but its value is never used
#pragma warning disable IDE1006 // disable naming rule violation check
[NonSerialized]
Expand All @@ -47,34 +60,35 @@ internal NetworkSerializer __beginSendServerRpc(uint rpcMethodId, ServerRpcParam
{
PooledNetworkWriter writer;

var rpcQueueContainer = NetworkManager.RpcQueueContainer;
var isUsingBatching = rpcQueueContainer.IsUsingBatching();
SetUpdateStage(ref serverRpcParams.Send);

if (serverRpcParams.Send.UpdateStage == NetworkUpdateStage.Initialization)
{
throw new NotSupportedException(
$"{nameof(NetworkUpdateStage.Initialization)} cannot be used as a target for processing RPCs.");
}

var messageQueueContainer = NetworkManager.MessageQueueContainer;
var transportChannel = rpcDelivery == RpcDelivery.Reliable ? NetworkChannel.ReliableRpc : NetworkChannel.UnreliableRpc;

if (IsHost)
{
writer = rpcQueueContainer.BeginAddQueueItemToFrame(RpcQueueContainer.QueueItemType.ServerRpc, Time.realtimeSinceStartup, transportChannel,
NetworkManager.ServerClientId, null, RpcQueueHistoryFrame.QueueFrameType.Inbound, serverRpcParams.Send.UpdateStage);

if (!isUsingBatching)
{
writer.WriteByte(NetworkConstants.SERVER_RPC); // MessageType
}
writer = messageQueueContainer.BeginAddQueueItemToFrame(MessageQueueContainer.MessageType.ServerRpc, Time.realtimeSinceStartup, transportChannel,
NetworkManager.ServerClientId, null, MessageQueueHistoryFrame.QueueFrameType.Inbound, serverRpcParams.Send.UpdateStage);
}
else
{
writer = rpcQueueContainer.BeginAddQueueItemToFrame(RpcQueueContainer.QueueItemType.ServerRpc, Time.realtimeSinceStartup, transportChannel,
NetworkManager.ServerClientId, null, RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
if (!isUsingBatching)
{
writer.WriteByte(NetworkConstants.SERVER_RPC); // MessageType
}
writer = messageQueueContainer.BeginAddQueueItemToFrame(MessageQueueContainer.MessageType.ServerRpc, Time.realtimeSinceStartup, transportChannel,
NetworkManager.ServerClientId, null, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);

writer.WriteByte((byte)MessageQueueContainer.MessageType.ServerRpc);
writer.WriteByte((byte)serverRpcParams.Send.UpdateStage); // NetworkUpdateStage
}

writer.WriteUInt64Packed(NetworkObjectId); // NetworkObjectId
writer.WriteUInt16Packed(NetworkBehaviourId); // NetworkBehaviourId
writer.WriteUInt32Packed(rpcMethodId); // NetworkRpcMethodId
writer.WriteByte((byte)serverRpcParams.Send.UpdateStage); // NetworkUpdateStage

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so in the old code we wrote out the UpdateStage regardless, but now we only do so if we're not a host. Is this right?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that is the "host loopback" and so it doesn't need to write it out because it is being directly inserted into the next frame's inbound local MessageQueue.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UpdateStage is part of the unified header now. It's no longer part of the RPC message. The loopback code doesn't read the header for some reason... the fact that the behavior when pushing to the inbound queue is different than when pushing to the outbound is one of the things I'm addressing in my serialization RFC.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To elaborate a bit further...

The message header (including UpdateStage) is only used on the receiving end during HandleIncomingData() in order to properly add the item to the correct queue. When sending to loopback, the header isn't written because HandleIncomingData() is bypassed, so we skip it when the only destination is the inbound queue because skipping it here emulates the behavior of HandleIncomingData in reading it (and thus pushing the read head past it).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, brilliant @ShadauxCat , can we hoist this last comment into a comment in the code? This is something subtle and worth placing here for future devs / users.


return writer.Serializer;
}
Expand All @@ -89,14 +103,16 @@ internal void __endSendServerRpc(NetworkSerializer serializer, uint rpcMethodId,
return;
}

var rpcQueueContainer = NetworkManager.RpcQueueContainer;
SetUpdateStage(ref serverRpcParams.Send);

var messageQueueContainer = NetworkManager.MessageQueueContainer;
if (IsHost)
{
rpcQueueContainer.EndAddQueueItemToFrame(serializer.Writer, RpcQueueHistoryFrame.QueueFrameType.Inbound, serverRpcParams.Send.UpdateStage);
messageQueueContainer.EndAddQueueItemToFrame(serializer.Writer, MessageQueueHistoryFrame.QueueFrameType.Inbound, serverRpcParams.Send.UpdateStage);
}
else
{
rpcQueueContainer.EndAddQueueItemToFrame(serializer.Writer, RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
messageQueueContainer.EndAddQueueItemToFrame(serializer.Writer, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
}
}

Expand All @@ -107,66 +123,66 @@ internal NetworkSerializer __beginSendClientRpc(uint rpcMethodId, ClientRpcParam
{
PooledNetworkWriter writer;

SetUpdateStage(ref clientRpcParams.Send);

if (clientRpcParams.Send.UpdateStage == NetworkUpdateStage.Initialization)
{
throw new NotSupportedException(
$"{nameof(NetworkUpdateStage.Initialization)} cannot be used as a target for processing RPCs.");
}

// This will start a new queue item entry and will then return the writer to the current frame's stream
var rpcQueueContainer = NetworkManager.RpcQueueContainer;
var isUsingBatching = rpcQueueContainer.IsUsingBatching();
var transportChannel = rpcDelivery == RpcDelivery.Reliable ? NetworkChannel.ReliableRpc : NetworkChannel.UnreliableRpc;

ulong[] clientIds = clientRpcParams.Send.TargetClientIds ?? NetworkManager.ConnectedClientsList.Select(c => c.ClientId).ToArray();
ulong[] clientIds = clientRpcParams.Send.TargetClientIds ?? NetworkManager.ConnectedClientsIds;
if (clientRpcParams.Send.TargetClientIds != null && clientRpcParams.Send.TargetClientIds.Length == 0)
{
clientIds = NetworkManager.ConnectedClientsList.Select(c => c.ClientId).ToArray();
clientIds = NetworkManager.ConnectedClientsIds;
}

//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.ServerClientId);
bool addHeader = true;
var messageQueueContainer = NetworkManager.MessageQueueContainer;
if (IsHost && containsServerClientId)
{
//Always write to the next frame's inbound queue
writer = rpcQueueContainer.BeginAddQueueItemToFrame(RpcQueueContainer.QueueItemType.ClientRpc, Time.realtimeSinceStartup, transportChannel,
NetworkManager.ServerClientId, null, RpcQueueHistoryFrame.QueueFrameType.Inbound, clientRpcParams.Send.UpdateStage);
writer = messageQueueContainer.BeginAddQueueItemToFrame(MessageQueueContainer.MessageType.ClientRpc, Time.realtimeSinceStartup, transportChannel,
NetworkManager.ServerClientId, null, MessageQueueHistoryFrame.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);
messageQueueContainer.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
}
writer = messageQueueContainer.BeginAddQueueItemToFrame(MessageQueueContainer.MessageType.ClientRpc, Time.realtimeSinceStartup, transportChannel, NetworkObjectId,
clientIds, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
}
else
{
if (!isUsingBatching)
{
writer.WriteByte(NetworkConstants.CLIENT_RPC); // MessageType
}
addHeader = false;
}
}
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 = messageQueueContainer.BeginAddQueueItemToFrame(MessageQueueContainer.MessageType.ClientRpc, Time.realtimeSinceStartup, transportChannel, NetworkObjectId,
clientIds, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
}

if (addHeader)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultra nit, I would probably rename this / invert to 'skipHeader' and (even though it's pretty obvious when you look at the code) have a comment: "don't send a header for 1 client"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I generally dislike using negative terms for variables and then inverting them. I find "if(do thing)" to be more intuitive and less prone to being misread than "if(not don't do thing)". But I won't die on that hill if you disagree.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I can see that...but let's add the don't send a header for 1 client comment somewhere around addHeader

{
writer.WriteByte((byte)MessageQueueContainer.MessageType.ClientRpc);
writer.WriteByte((byte)clientRpcParams.Send.UpdateStage); // NetworkUpdateStage
}
writer.WriteUInt64Packed(NetworkObjectId); // NetworkObjectId
writer.WriteUInt16Packed(NetworkBehaviourId); // NetworkBehaviourId
writer.WriteUInt32Packed(rpcMethodId); // NetworkRpcMethodId
writer.WriteByte((byte)clientRpcParams.Send.UpdateStage); // NetworkUpdateStage


return writer.Serializer;
}
Expand All @@ -181,25 +197,27 @@ internal void __endSendClientRpc(NetworkSerializer serializer, uint rpcMethodId,
return;
}

var rpcQueueContainer = NetworkManager.RpcQueueContainer;
SetUpdateStage(ref clientRpcParams.Send);

var messageQueueContainer = NetworkManager.MessageQueueContainer;

if (IsHost)
{
ulong[] clientIds = clientRpcParams.Send.TargetClientIds ?? NetworkManager.ConnectedClientsList.Select(c => c.ClientId).ToArray();
ulong[] clientIds = clientRpcParams.Send.TargetClientIds ?? NetworkManager.ConnectedClientsIds;
if (clientRpcParams.Send.TargetClientIds != null && clientRpcParams.Send.TargetClientIds.Length == 0)
{
clientIds = NetworkManager.ConnectedClientsList.Select(c => c.ClientId).ToArray();
clientIds = NetworkManager.ConnectedClientsIds;
}

var containsServerClientId = clientIds.Contains(NetworkManager.ServerClientId);
if (containsServerClientId && clientIds.Length == 1)
{
rpcQueueContainer.EndAddQueueItemToFrame(serializer.Writer, RpcQueueHistoryFrame.QueueFrameType.Inbound, clientRpcParams.Send.UpdateStage);
messageQueueContainer.EndAddQueueItemToFrame(serializer.Writer, MessageQueueHistoryFrame.QueueFrameType.Inbound, clientRpcParams.Send.UpdateStage);
return;
}
}

rpcQueueContainer.EndAddQueueItemToFrame(serializer.Writer, RpcQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
messageQueueContainer.EndAddQueueItemToFrame(serializer.Writer, MessageQueueHistoryFrame.QueueFrameType.Outbound, NetworkUpdateStage.PostLateUpdate);
}

/// <summary>
Expand Down Expand Up @@ -564,8 +582,16 @@ private void NetworkVariableUpdate(ulong clientId, int behaviourIndex)

if (writtenAny)
{
NetworkManager.MessageSender.Send(clientId, NetworkConstants.NETWORK_VARIABLE_DELTA,
m_ChannelsForNetworkVariableGroups[j], buffer);
var context = NetworkManager.MessageQueueContainer.EnterInternalCommandContext(
MessageQueueContainer.MessageType.NetworkVariableDelta, m_ChannelsForNetworkVariableGroups[j],
new[] {clientId}, NetworkUpdateLoop.UpdateStage);
if (context != null)
{
using (var nonNullContext = (InternalCommandContext)context)
{
nonNullContext.NetworkWriter.WriteBytes(buffer.GetBuffer(), buffer.Length);
}
}
}
}
}
Expand Down
Loading