Skip to content

Commit d565d9d

Browse files
authored
feat: Support for generic NetworkBehaviour types, and fix: issues with domain reload/scene reload being disabled (#2720)
Adds support for generic network behaviours, as well as for serializing generic types in RPCs (so long as the type is defined on the behaviour and not on the RPC itself; which is to say, this is supported: ```csharp public class Foo<T> : NetworkBehaviour { [ServerRpc] public void MyServerRpc(T val) {} } ``` But this is not: ```csharp public class Foo : NetworkBehaviour { [ServerRpc] public void MyServerRpc<T>(T val) {} } ``` As in the former case, when the class is instantiated, it knows what T is and there is only one version of the RPC per class, while the latter case would require much more significant plumbing for runtime type identification of RPC parameters in order to be able to instantiate the RPC correctly on the receiving side.) In fixing this, the majority of the issues with domain reload were also fixed coincidentally, so I went ahead and fixed the couple of remaining issues as well.
1 parent a5c0130 commit d565d9d

File tree

9 files changed

+302
-68
lines changed

9 files changed

+302
-68
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ Additional documentation and release notes are available at [Multiplayer Documen
1818
- Added `NetworkVariableBase.MarkNetworkBehaviourDirty` so that user-created network variable types can mark their containing `NetworkBehaviour` to be processed by the update loop. (#2694)
1919

2020
### Fixed
21-
21+
- Generic NetworkBehaviour types no longer result in compile errors or runtime errors (#2720)
22+
- Rpcs within Generic NetworkBehaviour types can now serialize parameters of the class's generic types (but may not have generic types of their own) (#2720)
23+
- Errors are no longer thrown when entering play mode with domain reload disabled (#2720)
24+
- NetworkSpawn is now correctly called each time when entering play mode with scene reload disabled (#2720)
2225
- Fixed issue where `UnityTransport` would attempt to establish WebSocket connections even if using UDP/DTLS Relay allocations when the build target was WebGL. This only applied to working in the editor since UDP/DTLS can't work in the browser. (#2695)
2326
- Fixed issue where a `NetworkBehaviour` component's `OnNetworkDespawn` was not being invoked on the host-server side for an in-scene placed `NetworkObject` when a scene was unloaded (during a scene transition) and the `NetworkBehaviour` component was positioned/ordered before the `NetworkObject` component. (#2685)
2427
- Fixed issue where `SpawnWithObservers` was not being honored when `NetworkConfig.EnableSceneManagement` was disabled. (#2682)

com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs

Lines changed: 140 additions & 53 deletions
Large diffs are not rendered by default.

com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
5353
ProcessNetworkBehaviour(typeDefinition);
5454
break;
5555
case nameof(__RpcParams):
56+
case nameof(RpcFallbackSerialization):
5657
typeDefinition.IsPublic = true;
5758
break;
5859
}
@@ -79,6 +80,9 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
7980
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
8081
}
8182

83+
// TODO: Deprecate...
84+
// This is changing accessibility for values that are no longer used, but since our validator runs
85+
// after ILPP and sees those values as public, they cannot be removed until a major version change.
8286
private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assemblyDefines)
8387
{
8488
foreach (var fieldDefinition in typeDefinition.Fields)
@@ -116,6 +120,10 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
116120
{
117121
nestedType.IsNestedFamily = true;
118122
}
123+
if (nestedType.Name == nameof(NetworkBehaviour.RpcReceiveHandler))
124+
{
125+
nestedType.IsNestedPublic = true;
126+
}
119127
}
120128

121129
foreach (var fieldDefinition in typeDefinition.Fields)
@@ -124,6 +132,20 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
124132
{
125133
fieldDefinition.IsFamilyOrAssembly = true;
126134
}
135+
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_func_table))
136+
{
137+
fieldDefinition.IsFamilyOrAssembly = true;
138+
}
139+
140+
if (fieldDefinition.Name == nameof(NetworkBehaviour.RpcReceiveHandler))
141+
{
142+
fieldDefinition.IsFamilyOrAssembly = true;
143+
}
144+
145+
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_name_table))
146+
{
147+
fieldDefinition.IsFamilyOrAssembly = true;
148+
}
127149
}
128150

