Skip to content

Commit

Permalink
Support for generic NetworkBehaviour types
Browse files Browse the repository at this point in the history
  • Loading branch information
ShadauxCat committed Sep 14, 2023
1 parent ca577a6 commit d86c21d
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 112 deletions.
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
Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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))
{
Expand Down
44 changes: 42 additions & 2 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Unity.Collections;
using UnityEngine;


namespace Unity.Netcode
{
/// <summary>
Expand All @@ -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
{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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.
Expand All @@ -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();

{
Expand Down
15 changes: 0 additions & 15 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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}");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand All @@ -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?)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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");
Expand Down

0 comments on commit d86c21d

Please sign in to comment.