Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support for generic NetworkBehaviour types, and fix: issues with domain reload/scene reload being disabled #2720

Merged
merged 6 commits into from
Oct 3, 2023
Merged
Changes from 1 commit
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
Next Next commit
Support for generic NetworkBehaviour types
ShadauxCat committed Sep 14, 2023
commit d86c21d7fc2327c4525669ef20fb023c580ed043
193 changes: 140 additions & 53 deletions com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -46,13 +46,11 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)

switch (typeDefinition.Name)
{
case nameof(NetworkManager):
ProcessNetworkManager(typeDefinition, compiledAssembly.Defines);
break;
case nameof(NetworkBehaviour):
ProcessNetworkBehaviour(typeDefinition);
break;
case nameof(__RpcParams):
case nameof(RpcFallbackSerialization):
typeDefinition.IsPublic = true;
break;
}
@@ -79,48 +77,37 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
}

private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assemblyDefines)
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
{
foreach (var fieldDefinition in typeDefinition.Fields)
foreach (var nestedType in typeDefinition.NestedTypes)
{
if (fieldDefinition.Name == nameof(NetworkManager.__rpc_func_table))
{
fieldDefinition.IsPublic = true;
}

if (fieldDefinition.Name == nameof(NetworkManager.RpcReceiveHandler))
if (nestedType.Name == nameof(NetworkBehaviour.__RpcExecStage))
{
fieldDefinition.IsPublic = true;
nestedType.IsNestedFamily = true;
}

if (fieldDefinition.Name == nameof(NetworkManager.__rpc_name_table))
if (nestedType.Name == nameof(NetworkBehaviour.RpcReceiveHandler))
{
fieldDefinition.IsPublic = true;
nestedType.IsNestedPublic = true;
}
}

foreach (var nestedTypeDefinition in typeDefinition.NestedTypes)
foreach (var fieldDefinition in typeDefinition.Fields)
{
if (nestedTypeDefinition.Name == nameof(NetworkManager.RpcReceiveHandler))
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_exec_stage) || fieldDefinition.Name == nameof(NetworkBehaviour.NetworkVariableFields))
{
nestedTypeDefinition.IsNestedPublic = true;
fieldDefinition.IsFamilyOrAssembly = true;
}
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_func_table))
{
fieldDefinition.IsFamilyOrAssembly = true;
}
}
}

