Skip to content

Commit 9b8d070

Browse files
andrews-unityShadauxCatnetcode-ci-service
authored
fix: Fixed a runtime error when passing an array of struct INetworkSerializables to an RPC (#1402) (#1456)
* Fixed a runtime error when passing an array of struct INetworkSerializables to an RPC Co-authored-by: Jaedyn Draper <[email protected]> Co-authored-by: Unity Netcode CI <[email protected]>
1 parent 81bc09d commit 9b8d070

File tree

2 files changed

+188
-5
lines changed

2 files changed

+188
-5
lines changed

com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ public void WriteNetworkSerializable<T>(in T value) where T : INetworkSerializab
428428
/// <param name="count"></param>
429429
/// <param name="offset"></param>
430430
/// <typeparam name="T"></typeparam>
431-
public void WriteNetworkSerializable<T>(INetworkSerializable[] array, int count = -1, int offset = 0) where T : INetworkSerializable
431+
public void WriteNetworkSerializable<T>(T[] array, int count = -1, int offset = 0) where T : INetworkSerializable
432432
{
433433
int sizeInTs = count != -1 ? count : array.Length - offset;
434434
WriteValueSafe(sizeInTs);

testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs

Lines changed: 187 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@ namespace TestProject.RuntimeTests
1111
public class RpcUserSerializableTypesTest : BaseMultiInstanceTest
1212
{
1313
private UserSerializableClass m_UserSerializableClass;
14+
private UserSerializableStruct m_UserSerializableStruct;
1415
private List<UserSerializableClass> m_UserSerializableClassArray;
16+
private List<UserSerializableStruct> m_UserSerializableStructArray;
1517

1618
private bool m_FinishedTest;
19+
private bool m_FinishedStructTest;
20+
private bool m_FinishedClassTest;
1721

1822
private bool m_IsSendingNull;
1923
private bool m_IsArrayEmpty;
@@ -34,6 +38,8 @@ public override IEnumerator Setup()
3438
public IEnumerator NetworkSerializableTest()
3539
{
3640
m_FinishedTest = false;
41+
m_FinishedStructTest = false;
42+
m_FinishedClassTest = false;
3743
var startTime = Time.realtimeSinceStartup;
3844

3945
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab =>
@@ -46,6 +52,7 @@ public IEnumerator NetworkSerializableTest()
4652
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult));
4753
var clientSideNetworkBehaviourClass = clientClientPlayerResult.Result.gameObject.GetComponent<TestSerializationComponent>();
4854
clientSideNetworkBehaviourClass.OnSerializableClassUpdated = OnClientReceivedUserSerializableClassUpdated;
55+
clientSideNetworkBehaviourClass.OnSerializableStructUpdated = OnClientReceivedUserSerializableStructUpdated;
4956

5057

5158
var userSerializableClass = new UserSerializableClass();
@@ -57,7 +64,13 @@ public IEnumerator NetworkSerializableTest()
5764
userSerializableClass.MyintValue = 1;
5865
userSerializableClass.MyulongValue = 100;
5966

67+
var userSerializableStruct = new UserSerializableStruct();
68+
69+
userSerializableStruct.MyintValue = 1;
70+
userSerializableStruct.MyulongValue = 100;
71+
6072
clientSideNetworkBehaviourClass.ClientStartTest(userSerializableClass);
73+
clientSideNetworkBehaviourClass.ClientStartTest(userSerializableStruct);
6174

6275
// Wait until the test has finished or we time out
6376
var timeOutPeriod = Time.realtimeSinceStartup + 5;
@@ -86,6 +99,9 @@ public IEnumerator NetworkSerializableTest()
8699
Assert.AreEqual(m_UserSerializableClass.MyByteListValues[i], i);
87100
}
88101

102+
Assert.AreEqual(m_UserSerializableStruct.MyintValue, userSerializableStruct.MyintValue + 1);
103+
Assert.AreEqual(m_UserSerializableStruct.MyulongValue, userSerializableStruct.MyulongValue + 1);
104+
89105
// End of test
90106
m_ClientNetworkManagers[0].Shutdown();
91107
m_ServerNetworkManager.Shutdown();
@@ -286,7 +302,19 @@ public IEnumerator ExtensionMethodArrayRpcTest()
286302
private void OnClientReceivedUserSerializableClassUpdated(UserSerializableClass userSerializableClass)
287303
{
288304
m_UserSerializableClass = userSerializableClass;
289-
m_FinishedTest = true;
305+
m_FinishedClassTest = true;
306+
m_FinishedTest = m_FinishedClassTest && m_FinishedStructTest;
307+
}
308+
309+
/// <summary>
310+
/// Delegate handler invoked towards the end of the when the NetworkSerializableTest
311+
/// </summary>
312+
/// <param name="userSerializableStruct"></param>
313+
private void OnClientReceivedUserSerializableStructUpdated(UserSerializableStruct userSerializableStruct)
314+
{
315+
m_UserSerializableStruct = userSerializableStruct;
316+
m_FinishedStructTest = true;
317+
m_FinishedTest = m_FinishedClassTest && m_FinishedStructTest;
290318
}
291319

292320
/// <summary>
@@ -352,14 +380,17 @@ public IEnumerator NetworkSerializableArrayTestHandler(int arraySize, bool sendN
352380
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult));
353381
var serverSideNetworkBehaviourClass = serverClientPlayerResult.Result.gameObject.GetComponent<TestCustomTypesArrayComponent>();
354382
serverSideNetworkBehaviourClass.OnSerializableClassesUpdatedServerRpc = OnServerReceivedUserSerializableClassesUpdated;
383+
serverSideNetworkBehaviourClass.OnSerializableStructsUpdatedServerRpc = OnServerReceivedUserSerializableStructsUpdated;
355384