129151
foreach (var methodDefinition in typeDefinition.Methods)
@@ -133,6 +155,8 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
133155
methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) ||
134156
methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc) ||
135157
methodDefinition.Name == nameof(NetworkBehaviour.__initializeVariables) ||
158+
methodDefinition.Name == nameof(NetworkBehaviour.__initializeRpcs) ||
159+
methodDefinition.Name == nameof(NetworkBehaviour.__registerRpc) ||
136160
methodDefinition.Name == nameof(NetworkBehaviour.__nameNetworkVariable) ||
137161
methodDefinition.Name == nameof(NetworkBehaviour.__createNativeList))
138162
{

com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Unity.Collections;
44
using UnityEngine;
55

6+
67
namespace Unity.Netcode
78
{
89
/// <summary>
@@ -11,6 +12,18 @@ namespace Unity.Netcode
1112
public abstract class NetworkBehaviour : MonoBehaviour
1213
{
1314
#pragma warning disable IDE1006 // disable naming rule violation check
15+
16+
// RuntimeAccessModifiersILPP will make this `public`
17+
internal delegate void RpcReceiveHandler(NetworkBehaviour behaviour, FastBufferReader reader, __RpcParams parameters);
18+
19+
// RuntimeAccessModifiersILPP will make this `public`
20+
internal static readonly Dictionary<Type, Dictionary<uint, RpcReceiveHandler>> __rpc_func_table = new Dictionary<Type, Dictionary<uint, RpcReceiveHandler>>();
21+
22+
#if DEVELOPMENT_BUILD || UNITY_EDITOR
23+
// RuntimeAccessModifiersILPP will make this `public`
24+
internal static readonly Dictionary<Type, Dictionary<uint, string>> __rpc_name_table = new Dictionary<Type, Dictionary<uint, string>>();
25+
#endif
26+
1427
// RuntimeAccessModifiersILPP will make this `protected`
1528
internal enum __RpcExecStage
1629
{
@@ -97,7 +110,7 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth
97110

98111
bufferWriter.Dispose();
99112
#if DEVELOPMENT_BUILD || UNITY_EDITOR
100-
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
113+
if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName))
101114
{
102115
NetworkManager.NetworkMetrics.TrackRpcSent(
103116
NetworkManager.ServerClientId,
@@ -228,7 +241,7 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth
228241

229242
bufferWriter.Dispose();
230243
#if DEVELOPMENT_BUILD || UNITY_EDITOR
231-
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
244+
if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName))
232245
{
233246
if (clientRpcParams.Send.TargetClientIds != null)
234247
{
@@ -582,6 +595,25 @@ internal virtual void __initializeVariables()
582595
// ILPP generates code for all NetworkBehaviour subtypes to initialize each type's network variables.
583596
}
584597

598+
#pragma warning disable IDE1006 // disable naming rule violation check
599+
// RuntimeAccessModifiersILPP will make this `protected`
600+
internal virtual void __initializeRpcs()
601+
#pragma warning restore IDE1006 // restore naming rule violation check
602+
{
603+
// ILPP generates code for all NetworkBehaviour subtypes to initialize each type's RPCs.
604+
}
605+
606+
#pragma warning disable IDE1006 // disable naming rule violation check
607+
// RuntimeAccessModifiersILPP will make this `protected`
608+
internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMethodName)
609+
#pragma warning restore IDE1006 // restore naming rule violation check
610+
{
611+
__rpc_func_table[GetType()][hash] = handler;
612+
#if DEVELOPMENT_BUILD || UNITY_EDITOR
613+
__rpc_name_table[GetType()][hash] = rpcMethodName;
614+
#endif
615+
}
616+
585617
#pragma warning disable IDE1006 // disable naming rule violation check
586618
// RuntimeAccessModifiersILPP will make this `protected`
587619
// Using this method here because ILPP doesn't seem to let us do visibility modification on properties.
@@ -600,6 +632,14 @@ internal void InitializeVariables()
600632

601633
m_VarInit = true;
602634

635+
if (!__rpc_func_table.ContainsKey(GetType()))
636+
{
637+
__rpc_func_table[GetType()] = new Dictionary<uint, RpcReceiveHandler>();
638+
#if UNITY_EDITOR || DEVELOPMENT_BUILD
639+
__rpc_name_table[GetType()] = new Dictionary<uint, string>();
640+
#endif
641+
__initializeRpcs();
642+
}
603643
__initializeVariables();
604644

605645
{

com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ namespace Unity.Netcode
1515
[AddComponentMenu("Netcode/Network Manager", -100)]
1616
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
1717
{
18+
// TODO: Deprecate...
19+
// The following internal values are not used, but because ILPP makes them public in the assembly, they cannot
20+
// be removed thanks to our semver validation.
1821
#pragma warning disable IDE1006 // disable naming rule violation check
1922

2023
// RuntimeAccessModifiersILPP will make this `public`
@@ -491,6 +494,15 @@ internal void OnValidate()
491494
}
492495
}
493496
}
497+
498+
private void ModeChanged(PlayModeStateChange change)
499+
{
500+
if (IsListening && change == PlayModeStateChange.ExitingPlayMode)
501+
{
502+
// Make sure we are not holding onto anything in case domain reload is disabled
503+
ShutdownInternal();
504+
}
505+
}
494506
#endif
495507

496508
/// <summary>
@@ -539,6 +551,9 @@ private void Awake()
539551
NetworkConfig?.InitializePrefabs();
540552

541553
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
554+
#if UNITY_EDITOR
555+
EditorApplication.playModeStateChanged += ModeChanged;
556+
#endif
542557
}
543558

