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
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 4 additions & 1 deletion com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Added `NetworkVariableBase.MarkNetworkBehaviourDirty` so that user-created network variable types can mark their containing `NetworkBehaviour` to be processed by the update loop. (#2694)

### Fixed

- Generic NetworkBehaviour types no longer result in compile errors or runtime errors (#2720)
- 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)
- Errors are no longer thrown when entering play mode with domain reload disabled (#2720)
- NetworkSpawn is now correctly called each time when entering play mode with scene reload disabled (#2720)
- 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)
- 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)
- Fixed issue where `SpawnWithObservers` was not being honored when `NetworkConfig.EnableSceneManagement` was disabled. (#2682)
Expand Down
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 @@ -53,6 +53,7 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
ProcessNetworkBehaviour(typeDefinition);
break;
case nameof(__RpcParams):
case nameof(RpcFallbackSerialization):
typeDefinition.IsPublic = true;
break;
}
Expand All @@ -79,6 +80,9 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
}

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

foreach (var fieldDefinition in typeDefinition.Fields)
Expand All @@ -124,6 +132,20 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
{
fieldDefinition.IsFamilyOrAssembly = true;
}
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_func_table))
{
fieldDefinition.IsFamilyOrAssembly = true;
}

if (fieldDefinition.Name == nameof(NetworkBehaviour.RpcReceiveHandler))
{
fieldDefinition.IsFamilyOrAssembly = true;
}

if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_name_table))
{
fieldDefinition.IsFamilyOrAssembly = true;
}
}

foreach (var methodDefinition in typeDefinition.Methods)
Expand All @@ -133,6 +155,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 @@ -582,6 +595,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 @@ -600,6 +632,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: 15 additions & 0 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ namespace Unity.Netcode
[AddComponentMenu("Netcode/Network Manager", -100)]
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
{
// TODO: Deprecate...
// The following internal values are not used, but because ILPP makes them public in the assembly, they cannot
// be removed thanks to our semver validation.
#pragma warning disable IDE1006 // disable naming rule violation check

// RuntimeAccessModifiersILPP will make this `public`
Expand Down Expand Up @@ -491,6 +494,15 @@ internal void OnValidate()
}
}
}

private void ModeChanged(PlayModeStateChange change)
{
if (IsListening && change == PlayModeStateChange.ExitingPlayMode)
{
// Make sure we are not holding onto anything in case domain reload is disabled
ShutdownInternal();
}
}
#endif

/// <summary>
Expand Down Expand Up @@ -539,6 +551,9 @@ private void Awake()
NetworkConfig?.InitializePrefabs();

UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
#if UNITY_EDITOR
EditorApplication.playModeStateChanged += ModeChanged;
#endif
}

private void OnEnable()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Unity.Netcode
{
Expand All @@ -13,5 +16,24 @@ internal struct ILPPMessageProvider : INetworkMessageProvider
{
return __network_message_types;
}

#if UNITY_EDITOR
[InitializeOnLoadMethod]
public static void NotifyOnPlayStateChange()
{
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}

public static void OnPlayModeStateChanged(PlayModeStateChange change)
{
if (change == PlayModeStateChange.ExitingPlayMode)
{
// Clear out the network message types, because ILPP-generated RuntimeInitializeOnLoad code will
// run again and add more messages to it.
__network_message_types.Clear();
}
}

#endif
}
}
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 @@ -1094,4 +1094,20 @@ internal static bool ClassEquals<TValueType>(ref TValueType a, ref TValueType b)
return a == b;
}
}

// 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
Loading