356385
// [Client-Side] Get the client side Player's NetworkObject so we can grab that instance of the TestCustomTypesArrayComponent
357386
var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
358387
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult));
359388
var clientSideNetworkBehaviourClass = clientClientPlayerResult.Result.gameObject.GetComponent<TestCustomTypesArrayComponent>();
360389
clientSideNetworkBehaviourClass.OnSerializableClassesUpdatedClientRpc = OnClientReceivedUserSerializableClassesUpdated;
390+
clientSideNetworkBehaviourClass.OnSerializableStructsUpdatedClientRpc = OnClientReceivedUserSerializableStructsUpdated;
361391

362392
m_UserSerializableClassArray = new List<UserSerializableClass>();
393+
m_UserSerializableStructArray = new List<UserSerializableStruct>();
363394

364395
if (!m_IsSendingNull)
365396
{
@@ -370,13 +401,20 @@ public IEnumerator NetworkSerializableArrayTestHandler(int arraySize, bool sendN
370401
//Used for testing order of the array
371402
userSerializableClass.MyintValue = i;
372403
m_UserSerializableClassArray.Add(userSerializableClass);
404+
405+
var userSerializableStruct = new UserSerializableStruct();
406+
//Used for testing order of the array
407+
userSerializableStruct.MyintValue = i;
408+
m_UserSerializableStructArray.Add(userSerializableStruct);
373409
}
374410

375411
clientSideNetworkBehaviourClass.ClientStartTest(m_UserSerializableClassArray.ToArray());
412+
clientSideNetworkBehaviourClass.ClientStartStructTest(m_UserSerializableStructArray.ToArray());
376413
}
377414
else
378415
{
379416
clientSideNetworkBehaviourClass.ClientStartTest(null);
417+
clientSideNetworkBehaviourClass.ClientStartStructTest(null);
380418
}
381419

