diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md
index beeb949d80..11adaeaa6a 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Added
+- `RpcInvokePermission` to control who has permission to invoke specific RPC methods. (#3731)
- Added NetworkRigidbody documentation section. (#3664)
- Added new fields to the `SceneMap` struct when using Unity 6.3 or higher. These fields allow referencing scene handles via the new `SceneHandle` struct. (#3734)
@@ -25,11 +26,11 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Deprecated
+- Deprecated all `RequireOwnership` fields around the RPCs in favor of the `RpcInvokePermission`. (#3731)
- On Unity 6.5 some `SceneMap` fields that use an `int` to represent a `SceneHandle` are deprecated. (#3734)
### Removed
-
### Fixed
- Multiple disconnect events from the same transport will no longer disconnect the host. (#3707)
diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md
index c004571dbd..2c7d915d06 100644
--- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md
+++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md
@@ -1,6 +1,6 @@
# RPC
-Any process can communicate with any other process by sending a remote procedure call (RPC). As of Netcode for GameObjects version 1.8.0, the `Rpc` attribute encompasses server to client RPCs, client to server RPCs, and client to client RPCs.
+Any process can communicate with any other process by sending a remote procedure call (RPC). The `Rpc` attribute is the recommended attribute to use when declaring RPC methods. The `Rpc` attribute's parameters define the receivers and execution rights for the RPC method.

@@ -204,7 +204,7 @@ There are a few other parameters that can be passed to either the `Rpc` attribut
| Parameter | Description |
| ----------------------- | ------------------------------------------------------------ |
| `Delivery` | Controls whether the delivery is reliable (default) or unreliable.
Options: `RpcDelivery.Reliable` or `RpcDelivery.Unreliable`
Default: `RpcDelivery.Reliable` |
-| `RequireOwnership` | If `true`, this RPC throws an exception if invoked by a player that does not own the object. This is in effect for server-to-client, client-to-server, and client-to-client RPCs - i.e., a server-to-client RPC will still fail if the server is not the object's owner.
Default: `false` |
+| `InvokePermission` | Sets an RPC's invocation permissions.
Options:
`RpcInvokePermission.Server` - This RPC throws an exception if invoked by a game client that is not the server.
`RpcInvokePermission.Owner` - This RPC throws an exception if invoked by a game client that does not own the object.
`RpcInvokePermission.Everyone` - This can be invoked by any connected game client.
Default: `RpcInvokePermission.Everyone` |
| `DeferLocal` | If `true`, RPCs that execute locally will be deferred until the start of the next frame, as if they had been sent over the network. (They will not actually be sent over the network, but will be treated as if they were.) This is useful for mutually recursive RPCs on hosts, where sending back and forth between the server and the "host client" will cause a stack overflow if each RPC is executed instantly; simulating the flow of RPCs between remote client and server enables this flow to work the same in both contexts.
Default: `false` |
| `AllowTargetOverride` | By default, any `SendTo` value other than `SendTo.SpecifiedInParams` is a hard-coded value that cannot be changed. Setting this to `true` allows you to provide an alternate target at runtime, while using the `SendTo` value as a fallback if no runtime value is provided. |
@@ -215,6 +215,116 @@ There are a few other parameters that can be passed to either the `Rpc` attribut
| `Target` | Runtime override destination for the RPC. (See above for more details.) Populating this value will throw an exception unless either the `SendTo` value for the RPC is `SendTo.SpecifiedInParams`, or `AllowTargetOverride` is `true`.
Default: `null` |
| `LocalDeferMode` | Overrides the `DeferLocal` value. `DeferLocalMode.Defer` causes this particular invocation of this RPC to be deferred until the next frame even if `DeferLocal` is `false`, while `DeferLocalMode.SendImmediate` causes the RPC to be executed immediately on the local machine even if `DeferLocal` is `true`. `DeferLocalMode.Default` does whatever the `DeferLocal` value in the attribute is configured to do.
Options: `DeferLocalMode.Default`, `DeferLocalMode.Defer`, `DeferLocalMode.SendImmediate`
Default: `DeferLocalMode.Default` |
+## Invocation order
+
+Rpc message sent with `RpcDelivery.Reliable` will be sent and invoked on other game clients in the same order as they were called on the local game client.
+
+```csharp
+[Rpc(SendTo.Server)]
+public void OpenDoorRpc(int doorId, RpcParams rpcParams)
+{
+ Debug.Log($"client {rpcParams.Receive.SenderClientId} has opened door {doorId}");
+
+ // Server can handle door opening here
+}
+
+[Rpc(SendTo.Server)]
+void OpenChestRPC(int chestId, RpcParams rpcParams)
+{
+ Debug.Log($"client {rpcParams.Receive.SenderClientId} has opened chest {chestId}");
+
+ // Server can handle door opening here
+}
+
+void Update()
+{
+ if (IsClient && Input.GetKeyDown(KeyCode.O))
+ {
+ OpenDoorRpc(1)
+ OpenDoorRpc(2)
+ OpenChestRpc(5)
+ }
+
+ // Other clients will log:
+ //
+ // "client 1 has opened door 1"
+ // "client 1 has opened door 2"
+ // "client 1 has opened chest 5"
+}
+```
+
+> [!Warning]
+> Invocation order is not guaranteed with nested RPC invocations that include targets that may invoke locally. Invocation order is also not guaranteed when using `RpcDelivery.Unreliable`
+
+### Deferring local invocation
+
+Invoking an RPC from within another RPC introduces the risk that the local RPC may invoke before messages are sent to other game clients. This will result in the RPC message for the inner RPC invocation being sent before the message for the outer RPC.
+
+```csharp
+[Rpc(SendTo.Everyone)]
+public void TryOpenDoorRpc(int doorId, RpcParams rpcParams)
+{
+ Debug.Log($"client {rpcParams.Receive.SenderClientId} is trying to open door {doorId}");
+
+ if (HasAuthority) {
+ // Authority handles opening the door here
+
+ // If the authority is invoking TryOpenDoorRpc locally before the authority has sent TryOpenDoorRpc to other clients, OpenDoorRpc will be sent before TryOpenDoorRpc.
+ OpenDoorRpc(doorId);
+ }
+}
+
+[Rpc(SendTo.Everyone)]
+public void OpenDoorRpc(int doorId, RpcParams rpcParams)
+{
+ Debug.Log($"client {rpcParams.Receive.SenderClientId} marked door {doorId} as open");
+}
+
+void Update()
+{
+ if (Input.GetKeyDown(KeyCode.O))
+ {
+ // Invocation of TryOpenDoorRpc and OpenDoorRpc may be inverted depending on the context in which TryOpenDoorRpc is invoked
+ TryOpenDoorRpc(20);
+ }
+}
+```
+
+Use the RPC `LocalDeferMode` to resolve issue. Configuring the RPC to be deferred when invoked locally will ensure that any outer RPC messages are always sent before the inner function is invoked.
+
+```csharp
+// An RPC can be configured to defer the local invocation in the attribute definition
+[Rpc(SendTo.Everyone, DeferLocal = true)]
+public void TryOpenDoorRpc(int doorId, RpcParams rpcParams = default)
+{
+ Debug.Log($"client {rpcParams.Receive.SenderClientId} is trying to open door {doorId}");
+
+ if (HasAuthority) {
+
+ // Authority handles opening the door here
+
+ // Defer mode can also be passed in at the call site.
+ OpenDoorRpc(doorId, LocalDeferMode.Defer);
+ }
+}
+
+[Rpc(SendTo.Everyone)]
+public void OpenDoorRpc(int doorId, RpcParams rpcParams = default)
+{
+ Debug.Log($"client {rpcParams.Receive.SenderClientId} marked door {doorId} as open");
+}
+
+void Update()
+{
+ if (Input.GetKeyDown(KeyCode.O))
+ {
+ // TryOpenDoorRpc is defined with DeferLocal
+ // DeferLocal ensures that RPC messages are sent to all other targets before the RPC is invoked locally
+ TryOpenDoorRpc(20);
+ }
+}
+```
+
## Additional resources
* [RPC parameters](rpc-params.md)
diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md b/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md
index ab648a7056..3aa5865386 100644
--- a/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md
+++ b/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md
@@ -145,7 +145,7 @@ This works the same way with dynamically spawned NetworkObjects.
The [synchronization and notification example](#synchronization-and-notification-example) highlights the differences between synchronizing a `NetworkVariable` with newly-joining clients and notifying connected clients when a `NetworkVariable` changes, but it doesn't provide any concrete example usage.
-The `OnValueChanged` example shows a simple server-authoritative `NetworkVariable` being used to track the state of a door (that is, open or closed) using a non-ownership-based server RPC. With `RequireOwnership = false` any client can notify the server that it's performing an action on the door. Each time the door is used by a client, the `Door.ToggleServerRpc` is invoked and the server-side toggles the state of the door. When the `Door.State.Value` changes, all connected clients are synchronized to the (new) current `Value` and the `OnStateChanged` method is invoked locally on each client.
+The `OnValueChanged` example shows a simple server-authoritative `NetworkVariable` being used to track the state of a door (that is, open or closed) using an RPC that is sent to the server. Each time the door is used by a client, the `Door.ToggleStateRpc` is invoked and the server-side toggles the state of the door. When the `Door.State.Value` changes, all connected clients are synchronized to the (new) current `Value` and the `OnStateChanged` method is invoked locally on each client.
```csharp
public class Door : NetworkBehaviour
@@ -180,7 +180,7 @@ public class Door : NetworkBehaviour
}
[Rpc(SendTo.Server)]
- public void ToggleServerRpc()
+ public void ToggleStateRpc()
{
// this will cause a replication over the network
// and ultimately invoke `OnValueChanged` on receivers
diff --git a/com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md b/com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md
index ab536a17cd..d87c2eb96a 100644
--- a/com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md
+++ b/com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md
@@ -58,4 +58,4 @@ When requesting ownership of a NetworkObject using [`NetworkObject.RequestOwners
* [Authority](authority.md)
* [Client-server](client-server.md)
-* [Distributed authority](distributed-authority.md)
\ No newline at end of file
+* [Distributed authority](distributed-authority.md)
diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs
index 5396087381..54271e1b65 100644
--- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs
+++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs
@@ -470,6 +470,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly,
private FieldReference m_UniversalRpcParams_Receive_SenderClientId_FieldRef;
private TypeReference m_UniversalRpcParams_TypeRef;
private TypeReference m_ClientRpcParams_TypeRef;
+ private TypeReference m_RpcInvokePermissions_TypeRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpy_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedByMemcpyArray_MethodRef;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
@@ -609,7 +610,13 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly,
private const string k_NetworkVariableBase_Initialize = nameof(NetworkVariableBase.Initialize);
private const string k_RpcAttribute_Delivery = nameof(RpcAttribute.Delivery);
+ private const string k_RpcAttribute_InvokePermission = nameof(RpcAttribute.InvokePermission);
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ // Need to ignore the obsolete warning as the obsolete behaviour still needs to work
private const string k_ServerRpcAttribute_RequireOwnership = nameof(ServerRpcAttribute.RequireOwnership);
+#pragma warning restore CS0618 // Type or member is obsolete
+
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);
@@ -650,6 +657,7 @@ private bool ImportReferences(ModuleDefinition moduleDefinition, string[] assemb
TypeDefinition serverRpcParamsTypeDef = null;
TypeDefinition clientRpcParamsTypeDef = null;
TypeDefinition universalRpcParamsTypeDef = null;
+ TypeDefinition rpcInvokePermissionTypeDef = null;
TypeDefinition fastBufferWriterTypeDef = null;
TypeDefinition fastBufferReaderTypeDef = null;
TypeDefinition networkVariableSerializationTypesTypeDef = null;
@@ -711,6 +719,12 @@ private bool ImportReferences(ModuleDefinition moduleDefinition, string[] assemb
continue;
}
+ if (rpcInvokePermissionTypeDef == null && netcodeTypeDef.Name == nameof(RpcInvokePermission))
+ {
+ rpcInvokePermissionTypeDef = netcodeTypeDef;
+ continue;
+ }
+
if (fastBufferWriterTypeDef == null && netcodeTypeDef.Name == nameof(FastBufferWriter))
{
fastBufferWriterTypeDef = netcodeTypeDef;
@@ -936,6 +950,7 @@ private bool ImportReferences(ModuleDefinition moduleDefinition, string[] assemb
}
m_ClientRpcParams_TypeRef = moduleDefinition.ImportReference(clientRpcParamsTypeDef);
+ m_RpcInvokePermissions_TypeRef = moduleDefinition.ImportReference(rpcInvokePermissionTypeDef);
m_FastBufferWriter_TypeRef = moduleDefinition.ImportReference(fastBufferWriterTypeDef);
m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(fastBufferReaderTypeDef);
@@ -1311,9 +1326,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass
return;
}
}
- var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler, string RpcMethodName)>();
-
- bool isEditorOrDevelopment = assemblyDefines.Contains("UNITY_EDITOR") || assemblyDefines.Contains("DEVELOPMENT_BUILD");
+ var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler, string RpcMethodName, CustomAttribute rpcAttribute)>();
foreach (var methodDefinition in typeDefinition.Methods)
{
@@ -1342,7 +1355,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass
InjectWriteAndCallBlocks(methodDefinition, rpcAttribute, rpcMethodId);
- rpcHandlers.Add((rpcMethodId, GenerateStaticHandler(methodDefinition, rpcAttribute, rpcMethodId), methodDefinition.Name));
+ rpcHandlers.Add((rpcMethodId, GenerateStaticHandler(methodDefinition, rpcAttribute, rpcMethodId), methodDefinition.Name, rpcAttribute));
}
GenerateVariableInitialization(typeDefinition);
@@ -1424,7 +1437,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass
var instructions = new List();
var processor = initializeRpcsMethodDef.Body.GetILProcessor();
- foreach (var (rpcMethodId, rpcHandler, rpcMethodName) in rpcHandlers)
+ foreach (var (rpcMethodId, rpcHandler, rpcMethodName, rpcAttribute) in rpcHandlers)
{
typeDefinition.Methods.Add(rpcHandler);
@@ -1435,13 +1448,38 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass
callMethod = callMethod.MakeGeneric(genericTypes.ToArray());
}
- // __registerRpc(RpcMethodId, HandleFunc, methodName);
+ var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName;
+ var isClientRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ClientRpcAttribute_FullName;
+
+ var invokePermission = isServerRpc ? RpcInvokePermission.Owner : RpcInvokePermission.Everyone;
+
+ foreach (var attrField in rpcAttribute.Fields)
+ {
+ switch (attrField.Name)
+ {
+ case k_ServerRpcAttribute_RequireOwnership:
+ var requireOwnership = attrField.Argument.Type == rpcHandler.Module.TypeSystem.Boolean && (bool)attrField.Argument.Value;
+ invokePermission = requireOwnership ? RpcInvokePermission.Owner : RpcInvokePermission.Everyone;
+ break;
+ case k_RpcAttribute_InvokePermission:
+ invokePermission = (RpcInvokePermission)attrField.Argument.Value;
+ break;
+ }
+ }
+
+ if (isClientRpc)
+ {
+ invokePermission = RpcInvokePermission.Server;
+ }
+
+ // __registerRpc(RpcMethodId, HandleFunc, invokePermission, methodName);
instructions.Add(processor.Create(OpCodes.Ldarg_0));
instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId)));
instructions.Add(processor.Create(OpCodes.Ldnull));
instructions.Add(processor.Create(OpCodes.Ldftn, callMethod));
instructions.Add(processor.Create(OpCodes.Newobj, m_NetworkHandlerDelegateCtor_MethodRef));
instructions.Add(processor.Create(OpCodes.Ldstr, rpcMethodName));
+ instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)invokePermission));
instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour___registerRpc_MethodRef));
}
@@ -1517,6 +1555,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass
private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinition)
{
CustomAttribute rpcAttribute = null;
+
foreach (var customAttribute in methodDefinition.CustomAttributes)
{
var customAttributeType_FullName = customAttribute.AttributeType.FullName;
@@ -1558,17 +1597,24 @@ private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinitio
isValid = false;
}
- if (customAttributeType_FullName == CodeGenHelpers.RpcAttribute_FullName &&
- !methodDefinition.Name.EndsWith("Rpc", StringComparison.OrdinalIgnoreCase))
+ if (customAttributeType_FullName == CodeGenHelpers.ClientRpcAttribute_FullName &&
+ !methodDefinition.Name.EndsWith("ClientRpc", StringComparison.OrdinalIgnoreCase))
{
- m_Diagnostics.AddError(methodDefinition, "Rpc method must end with 'Rpc' suffix!");
+ m_Diagnostics.AddError(methodDefinition, "ClientRpc method must end with 'ClientRpc' suffix!");
isValid = false;
}
- if (customAttributeType_FullName == CodeGenHelpers.ClientRpcAttribute_FullName &&
- !methodDefinition.Name.EndsWith("ClientRpc", StringComparison.OrdinalIgnoreCase))
+ if (customAttributeType_FullName == CodeGenHelpers.RpcAttribute_FullName &&
+ !methodDefinition.Name.EndsWith("Rpc", StringComparison.OrdinalIgnoreCase))
{
- m_Diagnostics.AddError(methodDefinition, "ClientRpc method must end with 'ClientRpc' suffix!");
+ m_Diagnostics.AddError(methodDefinition, "Rpc method must end with 'Rpc' suffix!");
+
+ // Extra compiler information if a method was defined as a local function
+ if (methodDefinition.Name.Contains("Rpc|", StringComparison.OrdinalIgnoreCase) && methodDefinition.Name.StartsWith("g__", StringComparison.OrdinalIgnoreCase))
+ {
+ m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} appears to be a local function. Local functions cannot be RPCs.");
+ }
+
isValid = false;
}
@@ -1600,6 +1646,38 @@ private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinitio
return null;
}
+
+ var typeSystem = methodDefinition.Module.TypeSystem;
+ var hasInvokePermission = false;
+
+ CustomAttributeNamedArgument? invokePermissionAttribute = null;
+ foreach (var argument in rpcAttribute.Fields)
+ {
+ switch (argument.Name)
+ {
+ case k_ServerRpcAttribute_RequireOwnership:
+ var requireOwnership = argument.Argument.Type == typeSystem.Boolean && (bool)argument.Argument.Value;
+ var invokePermissionArg = new CustomAttributeArgument(m_RpcInvokePermissions_TypeRef, requireOwnership ? RpcInvokePermission.Owner : RpcInvokePermission.Everyone);
+ invokePermissionAttribute = new CustomAttributeNamedArgument(k_RpcAttribute_InvokePermission, invokePermissionArg);
+ break;
+ case k_RpcAttribute_InvokePermission:
+ hasInvokePermission = true;
+ break;
+ }
+ }
+
+ if (invokePermissionAttribute != null)
+ {
+ if (hasInvokePermission)
+ {
+ m_Diagnostics.AddError($"{methodDefinition.Name} cannot declare both RequireOwnership and InvokePermission!");
+ return null;
+ }
+
+ rpcAttribute.Fields.Add(invokePermissionAttribute.Value);
+ }
+
+
// Checks for IsSerializable are moved to later as the check is now done by dynamically seeing if any valid
// serializer OR extension method exists for it.
return rpcAttribute;
@@ -2151,18 +2229,24 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA
{
var returnInstr = processor.Create(OpCodes.Ret);
var lastInstr = processor.Create(OpCodes.Nop);
+ var logNextInstr = processor.Create(OpCodes.Nop);
// networkManager = this.NetworkManager;
instructions.Add(processor.Create(OpCodes.Ldarg_0));
instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_getNetworkManager_MethodRef));
instructions.Add(processor.Create(OpCodes.Stloc, netManLocIdx));
- // if (networkManager == null || !networkManager.IsListening) return;
+ // if (networkManager == null || !networkManager.IsListening) { ... return };
instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx));
- instructions.Add(processor.Create(OpCodes.Brfalse, returnInstr));
+ instructions.Add(processor.Create(OpCodes.Brfalse_S, logNextInstr));
instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx));
instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkManager_getIsListening_MethodRef));
- instructions.Add(processor.Create(OpCodes.Brtrue, lastInstr));
+ instructions.Add(processor.Create(OpCodes.Brtrue_S, lastInstr));
+
+ // Debug.LogError(...);
+ instructions.Add(logNextInstr);
+ instructions.Add(processor.Create(OpCodes.Ldstr, "Rpc methods can only be invoked after starting the NetworkManager!"));
+ instructions.Add(processor.Create(OpCodes.Call, m_Debug_LogError_MethodRef));
instructions.Add(returnInstr);
instructions.Add(lastInstr);
@@ -2292,7 +2376,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA
instructions.Add(processor.Create(OpCodes.Ldloca, rpcAttributeParamsIdx));
instructions.Add(processor.Create(OpCodes.Initobj, m_AttributeParamsType_TypeRef));
- RpcAttribute.RpcAttributeParams dflt = default;
+ RpcAttribute.RpcAttributeParams defaultParameters = default;
foreach (var field in rpcAttribute.Fields)
{
var found = false;
@@ -2302,8 +2386,8 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA
{
found = true;
var value = field.Argument.Value;
- var paramField = dflt.GetType().GetField(attrField.Name);
- if (value != paramField.GetValue(dflt))
+ var paramField = defaultParameters.GetType().GetField(attrField.Name);
+ if (value != paramField.GetValue(defaultParameters))
{
instructions.Add(processor.Create(OpCodes.Ldloca, rpcAttributeParamsIdx));
var type = value.GetType();
@@ -2346,6 +2430,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA
m_Diagnostics.AddError($"{nameof(RpcAttribute)} contains field {field} which is not present in {nameof(RpcAttribute.RpcAttributeParams)}.");
}
}
+
instructions.Add(processor.Create(OpCodes.Ldloc, rpcAttributeParamsIdx));
// defaultTarget
@@ -2408,11 +2493,11 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA
{
if (paramIndex != paramCount - 1)
{
- m_Diagnostics.AddError(methodDefinition, $"{nameof(RpcParams)} must be the last parameter in a ClientRpc.");
+ m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.Name} is invalid. {nameof(RpcParams)} must be the last parameter in a ClientRpc.");
}
if (!isGenericRpc)
{
- m_Diagnostics.AddError($"Only Rpcs may accept {nameof(RpcParams)} as a parameter.");
+ m_Diagnostics.AddError($"{methodDefinition.Name} is invalid. Only Rpcs may accept {nameof(RpcParams)} as a parameter.");
}
continue;
}
@@ -2845,8 +2930,6 @@ 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)
{
diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
index f2331a267f..92b4679fdf 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
@@ -38,6 +38,7 @@ public abstract class NetworkBehaviour : MonoBehaviour
// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary> __rpc_func_table = new Dictionary>();
+ internal static readonly Dictionary> __rpc_permission_table = new Dictionary>();
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
// RuntimeAccessModifiersILPP will make this `public`
@@ -328,7 +329,13 @@ internal FastBufferWriter __beginSendRpc(uint rpcMethodId, RpcParams rpcParams,
{
throw new RpcException("The NetworkBehaviour must be spawned before calling this method.");
}
- if (attributeParams.RequireOwnership && !IsOwner)
+
+ if (attributeParams.InvokePermission == RpcInvokePermission.Server && !IsServer)
+ {
+ throw new RpcException("This RPC can only be sent by the server.");
+ }
+
+ if (attributeParams.InvokePermission == RpcInvokePermission.Owner && !IsOwner)
{
throw new RpcException("This RPC can only be sent by its owner.");
}
@@ -934,11 +941,14 @@ internal virtual void __initializeRpcs()
}
#pragma warning disable IDE1006 // disable naming rule violation check
+ // This is needed to add the RpcInvokePermission as even with an optional parameter, the change counts as a breaking change.
+ internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMethodName) => __registerRpc(hash, handler, rpcMethodName, RpcInvokePermission.Everyone);
// RuntimeAccessModifiersILPP will make this `protected`
- internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMethodName)
+ internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMethodName, RpcInvokePermission permission)
#pragma warning restore IDE1006 // restore naming rule violation check
{
__rpc_func_table[GetType()][hash] = handler;
+ __rpc_permission_table[GetType()][hash] = permission;
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
__rpc_name_table[GetType()][hash] = rpcMethodName;
#endif
@@ -985,6 +995,7 @@ internal void InitializeVariables()
if (!__rpc_func_table.ContainsKey(GetType()))
{
__rpc_func_table[GetType()] = new Dictionary();
+ __rpc_permission_table[GetType()] = new Dictionary();
#if UNITY_EDITOR || DEVELOPMENT_BUILD || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
__rpc_name_table[GetType()] = new Dictionary();
#endif
diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs
index 57c8345175..95b4d41a49 100644
--- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ProxyMessage.cs
@@ -41,24 +41,51 @@ public unsafe void Handle(ref NetworkContext context)
}
return;
}
-
var observers = networkObject.Observers;
+ // Validate message if server
+ if (networkManager.IsServer)
+ {
+ var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(WrappedMessage.Metadata.NetworkBehaviourId);
+
+ RpcInvokePermission permission = NetworkBehaviour.__rpc_permission_table[networkBehaviour.GetType()][WrappedMessage.Metadata.NetworkRpcMethodId];
+ bool hasPermission = permission switch
+ {
+ RpcInvokePermission.Everyone => true,
+ RpcInvokePermission.Server => context.SenderId == networkManager.LocalClientId,
+ RpcInvokePermission.Owner => context.SenderId == networkBehaviour.OwnerClientId,
+ _ => false,
+ };
+
+ // Do not handle the message if the sender does not have permission to do so.
+ if (!hasPermission)
+ {
+ if (networkManager.LogLevel <= LogLevel.Developer)
+ {
+ NetworkLog.LogErrorServer($"Rpc message received from client-{context.SenderId} who does not have permission to perform this operation!");
+ }
+ return;
+ }
+
+ WrappedMessage.SenderClientId = context.SenderId;
+ }
+
+
var nonServerIds = new NativeList(Allocator.Temp);
- for (var i = 0; i < TargetClientIds.Length; ++i)
+ foreach (var client in TargetClientIds)
{
- if (!observers.Contains(TargetClientIds[i]))
+ if (!observers.Contains(client))
{
continue;
}
- if (TargetClientIds[i] == NetworkManager.ServerClientId)
+ if (client == NetworkManager.ServerClientId)
{
WrappedMessage.Handle(ref context);
}
else
{
- nonServerIds.Add(TargetClientIds[i]);
+ nonServerIds.Add(client);
}
}
diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs
index 70e4c2aadf..d37015fdcd 100644
--- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs
@@ -66,23 +66,37 @@ public static void Handle(ref NetworkContext context, ref RpcMetadata metadata,
{
NetworkLog.LogWarning($"[{metadata.NetworkObjectId}, {metadata.NetworkBehaviourId}, {metadata.NetworkRpcMethodId}] 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.");
}
+
return;
}
- var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(metadata.NetworkBehaviourId);
+ var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(metadata.NetworkBehaviourId);
try
{
+ var permission = NetworkBehaviour.__rpc_permission_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId];
+
+ if ((permission == RpcInvokePermission.Server && rpcParams.SenderId != NetworkManager.ServerClientId) ||
+ (permission == RpcInvokePermission.Owner && rpcParams.SenderId != networkObject.OwnerClientId))
+ {
+ if (networkManager.LogLevel <= LogLevel.Developer)
+ {
+ NetworkLog.LogErrorServer($"Rpc message received from client-{rpcParams.SenderId} who does not have permission to perform this operation!");
+ }
+ return;
+ }
+
NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams);
}
catch (Exception ex)
{
- Debug.LogException(new Exception("Unhandled RPC exception!", ex));
- if (networkManager.LogLevel == LogLevel.Developer)
+ Debug.LogException(new Exception($"Unhandled RPC exception!", ex));
+ if (networkManager.LogLevel <= LogLevel.Developer)
{
Debug.Log($"RPC Table Contents");
foreach (var entry in NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()])
{
- Debug.Log($"{entry.Key} | {entry.Value.Method.Name}");
+ var permission = NetworkBehaviour.__rpc_permission_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId];
+ Debug.Log($"{entry.Key} | {entry.Value.Method.Name} | {permission}");
}
}
}
@@ -121,6 +135,7 @@ public void Handle(ref NetworkContext context)
{
var rpcParams = new __RpcParams
{
+ SenderId = context.SenderId,
Server = new ServerRpcParams
{
Receive = new ServerRpcReceiveParams
@@ -158,6 +173,7 @@ public void Handle(ref NetworkContext context)
{
var rpcParams = new __RpcParams
{
+ SenderId = NetworkManager.ServerClientId,
Client = new ClientRpcParams
{
Receive = new ClientRpcReceiveParams
@@ -196,13 +212,20 @@ public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext conte
public void Handle(ref NetworkContext context)
{
+ var networkManager = (NetworkManager)context.SystemOwner;
+
+ // If the server is receiving, always trust the transportId for the SenderClientId
+ // Otherwise, use the proxied id.
+ var senderId = networkManager.IsServer ? context.SenderId : SenderClientId;
+
var rpcParams = new __RpcParams
{
+ SenderId = senderId,
Ext = new RpcParams
{
Receive = new RpcReceiveParams
{
- SenderClientId = SenderClientId
+ SenderClientId = senderId
}
}
};
diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs
index 6d276ab580..59555210c3 100644
--- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcAttributes.cs
@@ -19,7 +19,29 @@ public enum RpcDelivery
}
///
- /// Represents the common base class for Rpc attributes.
+ /// RPC invoke permissions
+ ///
+ public enum RpcInvokePermission
+ {
+ ///
+ /// Any connected client can invoke the Rpc.
+ ///
+ Everyone = 0,
+
+ ///
+ /// Rpc can only be invoked by the server.
+ ///
+ Server,
+
+ ///
+ /// Rpc can only be invoked by the owner of the NetworkBehaviour.
+ ///
+ Owner,
+ }
+
+ ///
+ /// Marks a method as a remote procedure call (RPC).
+ /// The marked method will be executed on all game instances defined by the target.
///
[AttributeUsage(AttributeTargets.Method)]
public class RpcAttribute : Attribute
@@ -37,8 +59,17 @@ public struct RpcAttributeParams
///
/// When true, only the owner of the object can execute this RPC
///
+ ///
+ /// Deprecated in favor of .
+ ///
+ [Obsolete("RequireOwnership is deprecated. Please use InvokePermission instead.")]
public bool RequireOwnership;
+ ///
+ /// Who has network permission to invoke this RPC
+ ///
+ public RpcInvokePermission InvokePermission;
+
///
/// When true, local execution of the RPC is deferred until the next network tick
///
@@ -56,9 +87,18 @@ public struct RpcAttributeParams
///
public RpcDelivery Delivery = RpcDelivery.Reliable;
+ ///
+ /// Controls who has permission to invoke this RPC. The default setting is
+ ///
+ public RpcInvokePermission InvokePermission;
+
///
/// When true, only the owner of the object can execute this RPC
///
+ ///
+ /// Deprecated in favor of .
+ ///
+ [Obsolete("RequireOwnership is deprecated. Please use InvokePermission = RpcInvokePermission.Owner or InvokePermission = RpcInvokePermission.Everyone instead.")]
public bool RequireOwnership;
///
@@ -95,8 +135,23 @@ public class ServerRpcAttribute : RpcAttribute
{
///
/// When true, only the owner of the NetworkObject can invoke this ServerRpc.
- /// This property overrides the base RpcAttribute.RequireOwnership.
///
+ ///
+ /// Deprecated in favor of using with a target and an .
+ ///
+ /// [ServerRpc(RequireOwnership = false)]
+ /// // is replaced with
+ /// [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
+ /// // as InvokePermission has a default setting of RpcInvokePermission.Everyone, you can also use
+ /// [Rpc(SendTo.Server)]
+ ///
+ ///
+ /// [ServerRpc(RequireOwnership = true)]
+ /// // is replaced with
+ /// [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Owner)]
+ ///
+ ///
+ [Obsolete("ServerRpc with RequireOwnership is deprecated. Use [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] or [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Owner)] instead.)]")]
public new bool RequireOwnership;
///
@@ -120,7 +175,6 @@ 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 b302e3f8f7..3323941803 100644
--- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcParams.cs
@@ -9,8 +9,12 @@ namespace Unity.Netcode
public enum LocalDeferMode
{
///
- /// Uses the default behavior for RPC message handling
+ /// Uses the default behavior for RPC message handling.
+ /// The default behavior is .
///
+ ///
+ /// If is enabled, the behavior of this field wil change to .
+ ///
Default,
///
@@ -25,7 +29,7 @@ public enum LocalDeferMode
}
///
- /// Generic RPC. Defines parameters for sending Remote Procedure Calls (RPCs) in the network system
+ /// Defines parameters for sending Remote Procedure Calls (RPCs) in the network system
///
public struct RpcSendParams
{
@@ -55,31 +59,29 @@ public struct RpcSendParams
}
///
- /// The receive parameters for server-side remote procedure calls
+ /// The receive parameters for an RPC call
///
public struct RpcReceiveParams
{
///
- /// Server-Side RPC
- /// The client identifier of the sender
+ /// The sender's client identifier
///
public ulong SenderClientId;
}
///
- /// Server-Side RPC
- /// Can be used with any sever-side remote procedure call
- /// Note: typically this is use primarily for the
+ /// Parameters for an RPC call
+ /// Can be used with any remote procedure call
///
public struct RpcParams
{
///
- /// The server RPC send parameters (currently a place holder)
+ /// The RPC send parameters (currently a place holder)
///
public RpcSendParams Send;
///
- /// The client RPC receive parameters provides you with the sender's identifier
+ /// The RPC receive parameters provides you with the sender's identifier
///
public RpcReceiveParams Receive;
@@ -211,5 +213,10 @@ internal struct __RpcParams
public RpcParams Ext;
public ServerRpcParams Server;
public ClientRpcParams Client;
+
+ ///
+ /// Internal information used by to help handle this message.
+ ///
+ internal ulong SenderId;
}
}
diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkBehaviourEditorTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkBehaviourEditorTests.cs
index 67bc886ae7..ca7d121ea5 100644
--- a/com.unity.netcode.gameobjects/Tests/Editor/NetworkBehaviourEditorTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkBehaviourEditorTests.cs
@@ -1,5 +1,7 @@
+using System.Collections;
using NUnit.Framework;
using UnityEngine;
+using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace Unity.Netcode.EditorTests
@@ -69,6 +71,34 @@ public void AccessNetworkObjectTestInDerivedClassWithOverrideFunctions()
Object.DestroyImmediate(gameObject);
}
+ [UnityTest]
+ public IEnumerator RpcShouldNoopWhenInvokedWithoutANetworkManagerSession()
+ {
+ var noNetworkError = "Rpc methods can only be invoked after starting the NetworkManager!";
+ var gameObject = new GameObject(nameof(AccessNetworkObjectTestInDerivedClassWithOverrideFunctions));
+ var networkBehaviour = gameObject.AddComponent();
+
+ // No networkManager exists so error should be logged
+ LogAssert.Expect(LogType.Error, noNetworkError);
+ networkBehaviour.NoNetworkRpc();
+
+ // Ensure RPC was not invoked locally
+ yield return null;
+ Assert.That(networkBehaviour.RpcWasInvoked, Is.False);
+
+ var networkManager = gameObject.AddComponent();
+ networkManager.SetSingleton();
+
+ LogAssert.Expect(LogType.Error, noNetworkError);
+ networkBehaviour.NoNetworkRpc();
+
+ // Ensure RPC was not invoked locally
+ yield return null;
+ Assert.That(networkBehaviour.RpcWasInvoked, Is.False);
+
+ Object.DestroyImmediate(gameObject);
+ }
+
// Note: in order to repro https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues/1078
// this child class must be defined before its parent to assure it is processed first by ILPP
internal class DerivedNetworkBehaviour : EmptyNetworkBehaviour
@@ -78,5 +108,16 @@ internal class DerivedNetworkBehaviour : EmptyNetworkBehaviour
internal class EmptyNetworkBehaviour : NetworkBehaviour
{
}
+
+ internal class RpcNetworkBehaviour : NetworkBehaviour
+ {
+ public bool RpcWasInvoked;
+
+ [Rpc(SendTo.Everyone)]
+ public void NoNetworkRpc()
+ {
+ RpcWasInvoked = true;
+ }
+ }
}
}
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformParentingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformParentingTests.cs
index 896e7ddda2..c76e7b3ef8 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformParentingTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformParentingTests.cs
@@ -99,8 +99,8 @@ public override void OnNetworkSpawn()
/// A ServerRpc that requests the server to spawn a player object for the client that invoked this RPC.
///
/// Parameters for the ServerRpc, including the sender's client ID.
- [ServerRpc(RequireOwnership = false)]
- private void RequestPlayerObjectSpawnServerRpc(ServerRpcParams rpcParams = default)
+ [Rpc(SendTo.Server)]
+ private void RequestPlayerObjectSpawnServerRpc(RpcParams rpcParams = default)
{
SpawnedPlayer = Instantiate(PlayerPrefab);
SpawnedPlayer.SpawnAsPlayerObject(rpcParams.Receive.SenderClientId);
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc.meta
new file mode 100644
index 0000000000..765c779812
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c6bfc9360d1b4fe495c54c1f3004bb39
+timeCreated: 1760724018
\ No newline at end of file
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs
new file mode 100644
index 0000000000..ba9708da9b
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs
@@ -0,0 +1,502 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Text;
+using NUnit.Framework;
+using Unity.Collections;
+using Unity.Netcode.TestHelpers.Runtime;
+using UnityEngine;
+using UnityEngine.TestTools;
+
+namespace Unity.Netcode.RuntimeTests
+{
+ [TestFixture(NetworkTopologyTypes.DistributedAuthority)]
+ [TestFixture(NetworkTopologyTypes.ClientServer)]
+ internal class RpcInvocationTests : NetcodeIntegrationTest
+ {
+ protected override int NumberOfClients => 3;
+
+ public RpcInvocationTests(NetworkTopologyTypes topologyType) : base(topologyType) { }
+
+ private GameObject m_Prefab;
+
+ private Dictionary m_InvokeInstances = new();
+
+ // TODO: [CmbServiceTests] Enable once the CMB service fixes the client spoofing issue.
+ protected override bool UseCMBService() => false;
+
+ protected override void OnServerAndClientsCreated()
+ {
+ m_Prefab = CreateNetworkObjectPrefab("RpcInvokePermissionTest");
+ m_Prefab.AddComponent();
+
+ base.OnServerAndClientsCreated();
+ }
+
+ private void BuildInvokeInstancesMap(ulong networkObjectId)
+ {
+ m_InvokeInstances.Clear();
+ foreach (var manager in m_NetworkManagers)
+ {
+ Assert.IsTrue(manager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var instance));
+ m_InvokeInstances.Add(manager, instance.GetComponent());
+ }
+ }
+
+ private bool AllExpectedCallsReceived(StringBuilder errorLog)
+ {
+ var allInstancesValid = true;
+ foreach (var instance in m_InvokeInstances.Values)
+ {
+ if (!instance.HasReceivedExpectedRpcs(errorLog))
+ {
+ allInstancesValid = false;
+ }
+ }
+ return allInstancesValid;
+ }
+
+ [UnityTest]
+ public IEnumerator RpcInvokePermissionSendingTests()
+ {
+ var nonAuthority = GetNonAuthorityNetworkManager();
+ var authorityInstance = SpawnObject(m_Prefab, nonAuthority).GetComponent();
+
+ yield return WaitForSpawnedOnAllOrTimeOut(authorityInstance);
+ AssertOnTimeout("Failed to spawn InvokePermissions test object");
+
+ BuildInvokeInstancesMap(authorityInstance.NetworkObjectId);
+
+ // [Rpc(SendTo.Everyone, InvokePermission.Server)]
+ foreach (var (manager, instance) in m_InvokeInstances)
+ {
+ // When using the Cmb service there is no server so no calls should be made.
+ if (!m_UseCmbService)
+ {
+ instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.ServerInvokePermissionRpc)] = 1;
+ }
+
+ var threwException = false;
+ try
+ {
+ instance.ServerInvokePermissionRpc();
+ }
+ catch (RpcException)
+ {
+ Assert.IsFalse(manager.IsServer);
+ threwException = true;
+ }
+
+ // Server should not throw, everyone else should throw
+ Assert.AreEqual(!manager.IsServer, threwException, $"[Client-{manager.LocalClientId}] had an unexpected exception behaviour. Expected {(manager.IsServer ? "no exception" : "exception")} but was {(threwException ? "exception" : "no exception")}");
+ }
+
+ yield return WaitForConditionOrTimeOut(AllExpectedCallsReceived);
+ AssertOnTimeout("[InvokePermissions.Server] Rpc invoked an incorrect number of times");
+
+ // [Rpc(SendTo.Everyone, InvokePermission.Owner)]
+ foreach (var (manager, instance) in m_InvokeInstances)
+ {
+ instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.OwnerInvokePermissionRpc)] = 1;
+
+ var threwException = false;
+ try
+ {
+ instance.OwnerInvokePermissionRpc();
+ }
+ catch (RpcException)
+ {
+ Assert.IsFalse(instance.IsOwner);
+ threwException = true;
+ }
+
+ Assert.AreEqual(!instance.IsOwner, threwException, $"[Client-{manager.LocalClientId}] had an unexpected exception behaviour. Expected {(instance.IsOwner ? "no exception" : "exception")} but was {(threwException ? "exception" : "no exception")}");
+ }
+
+ yield return WaitForConditionOrTimeOut(AllExpectedCallsReceived);
+ AssertOnTimeout("[InvokePermissions.Owner] Rpc invoked an incorrect number of times");
+
+ // [Rpc(SendTo.Everyone, InvokePermission.Everyone)]
+ foreach (var (_, instance) in m_InvokeInstances)
+ {
+ instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.EveryoneInvokePermissionRpc)] = NumberOfClients + 1;
+
+ try
+ {
+ instance.EveryoneInvokePermissionRpc();
+ }
+ catch (RpcException e)
+ {
+ Assert.Fail($"Unexpected RpcException was thrown! Exception: {e}");
+ }
+ }
+
+ yield return WaitForConditionOrTimeOut(AllExpectedCallsReceived);
+ AssertOnTimeout("[InvokePermissions.Everyone] Rpc invoked an incorrect number of times");
+ }
+
+
+ [UnityTest]
+ public IEnumerator RpcInvokePermissionReceivingTests()
+ {
+ var firstClient = GetNonAuthorityNetworkManager(0);
+
+ var spawnedObject = SpawnObject(m_Prefab, firstClient).GetComponent();
+
+ yield return WaitForSpawnedOnAllOrTimeOut(spawnedObject);
+ AssertOnTimeout("Failed to spawn InvokePermissions test object");
+
+ BuildInvokeInstancesMap(spawnedObject.NetworkObjectId);
+
+ // [Rpc(SendTo.Everyone, InvokePermission.Server)]
+ foreach (var (manager, instance) in m_InvokeInstances)
+ {
+ // When using the Cmb service there is no server so no calls should be made.
+ if (!m_UseCmbService)
+ {
+ instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.ServerInvokePermissionRpc)] = 1;
+ }
+
+ SendUncheckedMessage(manager, instance, nameof(InvokePermissionBehaviour.ServerInvokePermissionRpc));
+ }
+
+ yield return WaitForConditionOrTimeOut(AllExpectedCallsReceived);
+ AssertOnTimeout("[InvokePermissions.Server] Incorrect Rpc calls received");
+
+ // [Rpc(SendTo.Everyone, InvokePermission.Owner)]
+ foreach (var (manager, instance) in m_InvokeInstances)
+ {
+ instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.OwnerInvokePermissionRpc)] = 1;
+
+ SendUncheckedMessage(manager, instance, nameof(InvokePermissionBehaviour.OwnerInvokePermissionRpc));
+ }
+
+ yield return WaitForConditionOrTimeOut(AllExpectedCallsReceived);
+ AssertOnTimeout("[InvokePermissions.Owner] Incorrect Rpc calls received");
+
+ // [Rpc(SendTo.Everyone, InvokePermission.Everyone)]
+ foreach (var (manager, instance) in m_InvokeInstances)
+ {
+ instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.EveryoneInvokePermissionRpc)] = NumberOfClients + 1;
+
+ SendUncheckedMessage(manager, instance, nameof(InvokePermissionBehaviour.EveryoneInvokePermissionRpc));
+ }
+
+ yield return WaitForConditionOrTimeOut(AllExpectedCallsReceived);
+ AssertOnTimeout("[InvokePermissions.Everyone] Incorrect Rpc calls received");
+
+ var firstClientInstance = m_InvokeInstances[firstClient];
+ var secondClient = GetNonAuthorityNetworkManager(1);
+ var thirdClient = GetNonAuthorityNetworkManager(2);
+
+ firstClientInstance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.TrackSenderIdRpc)] = 1;
+
+ // Manually set the senderId to an incorrect value
+ var secondClientInstance = m_InvokeInstances[secondClient];
+ var bufferWriter = new FastBufferWriter(1024, Allocator.Temp);
+ using (bufferWriter)
+ {
+ var rpcMessage = new RpcMessage
+ {
+ Metadata = new RpcMetadata
+ {
+ NetworkObjectId = secondClientInstance.NetworkObjectId,
+ NetworkBehaviourId = secondClientInstance.NetworkBehaviourId,
+ NetworkRpcMethodId = GetMethodIdFromMethodName(nameof(InvokePermissionBehaviour.TrackSenderIdRpc)),
+ },
+ // Set the sender to the third client
+ SenderClientId = thirdClient.LocalClientId,
+ WriteBuffer = bufferWriter
+ };
+
+ // Send the message on the second client
+ secondClientInstance.RpcTarget.Owner.Send(secondClientInstance, ref rpcMessage, NetworkDelivery.Reliable, new RpcParams());
+ }
+
+ yield return WaitForConditionOrTimeOut(AllExpectedCallsReceived);
+ AssertOnTimeout("[SpoofedSenderId] Incorrect Rpc calls received");
+
+ Assert.That(firstClientInstance.SenderIdReceived, Is.EqualTo(secondClient.LocalClientId), "Received spoofed sender id!");
+ }
+
+ private bool ValidateInvocationOrder(StringBuilder errorLog)
+ {
+ var allInstancesValid = true;
+ foreach (var instance in m_InvokeInstances.Values)
+ {
+ if (!instance.RpcsWereInvokedInExpectedOrder(errorLog))
+ {
+ allInstancesValid = false;
+ }
+ }
+ return allInstancesValid;
+ }
+
+ [UnityTest]
+ public IEnumerator RpcInvocationOrderTests()
+ {
+ var authority = GetAuthorityNetworkManager();
+ var authorityInstance = SpawnObject(m_Prefab, authority).GetComponent();
+ var errorLog = new StringBuilder();
+
+ yield return WaitForSpawnedOnAllOrTimeOut(authorityInstance.NetworkObjectId);
+ AssertOnTimeout("Failed to spawn InvokePermissions test object");
+
+ Assert.IsTrue(authorityInstance.IsOwner);
+
+ BuildInvokeInstancesMap(authorityInstance.NetworkObjectId);
+
+ var expectedOrder = new List()
+ {
+ nameof(InvokePermissionBehaviour.EveryoneInvokePermissionRpc),
+ nameof(InvokePermissionBehaviour.OwnerInvokePermissionRpc),
+ nameof(InvokePermissionBehaviour.AnotherEveryoneInvokePermissionRpc),
+ };
+ foreach (var instance in m_InvokeInstances.Values)
+ {
+ instance.ExpectedInvocationOrder = expectedOrder;
+ instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.EveryoneInvokePermissionRpc)] = 1;
+ instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.OwnerInvokePermissionRpc)] = 1;
+ instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.AnotherEveryoneInvokePermissionRpc)] = 1;
+ }
+
+ authorityInstance.EveryoneInvokePermissionRpc();
+ authorityInstance.OwnerInvokePermissionRpc();
+ authorityInstance.AnotherEveryoneInvokePermissionRpc();
+
+ yield return WaitForConditionOrTimeOut(AllExpectedCallsReceived);
+ AssertOnTimeout("[Simple ordering][authority] Incorrect number of rpcs were invoked");
+ Assert.IsTrue(ValidateInvocationOrder(errorLog), $"[Simple ordering][authority] Rpcs were invoked in an incorrect order\n {errorLog}");
+ errorLog.Clear();
+
+ ResetAllExpectedInvocations();
+
+ var nonAuthority = GetNonAuthorityNetworkManager();
+ var nonAuthorityInstance = m_InvokeInstances[nonAuthority];
+
+ expectedOrder = new List()
+ {
+ nameof(InvokePermissionBehaviour.AnotherEveryoneInvokePermissionRpc),
+ nameof(InvokePermissionBehaviour.EveryoneInvokePermissionRpc),
+ nameof(InvokePermissionBehaviour.AnotherEveryoneInvokePermissionRpc),
+ };
+ foreach (var instance in m_InvokeInstances.Values)
+ {
+ instance.ExpectedInvocationOrder = expectedOrder;
+ instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.EveryoneInvokePermissionRpc)] = 1;
+ instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.AnotherEveryoneInvokePermissionRpc)] = 2;
+ }
+
+ nonAuthorityInstance.AnotherEveryoneInvokePermissionRpc();
+ nonAuthorityInstance.EveryoneInvokePermissionRpc();
+ nonAuthorityInstance.AnotherEveryoneInvokePermissionRpc();
+
+ yield return WaitForConditionOrTimeOut(AllExpectedCallsReceived);
+ AssertOnTimeout("[Simple ordering][nonAuthority] Incorrect number of rpcs were invoked");
+ Assert.IsTrue(ValidateInvocationOrder(errorLog), $"[Simple ordering][nonAuthority] Rpcs were invoked in an incorrect order\n {errorLog}");
+ errorLog.Clear();
+
+ for (var i = 0; i < 3; i++)
+ {
+ var testType = (LocalDeferMode)i;
+
+ ResetAllExpectedInvocations();
+
+ expectedOrder = new List()
+ {
+ nameof(InvokePermissionBehaviour.NestedInvocationRpc),
+ nameof(InvokePermissionBehaviour.EveryoneInvokePermissionRpc),
+ };
+ var reversedOrder = new List() { expectedOrder[1], expectedOrder[0] };
+ foreach (var (manager, instance) in m_InvokeInstances)
+ {
+ // Invocation order will be reversed when not the invoking instance if not using defer mode
+ var isReversed = testType != LocalDeferMode.Defer && manager != nonAuthority;
+ instance.ExpectedInvocationOrder = isReversed ? reversedOrder : expectedOrder;
+ instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.NestedInvocationRpc)] = 1;
+ instance.ExpectedCallCounts[nameof(InvokePermissionBehaviour.EveryoneInvokePermissionRpc)] = 1;
+ }
+
+ nonAuthorityInstance.NestedInvocationRpc(testType);
+
+ yield return WaitForConditionOrTimeOut(AllExpectedCallsReceived);
+ AssertOnTimeout($"[Has nested][nonAuthority][{testType}] Incorrect number of rpcs were invoked");
+ Assert.IsTrue(ValidateInvocationOrder(errorLog), $"[Has nested][nonAuthority][{testType}] Rpcs were invoked in an incorrect order\n {errorLog}");
+ errorLog.Clear();
+ }
+ }
+
+ private void ResetAllExpectedInvocations()
+ {
+ foreach (var instance in m_InvokeInstances.Values)
+ {
+ instance.Reset();
+ }
+ }
+
+
+ private void SendUncheckedMessage(NetworkManager manager, InvokePermissionBehaviour invokePermissionsObject, string rpcMethodName)
+ {
+ using var bufferWriter = new FastBufferWriter(1024, Allocator.Temp);
+ var rpcMessage = new RpcMessage
+ {
+ Metadata = new RpcMetadata
+ {
+ NetworkObjectId = invokePermissionsObject.NetworkObjectId,
+ NetworkBehaviourId = invokePermissionsObject.NetworkBehaviourId,
+ NetworkRpcMethodId = GetMethodIdFromMethodName(rpcMethodName),
+ },
+ SenderClientId = manager.LocalClientId,
+ WriteBuffer = bufferWriter
+ };
+
+ invokePermissionsObject.RpcTarget.Everyone.Send(invokePermissionsObject, ref rpcMessage, NetworkDelivery.Reliable, new RpcParams());
+ }
+
+ private static readonly Dictionary k_MethodIdLookups = new();
+
+ private uint GetMethodIdFromMethodName(string methodName)
+ {
+ if (k_MethodIdLookups.TryGetValue(methodName, out var id))
+ {
+ return id;
+ }
+
+ var nameLookup = NetworkBehaviour.__rpc_name_table.GetValueOrDefault(typeof(InvokePermissionBehaviour));
+
+ foreach (var (rpcMethodId, rpcMethodName) in nameLookup)
+ {
+ if (rpcMethodName == methodName)
+ {
+ k_MethodIdLookups.Add(rpcMethodName, rpcMethodId);
+ return rpcMethodId;
+ }
+ }
+
+ Assert.Fail($"Method \"{methodName}\" was not found in rpc method id lookups.");
+ return default;
+ }
+ }
+
+ internal class InvokePermissionBehaviour : NetworkBehaviour
+ {
+ private readonly Dictionary m_RpcCallCounts = new();
+ public readonly Dictionary ExpectedCallCounts = new();
+ private readonly List m_RpcInvocationOrder = new();
+ public List ExpectedInvocationOrder = new();
+
+ public bool HasReceivedExpectedRpcs(StringBuilder errorLog)
+ {
+ var isValid = true;
+ var seen = new HashSet();
+ foreach (var (expectedMethodCall, expectedCallCount) in ExpectedCallCounts)
+ {
+ seen.Add(expectedMethodCall);
+ if (!m_RpcCallCounts.TryGetValue(expectedMethodCall, out var actualCallCount))
+ {
+ errorLog.AppendLine($"[Client-{NetworkManager.LocalClientId}] Expected {expectedMethodCall} to have been invoked!");
+ }
+
+ if (expectedCallCount != actualCallCount)
+ {
+ isValid = false;
+ errorLog.AppendLine($"[Client-{NetworkManager.LocalClientId}] {expectedMethodCall} was invoked an incorrect number of times! Expected: {expectedCallCount}, Received: {actualCallCount}");
+ }
+ }
+
+ // Ensure no other rpcs were called when they weren't expected
+ foreach (var rpcCall in m_RpcCallCounts.Keys)
+ {
+ if (!seen.Contains(rpcCall))
+ {
+ isValid = false;
+ errorLog.AppendLine($"[Client-{NetworkManager.LocalClientId}] {rpcCall} was invoked when it should not have been.");
+ }
+ }
+
+ return isValid;
+ }
+
+ public bool RpcsWereInvokedInExpectedOrder(StringBuilder errorLog)
+ {
+ var isValid = true;
+ for (var i = 0; i < m_RpcInvocationOrder.Count; i++)
+ {
+ if (!ExpectedInvocationOrder[i].Equals(m_RpcInvocationOrder[i]))
+ {
+ errorLog.AppendLine($"[Client-{NetworkManager.LocalClientId}][Invocation-{i}] Rpc invoked in incorrect order. Expected {ExpectedInvocationOrder[i]}, but was {m_RpcInvocationOrder[i]}");
+ isValid = false;
+ }
+ }
+ return isValid;
+ }
+
+ public void Reset()
+ {
+ m_RpcCallCounts.Clear();
+ ExpectedCallCounts.Clear();
+ m_RpcInvocationOrder.Clear();
+ ExpectedInvocationOrder.Clear();
+ }
+
+ [Rpc(SendTo.Everyone, InvokePermission = RpcInvokePermission.Server)]
+ public void ServerInvokePermissionRpc()
+ {
+ TrackRpcCalled(GetCaller());
+ }
+
+ [Rpc(SendTo.Everyone, InvokePermission = RpcInvokePermission.Owner)]
+ public void OwnerInvokePermissionRpc()
+ {
+ TrackRpcCalled(GetCaller());
+ }
+
+ [Rpc(SendTo.Everyone, InvokePermission = RpcInvokePermission.Everyone)]
+ public void EveryoneInvokePermissionRpc()
+ {
+ TrackRpcCalled(GetCaller());
+ }
+
+ [Rpc(SendTo.Everyone, InvokePermission = RpcInvokePermission.Everyone)]
+ public void AnotherEveryoneInvokePermissionRpc()
+ {
+ TrackRpcCalled(GetCaller());
+ }
+
+ [Rpc(SendTo.Everyone)]
+ public void NestedInvocationRpc(RpcParams rpcParams = default)
+ {
+ TrackRpcCalled(GetCaller());
+
+ if (rpcParams.Receive.SenderClientId == NetworkManager.LocalClientId)
+ {
+ EveryoneInvokePermissionRpc();
+ }
+ }
+
+ internal ulong SenderIdReceived;
+ [Rpc(SendTo.Owner)]
+ public void TrackSenderIdRpc(RpcParams rpcParams)
+ {
+ TrackRpcCalled(GetCaller());
+ SenderIdReceived = rpcParams.Receive.SenderClientId;
+ }
+
+ private void TrackRpcCalled(string rpcName)
+ {
+ // TryAdd returns false and will not add anything if the key already existed.
+ if (!m_RpcCallCounts.TryAdd(rpcName, 1))
+ {
+ // If the key already existed, increment it
+ m_RpcCallCounts[rpcName]++;
+ }
+
+ m_RpcInvocationOrder.Add(rpcName);
+ }
+
+ private static string GetCaller([CallerMemberName] string caller = null)
+ {
+ return caller;
+ }
+ }
+}
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs.meta
new file mode 100644
index 0000000000..5337d29a7b
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 9833d655491744a199494f0715dc8a62
+timeCreated: 1760724189
\ No newline at end of file
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcManyClientsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcManyClientsTests.cs
similarity index 92%
rename from com.unity.netcode.gameobjects/Tests/Runtime/RpcManyClientsTests.cs
rename to com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcManyClientsTests.cs
index 5af6daa6c0..62fb94bbe5 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/RpcManyClientsTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcManyClientsTests.cs
@@ -11,33 +11,33 @@ internal class RpcManyClientsObject : NetworkBehaviour
{
public int Count = 0;
public List ReceivedFrom = new List();
- [ServerRpc(RequireOwnership = false)]
- public void ResponseServerRpc(ServerRpcParams rpcParams = default)
+ [Rpc(SendTo.Server)]
+ public void ResponseServerRpc(RpcParams rpcParams = default)
{
ReceivedFrom.Add(rpcParams.Receive.SenderClientId);
Count++;
}
- [ClientRpc]
+ [Rpc(SendTo.ClientsAndHost)]
public void NoParamsClientRpc()
{
ResponseServerRpc();
}
- [ClientRpc]
+ [Rpc(SendTo.ClientsAndHost)]
public void OneParamClientRpc(int value)
{
ResponseServerRpc();
}
- [ClientRpc]
+ [Rpc(SendTo.ClientsAndHost)]
public void TwoParamsClientRpc(int value1, int value2)
{
ResponseServerRpc();
}
- [ClientRpc]
- public void WithParamsClientRpc(ClientRpcParams param)
+ [Rpc(SendTo.SpecifiedInParams)]
+ public void WithParamsClientRpc(RpcParams param)
{
ResponseServerRpc();
}
@@ -114,7 +114,7 @@ public void RpcManyClientsTest()
success = WaitForConditionOrTimeOutWithTimeTravel(() => TotalClients == rpcManyClientsObject.Count);
Assert.True(success, $"Timed out wait for {nameof(rpcManyClientsObject.OneParamClientRpc)}! Only {rpcManyClientsObject.Count} of {TotalClients} was received!");
- var param = new ClientRpcParams();
+ var param = new RpcParams();
rpcManyClientsObject.Count = 0;
rpcManyClientsObject.TwoParamsClientRpc(0, 0); // RPC with two params
@@ -127,8 +127,8 @@ public void RpcManyClientsTest()
rpcManyClientsObject.ReceivedFrom.Clear();
rpcManyClientsObject.Count = 0;
- var target = new List { m_ClientNetworkManagers[1].LocalClientId, m_ClientNetworkManagers[2].LocalClientId };
- param.Send.TargetClientIds = target;
+ var target = new[] { m_ClientNetworkManagers[1].LocalClientId, m_ClientNetworkManagers[2].LocalClientId };
+ param.Send.Target = rpcManyClientsObject.RpcTarget.Group(target, RpcTargetUse.Temp);
rpcManyClientsObject.WithParamsClientRpc(param);
messageHookList.Clear();
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcManyClientsTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcManyClientsTests.cs.meta
similarity index 100%
rename from com.unity.netcode.gameobjects/Tests/Runtime/RpcManyClientsTests.cs.meta
rename to com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcManyClientsTests.cs.meta
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcQueueTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs
similarity index 100%
rename from com.unity.netcode.gameobjects/Tests/Runtime/RpcQueueTests.cs
rename to com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcQueueTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs.meta
similarity index 100%
rename from com.unity.netcode.gameobjects/Tests/Runtime/RpcQueueTests.cs.meta
rename to com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs.meta
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTests.cs
similarity index 100%
rename from com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs
rename to com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTests.cs
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTests.cs.meta
similarity index 100%
rename from com.unity.netcode.gameobjects/Tests/Runtime/RpcTests.cs.meta
rename to com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTests.cs.meta
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTypeSerializationTests.cs
similarity index 100%
rename from com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs
rename to com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTypeSerializationTests.cs
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTypeSerializationTests.cs.meta
similarity index 100%
rename from com.unity.netcode.gameobjects/Tests/Runtime/RpcTypeSerializationTests.cs.meta
rename to com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTypeSerializationTests.cs.meta
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/UniversalRpcTests.cs
similarity index 99%
rename from com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs
rename to com.unity.netcode.gameobjects/Tests/Runtime/Rpc/UniversalRpcTests.cs
index bccb9aae40..be1a15de20 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/UniversalRpcTests.cs
@@ -433,69 +433,69 @@ public void DefaultToNotAuthorityDeferLocalRpc(RpcParams rpcParams)
OnRpcReceived();
}
- // RPCs with RequireOwnership = true
+ // RPCs with InvokePermission.Owner
- [Rpc(SendTo.Everyone, RequireOwnership = true)]
+ [Rpc(SendTo.Everyone, InvokePermission = RpcInvokePermission.Owner)]
public void DefaultToEveryoneRequireOwnershipRpc()
{
OnRpcReceived();
}
- [Rpc(SendTo.Me, RequireOwnership = true)]
+ [Rpc(SendTo.Me, InvokePermission = RpcInvokePermission.Owner)]
public void DefaultToMeRequireOwnershipRpc()
{
OnRpcReceived();
}
- [Rpc(SendTo.Owner, RequireOwnership = true)]
+ [Rpc(SendTo.Owner, InvokePermission = RpcInvokePermission.Owner)]
public void DefaultToOwnerRequireOwnershipRpc()
{
OnRpcReceived();
}
- [Rpc(SendTo.NotOwner, RequireOwnership = true)]
+ [Rpc(SendTo.NotOwner, InvokePermission = RpcInvokePermission.Owner)]
public void DefaultToNotOwnerRequireOwnershipRpc()
{
OnRpcReceived();
}
- [Rpc(SendTo.Server, RequireOwnership = true)]
+ [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Owner)]
public void DefaultToServerRequireOwnershipRpc()
{
OnRpcReceived();
}
- [Rpc(SendTo.NotMe, RequireOwnership = true)]
+ [Rpc(SendTo.NotMe, InvokePermission = RpcInvokePermission.Owner)]
public void DefaultToNotMeRequireOwnershipRpc()
{
OnRpcReceived();
}
- [Rpc(SendTo.NotServer, RequireOwnership = true)]
+ [Rpc(SendTo.NotServer, InvokePermission = RpcInvokePermission.Owner)]
public void DefaultToNotServerRequireOwnershipRpc()
{
OnRpcReceived();
}
- [Rpc(SendTo.ClientsAndHost, RequireOwnership = true)]
+ [Rpc(SendTo.ClientsAndHost, InvokePermission = RpcInvokePermission.Owner)]
public void DefaultToClientsAndHostRequireOwnershipRpc()
{
OnRpcReceived();
}
- [Rpc(SendTo.SpecifiedInParams, RequireOwnership = true)]
+ [Rpc(SendTo.SpecifiedInParams, InvokePermission = RpcInvokePermission.Owner)]
public void SpecifiedInParamsRequireOwnershipRpc(RpcParams rpcParams)
{
OnRpcReceived();
}
- [Rpc(SendTo.Authority, RequireOwnership = true)]
+ [Rpc(SendTo.Authority, InvokePermission = RpcInvokePermission.Owner)]
public void DefaultToAuthorityRequireOwnershipRpc()
{
OnRpcReceived();
}
- [Rpc(SendTo.NotAuthority, RequireOwnership = true)]
+ [Rpc(SendTo.NotAuthority, InvokePermission = RpcInvokePermission.Owner)]
public void DefaultToNotAuthorityRequireOwnershipRpc()
{
OnRpcReceived();
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/UniversalRpcTests.cs.meta
similarity index 100%
rename from com.unity.netcode.gameobjects/Tests/Runtime/UniversalRpcTests.cs.meta
rename to com.unity.netcode.gameobjects/Tests/Runtime/Rpc/UniversalRpcTests.cs.meta
diff --git a/testproject/Assets/Samples/SpawnObject/SpawnObjectHandler.cs b/testproject/Assets/Samples/SpawnObject/SpawnObjectHandler.cs
index cf1ee9ee24..1a5b88b07b 100644
--- a/testproject/Assets/Samples/SpawnObject/SpawnObjectHandler.cs
+++ b/testproject/Assets/Samples/SpawnObject/SpawnObjectHandler.cs
@@ -65,8 +65,8 @@ private void AutoSpawnObject(ulong ownerId, int selection)
SetMotion(networkObject.gameObject);
}
- [ServerRpc(RequireOwnership = false)]
- private void AutoSpawnObjectServerRpc(int selection, ServerRpcParams serverRpcParams = default)
+ [Rpc(SendTo.Server)]
+ private void AutoSpawnObjectServerRpc(int selection, RpcParams serverRpcParams = default)
{
AutoSpawnObject(serverRpcParams.Receive.SenderClientId, selection);
}
@@ -88,8 +88,8 @@ public void AutoSpawnClick()
}
}
- [ServerRpc(RequireOwnership = false)]
- private void ManualSpawnObjectServerRpc(int selection, ServerRpcParams serverRpcParams = default)
+ [Rpc(SendTo.Server)]
+ private void ManualSpawnObjectServerRpc(int selection, RpcParams serverRpcParams = default)
{
AutoSpawnObject(serverRpcParams.Receive.SenderClientId, selection);
}
diff --git a/testproject/Assets/Scripts/GrabbableBall.cs b/testproject/Assets/Scripts/GrabbableBall.cs
index 181338d455..89a7526937 100644
--- a/testproject/Assets/Scripts/GrabbableBall.cs
+++ b/testproject/Assets/Scripts/GrabbableBall.cs
@@ -75,8 +75,8 @@ public override void OnNetworkObjectParentChanged(NetworkObject parentNetworkObj
}
}
- [ServerRpc(RequireOwnership = false)]
- private void TryGrabServerRpc(ServerRpcParams serverRpcParams = default)
+ [Rpc(SendTo.Server)]
+ private void TryGrabServerRpc(RpcParams serverRpcParams = default)
{
if (!m_IsGrabbed.Value)
{
diff --git a/testproject/Assets/Tests/Manual/DeltaPositionNetworkTransform/LinearMotionHandler.cs b/testproject/Assets/Tests/Manual/DeltaPositionNetworkTransform/LinearMotionHandler.cs
index d9b1567093..0e5ea3cc56 100644
--- a/testproject/Assets/Tests/Manual/DeltaPositionNetworkTransform/LinearMotionHandler.cs
+++ b/testproject/Assets/Tests/Manual/DeltaPositionNetworkTransform/LinearMotionHandler.cs
@@ -412,7 +412,7 @@ private void SetPositionText()
ClientDelta.text = $"C-Delta: {GetVector3AsString(ref m_ClientDelta)}";
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void OnNonAuthorityUpdatePositionServerRpc(Vector3 position)
{
m_ClientPosition = position;
diff --git a/testproject/Assets/Tests/Manual/HybridScripts/RpcQueueManualTests.cs b/testproject/Assets/Tests/Manual/HybridScripts/RpcQueueManualTests.cs
index db94d86579..ca7def99da 100644
--- a/testproject/Assets/Tests/Manual/HybridScripts/RpcQueueManualTests.cs
+++ b/testproject/Assets/Tests/Manual/HybridScripts/RpcQueueManualTests.cs
@@ -101,7 +101,7 @@ private enum NetworkManagerMode
private ClientRpcDirectTestingModes m_ClientRpcDirectTestingMode;
- private ServerRpcParams m_ServerRpcParams;
+ private RpcParams m_ServerRpcParams;
private ClientRpcParams m_ClientRpcParams;
private ClientRpcParams m_ClientRpcParamsMultiParameter;
@@ -616,8 +616,8 @@ private void UnifiedDirectUpdate()
///
/// the client side counter
///
- [ServerRpc(RequireOwnership = false)]
- private void OnSendCounterServerRpc(int counter, ulong clientId, ServerRpcParams parameters = default)
+ [Rpc(SendTo.Server)]
+ private void OnSendCounterServerRpc(int counter, ulong clientId, RpcParams parameters = default)
{
//This is just for debug purposes so I can trap for "non-local" clients
if (m_ClientSpecificCounters.ContainsKey(parameters.Receive.SenderClientId))
@@ -638,8 +638,8 @@ private void OnSendCounterServerRpc(int counter, ulong clientId, ServerRpcParams
/// Sends no parameters to the server
///
///
- [ServerRpc(RequireOwnership = false)]
- private void OnSendNoParametersServerRpc(ServerRpcParams parameters = default)
+ [Rpc(SendTo.Server)]
+ private void OnSendNoParametersServerRpc(RpcParams parameters = default)
{
m_ClientRpcParamsMultiParameter.Send.TargetClientIds = new[] { parameters.Receive.SenderClientId };
OnSendNoParametersClientRpc(m_ClientRpcParamsMultiParameter);
@@ -650,8 +650,8 @@ private void OnSendNoParametersServerRpc(ServerRpcParams parameters = default)
/// Sends multiple parameters to the server
///
///
- [ServerRpc(RequireOwnership = false)]
- private void OnSendMultiParametersServerRpc(int count, float floatValue, long longValue, ServerRpcParams parameters = default)
+ [Rpc(SendTo.Server)]
+ private void OnSendMultiParametersServerRpc(int count, float floatValue, long longValue, RpcParams parameters = default)
{
m_ClientRpcParamsMultiParameter.Send.TargetClientIds = new[] { parameters.Receive.SenderClientId };
OnSendMultiParametersClientRpc(count, floatValue, longValue, m_ClientRpcParamsMultiParameter);
diff --git a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs
index decc21ab13..f01e983799 100644
--- a/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs
+++ b/testproject/Assets/Tests/Manual/InSceneObjectParentingTests/ChildObjectScript.cs
@@ -95,8 +95,8 @@ private void PickUpDropItem(NetworkObject player, bool worldPositionStays = true
}
- [ServerRpc(RequireOwnership = false)]
- public void PickupItemServerRpc(bool worldPositionStays = true, ServerRpcParams serverRpcParams = default)
+ [Rpc(SendTo.Server)]
+ public void PickupItemServerRpc(bool worldPositionStays = true, RpcParams serverRpcParams = default)
{
if (NetworkManager.ConnectedClients.ContainsKey(serverRpcParams.Receive.SenderClientId))
{
@@ -104,8 +104,8 @@ public void PickupItemServerRpc(bool worldPositionStays = true, ServerRpcParams
}
}
- [ServerRpc(RequireOwnership = false)]
- public void DropItemServerRpc(ServerRpcParams serverRpcParams = default)
+ [Rpc(SendTo.Server)]
+ public void DropItemServerRpc(RpcParams serverRpcParams = default)
{
if (NetworkManager.ConnectedClients.ContainsKey(serverRpcParams.Receive.SenderClientId))
{
diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/LerpVsSlerpControls.cs b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/LerpVsSlerpControls.cs
index 9cd320255d..955e7939e5 100644
--- a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/LerpVsSlerpControls.cs
+++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/LerpVsSlerpControls.cs
@@ -60,7 +60,7 @@ private void UpdateRotationSpeed(float speed)
ChildMover.RotationSpeed = speed;
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void OnSliderUpdatedServerRpc(float sliderValue)
{
UpdateRotationSpeed(sliderValue);
@@ -80,7 +80,7 @@ private void UpdateSlerPosition(bool isOn)
ChildMover.SlerpPosition = isOn;
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void OnInterpolateToggleUpdatedServerRpc(bool toggleState)
{
UpdateSlerPosition(toggleState);
diff --git a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs
index 7898adb47b..5efbb54fa4 100644
--- a/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs
+++ b/testproject/Assets/Tests/Manual/NetworkAnimatorTests/AnimatedCubeController.cs
@@ -55,7 +55,7 @@ private bool IsOwnerAuthority()
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void ToggleRotateAnimationServerRpc(bool rotate)
{
m_Rotate = rotate;
@@ -82,7 +82,7 @@ internal void ToggleRotateAnimation()
}
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void PlayPulseAnimationServerRpc(bool rotate)
{
m_NetworkAnimator.SetTrigger("Pulse");
@@ -264,4 +264,3 @@ private void LateUpdate()
}
}
}
-
diff --git a/testproject/Assets/Tests/Manual/Scripts/IntegrationNetworkTransform.cs b/testproject/Assets/Tests/Manual/Scripts/IntegrationNetworkTransform.cs
index feeec569f4..9d1243f302 100644
--- a/testproject/Assets/Tests/Manual/Scripts/IntegrationNetworkTransform.cs
+++ b/testproject/Assets/Tests/Manual/Scripts/IntegrationNetworkTransform.cs
@@ -241,8 +241,8 @@ private void SendStateLogToServer(ulong ownerId)
}
}
- [ServerRpc(RequireOwnership = false)]
- private void AddLogEntryServerRpc(HalfPosDebugStates logEntry, ulong ownerId, ServerRpcParams serverRpcParams = default)
+ [Rpc(SendTo.Server)]
+ private void AddLogEntryServerRpc(HalfPosDebugStates logEntry, ulong ownerId, RpcParams serverRpcParams = default)
{
if (!m_FirstInitialStateUpdates.ContainsKey(ownerId))
{
@@ -316,7 +316,7 @@ private void DebugTransformStateUpdate(NetworkTransformState oldState, NetworkTr
OnNetworkTransformStateUpdate(ref m_NetworkTransformStateUpdate);
}
-#endif
+#endif
#if DEBUG_NETWORKTRANSFORM || UNITY_INCLUDE_TESTS
///
diff --git a/testproject/Assets/Tests/Manual/Scripts/StatsDisplay.cs b/testproject/Assets/Tests/Manual/Scripts/StatsDisplay.cs
index 4d45807013..d1482c0d7c 100644
--- a/testproject/Assets/Tests/Manual/Scripts/StatsDisplay.cs
+++ b/testproject/Assets/Tests/Manual/Scripts/StatsDisplay.cs
@@ -149,7 +149,7 @@ private void ReceiveStatsClientRpc(StatsInfoContainer statsinfo)
/// RPC used to notify server that a specific client wants to receive its stats info
///
///
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void GetStatsServerRPC(ulong clientId)
{
if (!m_ClientsToUpdate.Contains(clientId))
diff --git a/testproject/Assets/Tests/Runtime/RpcObserverTests.cs b/testproject/Assets/Tests/Runtime/RpcObserverTests.cs
index 0db86ccb1a..b71c7e03dd 100644
--- a/testproject/Assets/Tests/Runtime/RpcObserverTests.cs
+++ b/testproject/Assets/Tests/Runtime/RpcObserverTests.cs
@@ -326,8 +326,8 @@ public void ObserverMessageClientRpc(ClientRpcParams clientRpcParams = default)
/// Called by each observer client that received the ObserverMessageClientRpc message
/// The sender id is added to the ObserversThatReceivedRPC list
///
- [ServerRpc(RequireOwnership = false)]
- public void ObserverMessageServerRpc(ServerRpcParams serverRpcParams = default)
+ [Rpc(SendTo.Server)]
+ public void ObserverMessageServerRpc(RpcParams serverRpcParams = default)
{
ObserversThatReceivedRPC.Add(serverRpcParams.Receive.SenderClientId);
}
diff --git a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs
index e9cc45211f..ae10ea60c9 100644
--- a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs
+++ b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs
@@ -115,7 +115,7 @@ public IEnumerator NetworkSerializableTest()
yield return StartServerAndClients();
// [Client-Side] We only need to get the client side Player's NetworkObject so we can grab that instance of the TestSerializationComponent
- // Use the client's instance of the
+ // Use the client's instance of the
var targetContext = m_ClientNetworkManagers[0];
// When in distributed authority mode:
@@ -994,7 +994,7 @@ private void SendNotOwnerSerializedDataClassRpc(UserSerializableClass userSerial
/// Server receives the UserSerializableClass, modifies it, and sends it back
///
///
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void SendServerSerializedDataClassServerRpc(UserSerializableClass userSerializableClass)
{
ProcessSerializedDataClass(userSerializableClass);
@@ -1043,7 +1043,7 @@ private void SendTemplateStructNotOwnerRpc(TemplatedType t1val, TemplatedTy
OnTemplateStructUpdated?.Invoke(t1val, t2val, enumVal);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void SendTemplateStructServerRpc(TemplatedType t1val, TemplatedType.NestedTemplatedType t2val, TemplatedType.Enum enumVal)
{
Debug.Log($"Received server RPC values {t1val.Value} {t2val.Value1} {t2val.Value2} {enumVal}");
@@ -1093,7 +1093,7 @@ private void SendNetworkSerializableTemplateStructNotOwnerRpc(NetworkSerializabl
OnNetworkSerializableTemplateStructUpdated?.Invoke(t1val, t2val);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void SendNetworkSerializableTemplateStructServerRpc(NetworkSerializableTemplatedType t1val, NetworkSerializableTemplatedType.NestedTemplatedType t2val)
{
Debug.Log($"Received NetworkSerializable server RPC values {t1val.Value} {t2val.Value1} {t2val.Value2}");
@@ -1143,7 +1143,7 @@ private void SendTemplateStructNotOwnerRpc(TemplatedType[] t1val, Templated
OnTemplateStructsUpdated?.Invoke(t1val, t2val, enumVal);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void SendTemplateStructServerRpc(TemplatedType[] t1val, TemplatedType.NestedTemplatedType[] t2val, TemplatedType.Enum[] enumVal)
{
Debug.Log($"Received server RPC values {t1val[0].Value} {t2val[0].Value1} {t2val[0].Value2} {enumVal[0]}");
@@ -1194,7 +1194,7 @@ private void SendNetworkSerializableTemplateStructNotOwnerRpc(NetworkSerializabl
OnNetworkSerializableTemplateStructsUpdated?.Invoke(t1val, t2val);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void SendNetworkSerializableTemplateStructServerRpc(NetworkSerializableTemplatedType[] t1val, NetworkSerializableTemplatedType.NestedTemplatedType[] t2val)
{
Debug.Log($"Received NetworkSerializable server RPC values {t1val[0].Value} {t2val[0].Value1} {t2val[0].Value2}");
@@ -1248,7 +1248,7 @@ private void SendClientSerializedDataStructNotOwnerRpc(UserSerializableStruct us
/// Server receives the UserSerializableStruct, modifies it, and sends it back
///
///
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void SendServerSerializedDataStructServerRpc(UserSerializableStruct userSerializableStruct)
{
userSerializableStruct.MyintValue++;
@@ -1289,7 +1289,7 @@ public void SendMyObjectClientRpc(MyObject obj)
OnMyObjectUpdated?.Invoke(obj);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void SendMyObjectServerRpc(MyObject obj)
{
OnMyObjectUpdated?.Invoke(obj);
@@ -1317,7 +1317,7 @@ public void SendIntListClientRpc(List lst)
OnIntListUpdated?.Invoke(lst);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void SendIntListServerRpc(List lst)
{
OnIntListUpdated?.Invoke(lst);
@@ -1345,7 +1345,7 @@ public void SendStringListClientRpc(List lst)
OnStringListUpdated?.Invoke(lst);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void SendStringListServerRpc(List lst)
{
OnStringListUpdated?.Invoke(lst);
@@ -1373,7 +1373,7 @@ public void SendMyObjectPassedWithThisRefClientRpc(MyObjectPassedWithThisRef obj
OnMyObjectPassedWithThisRefUpdated?.Invoke(obj);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void SendMyObjectPassedWithThisRefServerRpc(MyObjectPassedWithThisRef obj)
{
OnMyObjectPassedWithThisRefUpdated?.Invoke(obj);
@@ -1401,7 +1401,7 @@ public void SendMySharedObjectReferencedByIdClientRpc(MySharedObjectReferencedBy
OnMySharedObjectReferencedByIdUpdated?.Invoke(obj);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void SendMySharedObjectReferencedByIdServerRpc(MySharedObjectReferencedById obj)
{
OnMySharedObjectReferencedByIdUpdated?.Invoke(obj);
@@ -1484,7 +1484,7 @@ private void SendClientSerializedDataClassArrayNotOwnerRpc(UserSerializableClass
/// that checks the order, and then passes it back to the client
///
///
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void SendServerSerializedDataClassArryServerRpc(UserSerializableClass[] userSerializableClasses)
{
OnSerializableClassesUpdatedServerRpc?.Invoke(userSerializableClasses);
@@ -1549,7 +1549,7 @@ private void SendClientSerializedDataStructArrayNotOwnerRpc(UserSerializableStru
/// that checks the order, and then passes it back to the client
///
///
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void SendServerSerializedDataStructArrayServerRpc(UserSerializableStruct[] userSerializableStructs)
{
OnSerializableStructsUpdatedServerRpc?.Invoke(userSerializableStructs);
@@ -1588,7 +1588,7 @@ public void SendMyObjectClientRpc(MyObject[] objs)
OnMyObjectUpdated?.Invoke(objs);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void SendMyObjectServerRpc(MyObject[] objs)
{
OnMyObjectUpdated?.Invoke(objs);
@@ -1616,7 +1616,7 @@ public void SendIntListClientRpc(List[] lists)
OnIntListUpdated?.Invoke(lists);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void SendIntListServerRpc(List[] lists)
{
OnIntListUpdated?.Invoke(lists);
@@ -1644,7 +1644,7 @@ public void SendStringListClientRpc(List[] lists)
OnStringListUpdated?.Invoke(lists);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void SendStringListServerRpc(List[] lists)
{
OnStringListUpdated?.Invoke(lists);
@@ -1672,7 +1672,7 @@ public void SendMyObjectPassedWithThisRefClientRpc(MyObjectPassedWithThisRef[] o
OnMyObjectPassedWithThisRefUpdated?.Invoke(objs);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void SendMyObjectPassedWithThisRefServerRpc(MyObjectPassedWithThisRef[] objs)
{
OnMyObjectPassedWithThisRefUpdated?.Invoke(objs);
@@ -1701,7 +1701,7 @@ public void SendMySharedObjectReferencedByIdClientRpc(MySharedObjectReferencedBy
OnMySharedObjectReferencedByIdUpdated?.Invoke(objs);
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void SendMySharedObjectReferencedByIdServerRpc(MySharedObjectReferencedById[] objs)
{
OnMySharedObjectReferencedByIdUpdated?.Invoke(objs);
diff --git a/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs b/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs
index d93f038a78..c16cceda66 100644
--- a/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs
+++ b/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs
@@ -47,7 +47,7 @@ public override void OnNetworkSpawn()
base.OnNetworkSpawn();
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void ClientToServerRpc()
{
Debug.Log($"Received {nameof(ClientToServerRpc)}");
diff --git a/testproject/Assets/Tests/Runtime/Support/ShutdownDuringOnNetworkSpawnBehaviour.cs b/testproject/Assets/Tests/Runtime/Support/ShutdownDuringOnNetworkSpawnBehaviour.cs
index 33b10b588d..7a8e303fa4 100644
--- a/testproject/Assets/Tests/Runtime/Support/ShutdownDuringOnNetworkSpawnBehaviour.cs
+++ b/testproject/Assets/Tests/Runtime/Support/ShutdownDuringOnNetworkSpawnBehaviour.cs
@@ -29,7 +29,7 @@ private void TestClientRpc()
++ClientRpcsCalled;
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
private void TestServerRpc()
{
++ServerRpcsCalled;
diff --git a/testproject/Legacy/MultiprocessRuntime/TestCoordinator.cs b/testproject/Legacy/MultiprocessRuntime/TestCoordinator.cs
index e9a7bdeb4f..6eeafd23b0 100644
--- a/testproject/Legacy/MultiprocessRuntime/TestCoordinator.cs
+++ b/testproject/Legacy/MultiprocessRuntime/TestCoordinator.cs
@@ -402,7 +402,7 @@ public static Func ConsumeClientIsFinished(ulong clientId, bool useTimeout
};
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void ClientFinishedServerRpc(ServerRpcParams p = default)
{
// signal from clients to the server to say the client is done with it's task
@@ -487,7 +487,7 @@ public void KeepAliveClientRpc()
m_TimeSinceLastKeepAlive = Time.time;
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void WriteTestResultsServerRpc(float result, ServerRpcParams receiveParams = default)
{
var senderId = receiveParams.Receive.SenderClientId;
@@ -506,16 +506,15 @@ public void WriteTestResultsServerRpc(float result, ServerRpcParams receiveParam
///
/// Use to log server-side without MultiprocessLogger formatting.
///
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void WriteErrorServerRpc(string errorMessage, ServerRpcParams receiveParams = default)
{
MultiprocessLogger.LogError($"[Netcode-Server Sender={receiveParams.Receive.SenderClientId}] {errorMessage}");
}
- [ServerRpc(RequireOwnership = false)]
+ [Rpc(SendTo.Server)]
public void WriteLogServerRpc(string logMessage, ServerRpcParams receiveParams = default)
{
MultiprocessLogger.Log($"[Netcode-Server Sender={receiveParams.Receive.SenderClientId}] {logMessage}");
}
}
-