88
99namespace Unity . Netcode
1010{
11+ internal class HandlerNotRegisteredException : SystemException
12+ {
13+ public HandlerNotRegisteredException ( ) { }
14+ public HandlerNotRegisteredException ( string issue ) : base ( issue ) { }
15+ }
1116
1217 internal class InvalidMessageStructureException : SystemException
1318 {
@@ -44,8 +49,9 @@ public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerA
4449
4550 private NativeList < ReceiveQueueItem > m_IncomingMessageQueue = new NativeList < ReceiveQueueItem > ( 16 , Allocator . Persistent ) ;
4651
47- private MessageHandler [ ] m_MessageHandlers = new MessageHandler [ 255 ] ;
48- private Type [ ] m_ReverseTypeMap = new Type [ 255 ] ;
52+ // These array will grow as we need more message handlers. 4 is just a starting size.
53+ private MessageHandler [ ] m_MessageHandlers = new MessageHandler [ 4 ] ;
54+ private Type [ ] m_ReverseTypeMap = new Type [ 4 ] ;
4955
5056 private Dictionary < Type , uint > m_MessageTypes = new Dictionary < Type , uint > ( ) ;
5157 private Dictionary < ulong , NativeList < SendQueueItem > > m_SendQueues = new Dictionary < ulong , NativeList < SendQueueItem > > ( ) ;
@@ -59,6 +65,7 @@ public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerA
5965
6066 internal Type [ ] MessageTypes => m_ReverseTypeMap ;
6167 internal MessageHandler [ ] MessageHandlers => m_MessageHandlers ;
68+
6269 internal uint MessageHandlerCount => m_HighMessageType ;
6370
6471 internal uint GetMessageType ( Type t )
@@ -75,6 +82,35 @@ internal struct MessageWithHandler
7582 public MessageHandler Handler ;
7683 }
7784
85+ internal List < MessageWithHandler > PrioritizeMessageOrder ( List < MessageWithHandler > allowedTypes )
86+ {
87+ var prioritizedTypes = new List < MessageWithHandler > ( ) ;
88+
89+ // first pass puts the priority message in the first indices
90+ // Those are the messages that must be delivered in order to allow re-ordering the others later
91+ foreach ( var t in allowedTypes )
92+ {
93+ if ( t . MessageType . FullName == "Unity.Netcode.ConnectionRequestMessage" ||
94+ t . MessageType . FullName == "Unity.Netcode.ConnectionApprovedMessage" ||
95+ t . MessageType . FullName == "Unity.Netcode.OrderingMessage" )
96+ {
97+ prioritizedTypes . Add ( t ) ;
98+ }
99+ }
100+
101+ foreach ( var t in allowedTypes )
102+ {
103+ if ( t . MessageType . FullName != "Unity.Netcode.ConnectionRequestMessage" &&
104+ t . MessageType . FullName != "Unity.Netcode.ConnectionApprovedMessage" &&
105+ t . MessageType . FullName != "Unity.Netcode.OrderingMessage" )
106+ {
107+ prioritizedTypes . Add ( t ) ;
108+ }
109+ }
110+
111+ return prioritizedTypes ;
112+ }
113+
78114 public MessagingSystem ( IMessageSender messageSender , object owner , IMessageProvider provider = null )
79115 {
80116 try
@@ -89,6 +125,7 @@ public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvi
89125 var allowedTypes = provider . GetMessages ( ) ;
90126
91127 allowedTypes . Sort ( ( a , b ) => string . CompareOrdinal ( a . MessageType . FullName , b . MessageType . FullName ) ) ;
128+ allowedTypes = PrioritizeMessageOrder ( allowedTypes ) ;
92129 foreach ( var type in allowedTypes )
93130 {
94131 RegisterMessageType ( type ) ;
@@ -143,6 +180,13 @@ public void Unhook(INetworkHooks hooks)
143180
144181 private void RegisterMessageType ( MessageWithHandler messageWithHandler )
145182 {
183+ // if we are out of space, perform amortized linear growth
184+ if ( m_HighMessageType == m_MessageHandlers . Length )
185+ {
186+ Array . Resize ( ref m_MessageHandlers , 2 * m_MessageHandlers . Length ) ;
187+ Array . Resize ( ref m_ReverseTypeMap , 2 * m_ReverseTypeMap . Length ) ;
188+ }
189+
146190 m_MessageHandlers [ m_HighMessageType ] = messageWithHandler . Handler ;
147191 m_ReverseTypeMap [ m_HighMessageType ] = messageWithHandler . MessageType ;
148192 m_MessageTypes [ messageWithHandler . MessageType ] = m_HighMessageType ++ ;
@@ -226,6 +270,70 @@ private bool CanReceive(ulong clientId, Type messageType, FastBufferReader messa
226270 return true ;
227271 }
228272
273+ // Moves the handler for the type having hash `targetHash` to the `desiredOrder` position, in the handler list
274+ // This allows the server to tell the client which id it is using for which message and make sure the right
275+ // message is used when deserializing.
276+ internal void ReorderMessage ( int desiredOrder , uint targetHash )
277+ {
278+ if ( desiredOrder < 0 )
279+ {
280+ throw new ArgumentException ( "ReorderMessage desiredOrder must be positive" ) ;
281+ }
282+
283+ if ( desiredOrder < m_ReverseTypeMap . Length &&
284+ XXHash . Hash32 ( m_ReverseTypeMap [ desiredOrder ] . FullName ) == targetHash )
285+ {
286+ // matching positions and hashes. All good.
287+ return ;
288+ }
289+
290+ Debug . Log ( $ "Unexpected hash for { desiredOrder } ") ;
291+
292+ // Since the message at `desiredOrder` is not the expected one,
293+ // insert an empty placeholder and move the messages down
294+ var typesAsList = new List < Type > ( m_ReverseTypeMap ) ;
295+
296+ typesAsList . Insert ( desiredOrder , null ) ;
297+ var handlersAsList = new List < MessageHandler > ( m_MessageHandlers ) ;
298+ handlersAsList . Insert ( desiredOrder , null ) ;
299+
300+ // we added a dummy message, bump the end up
301+ m_HighMessageType ++ ;
302+
303+ // Here, we rely on the server telling us about all messages, in order.
304+ // So, we know the handlers before desiredOrder are correct.
305+ // We start at desiredOrder to not shift them when we insert.
306+ int position = desiredOrder ;
307+ bool found = false ;
308+ while ( position < typesAsList . Count )
309+ {
310+ if ( typesAsList [ position ] != null &&
311+ XXHash . Hash32 ( typesAsList [ position ] . FullName ) == targetHash )
312+ {
313+ found = true ;
314+ break ;
315+ }
316+
317+ position ++ ;
318+ }
319+
320+ if ( found )
321+ {
322+ // Copy the handler and type to the right index
323+
324+ typesAsList [ desiredOrder ] = typesAsList [ position ] ;
325+ handlersAsList [ desiredOrder ] = handlersAsList [ position ] ;
326+ typesAsList . RemoveAt ( position ) ;
327+ handlersAsList . RemoveAt ( position ) ;
328+
329+ // we removed a copy after moving a message, reduce the high message index
330+ m_HighMessageType -- ;
331+ }
332+
333+ m_ReverseTypeMap = typesAsList . ToArray ( ) ;
334+ m_MessageHandlers = handlersAsList . ToArray ( ) ;
335+ }
336+
229337 public void HandleMessage ( in MessageHeader header , FastBufferReader reader , ulong senderId , float timestamp , int serializedHeaderSize )
230338 {
231339 if ( header . MessageType >= m_HighMessageType )
@@ -259,18 +367,29 @@ public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulon
259367 var handler = m_MessageHandlers [ header . MessageType ] ;
260368 using ( reader )
261369 {
262- // No user-land message handler exceptions should escape the receive loop.
263- // If an exception is throw, the message is ignored.
264- // Example use case: A bad message is received that can't be deserialized and throws
265- // an OverflowException because it specifies a length greater than the number of bytes in it
266- // for some dynamic-length value.
267- try
370+ // This will also log an exception is if the server knows about a message type the client doesn't know
371+ // about. In this case the handler will be null. It is still an issue the user must deal with: If the
372+ // two connecting builds know about different messages, the server should not send a message to a client
373+ // that doesn't know about it
374+ if ( handler == null )
268375 {
269- handler . Invoke ( reader , ref context , this ) ;
376+ Debug . LogException ( new HandlerNotRegisteredException ( header . MessageType . ToString ( ) ) ) ;
270377 }
271- catch ( Exception e )
378+ else
272379 {
273- Debug . LogException ( e ) ;
380+ // No user-land message handler exceptions should escape the receive loop.
381+ // If an exception is throw, the message is ignored.
382+ // Example use case: A bad message is received that can't be deserialized and throws
383+ // an OverflowException because it specifies a length greater than the number of bytes in it
384+ // for some dynamic-length value.
385+ try
386+ {
387+ handler . Invoke ( reader , ref context , this ) ;
388+ }
389+ catch ( Exception e )
390+ {
391+ Debug . LogException ( e ) ;
392+ }
274393 }
275394 }
276395 for ( var hookIdx = 0 ; hookIdx < m_Hooks . Count ; ++ hookIdx )
0 commit comments