diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index 36e37b0b0..98c7c2432 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -178,6 +178,7 @@ struct RpcItem NetworkRpcName Name; NetworkRpcInfo Info; BytesContainer ArgsData; + DataContainer Targets; }; namespace @@ -329,6 +330,46 @@ void BuildCachedTargets(const Array& clients, const DataContaine } } +void BuildCachedTargets(const Array& clients, const DataContainer& clientIds1, const Span& clientIds2, const uint32 excludedClientId = NetworkManager::ServerClientId) +{ + CachedTargets.Clear(); + if (clientIds1.IsValid()) + { + if (clientIds2.IsValid()) + { + for (const NetworkClient* client : clients) + { + if (client->State == NetworkConnectionState::Connected && client->ClientId != excludedClientId) + { + for (int32 i = 0; i < clientIds1.Length(); i++) + { + if (clientIds1[i] == client->ClientId) + { + for (int32 j = 0; j < clientIds2.Length(); j++) + { + if (clientIds2[j] == client->ClientId) + { + CachedTargets.Add(client->Connection); + break; + } + } + break; + } + } + } + } + } + else + { + BuildCachedTargets(clients, clientIds1, excludedClientId); + } + } + else + { + BuildCachedTargets(clients, clientIds2, excludedClientId); + } +} + FORCE_INLINE void BuildCachedTargets(const NetworkReplicatedObject& item) { // By default send object to all connected clients excluding the owner but with optional TargetClientIds list @@ -560,6 +601,11 @@ void InvokeObjectReplication(NetworkReplicatedObject& item, uint32 ownerFrame, b DirtyObjectImpl(item, obj); } +NetworkRpcParams::NetworkRpcParams(const NetworkStream* stream) + : SenderId(stream->SenderId) +{ +} + #if !COMPILE_WITHOUT_CSHARP #include "Engine/Scripting/ManagedCLR/MUtils.h" @@ -601,9 +647,9 @@ void NetworkReplicator::AddRPC(const ScriptingTypeHandle& typeHandle, const Stri NetworkRpcInfo::RPCsTable[rpcName] = rpcInfo; } -void NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream) +void NetworkReplicator::CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MonoArray* targetIds) { - EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream); + EndInvokeRPC(obj, type, GetCSharpCachedName(name), argsStream, MUtils::ToSpan(targetIds)); } StringAnsiView NetworkReplicator::GetCSharpCachedName(const StringAnsiView& name) @@ -886,7 +932,7 @@ NetworkStream* NetworkReplicator::BeginInvokeRPC() return CachedWriteStream; } -void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream) +void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span targetIds) { const NetworkRpcInfo* info = NetworkRpcInfo::RPCsTable.TryGet(NetworkRpcName(type, name)); if (!info || !obj || NetworkManager::IsOffline()) @@ -897,8 +943,8 @@ void NetworkReplicator::EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHa rpc.Name.First = type; rpc.Name.Second = name; rpc.Info = *info; - const Span argsData(argsStream->GetBuffer(), argsStream->GetPosition()); - rpc.ArgsData.Copy(argsData); + rpc.ArgsData.Copy(Span(argsStream->GetBuffer(), argsStream->GetPosition())); + rpc.Targets.Copy(targetIds); #if USE_EDITOR || !BUILD_RELEASE auto it = Objects.Find(obj->GetID()); if (it == Objects.End()) @@ -1279,12 +1325,16 @@ void NetworkInternal::NetworkReplicatorUpdate() if (e.Info.Server && isClient) { // Client -> Server +#if !BUILD_RELEASE + if (e.Targets.Length() != 0) + NETWORK_REPLICATOR_LOG(Error, "[NetworkReplicator] Server RPC '{}::{}' called with non-empty list of targets is not supported (only server will receive it)", e.Name.First.ToString(), e.Name.Second.ToString()); +#endif peer->EndSendMessage(channel, msg); } else if (e.Info.Client && (isServer || isHost)) { // Server -> Client(s) - BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, NetworkManager::LocalClientId); + BuildCachedTargets(NetworkManager::Clients, item.TargetClientIds, e.Targets, NetworkManager::LocalClientId); peer->EndSendMessage(channel, msg, CachedTargets); } } @@ -1310,7 +1360,7 @@ void NetworkInternal::OnNetworkMessageObjectReplicate(NetworkEvent& event, Netwo if (client && item.OwnerClientId != client->ClientId) return; - const uint32 senderClientId = client ? client->ClientId : NetworkManager::LocalClientId; + const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId; if (msgData.PartsCount == 1) { // Replicate @@ -1333,7 +1383,7 @@ void NetworkInternal::OnNetworkMessageObjectReplicatePart(NetworkEvent& event, N if (DespawnedObjects.Contains(msgData.ObjectId)) return; // Skip replicating not-existing objects - const uint32 senderClientId = client ? client->ClientId : NetworkManager::LocalClientId; + const uint32 senderClientId = client ? client->ClientId : NetworkManager::ServerClientId; AddObjectReplicateItem(event, msgData, msgData.PartStart, msgData.PartSize, senderClientId); } @@ -1627,7 +1677,7 @@ void NetworkInternal::OnNetworkMessageObjectRpc(NetworkEvent& event, NetworkClie if (CachedReadStream == nullptr) CachedReadStream = New(); NetworkStream* stream = CachedReadStream; - stream->SenderId = client ? client->ClientId : NetworkManager::LocalClientId; + stream->SenderId = client ? client->ClientId : NetworkManager::ServerClientId; stream->Initialize(event.Message.Buffer + event.Message.Position, msgData.ArgsSize); // Execute RPC diff --git a/Source/Engine/Networking/NetworkReplicator.cs b/Source/Engine/Networking/NetworkReplicator.cs index dc2bc77c8..45fc45ba1 100644 --- a/Source/Engine/Networking/NetworkReplicator.cs +++ b/Source/Engine/Networking/NetworkReplicator.cs @@ -109,10 +109,11 @@ namespace FlaxEngine.Networking /// The RPC type. /// The RPC name. /// The RPC serialized arguments stream returned from BeginInvokeRPC. + /// Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs. [Unmanaged] - public static void EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream) + public static void EndInvokeRPC(Object obj, Type type, string name, NetworkStream argsStream, uint[] targetIds = null) { - Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream)); + Internal_CSharpEndInvokeRPC(FlaxEngine.Object.GetUnmanagedPtr(obj), type, name, FlaxEngine.Object.GetUnmanagedPtr(argsStream), targetIds); } /// diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index 24cfddd97..e0893cc64 100644 --- a/Source/Engine/Networking/NetworkReplicator.h +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -3,6 +3,7 @@ #pragma once #include "Types.h" +#include "Engine/Core/Types/Span.h" #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingType.h" @@ -175,13 +176,14 @@ public: /// The RPC type. /// The RPC name. /// The RPC serialized arguments stream returned from BeginInvokeRPC. - static void EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream); + /// Optional list with network client IDs that should receive RPC. Empty to send on all clients. Ignored by Server RPCs. + static void EndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, Span targetIds = Span()); private: #if !COMPILE_WITHOUT_CSHARP API_FUNCTION(NoProxy) static void AddSerializer(const ScriptingTypeHandle& typeHandle, const Function& serialize, const Function& deserialize); API_FUNCTION(NoProxy) static void AddRPC(const ScriptingTypeHandle& typeHandle, const StringAnsiView& name, const Function& execute, bool isServer, bool isClient, NetworkChannelType channel); - API_FUNCTION(NoProxy) static void CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream); + API_FUNCTION(NoProxy) static void CSharpEndInvokeRPC(ScriptingObject* obj, const ScriptingTypeHandle& type, const StringAnsiView& name, NetworkStream* argsStream, MonoArray* targetIds); static StringAnsiView GetCSharpCachedName(const StringAnsiView& name); #endif }; diff --git a/Source/Engine/Networking/NetworkRpc.h b/Source/Engine/Networking/NetworkRpc.h index dc485c3bb..c0c8a558e 100644 --- a/Source/Engine/Networking/NetworkRpc.h +++ b/Source/Engine/Networking/NetworkRpc.h @@ -5,6 +5,7 @@ #include "Engine/Core/Log.h" #include "Engine/Core/Types/StringView.h" #include "Engine/Core/Types/Pair.h" +#include "Engine/Core/Types/Span.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Scripting/ScriptingType.h" @@ -12,6 +13,24 @@ class NetworkStream; +// Additional context parameters for Network RPC execution (eg. to identify who sends the data). +API_STRUCT(NoDefault, Namespace="FlaxEngine.Networking") struct FLAXENGINE_API NetworkRpcParams +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkRpcParams); + NetworkRpcParams() = default; + NetworkRpcParams(const NetworkStream* stream); + + /// + /// The ClientId of the network client that is a data sender. Can be used to detect who send the incoming RPC or replication data. Ignored when sending data. + /// + API_FIELD() uint32 SenderId = 0; + + /// + /// The list of ClientId of the network clients that should receive RPC. Can be used to send RPC to a specific client(s). Ignored when receiving data. + /// + API_FIELD() Span TargetIds; +}; + // Network RPC identifier name (pair of type and function name) typedef Pair NetworkRpcName; diff --git a/Source/Engine/Networking/Types.h b/Source/Engine/Networking/Types.h index 5dd9fc140..11675023a 100644 --- a/Source/Engine/Networking/Types.h +++ b/Source/Engine/Networking/Types.h @@ -17,3 +17,4 @@ struct NetworkConnection; struct NetworkMessage; struct NetworkConfig; struct NetworkDriverStats; +struct NetworkRpcParams; diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 40f86ef66..1266e387d 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -13,7 +13,7 @@ using Mono.Cecil.Cil; namespace Flax.Build.Plugins { /// - /// Flax.Build plugin for Networking extenrions support. Generates required bindings glue code for automatic types replication and RPCs invoking. + /// Flax.Build plugin for Networking extensions support. Generates required bindings glue code for automatic types replication and RPCs invoking. /// /// internal sealed class NetworkingPlugin : Plugin @@ -231,9 +231,16 @@ namespace Flax.Build.Plugins var arg = functionInfo.Parameters[i]; if (i != 0) argNames += ", "; - argNames += arg.Name; - + + // Special handling of Rpc Params + if (!arg.Type.IsPtr && arg.Type.Type == "NetworkRpcParams") + { + argNames += "NetworkRpcParams(stream)"; + continue; + } + // Deserialize arguments + argNames += arg.Name; contents.AppendLine($" {arg.Type.Type} {arg.Name};"); contents.AppendLine($" stream->Read({arg.Name});"); } @@ -250,16 +257,24 @@ namespace Flax.Build.Plugins contents.Append(" static void ").Append(functionInfo.Name).AppendLine("_Invoke(ScriptingObject* obj, void** args)"); contents.AppendLine(" {"); contents.AppendLine(" NetworkStream* stream = NetworkReplicator::BeginInvokeRPC();"); + contents.AppendLine(" Span targetIds;"); for (int i = 0; i < functionInfo.Parameters.Count; i++) { var arg = functionInfo.Parameters[i]; + // Special handling of Rpc Params + if (!arg.Type.IsPtr && arg.Type.Type == "NetworkRpcParams") + { + contents.AppendLine($" targetIds = ((NetworkRpcParams*)args[{i}])->TargetIds;"); + continue; + } + // Serialize arguments contents.AppendLine($" stream->Write(*({arg.Type.Type}*)args[{i}]);"); } // Invoke RPC - contents.AppendLine($" NetworkReplicator::EndInvokeRPC(obj, {typeInfo.NativeName}::TypeInitializer, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), stream);"); + contents.AppendLine($" NetworkReplicator::EndInvokeRPC(obj, {typeInfo.NativeName}::TypeInitializer, StringAnsiView(\"{functionInfo.Name}\", {functionInfo.Name.Length}), stream, targetIds);"); contents.AppendLine(" }"); } contents.AppendLine(); @@ -1403,6 +1418,22 @@ namespace Flax.Build.Plugins { var parameter = method.Parameters[i]; var parameterType = parameter.ParameterType; + + // Special handling of Rpc Params + if (string.Equals(parameterType.FullName, "FlaxEngine.Networking.NetworkRpcParams", StringComparison.OrdinalIgnoreCase)) + { + // new NetworkRpcParams { SenderId = networkStream.SenderId } + il.Emit(OpCodes.Ldloca_S, (byte)(argsStart + i)); + il.Emit(OpCodes.Initobj, parameterType); + il.Emit(OpCodes.Ldloca_S, (byte)(argsStart + i)); + il.Emit(OpCodes.Ldloc_1); + var getSenderId = networkStreamType.Resolve().GetMethod("get_SenderId"); + il.Emit(OpCodes.Callvirt, module.ImportReference(getSenderId)); + var senderId = parameterType.Resolve().GetField("SenderId"); + il.Emit(OpCodes.Stfld, module.ImportReference(senderId)); + continue; + } + GenerateDotNetRPCSerializerType(ref context, type, false, argsStart + i, parameterType, il, networkStream.Resolve(), 1, null); } @@ -1432,6 +1463,8 @@ namespace Flax.Build.Plugins il.Body.InitLocals = true; var varsStart = il.Body.Variables.Count; + il.InsertBefore(ilStart, il.Create(OpCodes.Nop)); + // Is Server/Is Client boolean constants il.Body.Variables.Add(new VariableDefinition(module.ImportReference(boolType))); // [0] il.Body.Variables.Add(new VariableDefinition(module.ImportReference(boolType))); // [1] @@ -1476,14 +1509,25 @@ namespace Flax.Build.Plugins il.InsertBefore(ilStart, il.Create(OpCodes.Stloc, streamLocalIndex)); // stream loc=3 // Serialize all RPC parameters + var targetIdsArgIndex = -1; + FieldDefinition targetIdsField = null; for (int i = 0; i < method.Parameters.Count; i++) { var parameter = method.Parameters[i]; var parameterType = parameter.ParameterType; + + // Special handling of Rpc Params + if (string.Equals(parameterType.FullName, "FlaxEngine.Networking.NetworkRpcParams", StringComparison.OrdinalIgnoreCase)) + { + targetIdsArgIndex = i + 1; // NetworkRpcParams value argument index (starts at 1, 0 holds this) + targetIdsField = parameterType.Resolve().GetField("TargetIds"); + continue; + } + GenerateDotNetRPCSerializerType(ref context, type, true, i + 1, parameterType, il, networkStream.Resolve(), streamLocalIndex, ilStart); } - // NetworkReplicator.EndInvokeRPC(this, typeof(), "", stream); + // NetworkReplicator.EndInvokeRPC(this, typeof(), "", stream, targetIds); il.InsertBefore(ilStart, il.Create(OpCodes.Nop)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldarg_0)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldtoken, type)); @@ -1492,7 +1536,14 @@ namespace Flax.Build.Plugins il.InsertBefore(ilStart, il.Create(OpCodes.Call, module.ImportReference(getTypeFromHandle))); il.InsertBefore(ilStart, il.Create(OpCodes.Ldstr, method.Name)); il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, streamLocalIndex)); - var endInvokeRPC = networkReplicatorType.Resolve().GetMethod("EndInvokeRPC", 4); + if (targetIdsArgIndex != -1) + { + il.InsertBefore(ilStart, il.Create(OpCodes.Ldarg, targetIdsArgIndex)); + il.InsertBefore(ilStart, il.Create(OpCodes.Ldfld, module.ImportReference(targetIdsField))); + } + else + il.InsertBefore(ilStart, il.Create(OpCodes.Ldnull)); + var endInvokeRPC = networkReplicatorType.Resolve().GetMethod("EndInvokeRPC", 5); il.InsertBefore(ilStart, il.Create(OpCodes.Call, module.ImportReference(endInvokeRPC))); // if (server && networkMode == NetworkManagerMode.Client) return;