Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
15 changes: 15 additions & 0 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2065,6 +2065,21 @@ internal void HandleConnectionApproval(ulong ownerClientId, ConnectionApprovalRe

SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);

for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
Comment thread
jeffreyrainy marked this conversation as resolved.
{
if (MessagingSystem.ReverseTypeMap[index] != null)
{
var orderingMessage = new OrderingMessage
{
Order = index,
Hash = MessagingSystem.ReverseTypeMap[index].FullName.GetHashCode()
Comment thread
jeffreyrainy marked this conversation as resolved.
Outdated
};

SendMessage(ref orderingMessage, NetworkDelivery.ReliableFragmentedSequenced,
ownerClientId);
Comment thread
jeffreyrainy marked this conversation as resolved.
Outdated
}
}

// If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization
if (!NetworkConfig.EnableSceneManagement)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;

namespace Unity.Netcode
{
/// <summary>
/// This particular struct is a little weird because it doesn't actually contain the data
/// it's serializing. Instead, it contains references to the data it needs to do the
/// serialization. This is due to the generally amorphous nature of network variable
/// deltas, since they're all driven by custom virtual method overloads.
/// </summary>
internal struct OrderingMessage : INetworkMessage
Comment thread
jeffreyrainy marked this conversation as resolved.
{
public int Order;
public int Hash;

public void Serialize(FastBufferWriter writer)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
{
throw new OverflowException($"Not enough space in the buffer to write {nameof(OrderingMessage)}");
}

writer.WriteValue(Order);
writer.WriteValue(Hash);
}

public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
{
throw new OverflowException($"Not enough data in the buffer to read {nameof(OrderingMessage)}");
}

reader.ReadValue(out Order);
reader.ReadValue(out Hash);

return true;
}

public void Handle(ref NetworkContext context)
{
((NetworkManager)context.SystemOwner).MessagingSystem.ReorderMessage(Order, Hash);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
Expand Down Expand Up @@ -59,6 +60,8 @@ public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerA

internal Type[] MessageTypes => m_ReverseTypeMap;
internal MessageHandler[] MessageHandlers => m_MessageHandlers;
Comment thread
JesseOlmer marked this conversation as resolved.
internal Type[] ReverseTypeMap => m_ReverseTypeMap;
Comment thread
jeffreyrainy marked this conversation as resolved.
Outdated

internal uint MessageHandlerCount => m_HighMessageType;

internal uint GetMessageType(Type t)
Expand All @@ -75,6 +78,35 @@ internal struct MessageWithHandler
public MessageHandler Handler;
}

private List<MessageWithHandler> PrioritizeMessageOrder(List<MessageWithHandler> allowedTypes)
{
var prioritizedTypes = new List<MessageWithHandler>();

// first pass puts the priority message in the first indices
// Those are the messages that must be delivered in order to allow re-ordering the others later
foreach (var t in allowedTypes)
{
if (t.MessageType.FullName == "Unity.Netcode.ConnectionRequestMessage" ||
t.MessageType.FullName == "Unity.Netcode.ConnectionApprovedMessage" ||
t.MessageType.FullName == "Unity.Netcode.OrderingMessage")
{
prioritizedTypes.Add(t);
}
}

foreach (var t in allowedTypes)
{
if (t.MessageType.FullName != "Unity.Netcode.ConnectionRequestMessage" &&
t.MessageType.FullName != "Unity.Netcode.ConnectionApprovedMessage" &&
t.MessageType.FullName != "Unity.Netcode.OrderingMessage")
{
prioritizedTypes.Add(t);
}
}

return prioritizedTypes;
}

public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvider provider = null)
{
try
Expand All @@ -89,6 +121,7 @@ public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvi
var allowedTypes = provider.GetMessages();

allowedTypes.Sort((a, b) => string.CompareOrdinal(a.MessageType.FullName, b.MessageType.FullName));
allowedTypes = PrioritizeMessageOrder(allowedTypes);
foreach (var type in allowedTypes)
{
RegisterMessageType(type);
Expand Down Expand Up @@ -226,6 +259,66 @@ private bool CanReceive(ulong clientId, Type messageType, FastBufferReader messa
return true;
}

internal void ReorderMessage(int desiredOrder, int targetHash)
{
if (desiredOrder < 0)
{
// invalid message re-ordering request. Ignore.
return;
Comment thread
jeffreyrainy marked this conversation as resolved.
Outdated
}

if (desiredOrder < m_ReverseTypeMap.Length &&
m_ReverseTypeMap[desiredOrder].FullName.GetHashCode() == targetHash)
Comment thread
jeffreyrainy marked this conversation as resolved.
Outdated
{
// matching positions and hashes. All good.
return;
}

// Since the message at `desiredOrder` is not the expected one,
// insert an empty placeholder and move the messages down
var typesAsList = m_ReverseTypeMap.ToList();
typesAsList.Insert(desiredOrder, null);
m_ReverseTypeMap = typesAsList.ToArray();
var handlersAsList = m_MessageHandlers.ToList();
handlersAsList.Insert(desiredOrder, null);
m_MessageHandlers = handlersAsList.ToArray();
Comment thread
jeffreyrainy marked this conversation as resolved.
Outdated

// we added a dummy message, bump the end up
m_HighMessageType++;

int position = desiredOrder;
Comment thread
jeffreyrainy marked this conversation as resolved.
bool found = false;
while (position < m_ReverseTypeMap.Length)
{
if (m_ReverseTypeMap[position] != null &&
m_ReverseTypeMap[position].FullName.GetHashCode() == targetHash)
{
found = true;
break;
}

position++;
}

if (found)
Comment thread
jeffreyrainy marked this conversation as resolved.
{
// Copy the handler and type to the right index
m_ReverseTypeMap[desiredOrder] = m_ReverseTypeMap[position];
m_MessageHandlers[desiredOrder] = m_MessageHandlers[position];

// Shift back the remaining messages
typesAsList = m_ReverseTypeMap.ToList();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why are you doing array->list->array conversions again? Shouldn't you either always have them be lists as in a comment I made above, or else convert array->list, do ALL operations in this method on the lists, then just do 1 conversion back to array?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well, it was either:

  1. rework the whole code. I dislike doing that on point releases. Especially when the original code author is away for a while
  2. write hard-to-read iterative in-array shifting of the values. Would be faster at runtime, though.
  3. write pretty direct, clear semantic, list insert and remove. But yeah, on the performance front, it's lacking. But this happens once at connect time. Even with many messages, it will finish in less than a milli.

I picked 3, mainly so intent was clear. If you insist, I can do 1. or 2. But I really feel, although the code looks to do repeating operations, it will be easier to maintain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You can do #3 with 2 fewer Array allocations and 2 fewer List allocations per message. I'll add some suggested changes.

Copy link
Copy Markdown
Contributor

@JesseOlmer JesseOlmer Aug 5, 2022

Choose a reason for hiding this comment

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

Check out
onelist.txt
... I THINK it should work - it passes your tests, but it also passed your tests when it initially has a bug in it so 🤷

Bonus is it gets rid of Linq usages (which you can do even if you don't take the rest of the change)

typesAsList.RemoveAt(position);
m_ReverseTypeMap = typesAsList.ToArray();
handlersAsList = m_MessageHandlers.ToList();
handlersAsList.RemoveAt(position);
m_MessageHandlers = handlersAsList.ToArray();

// we removed a copy after moving a message, reduce the high message index
m_HighMessageType--;
}
}

public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
{
if (header.MessageType >= m_HighMessageType)
Expand Down