diff --git a/Source/Engine/Networking/NetworkReplicator.cpp b/Source/Engine/Networking/NetworkReplicator.cpp index b22cdf870..bceab67bb 100644 --- a/Source/Engine/Networking/NetworkReplicator.cpp +++ b/Source/Engine/Networking/NetworkReplicator.cpp @@ -96,6 +96,18 @@ void NetworkReplicationService::Dispose() NetworkReplicationService NetworkReplicationServiceInstance; +Dictionary NetworkReplicator::SerializersTable; + +void INetworkSerializable_Serialize(void* instance, NetworkStream* stream) +{ + ((INetworkSerializable*)instance)->Serialize(stream); +} + +void INetworkSerializable_Deserialize(void* instance, NetworkStream* stream) +{ + ((INetworkSerializable*)instance)->Deserialize(stream); +} + NetworkReplicatedObject* ResolveObject(Guid objectId, Guid ownerId, char objectTypeName[128]) { // Lookup object @@ -131,6 +143,25 @@ NetworkReplicatedObject* ResolveObject(Guid objectId, Guid ownerId, char objectT return nullptr; } +NetworkReplicator::SerializeFuncPair NetworkReplicator::GetSerializer(const ScriptingTypeHandle& typeHandle) +{ + // Get serializers pair from table + SerializeFuncPair result(nullptr, nullptr); + if (!SerializersTable.TryGet(typeHandle, result)) + { + // Fallback to INetworkSerializable interface (if type implements it) + const ScriptingType& type = typeHandle.GetType(); + const ScriptingType::InterfaceImplementation* interface = type.GetInterface(INetworkSerializable::TypeInitializer); + if (interface) + { + result.First = INetworkSerializable_Serialize; + result.Second = INetworkSerializable_Deserialize; + SerializersTable.Add(typeHandle, result); + } + } + return result; +} + void NetworkReplicator::AddObject(ScriptingObject* obj, ScriptingObject* owner) { if (!obj || NetworkManager::State == NetworkConnectionState::Offline) @@ -215,11 +246,11 @@ void NetworkInternal::NetworkReplicatorUpdate() } // Serialize object - // TODO: cache per-type serialization thunk to boost CPU performance stream->Initialize(); - if (auto* serializable = ScriptingObject::ToInterface(obj)) + const auto serializerFunc = NetworkReplicator::GetSerializer(obj->GetTypeHandle()).First; + if (serializerFunc) { - serializable->Serialize(stream); + serializerFunc(obj, stream); } else { @@ -227,7 +258,7 @@ void NetworkInternal::NetworkReplicatorUpdate() if (!item.InvalidTypeWarn) { item.InvalidTypeWarn = true; - LOG(Error, "[NetworkReplicator] Cannot serialize object {} (missing serialization logic)", item.ToString()); + LOG(Error, "[NetworkReplicator] Cannot serialize object {} of type {} (missing serialization logic)", item.ToString(), obj->GetType().ToString()); } #endif continue; @@ -279,10 +310,10 @@ void NetworkInternal::OnNetworkMessageReplicatedObject(NetworkEvent& event, Netw stream->Initialize(event.Message.Buffer + event.Message.Position, msgData.DataSize); // Deserialize object - // TODO: cache per-type serialization thunk to boost CPU performance - if (auto* serializable = ScriptingObject::ToInterface(obj)) + const auto deserializerFunc = NetworkReplicator::GetSerializer(obj->GetTypeHandle()).Second; + if (deserializerFunc) { - serializable->Deserialize(stream); + deserializerFunc(obj, stream); } else { @@ -290,7 +321,7 @@ void NetworkInternal::OnNetworkMessageReplicatedObject(NetworkEvent& event, Netw if (!item.InvalidTypeWarn) { item.InvalidTypeWarn = true; - LOG(Error, "[NetworkReplicator] Cannot serialize object {} (missing serialization logic)", item.ToString()); + LOG(Error, "[NetworkReplicator] Cannot serialize object {} of type {} (missing serialization logic)", item.ToString(), obj->GetType().ToString()); } #endif } diff --git a/Source/Engine/Networking/NetworkReplicator.h b/Source/Engine/Networking/NetworkReplicator.h index f66815a5e..90acc0bc0 100644 --- a/Source/Engine/Networking/NetworkReplicator.h +++ b/Source/Engine/Networking/NetworkReplicator.h @@ -3,6 +3,8 @@ #pragma once #include "Types.h" +#include "Engine/Core/Types/Pair.h" +#include "Engine/Core/Collections/Dictionary.h" #include "Engine/Scripting/ScriptingObject.h" #include "Engine/Scripting/ScriptingType.h" @@ -12,6 +14,17 @@ API_CLASS(static, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicator { DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkReplicator); + +public: + typedef void (*SerializeFunc)(void* instance, NetworkStream* stream); + typedef Pair SerializeFuncPair; + static SerializeFuncPair GetSerializer(const ScriptingTypeHandle& typeHandle); + + /// + /// Global table for registered types serialization methods (key is type name, value is pair of methods to serialize and deserialize object). + /// + static Dictionary SerializersTable; + public: /// /// Adds the object to the network replication system. diff --git a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs index eaafcc615..9cd21f16e 100644 --- a/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs +++ b/Source/Tools/Flax.Build/Bindings/BindingsGenerator.CSharp.cs @@ -117,7 +117,7 @@ namespace Flax.Build.Bindings ApiTypeInfo apiType = null; if (dot != -1) { - var type = new TypeInfo { Type = value.Substring(0, dot) }; + var type = new TypeInfo(value.Substring(0, dot)); apiType = FindApiTypeInfo(buildData, type, caller); } @@ -697,7 +697,7 @@ namespace Flax.Build.Bindings contents.Append("unsafe partial class ").Append(classInfo.Name); var hasBase = classInfo.BaseType != null && !classInfo.IsBaseTypeHidden; if (hasBase) - contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo { Type = classInfo.BaseType.Name }, classInfo)); + contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo(classInfo.BaseType), classInfo)); var hasInterface = false; if (classInfo.Interfaces != null) { @@ -1142,7 +1142,7 @@ namespace Flax.Build.Bindings contents.Append("private "); contents.Append("unsafe partial struct ").Append(structureInfo.Name); if (structureInfo.BaseType != null && structureInfo.IsPod) - contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo { Type = structureInfo.BaseType.Name }, structureInfo)); + contents.Append(" : ").Append(GenerateCSharpNativeToManaged(buildData, new TypeInfo(structureInfo.BaseType), structureInfo)); contents.AppendLine(); contents.Append(indent + "{"); indent += " "; diff --git a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs index a1e6bcb5d..5f3e774c9 100644 --- a/Source/Tools/Flax.Build/Bindings/TypeInfo.cs +++ b/Source/Tools/Flax.Build/Bindings/TypeInfo.cs @@ -55,6 +55,11 @@ namespace Flax.Build.Bindings GenericArgs = other.GenericArgs != null ? new List(other.GenericArgs) : null; } + public TypeInfo(ApiTypeInfo apiTypeInfo) + { + Type = apiTypeInfo.Name; + } + /// /// Inflates the type with typedefs for generic arguments. /// diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs new file mode 100644 index 000000000..e324aaff0 --- /dev/null +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -0,0 +1,205 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +using System; +using System.Text; +using System.Collections.Generic; +using Flax.Build.Bindings; + +namespace Flax.Build.Plugins +{ + /// + /// Flax.Build plugin for Networking extenrions support. Generates required bindings glue code for automatic types replication and RPCs invoking. + /// + /// + internal sealed class NetworkingPlugin : Plugin + { + internal const string NetworkReplicated = "NetworkReplicated"; + + /// + public override void Init() + { + base.Init(); + + BindingsGenerator.ParseMemberTag += OnParseMemberTag; + BindingsGenerator.GenerateCppTypeInternals += OnGenerateCppTypeInternals; + BindingsGenerator.GenerateCppTypeInitRuntime += OnGenerateCppTypeInitRuntime; + } + + private void OnParseMemberTag(ref bool valid, BindingsGenerator.TagParameter tag, MemberInfo memberInfo) + { + if (tag.Tag != NetworkReplicated) + return; + if (memberInfo is FieldInfo) + { + // Mark member as replicated + valid = true; + memberInfo.SetTag(NetworkReplicated, string.Empty); + } + } + + private void OnGenerateCppTypeInternals(Builder.BuildData buildData, ApiTypeInfo typeInfo, StringBuilder contents) + { + // Skip modules that don't use networking + var module = BindingsGenerator.CurrentModule; + if (module.GetTag(NetworkReplicated) == null) + return; + + // Check if type uses automated network replication + List fields = null; + if (typeInfo is ClassInfo classInfo) + { + fields = classInfo.Fields; + // TODO: add support for class properties replication + } + else if (typeInfo is StructureInfo structInfo) + { + fields = structInfo.Fields; + } + bool useReplication = false; + if (fields != null) + { + foreach (var fieldInfo in fields) + { + if (fieldInfo.GetTag(NetworkReplicated) != null) + { + useReplication = true; + break; + } + } + } + if (!useReplication) + return; + + typeInfo.SetTag(NetworkReplicated, string.Empty); + + // Generate C++ wrapper functions to serialize/deserialize type + BindingsGenerator.CppIncludeFiles.Add("Engine/Networking/NetworkReplicator.h"); + BindingsGenerator.CppIncludeFiles.Add("Engine/Networking/NetworkStream.h"); + OnGenerateCppTypeSerialize(buildData, typeInfo, contents, fields, true); + OnGenerateCppTypeSerialize(buildData, typeInfo, contents, fields, false); + } + + private void OnGenerateCppTypeSerialize(Builder.BuildData buildData, ApiTypeInfo typeInfo, StringBuilder contents, List fields, bool serialize) + { + var thunk1 = "INetworkSerializable_Serialize"; + var thunk2 = "INetworkSerializable_Deserialize"; + contents.Append(" static void ").Append(serialize ? thunk1 : thunk2).AppendLine("(void* instance, NetworkStream* stream)"); + contents.AppendLine(" {"); + contents.AppendLine($" {typeInfo.NativeName}& object = *({typeInfo.NativeName}*)instance;"); + if (IsRawPOD(buildData, typeInfo)) + { + // POD types as raw bytes + OnGenerateCppWriteRaw(contents, "object", serialize); + } + else + { + if (typeInfo is ClassStructInfo classStructInfo && classStructInfo.BaseType != null && classStructInfo.BaseType.Name != "ScriptingObject") + { + // Replicate base type + OnGenerateCppWriteSerializer(contents, classStructInfo.BaseType.NativeName, "object", serialize); + } + + // Replicate all marked fields + if (fields != null) + { + foreach (var fieldInfo in fields) + { + if (fieldInfo.GetTag(NetworkReplicated) == null) + continue; + OnGenerateCppTypeSerializeData(buildData, typeInfo, contents, fieldInfo.Type, $"object.{fieldInfo.Name}", serialize); + } + } + // TODO: add support for class properties replication + } + contents.AppendLine(" }"); + contents.AppendLine(); + } + + private bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo type) + { + // TODO: what if type fields have custom replication settings (eg. compression)? + type.EnsureInited(buildData); + return type.IsPod; + } + + private bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo caller, ApiTypeInfo apiType, TypeInfo type) + { + if (type.IsPod(buildData, caller)) + { + if (apiType != null) + return IsRawPOD(buildData, apiType); + } + return false; + } + + private void OnGenerateCppTypeSerializeData(Builder.BuildData buildData, ApiTypeInfo caller, StringBuilder contents, TypeInfo type, string name, bool serialize) + { + var apiType = BindingsGenerator.FindApiTypeInfo(buildData, type, caller); + if (apiType == null || IsRawPOD(buildData, caller, apiType, type)) + { + // POD types as raw bytes + if (type.IsPtr) + throw new Exception($"Invalid pointer type '{type}' that cannot be serialized for replication of {caller.Name}."); + if (type.IsRef) + throw new Exception($"Invalid reference type '{type}' that cannot be serialized for replication of {caller.Name}."); + OnGenerateCppWriteRaw(contents, "&" + name, serialize); + } + else if (apiType.IsScriptingObject) + { + // Object ID + if (serialize) + { + contents.AppendLine($" {{Guid id = {name} ? {name}->GetID() : Guid::Empty;"); + OnGenerateCppWriteRaw(contents, "&id", serialize); + contents.AppendLine(" }"); + } + else + { + contents.AppendLine($" {{Guid id;"); + OnGenerateCppWriteRaw(contents, "&id", serialize); + contents.AppendLine($" {name} = Scripting::TryFindObject(id);}}"); + } + } + else if (apiType.IsStruct) + { + if (type.IsPtr) + throw new Exception($"Invalid pointer type '{type}' that cannot be serialized for replication of {caller.Name}."); + if (type.IsRef) + throw new Exception($"Invalid reference type '{type}' that cannot be serialized for replication of {caller.Name}."); + + // Structure serializer + OnGenerateCppWriteSerializer(contents, apiType.NativeName, name, serialize); + } + else + { + // In-built serialization route (compiler will warn if type is not supported) + OnGenerateCppWriteRaw(contents, name, serialize); + } + } + + private void OnGenerateCppWriteRaw(StringBuilder contents, string data, bool serialize) + { + var method = serialize ? "Write" : "Read"; + contents.AppendLine($" stream->{method}({data});"); + } + + private void OnGenerateCppWriteSerializer(StringBuilder contents, string type, string data, bool serialize) + { + var comp = serialize ? "First" : "Second"; + contents.AppendLine($" {{const auto serializer = NetworkReplicator::GetSerializer({type}::TypeInitializer);"); + contents.AppendLine($" if (serializer.{comp})"); + contents.AppendLine($" serializer.{comp}(&{data}, stream);}}"); + } + + private void OnGenerateCppTypeInitRuntime(Builder.BuildData buildData, ApiTypeInfo typeInfo, StringBuilder contents) + { + if (typeInfo.GetTag(NetworkReplicated) == null) + return; + var typeNameNative = typeInfo.FullNameNative; + var typeNameInternal = typeInfo.FullNameNativeInternal; + + // Register generated serializer functions + contents.AppendLine($" NetworkReplicator::SerializersTable[ScriptingTypeHandle({typeNameNative}::TypeInitializer)] = NetworkReplicator::SerializeFuncPair({typeNameInternal}Internal::INetworkSerializable_Serialize, {typeNameInternal}Internal::INetworkSerializable_Deserialize);"); + } + } +} diff --git a/Source/Tools/Flax.Build/Flax.Build.csproj b/Source/Tools/Flax.Build/Flax.Build.csproj index 0922460f1..0d592b0ac 100644 --- a/Source/Tools/Flax.Build/Flax.Build.csproj +++ b/Source/Tools/Flax.Build/Flax.Build.csproj @@ -114,6 +114,7 @@ +