382420
// Wait until the test has finished or we time out
@@ -437,7 +475,8 @@ private void ValidateUserSerializableClasses(UserSerializableClass[] userSeriali
437475
private void OnClientReceivedUserSerializableClassesUpdated(UserSerializableClass[] userSerializableClass)
438476
{
439477
ValidateUserSerializableClasses(userSerializableClass);
440-
m_FinishedTest = true;
478+
m_FinishedClassTest = true;
479+
m_FinishedTest = m_FinishedClassTest && m_FinishedStructTest;
441480
}
442481

443482
/// <summary>
@@ -450,6 +489,55 @@ private void OnServerReceivedUserSerializableClassesUpdated(UserSerializableClas
450489
ValidateUserSerializableClasses(userSerializableClass);
451490
}
452491

492+
/// <summary>
493+
/// Verifies that the UserSerializableStruct array is in the same order
494+
/// that it was sent.
495+
/// </summary>
496+
/// <param name="userSerializableStruct"></param>
497+
private void ValidateUserSerializableStructs(UserSerializableStruct[] userSerializableStruct)
498+
{
499+
if (m_IsSendingNull)
500+
{
501+
Assert.IsNull(userSerializableStruct);
502+
}
503+
else if (m_IsArrayEmpty)
504+
{
505+
Assert.AreEqual(userSerializableStruct.Length, 0);
506+
}
507+
else
508+
{
509+
var indexCount = 0;
510+
// Check the order of the array
511+
foreach (var customTypeEntry in userSerializableStruct)
512+
{
513+
Assert.AreEqual(customTypeEntry.MyintValue, indexCount);
514+
indexCount++;
515+
}
516+
}
517+
}
518+
519+
/// <summary>
520+
/// Delegate handler invoked when the server sends the client
521+
/// the UserSerializableStruct array during the NetworkSerializableArrayTest
522+
/// </summary>
523+
/// <param name="userSerializableStruct"></param>
524+
private void OnClientReceivedUserSerializableStructsUpdated(UserSerializableStruct[] userSerializableStruct)
525+
{
526+
ValidateUserSerializableStructs(userSerializableStruct);
527+
m_FinishedStructTest = true;
528+
m_FinishedTest = m_FinishedClassTest && m_FinishedStructTest;
529+
}
530+
531+
/// <summary>
532+
/// Delegate handler invoked when the client sends the server
533+
/// the UserSerializableStruct array during the NetworkSerializableArrayTest
534+
/// </summary>
535+
/// <param name="userSerializableStruct"></param>
536+
private void OnServerReceivedUserSerializableStructsUpdated(UserSerializableStruct[] userSerializableStruct)
537+
{
538+
ValidateUserSerializableStructs(userSerializableStruct);
539+
}
540+
453541
}
454542

455543
/// <summary>
@@ -461,6 +549,9 @@ public class TestSerializationComponent : NetworkBehaviour
461549
public delegate void OnSerializableClassUpdatedDelgateHandler(UserSerializableClass userSerializableClass);
462550
public OnSerializableClassUpdatedDelgateHandler OnSerializableClassUpdated;
463551

552+
public delegate void OnSerializableStructUpdatedDelgateHandler(UserSerializableStruct userSerializableStruct);
553+
public OnSerializableStructUpdatedDelgateHandler OnSerializableStructUpdated;
554+
464555
public delegate void OnMySharedObjectReferencedByIdUpdatedDelgateHandler(MySharedObjectReferencedById obj);
465556
public OnMySharedObjectReferencedByIdUpdatedDelgateHandler OnMySharedObjectReferencedByIdUpdated;
466557

@@ -511,6 +602,41 @@ private void SendClientSerializedDataClientRpc(UserSerializableClass userSeriali
511602
}
512603
}
513604