544559
private void OnEnable()

com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
using System.Collections.Generic;
2+
#if UNITY_EDITOR
3+
using UnityEditor;
4+
#endif
25

36
namespace Unity.Netcode
47
{
@@ -13,5 +16,24 @@ internal struct ILPPMessageProvider : INetworkMessageProvider
1316
{
1417
return __network_message_types;
1518
}
19+
20+
#if UNITY_EDITOR
21+
[InitializeOnLoadMethod]
22+
public static void NotifyOnPlayStateChange()
23+
{
24+
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
25+
}
26+
27+
public static void OnPlayModeStateChanged(PlayModeStateChange change)
28+
{
29+
if (change == PlayModeStateChange.ExitingPlayMode)
30+
{
31+
// Clear out the network message types, because ILPP-generated RuntimeInitializeOnLoad code will
32+
// run again and add more messages to it.
33+
__network_message_types.Clear();
34+
}
35+
}
36+
37+
#endif
1638
}
1739
}

com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkCo
3434
return false;
3535
}
3636

37-
if (!NetworkManager.__rpc_func_table.ContainsKey(metadata.NetworkRpcMethodId))
37+
if (!NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()].ContainsKey(metadata.NetworkRpcMethodId))
3838
{
3939
return false;
4040
}
4141

4242
payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position);
4343

4444
#if DEVELOPMENT_BUILD || UNITY_EDITOR
45-
if (NetworkManager.__rpc_name_table.TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName))
45+
if (NetworkBehaviour.__rpc_name_table[networkBehaviour.GetType()].TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName))
4646
{
4747
networkManager.NetworkMetrics.TrackRpcReceived(
4848
context.SenderId,
@@ -67,15 +67,15 @@ public static void Handle(ref NetworkContext context, ref RpcMetadata metadata,
6767

6868
try
6969
{
70-
NetworkManager.__rpc_func_table[metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams);
70+
NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams);
7171
}
7272
catch (Exception ex)
7373
{
7474
Debug.LogException(new Exception("Unhandled RPC exception!", ex));
7575
if (networkManager.LogLevel == LogLevel.Developer)
7676
{
7777
Debug.Log($"RPC Table Contents");
78-
foreach (var entry in NetworkManager.__rpc_func_table)
78+
foreach (var entry in NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()])
7979
{
8080
Debug.Log($"{entry.Key} | {entry.Value.Method.Name}");
8181
}

com.unity.netcode.gameobjects/Runtime/NetworkVariable/NetworkVariableSerialization.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,4 +1094,20 @@ internal static bool ClassEquals<TValueType>(ref TValueType a, ref TValueType b)
10941094
return a == b;
10951095
}
10961096
}
1097+
1098+
// RuntimeAccessModifiersILPP will make this `public`
1099+
// This is just pass-through to NetworkVariableSerialization<T> but is here becaues I could not get ILPP
1100+
// to generate code that would successfully call Type<T>.Method(T), but it has no problem calling Type.Method<T>(T)
1101+
internal class RpcFallbackSerialization
1102+
{
1103+
public static void Write<T>(FastBufferWriter writer, ref T value)
1104+
{
1105+
NetworkVariableSerialization<T>.Write(writer, ref value);
1106+
}
1107+
1108+
public static void Read<T>(FastBufferReader reader, ref T value)
1109+
{
1110+
NetworkVariableSerialization<T>.Read(reader, ref value);
1111+
}
1112+
}
10971113
}