private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
{
foreach (var nestedType in typeDefinition.NestedTypes)
{
if (nestedType.Name == nameof(NetworkBehaviour.__RpcExecStage))
if (fieldDefinition.Name == nameof(NetworkBehaviour.RpcReceiveHandler))
{
nestedType.IsNestedFamily = true;
fieldDefinition.IsFamilyOrAssembly = true;
}
}

foreach (var fieldDefinition in typeDefinition.Fields)
{
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_exec_stage) || fieldDefinition.Name == nameof(NetworkBehaviour.NetworkVariableFields))
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_name_table))
{
fieldDefinition.IsFamilyOrAssembly = true;
}
@@ -133,6 +120,8 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__initializeVariables) ||
methodDefinition.Name == nameof(NetworkBehaviour.__initializeRpcs) ||
methodDefinition.Name == nameof(NetworkBehaviour.__registerRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__nameNetworkVariable) ||
methodDefinition.Name == nameof(NetworkBehaviour.__createNativeList))
{
44 changes: 42 additions & 2 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
using Unity.Collections;
using UnityEngine;


namespace Unity.Netcode
{
/// <summary>
@@ -11,6 +12,18 @@ namespace Unity.Netcode
public abstract class NetworkBehaviour : MonoBehaviour
{
#pragma warning disable IDE1006 // disable naming rule violation check

// RuntimeAccessModifiersILPP will make this `public`
internal delegate void RpcReceiveHandler(NetworkBehaviour behaviour, FastBufferReader reader, __RpcParams parameters);

// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<Type, Dictionary<uint, RpcReceiveHandler>> __rpc_func_table = new Dictionary<Type, Dictionary<uint, RpcReceiveHandler>>();

#if DEVELOPMENT_BUILD || UNITY_EDITOR
// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<Type, Dictionary<uint, string>> __rpc_name_table = new Dictionary<Type, Dictionary<uint, string>>();
#endif

// RuntimeAccessModifiersILPP will make this `protected`
internal enum __RpcExecStage
{
@@ -97,7 +110,7 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth

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

bufferWriter.Dispose();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName))
{
if (clientRpcParams.Send.TargetClientIds != null)
{
@@ -565,6 +578,25 @@ internal virtual void __initializeVariables()
// ILPP generates code for all NetworkBehaviour subtypes to initialize each type's network variables.
}

#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal virtual void __initializeRpcs()
#pragma warning restore IDE1006 // restore naming rule violation check
{
// ILPP generates code for all NetworkBehaviour subtypes to initialize each type's RPCs.
}

#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMethodName)
#pragma warning restore IDE1006 // restore naming rule violation check
{
__rpc_func_table[GetType()][hash] = handler;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
__rpc_name_table[GetType()][hash] = rpcMethodName;
#endif
}

#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
// Using this method here because ILPP doesn't seem to let us do visibility modification on properties.
@@ -583,6 +615,14 @@ internal void InitializeVariables()

m_VarInit = true;

if (!__rpc_func_table.ContainsKey(GetType()))
{
__rpc_func_table[GetType()] = new Dictionary<uint, RpcReceiveHandler>();
#if UNITY_EDITOR || DEVELOPMENT_BUILD
__rpc_name_table[GetType()] = new Dictionary<uint, string>();
#endif
__initializeRpcs();
}
__initializeVariables();

{
15 changes: 0 additions & 15 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
Original file line number Diff line number Diff line change
@@ -15,21 +15,6 @@ namespace Unity.Netcode
[AddComponentMenu("Netcode/Network Manager", -100)]
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
{
#pragma warning disable IDE1006 // disable naming rule violation check

// RuntimeAccessModifiersILPP will make this `public`
internal delegate void RpcReceiveHandler(NetworkBehaviour behaviour, FastBufferReader reader, __RpcParams parameters);

// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<uint, RpcReceiveHandler> __rpc_func_table = new Dictionary<uint, RpcReceiveHandler>();

#if DEVELOPMENT_BUILD || UNITY_EDITOR
// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<uint, string> __rpc_name_table = new Dictionary<uint, string>();
#endif

#pragma warning restore IDE1006 // restore naming rule violation check

public void NetworkUpdate(NetworkUpdateStage updateStage)
{
switch (updateStage)
Original file line number Diff line number Diff line change
@@ -34,15 +34,15 @@ public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkCo
return false;
}

if (!NetworkManager.__rpc_func_table.ContainsKey(metadata.NetworkRpcMethodId))
if (!NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()].ContainsKey(metadata.NetworkRpcMethodId))
{
return false;
}

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

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

try
{
NetworkManager.__rpc_func_table[metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams);
NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams);
}
catch (Exception ex)
{
Debug.LogException(new Exception("Unhandled RPC exception!", ex));
if (networkManager.LogLevel == LogLevel.Developer)
{
Debug.Log($"RPC Table Contents");
foreach (var entry in NetworkManager.__rpc_func_table)
foreach (var entry in NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()])
{
Debug.Log($"{entry.Key} | {entry.Value.Method.Name}");
}
Original file line number Diff line number Diff line change
@@ -1012,4 +1012,20 @@ internal static void Read(FastBufferReader reader, ref T value)
Serializer.Read(reader, ref value);
}
}

// RuntimeAccessModifiersILPP will make this `public`
// This is just pass-through to NetworkVariableSerialization<T> but is here becaues I could not get ILPP
// to generate code that would successfully call Type<T>.Method(T), but it has no problem calling Type.Method<T>(T)
internal class RpcFallbackSerialization
{
public static void Write<T>(FastBufferWriter writer, ref T value)
{
NetworkVariableSerialization<T>.Write(writer, ref value);
}

public static void Read<T>(FastBufferReader reader, ref T value)
{
NetworkVariableSerialization<T>.Read(reader, ref value);
}
}
}
43 changes: 35 additions & 8 deletions com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs
Original file line number Diff line number Diff line change
@@ -12,9 +12,23 @@ namespace Unity.Netcode.RuntimeTests
{
public class RpcTests : NetcodeIntegrationTest
{
public class RpcTestNB : NetworkBehaviour
public class GenericRpcTestNB<T> : NetworkBehaviour where T: unmanaged
{
public event Action<T, ServerRpcParams> OnServer_Rpc;

[ServerRpc]
public void MyServerRpc(T clientId, ServerRpcParams param = default)
{
OnServer_Rpc(clientId, param);
}
}

public class RpcTestNBFloat : GenericRpcTestNB<float>
{
}

public class RpcTestNB : GenericRpcTestNB<ulong>
{
public event Action<ulong, ServerRpcParams> OnServer_Rpc;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public event Action<NativeList<ulong>, ServerRpcParams> OnNativeListServer_Rpc;
#endif
@@ -26,12 +40,6 @@ public class RpcTestNB : NetworkBehaviour

public event Action OnClient_Rpc;

[ServerRpc]
public void MyServerRpc(ulong clientId, ServerRpcParams param = default)
{
OnServer_Rpc(clientId, param);
}

#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
[ServerRpc]
public void MyNativeListServerRpc(NativeList<ulong> clientId, ServerRpcParams param = default)
@@ -67,19 +75,23 @@ public void MyTypedServerRpc(Vector3 param1, Vector3[] param2,
protected override void OnCreatePlayerPrefab()
{
m_PlayerPrefab.AddComponent<RpcTestNB>();
m_PlayerPrefab.AddComponent<RpcTestNBFloat>();
}

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

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

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

localClienRpcTestNBFloat.OnServer_Rpc += (clientId, param) =>
{
// The RPC invoked locally. (Weaver failure?)
Assert.Fail("ServerRpc (float) invoked locally. Weaver failure?");
};

serverClientRpcTestNB.OnServer_Rpc += (clientId, param) =>
{
Debug.Log("ServerRpc received on server object");
Assert.True(param.Receive.SenderClientId == clientId);
hasReceivedServerRpc = true;
};

serverClientRpcTestNBFloat.OnServer_Rpc += (clientId, param) =>
{
Debug.Log("ServerRpc (float) received on server object");
Assert.True(param.Receive.SenderClientId == clientId);
hasReceivedFloatServerRpc = true;
};

serverClientRpcTestNB.OnClient_Rpc += () =>
{
// The RPC invoked locally. (Weaver failure?)
@@ -145,6 +170,7 @@ public IEnumerator TestRpcs()

// Send ServerRpc
localClienRpcTestNB.MyServerRpc(m_ClientNetworkManagers[0].LocalClientId);
localClienRpcTestNBFloat.MyServerRpc(m_ClientNetworkManagers[0].LocalClientId);

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

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