8
8
9
9
namespace Unity . Netcode
10
10
{
11
+ internal class HandlerNotRegisteredException : SystemException
12
+ {
13
+ public HandlerNotRegisteredException ( ) { }
14
+ public HandlerNotRegisteredException ( string issue ) : base ( issue ) { }
15
+ }
11
16
12
17
internal class InvalidMessageStructureException : SystemException
13
18
{
@@ -44,8 +49,9 @@ public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerA
44
49
45
50
private NativeList < ReceiveQueueItem > m_IncomingMessageQueue = new NativeList < ReceiveQueueItem > ( 16 , Allocator . Persistent ) ;
46
51
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 ] ;
49
55
50
56
private Dictionary < Type , uint > m_MessageTypes = new Dictionary < Type , uint > ( ) ;
51
57
private Dictionary < ulong , NativeList < SendQueueItem > > m_SendQueues = new Dictionary < ulong , NativeList < SendQueueItem > > ( ) ;
@@ -59,6 +65,7 @@ public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerA
59
65
60
66
internal Type [ ] MessageTypes => m_ReverseTypeMap ;
61
67
internal MessageHandler [ ] MessageHandlers => m_MessageHandlers ;
68
+
62
69
internal uint MessageHandlerCount => m_HighMessageType ;
63
70
64
71
internal uint GetMessageType ( Type t )
@@ -75,6 +82,35 @@ internal struct MessageWithHandler
75
82
public MessageHandler Handler ;
76
83
}
77
84
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
+
78
114
public MessagingSystem ( IMessageSender messageSender , object owner , IMessageProvider provider = null )
79
115
{
80
116
try
@@ -89,6 +125,7 @@ public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvi
89
125
var allowedTypes = provider . GetMessages ( ) ;
90
126
91
127
allowedTypes . Sort ( ( a , b ) => string . CompareOrdinal ( a . MessageType . FullName , b . MessageType . FullName ) ) ;
128
+ allowedTypes = PrioritizeMessageOrder ( allowedTypes ) ;
92
129
foreach ( var type in allowedTypes )
93
130
{
94
131
RegisterMessageType ( type ) ;
@@ -143,6 +180,13 @@ public void Unhook(INetworkHooks hooks)
143
180
144
181
private void RegisterMessageType ( MessageWithHandler messageWithHandler )
145
182
{
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
+
146
190
m_MessageHandlers [ m_HighMessageType ] = messageWithHandler . Handler ;
147
191
m_ReverseTypeMap [ m_HighMessageType ] = messageWithHandler . MessageType ;
148
192
m_MessageTypes [ messageWithHandler . MessageType ] = m_HighMessageType ++ ;
@@ -226,6 +270,70 @@ private bool CanReceive(ulong clientId, Type messageType, FastBufferReader messa
226
270
return true ;
227
271
}
228
272
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
+
229
337
public void HandleMessage ( in MessageHeader header , FastBufferReader reader , ulong senderId , float timestamp , int serializedHeaderSize )
230
338
{
231
339
if ( header . MessageType >= m_HighMessageType )
@@ -259,18 +367,29 @@ public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulon
259
367
var handler = m_MessageHandlers [ header . MessageType ] ;
260
368
using ( reader )
261
369
{
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 )
268
375
{
269
- handler . Invoke ( reader , ref context , this ) ;
376
+ Debug . LogException ( new HandlerNotRegisteredException ( header . MessageType . ToString ( ) ) ) ;
270
377
}
271
- catch ( Exception e )
378
+ else
272
379
{
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
+ }
274
393
}
275
394
}
276
395
for ( var hookIdx = 0 ; hookIdx < m_Hooks . Count ; ++ hookIdx )
0 commit comments