605+
/// <summary>
606+
/// Starts the unit test and passes the UserSerializableStruct from the client to the server
607+
/// </summary>
608+
/// <param name="userSerializableStruct"></param>
609+
public void ClientStartTest(UserSerializableStruct userSerializableStruct)
610+
{
611+
SendServerSerializedDataServerRpc(userSerializableStruct);
612+
}
613+
614+
/// <summary>
615+
/// Server receives the UserSerializableStruct, modifies it, and sends it back
616+
/// </summary>
617+
/// <param name="userSerializableStruct"></param>
618+
[ServerRpc(RequireOwnership = false)]
619+
private void SendServerSerializedDataServerRpc(UserSerializableStruct userSerializableStruct)
620+
{
621+
userSerializableStruct.MyintValue++;
622+
userSerializableStruct.MyulongValue++;
623+
624+
SendClientSerializedDataClientRpc(userSerializableStruct);
625+
}
626+
627+
/// <summary>
628+
/// Client receives the UserSerializableStruct and then invokes the OnSerializableStructUpdated (if set)
629+
/// </summary>
630+
/// <param name="userSerializableStruct"></param>
631+
[ClientRpc]
632+
private void SendClientSerializedDataClientRpc(UserSerializableStruct userSerializableStruct)
633+
{
634+
if (OnSerializableStructUpdated != null)
635+
{
636+
OnSerializableStructUpdated.Invoke(userSerializableStruct);
637+
}
638+
}
639+
514640
[ClientRpc]
515641
public void SendMyObjectClientRpc(MyObject obj)
516642
{
@@ -559,6 +685,7 @@ public void SendMySharedObjectReferencedByIdServerRpc(MySharedObjectReferencedBy
559685
public class TestCustomTypesArrayComponent : NetworkBehaviour
560686
{
561687
public delegate void OnSerializableClassesUpdatedDelgateHandler(UserSerializableClass[] userSerializableClasses);
688+
public delegate void OnSerializableStructsUpdatedDelgateHandler(UserSerializableStruct[] userSerializableStructs);
562689

563690
public delegate void OnMySharedObjectReferencedByIdUpdatedDelgateHandler(MySharedObjectReferencedById[] obj);
564691
public OnMySharedObjectReferencedByIdUpdatedDelgateHandler OnMySharedObjectReferencedByIdUpdated;
@@ -568,6 +695,8 @@ public class TestCustomTypesArrayComponent : NetworkBehaviour
568695

569696
public OnSerializableClassesUpdatedDelgateHandler OnSerializableClassesUpdatedServerRpc;
570697
public OnSerializableClassesUpdatedDelgateHandler OnSerializableClassesUpdatedClientRpc;
698+
public OnSerializableStructsUpdatedDelgateHandler OnSerializableStructsUpdatedServerRpc;
699+
public OnSerializableStructsUpdatedDelgateHandler OnSerializableStructsUpdatedClientRpc;
571700

572701
/// <summary>
573702
/// Starts the unit test and passes the userSerializableClasses array
@@ -595,10 +724,10 @@ private void SendServerSerializedDataServerRpc(UserSerializableClass[] userSeria
595724
}
596725

597726
/// <summary>
598-
/// Client receives the UserSerializableClasses array and invokes the callback
727+
/// Client receives the UserSerializableClass array and invokes the callback
599728
/// for verification and signaling the test is complete.
600729
/// </summary>
601-
/// <param name="userSerializableClass"></param>
730+
/// <param name="userSerializableClasses"></param>
602731
[ClientRpc]
603732
private void SendClientSerializedDataClientRpc(UserSerializableClass[] userSerializableClasses)
604733
{
@@ -608,6 +737,45 @@ private void SendClientSerializedDataClientRpc(UserSerializableClass[] userSeria
608737
}
609738
}
610739

740+
/// <summary>
741+
/// Starts the unit test and passes the userSerializableStructs array
742+
/// from the client to the server
743+
/// </summary>
744+
/// <param name="userSerializableStructs"></param>
745+
public void ClientStartStructTest(UserSerializableStruct[] userSerializableStructs)
746+
{
747+
SendServerSerializedDataServerRpc(userSerializableStructs);
748+
}
749+
750+
/// <summary>
751+
/// Server receives the UserSerializableStructs array, invokes the callback
752+
/// that checks the order, and then passes it back to the client
753+
/// </summary>
754+
/// <param name="userSerializableStructs"></param>
755+
[ServerRpc(RequireOwnership = false)]
756+
private void SendServerSerializedDataServerRpc(UserSerializableStruct[] userSerializableStructs)
757+
{
758+
if (OnSerializableStructsUpdatedServerRpc != null)
759+
{
760+
OnSerializableStructsUpdatedServerRpc.Invoke(userSerializableStructs);
761+
}
762+
SendClientSerializedDataClientRpc(userSerializableStructs);
763+
}
764+
765+
/// <summary>
766+
/// Client receives the userSerializableStructs array and invokes the callback
767+
/// for verification and signaling the test is complete.
768+
/// </summary>
769+
/// <param name="userSerializableStructs"></param>
770+
[ClientRpc]
771+
private void SendClientSerializedDataClientRpc(UserSerializableStruct[] userSerializableStructs)
772+
{
773+
if (OnSerializableStructsUpdatedClientRpc != null)
774+
{
775+
OnSerializableStructsUpdatedClientRpc.Invoke(userSerializableStructs);
776+
}
777+
}
778+
611779
[ClientRpc]
612780
public void SendMyObjectClientRpc(MyObject[] objs)
613781
{
@@ -682,6 +850,21 @@ public UserSerializableClass()
682850
}
683851
}
684852

853+
/// <summary>
854+
/// The test version of a custom user-defined struct that implements INetworkSerializable
855+
/// </summary>
856+
public struct UserSerializableStruct : INetworkSerializable
857+
{
858+
public int MyintValue;
859+
public ulong MyulongValue;
860+
861+
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
862+
{
863+
serializer.SerializeValue(ref MyintValue);
864+
serializer.SerializeValue(ref MyulongValue);
865+
}
866+
}
867+
685868
public class MyObject
686869
{
687870
public int I;

0 commit comments

Comments
 (0)