com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,23 @@ namespace Unity.Netcode.RuntimeTests
1212
{
1313
public class RpcTests : NetcodeIntegrationTest
1414
{
15-
public class RpcTestNB : NetworkBehaviour
15+
public class GenericRpcTestNB<T> : NetworkBehaviour where T : unmanaged
16+
{
17+
public event Action<T, ServerRpcParams> OnServer_Rpc;
18+
19+
[ServerRpc]
20+
public void MyServerRpc(T clientId, ServerRpcParams param = default)
21+
{
22+
OnServer_Rpc(clientId, param);
23+
}
24+
}
25+
26+
public class RpcTestNBFloat : GenericRpcTestNB<float>
27+
{
28+
}
29+
30+
public class RpcTestNB : GenericRpcTestNB<ulong>
1631
{
17-
public event Action<ulong, ServerRpcParams> OnServer_Rpc;
1832
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
1933
public event Action<NativeList<ulong>, ServerRpcParams> OnNativeListServer_Rpc;
2034
#endif
@@ -26,12 +40,6 @@ public class RpcTestNB : NetworkBehaviour
2640

2741
public event Action OnClient_Rpc;
2842

29-
[ServerRpc]
30-
public void MyServerRpc(ulong clientId, ServerRpcParams param = default)
31-
{
32-
OnServer_Rpc(clientId, param);
33-
}
34-
3543
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
3644
[ServerRpc]
3745
public void MyNativeListServerRpc(NativeList<ulong> clientId, ServerRpcParams param = default)
@@ -67,19 +75,23 @@ public void MyTypedServerRpc(Vector3 param1, Vector3[] param2,
6775
protected override void OnCreatePlayerPrefab()
6876
{
6977
m_PlayerPrefab.AddComponent<RpcTestNB>();
78+
m_PlayerPrefab.AddComponent<RpcTestNBFloat>();
7079
}
7180

7281
[UnityTest]
7382
public IEnumerator TestRpcs()
7483
{
7584
// This is the *SERVER VERSION* of the *CLIENT PLAYER* RpcTestNB component
7685
var serverClientRpcTestNB = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent<RpcTestNB>();
86+
var serverClientRpcTestNBFloat = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent<RpcTestNBFloat>();
7787

7888
// This is the *CLIENT VERSION* of the *CLIENT PLAYER* RpcTestNB component
7989
var localClienRpcTestNB = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent<RpcTestNB>();
90+
var localClienRpcTestNBFloat = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent<RpcTestNBFloat>();
8091

8192
// Setup state
8293
bool hasReceivedServerRpc = false;
94+
bool hasReceivedFloatServerRpc = false;
8395
bool hasReceivedTypedServerRpc = false;
8496
bool hasReceivedClientRpcRemotely = false;
8597
bool hasReceivedClientRpcLocally = false;
@@ -106,13 +118,26 @@ public IEnumerator TestRpcs()
106118
Assert.Fail("ServerRpc invoked locally. Weaver failure?");
107119
};
108120

121+
localClienRpcTestNBFloat.OnServer_Rpc += (clientId, param) =>
122+
{
123+
// The RPC invoked locally. (Weaver failure?)
124+
Assert.Fail("ServerRpc (float) invoked locally. Weaver failure?");
125+
};
126+
109127
serverClientRpcTestNB.OnServer_Rpc += (clientId, param) =>
110128
{
111129
Debug.Log("ServerRpc received on server object");
112130
Assert.True(param.Receive.SenderClientId == clientId);
113131
hasReceivedServerRpc = true;
114132
};
115133

134+
serverClientRpcTestNBFloat.OnServer_Rpc += (clientId, param) =>
135+
{
136+
Debug.Log("ServerRpc (float) received on server object");
137+
Assert.True(param.Receive.SenderClientId == clientId);
138+
hasReceivedFloatServerRpc = true;
139+
};
140+
116141
serverClientRpcTestNB.OnClient_Rpc += () =>
117142
{
118143
// The RPC invoked locally. (Weaver failure?)
@@ -145,6 +170,7 @@ public IEnumerator TestRpcs()
145170

146171
// Send ServerRpc
147172
localClienRpcTestNB.MyServerRpc(m_ClientNetworkManagers[0].LocalClientId);
173+
localClienRpcTestNBFloat.MyServerRpc(m_ClientNetworkManagers[0].LocalClientId);
148174

149175
// Send TypedServerRpc
150176
localClienRpcTestNB.MyTypedServerRpc(vector3, vector3s,
@@ -181,6 +207,7 @@ public IEnumerator TestRpcs()
181207
yield return WaitForConditionOrTimeOut(() => hasReceivedServerRpc && hasReceivedClientRpcLocally && hasReceivedClientRpcRemotely && hasReceivedTypedServerRpc);
182208

183209
Assert.True(hasReceivedServerRpc, "ServerRpc was not received");
210+
Assert.True(hasReceivedFloatServerRpc, "ServerRpc was not received");
184211
Assert.True(hasReceivedTypedServerRpc, "TypedServerRpc was not received");
185212
Assert.True(hasReceivedClientRpcLocally, "ClientRpc was not locally received on the server");
186213
Assert.True(hasReceivedClientRpcRemotely, "ClientRpc was not remotely received on the client");

0 commit comments

Comments
 (0)