From 2354d7dcba0b2f9ff32abe9cf3878b607d033d0d Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 26 Oct 2023 15:19:07 -0500 Subject: [PATCH 01/13] feat: Added a universal RPC attribute The new attribute is more configurable at both compile time and runtime than the existing ClientRpc and ServerRpc attributes, and also includes support for clients sending to themselves, and for optionally delaying the invocation of local RPCs until the start of the next frame to enable mutually recursive client/server RPCs to function on a host the same as on other clients (as well as mutually recursive Owner/NotOwner RPCs and other similar patterns). This attribute supersedes ClientRpc and ServerRpc and will be the new default recommendation for creating RPCs. ClientRpc and ServerRpc will eventually be deprecated in favor of the new [Rpc] attribute. --- .../Editor/CodeGen/CodeGenHelpers.cs | 2 + .../Editor/CodeGen/NetworkBehaviourILPP.cs | 362 +++- .../CodeGen/RuntimeAccessModifiersILPP.cs | 10 + .../Connection/NetworkConnectionManager.cs | 71 +- .../Runtime/Core/NetworkBehaviour.cs | 129 +- .../Runtime/Core/NetworkManager.cs | 52 + .../Runtime/Core/NetworkObject.cs | 10 +- .../Messaging/DeferredMessageManager.cs | 2 +- .../IDeferredNetworkMessageManager.cs | 1 + .../Messages/AddObserverMessage.cs.meta | 3 + .../Messages/ClientConnectedMessage.cs | 32 + .../Messages/ClientConnectedMessage.cs.meta | 3 + .../Messages/ClientDisconnectedMessage.cs | 32 + .../ClientDisconnectedMessage.cs.meta | 3 + .../Messages/ConnectionApprovedMessage.cs | 43 +- .../Messaging/Messages/ProxyMessage.cs | 70 + .../Messaging/Messages/ProxyMessage.cs.meta | 3 + .../Runtime/Messaging/Messages/RpcMessages.cs | 38 + .../Messaging/NetworkMessageManager.cs | 14 +- .../Runtime/Messaging/RpcAttributes.cs | 44 +- .../Runtime/Messaging/RpcParams.cs | 55 + .../Runtime/Messaging/RpcTargets.meta | 3 + .../Messaging/RpcTargets/BaseRpcTarget.cs | 36 + .../RpcTargets/BaseRpcTarget.cs.meta | 2 + .../RpcTargets/ClientsAndHostRpcTarget.cs | 33 + .../ClientsAndHostRpcTarget.cs.meta | 3 + .../RpcTargets/DirectSendRpcTarget.cs | 29 + .../RpcTargets/DirectSendRpcTarget.cs.meta | 3 + .../Messaging/RpcTargets/EveryoneRpcTarget.cs | 26 + .../RpcTargets/EveryoneRpcTarget.cs.meta | 3 + .../Messaging/RpcTargets/IGroupRpcTarget.cs | 9 + .../RpcTargets/IGroupRpcTarget.cs.meta | 3 + .../RpcTargets/IIndividualRpcTarget.cs | 8 + .../RpcTargets/IIndividualRpcTarget.cs.meta | 3 + .../RpcTargets/LocalSendRpcTarget.cs | 67 + .../RpcTargets/LocalSendRpcTarget.cs.meta | 3 + .../Messaging/RpcTargets/NotMeRpcTarget.cs | 67 + .../RpcTargets/NotMeRpcTarget.cs.meta | 3 + .../Messaging/RpcTargets/NotOwnerRpcTarget.cs | 83 + .../RpcTargets/NotOwnerRpcTarget.cs.meta | 3 + .../RpcTargets/NotServerRpcTarget.cs | 72 + .../RpcTargets/NotServerRpcTarget.cs.meta | 3 + .../Messaging/RpcTargets/OwnerRpcTarget.cs | 54 + .../RpcTargets/OwnerRpcTarget.cs.meta | 3 + .../Messaging/RpcTargets/ProxyRpcTarget.cs | 16 + .../RpcTargets/ProxyRpcTarget.cs.meta | 3 + .../RpcTargets/ProxyRpcTargetGroup.cs | 99 ++ .../RpcTargets/ProxyRpcTargetGroup.cs.meta | 3 + .../Runtime/Messaging/RpcTargets/RpcTarget.cs | 294 ++++ .../Messaging/RpcTargets/RpcTarget.cs.meta | 3 + .../Messaging/RpcTargets/RpcTargetGroup.cs | 79 + .../RpcTargets/RpcTargetGroup.cs.meta | 3 + .../Messaging/RpcTargets/ServerRpcTarget.cs | 36 + .../RpcTargets/ServerRpcTarget.cs.meta | 3 + .../SceneManagement/NetworkSceneManager.cs | 14 + .../Runtime/Spawning/NetworkSpawnManager.cs | 10 +- .../Tests/Runtime/DeferredMessagingTests.cs | 4 + .../Runtime/InvalidConnectionEventsTest.cs | 10 +- .../Runtime/PeerDisconnectCallbackTests.cs | 181 ++ .../PeerDisconnectCallbackTests.cs.meta | 3 + .../Tests/Runtime/RpcTests.cs | 1 - .../Tests/Runtime/UniversalRpcTests.cs | 1550 +++++++++++++++++ .../Tests/Runtime/UniversalRpcTests.cs.meta | 3 + .../Assets/Scripts/CommandLineHandler.cs | 4 +- .../Assets/Scripts/ConnectionModeScript.cs | 25 +- .../Tests/Manual/Scripts/StatsDisplay.cs | 5 +- .../Runtime/MultiClientConnectionApproval.cs | 27 +- .../Runtime/RpcUserSerializableTypesTest.cs | 3 +- 68 files changed, 3784 insertions(+), 93 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AddObserverMessage.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/DirectSendRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/DirectSendRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IGroupRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IGroupRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IIndividualRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IIndividualRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTargetGroup.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTargetGroup.cs.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs index 1bb3ab1b16..6368e5e4ff 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs @@ -26,8 +26,10 @@ internal static class CodeGenHelpers public static readonly string INetworkMessage_FullName = typeof(INetworkMessage).FullName; public static readonly string ServerRpcAttribute_FullName = typeof(ServerRpcAttribute).FullName; public static readonly string ClientRpcAttribute_FullName = typeof(ClientRpcAttribute).FullName; + public static readonly string RpcAttribute_FullName = typeof(RpcAttribute).FullName; public static readonly string ServerRpcParams_FullName = typeof(ServerRpcParams).FullName; public static readonly string ClientRpcParams_FullName = typeof(ClientRpcParams).FullName; + public static readonly string RpcParams_FullName = typeof(RpcParams).FullName; public static readonly string ClientRpcSendParams_FullName = typeof(ClientRpcSendParams).FullName; public static readonly string ClientRpcReceiveParams_FullName = typeof(ClientRpcReceiveParams).FullName; public static readonly string ServerRpcSendParams_FullName = typeof(ServerRpcSendParams).FullName; diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index c15ea8a027..070b59b9f3 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -363,11 +363,14 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, private FieldReference m_NetworkManager_LogLevel_FieldRef; private MethodReference m_NetworkBehaviour___registerRpc_MethodRef; private TypeReference m_NetworkBehaviour_TypeRef; + private TypeReference m_AttributeParamsType_TypeRef; private TypeReference m_NetworkVariableBase_TypeRef; private MethodReference m_NetworkVariableBase_Initialize_MethodRef; private MethodReference m_NetworkBehaviour___nameNetworkVariable_MethodRef; private MethodReference m_NetworkBehaviour_beginSendServerRpc_MethodRef; private MethodReference m_NetworkBehaviour_endSendServerRpc_MethodRef; + private MethodReference m_NetworkBehaviour_beginSendRpc_MethodRef; + private MethodReference m_NetworkBehaviour_endSendRpc_MethodRef; private MethodReference m_NetworkBehaviour_beginSendClientRpc_MethodRef; private MethodReference m_NetworkBehaviour_endSendClientRpc_MethodRef; private FieldReference m_NetworkBehaviour_rpc_exec_stage_FieldRef; @@ -378,9 +381,13 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, private TypeReference m_RpcParams_TypeRef; private FieldReference m_RpcParams_Server_FieldRef; private FieldReference m_RpcParams_Client_FieldRef; + private FieldReference m_RpcParams_Ext_FieldRef; private TypeReference m_ServerRpcParams_TypeRef; private FieldReference m_ServerRpcParams_Receive_FieldRef; private FieldReference m_ServerRpcParams_Receive_SenderClientId_FieldRef; + private FieldReference m_UniversalRpcParams_Receive_FieldRef; + private FieldReference m_UniversalRpcParams_Receive_SenderClientId_FieldRef; + private TypeReference m_UniversalRpcParams_TypeRef; private TypeReference m_ClientRpcParams_TypeRef; private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef; private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyArray_MethodRef; @@ -495,6 +502,8 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, private const string k_NetworkBehaviour_NetworkVariableFields = nameof(NetworkBehaviour.NetworkVariableFields); private const string k_NetworkBehaviour_beginSendServerRpc = nameof(NetworkBehaviour.__beginSendServerRpc); private const string k_NetworkBehaviour_endSendServerRpc = nameof(NetworkBehaviour.__endSendServerRpc); + private const string k_NetworkBehaviour_beginSendRpc = nameof(NetworkBehaviour.__beginSendRpc); + private const string k_NetworkBehaviour_endSendRpc = nameof(NetworkBehaviour.__endSendRpc); private const string k_NetworkBehaviour_beginSendClientRpc = nameof(NetworkBehaviour.__beginSendClientRpc); private const string k_NetworkBehaviour_endSendClientRpc = nameof(NetworkBehaviour.__endSendClientRpc); private const string k_NetworkBehaviour___initializeVariables = nameof(NetworkBehaviour.__initializeVariables); @@ -511,8 +520,11 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, private const string k_ServerRpcAttribute_RequireOwnership = nameof(ServerRpcAttribute.RequireOwnership); private const string k_RpcParams_Server = nameof(__RpcParams.Server); private const string k_RpcParams_Client = nameof(__RpcParams.Client); + private const string k_RpcParams_Ext = nameof(__RpcParams.Ext); private const string k_ServerRpcParams_Receive = nameof(ServerRpcParams.Receive); + private const string k_RpcParams_Receive = nameof(RpcParams.Receive); private const string k_ServerRpcReceiveParams_SenderClientId = nameof(ServerRpcReceiveParams.SenderClientId); + private const string k_RpcReceiveParams_SenderClientId = nameof(RpcReceiveParams.SenderClientId); // CodeGen cannot reference the collections assembly to do a typeof() on it due to a bug that causes that to crash. private const string k_INativeListBool_FullName = "Unity.Collections.INativeList`1"; @@ -545,13 +557,20 @@ private bool ImportReferences(ModuleDefinition moduleDefinition, string[] assemb TypeDefinition rpcParamsTypeDef = null; TypeDefinition serverRpcParamsTypeDef = null; TypeDefinition clientRpcParamsTypeDef = null; + TypeDefinition universalRpcParamsTypeDef = null; TypeDefinition fastBufferWriterTypeDef = null; TypeDefinition fastBufferReaderTypeDef = null; TypeDefinition networkVariableSerializationTypesTypeDef = null; TypeDefinition bytePackerTypeDef = null; TypeDefinition byteUnpackerTypeDef = null; + TypeDefinition attributeParamsType = null; foreach (var netcodeTypeDef in m_NetcodeModule.GetAllTypes()) { + if (attributeParamsType == null && netcodeTypeDef.Name == nameof(RpcAttribute.RpcAttributeParams)) + { + attributeParamsType = netcodeTypeDef; + continue; + } if (networkManagerTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager)) { networkManagerTypeDef = netcodeTypeDef; @@ -588,6 +607,12 @@ private bool ImportReferences(ModuleDefinition moduleDefinition, string[] assemb continue; } + if (universalRpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(RpcParams)) + { + universalRpcParamsTypeDef = netcodeTypeDef; + continue; + } + if (clientRpcParamsTypeDef == null && netcodeTypeDef.Name == nameof(ClientRpcParams)) { clientRpcParamsTypeDef = netcodeTypeDef; @@ -662,6 +687,8 @@ private bool ImportReferences(ModuleDefinition moduleDefinition, string[] assemb } } + m_AttributeParamsType_TypeRef = moduleDefinition.ImportReference(attributeParamsType); + foreach (var fieldDef in networkManagerTypeDef.Fields) { switch (fieldDef.Name) @@ -696,6 +723,12 @@ private bool ImportReferences(ModuleDefinition moduleDefinition, string[] assemb case k_NetworkBehaviour_endSendServerRpc: m_NetworkBehaviour_endSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodDef); break; + case k_NetworkBehaviour_beginSendRpc: + m_NetworkBehaviour_beginSendRpc_MethodRef = moduleDefinition.ImportReference(methodDef); + break; + case k_NetworkBehaviour_endSendRpc: + m_NetworkBehaviour_endSendRpc_MethodRef = moduleDefinition.ImportReference(methodDef); + break; case k_NetworkBehaviour_beginSendClientRpc: m_NetworkBehaviour_beginSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodDef); break; @@ -763,6 +796,9 @@ private bool ImportReferences(ModuleDefinition moduleDefinition, string[] assemb case k_RpcParams_Client: m_RpcParams_Client_FieldRef = moduleDefinition.ImportReference(fieldDef); break; + case k_RpcParams_Ext: + m_RpcParams_Ext_FieldRef = moduleDefinition.ImportReference(fieldDef); + break; } } @@ -786,6 +822,26 @@ private bool ImportReferences(ModuleDefinition moduleDefinition, string[] assemb break; } } + m_UniversalRpcParams_TypeRef = moduleDefinition.ImportReference(rpcParamsTypeDef); + foreach (var fieldDef in rpcParamsTypeDef.Fields) + { + switch (fieldDef.Name) + { + case k_RpcParams_Receive: + foreach (var recvFieldDef in fieldDef.FieldType.Resolve().Fields) + { + switch (recvFieldDef.Name) + { + case k_RpcReceiveParams_SenderClientId: + m_UniversalRpcParams_Receive_SenderClientId_FieldRef = moduleDefinition.ImportReference(recvFieldDef); + break; + } + } + + m_UniversalRpcParams_Receive_FieldRef = moduleDefinition.ImportReference(fieldDef); + break; + } + } m_ClientRpcParams_TypeRef = moduleDefinition.ImportReference(clientRpcParamsTypeDef); m_FastBufferWriter_TypeRef = moduleDefinition.ImportReference(fastBufferWriterTypeDef); @@ -1352,7 +1408,8 @@ private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinitio var customAttributeType_FullName = customAttribute.AttributeType.FullName; if (customAttributeType_FullName == CodeGenHelpers.ServerRpcAttribute_FullName || - customAttributeType_FullName == CodeGenHelpers.ClientRpcAttribute_FullName) + customAttributeType_FullName == CodeGenHelpers.ClientRpcAttribute_FullName || + customAttributeType_FullName == CodeGenHelpers.RpcAttribute_FullName) { bool isValid = true; @@ -1387,6 +1444,13 @@ private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinitio isValid = false; } + if (customAttributeType_FullName == CodeGenHelpers.RpcAttribute_FullName && + !methodDefinition.Name.EndsWith("Rpc", StringComparison.OrdinalIgnoreCase)) + { + m_Diagnostics.AddError(methodDefinition, "Rpc method must end with 'Rpc' suffix!"); + isValid = false; + } + if (customAttributeType_FullName == CodeGenHelpers.ClientRpcAttribute_FullName && !methodDefinition.Name.EndsWith("ClientRpc", StringComparison.OrdinalIgnoreCase)) { @@ -1409,11 +1473,15 @@ private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinitio { if (methodDefinition.Name.EndsWith("ServerRpc", StringComparison.OrdinalIgnoreCase)) { - m_Diagnostics.AddError(methodDefinition, "ServerRpc method must be marked with 'ServerRpc' attribute!"); + m_Diagnostics.AddError(methodDefinition, $"ServerRpc method {methodDefinition} must be marked with 'ServerRpc' attribute!"); } else if (methodDefinition.Name.EndsWith("ClientRpc", StringComparison.OrdinalIgnoreCase)) { - m_Diagnostics.AddError(methodDefinition, "ClientRpc method must be marked with 'ClientRpc' attribute!"); + m_Diagnostics.AddError(methodDefinition, $"ClientRpc method {methodDefinition} must be marked with 'ClientRpc' attribute!"); + } + else if (methodDefinition.Name.EndsWith("ExtRpc", StringComparison.OrdinalIgnoreCase)) + { + m_Diagnostics.AddError(methodDefinition, $"Ext Rpc method {methodDefinition} must be marked with 'ExtRpc' attribute!"); } return null; @@ -1885,8 +1953,17 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA var instructions = new List(); var processor = methodDefinition.Body.GetILProcessor(); var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName; + var isClientRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ClientRpcAttribute_FullName; + var isGenericRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.RpcAttribute_FullName; var requireOwnership = true; // default value MUST be == `ServerRpcAttribute.RequireOwnership` var rpcDelivery = RpcDelivery.Reliable; // default value MUST be == `RpcAttribute.Delivery` + var defaultTarget = SendTo.Everyone; + var allowTargetOverride = false; + + if (isGenericRpc) + { + defaultTarget = (SendTo)rpcAttribute.ConstructorArguments[0].Value; + } foreach (var attrField in rpcAttribute.Fields) { switch (attrField.Name) @@ -1897,6 +1974,9 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA case k_ServerRpcAttribute_RequireOwnership: requireOwnership = attrField.Argument.Type == typeSystem.Boolean && (bool)attrField.Argument.Value; break; + case nameof(RpcAttribute.AllowTargetOverride): + allowTargetOverride = attrField.Argument.Type == typeSystem.Boolean && (bool)attrField.Argument.Value; + break; } } @@ -1904,7 +1984,33 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA var hasRpcParams = paramCount > 0 && ((isServerRpc && methodDefinition.Parameters[paramCount - 1].ParameterType.FullName == CodeGenHelpers.ServerRpcParams_FullName) || - (!isServerRpc && methodDefinition.Parameters[paramCount - 1].ParameterType.FullName == CodeGenHelpers.ClientRpcParams_FullName)); + (isClientRpc && methodDefinition.Parameters[paramCount - 1].ParameterType.FullName == CodeGenHelpers.ClientRpcParams_FullName) || + (isGenericRpc && methodDefinition.Parameters[paramCount - 1].ParameterType.FullName == CodeGenHelpers.RpcParams_FullName)); + + if (isGenericRpc && defaultTarget == SendTo.SpecifiedInParams) + { + if (!hasRpcParams) + { + m_Diagnostics.AddError($"{methodDefinition}: {nameof(SendTo)}.{nameof(SendTo.SpecifiedInParams)} cannot be used without a final parameter of type {CodeGenHelpers.RpcParams_FullName}."); + } + + foreach (var attrField in rpcAttribute.Fields) + { + switch (attrField.Name) + { + case nameof(RpcAttribute.AllowTargetOverride): + m_Diagnostics.AddWarning($"{methodDefinition}: {nameof(RpcAttribute.AllowTargetOverride)} is ignored with {nameof(SendTo)}.{nameof(SendTo.SpecifiedInParams)}"); + break; + } + } + } + if (isGenericRpc && allowTargetOverride) + { + if (!hasRpcParams) + { + m_Diagnostics.AddError($"{methodDefinition}: {nameof(RpcAttribute.AllowTargetOverride)} cannot be used without a final parameter of type {CodeGenHelpers.RpcParams_FullName}."); + } + } methodDefinition.Body.InitLocals = true; // NetworkManager networkManager; @@ -1917,10 +2023,17 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA // XXXRpcParams if (!hasRpcParams) { - methodDefinition.Body.Variables.Add(new VariableDefinition(isServerRpc ? m_ServerRpcParams_TypeRef : m_ClientRpcParams_TypeRef)); + methodDefinition.Body.Variables.Add(new VariableDefinition(isServerRpc ? m_ServerRpcParams_TypeRef : (isClientRpc ? m_ClientRpcParams_TypeRef : m_UniversalRpcParams_TypeRef))); } int rpcParamsIdx = !hasRpcParams ? methodDefinition.Body.Variables.Count - 1 : -1; + if (isGenericRpc) + { + methodDefinition.Body.Variables.Add(new VariableDefinition(m_AttributeParamsType_TypeRef)); + } + + int rpcAttributeParamsIdx = isGenericRpc ? methodDefinition.Body.Variables.Count - 1 : -1; + { var returnInstr = processor.Create(OpCodes.Ret); var lastInstr = processor.Create(OpCodes.Nop); @@ -1950,20 +2063,23 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA // if (__rpc_exec_stage != __RpcExecStage.Client) -> ClientRpc instructions.Add(processor.Create(OpCodes.Ldarg_0)); instructions.Add(processor.Create(OpCodes.Ldfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef)); - instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)(isServerRpc ? NetworkBehaviour.__RpcExecStage.Server : NetworkBehaviour.__RpcExecStage.Client))); + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.Execute)); instructions.Add(processor.Create(OpCodes.Ceq)); instructions.Add(processor.Create(OpCodes.Ldc_I4, 0)); instructions.Add(processor.Create(OpCodes.Ceq)); instructions.Add(processor.Create(OpCodes.Brfalse, lastInstr)); - // if (networkManager.IsClient || networkManager.IsHost) { ... } -> ServerRpc - // if (networkManager.IsServer || networkManager.IsHost) { ... } -> ClientRpc - instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); - instructions.Add(processor.Create(OpCodes.Callvirt, isServerRpc ? m_NetworkManager_getIsClient_MethodRef : m_NetworkManager_getIsServer_MethodRef)); - instructions.Add(processor.Create(OpCodes.Brtrue, beginInstr)); - instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkManager_getIsHost_MethodRef)); - instructions.Add(processor.Create(OpCodes.Brfalse, lastInstr)); + if (!isGenericRpc) + { + // if (networkManager.IsClient || networkManager.IsHost) { ... } -> ServerRpc + // if (networkManager.IsServer || networkManager.IsHost) { ... } -> ClientRpc + instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); + instructions.Add(processor.Create(OpCodes.Callvirt, isServerRpc ? m_NetworkManager_getIsClient_MethodRef : m_NetworkManager_getIsServer_MethodRef)); + instructions.Add(processor.Create(OpCodes.Brtrue, beginInstr)); + instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); + instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkManager_getIsHost_MethodRef)); + instructions.Add(processor.Create(OpCodes.Brfalse, lastInstr)); + } instructions.Add(beginInstr); @@ -2025,7 +2141,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_beginSendServerRpc_MethodRef)); instructions.Add(processor.Create(OpCodes.Stloc, bufWriterLocIdx)); } - else + else if (isClientRpc) { // ClientRpc @@ -2045,6 +2161,89 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_beginSendClientRpc_MethodRef)); instructions.Add(processor.Create(OpCodes.Stloc, bufWriterLocIdx)); } + else + { + // Generic RPC + + // var bufferWriter = __beginSendRpc(rpcMethodId, rpcParams, rpcAttributeParams, defaultTarget, rpcDelivery); + instructions.Add(processor.Create(OpCodes.Ldarg_0)); + + // rpcMethodId + instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); + + // rpcParams + instructions.Add(hasRpcParams ? processor.Create(OpCodes.Ldarg, paramCount) : processor.Create(OpCodes.Ldloc, rpcParamsIdx)); + + // rpcAttributeParams + instructions.Add(processor.Create(OpCodes.Ldloca, rpcAttributeParamsIdx)); + instructions.Add(processor.Create(OpCodes.Initobj, m_AttributeParamsType_TypeRef)); + + RpcAttribute.RpcAttributeParams dflt = default; + foreach (var field in rpcAttribute.Fields) + { + var found = false; + foreach (var attrField in m_AttributeParamsType_TypeRef.Resolve().Fields) + { + if (attrField.Name == field.Name) + { + found = true; + var value = field.Argument.Value; + var paramField = dflt.GetType().GetField(attrField.Name); + if (value != paramField.GetValue(dflt)) + { + instructions.Add(processor.Create(OpCodes.Ldloca, rpcAttributeParamsIdx)); + var type = value.GetType(); + if (type == typeof(bool)) + { + instructions.Add(processor.Create(OpCodes.Ldc_I4, (bool)value ? 1 : 0)); + } + else if (type == typeof(short) || type == typeof(int) || type == typeof(ushort) + || type == typeof(byte) || type == typeof(sbyte) || type == typeof(char)) + { + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)value)); + } + else if (type == typeof(long) || type == typeof(ulong)) + { + instructions.Add(processor.Create(OpCodes.Ldc_I8, (long)value)); + } + else if (type == typeof(float)) + { + instructions.Add(processor.Create(OpCodes.Ldc_R8, (float)value)); + + } + else if (type == typeof(double)) + { + instructions.Add(processor.Create(OpCodes.Ldc_R8, (double)value)); + } + else + { + m_Diagnostics.AddError("Unsupported attribute parameter type."); + } + } + + instructions.Add(processor.Create(OpCodes.Stfld, m_MainModule.ImportReference(attrField))); + + break; + } + } + + if (!found) + { + m_Diagnostics.AddError($"{nameof(RpcAttribute)} contains field {field} which is not present in {nameof(RpcAttribute.RpcAttributeParams)}."); + } + } + instructions.Add(processor.Create(OpCodes.Ldloc, rpcAttributeParamsIdx)); + + // defaultTarget + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)defaultTarget)); + + // rpcDelivery + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery)); + + // __beginSendRpc + instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_beginSendRpc_MethodRef)); + instructions.Add(processor.Create(OpCodes.Stloc, bufWriterLocIdx)); + } // write method parameters into stream for (int paramIndex = 0; paramIndex < paramCount; ++paramIndex) @@ -2073,7 +2272,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA } if (!isServerRpc) { - m_Diagnostics.AddError($"ClientRpcs may not accept {nameof(ServerRpcParams)} as a parameter."); + m_Diagnostics.AddError($"Only ServerRpcs may accept {nameof(ServerRpcParams)} as a parameter."); } continue; } @@ -2084,9 +2283,22 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA { m_Diagnostics.AddError(methodDefinition, $"{nameof(ClientRpcParams)} must be the last parameter in a ClientRpc."); } - if (isServerRpc) + if (!isClientRpc) { - m_Diagnostics.AddError($"ServerRpcs may not accept {nameof(ClientRpcParams)} as a parameter."); + m_Diagnostics.AddError($"Only clientRpcs may accept {nameof(ClientRpcParams)} as a parameter."); + } + continue; + } + // RpcParams + if (paramType.FullName == CodeGenHelpers.RpcParams_FullName) + { + if (paramIndex != paramCount - 1) + { + m_Diagnostics.AddError(methodDefinition, $"{nameof(RpcParams)} must be the last parameter in a ClientRpc."); + } + if (!isGenericRpc) + { + m_Diagnostics.AddError($"Only Rpcs may accept {nameof(RpcParams)} as a parameter."); } continue; } @@ -2249,7 +2461,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA // __endSendServerRpc instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_endSendServerRpc_MethodRef)); } - else + else if (isClientRpc) { // ClientRpc @@ -2277,6 +2489,41 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA // __endSendClientRpc instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_endSendClientRpc_MethodRef)); } + else + { + // Generic Rpc + + // __endSendRpc(ref bufferWriter, rpcMethodId, rpcParams, rpcAttributeParams, defaultTarget, rpcDelivery); + instructions.Add(processor.Create(OpCodes.Ldarg_0)); + + // bufferWriter + instructions.Add(processor.Create(OpCodes.Ldloca, bufWriterLocIdx)); + + // rpcMethodId + instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); + if (hasRpcParams) + { + // rpcParams + instructions.Add(processor.Create(OpCodes.Ldarg, paramCount)); + } + else + { + // default + instructions.Add(processor.Create(OpCodes.Ldloc, rpcParamsIdx)); + } + + // rpcAttributeParams + instructions.Add(processor.Create(OpCodes.Ldloc, rpcAttributeParamsIdx)); + + // defaultTarget + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)defaultTarget)); + + // rpcDelivery + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery)); + + // __endSendClientRpc + instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_endSendRpc_MethodRef)); + } instructions.Add(lastInstr); } @@ -2285,25 +2532,53 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA var returnInstr = processor.Create(OpCodes.Ret); var lastInstr = processor.Create(OpCodes.Nop); - // if (__rpc_exec_stage == __RpcExecStage.Server) -> ServerRpc - // if (__rpc_exec_stage == __RpcExecStage.Client) -> ClientRpc - instructions.Add(processor.Create(OpCodes.Ldarg_0)); - instructions.Add(processor.Create(OpCodes.Ldfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef)); - instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)(isServerRpc ? NetworkBehaviour.__RpcExecStage.Server : NetworkBehaviour.__RpcExecStage.Client))); - instructions.Add(processor.Create(OpCodes.Ceq)); - instructions.Add(processor.Create(OpCodes.Brfalse, returnInstr)); + if (!isGenericRpc) + { + // if (__rpc_exec_stage == __RpcExecStage.Execute) + instructions.Add(processor.Create(OpCodes.Ldarg_0)); + instructions.Add(processor.Create(OpCodes.Ldfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef)); + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.Execute)); + instructions.Add(processor.Create(OpCodes.Ceq)); + instructions.Add(processor.Create(OpCodes.Brfalse, returnInstr)); + + // if (networkManager.IsServer || networkManager.IsHost) -> ServerRpc + // if (networkManager.IsClient || networkManager.IsHost) -> ClientRpc + instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); + instructions.Add(processor.Create(OpCodes.Callvirt, isServerRpc ? m_NetworkManager_getIsServer_MethodRef : m_NetworkManager_getIsClient_MethodRef)); + instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr)); + instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); + instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkManager_getIsHost_MethodRef)); + instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr)); + instructions.Add(returnInstr); + instructions.Add(lastInstr); + + // This needs to be set back before executing the callback or else sending another RPC + // from within an RPC will not work. + // __rpc_exec_stage = __RpcExecStage.Send + instructions.Add(processor.Create(OpCodes.Ldarg_0)); + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.Send)); + instructions.Add(processor.Create(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef)); + } + else + { + // if (__rpc_exec_stage == __RpcExecStage.Execute) + instructions.Add(processor.Create(OpCodes.Ldarg_0)); + instructions.Add(processor.Create(OpCodes.Ldfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef)); + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.Execute)); + instructions.Add(processor.Create(OpCodes.Ceq)); + instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr)); - // if (networkManager.IsServer || networkManager.IsHost) -> ServerRpc - // if (networkManager.IsClient || networkManager.IsHost) -> ClientRpc - instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); - instructions.Add(processor.Create(OpCodes.Callvirt, isServerRpc ? m_NetworkManager_getIsServer_MethodRef : m_NetworkManager_getIsClient_MethodRef)); - instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr)); - instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); - instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkManager_getIsHost_MethodRef)); - instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr)); + instructions.Add(returnInstr); + instructions.Add(lastInstr); + + // This needs to be set back before executing the callback or else sending another RPC + // from within an RPC will not work. + // __rpc_exec_stage = __RpcExecStage.Send + instructions.Add(processor.Create(OpCodes.Ldarg_0)); + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.Send)); + instructions.Add(processor.Create(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef)); + } - instructions.Add(returnInstr); - instructions.Add(lastInstr); } instructions.Reverse(); @@ -2456,6 +2731,8 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition var processor = rpcHandler.Body.GetILProcessor(); var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName; + var isCientRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ClientRpcAttribute_FullName; + var isGenericRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.RpcAttribute_FullName; var requireOwnership = true; // default value MUST be == `ServerRpcAttribute.RequireOwnership` foreach (var attrField in rpcAttribute.Fields) { @@ -2562,6 +2839,15 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition processor.Emit(OpCodes.Stloc, localIndex); continue; } + + // RpcParams + if (paramType.FullName == CodeGenHelpers.RpcParams_FullName) + { + processor.Emit(OpCodes.Ldarg_2); + processor.Emit(OpCodes.Ldfld, m_RpcParams_Ext_FieldRef); + processor.Emit(OpCodes.Stloc, localIndex); + continue; + } } Instruction jumpInstruction = null; @@ -2688,7 +2974,7 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition // NetworkBehaviour.__rpc_exec_stage = __RpcExecStage.Server; -> ServerRpc // NetworkBehaviour.__rpc_exec_stage = __RpcExecStage.Client; -> ClientRpc processor.Emit(OpCodes.Ldarg_0); - processor.Emit(OpCodes.Ldc_I4, (int)(isServerRpc ? NetworkBehaviour.__RpcExecStage.Server : NetworkBehaviour.__RpcExecStage.Client)); + processor.Emit(OpCodes.Ldc_I4, (int)(NetworkBehaviour.__RpcExecStage.Execute)); processor.Emit(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef); // NetworkBehaviour.XXXRpc(...); @@ -2711,7 +2997,7 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition // NetworkBehaviour.__rpc_exec_stage = __RpcExecStage.None; processor.Emit(OpCodes.Ldarg_0); - processor.Emit(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.None); + processor.Emit(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.Send); processor.Emit(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef); processor.Emit(OpCodes.Ret); diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs index 0ef4f2cf76..7af8fed65c 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs @@ -2,6 +2,7 @@ using System.IO; using Mono.Cecil; using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; using Unity.CompilationPipeline.Common.Diagnostics; using Unity.CompilationPipeline.Common.ILPostProcessing; using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor; @@ -52,6 +53,15 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) case nameof(NetworkBehaviour): ProcessNetworkBehaviour(typeDefinition); break; + case nameof(RpcAttribute): + foreach (var methodDefinition in typeDefinition.GetConstructors()) + { + if (methodDefinition.Parameters.Count == 0) + { + methodDefinition.IsPublic = true; + } + } + break; case nameof(__RpcParams): case nameof(RpcFallbackSerialization): typeDefinition.IsPublic = true; diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index ccbe7756bf..fa85535306 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -42,8 +42,21 @@ public sealed class NetworkConnectionManager /// public event Action OnClientDisconnectCallback = null; + /// + /// The callback to invoke once a peer connects. This callback is only ran on the server and on the local client that connects. + /// + public event Action OnPeerConnectedCallback = null; + + /// + /// The callback to invoke when a peer disconnects. This callback is only ran on the server and on the local client that disconnects. + /// + public event Action OnPeerDisconnectCallback = null; + internal void InvokeOnClientConnectedCallback(ulong clientId) => OnClientConnectedCallback?.Invoke(clientId); + internal void InvokeOnPeerConnectedCallback(ulong clientId) => OnPeerConnectedCallback?.Invoke(clientId); + internal void InvokeOnPeerDisconnectedCallback(ulong clientId) => OnPeerDisconnectCallback?.Invoke(clientId); + /// /// The callback to invoke if the fails. /// @@ -72,6 +85,7 @@ public sealed class NetworkConnectionManager internal Dictionary TransportIdToClientIdMap = new Dictionary(); internal List ConnectedClientsList = new List(); internal List ConnectedClientIds = new List(); + internal HashSet KnownClientIds = new HashSet(); internal Action ConnectionApprovalCallback; /// @@ -364,6 +378,18 @@ internal void DisconnectEventHandler(ulong transportClientId) Debug.LogException(exception); } + if (LocalClient.IsHost) + { + try + { + OnPeerDisconnectCallback?.Invoke(clientId); + } + catch (Exception exception) + { + Debug.LogException(exception); + } + } + if (LocalClient.IsServer) { OnClientDisconnectFromServer(clientId); @@ -623,8 +649,17 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne var message = new ConnectionApprovedMessage { OwnerClientId = ownerClientId, - NetworkTick = NetworkManager.LocalTime.Tick + NetworkTick = NetworkManager.LocalTime.Tick, + KnownClientIds = new NativeArray(KnownClientIds.Count, Allocator.Temp) }; + + var i = 0; + foreach (var clientId in KnownClientIds) + { + message.KnownClientIds[i] = clientId; + ++i; + } + if (!NetworkManager.NetworkConfig.EnableSceneManagement) { // Update the observed spawned NetworkObjects for the newly connected player when scene management is disabled @@ -651,12 +686,17 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); message.MessageVersions.Dispose(); + message.KnownClientIds.Dispose(); // If scene management is disabled, then we are done and notify the local host-server the client is connected if (!NetworkManager.NetworkConfig.EnableSceneManagement) { NetworkManager.ConnectedClients[ownerClientId].IsConnected = true; InvokeOnClientConnectedCallback(ownerClientId); + if (LocalClient.IsHost) + { + InvokeOnPeerConnectedCallback(ownerClientId); + } } else // Otherwise, let NetworkSceneManager handle the initial scene and NetworkObject synchronization { @@ -740,7 +780,10 @@ internal NetworkClient AddClient(ulong clientId) ConnectedClients.Add(clientId, networkClient); ConnectedClientsList.Add(networkClient); + var message = new ClientConnectedMessage { ClientId = clientId }; + NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); ConnectedClientIds.Add(clientId); + KnownClientIds.Add(clientId); return networkClient; } @@ -827,7 +870,14 @@ internal void OnClientDisconnectFromServer(ulong clientId) // TODO: Could(should?) be replaced with more memory per client, by storing the visibility foreach (var sobj in NetworkManager.SpawnManager.SpawnedObjectsList) { - sobj.Observers.Remove(clientId); + if (sobj.Observers.Contains(clientId)) + { + sobj.Observers.Remove(clientId); + /*foreach (var behaviour in sobj.ChildNetworkBehaviours) + { + behaviour.Observers.Remove(clientId); + }*/ + } } if (ConnectedClients.ContainsKey(clientId)) @@ -837,6 +887,9 @@ internal void OnClientDisconnectFromServer(ulong clientId) } ConnectedClientIds.Remove(clientId); + KnownClientIds.Remove(clientId); + var message = new ClientDisconnectedMessage { ClientId = clientId }; + NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); } // If the client ID transport map exists @@ -854,6 +907,18 @@ internal void OnClientDisconnectFromServer(ulong clientId) Debug.LogException(exception); } + if (LocalClient.IsHost) + { + try + { + OnPeerDisconnectCallback?.Invoke(clientId); + } + catch (Exception exception) + { + Debug.LogException(exception); + } + } + // Clean up the transport to client (and vice versa) mappings TransportIdCleanUp(transportId); } @@ -913,6 +978,7 @@ internal void Initialize(NetworkManager networkManager) ConnectedClients.Clear(); ConnectedClientsList.Clear(); ConnectedClientIds.Clear(); + KnownClientIds.Clear(); ClientIdToTransportIdMap.Clear(); TransportIdToClientIdMap.Clear(); ClientsToApprove.Clear(); @@ -935,6 +1001,7 @@ internal void Shutdown() { LocalClient.IsApproved = false; LocalClient.IsConnected = false; + KnownClientIds.Clear(); if (LocalClient.IsServer) { // make sure all messages are flushed before transport disconnect clients diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index e0d0f21bef..f30d924675 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -6,6 +6,14 @@ namespace Unity.Netcode { + public class RpcException : Exception + { + public RpcException(string message) : base(message) + { + + } + } + /// /// The base class to override to write network code. Inherits MonoBehaviour /// @@ -27,16 +35,22 @@ public abstract class NetworkBehaviour : MonoBehaviour // RuntimeAccessModifiersILPP will make this `protected` internal enum __RpcExecStage { + // Technically will overlap with None and Server + // but it doesn't matter since we don't use None and Server + Send = 0, + Execute = 1, + + // Backward compatibility, not used... None = 0, Server = 1, - Client = 2 + Client = 2, } // NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type internal virtual string __getTypeName() => nameof(NetworkBehaviour); [NonSerialized] // RuntimeAccessModifiersILPP will make this `protected` - internal __RpcExecStage __rpc_exec_stage = __RpcExecStage.None; + internal __RpcExecStage __rpc_exec_stage = __RpcExecStage.Send; #pragma warning restore IDE1006 // restore naming rule violation check private const int k_RpcMessageDefaultSize = 1024; // 1k @@ -284,6 +298,99 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth #endif } + +#pragma warning disable IDE1006 // disable naming rule violation check + // RuntimeAccessModifiersILPP will make this `protected` + internal FastBufferWriter __beginSendRpc(uint rpcMethodId, RpcParams rpcParams, RpcAttribute.RpcAttributeParams attributeParams, SendTo defaultTarget, RpcDelivery rpcDelivery) +#pragma warning restore IDE1006 // restore naming rule violation check + { + if (attributeParams.RequireOwnership && !IsOwner) + { + throw new RpcException("This RPC can only be sent by its owner."); + } + return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize); + } + +#pragma warning disable IDE1006 // disable naming rule violation check + // RuntimeAccessModifiersILPP will make this `protected` + internal void __endSendRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, RpcParams rpcParams, RpcAttribute.RpcAttributeParams attributeParams, SendTo defaultTarget, RpcDelivery rpcDelivery) +#pragma warning restore IDE1006 // restore naming rule violation check + { + var rpcMessage = new RpcMessage + { + Metadata = new RpcMetadata + { + NetworkObjectId = NetworkObjectId, + NetworkBehaviourId = NetworkBehaviourId, + NetworkRpcMethodId = rpcMethodId, + }, + SenderClientId = NetworkManager.LocalClientId, + WriteBuffer = bufferWriter + }; + + NetworkDelivery networkDelivery; + switch (rpcDelivery) + { + default: + case RpcDelivery.Reliable: + networkDelivery = NetworkDelivery.ReliableFragmentedSequenced; + break; + case RpcDelivery.Unreliable: + if (bufferWriter.Length > NetworkManager.MessageManager.NonFragmentedMessageMaxSize) + { + throw new OverflowException("RPC parameters are too large for unreliable delivery."); + } + networkDelivery = NetworkDelivery.Unreliable; + break; + } + + if (rpcParams.Send.Target == null) + { + switch (defaultTarget) + { + case SendTo.Everyone: + rpcParams.Send.Target = RpcTarget.Everyone; + break; + case SendTo.Owner: + rpcParams.Send.Target = RpcTarget.Owner; + break; + case SendTo.Server: + rpcParams.Send.Target = RpcTarget.Server; + break; + case SendTo.NotServer: + rpcParams.Send.Target = RpcTarget.NotServer; + break; + case SendTo.NotMe: + rpcParams.Send.Target = RpcTarget.NotMe; + break; + case SendTo.NotOwner: + rpcParams.Send.Target = RpcTarget.NotOwner; + break; + case SendTo.Me: + rpcParams.Send.Target = RpcTarget.Me; + break; + case SendTo.ClientsAndHost: + rpcParams.Send.Target = RpcTarget.ClientsAndHost; + break; + case SendTo.SpecifiedInParams: + throw new RpcException("This method requires a runtime-specified send target."); + } + } + else if (defaultTarget != SendTo.SpecifiedInParams && !attributeParams.AllowTargetOverride) + { + throw new RpcException("Target override is not allowed for this method."); + } + + if (rpcParams.Send.LocalDeferMode == LocalDeferMode.Default) + { + rpcParams.Send.LocalDeferMode = attributeParams.DeferLocal ? LocalDeferMode.Defer : LocalDeferMode.SendImmediate; + } + + rpcParams.Send.Target.Send(this, ref rpcMessage, networkDelivery, rpcParams); + + bufferWriter.Dispose(); + } + #pragma warning disable IDE1006 // disable naming rule violation check // RuntimeAccessModifiersILPP will make this `protected` internal static NativeList __createNativeList() where T : unmanaged @@ -315,6 +422,16 @@ public NetworkManager NetworkManager } } + /// + /// Provides access to the various targets at runtime, as well as + /// runtime-bound targets like , + /// , + /// , + /// , and + /// + /// + public RpcTarget RpcTarget => NetworkManager.RpcTarget; + /// /// If a NetworkObject is assigned, it will return whether or not this NetworkObject /// is the local player object. If no NetworkObject is assigned it will always return false. @@ -331,6 +448,11 @@ public NetworkManager NetworkManager /// public bool IsServer { get; private set; } + /// + /// Gets if the server (local or remote) is a host - i.e., also a client + /// + public bool ServerIsHost { get; private set; } + /// /// Gets if we are executing as client /// @@ -472,12 +594,13 @@ internal void UpdateNetworkProperties() IsHost = NetworkManager.IsListening && NetworkManager.IsHost; IsClient = NetworkManager.IsListening && NetworkManager.IsClient; IsServer = NetworkManager.IsListening && NetworkManager.IsServer; + ServerIsHost = NetworkManager.IsListening && NetworkManager.ServerIsHost; } } else // Shouldn't happen, but if so then set the properties to their default value; { OwnerClientId = NetworkObjectId = default; - IsOwnedByServer = IsOwner = IsHost = IsClient = IsServer = default; + IsOwnedByServer = IsOwner = IsHost = IsClient = IsServer = ServerIsHost = default; NetworkBehaviourId = default; } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 4270331e31..2d103be38e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Unity.Collections; using UnityEngine; #if UNITY_EDITOR using UnityEditor; @@ -42,6 +43,8 @@ public void NetworkUpdate(NetworkUpdateStage updateStage) ConnectionManager.ProcessPendingApprovals(); ConnectionManager.PollAndHandleNetworkEvents(); + DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnNextFrame, 0); + MessageManager.ProcessIncomingMessageQueue(); MessageManager.CleanupDisconnectedClients(); } @@ -106,6 +109,15 @@ public ulong LocalClientId /// public IReadOnlyList ConnectedClientsIds => IsServer ? ConnectionManager.ConnectedClientIds : throw new NotServerException($"{nameof(ConnectionManager.ConnectedClientIds)} should only be accessed on server."); + /// + /// Gets a list of just the IDs of all known clients. + /// This is accessible from client, server, and host. + /// Currently, this set contains the list of all client ids connected to the server, but this set + /// should be treated as the set of ids that a given process is allowed to send messages to, + /// and should not be depended on to always contain the full set of ids connected to the server in the future. + /// + public IReadOnlyCollection KnownClientIds => ConnectionManager.KnownClientIds; + /// /// Gets the local for this client. /// @@ -122,6 +134,11 @@ public ulong LocalClientId /// public bool IsServer => ConnectionManager.LocalClient.IsServer; + /// + /// Gets whether or not the current server (local or remote) is a host - i.e., also a client + /// + public bool ServerIsHost => ConnectionManager.KnownClientIds.Contains(ServerClientId); + /// /// Gets Whether or not a client is running /// @@ -225,6 +242,24 @@ public event Action OnClientDisconnectCallback remove => ConnectionManager.OnClientDisconnectCallback -= value; } + /// + /// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects. + /// + public event Action OnPeerConnectedCallback + { + add => ConnectionManager.OnPeerConnectedCallback += value; + remove => ConnectionManager.OnPeerConnectedCallback -= value; + } + + /// + /// The callback to invoke when a client disconnects. This callback is only ran on the server and on the local client that disconnects. + /// + public event Action OnPeerDisconnectCallback + { + add => ConnectionManager.OnPeerDisconnectCallback += value; + remove => ConnectionManager.OnPeerDisconnectCallback -= value; + } + /// /// The current host name we are connected to, used to validate certificate /// @@ -379,6 +414,16 @@ public NetworkPrefabHandler PrefabHandler internal IDeferredNetworkMessageManager DeferredMessageManager { get; private set; } + /// + /// Provides access to the various targets at runtime, as well as + /// runtime-bound targets like , + /// , + /// , + /// , and + /// + /// + public RpcTarget RpcTarget; + /// /// Gets the CustomMessagingManager for this NetworkManager /// @@ -714,6 +759,8 @@ internal void Initialize(bool server) DeferredMessageManager = ComponentFactory.Create(this); + RpcTarget = new RpcTarget(this); + CustomMessagingManager = new CustomMessagingManager(this); SceneManager = new NetworkSceneManager(this); @@ -913,7 +960,9 @@ public bool StartHost() private void HostServerInitialize() { LocalClientId = ServerClientId; + ConnectionManager.KnownClientIds.Add(ServerClientId); NetworkMetrics.SetConnectionId(LocalClientId); + MessageManager.SetLocalClientId(LocalClientId); if (NetworkConfig.ConnectionApproval && ConnectionApprovalCallback != null) { @@ -1021,6 +1070,9 @@ internal void ShutdownInternal() DeferredMessageManager?.CleanupAllTriggers(); CustomMessagingManager = null; + RpcTarget?.Dispose(); + RpcTarget = null; + BehaviourUpdater?.Shutdown(); BehaviourUpdater = null; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index f7d62176a8..c1159ee4bf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -518,7 +518,10 @@ public void NetworkShow(ulong clientId) return; } NetworkManager.SpawnManager.MarkObjectForShowingTo(this, clientId); - Observers.Add(clientId); + if (!Observers.Contains(clientId)) + { + Observers.Add(clientId); + } } @@ -613,7 +616,10 @@ public void NetworkHide(ulong clientId) { throw new VisibilityChangeException("The object is already hidden"); } - Observers.Remove(clientId); + if (Observers.Contains(clientId)) + { + Observers.Remove(clientId); + } var message = new DestroyObjectMessage { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs index 227b167428..728f0a7f64 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs @@ -113,6 +113,7 @@ public virtual void ProcessTriggers(IDeferredNetworkMessageManager.TriggerType t // processed before the object is fully spawned. This must be the last thing done in the spawn process. if (triggers.TryGetValue(key, out var triggerInfo)) { + triggers.Remove(key); foreach (var deferredMessage in triggerInfo.TriggerData) { // Reader will be disposed within HandleMessage @@ -120,7 +121,6 @@ public virtual void ProcessTriggers(IDeferredNetworkMessageManager.TriggerType t } triggerInfo.TriggerData.Dispose(); - triggers.Remove(key); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs index 1fc7a4bf67..0a9ec917f3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs @@ -6,6 +6,7 @@ internal enum TriggerType { OnSpawn, OnAddPrefab, + OnNextFrame, } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AddObserverMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AddObserverMessage.cs.meta new file mode 100644 index 0000000000..f7649ca357 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AddObserverMessage.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3653bc4a709247a3b84b79d4a758f9d5 +timeCreated: 1699310795 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs new file mode 100644 index 0000000000..ae4eb5b14e --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs @@ -0,0 +1,32 @@ +namespace Unity.Netcode +{ + internal struct ClientConnectedMessage : INetworkMessage, INetworkSerializeByMemcpy + { + public int Version => 0; + + public ulong ClientId; + + public void Serialize(FastBufferWriter writer, int targetVersion) + { + BytePacker.WriteValueBitPacked(writer, ClientId); + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.IsClient) + { + return false; + } + ByteUnpacker.ReadValueBitPacked(reader, out ClientId); + return true; + } + + public void Handle(ref NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + networkManager.ConnectionManager.KnownClientIds.Add(ClientId); + networkManager.ConnectionManager.InvokeOnPeerConnectedCallback(ClientId); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs.meta new file mode 100644 index 0000000000..7eafca155d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 158454105806474cba54a4ea5a0bfb12 +timeCreated: 1697836112 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs new file mode 100644 index 0000000000..369df19a5d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs @@ -0,0 +1,32 @@ +namespace Unity.Netcode +{ + internal struct ClientDisconnectedMessage : INetworkMessage, INetworkSerializeByMemcpy + { + public int Version => 0; + + public ulong ClientId; + + public void Serialize(FastBufferWriter writer, int targetVersion) + { + BytePacker.WriteValueBitPacked(writer, ClientId); + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) + { + var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.IsClient) + { + return false; + } + ByteUnpacker.ReadValueBitPacked(reader, out ClientId); + return true; + } + + public void Handle(ref NetworkContext context) + { + var networkManager = (NetworkManager)context.SystemOwner; + networkManager.ConnectionManager.KnownClientIds.Remove(ClientId); + networkManager.ConnectionManager.InvokeOnPeerDisconnectedCallback(ClientId); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs.meta new file mode 100644 index 0000000000..c639defb72 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8f91296c8e5f40b1a2a03d74a31526b6 +timeCreated: 1697836161 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 0a0b398c53..5933f358d1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -5,7 +5,8 @@ namespace Unity.Netcode { internal struct ConnectionApprovedMessage : INetworkMessage { - public int Version => 0; + private const int k_VersionAddClientIds = 1; + public int Version => k_VersionAddClientIds; public ulong OwnerClientId; public int NetworkTick; @@ -17,6 +18,8 @@ internal struct ConnectionApprovedMessage : INetworkMessage public NativeArray MessageVersions; + public NativeArray KnownClientIds; + public void Serialize(FastBufferWriter writer, int targetVersion) { // ============================================================ @@ -36,6 +39,11 @@ public void Serialize(FastBufferWriter writer, int targetVersion) BytePacker.WriteValueBitPacked(writer, OwnerClientId); BytePacker.WriteValueBitPacked(writer, NetworkTick); + if (targetVersion >= k_VersionAddClientIds) + { + writer.WriteValueSafe(KnownClientIds); + } + uint sceneObjectCount = 0; // When SpawnedObjectsList is not null then scene management is disabled. Provide a list of @@ -50,7 +58,10 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { if (sobj.SpawnWithObservers && (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId))) { - sobj.Observers.Add(OwnerClientId); + if (!sobj.Observers.Contains(OwnerClientId)) + { + sobj.Observers.Add(OwnerClientId); + } var sceneObject = sobj.GetMessageSceneObject(OwnerClientId); sceneObject.Serialize(writer); ++sceneObjectCount; @@ -106,6 +117,16 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId); ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick); + + if (receivedMessageVersion >= k_VersionAddClientIds) + { + reader.ReadValueSafe(out KnownClientIds, Allocator.TempJob); + } + else + { + KnownClientIds = new NativeArray(0, Allocator.TempJob); + } + m_ReceivedSceneObjectData = reader; return true; } @@ -114,6 +135,7 @@ public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; networkManager.LocalClientId = OwnerClientId; + networkManager.MessageManager.SetLocalClientId(networkManager.LocalClientId); networkManager.NetworkMetrics.SetConnectionId(networkManager.LocalClientId); var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, NetworkTick); @@ -126,6 +148,12 @@ public void Handle(ref NetworkContext context) // Stop the client-side approval timeout coroutine since we are approved. networkManager.ConnectionManager.StopClientApprovalCoroutine(); + networkManager.ConnectionManager.KnownClientIds.Clear(); + foreach (var clientId in KnownClientIds) + { + networkManager.ConnectionManager.KnownClientIds.Add(clientId); + } + // Only if scene management is disabled do we handle NetworkObject synchronization at this point if (!networkManager.NetworkConfig.EnableSceneManagement) { @@ -145,7 +173,18 @@ public void Handle(ref NetworkContext context) networkManager.IsConnectedClient = true; // When scene management is disabled we notify after everything is synchronized networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId); + + foreach (var clientId in KnownClientIds) + { + if (clientId == networkManager.LocalClientId) + { + continue; + } + networkManager.ConnectionManager.InvokeOnPeerConnectedCallback(clientId); + } } + + KnownClientIds.Dispose(); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs new file mode 100644 index 0000000000..f47237c8cb --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs @@ -0,0 +1,70 @@ +using System; +using Unity.Collections; + +namespace Unity.Netcode +{ + internal struct ProxyMessage : INetworkMessage + { + public NativeArray TargetClientIds; + public NetworkDelivery Delivery; + public RpcMessage WrappedMessage; + + // Version of ProxyMessage and RpcMessage must always match. + // If ProxyMessage needs to change, increment RpcMessage's version + public int Version => new RpcMessage().Version; + + public void Serialize(FastBufferWriter writer, int targetVersion) + { + writer.WriteValueSafe(TargetClientIds); + BytePacker.WriteValuePacked(writer, Delivery); + WrappedMessage.Serialize(writer, targetVersion); + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) + { + reader.ReadValueSafe(out TargetClientIds, Allocator.Temp); + ByteUnpacker.ReadValuePacked(reader, out Delivery); + WrappedMessage = new RpcMessage(); + WrappedMessage.Deserialize(reader, ref context, receivedMessageVersion); + return true; + } + + public unsafe void Handle(ref NetworkContext context) + { + + var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(WrappedMessage.Metadata.NetworkObjectId, out var networkObject)) + { + throw new InvalidOperationException($"An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs."); + } + + var observers = networkObject.Observers; + + var nonServerIds = new NativeList(Allocator.Temp); + for (var i = 0; i < TargetClientIds.Length; ++i) + { + if (!observers.Contains(TargetClientIds[i])) + { + continue; + } + + if (TargetClientIds[i] == NetworkManager.ServerClientId) + { + WrappedMessage.Handle(ref context); + } + else + { + nonServerIds.Add(TargetClientIds[i]); + } + } + + WrappedMessage.WriteBuffer = new FastBufferWriter(WrappedMessage.ReadBuffer.Length, Allocator.Temp); + + using (WrappedMessage.WriteBuffer) + { + WrappedMessage.WriteBuffer.WriteBytesSafe(WrappedMessage.ReadBuffer.GetUnsafePtr(), WrappedMessage.ReadBuffer.Length); + networkManager.MessageManager.SendMessage(ref WrappedMessage, Delivery, nonServerIds); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs.meta new file mode 100644 index 0000000000..8f291e4f3b --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e9ee0457d5b740b38dfe6542658fb522 +timeCreated: 1697825043 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index 1798c5c0c5..e6961fbada 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -159,4 +159,42 @@ public void Handle(ref NetworkContext context) RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams); } } + + internal struct RpcMessage : INetworkMessage + { + public int Version => 0; + + public RpcMetadata Metadata; + public ulong SenderClientId; + + public FastBufferWriter WriteBuffer; + public FastBufferReader ReadBuffer; + + public unsafe void Serialize(FastBufferWriter writer, int targetVersion) + { + BytePacker.WriteValuePacked(writer, SenderClientId); + RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer); + } + + public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion) + { + ByteUnpacker.ReadValuePacked(reader, out SenderClientId); + return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer); + } + + public void Handle(ref NetworkContext context) + { + var rpcParams = new __RpcParams + { + Ext = new RpcParams + { + Receive = new RpcReceiveParams + { + SenderClientId = SenderClientId + } + } + }; + RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams); + } + } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index e66b54e4a4..3e5433f7ed 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -85,6 +85,8 @@ public SendQueueItem(NetworkDelivery delivery, int writerSize, Allocator writerA private INetworkMessageSender m_Sender; private bool m_Disposed; + private ulong m_LocalClientId; + internal Type[] MessageTypes => m_ReverseTypeMap; internal MessageHandler[] MessageHandlers => m_MessageHandlers; @@ -95,6 +97,16 @@ internal uint GetMessageType(Type t) return m_MessageTypes[t]; } + internal object GetOwner() + { + return m_Owner; + } + + internal void SetLocalClientId(ulong id) + { + m_LocalClientId = id; + } + public const int DefaultNonFragmentedMessageMaxSize = 1300 & ~7; // Round down to nearest word aligned size (1296) public int NonFragmentedMessageMaxSize = DefaultNonFragmentedMessageMaxSize; public int FragmentedMessageMaxSize = int.MaxValue; @@ -551,7 +563,7 @@ internal int GetMessageVersion(Type type, ulong clientId, bool forReceive = fals // Special cases because these are the messages that carry the version info - thus the version info isn't // populated yet when we get these. The first part of these messages always has to be the version data // and can't change. - if (typeof(T) != typeof(ConnectionRequestMessage) && typeof(T) != typeof(ConnectionApprovedMessage) && typeof(T) != typeof(DisconnectReasonMessage)) + if (typeof(T) != typeof(ConnectionRequestMessage) && typeof(T) != typeof(ConnectionApprovedMessage) && typeof(T) != typeof(DisconnectReasonMessage) && context.SenderId != manager.m_LocalClientId) { messageVersion = manager.GetMessageVersion(typeof(T), context.SenderId, true); if (messageVersion < 0) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs index 5d0a50178d..acb6289b5b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs @@ -21,12 +21,36 @@ public enum RpcDelivery /// /// Represents the common base class for Rpc attributes. /// - public abstract class RpcAttribute : Attribute + [AttributeUsage(AttributeTargets.Method)] + public class RpcAttribute : Attribute { + // Must match the set of parameters below + public struct RpcAttributeParams + { + public RpcDelivery Delivery; + public bool RequireOwnership; + public bool DeferLocal; + public bool AllowTargetOverride; + } + + // Must match the fields in RemoteAttributeParams /// /// Type of RPC delivery method /// public RpcDelivery Delivery = RpcDelivery.Reliable; + public bool RequireOwnership; + public bool DeferLocal; + public bool AllowTargetOverride; + + public RpcAttribute(SendTo target) + { + } + + // To get around an issue with the release validator, RuntimeAccessModifiersILPP will make this 'public' + private RpcAttribute() + { + + } } /// @@ -36,10 +60,12 @@ public abstract class RpcAttribute : Attribute [AttributeUsage(AttributeTargets.Method)] public class ServerRpcAttribute : RpcAttribute { - /// - /// Whether or not the ServerRpc should only be run if executed by the owner of the object - /// - public bool RequireOwnership = true; + public new bool RequireOwnership; + + public ServerRpcAttribute() : base(SendTo.Server) + { + + } } /// @@ -47,5 +73,11 @@ public class ServerRpcAttribute : RpcAttribute /// A ClientRpc marked method will be fired by the server but executed on clients. /// [AttributeUsage(AttributeTargets.Method)] - public class ClientRpcAttribute : RpcAttribute { } + public class ClientRpcAttribute : RpcAttribute + { + public ClientRpcAttribute() : base(SendTo.NotServer) + { + + } + } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs index ed49a0895f..7eb1a18009 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs @@ -3,6 +3,60 @@ namespace Unity.Netcode { + public enum LocalDeferMode + { + Default, + Defer, + SendImmediate + } + /// + /// Generic RPC + /// + public struct RpcSendParams + { + public BaseRpcTarget Target; + + public LocalDeferMode LocalDeferMode; + + public static implicit operator RpcSendParams(BaseRpcTarget target) => new RpcSendParams { Target = target }; + public static implicit operator RpcSendParams(LocalDeferMode deferMode) => new RpcSendParams { LocalDeferMode = deferMode }; + } + + /// + /// The receive parameters for server-side remote procedure calls + /// + public struct RpcReceiveParams + { + /// + /// Server-Side RPC + /// The client identifier of the sender + /// + public ulong SenderClientId; + } + + /// + /// Server-Side RPC + /// Can be used with any sever-side remote procedure call + /// Note: typically this is use primarily for the + /// + public struct RpcParams + { + /// + /// The server RPC send parameters (currently a place holder) + /// + public RpcSendParams Send; + + /// + /// The client RPC receive parameters provides you with the sender's identifier + /// + public RpcReceiveParams Receive; + + public static implicit operator RpcParams(RpcSendParams send) => new RpcParams { Send = send }; + public static implicit operator RpcParams(BaseRpcTarget target) => new RpcParams { Send = new RpcSendParams { Target = target } }; + public static implicit operator RpcParams(LocalDeferMode deferMode) => new RpcParams { Send = new RpcSendParams { LocalDeferMode = deferMode } }; + public static implicit operator RpcParams(RpcReceiveParams receive) => new RpcParams { Receive = receive }; + } + /// /// Server-Side RPC /// Place holder. @@ -99,6 +153,7 @@ public struct ClientRpcParams internal struct __RpcParams #pragma warning restore IDE1006 // restore naming rule violation check { + public RpcParams Ext; public ServerRpcParams Server; public ClientRpcParams Client; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets.meta new file mode 100644 index 0000000000..06237e7527 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b02186acd1144e20acbd0dcb69b14938 +timeCreated: 1697824888 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs new file mode 100644 index 0000000000..2a74252706 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs @@ -0,0 +1,36 @@ +namespace Unity.Netcode +{ + public abstract class BaseRpcTarget + { + protected NetworkManager m_NetworkManager; + + internal BaseRpcTarget(NetworkManager manager) + { + m_NetworkManager = manager; + } + + public abstract void Dispose(); + + internal abstract void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams); + + private protected void SendMessageToClient(NetworkBehaviour behaviour, ulong clientId, ref RpcMessage message, NetworkDelivery delivery) + { +#if DEVELOPMENT_BUILD || UNITY_EDITOR + var size = +#endif + behaviour.NetworkManager.MessageManager.SendMessage(ref message, delivery, clientId); + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) + { + behaviour.NetworkManager.NetworkMetrics.TrackRpcSent( + clientId, + behaviour.NetworkObject, + rpcMethodName, + behaviour.__getTypeName(), + size); + } +#endif + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs.meta new file mode 100644 index 0000000000..420764f6d8 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 07c2620262e24eb5a426b521c09b3091 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs new file mode 100644 index 0000000000..5d5585c0b6 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs @@ -0,0 +1,33 @@ +namespace Unity.Netcode +{ + internal class ClientsAndHostRpcTarget : BaseRpcTarget + { + private BaseRpcTarget m_UnderlyingTarget; + + public override void Dispose() + { + m_UnderlyingTarget = null; + } + + internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) + { + if (m_UnderlyingTarget == null) + { + if (behaviour.NetworkManager.ConnectionManager.KnownClientIds.Contains(NetworkManager.ServerClientId)) + { + m_UnderlyingTarget = behaviour.RpcTarget.Everyone; + } + else + { + m_UnderlyingTarget = behaviour.RpcTarget.NotServer; + } + } + + m_UnderlyingTarget.Send(behaviour, ref message, delivery, rpcParams); + } + + internal ClientsAndHostRpcTarget(NetworkManager manager) : base(manager) + { + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs.meta new file mode 100644 index 0000000000..1f79dbedc2 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c9f883d678ec4715b160dd9497d5f42d +timeCreated: 1699481382 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/DirectSendRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/DirectSendRpcTarget.cs new file mode 100644 index 0000000000..9cdaafccb1 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/DirectSendRpcTarget.cs @@ -0,0 +1,29 @@ +namespace Unity.Netcode +{ + internal class DirectSendRpcTarget : BaseRpcTarget, IIndividualRpcTarget + { + public BaseRpcTarget Target => this; + + internal ulong ClientId; + + public override void Dispose() + { + + } + + internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) + { + SendMessageToClient(behaviour, ClientId, ref message, delivery); + } + + public void SetClientId(ulong clientId) + { + ClientId = clientId; + } + + internal DirectSendRpcTarget(NetworkManager manager) : base(manager) + { + + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/DirectSendRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/DirectSendRpcTarget.cs.meta new file mode 100644 index 0000000000..f1ea3b718b --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/DirectSendRpcTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 077544cfd0b94cfc8a2a55d3828b74bb +timeCreated: 1697824873 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs new file mode 100644 index 0000000000..b0ae7382c7 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs @@ -0,0 +1,26 @@ +namespace Unity.Netcode +{ + internal class EveryoneRpcTarget : BaseRpcTarget + { + private NotServerRpcTarget m_NotServerRpcTarget; + private ServerRpcTarget m_ServerRpcTarget; + + public override void Dispose() + { + m_NotServerRpcTarget.Dispose(); + m_ServerRpcTarget.Dispose(); + } + + internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) + { + m_NotServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + } + + internal EveryoneRpcTarget(NetworkManager manager) : base(manager) + { + m_NotServerRpcTarget = new NotServerRpcTarget(manager); + m_ServerRpcTarget = new ServerRpcTarget(manager); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs.meta new file mode 100644 index 0000000000..18b22632ba --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/EveryoneRpcTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 675d4a5c79fc47078092ac15d255745d +timeCreated: 1697824941 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IGroupRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IGroupRpcTarget.cs new file mode 100644 index 0000000000..88aac5073e --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IGroupRpcTarget.cs @@ -0,0 +1,9 @@ +namespace Unity.Netcode +{ + internal interface IGroupRpcTarget + { + void Add(ulong clientId); + void Clear(); + BaseRpcTarget Target { get; } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IGroupRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IGroupRpcTarget.cs.meta new file mode 100644 index 0000000000..769ed8b350 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IGroupRpcTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: beb19a6bb1334252a89b21c8490f7cbe +timeCreated: 1697825109 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IIndividualRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IIndividualRpcTarget.cs new file mode 100644 index 0000000000..0e654a8328 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IIndividualRpcTarget.cs @@ -0,0 +1,8 @@ +namespace Unity.Netcode +{ + internal interface IIndividualRpcTarget + { + void SetClientId(ulong clientId); + BaseRpcTarget Target { get; } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IIndividualRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IIndividualRpcTarget.cs.meta new file mode 100644 index 0000000000..f3fa862daa --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/IIndividualRpcTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c658d9641f564d9890bef4f558f1cea6 +timeCreated: 1697825115 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs new file mode 100644 index 0000000000..92e2083163 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs @@ -0,0 +1,67 @@ +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace Unity.Netcode +{ + internal class LocalSendRpcTarget : BaseRpcTarget + { + public override void Dispose() + { + + } + + internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) + { + var networkManager = behaviour.NetworkManager; + var context = new NetworkContext + { + SenderId = m_NetworkManager.LocalClientId, + Timestamp = networkManager.RealTimeProvider.RealTimeSinceStartup, + SystemOwner = networkManager, + // header information isn't valid since it's not a real message. + // RpcMessage doesn't access this stuff so it's just left empty. + Header = new NetworkMessageHeader(), + SerializedHeaderSize = 0, + MessageSize = 0 + }; + int length; + if (rpcParams.Send.LocalDeferMode == LocalDeferMode.Defer) + { + using var serializedWriter = new FastBufferWriter(message.WriteBuffer.Length + UnsafeUtility.SizeOf(), Allocator.Temp, int.MaxValue); + message.Serialize(serializedWriter, message.Version); + using var reader = new FastBufferReader(serializedWriter, Allocator.None); + context.Header = new NetworkMessageHeader + { + MessageSize = (uint)reader.Length, + MessageType = m_NetworkManager.MessageManager.GetMessageType(typeof(RpcMessage)) + }; + + behaviour.NetworkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnNextFrame, 0, reader, ref context); + length = reader.Length; + } + else + { + using var tempBuffer = new FastBufferReader(message.WriteBuffer, Allocator.None); + message.ReadBuffer = tempBuffer; + message.Handle(ref context); + length = tempBuffer.Length; + } +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) + { + behaviour.NetworkManager.NetworkMetrics.TrackRpcSent( + behaviour.NetworkManager.LocalClientId, + behaviour.NetworkObject, + rpcMethodName, + behaviour.__getTypeName(), + length); + } +#endif + } + + internal LocalSendRpcTarget(NetworkManager manager) : base(manager) + { + + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs.meta new file mode 100644 index 0000000000..1b659b937b --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c3b290cdc20d4d2293652ec79652962a +timeCreated: 1697824985 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs new file mode 100644 index 0000000000..9f500b00d7 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs @@ -0,0 +1,67 @@ +namespace Unity.Netcode +{ + internal class NotMeRpcTarget : BaseRpcTarget + { + private IGroupRpcTarget m_GroupSendTarget; + private ServerRpcTarget m_ServerRpcTarget; + + public override void Dispose() + { + m_ServerRpcTarget.Dispose(); + if (m_GroupSendTarget != null) + { + m_GroupSendTarget.Target.Dispose(); + m_GroupSendTarget = null; + } + } + + internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) + { + if (m_GroupSendTarget == null) + { + if (behaviour.IsServer) + { + m_GroupSendTarget = new RpcTargetGroup(m_NetworkManager); + } + else + { + m_GroupSendTarget = new ProxyRpcTargetGroup(m_NetworkManager); + } + } + + m_GroupSendTarget.Clear(); + if (behaviour.IsServer) + { + foreach (var clientId in behaviour.NetworkObject.Observers) + { + if (clientId == behaviour.NetworkManager.LocalClientId) + { + continue; + } + m_GroupSendTarget.Add(clientId); + } + } + else + { + foreach (var clientId in m_NetworkManager.KnownClientIds) + { + if (clientId == behaviour.NetworkManager.LocalClientId) + { + continue; + } + m_GroupSendTarget.Add(clientId); + } + } + m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams); + if (!behaviour.IsServer) + { + m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + } + } + + internal NotMeRpcTarget(NetworkManager manager) : base(manager) + { + m_ServerRpcTarget = new ServerRpcTarget(manager); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs.meta new file mode 100644 index 0000000000..75100dad1a --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 99cd5e8be7bd454bab700ee08b8dad7b +timeCreated: 1697824966 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs new file mode 100644 index 0000000000..114a499fb7 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs @@ -0,0 +1,83 @@ +namespace Unity.Netcode +{ + internal class NotOwnerRpcTarget : BaseRpcTarget + { + private IGroupRpcTarget m_GroupSendTarget; + private ServerRpcTarget m_ServerRpcTarget; + private LocalSendRpcTarget m_LocalSendRpcTarget; + + public override void Dispose() + { + m_ServerRpcTarget.Dispose(); + m_LocalSendRpcTarget.Dispose(); + if (m_GroupSendTarget != null) + { + m_GroupSendTarget.Target.Dispose(); + m_GroupSendTarget = null; + } + } + + internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) + { + if (m_GroupSendTarget == null) + { + if (behaviour.IsServer) + { + m_GroupSendTarget = new RpcTargetGroup(m_NetworkManager); + } + else + { + m_GroupSendTarget = new ProxyRpcTargetGroup(m_NetworkManager); + } + } + m_GroupSendTarget.Clear(); + + if (behaviour.IsServer) + { + foreach (var clientId in behaviour.NetworkObject.Observers) + { + if (clientId == behaviour.OwnerClientId) + { + continue; + } + if (clientId == behaviour.NetworkManager.LocalClientId) + { + m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + continue; + } + + m_GroupSendTarget.Add(clientId); + } + } + else + { + foreach (var clientId in m_NetworkManager.KnownClientIds) + { + if (clientId == behaviour.OwnerClientId) + { + continue; + } + if (clientId == behaviour.NetworkManager.LocalClientId) + { + m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + continue; + } + + m_GroupSendTarget.Add(clientId); + } + } + + m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams); + if (behaviour.OwnerClientId != NetworkManager.ServerClientId) + { + m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + } + } + + internal NotOwnerRpcTarget(NetworkManager manager) : base(manager) + { + m_ServerRpcTarget = new ServerRpcTarget(manager); + m_LocalSendRpcTarget = new LocalSendRpcTarget(manager); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs.meta new file mode 100644 index 0000000000..09501cf8bc --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d7bc66c5253b44d09ad978ea9e51c96f +timeCreated: 1698789420 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs new file mode 100644 index 0000000000..695ac11386 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs @@ -0,0 +1,72 @@ +namespace Unity.Netcode +{ + internal class NotServerRpcTarget : BaseRpcTarget + { + private IGroupRpcTarget m_GroupSendTarget; + private LocalSendRpcTarget m_LocalSendRpcTarget; + + public override void Dispose() + { + m_LocalSendRpcTarget.Dispose(); + if (m_GroupSendTarget != null) + { + m_GroupSendTarget.Target.Dispose(); + m_GroupSendTarget = null; + } + } + + internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) + { + if (m_GroupSendTarget == null) + { + if (behaviour.IsServer) + { + m_GroupSendTarget = new RpcTargetGroup(m_NetworkManager); + } + else + { + m_GroupSendTarget = new ProxyRpcTargetGroup(m_NetworkManager); + } + } + m_GroupSendTarget.Clear(); + + if (behaviour.IsServer) + { + foreach (var clientId in behaviour.NetworkObject.Observers) + { + if (clientId == NetworkManager.ServerClientId) + { + continue; + } + + m_GroupSendTarget.Add(clientId); + } + } + else + { + foreach (var clientId in m_NetworkManager.KnownClientIds) + { + if (clientId == NetworkManager.ServerClientId) + { + continue; + } + + if (clientId == behaviour.NetworkManager.LocalClientId) + { + m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + continue; + } + + m_GroupSendTarget.Add(clientId); + } + } + + m_GroupSendTarget.Target.Send(behaviour, ref message, delivery, rpcParams); + } + + internal NotServerRpcTarget(NetworkManager manager) : base(manager) + { + m_LocalSendRpcTarget = new LocalSendRpcTarget(manager); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs.meta new file mode 100644 index 0000000000..f9fdd45ceb --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c63787afe52f45ffbd5d801f78e7c0d6 +timeCreated: 1697824954 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs new file mode 100644 index 0000000000..7c7829402d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs @@ -0,0 +1,54 @@ +namespace Unity.Netcode +{ + internal class OwnerRpcTarget : BaseRpcTarget + { + private IIndividualRpcTarget m_UnderlyingTarget; + private LocalSendRpcTarget m_LocalRpcTarget; + private ServerRpcTarget m_ServerRpcTarget; + + public override void Dispose() + { + m_LocalRpcTarget.Dispose(); + if (m_UnderlyingTarget != null) + { + m_UnderlyingTarget.Target.Dispose(); + m_UnderlyingTarget = null; + } + } + + internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) + { + if (behaviour.OwnerClientId == behaviour.NetworkManager.LocalClientId) + { + m_LocalRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + return; + } + + if (behaviour.OwnerClientId == NetworkManager.ServerClientId) + { + m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + return; + } + + if (m_UnderlyingTarget == null) + { + if (behaviour.NetworkManager.IsServer) + { + m_UnderlyingTarget = new DirectSendRpcTarget(m_NetworkManager); + } + else + { + m_UnderlyingTarget = new ProxyRpcTarget(behaviour.OwnerClientId, m_NetworkManager); + } + } + m_UnderlyingTarget.SetClientId(behaviour.OwnerClientId); + m_UnderlyingTarget.Target.Send(behaviour, ref message, delivery, rpcParams); + } + + internal OwnerRpcTarget(NetworkManager manager) : base(manager) + { + m_LocalRpcTarget = new LocalSendRpcTarget(manager); + m_ServerRpcTarget = new ServerRpcTarget(manager); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs.meta new file mode 100644 index 0000000000..aa85ed8ba0 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/OwnerRpcTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 23c4d52455fc419aaf03094617894257 +timeCreated: 1697824972 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTarget.cs new file mode 100644 index 0000000000..8ae5bfc8d6 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTarget.cs @@ -0,0 +1,16 @@ +namespace Unity.Netcode +{ + internal class ProxyRpcTarget : ProxyRpcTargetGroup, IIndividualRpcTarget + { + internal ProxyRpcTarget(ulong clientId, NetworkManager manager) : base(manager) + { + Add(clientId); + } + + public void SetClientId(ulong clientId) + { + Clear(); + Add(clientId); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTarget.cs.meta new file mode 100644 index 0000000000..dc192b1d6c --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 86002805bb9e422e8b71581d1325357f +timeCreated: 1697825007 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs new file mode 100644 index 0000000000..612981ad84 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; + +namespace Unity.Netcode +{ + internal class ProxyRpcTargetGroup : BaseRpcTarget, IDisposable, IGroupRpcTarget + { + public BaseRpcTarget Target => this; + + private ServerRpcTarget m_ServerRpcTarget; + private LocalSendRpcTarget m_LocalSendRpcTarget; + + private bool m_Disposed; + public NativeList TargetClientIds; + internal HashSet Ids = new HashSet(); + + internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) + { + var proxyMessage = new ProxyMessage { Delivery = delivery, TargetClientIds = TargetClientIds, WrappedMessage = message }; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + var size = +#endif + behaviour.NetworkManager.MessageManager.SendMessage(ref proxyMessage, delivery, NetworkManager.ServerClientId); + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName)) + { + foreach (var clientId in TargetClientIds) + { + behaviour.NetworkManager.NetworkMetrics.TrackRpcSent( + clientId, + behaviour.NetworkObject, + rpcMethodName, + behaviour.__getTypeName(), + size); + } + } +#endif + if (Ids.Contains(NetworkManager.ServerClientId)) + { + m_ServerRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + } + if (Ids.Contains(m_NetworkManager.LocalClientId)) + { + m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams); + } + } + + internal ProxyRpcTargetGroup(NetworkManager manager) : base(manager) + { + TargetClientIds = new NativeList(Allocator.Persistent); + m_ServerRpcTarget = new ServerRpcTarget(manager); + m_LocalSendRpcTarget = new LocalSendRpcTarget(manager); + } + + public override void Dispose() + { + if (!m_Disposed) + { + TargetClientIds.Dispose(); + m_Disposed = true; + m_ServerRpcTarget.Dispose(); + m_LocalSendRpcTarget.Dispose(); + } + } + + public void Add(ulong clientId) + { + if (!Ids.Contains(clientId)) + { + Ids.Add(clientId); + if (clientId != NetworkManager.ServerClientId && clientId != m_NetworkManager.LocalClientId) + { + TargetClientIds.Add(clientId); + } + } + } + + public void Remove(ulong clientId) + { + Ids.Remove(clientId); + for (var i = 0; i < TargetClientIds.Length; ++i) + { + if (TargetClientIds[i] == clientId) + { + TargetClientIds.RemoveAt(i); + break; + } + } + } + + public void Clear() + { + Ids.Clear(); + TargetClientIds.Clear(); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs.meta new file mode 100644 index 0000000000..fcc3b05c01 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5728dbab532e46a88127510b4ec75af9 +timeCreated: 1697825000 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs new file mode 100644 index 0000000000..d5ae7a2a75 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs @@ -0,0 +1,294 @@ +using System.Collections.Generic; +using Unity.Collections; + +namespace Unity.Netcode +{ + /// + /// Configuration for the default method by which an RPC is communicated across the network + /// + public enum SendTo + { + /// + /// Send to the NetworkObject's current owner + /// Will execute locally if the local process is the owner + /// + Owner, + /// + /// Send to everyone but the current owner, filtered to the current observer list + /// Will execute locally if the local process is not the owner + /// + NotOwner, + /// + /// Send to the server, regardless of ownership + /// Will execute locally if invoked on the server + /// + Server, + /// + /// Send to everyone but the server, filtered to the current observer list + /// Will execute locally if invoked on a client + /// Will NOT execute locally if invoked on a server running in host mode + /// + NotServer, + /// + /// Execute this RPC locally + /// + /// Normally this is no different from a standard function call. + /// + /// Using the DeferLocal parameter of the attribute or the LocalDeferMode override in RpcSendParams, + /// this can allow an RPC to be processed on localhost with a one-frame delay as if it were sent over + /// the network. + /// + Me, + /// + /// Send this RPC to everyone but the local machine, filtered to the current observer list + /// + NotMe, + /// + /// Send this RPC to everone, filtered to the current observer list + /// Will execute locally + /// + Everyone, + /// + /// Send this RPC to all clients, including the host. + /// If the server is running in host mode, this is the same as SendTo.Everyone + /// If the server is running in dedicated server mode, this is the same as SendTo.NotServer + /// + ClientsAndHost, + /// + /// This RPC cannot be sent without passing in a target in RpcSendParams + /// + SpecifiedInParams + } + + /// + /// Implementations of the various options, as well as additional runtime-only options + /// , + /// , + /// , + /// , and + /// + /// + public class RpcTarget + { + private NetworkManager m_NetworkManager; + internal RpcTarget(NetworkManager manager) + { + m_NetworkManager = manager; + + Everyone = new EveryoneRpcTarget(manager); + Owner = new OwnerRpcTarget(manager); + NotOwner = new NotOwnerRpcTarget(manager); + Server = new ServerRpcTarget(manager); + NotServer = new NotServerRpcTarget(manager); + NotMe = new NotMeRpcTarget(manager); + Me = new LocalSendRpcTarget(manager); + ClientsAndHost = new ClientsAndHostRpcTarget(manager); + + m_CachedProxyRpcTargetGroup = new ProxyRpcTargetGroup(manager); + m_CachedTargetGroup = new RpcTargetGroup(manager); + m_CachedDirectSendTarget = new DirectSendRpcTarget(manager); + m_CachedProxyRpcTarget = new ProxyRpcTarget(0, manager); + } + + public void Dispose() + { + Everyone.Dispose(); + Owner.Dispose(); + NotOwner.Dispose(); + Server.Dispose(); + NotServer.Dispose(); + NotMe.Dispose(); + Me.Dispose(); + ClientsAndHost.Dispose(); + + m_CachedProxyRpcTargetGroup.Dispose(); + m_CachedTargetGroup.Dispose(); + m_CachedDirectSendTarget.Dispose(); + m_CachedProxyRpcTarget.Dispose(); + } + + + /// + /// Send to the NetworkObject's current owner + /// Will execute locally if the local process is the owner + /// + public BaseRpcTarget Owner; + + /// + /// Send to everyone but the current owner, filtered to the current observer list + /// Will execute locally if the local process is not the owner + /// + public BaseRpcTarget NotOwner; + + /// + /// Send to the server, regardless of ownership + /// Will execute locally if invoked on the server + /// + public BaseRpcTarget Server; + + /// + /// Send to everyone but the server, filtered to the current observer list + /// Will execute locally if invoked on a client + /// Will NOT execute locally if invoked on a server running in host mode + /// + public BaseRpcTarget NotServer; + + /// + /// Execute this RPC locally + /// + /// Normally this is no different from a standard function call. + /// + /// Using the DeferLocal parameter of the attribute or the LocalDeferMode override in RpcSendParams, + /// this can allow an RPC to be processed on localhost with a one-frame delay as if it were sent over + /// the network. + /// + public BaseRpcTarget Me; + + /// + /// Send this RPC to everyone but the local machine, filtered to the current observer list + /// + public BaseRpcTarget NotMe; + + /// + /// Send this RPC to everone, filtered to the current observer list + /// Will execute locally + /// + public BaseRpcTarget Everyone; + + /// + /// Send this RPC to all clients, including the host. + /// If the server is running in host mode, this is the same as SendTo.Everyone + /// If the server is running in dedicated server mode, this is the same as SendTo.NotServer + /// + public BaseRpcTarget ClientsAndHost; + + /// + /// Send to a specific single client ID. + /// + /// Do not cache or reuse the result of this method. + /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, + /// and its contents are simply changed. + /// + /// + /// + public BaseRpcTarget Single(ulong clientId) + { + if (clientId == m_NetworkManager.LocalClientId) + { + return Me; + } + + if (m_NetworkManager.IsServer || clientId == NetworkManager.ServerClientId) + { + m_CachedDirectSendTarget.SetClientId(clientId); + return m_CachedDirectSendTarget; + } + + m_CachedProxyRpcTarget.SetClientId(clientId); + return m_CachedProxyRpcTarget; + } + + /// + /// Sends to a group of client IDs. + /// NativeArrays can be trivially constructed using Allocator.Temp, making this an efficient + /// Group method if the group list is dynamically constructed. + /// + /// Do not cache or reuse the result of this method. + /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, + /// and its contents are simply changed. + /// + /// + /// + public BaseRpcTarget Group(NativeArray clientIds) + { + IGroupRpcTarget target; + if (m_NetworkManager.IsServer) + { + target = m_CachedTargetGroup; + } + else + { + target = m_CachedProxyRpcTargetGroup; + } + target.Clear(); + foreach (var clientId in clientIds) + { + target.Add(clientId); + } + + return target.Target; + } + + /// + /// Sends to a group of client IDs. + /// NativeList can be trivially constructed using Allocator.Temp, making this an efficient + /// Group method if the group list is dynamically constructed. + /// + /// Do not cache or reuse the result of this method. + /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, + /// and its contents are simply changed. + /// + /// + /// + public BaseRpcTarget Group(NativeList clientIds) + { + return Group(clientIds.AsArray()); + } + + /// + /// Sends to a group of client IDs. + /// Constructing arrays requires garbage collected allocations. This override is only recommended + /// if you either have no strict performance requirements, or have the group of client IDs cached so + /// it is not created each time. + /// + /// Do not cache or reuse the result of this method. + /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, + /// and its contents are simply changed. + /// + /// + /// + public BaseRpcTarget Group(ulong[] clientIds) + { + return Group(new NativeArray(clientIds, Allocator.Temp)); + } + + + /// + /// Sends to a group of client IDs. + /// This accepts any IEnumerable type, such as List<ulong>, but cannot be called without + /// a garbage collected allocation (even if the type itself is a struct type, due to boxing). + /// This override is only recommended if you either have no strict performance requirements, + /// or have the group of client IDs cached so it is not created each time. + /// + /// Do not cache or reuse the result of this method. + /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, + /// and its contents are simply changed. + /// + /// + /// + public BaseRpcTarget Group(T clientIds) where T : IEnumerable + { + IGroupRpcTarget target; + if (m_NetworkManager.IsServer) + { + target = m_CachedTargetGroup; + } + else + { + target = m_CachedProxyRpcTargetGroup; + } + target.Clear(); + foreach (var clientId in clientIds) + { + target.Add(clientId); + } + + return target.Target; + } + + private ProxyRpcTargetGroup m_CachedProxyRpcTargetGroup; + private RpcTargetGroup m_CachedTargetGroup; + private DirectSendRpcTarget m_CachedDirectSendTarget; + private ProxyRpcTarget m_CachedProxyRpcTarget; + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs.meta new file mode 100644 index 0000000000..af3924677b --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1b26d0227e71408b918ae25ca2a0179b +timeCreated: 1699555535 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTargetGroup.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTargetGroup.cs new file mode 100644 index 0000000000..fb7a639cd9 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTargetGroup.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; + +namespace Unity.Netcode +{ + internal class RpcTargetGroup : BaseRpcTarget, IGroupRpcTarget + { + public BaseRpcTarget Target => this; + + internal List Targets = new List(); + + private LocalSendRpcTarget m_LocalSendRpcTarget; + private HashSet m_Ids = new HashSet(); + private Stack m_TargetCache = new Stack(); + + public override void Dispose() + { + foreach (var target in Targets) + { + target.Dispose(); + } + foreach (var target in m_TargetCache) + { + target.Dispose(); + } + m_LocalSendRpcTarget.Dispose(); + } + + internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) + { + foreach (var target in Targets) + { + target.Send(behaviour, ref message, delivery, rpcParams); + } + } + + public void Add(ulong clientId) + { + if (!m_Ids.Contains(clientId)) + { + m_Ids.Add(clientId); + if (clientId == m_NetworkManager.LocalClientId) + { + Targets.Add(m_LocalSendRpcTarget); + } + else + { + if (m_TargetCache.Count == 0) + { + Targets.Add(new DirectSendRpcTarget(m_NetworkManager) { ClientId = clientId }); + } + else + { + var target = m_TargetCache.Pop(); + target.ClientId = clientId; + Targets.Add(target); + } + } + } + } + + public void Clear() + { + m_Ids.Clear(); + foreach (var target in Targets) + { + if (target is DirectSendRpcTarget directSendRpcTarget) + { + m_TargetCache.Push(directSendRpcTarget); + } + } + Targets.Clear(); + } + + internal RpcTargetGroup(NetworkManager manager) : base(manager) + { + m_LocalSendRpcTarget = new LocalSendRpcTarget(manager); + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTargetGroup.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTargetGroup.cs.meta new file mode 100644 index 0000000000..dd7d202180 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTargetGroup.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7f8c0fc053b64a588c99dd7d706d9f0a +timeCreated: 1697824991 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs new file mode 100644 index 0000000000..09b789ddb9 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs @@ -0,0 +1,36 @@ +namespace Unity.Netcode +{ + internal class ServerRpcTarget : BaseRpcTarget + { + private BaseRpcTarget m_UnderlyingTarget; + + public override void Dispose() + { + if (m_UnderlyingTarget != null) + { + m_UnderlyingTarget.Dispose(); + m_UnderlyingTarget = null; + } + } + + internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams) + { + if (m_UnderlyingTarget == null) + { + if (behaviour.NetworkManager.IsServer) + { + m_UnderlyingTarget = new LocalSendRpcTarget(m_NetworkManager); + } + else + { + m_UnderlyingTarget = new DirectSendRpcTarget(m_NetworkManager) { ClientId = NetworkManager.ServerClientId }; + } + } + m_UnderlyingTarget.Send(behaviour, ref message, delivery, rpcParams); + } + + internal ServerRpcTarget(NetworkManager manager) : base(manager) + { + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs.meta new file mode 100644 index 0000000000..a1238cfc87 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c911725afb6d44f3bb1a1d567d9dee0f +timeCreated: 1697824979 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index ffb244ecdd..154bbf490f 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -2067,6 +2067,15 @@ private void HandleClientSceneEvent(uint sceneEventId) // Client is now synchronized and fully "connected". This also means the client can send "RPCs" at this time NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(NetworkManager.LocalClientId); + foreach (var peerId in NetworkManager.KnownClientIds) + { + if (peerId == NetworkManager.LocalClientId) + { + continue; + } + NetworkManager.ConnectionManager.InvokeOnPeerConnectedCallback(peerId); + } + // Notify the client that they have finished synchronizing OnSceneEvent?.Invoke(new SceneEvent() { @@ -2213,6 +2222,11 @@ private void HandleServerSceneEvent(uint sceneEventId, ulong clientId) // of the client was persisted since MLAPI) NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(clientId); + if (NetworkManager.IsHost) + { + NetworkManager.ConnectionManager.InvokeOnPeerConnectedCallback(clientId); + } + // Check to see if the client needs to resynchronize and before sending the message make sure the client is still connected to avoid // a potential crash within the MessageSystem (i.e. sending to a client that no longer exists) if (sceneEventData.ClientNeedsReSynchronization() && !DisableReSynchronization && NetworkManager.ConnectedClients.ContainsKey(clientId)) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index c11b487b7d..94ecf70f29 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -69,7 +69,10 @@ internal bool RemoveObjectFromShowingTo(NetworkObject networkObject, ulong clien if (ret) { - networkObject.Observers.Remove(clientId); + if (networkObject.Observers.Contains(clientId)) + { + networkObject.Observers.Remove(clientId); + } } return ret; @@ -947,7 +950,10 @@ internal void UpdateObservedNetworkObjects(ulong clientId) // CheckObject visibility overrides SpawnWithObservers under this condition if (sobj.CheckObjectVisibility(clientId)) { - sobj.Observers.Add(clientId); + if (!sobj.Observers.Contains(clientId)) + { + sobj.Observers.Add(clientId); + } } else // Otherwise, if the observers contains the clientId (shouldn't happen) then remove it since CheckObjectVisibility returned false if (sobj.Observers.Contains(clientId)) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs index de80a230c8..f85a9cbd41 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DeferredMessagingTests.cs @@ -94,6 +94,10 @@ public override void DeferMessage(IDeferredNetworkMessageManager.TriggerType tri public override void ProcessTriggers(IDeferredNetworkMessageManager.TriggerType trigger, ulong key) { + if (trigger == IDeferredNetworkMessageManager.TriggerType.OnNextFrame) + { + return; + } ProcessTriggersCalled = true; base.ProcessTriggers(trigger, key); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs index 6504f99b92..705db4fbaa 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs @@ -79,7 +79,10 @@ public void OnAfterHandleMessage(ref T message, ref NetworkContext context) w [UnityTest] public IEnumerator WhenSendingConnectionApprovedToAlreadyConnectedClient_ConnectionApprovedMessageIsRejected() { - var message = new ConnectionApprovedMessage(); + var message = new ConnectionApprovedMessage + { + KnownClientIds = new NativeArray(0, Allocator.Temp) + }; m_ServerNetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, m_ClientNetworkManagers[0].LocalClientId); // Unnamed message is something to wait for. When this one is received, @@ -145,7 +148,10 @@ public IEnumerator WhenSendingConnectionRequestFromAlreadyConnectedClient_Connec [UnityTest] public IEnumerator WhenSendingConnectionApprovedFromAnyClient_ConnectionApprovedMessageIsRejected() { - var message = new ConnectionApprovedMessage(); + var message = new ConnectionApprovedMessage + { + KnownClientIds = new NativeArray(0, Allocator.Temp) + }; m_ClientNetworkManagers[0].ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, m_ServerNetworkManager.LocalClientId); // Unnamed message is something to wait for. When this one is received, diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs new file mode 100644 index 0000000000..c744900868 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs @@ -0,0 +1,181 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + /// + /// Validates the client disconnection process. + /// This assures that: + /// - When a client disconnects from the server that the server: + /// -- Detects the client disconnected. + /// -- Cleans up the transport to NGO client (and vice versa) mappings. + /// - When a server disconnects a client that: + /// -- The client detects this disconnection. + /// -- The server cleans up the transport to NGO client (and vice versa) mappings. + /// - When the server-side player object is destroyed + /// - When the server-side player object ownership is transferred back to the server + /// + [TestFixture(HostOrServer.Server)] + [TestFixture(HostOrServer.Host)] + public class PeerDisconnectCallbackTests : NetcodeIntegrationTest + { + + public enum ClientDisconnectType + { + ServerDisconnectsClient, + ClientDisconnectsFromServer + } + + protected override int NumberOfClients => 3; + + private int m_ClientDisconnectCount; + private int m_PeerDisconnectCount; + + + public PeerDisconnectCallbackTests(HostOrServer hostOrServer) + : base(hostOrServer) + { + } + + protected override void OnServerAndClientsCreated() + { + // Adjusting client and server timeout periods to reduce test time + // Get the tick frequency in milliseconds and triple it for the heartbeat timeout + var heartBeatTimeout = (int)(300 * (1.0f / m_ServerNetworkManager.NetworkConfig.TickRate)); + var unityTransport = m_ServerNetworkManager.NetworkConfig.NetworkTransport as Transports.UTP.UnityTransport; + if (unityTransport != null) + { + unityTransport.HeartbeatTimeoutMS = heartBeatTimeout; + } + + unityTransport = m_ClientNetworkManagers[0].NetworkConfig.NetworkTransport as Transports.UTP.UnityTransport; + if (unityTransport != null) + { + unityTransport.HeartbeatTimeoutMS = heartBeatTimeout; + } + + base.OnServerAndClientsCreated(); + } + + protected override IEnumerator OnSetup() + { + m_ClientDisconnectCount = 0; + m_PeerDisconnectCount = 0; + return base.OnSetup(); + } + + private void OnClientDisconnectCallback(ulong obj) + { + ++m_ClientDisconnectCount; + } + + private void OnPeerDisconnectCallback(ulong obj) + { + ++m_PeerDisconnectCount; + } + + [UnityTest] + public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clientDisconnectType, [Values(1ul, 2ul, 3ul)] ulong disconnectedClient) + { + foreach (var client in m_ClientNetworkManagers) + { + client.OnClientDisconnectCallback += OnClientDisconnectCallback; + client.OnPeerDisconnectCallback += OnPeerDisconnectCallback; + if(m_UseHost) + { + Assert.IsTrue(client.KnownClientIds.Contains(0ul)); + } + Assert.IsTrue(client.KnownClientIds.Contains(1ul)); + Assert.IsTrue(client.KnownClientIds.Contains(2ul)); + Assert.IsTrue(client.KnownClientIds.Contains(3ul)); + } + m_ServerNetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; + m_ServerNetworkManager.OnPeerDisconnectCallback += OnPeerDisconnectCallback; + if(m_UseHost) + { + Assert.IsTrue(m_ServerNetworkManager.KnownClientIds.Contains(0ul)); + } + Assert.IsTrue(m_ServerNetworkManager.KnownClientIds.Contains(1ul)); + Assert.IsTrue(m_ServerNetworkManager.KnownClientIds.Contains(2ul)); + Assert.IsTrue(m_ServerNetworkManager.KnownClientIds.Contains(3ul)); + + // Set up a WaitForMessageReceived hook. + // In some cases the message will be received during StopOneClient, but it is not guaranteed + // So we start the listener before we call Stop so it will be noticed regardless of whether it happens + // during StopOneClient or whether we have to wait for it + var messageHookEntriesForSpawn = new List(); + foreach (var clientNetworkManager in m_ClientNetworkManagers.Where(c => c.LocalClientId != disconnectedClient)) + { + var messageHook = new MessageHookEntry(clientNetworkManager); + messageHook.AssignMessageType(); + messageHookEntriesForSpawn.Add(messageHook); + } + + // Used to determine if all clients received the CreateObjectMessage + var hooks = new MessageHooksConditional(messageHookEntriesForSpawn); + + if (clientDisconnectType == ClientDisconnectType.ServerDisconnectsClient) + { + m_ServerNetworkManager.DisconnectClient(disconnectedClient); + } + else + { + yield return StopOneClient(m_ClientNetworkManagers[disconnectedClient-1]); + } + + yield return WaitForConditionOrTimeOut(hooks); + Assert.False(s_GlobalTimeoutHelper.TimedOut); + + foreach (var client in m_ClientNetworkManagers) + { + if (!client.IsConnectedClient) + { + Assert.IsEmpty(client.KnownClientIds); + continue; + } + if(m_UseHost) + { + Assert.IsTrue(client.KnownClientIds.Contains(0ul)); + } + + for (var i = 1ul; i < 3ul; ++i) + { + if (i == disconnectedClient) + { + Assert.IsFalse(client.KnownClientIds.Contains(i)); + } + else + { + Assert.IsTrue(client.KnownClientIds.Contains(i)); + } + } + } + if(m_UseHost) + { + Assert.IsTrue(m_ServerNetworkManager.KnownClientIds.Contains(0ul)); + } + + for (var i = 1ul; i < 3ul; ++i) + { + if (i == disconnectedClient) + { + Assert.IsFalse(m_ServerNetworkManager.KnownClientIds.Contains(i)); + } + else + { + Assert.IsTrue(m_ServerNetworkManager.KnownClientIds.Contains(i)); + } + } + + // If disconnected client-side, only server will receive it. + // If disconnected server-side, one for server, one for self + Assert.AreEqual(clientDisconnectType == ClientDisconnectType.ClientDisconnectsFromServer ? 1 : 2, m_ClientDisconnectCount); + // Host receives peer disconnect, dedicated server does not + Assert.AreEqual(m_UseHost ? 3 : 2, m_PeerDisconnectCount); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs.meta new file mode 100644 index 0000000000..2e6247fb56 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 283ae7cc9a0641c8b49dacad79242287 +timeCreated: 1699978638 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs index d676a14ef9..8202e93683 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs @@ -48,7 +48,6 @@ public void MyNativeListServerRpc(NativeList clientId, ServerRpcParams pa } #endif - [ClientRpc] public void MyClientRpc() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs new file mode 100644 index 0000000000..57a4958eac --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs @@ -0,0 +1,1550 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using Object = UnityEngine.Object; +using Random = System.Random; + +// NOTE: +// Unity's test runner cannot handle a single test fixture with thousands of tests in it. +// Since this file contains thousands of tests (once all parameters have been taken into account), +// I had to split up the tests into separate fixtures for each test case. +// That was the only way to get Unity to actually be able to handle this number of tests. +// I put them in their own namespace so they would be easier to navigate in the test list. +namespace Unity.Netcode.RuntimeTests.UniversalRpcTests +{ + public class UniversalRpcNetworkBehaviour : NetworkBehaviour + { + public bool Stop = false; + public string Received = string.Empty; + public Tuple ReceivedParams = null; + public ulong ReceivedFrom = ulong.MaxValue; + + public void OnRpcReceived() + { + var st = new StackTrace(); + var sf = st.GetFrame(1); + + var currentMethod = sf.GetMethod(); + Received = currentMethod.Name; + } + public void OnRpcReceivedWithParams(int a, bool b, float f, string s) + { + var st = new StackTrace(); + var sf = st.GetFrame(1); + + var currentMethod = sf.GetMethod(); + Received = currentMethod.Name; + ReceivedParams = new Tuple(a, b, f, s); + } + + // Basic RPCs + + [Rpc(SendTo.Everyone)] + public void DefaultToEveryoneRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.Me)] + public void DefaultToMeRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.Owner)] + public void DefaultToOwnerRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotOwner)] + public void DefaultToNotOwnerRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.Server)] + public void DefaultToServerRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotMe)] + public void DefaultToNotMeRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotServer)] + public void DefaultToNotServerRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.ClientsAndHost)] + public void DefaultToClientsAndHostRpc() + { + OnRpcReceived(); + } + + // RPCs with parameters + + [Rpc(SendTo.Everyone)] + public void DefaultToEveryoneWithParamsRpc(int i, bool b, float f, string s) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.Me)] + public void DefaultToMeWithParamsRpc(int i, bool b, float f, string s) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.Owner)] + public void DefaultToOwnerWithParamsRpc(int i, bool b, float f, string s) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.NotOwner)] + public void DefaultToNotOwnerWithParamsRpc(int i, bool b, float f, string s) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.Server)] + public void DefaultToServerWithParamsRpc(int i, bool b, float f, string s) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.NotMe)] + public void DefaultToNotMeWithParamsRpc(int i, bool b, float f, string s) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.NotServer)] + public void DefaultToNotServerWithParamsRpc(int i, bool b, float f, string s) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.ClientsAndHost)] + public void DefaultToClientsAndHostWithParamsRpc(int i, bool b, float f, string s) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + // RPCs with RPC parameters + + [Rpc(SendTo.Everyone)] + public void DefaultToEveryoneWithRpcParamsRpc(RpcParams rpcParams) + { + OnRpcReceived(); + ReceivedFrom = rpcParams.Receive.SenderClientId; + } + + [Rpc(SendTo.Me)] + public void DefaultToMeWithRpcParamsRpc(RpcParams rpcParams) + { + OnRpcReceived(); + ReceivedFrom = rpcParams.Receive.SenderClientId; + } + + [Rpc(SendTo.Owner)] + public void DefaultToOwnerWithRpcParamsRpc(RpcParams rpcParams) + { + OnRpcReceived(); + ReceivedFrom = rpcParams.Receive.SenderClientId; + } + + [Rpc(SendTo.NotOwner)] + public void DefaultToNotOwnerWithRpcParamsRpc(RpcParams rpcParams) + { + OnRpcReceived(); + ReceivedFrom = rpcParams.Receive.SenderClientId; + } + + [Rpc(SendTo.Server)] + public void DefaultToServerWithRpcParamsRpc(RpcParams rpcParams) + { + OnRpcReceived(); + ReceivedFrom = rpcParams.Receive.SenderClientId; + } + + [Rpc(SendTo.NotMe)] + public void DefaultToNotMeWithRpcParamsRpc(RpcParams rpcParams) + { + OnRpcReceived(); + ReceivedFrom = rpcParams.Receive.SenderClientId; + } + + [Rpc(SendTo.NotServer)] + public void DefaultToNotServerWithRpcParamsRpc(RpcParams rpcParams) + { + OnRpcReceived(); + ReceivedFrom = rpcParams.Receive.SenderClientId; + } + + [Rpc(SendTo.ClientsAndHost)] + public void DefaultToClientsAndHostWithRpcParamsRpc(RpcParams rpcParams) + { + OnRpcReceived(); + ReceivedFrom = rpcParams.Receive.SenderClientId; + } + + + // RPCs with parameters and RPC parameters + + [Rpc(SendTo.Everyone)] + public void DefaultToEveryoneWithParamsAndRpcParamsRpc(int i, bool b, float f, string s, RpcParams rpcParams) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.Me)] + public void DefaultToMeWithParamsAndRpcParamsRpc(int i, bool b, float f, string s, RpcParams rpcParams) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.Owner)] + public void DefaultToOwnerWithParamsAndRpcParamsRpc(int i, bool b, float f, string s, RpcParams rpcParams) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.NotOwner)] + public void DefaultToNotOwnerWithParamsAndRpcParamsRpc(int i, bool b, float f, string s, RpcParams rpcParams) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.Server)] + public void DefaultToServerWithParamsAndRpcParamsRpc(int i, bool b, float f, string s, RpcParams rpcParams) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.NotMe)] + public void DefaultToNotMeWithParamsAndRpcParamsRpc(int i, bool b, float f, string s, RpcParams rpcParams) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.NotServer)] + public void DefaultToNotServerWithParamsAndRpcParamsRpc(int i, bool b, float f, string s, RpcParams rpcParams) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + [Rpc(SendTo.ClientsAndHost)] + public void DefaultToClientsAndHostWithParamsAndRpcParamsRpc(int i, bool b, float f, string s, RpcParams rpcParams) + { + OnRpcReceivedWithParams(i, b, f, s); + } + + // RPCs with AllowTargetOverride = true + + // AllowTargetOverried is implied with SpecifiedInParams and does not need to be stated + // Including it will cause a compiler warning + [Rpc(SendTo.SpecifiedInParams)] + public void DefaultToSpecifiedInParamsAllowOverrideRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.Everyone, AllowTargetOverride = true)] + public void DefaultToEveryoneAllowOverrideRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.Me, AllowTargetOverride = true)] + public void DefaultToMeAllowOverrideRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.Owner, AllowTargetOverride = true)] + public void DefaultToOwnerAllowOverrideRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotOwner, AllowTargetOverride = true)] + public void DefaultToNotOwnerAllowOverrideRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.Server, AllowTargetOverride = true)] + public void DefaultToServerAllowOverrideRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotMe, AllowTargetOverride = true)] + public void DefaultToNotMeAllowOverrideRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotServer, AllowTargetOverride = true)] + public void DefaultToNotServerAllowOverrideRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.ClientsAndHost, AllowTargetOverride = true)] + public void DefaultToClientsAndHostAllowOverrideRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + // RPCs with DeferLocal = true + + [Rpc(SendTo.Everyone, DeferLocal = true)] + public void DefaultToEveryoneDeferLocalRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.Me, DeferLocal = true)] + public void DefaultToMeDeferLocalRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.Owner, DeferLocal = true)] + public void DefaultToOwnerDeferLocalRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotOwner, DeferLocal = true)] + public void DefaultToNotOwnerDeferLocalRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.Server, DeferLocal = true)] + public void DefaultToServerDeferLocalRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotServer, DeferLocal = true)] + public void DefaultToNotServerDeferLocalRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + [Rpc(SendTo.ClientsAndHost, DeferLocal = true)] + public void DefaultToClientsAndHostDeferLocalRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + // RPCs with RequireOwnership = true + + [Rpc(SendTo.Everyone, RequireOwnership = true)] + public void DefaultToEveryoneRequireOwnershipRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.Me, RequireOwnership = true)] + public void DefaultToMeRequireOwnershipRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.Owner, RequireOwnership = true)] + public void DefaultToOwnerRequireOwnershipRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotOwner, RequireOwnership = true)] + public void DefaultToNotOwnerRequireOwnershipRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.Server, RequireOwnership = true)] + public void DefaultToServerRequireOwnershipRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotMe, RequireOwnership = true)] + public void DefaultToNotMeRequireOwnershipRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.NotServer, RequireOwnership = true)] + public void DefaultToNotServerRequireOwnershipRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.ClientsAndHost, RequireOwnership = true)] + public void DefaultToClientsAndHostRequireOwnershipRpc() + { + OnRpcReceived(); + } + + [Rpc(SendTo.SpecifiedInParams, RequireOwnership = true)] + public void SpecifiedInParamsRequireOwnershipRpc(RpcParams rpcParams) + { + OnRpcReceived(); + } + + + // Mutual RPC Recursion + + [Rpc(SendTo.Server, DeferLocal = true)] + public void MutualRecursionServerRpc() + { + if (Stop) + { + Stop = false; + return; + } + OnRpcReceived(); + MutualRecursionClientRpc(); + } + + [Rpc(SendTo.NotServer, DeferLocal = true)] + public void MutualRecursionClientRpc() + { + OnRpcReceived(); + MutualRecursionServerRpc(); + } + + // Self recursion + [Rpc(SendTo.Server, DeferLocal = true)] + public void SelfRecursiveRpc() + { + if (Stop) + { + Stop = false; + return; + } + OnRpcReceived(); + SelfRecursiveRpc(); + } + } + + public class UniversalRpcTestsBase : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + public UniversalRpcTestsBase(HostOrServer hostOrServer) : base(hostOrServer) + { + } + + + protected override NetworkManagerInstatiationMode OnSetIntegrationTestMode() + { + return NetworkManagerInstatiationMode.AllTests; + } + + protected override bool m_EnableTimeTravel => true; + + protected override bool m_SetupIsACoroutine => false; + protected override bool m_TearDownIsACoroutine => false; + + protected GameObject m_ServerObject; + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + } + + protected override void OnServerAndClientsCreated() + { + m_ServerObject = new GameObject { name = "Server Object" }; + var networkObject = m_ServerObject.AddComponent(); + m_ServerObject.AddComponent(); + networkObject.NetworkManagerOwner = m_ServerNetworkManager; + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); + m_ServerNetworkManager.AddNetworkPrefab(m_ServerObject); + foreach (var client in m_ClientNetworkManagers) + { + client.AddNetworkPrefab(m_ServerObject); + } + } + + protected override void OnInlineTearDown() + { + Clear(); + } + + protected void Clear() + { + foreach (var obj in Object.FindObjectsByType(FindObjectsSortMode.None)) + { + obj.Received = string.Empty; + obj.ReceivedParams = null; + obj.ReceivedFrom = ulong.MaxValue; + } + } + + protected override void OnOneTimeTearDown() + { + Object.DestroyImmediate(m_ServerObject); + } + + protected override void OnTimeTravelServerAndClientsConnected() + { + m_ServerObject.GetComponent().Spawn(); + WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); + } + + protected UniversalRpcNetworkBehaviour GetPlayerObject(ulong ownerClientId, ulong onClient) + { + if (ownerClientId == NetworkManager.ServerClientId && !m_ServerNetworkManager.IsHost) + { + foreach (var obj in Object.FindObjectsByType(FindObjectsSortMode.None)) + { + if (obj.name.StartsWith("Server Object") && obj.OwnerClientId == ownerClientId && obj.NetworkManager.LocalClientId == onClient) + { + return obj; + } + } + } + + return m_PlayerNetworkObjects[onClient][ownerClientId].GetComponent(); + } + + protected void VerifyLocalReceived(ulong objectOwner, ulong sender, string name, bool verifyReceivedFrom) + { + var obj = GetPlayerObject(objectOwner, sender); + Assert.AreEqual(name, obj.Received); + Assert.IsNull(obj.ReceivedParams); + if (verifyReceivedFrom) + { + Assert.AreEqual(sender, obj.ReceivedFrom); + } + } + + protected void VerifyLocalReceivedWithParams(ulong objectOwner, ulong sender, string name, int i, bool b, float f, string s) + { + var obj = GetPlayerObject(objectOwner, sender); + Assert.AreEqual(name, obj.Received); + Assert.IsNotNull(obj.ReceivedParams); + Assert.AreEqual(i, obj.ReceivedParams.Item1); + Assert.AreEqual(b, obj.ReceivedParams.Item2); + Assert.AreEqual(f, obj.ReceivedParams.Item3); + Assert.AreEqual(s, obj.ReceivedParams.Item4); + } + + protected void VerifyNotReceived(ulong objectOwner, ulong[] receivedBy) + { + foreach (var client in receivedBy) + { + UniversalRpcNetworkBehaviour playerObject = GetPlayerObject(objectOwner, client); + Assert.AreEqual(string.Empty, playerObject.Received); + Assert.IsNull(playerObject.ReceivedParams); + } + } + + protected void VerifyRemoteReceived(ulong objectOwner, ulong sender, string message, ulong[] receivedBy, bool verifyReceivedFrom, bool waitForMessages = true) + { + foreach (var client in receivedBy) + { + if (client == sender) + { + VerifyLocalReceived(objectOwner, sender, message, verifyReceivedFrom); + + break; + } + } + + if (waitForMessages) + { + var needsProxyMessage = false; + var needsServerRpcMessage = false; + if (sender != 0) + { + foreach (var client in receivedBy) + { + if (client == sender) + { + continue; + } + + if (client != 0) + { + needsProxyMessage = true; + } + else + { + needsServerRpcMessage = true; + } + } + } + + if (needsProxyMessage) + { + var messages = new List { typeof(ProxyMessage) }; + if (needsServerRpcMessage) + { + messages.Add(typeof(RpcMessage)); + } + + WaitForMessagesReceivedWithTimeTravel(messages, new[] { m_ServerNetworkManager }.ToList()); + } + + var managersThatNeedToWaitForRpc = new List(); + if (needsServerRpcMessage && !needsProxyMessage) + { + managersThatNeedToWaitForRpc.Add(m_ServerNetworkManager); + } + + foreach (var client in receivedBy) + { + if (client != sender && client != 0) + { + managersThatNeedToWaitForRpc.Add(m_ClientNetworkManagers[client - 1]); + } + } + + WaitForMessageReceivedWithTimeTravel(managersThatNeedToWaitForRpc); + } + + foreach (var client in receivedBy) + { + UniversalRpcNetworkBehaviour playerObject = GetPlayerObject(objectOwner, client); + Assert.AreEqual(message, playerObject.Received); + Assert.IsNull(playerObject.ReceivedParams); + if (verifyReceivedFrom) + { + Assert.AreEqual(sender, playerObject.ReceivedFrom); + } + } + } + + protected void VerifyRemoteReceivedWithParams(ulong objectOwner, ulong sender, string message, ulong[] receivedBy, int i, bool b, float f, string s) + { + foreach (var client in receivedBy) + { + if (client == sender) + { + VerifyLocalReceivedWithParams(objectOwner, sender, message, i, b, f, s); + + break; + } + } + + var needsProxyMessage = false; + var needsServerRpcMessage = false; + if (sender != 0) + { + foreach (var client in receivedBy) + { + if (client == sender) + { + continue; + } + + if (client != 0) + { + needsProxyMessage = true; + } + else + { + needsServerRpcMessage = true; + } + } + } + + if (needsProxyMessage) + { + var messages = new List { typeof(ProxyMessage) }; + if (needsServerRpcMessage) + { + messages.Add(typeof(RpcMessage)); + } + + WaitForMessagesReceivedWithTimeTravel(messages, new[] { m_ServerNetworkManager }.ToList()); + } + + var managersThatNeedToWaitForRpc = new List(); + if (needsServerRpcMessage && !needsProxyMessage) + { + managersThatNeedToWaitForRpc.Add(m_ServerNetworkManager); + } + + foreach (var client in receivedBy) + { + if (client != sender && client != 0) + { + managersThatNeedToWaitForRpc.Add(m_ClientNetworkManagers[client - 1]); + } + } + + WaitForMessageReceivedWithTimeTravel(managersThatNeedToWaitForRpc); + + foreach (var client in receivedBy) + { + UniversalRpcNetworkBehaviour playerObject = GetPlayerObject(objectOwner, client); + Assert.AreEqual(message, playerObject.Received); + + Assert.IsNotNull(playerObject.ReceivedParams); + Assert.AreEqual(i, playerObject.ReceivedParams.Item1); + Assert.AreEqual(b, playerObject.ReceivedParams.Item2); + Assert.AreEqual(f, playerObject.ReceivedParams.Item3); + Assert.AreEqual(s, playerObject.ReceivedParams.Item4); + } + } + + protected static ulong[] s_ClientIds = new[] { 0ul, 1ul, 2ul }; + + public void VerifySentToEveryone(ulong objectOwner, ulong sender, string methodName) + { + VerifyRemoteReceived(objectOwner, sender, methodName, s_ClientIds, false); + } + + public void VerifySentToEveryoneWithReceivedFrom(ulong objectOwner, ulong sender, string methodName) + { + VerifyRemoteReceived(objectOwner, sender, methodName, s_ClientIds, true); + } + + public void VerifySentToEveryoneWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) + { + VerifyRemoteReceivedWithParams(objectOwner, sender, methodName, s_ClientIds, i, b, f, s); + } + + public void VerifySentToId(ulong objectOwner, ulong sender, ulong receiver, string methodName, bool verifyReceivedFrom) + { + VerifyRemoteReceived(objectOwner, sender, methodName, new[] { receiver }, verifyReceivedFrom); + VerifyNotReceived(objectOwner, s_ClientIds.Where(c => c != receiver).ToArray()); + + // Pass some time to make sure that no other client ever receives this + TimeTravel(1f, 30); + VerifyNotReceived(objectOwner, s_ClientIds.Where(c => c != receiver).ToArray()); + } + + public void VerifySentToNotId(ulong objectOwner, ulong sender, ulong notReceiver, string methodName, bool verifyReceivedFrom) + { + VerifyNotReceived(objectOwner, new[] { notReceiver }); + VerifyRemoteReceived(objectOwner, sender, methodName, s_ClientIds.Where(c => c != notReceiver).ToArray(), verifyReceivedFrom); + // Verify again after all the waiting is finished + VerifyNotReceived(objectOwner, new[] { notReceiver }); + } + + public void VerifySentToIdWithParams(ulong objectOwner, ulong sender, ulong receiver, string methodName, int i, bool b, float f, string s) + { + VerifyRemoteReceivedWithParams(objectOwner, sender, methodName, new[] { receiver }, i, b, f, s); + VerifyNotReceived(objectOwner, s_ClientIds.Where(c => c != receiver).ToArray()); + + // Pass some time to make sure that no other client ever receives this + TimeTravel(1f, 30); + VerifyNotReceived(objectOwner, s_ClientIds.Where(c => c != receiver).ToArray()); + } + + public void VerifySentToNotIdWithParams(ulong objectOwner, ulong sender, ulong notReceiver, string methodName, int i, bool b, float f, string s) + { + VerifyNotReceived(objectOwner, new[] { notReceiver }); + VerifyRemoteReceivedWithParams(objectOwner, sender, methodName, s_ClientIds.Where(c => c != notReceiver).ToArray(), i, b, f, s); + // Verify again after all the waiting is finished + VerifyNotReceived(objectOwner, new[] { notReceiver }); + } + + public void VerifySentToOwner(ulong objectOwner, ulong sender, string methodName) + { + VerifySentToId(objectOwner, sender, objectOwner, methodName, false); + } + + public void VerifySentToNotOwner(ulong objectOwner, ulong sender, string methodName) + { + VerifySentToNotId(objectOwner, sender, objectOwner, methodName, false); + } + + public void VerifySentToServer(ulong objectOwner, ulong sender, string methodName) + { + VerifySentToId(objectOwner, sender, NetworkManager.ServerClientId, methodName, false); + } + + public void VerifySentToNotServer(ulong objectOwner, ulong sender, string methodName) + { + VerifySentToNotId(objectOwner, sender, NetworkManager.ServerClientId, methodName, false); + } + + public void VerifySentToClientsAndHost(ulong objectOwner, ulong sender, string methodName) + { + if (m_ServerNetworkManager.IsHost) + { + VerifySentToEveryone(objectOwner, sender, methodName); + } + else + { + VerifySentToNotServer(objectOwner, sender, methodName); + } + } + + public void VerifySentToMe(ulong objectOwner, ulong sender, string methodName) + { + VerifySentToId(objectOwner, sender, sender, methodName, false); + } + + public void VerifySentToNotMe(ulong objectOwner, ulong sender, string methodName) + { + VerifySentToNotId(objectOwner, sender, sender, methodName, false); + } + + public void VerifySentToOwnerWithReceivedFrom(ulong objectOwner, ulong sender, string methodName) + { + VerifySentToId(objectOwner, sender, objectOwner, methodName, true); + } + + public void VerifySentToNotOwnerWithReceivedFrom(ulong objectOwner, ulong sender, string methodName) + { + VerifySentToNotId(objectOwner, sender, objectOwner, methodName, true); + } + + public void VerifySentToServerWithReceivedFrom(ulong objectOwner, ulong sender, string methodName) + { + VerifySentToId(objectOwner, sender, NetworkManager.ServerClientId, methodName, true); + } + + public void VerifySentToNotServerWithReceivedFrom(ulong objectOwner, ulong sender, string methodName) + { + VerifySentToNotId(objectOwner, sender, NetworkManager.ServerClientId, methodName, true); + } + + public void VerifySentToClientsAndHostWithReceivedFrom(ulong objectOwner, ulong sender, string methodName) + { + if (m_ServerNetworkManager.IsHost) + { + VerifySentToEveryoneWithReceivedFrom(objectOwner, sender, methodName); + } + else + { + VerifySentToNotServerWithReceivedFrom(objectOwner, sender, methodName); + } + } + + public void VerifySentToMeWithReceivedFrom(ulong objectOwner, ulong sender, string methodName) + { + VerifySentToId(objectOwner, sender, sender, methodName, true); + } + + public void VerifySentToNotMeWithReceivedFrom(ulong objectOwner, ulong sender, string methodName) + { + VerifySentToNotId(objectOwner, sender, sender, methodName, true); + } + + public void VerifySentToOwnerWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) + { + VerifySentToIdWithParams(objectOwner, sender, objectOwner, methodName, i, b, f, s); + } + + public void VerifySentToNotOwnerWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) + { + VerifySentToNotIdWithParams(objectOwner, sender, objectOwner, methodName, i, b, f, s); + } + + public void VerifySentToServerWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) + { + VerifySentToIdWithParams(objectOwner, sender, NetworkManager.ServerClientId, methodName, i, b, f, s); + } + + public void VerifySentToNotServerWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) + { + VerifySentToNotIdWithParams(objectOwner, sender, NetworkManager.ServerClientId, methodName, i, b, f, s); + } + + public void VerifySentToClientsAndHostWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) + { + if (m_ServerNetworkManager.IsHost) + { + VerifySentToEveryoneWithParams(objectOwner, sender, methodName, i, b, f, s); + } + else + { + VerifySentToNotServerWithParams(objectOwner, sender, methodName, i, b, f, s); + } + } + + public void VerifySentToMeWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) + { + VerifySentToIdWithParams(objectOwner, sender, sender, methodName, i, b, f, s); + } + + public void VerifySentToNotMeWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) + { + VerifySentToNotIdWithParams(objectOwner, sender, sender, methodName, i, b, f, s); + } + + public void RethrowTargetInvocationException(Action action) + { + try + { + action.Invoke(); + } + catch (TargetInvocationException e) + { + throw e.InnerException; + } + } + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestSendingNoOverride : UniversalRpcTestsBase + { + public UniversalRpcTestSendingNoOverride(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + [Test] + public void TestSendingNoOverride( + // Excludes SendTo.SpecifiedInParams + [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, + [Values(0u, 1u, 2u)] ulong objectOwner, + [Values(0u, 1u, 2u)] ulong sender + ) + { + var sendMethodName = $"DefaultTo{sendTo}Rpc"; + var verifyMethodName = $"VerifySentTo{sendTo}"; + + var senderObject = GetPlayerObject(objectOwner, sender); + var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + sendMethod.Invoke(senderObject, new object[] { }); + + var verifyMethod = GetType().GetMethod(verifyMethodName); + verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName }); + } + + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestSenderClientId : UniversalRpcTestsBase + { + public UniversalRpcTestSenderClientId(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + [Test] + public void TestSenderClientId( + // Excludes SendTo.SpecifiedInParams + [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, + [Values(0u, 1u, 2u)] ulong objectOwner, + [Values(0u, 1u, 2u)] ulong sender + ) + { + var sendMethodName = $"DefaultTo{sendTo}WithRpcParamsRpc"; + var verifyMethodName = $"VerifySentTo{sendTo}WithReceivedFrom"; + + var senderObject = GetPlayerObject(objectOwner, sender); + var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + sendMethod.Invoke(senderObject, new object[] { new RpcParams() }); + + var verifyMethod = GetType().GetMethod(verifyMethodName); + verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName }); + } + + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestSendingNoOverrideWithParams : UniversalRpcTestsBase + { + public UniversalRpcTestSendingNoOverrideWithParams(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + [Test] + public void TestSendingNoOverrideWithParams( + // Excludes SendTo.SpecifiedInParams + [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, + [Values(0u, 1u, 2u)] ulong objectOwner, + [Values(0u, 1u, 2u)] ulong sender + ) + { + var rand = new Random(); + var i = rand.Next(); + var f = (float)rand.NextDouble(); + var b = rand.Next() % 2 == 1; + var s = ""; + var numChars = rand.Next() % 5 + 5; + const string chars = "abcdefghijklmnopqrstuvwxycABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-=_+[]{}\\|;':\",./<>?"; + for (var j = 0; j < numChars; ++j) + { + s += chars[rand.Next(chars.Length)]; + } + + var sendMethodName = $"DefaultTo{sendTo}WithParamsRpc"; + var verifyMethodName = $"VerifySentTo{sendTo}WithParams"; + + var senderObject = GetPlayerObject(objectOwner, sender); + var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + sendMethod.Invoke(senderObject, new object[] { i, b, f, s }); + + var verifyMethod = GetType().GetMethod(verifyMethodName); + verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName, i, b, f, s }); + } + + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestSendingNoOverrideWithParamsAndRpcParams : UniversalRpcTestsBase + { + public UniversalRpcTestSendingNoOverrideWithParamsAndRpcParams(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + [Test] + public void TestSendingNoOverrideWithParamsAndRpcParams( + // Excludes SendTo.SpecifiedInParams + [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, + [Values(0u, 1u, 2u)] ulong objectOwner, + [Values(0u, 1u, 2u)] ulong sender + ) + { + var rand = new Random(); + var i = rand.Next(); + var f = (float)rand.NextDouble(); + var b = rand.Next() % 2 == 1; + var s = ""; + var numChars = rand.Next() % 5 + 5; + const string chars = "abcdefghijklmnopqrstuvwxycABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-=_+[]{}\\|;':\",./<>?"; + for (var j = 0; j < numChars; ++j) + { + s += chars[rand.Next(chars.Length)]; + } + + var sendMethodName = $"DefaultTo{sendTo}WithParamsAndRpcParamsRpc"; + var verifyMethodName = $"VerifySentTo{sendTo}WithParams"; + + var senderObject = GetPlayerObject(objectOwner, sender); + var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + sendMethod.Invoke(senderObject, new object[] { i, b, f, s, new RpcParams() }); + + var verifyMethod = GetType().GetMethod(verifyMethodName); + verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName, i, b, f, s }); + } + + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestRequireOwnership : UniversalRpcTestsBase + { + public UniversalRpcTestRequireOwnership(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + [Test] + public void TestRequireOwnership( + // Excludes SendTo.SpecifiedInParams + [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, + [Values(0u, 1u, 2u)] ulong objectOwner, + [Values(0u, 1u, 2u)] ulong sender + ) + { + var sendMethodName = $"DefaultTo{sendTo}RequireOwnershipRpc"; + + var senderObject = GetPlayerObject(objectOwner, sender); + var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + if (sender != objectOwner) + { + Assert.Throws(() => RethrowTargetInvocationException(() => sendMethod.Invoke(senderObject, new object[] { }))); + } + else + { + var verifyMethodName = $"VerifySentTo{sendTo}"; + sendMethod.Invoke(senderObject, new object[] { }); + + var verifyMethod = GetType().GetMethod(verifyMethodName); + verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName }); + } + } + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestDisallowedOverride : UniversalRpcTestsBase + { + public UniversalRpcTestDisallowedOverride(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + [Test] + public void TestDisallowedOverride( + // Excludes SendTo.SpecifiedInParams + [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo sendTo, + [Values(0u, 1u, 2u)] ulong objectOwner, + [Values(0u, 1u, 2u)] ulong sender) + { + var senderObject = GetPlayerObject(objectOwner, sender); + var methodName = $"DefaultTo{sendTo}WithRpcParamsRpc"; + var method = senderObject.GetType().GetMethod(methodName); + Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.Everyone }))); + Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.Owner }))); + Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.NotOwner }))); + Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.Server }))); + Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.NotServer }))); + Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.ClientsAndHost }))); + Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.Me }))); + Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.NotMe }))); + Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.Single(0) }))); + Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.Group(new[] { 0ul, 1ul, 2ul }) }))); + } + + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestSendingWithTargetOverride : UniversalRpcTestsBase + { + public UniversalRpcTestSendingWithTargetOverride(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + [Test] + public void TestSendingWithTargetOverride( + [Values] SendTo defaultSendTo, + [Values(SendTo.Everyone, SendTo.Me, SendTo.Owner, SendTo.Server, SendTo.NotMe, SendTo.NotOwner, SendTo.NotServer, SendTo.ClientsAndHost)] SendTo overrideSendTo, + [Values(0u, 1u, 2u)] ulong objectOwner, + [Values(0u, 1u, 2u)] ulong sender + ) + { + var sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc"; + var targetField = typeof(RpcTarget).GetField(overrideSendTo.ToString()); + var verifyMethodName = $"VerifySentTo{overrideSendTo}"; + + var senderObject = GetPlayerObject(objectOwner, sender); + var target = (BaseRpcTarget)targetField.GetValue(senderObject.RpcTarget); + var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + sendMethod.Invoke(senderObject, new object[] { (RpcParams)target }); + + var verifyMethod = GetType().GetMethod(verifyMethodName); + verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName }); + } + + + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestSendingWithSingleOverride : UniversalRpcTestsBase + { + public UniversalRpcTestSendingWithSingleOverride(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + [Test] + public void TestSendingWithSingleOverride( + [Values] SendTo defaultSendTo, + [Values(0u, 1u, 2u)] ulong recipient, + [Values(0u, 1u, 2u)] ulong objectOwner, + [Values(0u, 1u, 2u)] ulong sender + ) + { + var sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc"; + + var senderObject = GetPlayerObject(objectOwner, sender); + var target = senderObject.RpcTarget.Single(recipient); + var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + sendMethod.Invoke(senderObject, new object[] { (RpcParams)target }); + + VerifyRemoteReceived(objectOwner, sender, sendMethodName, new[] { recipient }, false); + VerifyNotReceived(objectOwner, s_ClientIds.Where(c => recipient != c).ToArray()); + + // Pass some time to make sure that no other client ever receives this + TimeTravel(1f, 30); + VerifyNotReceived(objectOwner, s_ClientIds.Where(c => recipient != c).ToArray()); + } + + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestSendingWithGroupOverride : UniversalRpcTestsBase + { + public UniversalRpcTestSendingWithGroupOverride(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + public static ulong[][] RecipientGroups = new[] + { + new[] { 0ul }, + new[] { 1ul }, + new[] { 0ul, 1ul }, + new[] { 0ul, 1ul, 2ul } + }; + + [Test] + public void TestSendingWithGroupOverride( + [Values] SendTo defaultSendTo, + [ValueSource(nameof(RecipientGroups))] ulong[] recipient, + [Values(0u, 1u, 2u)] ulong objectOwner, + [Values(0u, 1u, 2u)] ulong sender + ) + { + var sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc"; + + var senderObject = GetPlayerObject(objectOwner, sender); + var target = senderObject.RpcTarget.Group(recipient); + var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + sendMethod.Invoke(senderObject, new object[] { (RpcParams)target }); + + VerifyRemoteReceived(objectOwner, sender, sendMethodName, s_ClientIds.Where(c => recipient.Contains(c)).ToArray(), false); + VerifyNotReceived(objectOwner, s_ClientIds.Where(c => !recipient.Contains(c)).ToArray()); + + // Pass some time to make sure that no other client ever receives this + TimeTravel(1f, 30); + VerifyNotReceived(objectOwner, s_ClientIds.Where(c => !recipient.Contains(c)).ToArray()); + } + + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestDefaultSendToSpecifiedInParamsSendingToServerAndOwner : UniversalRpcTestsBase + { + public UniversalRpcTestDefaultSendToSpecifiedInParamsSendingToServerAndOwner(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestDeferLocal : UniversalRpcTestsBase + { + public UniversalRpcTestDeferLocal(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + [Test] + // All the test cases that involve sends that will be delivered locally + [TestCase(SendTo.Everyone, 0u, 0u)] + [TestCase(SendTo.Everyone, 0u, 1u)] + [TestCase(SendTo.Everyone, 0u, 2u)] + [TestCase(SendTo.Everyone, 1u, 0u)] + [TestCase(SendTo.Everyone, 1u, 1u)] + [TestCase(SendTo.Everyone, 1u, 2u)] + [TestCase(SendTo.Everyone, 2u, 0u)] + [TestCase(SendTo.Everyone, 2u, 1u)] + [TestCase(SendTo.Everyone, 2u, 2u)] + [TestCase(SendTo.Me, 0u, 0u)] + [TestCase(SendTo.Me, 0u, 1u)] + [TestCase(SendTo.Me, 0u, 2u)] + [TestCase(SendTo.Me, 1u, 0u)] + [TestCase(SendTo.Me, 1u, 1u)] + [TestCase(SendTo.Me, 1u, 2u)] + [TestCase(SendTo.Me, 2u, 0u)] + [TestCase(SendTo.Me, 2u, 1u)] + [TestCase(SendTo.Me, 2u, 2u)] + [TestCase(SendTo.Owner, 0u, 0u)] + [TestCase(SendTo.Owner, 1u, 1u)] + [TestCase(SendTo.Owner, 2u, 2u)] + [TestCase(SendTo.Server, 0u, 0u)] + [TestCase(SendTo.Server, 1u, 0u)] + [TestCase(SendTo.Server, 2u, 0u)] + [TestCase(SendTo.NotOwner, 0u, 1u)] + [TestCase(SendTo.NotOwner, 0u, 2u)] + [TestCase(SendTo.NotOwner, 1u, 0u)] + [TestCase(SendTo.NotOwner, 1u, 2u)] + [TestCase(SendTo.NotOwner, 2u, 0u)] + [TestCase(SendTo.NotOwner, 2u, 1u)] + [TestCase(SendTo.NotServer, 0u, 1u)] + [TestCase(SendTo.NotServer, 0u, 2u)] + [TestCase(SendTo.NotServer, 1u, 1u)] + [TestCase(SendTo.NotServer, 1u, 2u)] + [TestCase(SendTo.NotServer, 2u, 1u)] + [TestCase(SendTo.NotServer, 2u, 2u)] + [TestCase(SendTo.ClientsAndHost, 0u, 0u)] + [TestCase(SendTo.ClientsAndHost, 0u, 1u)] + [TestCase(SendTo.ClientsAndHost, 0u, 2u)] + [TestCase(SendTo.ClientsAndHost, 1u, 0u)] + [TestCase(SendTo.ClientsAndHost, 1u, 1u)] + [TestCase(SendTo.ClientsAndHost, 1u, 2u)] + [TestCase(SendTo.ClientsAndHost, 2u, 0u)] + [TestCase(SendTo.ClientsAndHost, 2u, 1u)] + [TestCase(SendTo.ClientsAndHost, 2u, 2u)] + public void TestDeferLocal( + SendTo defaultSendTo, + ulong objectOwner, + ulong sender + ) + { + if (defaultSendTo == SendTo.ClientsAndHost && sender == 0u && !m_ServerNetworkManager.IsHost) + { + // Not calling Assert.Ignore() because Unity will mark the whole block of tests as ignored + // Just consider this case a success... + return; + } + var sendMethodName = $"DefaultTo{defaultSendTo}DeferLocalRpc"; + var verifyMethodName = $"VerifySentTo{defaultSendTo}"; + var senderObject = GetPlayerObject(objectOwner, sender); + var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + sendMethod.Invoke(senderObject, new object[] { new RpcParams() }); + + VerifyNotReceived(objectOwner, new[] { sender }); + // Should be received on the next frame + SimulateOneFrame(); + VerifyLocalReceived(objectOwner, sender, sendMethodName, false); + + var verifyMethod = GetType().GetMethod(verifyMethodName); + verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName }); + } + + [Test] + // All the test cases that involve sends that will be delivered locally + [TestCase(SendTo.Everyone, 0u, 0u)] + [TestCase(SendTo.Everyone, 0u, 1u)] + [TestCase(SendTo.Everyone, 0u, 2u)] + [TestCase(SendTo.Everyone, 1u, 0u)] + [TestCase(SendTo.Everyone, 1u, 1u)] + [TestCase(SendTo.Everyone, 1u, 2u)] + [TestCase(SendTo.Everyone, 2u, 0u)] + [TestCase(SendTo.Everyone, 2u, 1u)] + [TestCase(SendTo.Everyone, 2u, 2u)] + [TestCase(SendTo.Me, 0u, 0u)] + [TestCase(SendTo.Me, 0u, 1u)] + [TestCase(SendTo.Me, 0u, 2u)] + [TestCase(SendTo.Me, 1u, 0u)] + [TestCase(SendTo.Me, 1u, 1u)] + [TestCase(SendTo.Me, 1u, 2u)] + [TestCase(SendTo.Me, 2u, 0u)] + [TestCase(SendTo.Me, 2u, 1u)] + [TestCase(SendTo.Me, 2u, 2u)] + [TestCase(SendTo.Owner, 0u, 0u)] + [TestCase(SendTo.Owner, 1u, 1u)] + [TestCase(SendTo.Owner, 2u, 2u)] + [TestCase(SendTo.Server, 0u, 0u)] + [TestCase(SendTo.Server, 1u, 0u)] + [TestCase(SendTo.Server, 2u, 0u)] + [TestCase(SendTo.NotOwner, 0u, 1u)] + [TestCase(SendTo.NotOwner, 0u, 2u)] + [TestCase(SendTo.NotOwner, 1u, 0u)] + [TestCase(SendTo.NotOwner, 1u, 2u)] + [TestCase(SendTo.NotOwner, 2u, 0u)] + [TestCase(SendTo.NotOwner, 2u, 1u)] + [TestCase(SendTo.NotServer, 0u, 1u)] + [TestCase(SendTo.NotServer, 0u, 2u)] + [TestCase(SendTo.NotServer, 1u, 1u)] + [TestCase(SendTo.NotServer, 1u, 2u)] + [TestCase(SendTo.NotServer, 2u, 1u)] + [TestCase(SendTo.NotServer, 2u, 2u)] + [TestCase(SendTo.ClientsAndHost, 0u, 0u)] + [TestCase(SendTo.ClientsAndHost, 0u, 1u)] + [TestCase(SendTo.ClientsAndHost, 0u, 2u)] + [TestCase(SendTo.ClientsAndHost, 1u, 0u)] + [TestCase(SendTo.ClientsAndHost, 1u, 1u)] + [TestCase(SendTo.ClientsAndHost, 1u, 2u)] + [TestCase(SendTo.ClientsAndHost, 2u, 0u)] + [TestCase(SendTo.ClientsAndHost, 2u, 1u)] + [TestCase(SendTo.ClientsAndHost, 2u, 2u)] + public void TestDeferLocalOverrideToTrue( + SendTo defaultSendTo, + ulong objectOwner, + ulong sender + ) + { + if (defaultSendTo == SendTo.ClientsAndHost && sender == 0u && !m_ServerNetworkManager.IsHost) + { + // Not calling Assert.Ignore() because Unity will mark the whole block of tests as ignored + // Just consider this case a success... + return; + } + var sendMethodName = $"DefaultTo{defaultSendTo}WithRpcParamsRpc"; + var verifyMethodName = $"VerifySentTo{defaultSendTo}"; + var senderObject = GetPlayerObject(objectOwner, sender); + var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + sendMethod.Invoke(senderObject, new object[] { (RpcParams)LocalDeferMode.Defer }); + + VerifyNotReceived(objectOwner, new[] { sender }); + // Should be received on the next frame + SimulateOneFrame(); + VerifyLocalReceived(objectOwner, sender, sendMethodName, false); + + var verifyMethod = GetType().GetMethod(verifyMethodName); + verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName }); + } + + [Test] + // All the test cases that involve sends that will be delivered locally + [TestCase(SendTo.Everyone, 0u, 0u)] + [TestCase(SendTo.Everyone, 0u, 1u)] + [TestCase(SendTo.Everyone, 0u, 2u)] + [TestCase(SendTo.Everyone, 1u, 0u)] + [TestCase(SendTo.Everyone, 1u, 1u)] + [TestCase(SendTo.Everyone, 1u, 2u)] + [TestCase(SendTo.Everyone, 2u, 0u)] + [TestCase(SendTo.Everyone, 2u, 1u)] + [TestCase(SendTo.Everyone, 2u, 2u)] + [TestCase(SendTo.Me, 0u, 0u)] + [TestCase(SendTo.Me, 0u, 1u)] + [TestCase(SendTo.Me, 0u, 2u)] + [TestCase(SendTo.Me, 1u, 0u)] + [TestCase(SendTo.Me, 1u, 1u)] + [TestCase(SendTo.Me, 1u, 2u)] + [TestCase(SendTo.Me, 2u, 0u)] + [TestCase(SendTo.Me, 2u, 1u)] + [TestCase(SendTo.Me, 2u, 2u)] + [TestCase(SendTo.Owner, 0u, 0u)] + [TestCase(SendTo.Owner, 1u, 1u)] + [TestCase(SendTo.Owner, 2u, 2u)] + [TestCase(SendTo.Server, 0u, 0u)] + [TestCase(SendTo.Server, 1u, 0u)] + [TestCase(SendTo.Server, 2u, 0u)] + [TestCase(SendTo.NotOwner, 0u, 1u)] + [TestCase(SendTo.NotOwner, 0u, 2u)] + [TestCase(SendTo.NotOwner, 1u, 0u)] + [TestCase(SendTo.NotOwner, 1u, 2u)] + [TestCase(SendTo.NotOwner, 2u, 0u)] + [TestCase(SendTo.NotOwner, 2u, 1u)] + [TestCase(SendTo.NotServer, 0u, 1u)] + [TestCase(SendTo.NotServer, 0u, 2u)] + [TestCase(SendTo.NotServer, 1u, 1u)] + [TestCase(SendTo.NotServer, 1u, 2u)] + [TestCase(SendTo.NotServer, 2u, 1u)] + [TestCase(SendTo.NotServer, 2u, 2u)] + [TestCase(SendTo.ClientsAndHost, 0u, 0u)] + [TestCase(SendTo.ClientsAndHost, 0u, 1u)] + [TestCase(SendTo.ClientsAndHost, 0u, 2u)] + [TestCase(SendTo.ClientsAndHost, 1u, 0u)] + [TestCase(SendTo.ClientsAndHost, 1u, 1u)] + [TestCase(SendTo.ClientsAndHost, 1u, 2u)] + [TestCase(SendTo.ClientsAndHost, 2u, 0u)] + [TestCase(SendTo.ClientsAndHost, 2u, 1u)] + [TestCase(SendTo.ClientsAndHost, 2u, 2u)] + public void TestDeferLocalOverrideToFalse( + SendTo defaultSendTo, + ulong objectOwner, + ulong sender + ) + { + if (defaultSendTo == SendTo.ClientsAndHost && sender == 0u && !m_ServerNetworkManager.IsHost) + { + // Not calling Assert.Ignore() because Unity will mark the whole block of tests as ignored + // Just consider this case a success... + return; + } + var sendMethodName = $"DefaultTo{defaultSendTo}DeferLocalRpc"; + var verifyMethodName = $"VerifySentTo{defaultSendTo}"; + var senderObject = GetPlayerObject(objectOwner, sender); + var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + sendMethod.Invoke(senderObject, new object[] { (RpcParams)LocalDeferMode.SendImmediate }); + + VerifyLocalReceived(objectOwner, sender, sendMethodName, false); + + var verifyMethod = GetType().GetMethod(verifyMethodName); + verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName }); + } + + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestMutualRecursion : UniversalRpcTestsBase + { + public UniversalRpcTestMutualRecursion(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + [Test] + public void TestMutualRecursion() + { + var serverObj = GetPlayerObject(NetworkManager.ServerClientId, NetworkManager.ServerClientId); + + serverObj.MutualRecursionClientRpc(); + + var serverIdArray = new[] { NetworkManager.ServerClientId }; + var clientIdArray = s_ClientIds.Where(c => c != NetworkManager.ServerClientId).ToArray(); + + var clientList = m_ClientNetworkManagers.ToList(); + var serverList = new List { m_ServerNetworkManager }; + + VerifyNotReceived(NetworkManager.ServerClientId, s_ClientIds); + + for (var i = 0; i < 10; ++i) + { + WaitForMessageReceivedWithTimeTravel(clientList); + VerifyRemoteReceived(NetworkManager.ServerClientId, NetworkManager.ServerClientId, nameof(UniversalRpcNetworkBehaviour.MutualRecursionClientRpc), clientIdArray, false, false); + VerifyNotReceived(NetworkManager.ServerClientId, serverIdArray); + + Clear(); + + WaitForMessageReceivedWithTimeTravel(serverList); + VerifyRemoteReceived(NetworkManager.ServerClientId, NetworkManager.ServerClientId, nameof(UniversalRpcNetworkBehaviour.MutualRecursionServerRpc), serverIdArray, false, false); + VerifyNotReceived(NetworkManager.ServerClientId, clientIdArray); + + Clear(); + } + serverObj.Stop = true; + WaitForMessageReceivedWithTimeTravel(serverList); + Assert.IsFalse(serverObj.Stop); + } + + + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestSelfRecursion : UniversalRpcTestsBase + { + public UniversalRpcTestSelfRecursion(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + [Test] + public void TestSelfRecursion() + { + var serverObj = GetPlayerObject(NetworkManager.ServerClientId, NetworkManager.ServerClientId); + + serverObj.SelfRecursiveRpc(); + + var serverIdArray = new[] { NetworkManager.ServerClientId }; + var clientIdArray = s_ClientIds.Where(c => c != NetworkManager.ServerClientId).ToArray(); + + var clientList = m_ClientNetworkManagers.ToList(); + var serverList = new List { m_ServerNetworkManager }; + + for (var i = 0; i < 10; ++i) + { + VerifyNotReceived(NetworkManager.ServerClientId, s_ClientIds); + SimulateOneFrame(); + VerifyLocalReceived(NetworkManager.ServerClientId, NetworkManager.ServerClientId, nameof(UniversalRpcNetworkBehaviour.SelfRecursiveRpc), false); + + Clear(); + } + + serverObj.Stop = true; + SimulateOneFrame(); + Assert.IsFalse(serverObj.Stop); + VerifyNotReceived(NetworkManager.ServerClientId, s_ClientIds); + } + + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs.meta new file mode 100644 index 0000000000..2564551c6b --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: af2b96a4f4d34fa798385b487fc5c97d +timeCreated: 1698158457 \ No newline at end of file diff --git a/testproject/Assets/Scripts/CommandLineHandler.cs b/testproject/Assets/Scripts/CommandLineHandler.cs index c8b45c51fd..5c58a6cdd1 100644 --- a/testproject/Assets/Scripts/CommandLineHandler.cs +++ b/testproject/Assets/Scripts/CommandLineHandler.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; -using UnityEngine; -using UnityEngine.SceneManagement; using Unity.Netcode; using Unity.Netcode.Transports.UTP; +using UnityEngine; +using UnityEngine.SceneManagement; #if UNITY_UNET_PRESENT using Unity.Netcode.Transports.UNET; #endif diff --git a/testproject/Assets/Scripts/ConnectionModeScript.cs b/testproject/Assets/Scripts/ConnectionModeScript.cs index bfaccc2d70..f69e37aefb 100644 --- a/testproject/Assets/Scripts/ConnectionModeScript.cs +++ b/testproject/Assets/Scripts/ConnectionModeScript.cs @@ -62,10 +62,7 @@ private IEnumerator WaitForNetworkManager() { if (NetworkManager.Singleton && !NetworkManager.Singleton.IsListening) { - if (m_ConnectionModeButtons != null) - { - m_ConnectionModeButtons.SetActive(false); - } + m_ConnectionModeButtons?.SetActive(false); m_CommandLineProcessor.ProcessCommandLine(); break; } @@ -129,14 +126,8 @@ private void OnServicesInitialized() if (HasRelaySupport()) { m_JoinCodeInput.SetActive(true); - if (m_ConnectionModeButtons != null) - { - m_ConnectionModeButtons.SetActive(false || AuthenticationService.Instance.IsSignedIn); - } - if (m_AuthenticationButtons != null) - { - m_AuthenticationButtons.SetActive(NetworkManager.Singleton && !NetworkManager.Singleton.IsListening && !AuthenticationService.Instance.IsSignedIn); - } + m_ConnectionModeButtons?.SetActive(false || AuthenticationService.Instance.IsSignedIn); + m_AuthenticationButtons?.SetActive(NetworkManager.Singleton && !NetworkManager.Singleton.IsListening && !AuthenticationService.Instance.IsSignedIn); } } @@ -186,10 +177,7 @@ private void OnServerStopped(bool obj) private IEnumerator StartRelayServer(Action postAllocationAction) { #if ENABLE_RELAY_SERVICE - if (m_ConnectionModeButtons != null) - { - m_ConnectionModeButtons.SetActive(false); - } + m_ConnectionModeButtons?.SetActive(false); var serverRelayUtilityTask = RelayUtility.AllocateRelayServerAndGetJoinCode(m_MaxConnections); while (!serverRelayUtilityTask.IsCompleted) @@ -274,10 +262,7 @@ private void StartClient() NetworkManager.Singleton.OnClientStopped += OnClientStopped; } - if (m_DisconnectClientButton != null) - { - m_DisconnectClientButton.SetActive(true); - } + m_DisconnectClientButton?.SetActive(true); } private void OnClientStopped(bool obj) diff --git a/testproject/Assets/Tests/Manual/Scripts/StatsDisplay.cs b/testproject/Assets/Tests/Manual/Scripts/StatsDisplay.cs index e350450ada..460066554a 100644 --- a/testproject/Assets/Tests/Manual/Scripts/StatsDisplay.cs +++ b/testproject/Assets/Tests/Manual/Scripts/StatsDisplay.cs @@ -54,10 +54,7 @@ public override void OnNetworkSpawn() } else { - if (m_ClientServerToggle != null) - { - m_ClientServerToggle.SetActive(true); - } + m_ClientServerToggle?.SetActive(true); UpdateButton(); } diff --git a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs index 8176acbb3d..c18dac2e8b 100644 --- a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs +++ b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs @@ -249,30 +249,35 @@ private void ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest private int m_ClientConnectedInvocations; + private int m_PeerConnectedInvocations; + /// /// Tests that the OnClientConnectedCallback is invoked when scene management is enabled and disabled /// /// [UnityTest] - public IEnumerator ClientConnectedCallbackTest([Values(true, false)] bool enableSceneManagement) + public IEnumerator ClientConnectedCallbackTest([Values(true, false)] bool enableSceneManagement, [Values(true, false)] bool isHost) { m_ServerClientConnectedInvocations = 0; m_ClientConnectedInvocations = 0; + m_PeerConnectedInvocations = 0; // Create Host and (numClients) clients Assert.True(NetcodeIntegrationTestHelpers.Create(3, out NetworkManager server, out NetworkManager[] clients)); server.NetworkConfig.EnableSceneManagement = enableSceneManagement; server.OnClientConnectedCallback += Server_OnClientConnectedCallback; + server.OnPeerConnectedCallback += Client_OnPeerConnectedCallback; foreach (var client in clients) { client.NetworkConfig.EnableSceneManagement = enableSceneManagement; client.OnClientConnectedCallback += Client_OnClientConnectedCallback; + client.OnPeerConnectedCallback += Client_OnPeerConnectedCallback; } // Start the instances - if (!NetcodeIntegrationTestHelpers.Start(true, server, clients)) + if (!NetcodeIntegrationTestHelpers.Start(isHost, server, clients)) { Assert.Fail("Failed to start instances"); } @@ -281,15 +286,20 @@ public IEnumerator ClientConnectedCallbackTest([Values(true, false)] bool enable yield return NetcodeIntegrationTestHelpers.WaitForClientsConnected(clients, null, 512); // [Host-Side] Check to make sure all clients are connected - yield return NetcodeIntegrationTestHelpers.WaitForClientsConnectedToServer(server, clients.Length + 1, null, 512); + yield return NetcodeIntegrationTestHelpers.WaitForClientsConnectedToServer(server, isHost ? (clients.Length + 1) : clients.Length, null, 512); Assert.AreEqual(3, m_ClientConnectedInvocations); var timeoutHelper = new TimeoutHelper(2); - yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => m_ServerClientConnectedInvocations == 4, timeoutHelper); - Assert.False(timeoutHelper.TimedOut, $"Timed out waiting for server client connections to reach a count of 4 but only has {m_ServerClientConnectedInvocations}!"); - Assert.AreEqual(4, m_ServerClientConnectedInvocations); + var expectedClientCount = (isHost ? 4 : 3); + yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => m_ServerClientConnectedInvocations == expectedClientCount, timeoutHelper); + Assert.False(timeoutHelper.TimedOut, $"Timed out waiting for server client connections to reach a count of {expectedClientCount} but only has {m_ServerClientConnectedInvocations}!"); + Assert.AreEqual(expectedClientCount, m_ServerClientConnectedInvocations); + + // Host will get peer connection events, server will not + var expectedPeerConnections = isHost ? (4 * (4 - 1)) : (3 * (3 - 1)); + Assert.AreEqual(expectedPeerConnections, m_PeerConnectedInvocations); } private void Client_OnClientConnectedCallback(ulong clientId) @@ -297,6 +307,11 @@ private void Client_OnClientConnectedCallback(ulong clientId) m_ClientConnectedInvocations++; } + private void Client_OnPeerConnectedCallback(ulong clientId) + { + m_PeerConnectedInvocations++; + } + private void Server_OnClientConnectedCallback(ulong clientId) { m_ServerClientConnectedInvocations++; diff --git a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs index 70772445a6..832870d564 100644 --- a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs +++ b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs @@ -333,6 +333,8 @@ public IEnumerator ExtensionMethodRpcTest() yield return new WaitForSeconds(0.1f); } + Debug.Log($"clientMyObjCalled {clientMyObjCalled} && clientMySharedObjCalled {clientMySharedObjCalled} && clientMyObjPassedWithThisRefCalled {clientMyObjPassedWithThisRefCalled} && serverMyObjCalled {serverMyObjCalled} && serverMySharedObjCalled {serverMySharedObjCalled} && serverMyObjPassedWithThisRefCalled {serverMyObjPassedWithThisRefCalled} && serverIntListCalled {serverIntListCalled} && serverStrListCalled {serverStrListCalled}"); + // Verify the test passed Assert.False(timedOut); @@ -1503,4 +1505,3 @@ public static void WriteValueSafe(this FastBufferWriter writer, MySharedObjectRe } } - From 8c960ff3c49787bbba183e4dc5b738f50e2efb56 Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Tue, 14 Nov 2023 13:17:25 -0600 Subject: [PATCH 02/13] Swapping KnownClientIds to PeerClientIds --- .../Runtime/Messaging/Messages/ClientConnectedMessage.cs | 2 +- .../Runtime/Messaging/Messages/ClientDisconnectedMessage.cs | 2 +- .../Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs | 2 +- .../Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs | 2 +- .../Runtime/SceneManagement/NetworkSceneManager.cs | 2 +- .../Tests/Runtime/InvalidConnectionEventsTest.cs | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs index ae4eb5b14e..5893b7c24e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs @@ -25,7 +25,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; - networkManager.ConnectionManager.KnownClientIds.Add(ClientId); + networkManager.ConnectionManager.PeerClientIds.Add(ClientId); networkManager.ConnectionManager.InvokeOnPeerConnectedCallback(ClientId); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs index 369df19a5d..7e9ea663e7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs @@ -25,7 +25,7 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; - networkManager.ConnectionManager.KnownClientIds.Remove(ClientId); + networkManager.ConnectionManager.PeerClientIds.Remove(ClientId); networkManager.ConnectionManager.InvokeOnPeerDisconnectedCallback(ClientId); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs index 9f500b00d7..62bcf0008f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs @@ -43,7 +43,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, } else { - foreach (var clientId in m_NetworkManager.KnownClientIds) + foreach (var clientId in m_NetworkManager.PeerClientIds) { if (clientId == behaviour.NetworkManager.LocalClientId) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs index 114a499fb7..ed769be170 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs @@ -51,7 +51,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, } else { - foreach (var clientId in m_NetworkManager.KnownClientIds) + foreach (var clientId in m_NetworkManager.PeerClientIds) { if (clientId == behaviour.OwnerClientId) { diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 154bbf490f..8f88cf27ef 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -2067,7 +2067,7 @@ private void HandleClientSceneEvent(uint sceneEventId) // Client is now synchronized and fully "connected". This also means the client can send "RPCs" at this time NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(NetworkManager.LocalClientId); - foreach (var peerId in NetworkManager.KnownClientIds) + foreach (var peerId in NetworkManager.PeerClientIds) { if (peerId == NetworkManager.LocalClientId) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs index 705db4fbaa..1a57ec0164 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs @@ -81,7 +81,7 @@ public IEnumerator WhenSendingConnectionApprovedToAlreadyConnectedClient_Connect { var message = new ConnectionApprovedMessage { - KnownClientIds = new NativeArray(0, Allocator.Temp) + PeerClientIds = new NativeArray(0, Allocator.Temp) }; m_ServerNetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, m_ClientNetworkManagers[0].LocalClientId); @@ -150,7 +150,7 @@ public IEnumerator WhenSendingConnectionApprovedFromAnyClient_ConnectionApproved { var message = new ConnectionApprovedMessage { - KnownClientIds = new NativeArray(0, Allocator.Temp) + PeerClientIds = new NativeArray(0, Allocator.Temp) }; m_ClientNetworkManagers[0].ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, m_ServerNetworkManager.LocalClientId); From de27803d2b62b25d2d62f18d0425d7e341a81625 Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Tue, 14 Nov 2023 13:18:52 -0600 Subject: [PATCH 03/13] Same as last commit, but better in that this one includes all the changed files. --- .../Connection/NetworkConnectionManager.cs | 18 +++++------ .../Runtime/Core/NetworkManager.cs | 8 ++--- .../Messages/AddObserverMessage.cs.meta | 3 -- .../Messages/ConnectionApprovedMessage.cs | 18 +++++------ .../RpcTargets/ClientsAndHostRpcTarget.cs | 2 +- .../RpcTargets/NotServerRpcTarget.cs | 2 +- .../Runtime/PeerDisconnectCallbackTests.cs | 30 +++++++++---------- 7 files changed, 39 insertions(+), 42 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AddObserverMessage.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index fa85535306..dbd12a3ec0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -85,7 +85,7 @@ public sealed class NetworkConnectionManager internal Dictionary TransportIdToClientIdMap = new Dictionary(); internal List ConnectedClientsList = new List(); internal List ConnectedClientIds = new List(); - internal HashSet KnownClientIds = new HashSet(); + internal HashSet PeerClientIds = new HashSet(); internal Action ConnectionApprovalCallback; /// @@ -650,13 +650,13 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne { OwnerClientId = ownerClientId, NetworkTick = NetworkManager.LocalTime.Tick, - KnownClientIds = new NativeArray(KnownClientIds.Count, Allocator.Temp) + PeerClientIds = new NativeArray(PeerClientIds.Count, Allocator.Temp) }; var i = 0; - foreach (var clientId in KnownClientIds) + foreach (var clientId in PeerClientIds) { - message.KnownClientIds[i] = clientId; + message.PeerClientIds[i] = clientId; ++i; } @@ -686,7 +686,7 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); message.MessageVersions.Dispose(); - message.KnownClientIds.Dispose(); + message.PeerClientIds.Dispose(); // If scene management is disabled, then we are done and notify the local host-server the client is connected if (!NetworkManager.NetworkConfig.EnableSceneManagement) @@ -783,7 +783,7 @@ internal NetworkClient AddClient(ulong clientId) var message = new ClientConnectedMessage { ClientId = clientId }; NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); ConnectedClientIds.Add(clientId); - KnownClientIds.Add(clientId); + PeerClientIds.Add(clientId); return networkClient; } @@ -887,7 +887,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) } ConnectedClientIds.Remove(clientId); - KnownClientIds.Remove(clientId); + PeerClientIds.Remove(clientId); var message = new ClientDisconnectedMessage { ClientId = clientId }; NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); } @@ -978,7 +978,7 @@ internal void Initialize(NetworkManager networkManager) ConnectedClients.Clear(); ConnectedClientsList.Clear(); ConnectedClientIds.Clear(); - KnownClientIds.Clear(); + PeerClientIds.Clear(); ClientIdToTransportIdMap.Clear(); TransportIdToClientIdMap.Clear(); ClientsToApprove.Clear(); @@ -1001,7 +1001,7 @@ internal void Shutdown() { LocalClient.IsApproved = false; LocalClient.IsConnected = false; - KnownClientIds.Clear(); + PeerClientIds.Clear(); if (LocalClient.IsServer) { // make sure all messages are flushed before transport disconnect clients diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 2d103be38e..78258ea3cd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -110,13 +110,13 @@ public ulong LocalClientId public IReadOnlyList ConnectedClientsIds => IsServer ? ConnectionManager.ConnectedClientIds : throw new NotServerException($"{nameof(ConnectionManager.ConnectedClientIds)} should only be accessed on server."); /// - /// Gets a list of just the IDs of all known clients. + /// Gets a list of just the IDs of all known peers. /// This is accessible from client, server, and host. /// Currently, this set contains the list of all client ids connected to the server, but this set /// should be treated as the set of ids that a given process is allowed to send messages to, /// and should not be depended on to always contain the full set of ids connected to the server in the future. /// - public IReadOnlyCollection KnownClientIds => ConnectionManager.KnownClientIds; + public IReadOnlyCollection PeerClientIds => ConnectionManager.PeerClientIds; /// /// Gets the local for this client. @@ -137,7 +137,7 @@ public ulong LocalClientId /// /// Gets whether or not the current server (local or remote) is a host - i.e., also a client /// - public bool ServerIsHost => ConnectionManager.KnownClientIds.Contains(ServerClientId); + public bool ServerIsHost => ConnectionManager.PeerClientIds.Contains(ServerClientId); /// /// Gets Whether or not a client is running @@ -960,7 +960,7 @@ public bool StartHost() private void HostServerInitialize() { LocalClientId = ServerClientId; - ConnectionManager.KnownClientIds.Add(ServerClientId); + ConnectionManager.PeerClientIds.Add(ServerClientId); NetworkMetrics.SetConnectionId(LocalClientId); MessageManager.SetLocalClientId(LocalClientId); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AddObserverMessage.cs.meta b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AddObserverMessage.cs.meta deleted file mode 100644 index f7649ca357..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/AddObserverMessage.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 3653bc4a709247a3b84b79d4a758f9d5 -timeCreated: 1699310795 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 5933f358d1..e050f979fc 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -18,7 +18,7 @@ internal struct ConnectionApprovedMessage : INetworkMessage public NativeArray MessageVersions; - public NativeArray KnownClientIds; + public NativeArray PeerClientIds; public void Serialize(FastBufferWriter writer, int targetVersion) { @@ -41,7 +41,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) if (targetVersion >= k_VersionAddClientIds) { - writer.WriteValueSafe(KnownClientIds); + writer.WriteValueSafe(PeerClientIds); } uint sceneObjectCount = 0; @@ -120,11 +120,11 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int if (receivedMessageVersion >= k_VersionAddClientIds) { - reader.ReadValueSafe(out KnownClientIds, Allocator.TempJob); + reader.ReadValueSafe(out PeerClientIds, Allocator.TempJob); } else { - KnownClientIds = new NativeArray(0, Allocator.TempJob); + PeerClientIds = new NativeArray(0, Allocator.TempJob); } m_ReceivedSceneObjectData = reader; @@ -148,10 +148,10 @@ public void Handle(ref NetworkContext context) // Stop the client-side approval timeout coroutine since we are approved. networkManager.ConnectionManager.StopClientApprovalCoroutine(); - networkManager.ConnectionManager.KnownClientIds.Clear(); - foreach (var clientId in KnownClientIds) + networkManager.ConnectionManager.PeerClientIds.Clear(); + foreach (var clientId in PeerClientIds) { - networkManager.ConnectionManager.KnownClientIds.Add(clientId); + networkManager.ConnectionManager.PeerClientIds.Add(clientId); } // Only if scene management is disabled do we handle NetworkObject synchronization at this point @@ -174,7 +174,7 @@ public void Handle(ref NetworkContext context) // When scene management is disabled we notify after everything is synchronized networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId); - foreach (var clientId in KnownClientIds) + foreach (var clientId in PeerClientIds) { if (clientId == networkManager.LocalClientId) { @@ -184,7 +184,7 @@ public void Handle(ref NetworkContext context) } } - KnownClientIds.Dispose(); + PeerClientIds.Dispose(); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs index 5d5585c0b6..bb32620e1e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs @@ -13,7 +13,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, { if (m_UnderlyingTarget == null) { - if (behaviour.NetworkManager.ConnectionManager.KnownClientIds.Contains(NetworkManager.ServerClientId)) + if (behaviour.NetworkManager.ConnectionManager.PeerClientIds.Contains(NetworkManager.ServerClientId)) { m_UnderlyingTarget = behaviour.RpcTarget.Everyone; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs index 695ac11386..ce53fa772a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs @@ -44,7 +44,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, } else { - foreach (var clientId in m_NetworkManager.KnownClientIds) + foreach (var clientId in m_NetworkManager.PeerClientIds) { if (clientId == NetworkManager.ServerClientId) { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs index c744900868..49fd9eb204 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs @@ -87,21 +87,21 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie client.OnPeerDisconnectCallback += OnPeerDisconnectCallback; if(m_UseHost) { - Assert.IsTrue(client.KnownClientIds.Contains(0ul)); + Assert.IsTrue(client.PeerClientIds.Contains(0ul)); } - Assert.IsTrue(client.KnownClientIds.Contains(1ul)); - Assert.IsTrue(client.KnownClientIds.Contains(2ul)); - Assert.IsTrue(client.KnownClientIds.Contains(3ul)); + Assert.IsTrue(client.PeerClientIds.Contains(1ul)); + Assert.IsTrue(client.PeerClientIds.Contains(2ul)); + Assert.IsTrue(client.PeerClientIds.Contains(3ul)); } m_ServerNetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; m_ServerNetworkManager.OnPeerDisconnectCallback += OnPeerDisconnectCallback; if(m_UseHost) { - Assert.IsTrue(m_ServerNetworkManager.KnownClientIds.Contains(0ul)); + Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(0ul)); } - Assert.IsTrue(m_ServerNetworkManager.KnownClientIds.Contains(1ul)); - Assert.IsTrue(m_ServerNetworkManager.KnownClientIds.Contains(2ul)); - Assert.IsTrue(m_ServerNetworkManager.KnownClientIds.Contains(3ul)); + Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(1ul)); + Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(2ul)); + Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(3ul)); // Set up a WaitForMessageReceived hook. // In some cases the message will be received during StopOneClient, but it is not guaranteed @@ -134,40 +134,40 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie { if (!client.IsConnectedClient) { - Assert.IsEmpty(client.KnownClientIds); + Assert.IsEmpty(client.PeerClientIds); continue; } if(m_UseHost) { - Assert.IsTrue(client.KnownClientIds.Contains(0ul)); + Assert.IsTrue(client.PeerClientIds.Contains(0ul)); } for (var i = 1ul; i < 3ul; ++i) { if (i == disconnectedClient) { - Assert.IsFalse(client.KnownClientIds.Contains(i)); + Assert.IsFalse(client.PeerClientIds.Contains(i)); } else { - Assert.IsTrue(client.KnownClientIds.Contains(i)); + Assert.IsTrue(client.PeerClientIds.Contains(i)); } } } if(m_UseHost) { - Assert.IsTrue(m_ServerNetworkManager.KnownClientIds.Contains(0ul)); + Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(0ul)); } for (var i = 1ul; i < 3ul; ++i) { if (i == disconnectedClient) { - Assert.IsFalse(m_ServerNetworkManager.KnownClientIds.Contains(i)); + Assert.IsFalse(m_ServerNetworkManager.PeerClientIds.Contains(i)); } else { - Assert.IsTrue(m_ServerNetworkManager.KnownClientIds.Contains(i)); + Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(i)); } } From 25d2f18eac9d8dd2781b344d6e9d01aaa49eae6b Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Tue, 14 Nov 2023 13:41:55 -0600 Subject: [PATCH 04/13] style and changelog --- com.unity.netcode.gameobjects/CHANGELOG.md | 7 +++++++ .../Runtime/Core/NetworkBehaviour.cs | 4 ++++ .../Runtime/Core/NetworkManager.cs | 4 ++++ .../Runtime/Messaging/RpcTargets/RpcTarget.cs | 12 ++++++------ .../Tests/Runtime/PeerDisconnectCallbackTests.cs | 10 +++++----- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 3330589993..252b1fea9c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,13 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added: Added a new RPC attribute, which is simply `Rpc`. (#2762) + - This is a generic attribute that can perform the functions of both Server and Client RPCs, as well as enabling client-to-client RPCs. Includes several default targets: `Server`, `NotServer`, `Owner`, `NotOwner`, `Me`, `NotMe`, `ClientsAndHost`, and `Everyone`. Runtime overrides are available for any of these targets, as well as for sending to a specific ID or groups of IDs. + - This attribute also includes the ability to defer RPCs that are sent to the local process to the start of the next frame instead of executing them immediately, treating them as if they had gone across the network. The default behavior is to execute immediately. + - This attribute effectively replaces `ServerRpc` and `ClientRpc`. `ServerRpc` and `ClientRpc` remain in their existing forms for backward compatibility, but `Rpc` will be the recommended and most supported option. +- Added: `NetworkManager.PeerClientIds` containing the client IDs of other clients connected to the same server (#2762) +- Added: `NetworkManager.OnPeerConnectedCallback` and `NetworkManager.OnPeerDisconnectCallback` so clients can respond to connection and disconnection of other clients (#2762) +- Added: `NetworkManager.ServerIsHost` and `NetworkBehaviour.ServerIsHost` to allow a client to tell if it is connected to a host or to a dedicated server (#2762) - Added `SceneEventProgress.SceneManagementNotEnabled` return status to be returned when a `NetworkSceneManager` method is invoked and scene management is not enabled. (#2735) - Added `SceneEventProgress.ServerOnlyAction` return status to be returned when a `NetworkSceneManager` method is invoked by a client. (#2735) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 1f7507a59e..946f905a5d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -422,6 +422,9 @@ public NetworkManager NetworkManager } } + // This erroneously tries to simplify these method references but the docs do not pick it up correctly + // because they try to resolve it on the field rather than the class of the same name. +#pragma warning disable IDE0001 /// /// Provides access to the various targets at runtime, as well as /// runtime-bound targets like , @@ -430,6 +433,7 @@ public NetworkManager NetworkManager /// , and /// /// +#pragma warning restore IDE0001 public RpcTarget RpcTarget => NetworkManager.RpcTarget; /// diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 3d1c0e256e..d6b2a99cbe 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -414,6 +414,9 @@ public NetworkPrefabHandler PrefabHandler internal IDeferredNetworkMessageManager DeferredMessageManager { get; private set; } + // This erroneously tries to simplify these method references but the docs do not pick it up correctly + // because they try to resolve it on the field rather than the class of the same name. +#pragma warning disable IDE0001 /// /// Provides access to the various targets at runtime, as well as /// runtime-bound targets like , @@ -422,6 +425,7 @@ public NetworkPrefabHandler PrefabHandler /// , and /// /// +#pragma warning restore IDE0001 public RpcTarget RpcTarget; /// diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs index d5ae7a2a75..f916a07b19 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Unity.Collections; namespace Unity.Netcode @@ -62,11 +62,11 @@ public enum SendTo /// /// Implementations of the various options, as well as additional runtime-only options - /// , - /// , - /// , - /// , and - /// + /// , + /// , + /// , + /// , and + /// /// public class RpcTarget { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs index 49fd9eb204..612abaf8dc 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs @@ -85,7 +85,7 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie { client.OnClientDisconnectCallback += OnClientDisconnectCallback; client.OnPeerDisconnectCallback += OnPeerDisconnectCallback; - if(m_UseHost) + if (m_UseHost) { Assert.IsTrue(client.PeerClientIds.Contains(0ul)); } @@ -95,7 +95,7 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie } m_ServerNetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; m_ServerNetworkManager.OnPeerDisconnectCallback += OnPeerDisconnectCallback; - if(m_UseHost) + if (m_UseHost) { Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(0ul)); } @@ -124,7 +124,7 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie } else { - yield return StopOneClient(m_ClientNetworkManagers[disconnectedClient-1]); + yield return StopOneClient(m_ClientNetworkManagers[disconnectedClient - 1]); } yield return WaitForConditionOrTimeOut(hooks); @@ -137,7 +137,7 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie Assert.IsEmpty(client.PeerClientIds); continue; } - if(m_UseHost) + if (m_UseHost) { Assert.IsTrue(client.PeerClientIds.Contains(0ul)); } @@ -154,7 +154,7 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie } } } - if(m_UseHost) + if (m_UseHost) { Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(0ul)); } From ba5d712b20bb680ec81726baf74c5a088c321d74 Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 16 Nov 2023 15:44:14 -0600 Subject: [PATCH 05/13] - Merged PeerClientIds into ConnectedClientsIds - Replaced OnPeerConnectedCallback and OnPeerDisconnectCallback with unified OnConnectionEvent --- com.unity.netcode.gameobjects/CHANGELOG.md | 7 +- .../Connection/NetworkConnectionManager.cs | 185 +++++++++++++----- .../Runtime/Core/NetworkManager.cs | 37 ++-- .../Messages/ClientConnectedMessage.cs | 7 +- .../Messages/ClientDisconnectedMessage.cs | 7 +- .../Messages/ConnectionApprovedMessage.cs | 25 +-- .../RpcTargets/ClientsAndHostRpcTarget.cs | 2 +- .../Messaging/RpcTargets/NotMeRpcTarget.cs | 2 +- .../Messaging/RpcTargets/NotOwnerRpcTarget.cs | 2 +- .../RpcTargets/NotServerRpcTarget.cs | 2 +- .../SceneManagement/NetworkSceneManager.cs | 9 - .../Runtime/InvalidConnectionEventsTest.cs | 4 +- .../Runtime/PeerDisconnectCallbackTests.cs | 57 +++--- .../Runtime/MultiClientConnectionApproval.cs | 51 ++++- 14 files changed, 255 insertions(+), 142 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 252b1fea9c..7f56292c27 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,12 +14,15 @@ Additional documentation and release notes are available at [Multiplayer Documen - This is a generic attribute that can perform the functions of both Server and Client RPCs, as well as enabling client-to-client RPCs. Includes several default targets: `Server`, `NotServer`, `Owner`, `NotOwner`, `Me`, `NotMe`, `ClientsAndHost`, and `Everyone`. Runtime overrides are available for any of these targets, as well as for sending to a specific ID or groups of IDs. - This attribute also includes the ability to defer RPCs that are sent to the local process to the start of the next frame instead of executing them immediately, treating them as if they had gone across the network. The default behavior is to execute immediately. - This attribute effectively replaces `ServerRpc` and `ClientRpc`. `ServerRpc` and `ClientRpc` remain in their existing forms for backward compatibility, but `Rpc` will be the recommended and most supported option. -- Added: `NetworkManager.PeerClientIds` containing the client IDs of other clients connected to the same server (#2762) -- Added: `NetworkManager.OnPeerConnectedCallback` and `NetworkManager.OnPeerDisconnectCallback` so clients can respond to connection and disconnection of other clients (#2762) +- Added: `NetworkManager.OnConnectionEvent` as a unified connection event callback to notify clients and servers of all client connections and disconnections within the session (#2762) - Added: `NetworkManager.ServerIsHost` and `NetworkBehaviour.ServerIsHost` to allow a client to tell if it is connected to a host or to a dedicated server (#2762) - Added `SceneEventProgress.SceneManagementNotEnabled` return status to be returned when a `NetworkSceneManager` method is invoked and scene management is not enabled. (#2735) - Added `SceneEventProgress.ServerOnlyAction` return status to be returned when a `NetworkSceneManager` method is invoked by a client. (#2735) +### Changed + +- `NetworkManager.ConnectedClientsIds` is now accessible on the client side and will contain the list of all clients in the session, including the host client if the server is operating in host mode (#2762) + ### Fixed - Fixed a bug where having a class with Rpcs that inherits from a class without Rpcs that inherits from NetworkVariable would cause a compile error. (#2751) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index dbd12a3ec0..f8b99bd64c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -10,6 +10,38 @@ namespace Unity.Netcode { + + public enum ConnectionEvent + { + ClientConnected, + PeerConnected, + ClientDisconnected, + PeerDisconnected + } + + public struct ConnectionEventData + { + public ConnectionEvent EventType; + + /// + /// The client ID for the client that just connected + /// For the and + /// events on the client side, this will be LocalClientId. + /// On the server side, this will be the ID of the client that just connected. + /// + /// For the and + /// events on the client side, this will be the client ID assigned by the server to the remote peer. + /// + public ulong ClientId; + + /// + /// This is only populated in on the client side, and + /// contains the list of other peers who were present before you connected. In all other situations, + /// this array will be uninitialized. + /// + public NativeArray PeerClientIds; + } + /// /// The NGO connection manager handles: /// - Client Connections @@ -45,17 +77,102 @@ public sealed class NetworkConnectionManager /// /// The callback to invoke once a peer connects. This callback is only ran on the server and on the local client that connects. /// - public event Action OnPeerConnectedCallback = null; + public event Action OnConnectionEvent = null; - /// - /// The callback to invoke when a peer disconnects. This callback is only ran on the server and on the local client that disconnects. - /// - public event Action OnPeerDisconnectCallback = null; - internal void InvokeOnClientConnectedCallback(ulong clientId) => OnClientConnectedCallback?.Invoke(clientId); + internal void InvokeOnClientConnectedCallback(ulong clientId) + { + try + { + OnClientConnectedCallback?.Invoke(clientId); + } + catch (Exception exception) + { + Debug.LogException(exception); + } + + if (!NetworkManager.IsServer) + { + var peerClientIds = new NativeArray(NetworkManager.ConnectedClientsIds.Count - 1, Allocator.Temp); + // `using var peerClientIds` or `using(peerClientIds)` renders it immutable... + using var sentinel = peerClientIds; + + var idx = 0; + foreach (var peerId in NetworkManager.ConnectedClientsIds) + { + if (peerId == NetworkManager.LocalClientId) + { + continue; + } + + peerClientIds[idx] = peerId; + ++idx; + } + + try + { + OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = NetworkManager.LocalClientId, EventType = ConnectionEvent.ClientConnected, PeerClientIds = peerClientIds }); + } + catch (Exception exception) + { + Debug.LogException(exception); + } + } + else + { + try + { + OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData{ClientId = clientId, EventType = ConnectionEvent.ClientConnected}); + } + catch (Exception exception) + { + Debug.LogException(exception); + } + } + } + + internal void InvokeOnClientDisconnectCallback(ulong clientId) + { + try + { + OnClientDisconnectCallback?.Invoke(clientId); + } + catch (Exception exception) + { + Debug.LogException(exception); + } + try + { + OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData{ClientId = clientId, EventType = ConnectionEvent.ClientDisconnected}); + } + catch (Exception exception) + { + Debug.LogException(exception); + } + } - internal void InvokeOnPeerConnectedCallback(ulong clientId) => OnPeerConnectedCallback?.Invoke(clientId); - internal void InvokeOnPeerDisconnectedCallback(ulong clientId) => OnPeerDisconnectCallback?.Invoke(clientId); + internal void InvokeOnPeerConnectedCallback(ulong clientId) + { + try + { + OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData{ClientId = clientId, EventType = ConnectionEvent.PeerConnected}); + } + catch (Exception exception) + { + Debug.LogException(exception); + } + } + internal void InvokeOnPeerDisconnectedCallback(ulong clientId) + { + try + { + OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData{ClientId = clientId, EventType = ConnectionEvent.PeerDisconnected}); + } + catch (Exception exception) + { + Debug.LogException(exception); + } + } /// /// The callback to invoke if the fails. @@ -85,7 +202,6 @@ public sealed class NetworkConnectionManager internal Dictionary TransportIdToClientIdMap = new Dictionary(); internal List ConnectedClientsList = new List(); internal List ConnectedClientIds = new List(); - internal HashSet PeerClientIds = new HashSet(); internal Action ConnectionApprovalCallback; /// @@ -369,25 +485,11 @@ internal void DisconnectEventHandler(ulong transportClientId) // Process the incoming message queue so that we get everything from the server disconnecting us or, if we are the server, so we got everything from that client. MessageManager.ProcessIncomingMessageQueue(); - try - { - OnClientDisconnectCallback?.Invoke(clientId); - } - catch (Exception exception) - { - Debug.LogException(exception); - } + InvokeOnClientDisconnectCallback(clientId); if (LocalClient.IsHost) { - try - { - OnPeerDisconnectCallback?.Invoke(clientId); - } - catch (Exception exception) - { - Debug.LogException(exception); - } + InvokeOnPeerDisconnectedCallback(clientId); } if (LocalClient.IsServer) @@ -650,13 +752,13 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne { OwnerClientId = ownerClientId, NetworkTick = NetworkManager.LocalTime.Tick, - PeerClientIds = new NativeArray(PeerClientIds.Count, Allocator.Temp) + ConnectedClientIds = new NativeArray(ConnectedClientIds.Count, Allocator.Temp) }; var i = 0; - foreach (var clientId in PeerClientIds) + foreach (var clientId in ConnectedClientIds) { - message.PeerClientIds[i] = clientId; + message.ConnectedClientIds[i] = clientId; ++i; } @@ -686,7 +788,7 @@ internal void HandleConnectionApproval(ulong ownerClientId, NetworkManager.Conne SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); message.MessageVersions.Dispose(); - message.PeerClientIds.Dispose(); + message.ConnectedClientIds.Dispose(); // If scene management is disabled, then we are done and notify the local host-server the client is connected if (!NetworkManager.NetworkConfig.EnableSceneManagement) @@ -783,7 +885,6 @@ internal NetworkClient AddClient(ulong clientId) var message = new ClientConnectedMessage { ClientId = clientId }; NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); ConnectedClientIds.Add(clientId); - PeerClientIds.Add(clientId); return networkClient; } @@ -887,7 +988,6 @@ internal void OnClientDisconnectFromServer(ulong clientId) } ConnectedClientIds.Remove(clientId); - PeerClientIds.Remove(clientId); var message = new ClientDisconnectedMessage { ClientId = clientId }; NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds); } @@ -898,25 +998,11 @@ internal void OnClientDisconnectFromServer(ulong clientId) var transportId = ClientIdToTransportId(clientId); NetworkManager.NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId); - try - { - OnClientDisconnectCallback?.Invoke(clientId); - } - catch (Exception exception) - { - Debug.LogException(exception); - } + InvokeOnClientDisconnectCallback(clientId); if (LocalClient.IsHost) { - try - { - OnPeerDisconnectCallback?.Invoke(clientId); - } - catch (Exception exception) - { - Debug.LogException(exception); - } + InvokeOnPeerDisconnectedCallback(clientId); } // Clean up the transport to client (and vice versa) mappings @@ -978,7 +1064,6 @@ internal void Initialize(NetworkManager networkManager) ConnectedClients.Clear(); ConnectedClientsList.Clear(); ConnectedClientIds.Clear(); - PeerClientIds.Clear(); ClientIdToTransportIdMap.Clear(); TransportIdToClientIdMap.Clear(); ClientsToApprove.Clear(); @@ -1001,7 +1086,9 @@ internal void Shutdown() { LocalClient.IsApproved = false; LocalClient.IsConnected = false; - PeerClientIds.Clear(); + ConnectedClients.Clear(); + ConnectedClientIds.Clear(); + ConnectedClientsList.Clear(); if (LocalClient.IsServer) { // make sure all messages are flushed before transport disconnect clients diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index d6b2a99cbe..f0ce45982e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -107,16 +107,7 @@ public ulong LocalClientId /// /// Gets a list of just the IDs of all connected clients. This is only accessible on the server. /// - public IReadOnlyList ConnectedClientsIds => IsServer ? ConnectionManager.ConnectedClientIds : throw new NotServerException($"{nameof(ConnectionManager.ConnectedClientIds)} should only be accessed on server."); - - /// - /// Gets a list of just the IDs of all known peers. - /// This is accessible from client, server, and host. - /// Currently, this set contains the list of all client ids connected to the server, but this set - /// should be treated as the set of ids that a given process is allowed to send messages to, - /// and should not be depended on to always contain the full set of ids connected to the server in the future. - /// - public IReadOnlyCollection PeerClientIds => ConnectionManager.PeerClientIds; + public IReadOnlyList ConnectedClientsIds => ConnectionManager.ConnectedClientIds; /// /// Gets the local for this client. @@ -137,7 +128,7 @@ public ulong LocalClientId /// /// Gets whether or not the current server (local or remote) is a host - i.e., also a client /// - public bool ServerIsHost => ConnectionManager.PeerClientIds.Contains(ServerClientId); + public bool ServerIsHost => ConnectionManager.ConnectedClientIds.Contains(ServerClientId); /// /// Gets Whether or not a client is running @@ -226,6 +217,8 @@ public Action ConnectionA /// /// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects. + /// + /// It is recommended to use OnConnectionEvent instead, as this will eventually be deprecated /// public event Action OnClientConnectedCallback { @@ -235,6 +228,8 @@ public event Action OnClientConnectedCallback /// /// The callback to invoke when a client disconnects. This callback is only ran on the server and on the local client that disconnects. + /// + /// It is recommended to use OnConnectionEvent instead, as this will eventually be deprecated /// public event Action OnClientDisconnectCallback { @@ -243,21 +238,13 @@ public event Action OnClientDisconnectCallback } /// - /// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects. - /// - public event Action OnPeerConnectedCallback - { - add => ConnectionManager.OnPeerConnectedCallback += value; - remove => ConnectionManager.OnPeerConnectedCallback -= value; - } - - /// - /// The callback to invoke when a client disconnects. This callback is only ran on the server and on the local client that disconnects. + /// The callback to invoke on any connection event. See and + /// for more info. /// - public event Action OnPeerDisconnectCallback + public event Action OnConnectionEvent { - add => ConnectionManager.OnPeerDisconnectCallback += value; - remove => ConnectionManager.OnPeerDisconnectCallback -= value; + add => ConnectionManager.OnConnectionEvent += value; + remove => ConnectionManager.OnConnectionEvent -= value; } /// @@ -979,7 +966,7 @@ public bool StartHost() private void HostServerInitialize() { LocalClientId = ServerClientId; - ConnectionManager.PeerClientIds.Add(ServerClientId); + //ConnectionManager.ConnectedClientIds.Add(ServerClientId); NetworkMetrics.SetConnectionId(LocalClientId); MessageManager.SetLocalClientId(LocalClientId); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs index 5893b7c24e..92ed2eb298 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientConnectedMessage.cs @@ -25,8 +25,11 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; - networkManager.ConnectionManager.PeerClientIds.Add(ClientId); - networkManager.ConnectionManager.InvokeOnPeerConnectedCallback(ClientId); + networkManager.ConnectionManager.ConnectedClientIds.Add(ClientId); + if (networkManager.IsConnectedClient) + { + networkManager.ConnectionManager.InvokeOnPeerConnectedCallback(ClientId); + } } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs index 7e9ea663e7..9d306cbb56 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ClientDisconnectedMessage.cs @@ -25,8 +25,11 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; - networkManager.ConnectionManager.PeerClientIds.Remove(ClientId); - networkManager.ConnectionManager.InvokeOnPeerDisconnectedCallback(ClientId); + networkManager.ConnectionManager.ConnectedClientIds.Remove(ClientId); + if (networkManager.IsConnectedClient) + { + networkManager.ConnectionManager.InvokeOnPeerDisconnectedCallback(ClientId); + } } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index e050f979fc..e0a42fd936 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -18,7 +18,7 @@ internal struct ConnectionApprovedMessage : INetworkMessage public NativeArray MessageVersions; - public NativeArray PeerClientIds; + public NativeArray ConnectedClientIds; public void Serialize(FastBufferWriter writer, int targetVersion) { @@ -41,7 +41,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) if (targetVersion >= k_VersionAddClientIds) { - writer.WriteValueSafe(PeerClientIds); + writer.WriteValueSafe(ConnectedClientIds); } uint sceneObjectCount = 0; @@ -120,11 +120,11 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int if (receivedMessageVersion >= k_VersionAddClientIds) { - reader.ReadValueSafe(out PeerClientIds, Allocator.TempJob); + reader.ReadValueSafe(out ConnectedClientIds, Allocator.TempJob); } else { - PeerClientIds = new NativeArray(0, Allocator.TempJob); + ConnectedClientIds = new NativeArray(0, Allocator.TempJob); } m_ReceivedSceneObjectData = reader; @@ -148,10 +148,10 @@ public void Handle(ref NetworkContext context) // Stop the client-side approval timeout coroutine since we are approved. networkManager.ConnectionManager.StopClientApprovalCoroutine(); - networkManager.ConnectionManager.PeerClientIds.Clear(); - foreach (var clientId in PeerClientIds) + networkManager.ConnectionManager.ConnectedClientIds.Clear(); + foreach (var clientId in ConnectedClientIds) { - networkManager.ConnectionManager.PeerClientIds.Add(clientId); + networkManager.ConnectionManager.ConnectedClientIds.Add(clientId); } // Only if scene management is disabled do we handle NetworkObject synchronization at this point @@ -173,18 +173,9 @@ public void Handle(ref NetworkContext context) networkManager.IsConnectedClient = true; // When scene management is disabled we notify after everything is synchronized networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId); - - foreach (var clientId in PeerClientIds) - { - if (clientId == networkManager.LocalClientId) - { - continue; - } - networkManager.ConnectionManager.InvokeOnPeerConnectedCallback(clientId); - } } - PeerClientIds.Dispose(); + ConnectedClientIds.Dispose(); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs index bb32620e1e..aa9d0a9c37 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs @@ -13,7 +13,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, { if (m_UnderlyingTarget == null) { - if (behaviour.NetworkManager.ConnectionManager.PeerClientIds.Contains(NetworkManager.ServerClientId)) + if (behaviour.NetworkManager.ConnectionManager.ConnectedClientIds.Contains(NetworkManager.ServerClientId)) { m_UnderlyingTarget = behaviour.RpcTarget.Everyone; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs index 62bcf0008f..50f81f2efe 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotMeRpcTarget.cs @@ -43,7 +43,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, } else { - foreach (var clientId in m_NetworkManager.PeerClientIds) + foreach (var clientId in m_NetworkManager.ConnectedClientsIds) { if (clientId == behaviour.NetworkManager.LocalClientId) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs index ed769be170..ece1ded9af 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotOwnerRpcTarget.cs @@ -51,7 +51,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, } else { - foreach (var clientId in m_NetworkManager.PeerClientIds) + foreach (var clientId in m_NetworkManager.ConnectedClientsIds) { if (clientId == behaviour.OwnerClientId) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs index ce53fa772a..d348660cd9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/NotServerRpcTarget.cs @@ -44,7 +44,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, } else { - foreach (var clientId in m_NetworkManager.PeerClientIds) + foreach (var clientId in m_NetworkManager.ConnectedClientsIds) { if (clientId == NetworkManager.ServerClientId) { diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 6f2ee47f10..732f7541fc 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -2115,15 +2115,6 @@ private void HandleClientSceneEvent(uint sceneEventId) // Client is now synchronized and fully "connected". This also means the client can send "RPCs" at this time NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(NetworkManager.LocalClientId); - foreach (var peerId in NetworkManager.PeerClientIds) - { - if (peerId == NetworkManager.LocalClientId) - { - continue; - } - NetworkManager.ConnectionManager.InvokeOnPeerConnectedCallback(peerId); - } - // Notify the client that they have finished synchronizing OnSceneEvent?.Invoke(new SceneEvent() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs index 1a57ec0164..220485229e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/InvalidConnectionEventsTest.cs @@ -81,7 +81,7 @@ public IEnumerator WhenSendingConnectionApprovedToAlreadyConnectedClient_Connect { var message = new ConnectionApprovedMessage { - PeerClientIds = new NativeArray(0, Allocator.Temp) + ConnectedClientIds = new NativeArray(0, Allocator.Temp) }; m_ServerNetworkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, m_ClientNetworkManagers[0].LocalClientId); @@ -150,7 +150,7 @@ public IEnumerator WhenSendingConnectionApprovedFromAnyClient_ConnectionApproved { var message = new ConnectionApprovedMessage { - PeerClientIds = new NativeArray(0, Allocator.Temp) + ConnectedClientIds = new NativeArray(0, Allocator.Temp) }; m_ClientNetworkManagers[0].ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, m_ServerNetworkManager.LocalClientId); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs index 612abaf8dc..aea5149053 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs @@ -68,14 +68,19 @@ protected override IEnumerator OnSetup() return base.OnSetup(); } - private void OnClientDisconnectCallback(ulong obj) + private void OnConnectionEventCallback(NetworkManager networkManager, ConnectionEventData data) { - ++m_ClientDisconnectCount; - } - - private void OnPeerDisconnectCallback(ulong obj) - { - ++m_PeerDisconnectCount; + switch (data.EventType) + { + case ConnectionEvent.ClientDisconnected: + Assert.IsFalse(data.PeerClientIds.IsCreated); + ++m_ClientDisconnectCount; + break; + case ConnectionEvent.PeerDisconnected: + Assert.IsFalse(data.PeerClientIds.IsCreated); + ++m_PeerDisconnectCount; + break; + } } [UnityTest] @@ -83,25 +88,25 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie { foreach (var client in m_ClientNetworkManagers) { - client.OnClientDisconnectCallback += OnClientDisconnectCallback; - client.OnPeerDisconnectCallback += OnPeerDisconnectCallback; + client.OnConnectionEvent += OnConnectionEventCallback; if (m_UseHost) { - Assert.IsTrue(client.PeerClientIds.Contains(0ul)); + Assert.IsTrue(client.ConnectedClientsIds.Contains(0ul)); } - Assert.IsTrue(client.PeerClientIds.Contains(1ul)); - Assert.IsTrue(client.PeerClientIds.Contains(2ul)); - Assert.IsTrue(client.PeerClientIds.Contains(3ul)); + Assert.IsTrue(client.ConnectedClientsIds.Contains(1ul)); + Assert.IsTrue(client.ConnectedClientsIds.Contains(2ul)); + Assert.IsTrue(client.ConnectedClientsIds.Contains(3ul)); + Assert.AreEqual(client.ServerIsHost, m_UseHost); } - m_ServerNetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; - m_ServerNetworkManager.OnPeerDisconnectCallback += OnPeerDisconnectCallback; + m_ServerNetworkManager.OnConnectionEvent += OnConnectionEventCallback; if (m_UseHost) { - Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(0ul)); + Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsIds.Contains(0ul)); } - Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(1ul)); - Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(2ul)); - Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(3ul)); + Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsIds.Contains(1ul)); + Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsIds.Contains(2ul)); + Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsIds.Contains(3ul)); + Assert.AreEqual(m_ServerNetworkManager.ServerIsHost, m_UseHost); // Set up a WaitForMessageReceived hook. // In some cases the message will be received during StopOneClient, but it is not guaranteed @@ -134,40 +139,40 @@ public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clie { if (!client.IsConnectedClient) { - Assert.IsEmpty(client.PeerClientIds); + Assert.IsEmpty(client.ConnectedClientsIds); continue; } if (m_UseHost) { - Assert.IsTrue(client.PeerClientIds.Contains(0ul)); + Assert.IsTrue(client.ConnectedClientsIds.Contains(0ul)); } for (var i = 1ul; i < 3ul; ++i) { if (i == disconnectedClient) { - Assert.IsFalse(client.PeerClientIds.Contains(i)); + Assert.IsFalse(client.ConnectedClientsIds.Contains(i)); } else { - Assert.IsTrue(client.PeerClientIds.Contains(i)); + Assert.IsTrue(client.ConnectedClientsIds.Contains(i)); } } } if (m_UseHost) { - Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(0ul)); + Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsIds.Contains(0ul)); } for (var i = 1ul; i < 3ul; ++i) { if (i == disconnectedClient) { - Assert.IsFalse(m_ServerNetworkManager.PeerClientIds.Contains(i)); + Assert.IsFalse(m_ServerNetworkManager.ConnectedClientsIds.Contains(i)); } else { - Assert.IsTrue(m_ServerNetworkManager.PeerClientIds.Contains(i)); + Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsIds.Contains(i)); } } diff --git a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs index c18dac2e8b..3282482fc5 100644 --- a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs +++ b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Text; using NUnit.Framework; using Unity.Netcode; @@ -247,8 +248,12 @@ private void ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest private int m_ServerClientConnectedInvocations; + private int m_ServerClientConnectedInvocationsThroughConnectionEvent; + private int m_ClientConnectedInvocations; + private int m_ClientConnectedInvocationsThroughConnectionEvent; + private int m_PeerConnectedInvocations; /// @@ -259,7 +264,9 @@ private void ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest public IEnumerator ClientConnectedCallbackTest([Values(true, false)] bool enableSceneManagement, [Values(true, false)] bool isHost) { m_ServerClientConnectedInvocations = 0; + m_ServerClientConnectedInvocationsThroughConnectionEvent = 0; m_ClientConnectedInvocations = 0; + m_ClientConnectedInvocationsThroughConnectionEvent = 0; m_PeerConnectedInvocations = 0; // Create Host and (numClients) clients @@ -267,13 +274,13 @@ public IEnumerator ClientConnectedCallbackTest([Values(true, false)] bool enable server.NetworkConfig.EnableSceneManagement = enableSceneManagement; server.OnClientConnectedCallback += Server_OnClientConnectedCallback; - server.OnPeerConnectedCallback += Client_OnPeerConnectedCallback; + server.OnConnectionEvent += Server_OnConnectionEventCallback; foreach (var client in clients) { client.NetworkConfig.EnableSceneManagement = enableSceneManagement; client.OnClientConnectedCallback += Client_OnClientConnectedCallback; - client.OnPeerConnectedCallback += Client_OnPeerConnectedCallback; + client.OnConnectionEvent += Client_OnConnectionEventCallback; } // Start the instances @@ -290,12 +297,14 @@ public IEnumerator ClientConnectedCallbackTest([Values(true, false)] bool enable Assert.AreEqual(3, m_ClientConnectedInvocations); + Assert.AreEqual(3, m_ClientConnectedInvocationsThroughConnectionEvent); var timeoutHelper = new TimeoutHelper(2); var expectedClientCount = (isHost ? 4 : 3); yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => m_ServerClientConnectedInvocations == expectedClientCount, timeoutHelper); Assert.False(timeoutHelper.TimedOut, $"Timed out waiting for server client connections to reach a count of {expectedClientCount} but only has {m_ServerClientConnectedInvocations}!"); Assert.AreEqual(expectedClientCount, m_ServerClientConnectedInvocations); + Assert.AreEqual(expectedClientCount, m_ServerClientConnectedInvocationsThroughConnectionEvent); // Host will get peer connection events, server will not var expectedPeerConnections = isHost ? (4 * (4 - 1)) : (3 * (3 - 1)); @@ -307,15 +316,49 @@ private void Client_OnClientConnectedCallback(ulong clientId) m_ClientConnectedInvocations++; } - private void Client_OnPeerConnectedCallback(ulong clientId) + + private void Client_OnConnectionEventCallback(NetworkManager networkManager, ConnectionEventData data) { - m_PeerConnectedInvocations++; + switch (data.EventType) + { + case ConnectionEvent.ClientConnected: + m_ClientConnectedInvocationsThroughConnectionEvent++; + Assert.AreEqual(networkManager.LocalClientId, data.ClientId); + Assert.AreEqual(networkManager.ConnectedClientsIds.Count - 1, data.PeerClientIds.Length); + var set = new HashSet(networkManager.ConnectedClientsIds); + for (var i = 0; i < data.PeerClientIds.Length; ++i) + { + Assert.IsTrue(set.Contains(data.PeerClientIds[i])); + // detect duplicates + set.Remove(data.PeerClientIds[i]); + } + Assert.IsTrue(set.Contains(data.ClientId)); + m_PeerConnectedInvocations += data.PeerClientIds.Length; + break; + case ConnectionEvent.PeerConnected: + m_PeerConnectedInvocations++; + break; + } } private void Server_OnClientConnectedCallback(ulong clientId) { m_ServerClientConnectedInvocations++; } + private void Server_OnConnectionEventCallback(NetworkManager networkManager, ConnectionEventData data) + { + switch (data.EventType) + { + case ConnectionEvent.ClientConnected: + m_ServerClientConnectedInvocationsThroughConnectionEvent++; + Assert.IsFalse(data.PeerClientIds.IsCreated); + break; + case ConnectionEvent.PeerConnected: + m_PeerConnectedInvocations++; + Assert.IsFalse(data.PeerClientIds.IsCreated); + break; + } + } private int m_ClientDisconnectedInvocations; From 980ac6003ba6cd4ea4cefb63bcedf5452129fbcc Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 16 Nov 2023 15:52:24 -0600 Subject: [PATCH 06/13] Minor changelog formatting fixes, and standards fixes --- com.unity.netcode.gameobjects/CHANGELOG.md | 6 +++--- .../Runtime/Connection/NetworkConnectionManager.cs | 8 ++++---- .../Assets/Tests/Runtime/MultiClientConnectionApproval.cs | 1 - 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 7f56292c27..2a23b1a835 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,12 +10,12 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added -- Added: Added a new RPC attribute, which is simply `Rpc`. (#2762) +- Added a new RPC attribute, which is simply `Rpc`. (#2762) - This is a generic attribute that can perform the functions of both Server and Client RPCs, as well as enabling client-to-client RPCs. Includes several default targets: `Server`, `NotServer`, `Owner`, `NotOwner`, `Me`, `NotMe`, `ClientsAndHost`, and `Everyone`. Runtime overrides are available for any of these targets, as well as for sending to a specific ID or groups of IDs. - This attribute also includes the ability to defer RPCs that are sent to the local process to the start of the next frame instead of executing them immediately, treating them as if they had gone across the network. The default behavior is to execute immediately. - This attribute effectively replaces `ServerRpc` and `ClientRpc`. `ServerRpc` and `ClientRpc` remain in their existing forms for backward compatibility, but `Rpc` will be the recommended and most supported option. -- Added: `NetworkManager.OnConnectionEvent` as a unified connection event callback to notify clients and servers of all client connections and disconnections within the session (#2762) -- Added: `NetworkManager.ServerIsHost` and `NetworkBehaviour.ServerIsHost` to allow a client to tell if it is connected to a host or to a dedicated server (#2762) +- Added `NetworkManager.OnConnectionEvent` as a unified connection event callback to notify clients and servers of all client connections and disconnections within the session (#2762) +- Added `NetworkManager.ServerIsHost` and `NetworkBehaviour.ServerIsHost` to allow a client to tell if it is connected to a host or to a dedicated server (#2762) - Added `SceneEventProgress.SceneManagementNotEnabled` return status to be returned when a `NetworkSceneManager` method is invoked and scene management is not enabled. (#2735) - Added `SceneEventProgress.ServerOnlyAction` return status to be returned when a `NetworkSceneManager` method is invoked by a client. (#2735) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index f8b99bd64c..77e0f71898 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -122,7 +122,7 @@ internal void InvokeOnClientConnectedCallback(ulong clientId) { try { - OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData{ClientId = clientId, EventType = ConnectionEvent.ClientConnected}); + OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.ClientConnected }); } catch (Exception exception) { @@ -143,7 +143,7 @@ internal void InvokeOnClientDisconnectCallback(ulong clientId) } try { - OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData{ClientId = clientId, EventType = ConnectionEvent.ClientDisconnected}); + OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.ClientDisconnected }); } catch (Exception exception) { @@ -155,7 +155,7 @@ internal void InvokeOnPeerConnectedCallback(ulong clientId) { try { - OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData{ClientId = clientId, EventType = ConnectionEvent.PeerConnected}); + OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.PeerConnected }); } catch (Exception exception) { @@ -166,7 +166,7 @@ internal void InvokeOnPeerDisconnectedCallback(ulong clientId) { try { - OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData{ClientId = clientId, EventType = ConnectionEvent.PeerDisconnected}); + OnConnectionEvent?.Invoke(NetworkManager, new ConnectionEventData { ClientId = clientId, EventType = ConnectionEvent.PeerDisconnected }); } catch (Exception exception) { diff --git a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs index 3282482fc5..fa3858c61d 100644 --- a/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs +++ b/testproject/Assets/Tests/Runtime/MultiClientConnectionApproval.cs @@ -1,6 +1,5 @@ using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Text; using NUnit.Framework; using Unity.Netcode; From ca3eba82bf119d013cdd7b89871749cc752dec33 Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 16 Nov 2023 16:09:17 -0600 Subject: [PATCH 07/13] Updated some comments for clarity. --- .../RpcTargets/ClientsAndHostRpcTarget.cs | 6 +- .../Runtime/Messaging/RpcTargets/RpcTarget.cs | 103 +++++++++++------- 2 files changed, 66 insertions(+), 43 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs index aa9d0a9c37..286b0b0134 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ClientsAndHostRpcTarget.cs @@ -13,7 +13,11 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, { if (m_UnderlyingTarget == null) { - if (behaviour.NetworkManager.ConnectionManager.ConnectedClientIds.Contains(NetworkManager.ServerClientId)) + // NotServer treats a host as being a server and will not send to it + // ClientsAndHost sends to everyone who runs any client logic + // So if the server is a host, this target includes it (as hosts run client logic) + // If the server is not a host, this target leaves it out, ergo the selection of NotServer. + if (behaviour.NetworkManager.ServerIsHost) { m_UnderlyingTarget = behaviour.RpcTarget.Everyone; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs index f916a07b19..c92c7e19e4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs @@ -9,53 +9,60 @@ namespace Unity.Netcode public enum SendTo { /// - /// Send to the NetworkObject's current owner - /// Will execute locally if the local process is the owner + /// Send to the NetworkObject's current owner. + /// Will execute locally if the local process is the owner. /// Owner, /// - /// Send to everyone but the current owner, filtered to the current observer list - /// Will execute locally if the local process is not the owner + /// Send to everyone but the current owner, filtered to the current observer list. + /// Will execute locally if the local process is not the owner. /// NotOwner, /// - /// Send to the server, regardless of ownership - /// Will execute locally if invoked on the server + /// Send to the server, regardless of ownership. + /// Will execute locally if invoked on the server. /// Server, /// - /// Send to everyone but the server, filtered to the current observer list - /// Will execute locally if invoked on a client - /// Will NOT execute locally if invoked on a server running in host mode + /// Send to everyone but the server, filtered to the current observer list. + /// Will NOT send to a server running in host mode - it is still treated as a server. + /// If you want to send to servers when they are host, but not when they are dedicated server, use + /// . + ///
+ ///
+ /// Will execute locally if invoked on a client. + /// Will NOT execute locally if invoked on a server running in host mode. ///
NotServer, /// - /// Execute this RPC locally - /// + /// Execute this RPC locally. + ///
+ ///
/// Normally this is no different from a standard function call. - /// + ///
+ ///
/// Using the DeferLocal parameter of the attribute or the LocalDeferMode override in RpcSendParams, /// this can allow an RPC to be processed on localhost with a one-frame delay as if it were sent over /// the network. ///
Me, /// - /// Send this RPC to everyone but the local machine, filtered to the current observer list + /// Send this RPC to everyone but the local machine, filtered to the current observer list. /// NotMe, /// - /// Send this RPC to everone, filtered to the current observer list - /// Will execute locally + /// Send this RPC to everone, filtered to the current observer list. + /// Will execute locally. /// Everyone, /// - /// Send this RPC to all clients, including the host. - /// If the server is running in host mode, this is the same as SendTo.Everyone - /// If the server is running in dedicated server mode, this is the same as SendTo.NotServer + /// Send this RPC to all clients, including the host, if a host exists. + /// If the server is running in host mode, this is the same as . + /// If the server is running in dedicated server mode, this is the same as . /// ClientsAndHost, /// - /// This RPC cannot be sent without passing in a target in RpcSendParams + /// This RPC cannot be sent without passing in a target in RpcSendParams. /// SpecifiedInParams } @@ -109,35 +116,42 @@ public void Dispose() /// - /// Send to the NetworkObject's current owner - /// Will execute locally if the local process is the owner + /// Send to the NetworkObject's current owner. + /// Will execute locally if the local process is the owner. /// public BaseRpcTarget Owner; /// - /// Send to everyone but the current owner, filtered to the current observer list - /// Will execute locally if the local process is not the owner + /// Send to everyone but the current owner, filtered to the current observer list. + /// Will execute locally if the local process is not the owner. /// public BaseRpcTarget NotOwner; /// - /// Send to the server, regardless of ownership - /// Will execute locally if invoked on the server + /// Send to the server, regardless of ownership. + /// Will execute locally if invoked on the server. /// public BaseRpcTarget Server; /// - /// Send to everyone but the server, filtered to the current observer list - /// Will execute locally if invoked on a client - /// Will NOT execute locally if invoked on a server running in host mode + /// Send to everyone but the server, filtered to the current observer list. + /// Will NOT send to a server running in host mode - it is still treated as a server. + /// If you want to send to servers when they are host, but not when they are dedicated server, use + /// . + ///
+ ///
+ /// Will execute locally if invoked on a client. + /// Will NOT execute locally if invoked on a server running in host mode. ///
public BaseRpcTarget NotServer; /// - /// Execute this RPC locally - /// + /// Execute this RPC locally. + ///
+ ///
/// Normally this is no different from a standard function call. - /// + ///
+ ///
/// Using the DeferLocal parameter of the attribute or the LocalDeferMode override in RpcSendParams, /// this can allow an RPC to be processed on localhost with a one-frame delay as if it were sent over /// the network. @@ -145,26 +159,27 @@ public void Dispose() public BaseRpcTarget Me; /// - /// Send this RPC to everyone but the local machine, filtered to the current observer list + /// Send this RPC to everyone but the local machine, filtered to the current observer list. /// public BaseRpcTarget NotMe; /// - /// Send this RPC to everone, filtered to the current observer list - /// Will execute locally + /// Send this RPC to everone, filtered to the current observer list. + /// Will execute locally. /// public BaseRpcTarget Everyone; /// - /// Send this RPC to all clients, including the host. - /// If the server is running in host mode, this is the same as SendTo.Everyone - /// If the server is running in dedicated server mode, this is the same as SendTo.NotServer + /// Send this RPC to all clients, including the host, if a host exists. + /// If the server is running in host mode, this is the same as . + /// If the server is running in dedicated server mode, this is the same as . /// public BaseRpcTarget ClientsAndHost; /// /// Send to a specific single client ID. - /// + ///
+ ///
/// Do not cache or reuse the result of this method. /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, /// and its contents are simply changed. @@ -192,7 +207,8 @@ public BaseRpcTarget Single(ulong clientId) /// Sends to a group of client IDs. /// NativeArrays can be trivially constructed using Allocator.Temp, making this an efficient /// Group method if the group list is dynamically constructed. - /// + ///
+ ///
/// Do not cache or reuse the result of this method. /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, /// and its contents are simply changed. @@ -223,7 +239,8 @@ public BaseRpcTarget Group(NativeArray clientIds) /// Sends to a group of client IDs. /// NativeList can be trivially constructed using Allocator.Temp, making this an efficient /// Group method if the group list is dynamically constructed. - /// + ///
+ ///
/// Do not cache or reuse the result of this method. /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, /// and its contents are simply changed. @@ -240,7 +257,8 @@ public BaseRpcTarget Group(NativeList clientIds) /// Constructing arrays requires garbage collected allocations. This override is only recommended /// if you either have no strict performance requirements, or have the group of client IDs cached so /// it is not created each time. - /// + ///
+ ///
/// Do not cache or reuse the result of this method. /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, /// and its contents are simply changed. @@ -259,7 +277,8 @@ public BaseRpcTarget Group(ulong[] clientIds) /// a garbage collected allocation (even if the type itself is a struct type, due to boxing). /// This override is only recommended if you either have no strict performance requirements, /// or have the group of client IDs cached so it is not created each time. - /// + ///
+ ///
/// Do not cache or reuse the result of this method. /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, /// and its contents are simply changed. From 75cabf93d41cc9578d7d65e5e76524b656f5ee1e Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Thu, 16 Nov 2023 18:00:33 -0600 Subject: [PATCH 08/13] removed some unneeded checks on hashsets --- .../Runtime/Connection/NetworkConnectionManager.cs | 9 +-------- .../Runtime/Core/NetworkObject.cs | 10 ++-------- .../Messaging/Messages/ConnectionApprovedMessage.cs | 5 +---- .../Runtime/Spawning/NetworkSpawnManager.cs | 13 +++---------- 4 files changed, 7 insertions(+), 30 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 77e0f71898..4ef5a2ddff 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -971,14 +971,7 @@ internal void OnClientDisconnectFromServer(ulong clientId) // TODO: Could(should?) be replaced with more memory per client, by storing the visibility foreach (var sobj in NetworkManager.SpawnManager.SpawnedObjectsList) { - if (sobj.Observers.Contains(clientId)) - { - sobj.Observers.Remove(clientId); - /*foreach (var behaviour in sobj.ChildNetworkBehaviours) - { - behaviour.Observers.Remove(clientId); - }*/ - } + sobj.Observers.Remove(clientId); } if (ConnectedClients.ContainsKey(clientId)) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index c1159ee4bf..f7d62176a8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -518,10 +518,7 @@ public void NetworkShow(ulong clientId) return; } NetworkManager.SpawnManager.MarkObjectForShowingTo(this, clientId); - if (!Observers.Contains(clientId)) - { - Observers.Add(clientId); - } + Observers.Add(clientId); } @@ -616,10 +613,7 @@ public void NetworkHide(ulong clientId) { throw new VisibilityChangeException("The object is already hidden"); } - if (Observers.Contains(clientId)) - { - Observers.Remove(clientId); - } + Observers.Remove(clientId); var message = new DestroyObjectMessage { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index e0a42fd936..5635f2369f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -58,10 +58,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { if (sobj.SpawnWithObservers && (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId))) { - if (!sobj.Observers.Contains(OwnerClientId)) - { - sobj.Observers.Add(OwnerClientId); - } + sobj.Observers.Add(OwnerClientId); var sceneObject = sobj.GetMessageSceneObject(OwnerClientId); sceneObject.Serialize(writer); ++sceneObjectCount; diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 94ecf70f29..953462f434 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -69,10 +69,7 @@ internal bool RemoveObjectFromShowingTo(NetworkObject networkObject, ulong clien if (ret) { - if (networkObject.Observers.Contains(clientId)) - { - networkObject.Observers.Remove(clientId); - } + networkObject.Observers.Remove(clientId); } return ret; @@ -940,7 +937,7 @@ internal void UpdateObservedNetworkObjects(ulong clientId) if (sobj.CheckObjectVisibility == null) { // If the client is not part of the observers and spawn with observers is enabled on this instance or the clientId is the server - if (!sobj.Observers.Contains(clientId) && (sobj.SpawnWithObservers || clientId == NetworkManager.ServerClientId)) + if (sobj.SpawnWithObservers || clientId == NetworkManager.ServerClientId) { sobj.Observers.Add(clientId); } @@ -950,13 +947,9 @@ internal void UpdateObservedNetworkObjects(ulong clientId) // CheckObject visibility overrides SpawnWithObservers under this condition if (sobj.CheckObjectVisibility(clientId)) { - if (!sobj.Observers.Contains(clientId)) - { - sobj.Observers.Add(clientId); - } + sobj.Observers.Add(clientId); } else // Otherwise, if the observers contains the clientId (shouldn't happen) then remove it since CheckObjectVisibility returned false - if (sobj.Observers.Contains(clientId)) { sobj.Observers.Remove(clientId); } From 1a85bb491ff61369a2c1e15f033d6baf24207741 Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Fri, 17 Nov 2023 10:45:30 -0600 Subject: [PATCH 09/13] standards + fix broken test --- .../Runtime/Connection/NetworkConnectionManager.cs | 2 +- .../Runtime/Messaging/RpcTargets/RpcTarget.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 4ef5a2ddff..d7c0ef4b05 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -93,7 +93,7 @@ internal void InvokeOnClientConnectedCallback(ulong clientId) if (!NetworkManager.IsServer) { - var peerClientIds = new NativeArray(NetworkManager.ConnectedClientsIds.Count - 1, Allocator.Temp); + var peerClientIds = new NativeArray(Math.Max(NetworkManager.ConnectedClientsIds.Count - 1, 0), Allocator.Temp); // `using var peerClientIds` or `using(peerClientIds)` renders it immutable... using var sentinel = peerClientIds; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs index c92c7e19e4..05901a183e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs @@ -27,7 +27,7 @@ public enum SendTo /// Send to everyone but the server, filtered to the current observer list. /// Will NOT send to a server running in host mode - it is still treated as a server. /// If you want to send to servers when they are host, but not when they are dedicated server, use - /// . + /// . ///
///
/// Will execute locally if invoked on a client. @@ -57,8 +57,8 @@ public enum SendTo Everyone, /// /// Send this RPC to all clients, including the host, if a host exists. - /// If the server is running in host mode, this is the same as . - /// If the server is running in dedicated server mode, this is the same as . + /// If the server is running in host mode, this is the same as . + /// If the server is running in dedicated server mode, this is the same as . /// ClientsAndHost, /// @@ -171,8 +171,8 @@ public void Dispose() /// /// Send this RPC to all clients, including the host, if a host exists. - /// If the server is running in host mode, this is the same as . - /// If the server is running in dedicated server mode, this is the same as . + /// If the server is running in host mode, this is the same as . + /// If the server is running in dedicated server mode, this is the same as . /// public BaseRpcTarget ClientsAndHost; From 2e59fb249a7d2d17e7cbf977d3929a421c21efdf Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Fri, 17 Nov 2023 11:41:44 -0600 Subject: [PATCH 10/13] - Added `Not()` runtime targets - Improved test coverage --- .../Runtime/Core/NetworkBehaviour.cs | 8 +- .../Runtime/Core/NetworkManager.cs | 8 +- .../Runtime/Messaging/RpcTargets/RpcTarget.cs | 182 +++++++++++++++++- .../Tests/Runtime/UniversalRpcTests.cs | 147 +++++++++++++- 4 files changed, 336 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 946f905a5d..6d099c0de7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -430,8 +430,12 @@ public NetworkManager NetworkManager /// runtime-bound targets like , /// , /// , - /// , and - /// + /// , + /// , , + /// , + /// , + /// , and + /// /// #pragma warning restore IDE0001 public RpcTarget RpcTarget => NetworkManager.RpcTarget; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index f0ce45982e..c994e4d8f3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -409,8 +409,12 @@ public NetworkPrefabHandler PrefabHandler /// runtime-bound targets like , /// , /// , - /// , and - /// + /// , + /// , , + /// , + /// , + /// , and + /// ///
#pragma warning restore IDE0001 public RpcTarget RpcTarget; diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs index 05901a183e..32ff87286d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Unity.Collections; namespace Unity.Netcode @@ -72,8 +73,12 @@ public enum SendTo /// , /// , /// , - /// , and - /// + /// , + /// , , + /// , + /// , + /// , and + /// ///
public class RpcTarget { @@ -203,6 +208,45 @@ public BaseRpcTarget Single(ulong clientId) return m_CachedProxyRpcTarget; } + /// + /// Send to everyone EXCEPT a specific single client ID. + ///
+ ///
+ /// Do not cache or reuse the result of this method. + /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, + /// and its contents are simply changed. + ///
+ /// + /// + public BaseRpcTarget Not(ulong excludedClientId) + { + IGroupRpcTarget target; + if (m_NetworkManager.IsServer) + { + target = m_CachedTargetGroup; + } + else + { + target = m_CachedProxyRpcTargetGroup; + } + target.Clear(); + foreach (var clientId in m_NetworkManager.ConnectedClientsIds) + { + if (clientId != excludedClientId) + { + target.Add(clientId); + } + } + + // If ServerIsHost, ConnectedClientIds already contains ServerClientId and this would duplicate it. + if (!m_NetworkManager.ServerIsHost && excludedClientId != NetworkManager.ServerClientId) + { + target.Add(NetworkManager.ServerClientId); + } + + return target.Target; + } + /// /// Sends to a group of client IDs. /// NativeArrays can be trivially constructed using Allocator.Temp, making this an efficient @@ -270,7 +314,6 @@ public BaseRpcTarget Group(ulong[] clientIds) return Group(new NativeArray(clientIds, Allocator.Temp)); } - /// /// Sends to a group of client IDs. /// This accepts any IEnumerable type, such as List<ulong>, but cannot be called without @@ -305,6 +348,139 @@ public BaseRpcTarget Group(T clientIds) where T : IEnumerable return target.Target; } + /// + /// Sends to everyone EXCEPT a group of client IDs. + /// NativeArrays can be trivially constructed using Allocator.Temp, making this an efficient + /// Group method if the group list is dynamically constructed. + ///
+ ///
+ /// Do not cache or reuse the result of this method. + /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, + /// and its contents are simply changed. + ///
+ /// + /// + public BaseRpcTarget Not(NativeArray excludedClientIds) + { + IGroupRpcTarget target; + if (m_NetworkManager.IsServer) + { + target = m_CachedTargetGroup; + } + else + { + target = m_CachedProxyRpcTargetGroup; + } + target.Clear(); + + using var asASet = new NativeHashSet(excludedClientIds.Length, Allocator.Temp); + foreach (var clientId in excludedClientIds) + { + asASet.Add(clientId); + } + + foreach (var clientId in m_NetworkManager.ConnectedClientsIds) + { + if(!asASet.Contains(clientId)) + { + target.Add(clientId); + } + } + + // If ServerIsHost, ConnectedClientIds already contains ServerClientId and this would duplicate it. + if (!m_NetworkManager.ServerIsHost && !asASet.Contains(NetworkManager.ServerClientId)) + { + target.Add(NetworkManager.ServerClientId); + } + + return target.Target; + } + + /// + /// Sends to everyone EXCEPT a group of client IDs. + /// NativeList can be trivially constructed using Allocator.Temp, making this an efficient + /// Group method if the group list is dynamically constructed. + ///
+ ///
+ /// Do not cache or reuse the result of this method. + /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, + /// and its contents are simply changed. + ///
+ /// + /// + public BaseRpcTarget Not(NativeList excludedClientIds) + { + return Not(excludedClientIds.AsArray()); + } + + /// + /// Sends to everyone EXCEPT a group of client IDs. + /// Constructing arrays requires garbage collected allocations. This override is only recommended + /// if you either have no strict performance requirements, or have the group of client IDs cached so + /// it is not created each time. + ///
+ ///
+ /// Do not cache or reuse the result of this method. + /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, + /// and its contents are simply changed. + ///
+ /// + /// + public BaseRpcTarget Not(ulong[] excludedClientIds) + { + return Not(new NativeArray(excludedClientIds, Allocator.Temp)); + } + + /// + /// Sends to everyone EXCEPT a group of client IDs. + /// This accepts any IEnumerable type, such as List<ulong>, but cannot be called without + /// a garbage collected allocation (even if the type itself is a struct type, due to boxing). + /// This override is only recommended if you either have no strict performance requirements, + /// or have the group of client IDs cached so it is not created each time. + ///
+ ///
+ /// Do not cache or reuse the result of this method. + /// For performance reasons, the same object is used each time to avoid garbage-collected allocations, + /// and its contents are simply changed. + ///
+ /// + /// + public BaseRpcTarget Not(T excludedClientIds) where T : IEnumerable + { + IGroupRpcTarget target; + if (m_NetworkManager.IsServer) + { + target = m_CachedTargetGroup; + } + else + { + target = m_CachedProxyRpcTargetGroup; + } + target.Clear(); + + using var asASet = new NativeHashSet(m_NetworkManager.ConnectedClientsIds.Count, Allocator.Temp); + foreach (var clientId in excludedClientIds) + { + asASet.Add(clientId); + } + + foreach (var clientId in m_NetworkManager.ConnectedClientsIds) + { + if(!asASet.Contains(clientId)) + { + target.Add(clientId); + } + } + + // If ServerIsHost, ConnectedClientIds already contains ServerClientId and this would duplicate it. + if (!m_NetworkManager.ServerIsHost && !asASet.Contains(NetworkManager.ServerClientId)) + { + target.Add(NetworkManager.ServerClientId); + } + + return target.Target; + } + private ProxyRpcTargetGroup m_CachedProxyRpcTargetGroup; private RpcTargetGroup m_CachedTargetGroup; private DirectSendRpcTarget m_CachedDirectSendTarget; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs index 57a4958eac..04ab704885 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using NUnit.Framework; +using Unity.Collections; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using Object = UnityEngine.Object; @@ -1109,7 +1110,9 @@ public void TestDisallowedOverride( Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.Me }))); Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.NotMe }))); Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.Single(0) }))); + Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.Not(0) }))); Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.Group(new[] { 0ul, 1ul, 2ul }) }))); + Assert.Throws(() => RethrowTargetInvocationException(() => method.Invoke(senderObject, new object[] { (RpcParams)senderObject.RpcTarget.Not(new[] { 0ul, 1ul, 2ul }) }))); } } @@ -1181,6 +1184,40 @@ public void TestSendingWithSingleOverride( } + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestSendingWithSingleNotOverride : UniversalRpcTestsBase + { + public UniversalRpcTestSendingWithSingleNotOverride(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + [Test] + public void TestSendingWithSingleNotOverride( + [Values] SendTo defaultSendTo, + [Values(0u, 1u, 2u)] ulong recipient, + [Values(0u, 1u, 2u)] ulong objectOwner, + [Values(0u, 1u, 2u)] ulong sender + ) + { + var sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc"; + + var senderObject = GetPlayerObject(objectOwner, sender); + var target = senderObject.RpcTarget.Not(recipient); + var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + sendMethod.Invoke(senderObject, new object[] { (RpcParams)target }); + + VerifyRemoteReceived(objectOwner, sender, sendMethodName, s_ClientIds.Where(c => recipient != c).ToArray(), false); + VerifyNotReceived(objectOwner, new[] { recipient }); + + // Pass some time to make sure that no other client ever receives this + TimeTravel(1f, 30); + VerifyNotReceived(objectOwner, new[] { recipient }); + } + + } + [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestSendingWithGroupOverride : UniversalRpcTestsBase @@ -1198,18 +1235,50 @@ public UniversalRpcTestSendingWithGroupOverride(HostOrServer hostOrServer) : bas new[] { 0ul, 1ul, 2ul } }; + public enum AllocationType + { + Array, + NativeArray, + NativeList, + List + } + [Test] public void TestSendingWithGroupOverride( [Values] SendTo defaultSendTo, [ValueSource(nameof(RecipientGroups))] ulong[] recipient, [Values(0u, 1u, 2u)] ulong objectOwner, - [Values(0u, 1u, 2u)] ulong sender + [Values(0u, 1u, 2u)] ulong sender, + [Values] AllocationType allocationType ) { var sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc"; var senderObject = GetPlayerObject(objectOwner, sender); - var target = senderObject.RpcTarget.Group(recipient); + BaseRpcTarget target = null; + switch (allocationType) + { + case AllocationType.Array: + target = senderObject.RpcTarget.Group(recipient); + break; + case AllocationType.List: + target = senderObject.RpcTarget.Group(recipient.ToList()); + break; + case AllocationType.NativeArray: + var arr = new NativeArray(recipient, Allocator.Temp); + target = senderObject.RpcTarget.Group(arr); + arr.Dispose(); + break; + case AllocationType.NativeList: + var list = new NativeList(recipient.Length, Allocator.Temp); + foreach (var id in recipient) + { + list.Add(id); + } + target = senderObject.RpcTarget.Group(list); + list.Dispose(); + break; + } var sendMethod = senderObject.GetType().GetMethod(sendMethodName); sendMethod.Invoke(senderObject, new object[] { (RpcParams)target }); @@ -1223,6 +1292,80 @@ public void TestSendingWithGroupOverride( } + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class UniversalRpcTestSendingWithGroupNotOverride : UniversalRpcTestsBase + { + public UniversalRpcTestSendingWithGroupNotOverride(HostOrServer hostOrServer) : base(hostOrServer) + { + + } + + public static ulong[][] RecipientGroups = new[] + { + new ulong[] {}, + new[] { 0ul }, + new[] { 1ul }, + new[] { 0ul, 1ul }, + }; + + public enum AllocationType + { + Array, + NativeArray, + NativeList, + List + } + + [Test] + public void TestSendingWithGroupNotOverride( + [Values] SendTo defaultSendTo, + [ValueSource(nameof(RecipientGroups))] ulong[] recipient, + [Values(0u, 1u, 2u)] ulong objectOwner, + [Values(0u, 1u, 2u)] ulong sender, + [Values] AllocationType allocationType + ) + { + var sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc"; + + var senderObject = GetPlayerObject(objectOwner, sender); + BaseRpcTarget target = null; + switch (allocationType) + { + case AllocationType.Array: + target = senderObject.RpcTarget.Not(recipient); + break; + case AllocationType.List: + target = senderObject.RpcTarget.Not(recipient.ToList()); + break; + case AllocationType.NativeArray: + var arr = new NativeArray(recipient, Allocator.Temp); + target = senderObject.RpcTarget.Not(arr); + arr.Dispose(); + break; + case AllocationType.NativeList: + var list = new NativeList(recipient.Length, Allocator.Temp); + foreach (var id in recipient) + { + list.Add(id); + } + target = senderObject.RpcTarget.Not(list); + list.Dispose(); + break; + } + var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + sendMethod.Invoke(senderObject, new object[] { (RpcParams)target }); + + VerifyRemoteReceived(objectOwner, sender, sendMethodName, s_ClientIds.Where(c => !recipient.Contains(c)).ToArray(), false); + VerifyNotReceived(objectOwner, s_ClientIds.Where(c => recipient.Contains(c)).ToArray()); + + // Pass some time to make sure that no other client ever receives this + TimeTravel(1f, 30); + VerifyNotReceived(objectOwner, s_ClientIds.Where(c => recipient.Contains(c)).ToArray()); + } + + } + [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] internal class UniversalRpcTestDefaultSendToSpecifiedInParamsSendingToServerAndOwner : UniversalRpcTestsBase From c38ee55d9bd21647187090bd211115a7f86049c0 Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Fri, 17 Nov 2023 12:01:14 -0600 Subject: [PATCH 11/13] Standards --- .../Runtime/Messaging/RpcTargets/RpcTarget.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs index 32ff87286d..2d2cbc2e76 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using Unity.Collections; namespace Unity.Netcode @@ -381,7 +380,7 @@ public BaseRpcTarget Not(NativeArray excludedClientIds) foreach (var clientId in m_NetworkManager.ConnectedClientsIds) { - if(!asASet.Contains(clientId)) + if (!asASet.Contains(clientId)) { target.Add(clientId); } @@ -466,7 +465,7 @@ public BaseRpcTarget Not(T excludedClientIds) where T : IEnumerable foreach (var clientId in m_NetworkManager.ConnectedClientsIds) { - if(!asASet.Contains(clientId)) + if (!asASet.Contains(clientId)) { target.Add(clientId); } From 5c2334dcf0f627c6861938639cfb6e5bedd3ce0e Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Fri, 17 Nov 2023 15:03:40 -0600 Subject: [PATCH 12/13] Fix test failures in 2020.3 --- .../Runtime/Messaging/RpcTargets/RpcTarget.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs index 2d2cbc2e76..35548d6b90 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs @@ -292,7 +292,8 @@ public BaseRpcTarget Group(NativeArray clientIds) /// public BaseRpcTarget Group(NativeList clientIds) { - return Group(clientIds.AsArray()); + var asArray = clientIds.AsArray(); + return Group(asArray); } /// @@ -409,7 +410,8 @@ public BaseRpcTarget Not(NativeArray excludedClientIds) /// public BaseRpcTarget Not(NativeList excludedClientIds) { - return Not(excludedClientIds.AsArray()); + var asArray = excludedClientIds.AsArray(); + return Not(asArray); } /// From 56861f9b5edf534754717b7c864c930d37d5079e Mon Sep 17 00:00:00 2001 From: Kitty Draper Date: Mon, 20 Nov 2023 14:54:55 -0600 Subject: [PATCH 13/13] Fix test failures in 2020.3 but for real this time --- .../Tests/Runtime/UniversalRpcTests.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs index 04ab704885..24204c77ac 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs @@ -1270,7 +1270,10 @@ [Values] AllocationType allocationType arr.Dispose(); break; case AllocationType.NativeList: - var list = new NativeList(recipient.Length, Allocator.Temp); + // For some reason on 2020.3, calling list.AsArray() and passing that to the next function + // causes Allocator.Temp allocations to become invalid somehow. This is not an issue on later + // versions of Unity. + var list = new NativeList(recipient.Length, Allocator.TempJob); foreach (var id in recipient) { list.Add(id); @@ -1344,7 +1347,10 @@ [Values] AllocationType allocationType arr.Dispose(); break; case AllocationType.NativeList: - var list = new NativeList(recipient.Length, Allocator.Temp); + // For some reason on 2020.3, calling list.AsArray() and passing that to the next function + // causes Allocator.Temp allocations to become invalid somehow. This is not an issue on later + // versions of Unity. + var list = new NativeList(recipient.Length, Allocator.TempJob); foreach (var id in recipient) { list.Add(id);