Add automatic replication for C# types with fields marked with NetworkReplicated attribute
This commit is contained in:
@@ -68,6 +68,12 @@ inline uint32 GetHash(const NetworkReplicatedObject& key)
|
||||
return GetHash(key.ObjectId);
|
||||
}
|
||||
|
||||
struct Serializer
|
||||
{
|
||||
NetworkReplicator::SerializeFunc Methods[2];
|
||||
void* Tags[2];
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
CriticalSection ObjectsLock;
|
||||
@@ -76,6 +82,7 @@ namespace
|
||||
NetworkStream* CachedWriteStream = nullptr;
|
||||
NetworkStream* CachedReadStream = nullptr;
|
||||
Array<NetworkConnection> CachedTargets;
|
||||
Dictionary<ScriptingTypeHandle, Serializer> SerializersTable;
|
||||
}
|
||||
|
||||
class NetworkReplicationService : public EngineService
|
||||
@@ -96,14 +103,12 @@ void NetworkReplicationService::Dispose()
|
||||
|
||||
NetworkReplicationService NetworkReplicationServiceInstance;
|
||||
|
||||
Dictionary<ScriptingTypeHandle, NetworkReplicator::SerializeFuncPair> NetworkReplicator::SerializersTable;
|
||||
|
||||
void INetworkSerializable_Serialize(void* instance, NetworkStream* stream)
|
||||
void INetworkSerializable_Serialize(void* instance, NetworkStream* stream, void* tag)
|
||||
{
|
||||
((INetworkSerializable*)instance)->Serialize(stream);
|
||||
}
|
||||
|
||||
void INetworkSerializable_Deserialize(void* instance, NetworkStream* stream)
|
||||
void INetworkSerializable_Deserialize(void* instance, NetworkStream* stream, void* tag)
|
||||
{
|
||||
((INetworkSerializable*)instance)->Deserialize(stream);
|
||||
}
|
||||
@@ -143,23 +148,60 @@ NetworkReplicatedObject* ResolveObject(Guid objectId, Guid ownerId, char objectT
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NetworkReplicator::SerializeFuncPair NetworkReplicator::GetSerializer(const ScriptingTypeHandle& typeHandle)
|
||||
#if !COMPILE_WITHOUT_CSHARP
|
||||
|
||||
#include "Engine/Scripting/ManagedCLR/MUtils.h"
|
||||
|
||||
void INetworkSerializable_Managed(void* instance, NetworkStream* stream, void* tag)
|
||||
{
|
||||
auto signature = (Function<void(void*, void*)>::Signature)tag;
|
||||
signature(instance, stream);
|
||||
}
|
||||
|
||||
void NetworkReplicator::AddSerializer(const ScriptingTypeHandle& typeHandle, const Function<void(void*, void*)>& serialize, const Function<void(void*, void*)>& deserialize)
|
||||
{
|
||||
if (!typeHandle)
|
||||
return;
|
||||
|
||||
// This assumes that C# glue code passed static method pointer (via Marshal.GetFunctionPointerForDelegate)
|
||||
const Serializer serializer{ INetworkSerializable_Managed, INetworkSerializable_Managed, *(SerializeFunc*)&serialize, *(SerializeFunc*)&deserialize };
|
||||
SerializersTable.Add(typeHandle, serializer);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void NetworkReplicator::AddSerializer(const ScriptingTypeHandle& typeHandle, SerializeFunc serialize, SerializeFunc deserialize, void* serializeTag, void* deserializeTag)
|
||||
{
|
||||
const Serializer serializer{ serialize, deserialize, serializeTag, deserializeTag };
|
||||
SerializersTable.Add(typeHandle, serializer);
|
||||
}
|
||||
|
||||
bool NetworkReplicator::InvokeSerializer(const ScriptingTypeHandle& typeHandle, void* instance, NetworkStream* stream, bool serialize)
|
||||
{
|
||||
if (!typeHandle || !instance || !stream)
|
||||
return true;
|
||||
|
||||
// Get serializers pair from table
|
||||
SerializeFuncPair result(nullptr, nullptr);
|
||||
if (!SerializersTable.TryGet(typeHandle, result))
|
||||
Serializer serializer;
|
||||
if (!SerializersTable.TryGet(typeHandle, serializer))
|
||||
{
|
||||
// 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);
|
||||
serializer.Methods[0] = INetworkSerializable_Serialize;
|
||||
serializer.Methods[1] = INetworkSerializable_Deserialize;
|
||||
SerializersTable.Add(typeHandle, serializer);
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
return result;
|
||||
|
||||
// Invoke serializer
|
||||
const byte idx = serialize ? 0 : 1;
|
||||
serializer.Methods[idx](instance, stream, serializer.Tags[idx]);
|
||||
return false;
|
||||
}
|
||||
|
||||
void NetworkReplicator::AddObject(ScriptingObject* obj, ScriptingObject* owner)
|
||||
@@ -247,12 +289,8 @@ void NetworkInternal::NetworkReplicatorUpdate()
|
||||
|
||||
// Serialize object
|
||||
stream->Initialize();
|
||||
const auto serializerFunc = NetworkReplicator::GetSerializer(obj->GetTypeHandle()).First;
|
||||
if (serializerFunc)
|
||||
{
|
||||
serializerFunc(obj, stream);
|
||||
}
|
||||
else
|
||||
const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, true);
|
||||
if (failed)
|
||||
{
|
||||
#if NETWORK_REPLICATOR_DEBUG_LOG
|
||||
if (!item.InvalidTypeWarn)
|
||||
@@ -310,15 +348,11 @@ void NetworkInternal::OnNetworkMessageReplicatedObject(NetworkEvent& event, Netw
|
||||
stream->Initialize(event.Message.Buffer + event.Message.Position, msgData.DataSize);
|
||||
|
||||
// Deserialize object
|
||||
const auto deserializerFunc = NetworkReplicator::GetSerializer(obj->GetTypeHandle()).Second;
|
||||
if (deserializerFunc)
|
||||
{
|
||||
deserializerFunc(obj, stream);
|
||||
}
|
||||
else
|
||||
const bool failed = NetworkReplicator::InvokeSerializer(obj->GetTypeHandle(), obj, stream, false);
|
||||
if (failed)
|
||||
{
|
||||
#if NETWORK_REPLICATOR_DEBUG_LOG
|
||||
if (!item.InvalidTypeWarn)
|
||||
if (failed && !item.InvalidTypeWarn)
|
||||
{
|
||||
item.InvalidTypeWarn = true;
|
||||
LOG(Error, "[NetworkReplicator] Cannot serialize object {} of type {} (missing serialization logic)", item.ToString(), obj->GetType().ToString());
|
||||
|
||||
97
Source/Engine/Networking/NetworkReplicator.cs
Normal file
97
Source/Engine/Networking/NetworkReplicator.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FlaxEngine.Networking
|
||||
{
|
||||
partial class NetworkReplicator
|
||||
{
|
||||
private static Dictionary<Type, KeyValuePair<SerializeFunc, SerializeFunc>> _managedSerializers;
|
||||
|
||||
#if FLAX_EDITOR
|
||||
private static void OnScriptsReloadBegin()
|
||||
{
|
||||
// Clear refs to managed types that will be hot-reloaded
|
||||
_managedSerializers.Clear();
|
||||
_managedSerializers = null;
|
||||
FlaxEditor.ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Network object replication serialization/deserialization delegate.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use Object.FromUnmanagedPtr(instancePtr/streamPtr) to get object or NetworkStream from raw native pointers.
|
||||
/// </remarks>
|
||||
/// <param name="instancePtr">var instance = Object.FromUnmanagedPtr(instancePtr)</param>
|
||||
/// <param name="streamPtr">var stream = (NetworkStream)Object.FromUnmanagedPtr(streamPtr)</param>
|
||||
public delegate void SerializeFunc(IntPtr instancePtr, IntPtr streamPtr);
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new serialization methods for a given C# type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use Object.FromUnmanagedPtr(instancePtr/streamPtr) to get object or NetworkStream from raw native pointers.
|
||||
/// </remarks>
|
||||
/// <param name="type">The C# type (class or structure).</param>
|
||||
/// <param name="serialize">Function to call for value serialization.</param>
|
||||
/// <param name="deserialize">Function to call for value deserialization.</param>
|
||||
[Unmanaged]
|
||||
public static void AddSerializer(Type type, SerializeFunc serialize, SerializeFunc deserialize)
|
||||
{
|
||||
// C#-only types (eg. custom C# structures) cannot use native serializers due to missing ScriptingType
|
||||
if (typeof(FlaxEngine.Object).IsAssignableFrom(type))
|
||||
{
|
||||
Internal_AddSerializer(type, Marshal.GetFunctionPointerForDelegate(serialize), Marshal.GetFunctionPointerForDelegate(deserialize));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_managedSerializers == null)
|
||||
{
|
||||
_managedSerializers = new Dictionary<Type, KeyValuePair<SerializeFunc, SerializeFunc>>();
|
||||
#if FLAX_EDITOR
|
||||
FlaxEditor.ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin;
|
||||
#endif
|
||||
}
|
||||
_managedSerializers[type] = new KeyValuePair<SerializeFunc, SerializeFunc>(serialize, deserialize);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the network replication serializer for a given type.
|
||||
/// </summary>
|
||||
/// <param name="type">The scripting type to serialize.</param>
|
||||
/// <param name="instance">The value instance to serialize.</param>
|
||||
/// <param name="stream">The input/output stream to use for serialization.</param>
|
||||
/// <param name="serialize">True if serialize, otherwise deserialize mode.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
[Unmanaged]
|
||||
public static bool InvokeSerializer(Type type, FlaxEngine.Object instance, NetworkStream stream, bool serialize)
|
||||
{
|
||||
return Internal_InvokeSerializer(type, FlaxEngine.Object.GetUnmanagedPtr(instance), FlaxEngine.Object.GetUnmanagedPtr(stream), serialize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the network replication serializer for a given type.
|
||||
/// </summary>
|
||||
/// <param name="type">The scripting type to serialize.</param>
|
||||
/// <param name="instance">The value instance to serialize.</param>
|
||||
/// <param name="stream">The input/output stream to use for serialization.</param>
|
||||
/// <param name="serialize">True if serialize, otherwise deserialize mode.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
[Unmanaged]
|
||||
public static bool InvokeSerializer(System.Type type, IntPtr instance, NetworkStream stream, bool serialize)
|
||||
{
|
||||
if (_managedSerializers != null && _managedSerializers.TryGetValue(type, out var e))
|
||||
{
|
||||
var serializer = serialize ? e.Key : e.Value;
|
||||
serializer(instance, FlaxEngine.Object.GetUnmanagedPtr(stream));
|
||||
return false;
|
||||
}
|
||||
return Internal_InvokeSerializer(type, instance, FlaxEngine.Object.GetUnmanagedPtr(stream), serialize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@
|
||||
#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"
|
||||
|
||||
@@ -14,18 +12,30 @@
|
||||
API_CLASS(static, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkReplicator
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(NetworkReplicator);
|
||||
friend class NetworkReplicatorInternal;
|
||||
typedef void (*SerializeFunc)(void* instance, NetworkStream* stream, void* tag);
|
||||
|
||||
public:
|
||||
typedef void (*SerializeFunc)(void* instance, NetworkStream* stream);
|
||||
typedef Pair<SerializeFunc, SerializeFunc> SerializeFuncPair;
|
||||
static SerializeFuncPair GetSerializer(const ScriptingTypeHandle& typeHandle);
|
||||
/// <summary>
|
||||
/// Adds the network replication serializer for a given type.
|
||||
/// </summary>
|
||||
/// <param name="typeHandle">The scripting type to serialize.</param>
|
||||
/// <param name="serialize">Serialization callback method.</param>
|
||||
/// <param name="deserialize">Deserialization callback method.</param>
|
||||
/// <param name="serializeTag">Serialization callback method tag value.</param>
|
||||
/// <param name="deserializeTag">Deserialization callback method tag value.</param>
|
||||
static void AddSerializer(const ScriptingTypeHandle& typeHandle, SerializeFunc serialize, SerializeFunc deserialize, void* serializeTag = nullptr, void* deserializeTag = nullptr);
|
||||
|
||||
/// <summary>
|
||||
/// Global table for registered types serialization methods (key is type name, value is pair of methods to serialize and deserialize object).
|
||||
/// Invokes the network replication serializer for a given type.
|
||||
/// </summary>
|
||||
static Dictionary<ScriptingTypeHandle, SerializeFuncPair> SerializersTable;
|
||||
/// <param name="typeHandle">The scripting type to serialize.</param>
|
||||
/// <param name="instance">The value instance to serialize.</param>
|
||||
/// <param name="stream">The input/output stream to use for serialization.</param>
|
||||
/// <param name="serialize">True if serialize, otherwise deserialize mode.</param>
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
API_FUNCTION(NoProxy) static bool InvokeSerializer(const ScriptingTypeHandle& typeHandle, void* instance, NetworkStream* stream, bool serialize);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Adds the object to the network replication system.
|
||||
/// </summary>
|
||||
@@ -33,4 +43,9 @@ public:
|
||||
/// <param name="obj">The object to replicate.</param>
|
||||
/// <param name="owner">The owner of the object (eg. player that spawned it).</param>
|
||||
API_FUNCTION() static void AddObject(ScriptingObject* obj, ScriptingObject* owner);
|
||||
|
||||
private:
|
||||
#if !COMPILE_WITHOUT_CSHARP
|
||||
API_FUNCTION(NoProxy) static void AddSerializer(const ScriptingTypeHandle& type, const Function<void(void*, void*)>& serialize, const Function<void(void*, void*)>& deserialize);
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a field or a property should be replicated over network.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public sealed class NetworkReplicatedAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,13 @@
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using Flax.Build.Graph;
|
||||
using Flax.Build.Bindings;
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
|
||||
namespace Flax.Build.Plugins
|
||||
{
|
||||
@@ -13,7 +18,48 @@ namespace Flax.Build.Plugins
|
||||
/// <seealso cref="Flax.Build.Plugin" />
|
||||
internal sealed class NetworkingPlugin : Plugin
|
||||
{
|
||||
private struct InBuildSerializer
|
||||
{
|
||||
public string WriteMethod;
|
||||
public string ReadMethod;
|
||||
|
||||
public InBuildSerializer(string write, string read)
|
||||
{
|
||||
WriteMethod = write;
|
||||
ReadMethod = read;
|
||||
}
|
||||
}
|
||||
|
||||
private struct TypeSerializer
|
||||
{
|
||||
public TypeDefinition Type;
|
||||
public MethodDefinition Serialize;
|
||||
public MethodDefinition Deserialize;
|
||||
}
|
||||
|
||||
internal const string NetworkReplicated = "NetworkReplicated";
|
||||
private const string Thunk1 = "INetworkSerializable_Serialize";
|
||||
private const string Thunk2 = "INetworkSerializable_Deserialize";
|
||||
private static Dictionary<string, InBuildSerializer> _inBuildSerializers = new Dictionary<string, InBuildSerializer>()
|
||||
{
|
||||
{ "System.Boolean", new InBuildSerializer("WriteBoolean", "ReadBoolean") },
|
||||
{ "System.Single", new InBuildSerializer("WriteSingle", "ReadSingle") },
|
||||
{ "System.Double", new InBuildSerializer("WriteDouble", "ReadDouble") },
|
||||
{ "System.Int64", new InBuildSerializer("WriteInt64", "ReadInt64") },
|
||||
{ "System.Int32", new InBuildSerializer("WriteInt32", "ReadInt32") },
|
||||
{ "System.Int16", new InBuildSerializer("WriteInt16", "ReadInt16") },
|
||||
{ "System.SByte", new InBuildSerializer("WriteSByte", "ReadSByte") },
|
||||
{ "System.UInt64", new InBuildSerializer("WriteUInt64", "ReadUInt64") },
|
||||
{ "System.UInt32", new InBuildSerializer("WriteUInt32", "ReadUInt32") },
|
||||
{ "System.UInt16", new InBuildSerializer("WriteUInt16", "ReadUInt16") },
|
||||
{ "System.Byte", new InBuildSerializer("WriteByte", "ReadByte") },
|
||||
{ "System.String", new InBuildSerializer("WriteString", "ReadString") },
|
||||
{ "System.Guid", new InBuildSerializer("WriteGuid", "ReadGuid") },
|
||||
{ "FlaxEngine.Vector2", new InBuildSerializer("WriteVector2", "ReadVector2") },
|
||||
{ "FlaxEngine.Vector3", new InBuildSerializer("WriteVector3", "ReadVector3") },
|
||||
{ "FlaxEngine.Vector4", new InBuildSerializer("WriteVector4", "ReadVector4") },
|
||||
{ "FlaxEngine.Quaternion", new InBuildSerializer("WriteQuaternion", "ReadQuaternion") },
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Init()
|
||||
@@ -23,6 +69,8 @@ namespace Flax.Build.Plugins
|
||||
BindingsGenerator.ParseMemberTag += OnParseMemberTag;
|
||||
BindingsGenerator.GenerateCppTypeInternals += OnGenerateCppTypeInternals;
|
||||
BindingsGenerator.GenerateCppTypeInitRuntime += OnGenerateCppTypeInitRuntime;
|
||||
BindingsGenerator.GenerateCSharpTypeInternals += OnGenerateCSharpTypeInternals;
|
||||
Builder.BuildDotNetAssembly += OnBuildDotNetAssembly;
|
||||
}
|
||||
|
||||
private void OnParseMemberTag(ref bool valid, BindingsGenerator.TagParameter tag, MemberInfo memberInfo)
|
||||
@@ -81,22 +129,20 @@ namespace Flax.Build.Plugins
|
||||
|
||||
private void OnGenerateCppTypeSerialize(Builder.BuildData buildData, ApiTypeInfo typeInfo, StringBuilder contents, List<FieldInfo> 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.Append(" static void ").Append(serialize ? Thunk1 : Thunk2).AppendLine("(void* instance, NetworkStream* stream, void* tag)");
|
||||
contents.AppendLine(" {");
|
||||
contents.AppendLine($" {typeInfo.NativeName}& object = *({typeInfo.NativeName}*)instance;");
|
||||
contents.AppendLine($" {typeInfo.NativeName}& obj = *({typeInfo.NativeName}*)instance;");
|
||||
if (IsRawPOD(buildData, typeInfo))
|
||||
{
|
||||
// POD types as raw bytes
|
||||
OnGenerateCppWriteRaw(contents, "object", serialize);
|
||||
OnGenerateCppWriteRaw(contents, "&obj", serialize);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (typeInfo is ClassStructInfo classStructInfo && classStructInfo.BaseType != null && classStructInfo.BaseType.Name != "ScriptingObject")
|
||||
{
|
||||
// Replicate base type
|
||||
OnGenerateCppWriteSerializer(contents, classStructInfo.BaseType.NativeName, "object", serialize);
|
||||
OnGenerateCppWriteSerializer(contents, classStructInfo.BaseType.NativeName, "obj", serialize);
|
||||
}
|
||||
|
||||
// Replicate all marked fields
|
||||
@@ -106,7 +152,7 @@ namespace Flax.Build.Plugins
|
||||
{
|
||||
if (fieldInfo.GetTag(NetworkReplicated) == null)
|
||||
continue;
|
||||
OnGenerateCppTypeSerializeData(buildData, typeInfo, contents, fieldInfo.Type, $"object.{fieldInfo.Name}", serialize);
|
||||
OnGenerateCppTypeSerializeData(buildData, typeInfo, contents, fieldInfo.Type, $"obj.{fieldInfo.Name}", serialize);
|
||||
}
|
||||
}
|
||||
// TODO: add support for class properties replication
|
||||
@@ -185,10 +231,8 @@ namespace Flax.Build.Plugins
|
||||
|
||||
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);}}");
|
||||
var mode = serialize ? "true" : "false";
|
||||
contents.AppendLine($" NetworkReplicator::InvokeSerializer({type}::TypeInitializer, &{data}, stream, {mode});");
|
||||
}
|
||||
|
||||
private void OnGenerateCppTypeInitRuntime(Builder.BuildData buildData, ApiTypeInfo typeInfo, StringBuilder contents)
|
||||
@@ -199,7 +243,569 @@ namespace Flax.Build.Plugins
|
||||
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);");
|
||||
contents.AppendLine($" NetworkReplicator::AddSerializer(ScriptingTypeHandle({typeNameNative}::TypeInitializer), {typeNameInternal}Internal::INetworkSerializable_Serialize, {typeNameInternal}Internal::INetworkSerializable_Deserialize);");
|
||||
}
|
||||
|
||||
private void OnGenerateCSharpTypeInternals(Builder.BuildData buildData, ApiTypeInfo typeInfo, StringBuilder contents, string indent)
|
||||
{
|
||||
// Skip types that don't use networking
|
||||
if (typeInfo.GetTag(NetworkReplicated) == null)
|
||||
return;
|
||||
|
||||
if (typeInfo is ClassInfo)
|
||||
return;
|
||||
|
||||
// Generate C# wrapper functions to serialize/deserialize type directly from managed code
|
||||
OnGenerateCSharpTypeSerialize(buildData, typeInfo, contents, indent, true);
|
||||
OnGenerateCSharpTypeSerialize(buildData, typeInfo, contents, indent, false);
|
||||
|
||||
}
|
||||
|
||||
private void OnGenerateCSharpTypeSerialize(Builder.BuildData buildData, ApiTypeInfo typeInfo, StringBuilder contents, string indent, bool serialize)
|
||||
{
|
||||
var mode = serialize ? "true" : "false";
|
||||
contents.AppendLine();
|
||||
contents.Append(indent).Append("public void ").Append(serialize ? Thunk1 : Thunk2).AppendLine("(FlaxEngine.Networking.NetworkStream stream)");
|
||||
contents.Append(indent).AppendLine("{");
|
||||
if (typeInfo is ClassInfo)
|
||||
{
|
||||
contents.Append(indent).AppendLine($" FlaxEngine.Networking.NetworkReplicator.InvokeSerializer(typeof({typeInfo.Name}), this, stream, {mode});");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (typeInfo.IsPod)
|
||||
{
|
||||
contents.Append(indent).AppendLine($" fixed ({typeInfo.Name}* ptr = &this)");
|
||||
contents.Append(indent).AppendLine($" FlaxEngine.Networking.NetworkReplicator.InvokeSerializer(typeof({typeInfo.Name}), new IntPtr(ptr), stream, {mode});");
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: generate C# data serialization (eg. in OnPatchAssembly) or generate internal call with managed -> native data conversion
|
||||
contents.Append(indent).AppendLine($" throw new NotImplementedException(\"Not supported native structure with references used in managed code for replication.\");");
|
||||
}
|
||||
}
|
||||
contents.Append(indent).AppendLine("}");
|
||||
}
|
||||
|
||||
private void OnBuildDotNetAssembly(TaskGraph graph, Builder.BuildData buildData, NativeCpp.BuildOptions buildOptions, Task buildTask, IGrouping<string, Module> binaryModule)
|
||||
{
|
||||
// Skip assemblies not using netowrking
|
||||
if (!binaryModule.Any(module => module.Tags.ContainsKey(NetworkReplicated)))
|
||||
return;
|
||||
|
||||
// Generate netoworking code inside assembly after it's being compiled
|
||||
var assemblyPath = buildTask.ProducedFiles[0];
|
||||
var task = graph.Add<Task>();
|
||||
task.ProducedFiles.Add(assemblyPath);
|
||||
task.WorkingDirectory = buildTask.WorkingDirectory;
|
||||
task.Command = () => OnPatchAssembly(buildData, buildOptions, buildTask, assemblyPath);
|
||||
task.CommandPath = null;
|
||||
task.InfoMessage = $"Generating netowrking code for {Path.GetFileName(assemblyPath)}...";
|
||||
task.Cost = 50;
|
||||
task.DisableCache = true;
|
||||
task.DependentTasks = new HashSet<Task>();
|
||||
task.DependentTasks.Add(buildTask);
|
||||
}
|
||||
|
||||
private void OnPatchAssembly(Builder.BuildData buildData, NativeCpp.BuildOptions buildOptions, Task buildTask, string assemblyPath)
|
||||
{
|
||||
using (DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver())
|
||||
using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters{ ReadWrite = true, ReadSymbols = true, AssemblyResolver = assemblyResolver }))
|
||||
{
|
||||
// Setup module search locations
|
||||
var searchDirectories = new HashSet<string>();
|
||||
searchDirectories.Add(Path.GetDirectoryName(assemblyPath));
|
||||
foreach (var file in buildTask.PrerequisiteFiles)
|
||||
{
|
||||
if (file.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
|
||||
searchDirectories.Add(Path.GetDirectoryName(file));
|
||||
}
|
||||
foreach (var e in searchDirectories)
|
||||
assemblyResolver.AddSearchDirectory(e);
|
||||
|
||||
ModuleDefinition module = assembly.MainModule;
|
||||
TypeReference voidType = module.ImportReference(typeof(void));
|
||||
module.GetType("FlaxEngine.Networking.NetworkStream", out var networkStreamType);
|
||||
|
||||
// Process all types within a module
|
||||
bool modified = false;
|
||||
bool failed = false;
|
||||
var addSerializers = new List<TypeSerializer>();
|
||||
foreach (TypeDefinition type in module.Types)
|
||||
{
|
||||
if (type.IsInterface || type.IsEnum)
|
||||
continue;
|
||||
var isNative = type.HasAttribute("FlaxEngine.UnmanagedAttribute");
|
||||
if (isNative)
|
||||
continue;
|
||||
if (type.HasMethod(Thunk1) || type.HasMethod(Thunk2))
|
||||
continue;
|
||||
var isINetworkSerializable = type.HasInterface("FlaxEngine.Networking.INetworkSerializable");
|
||||
MethodDefinition serializeINetworkSerializable = null, deserializeINetworkSerializable = null;
|
||||
if (isINetworkSerializable)
|
||||
{
|
||||
foreach (MethodDefinition m in type.Methods)
|
||||
{
|
||||
if (m.HasBody && m.Parameters.Count == 1 && m.Parameters[0].ParameterType.FullName == "FlaxEngine.Networking.NetworkStream")
|
||||
{
|
||||
if (m.Name == "Serialize")
|
||||
serializeINetworkSerializable = m;
|
||||
else if (m.Name == "Deserialize")
|
||||
deserializeINetworkSerializable = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
var isNetworkReplicated = false;
|
||||
foreach (FieldDefinition f in type.Fields)
|
||||
{
|
||||
if (!f.HasAttribute("FlaxEngine.NetworkReplicatedAttribute"))
|
||||
continue;
|
||||
isNetworkReplicated = true;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (type.IsValueType)
|
||||
{
|
||||
if (isINetworkSerializable)
|
||||
{
|
||||
// Generate INetworkSerializable interface method calls
|
||||
GenerateCallINetworkSerializable(type, Thunk1, voidType, networkStreamType, serializeINetworkSerializable);
|
||||
GenerateCallINetworkSerializable(type, Thunk2, voidType, networkStreamType, deserializeINetworkSerializable);
|
||||
modified = true;
|
||||
}
|
||||
else if (isNetworkReplicated)
|
||||
{
|
||||
// Generate serializization methods
|
||||
GenerateSerializer(type, true, ref failed, Thunk1, voidType, networkStreamType);
|
||||
GenerateSerializer(type, false, ref failed, Thunk2, voidType, networkStreamType);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
else if (!isINetworkSerializable && isNetworkReplicated)
|
||||
{
|
||||
// Generate serializization methods
|
||||
var addSerializer = new TypeSerializer();
|
||||
addSerializer.Type = type;
|
||||
addSerializer.Serialize = GenerateNativeSerializer(type, true, ref failed, Thunk1, voidType, networkStreamType);
|
||||
addSerializer.Deserialize = GenerateNativeSerializer(type, false, ref failed, Thunk2, voidType, networkStreamType);
|
||||
addSerializers.Add(addSerializer);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
if (failed)
|
||||
throw new Exception($"Failed to generate network replication for assembly {assemblyPath}");
|
||||
if (!modified)
|
||||
return;
|
||||
|
||||
// Generate serializers initializer (invoked on module load)
|
||||
if (addSerializers.Count != 0)
|
||||
{
|
||||
// Create class
|
||||
var name = "Initializer";
|
||||
var idx = 0;
|
||||
while (module.Types.Any(x => x.Name == name))
|
||||
name = "Initializer" + idx++;
|
||||
var c = new TypeDefinition("", name, TypeAttributes.Class | TypeAttributes.AutoLayout | TypeAttributes.AnsiClass | TypeAttributes.Abstract | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit);
|
||||
module.GetType("System.Object", out var objectType);
|
||||
c.BaseType = module.ImportReference(objectType);
|
||||
module.Types.Add(c);
|
||||
|
||||
// Add ModuleInitializer attribute
|
||||
module.GetType("FlaxEngine.ModuleInitializerAttribute", out var moduleInitializerAttribute);
|
||||
var ctor1 = moduleInitializerAttribute.Resolve();
|
||||
MethodDefinition ctor = moduleInitializerAttribute.Resolve().Methods[0];
|
||||
var attribute = new CustomAttribute(module.ImportReference(ctor));
|
||||
c.CustomAttributes.Add(attribute);
|
||||
|
||||
// Add Init method
|
||||
var m = new MethodDefinition("Init", MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig, voidType);
|
||||
ILProcessor il = m.Body.GetILProcessor();
|
||||
il.Emit(OpCodes.Nop);
|
||||
module.GetType("System.Type", out var typeType);
|
||||
var getTypeFromHandle = typeType.Resolve().GetMethod("GetTypeFromHandle");
|
||||
module.GetType("FlaxEngine.Networking.NetworkReplicator", out var networkReplicatorType);
|
||||
var addSerializer = networkReplicatorType.Resolve().GetMethod("AddSerializer", 3);
|
||||
module.ImportReference(addSerializer);
|
||||
var serializeFuncType = addSerializer.Parameters[1].ParameterType;
|
||||
var serializeFuncCtor = serializeFuncType.Resolve().GetMethod(".ctor");
|
||||
foreach (var e in addSerializers)
|
||||
{
|
||||
// NetworkReplicator.AddSerializer(typeof(<type>), <type>.INetworkSerializable_SerializeNative, <type>.INetworkSerializable_DeserializeNative);
|
||||
il.Emit(OpCodes.Ldtoken, e.Type);
|
||||
il.Emit(OpCodes.Call, module.ImportReference(getTypeFromHandle));
|
||||
il.Emit(OpCodes.Ldnull);
|
||||
il.Emit(OpCodes.Ldftn, e.Serialize);
|
||||
il.Emit(OpCodes.Newobj, module.ImportReference(serializeFuncCtor));
|
||||
il.Emit(OpCodes.Ldnull);
|
||||
il.Emit(OpCodes.Ldftn, e.Deserialize);
|
||||
il.Emit(OpCodes.Newobj, module.ImportReference(serializeFuncCtor));
|
||||
il.Emit(OpCodes.Call, module.ImportReference(addSerializer));
|
||||
}
|
||||
il.Emit(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Ret);
|
||||
c.Methods.Add(m);
|
||||
}
|
||||
|
||||
// Serialize assembly back to the file
|
||||
assembly.Write(new WriterParameters { WriteSymbols = true } );
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateCallINetworkSerializable(TypeDefinition type, string name, TypeReference voidType, TypeReference networkStreamType, MethodDefinition method)
|
||||
{
|
||||
var m = new MethodDefinition(name, MethodAttributes.Public | MethodAttributes.HideBySig, voidType);
|
||||
m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, networkStreamType));
|
||||
ILProcessor il = m.Body.GetILProcessor();
|
||||
il.Emit(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Call, method);
|
||||
il.Emit(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Ret);
|
||||
type.Methods.Add(m);
|
||||
}
|
||||
|
||||
private static MethodDefinition GenerateSerializer(TypeDefinition type, bool serialize, ref bool failed, string name, TypeReference voidType, TypeReference networkStreamType)
|
||||
{
|
||||
ModuleDefinition module = type.Module;
|
||||
var m = new MethodDefinition(name, MethodAttributes.Public | MethodAttributes.HideBySig, voidType);
|
||||
m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, networkStreamType));
|
||||
TypeDefinition networkStream = networkStreamType.Resolve();
|
||||
ILProcessor il = m.Body.GetILProcessor();
|
||||
il.Emit(OpCodes.Nop);
|
||||
|
||||
// Serialize base type
|
||||
if (type.BaseType != null && type.BaseType.FullName != "System.ValueType" && type.BaseType.FullName != "FlaxEngine.Object" && type.BaseType.CanBeResolved())
|
||||
{
|
||||
GenerateSerializeCallback(module, il, type.BaseType.Resolve(), serialize);
|
||||
}
|
||||
|
||||
// Serialize all type fields marked with NetworkReplicated attribute
|
||||
foreach (FieldDefinition f in type.Fields)
|
||||
{
|
||||
if (!f.HasAttribute("FlaxEngine.NetworkReplicatedAttribute"))
|
||||
continue;
|
||||
GenerateSerializerType(type, serialize, ref failed, f, f.FieldType, il, networkStream);
|
||||
}
|
||||
|
||||
if (serialize)
|
||||
il.Emit(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Ret);
|
||||
type.Methods.Add(m);
|
||||
return m;
|
||||
}
|
||||
|
||||
private static MethodDefinition GenerateNativeSerializer(TypeDefinition type, bool serialize, ref bool failed, string name, TypeReference voidType, TypeReference networkStreamType)
|
||||
{
|
||||
ModuleDefinition module = type.Module;
|
||||
module.GetType("System.IntPtr", out var intPtrType);
|
||||
module.GetType("FlaxEngine.Object", out var scriptingObjectType);
|
||||
var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr");
|
||||
|
||||
var m = new MethodDefinition(name + "Native", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, voidType);
|
||||
m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtrType));
|
||||
m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtrType));
|
||||
TypeReference networkStream = module.ImportReference(networkStreamType);
|
||||
ILProcessor il = m.Body.GetILProcessor();
|
||||
il.Emit(OpCodes.Nop);
|
||||
il.Body.InitLocals = true;
|
||||
|
||||
// <type> instance = (<type>)FlaxEngine.Object.FromUnmanagedPtr(instancePtr)
|
||||
il.Body.Variables.Add(new VariableDefinition(type));
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Call, module.ImportReference(fromUnmanagedPtr));
|
||||
il.Emit(OpCodes.Castclass, type);
|
||||
il.Emit(OpCodes.Stloc_0);
|
||||
|
||||
// NetworkStream stream = (NetworkStream)FlaxEngine.Object.FromUnmanagedPtr(streamPtr)
|
||||
il.Body.Variables.Add(new VariableDefinition(networkStream));
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Call, module.ImportReference(fromUnmanagedPtr));
|
||||
il.Emit(OpCodes.Castclass, module.ImportReference(networkStream));
|
||||
il.Emit(OpCodes.Stloc_1);
|
||||
|
||||
// Generate normal serializer
|
||||
var serializer = GenerateSerializer(type, serialize, ref failed, name, voidType, networkStreamType);
|
||||
|
||||
// Call serializer
|
||||
il.Emit(OpCodes.Ldloc_0);
|
||||
il.Emit(OpCodes.Ldloc_1);
|
||||
il.Emit(OpCodes.Callvirt, serializer);
|
||||
|
||||
if (serialize)
|
||||
il.Emit(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Ret);
|
||||
type.Methods.Add(m);
|
||||
return m;
|
||||
}
|
||||
|
||||
private static void GenerateSerializeCallback(ModuleDefinition module, ILProcessor il, TypeDefinition type, bool serialize)
|
||||
{
|
||||
if (type.IsScriptingObject())
|
||||
{
|
||||
// NetworkReplicator.InvokeSerializer(typeof(<type>), instance, stream, <serialize>)
|
||||
il.Emit(OpCodes.Ldtoken, module.ImportReference(type));
|
||||
module.GetType("System.Type", out var typeType);
|
||||
var getTypeFromHandle = typeType.Resolve().GetMethod("GetTypeFromHandle");
|
||||
il.Emit(OpCodes.Call, module.ImportReference(getTypeFromHandle));
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
if (serialize)
|
||||
il.Emit(OpCodes.Ldc_I4_1);
|
||||
else
|
||||
il.Emit(OpCodes.Ldc_I4_0);
|
||||
module.GetType("FlaxEngine.Networking.NetworkReplicator", out var networkReplicatorType);
|
||||
var invokeSerializer = networkReplicatorType.Resolve().GetMethod("InvokeSerializer", 4);
|
||||
il.Emit(OpCodes.Call, module.ImportReference(invokeSerializer));
|
||||
il.Emit(OpCodes.Pop);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Not supported base type for network serializer '{type.FullName}'");
|
||||
}
|
||||
}
|
||||
|
||||
private static void GenerateSerializerType(TypeDefinition type, bool serialize, ref bool failed, FieldReference field, TypeReference valueType, ILProcessor il, TypeDefinition networkStreamType)
|
||||
{
|
||||
ModuleDefinition module = type.Module;
|
||||
TypeDefinition valueTypeDef = valueType.Resolve();
|
||||
if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer))
|
||||
{
|
||||
// Call NetworkStream method to write/read data
|
||||
MethodDefinition m;
|
||||
if (serialize)
|
||||
{
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, field);
|
||||
m = networkStreamType.GetMethod(serializer.WriteMethod);
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
m = networkStreamType.GetMethod(serializer.ReadMethod);
|
||||
}
|
||||
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
|
||||
if (!serialize)
|
||||
il.Emit(OpCodes.Stfld, field);
|
||||
}
|
||||
else if (valueType.IsScriptingObject())
|
||||
{
|
||||
// Replicate ScriptingObject as Guid ID
|
||||
module.GetType("System.Guid", out var guidType);
|
||||
module.GetType("FlaxEngine.Object", out var scriptingObjectType);
|
||||
if (serialize)
|
||||
{
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, field);
|
||||
il.Emit(OpCodes.Dup);
|
||||
Instruction jmp1 = il.Create(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Brtrue_S, jmp1);
|
||||
il.Emit(OpCodes.Pop);
|
||||
var guidEmpty = guidType.Resolve().GetField("Empty");
|
||||
il.Emit(OpCodes.Ldsfld, module.ImportReference(guidEmpty));
|
||||
Instruction jmp2 = il.Create(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Br_S, jmp2);
|
||||
il.Append(jmp1);
|
||||
var getID = scriptingObjectType.Resolve().GetMethod("get_ID");
|
||||
il.Emit(OpCodes.Call, module.ImportReference(getID));
|
||||
il.Append(jmp2);
|
||||
var m = networkStreamType.GetMethod("WriteGuid");
|
||||
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
|
||||
}
|
||||
else
|
||||
{
|
||||
var m = networkStreamType.GetMethod("ReadGuid");
|
||||
module.GetType("System.Type", out var typeType);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
|
||||
il.Emit(OpCodes.Stloc_0);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
var varStart = il.Body.Variables.Count;
|
||||
il.Body.Variables.Add(new VariableDefinition(module.ImportReference(guidType)));
|
||||
il.Body.InitLocals = true;
|
||||
il.Emit(OpCodes.Ldloca_S, (byte)varStart);
|
||||
il.Emit(OpCodes.Ldtoken, valueType);
|
||||
var getTypeFromHandle = typeType.Resolve().GetMethod("GetTypeFromHandle");
|
||||
il.Emit(OpCodes.Call, module.ImportReference(getTypeFromHandle));
|
||||
var tryFind = scriptingObjectType.Resolve().GetMethod("TryFind", 2);
|
||||
il.Emit(OpCodes.Call, module.ImportReference(tryFind));
|
||||
il.Emit(OpCodes.Castclass, valueType);
|
||||
il.Emit(OpCodes.Stfld, field);
|
||||
}
|
||||
}
|
||||
else if (valueTypeDef.IsEnum)
|
||||
{
|
||||
// Replicate enum as bits
|
||||
// TODO: use smaller uint depending on enum values range
|
||||
if (serialize)
|
||||
{
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, field);
|
||||
var m = networkStreamType.GetMethod("WriteUInt32");
|
||||
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
|
||||
}
|
||||
else
|
||||
{
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
var m = networkStreamType.GetMethod("ReadUInt32");
|
||||
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
|
||||
il.Emit(OpCodes.Stfld, field);
|
||||
}
|
||||
}
|
||||
else if (valueType.IsValueType)
|
||||
{
|
||||
// Invoke structure generated serializer
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldflda, field);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
var m = valueTypeDef.GetMethod(serialize ? Thunk1 : Thunk2);
|
||||
il.Emit(OpCodes.Call, module.ImportReference(m));
|
||||
}
|
||||
else if (valueType.IsArray && valueType.GetElementType().IsValueType)
|
||||
{
|
||||
// TODO: support any array type by iterating over elements (separate serialize for each one)
|
||||
var elementType = valueType.GetElementType();
|
||||
var varStart = il.Body.Variables.Count;
|
||||
module.GetType("System.Int32", out var intType);
|
||||
il.Body.Variables.Add(new VariableDefinition(intType));
|
||||
il.Body.Variables.Add(new VariableDefinition(new PointerType(elementType)));
|
||||
il.Body.Variables.Add(new VariableDefinition(new PinnedType(valueType)));
|
||||
il.Body.InitLocals = true;
|
||||
if (serialize)
|
||||
{
|
||||
// <elementType>[] array2 = Array1;
|
||||
il.Emit(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, field);
|
||||
|
||||
// int num2 = ((array2 != null) ? array2.Length : 0);
|
||||
il.Emit(OpCodes.Dup);
|
||||
Instruction jmp1 = il.Create(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Brtrue_S, jmp1);
|
||||
il.Emit(OpCodes.Pop);
|
||||
il.Emit(OpCodes.Ldc_I4_0);
|
||||
Instruction jmp2 = il.Create(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Br_S, jmp2);
|
||||
il.Append(jmp1);
|
||||
il.Emit(OpCodes.Ldlen);
|
||||
il.Emit(OpCodes.Conv_I4);
|
||||
il.Append(jmp2);
|
||||
il.Emit(OpCodes.Stloc, varStart + 0);
|
||||
|
||||
// stream.WriteInt32(num2);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Ldloc, varStart + 0);
|
||||
var m = networkStreamType.GetMethod("WriteInt32");
|
||||
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
|
||||
|
||||
// fixed (<elementType>* bytes2 = Array1)
|
||||
il.Emit(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, field);
|
||||
il.Emit(OpCodes.Dup);
|
||||
il.Emit(OpCodes.Stloc, varStart + 2);
|
||||
Instruction jmp3 = il.Create(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Brfalse_S, jmp3);
|
||||
il.Emit(OpCodes.Ldloc_2);
|
||||
il.Emit(OpCodes.Ldlen);
|
||||
il.Emit(OpCodes.Conv_I4);
|
||||
Instruction jmp4 = il.Create(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Brtrue_S, jmp4);
|
||||
il.Append(jmp3);
|
||||
il.Emit(OpCodes.Ldc_I4_0);
|
||||
il.Emit(OpCodes.Conv_U);
|
||||
il.Emit(OpCodes.Stloc, varStart + 1);
|
||||
Instruction jmp5 = il.Create(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Br_S, jmp5);
|
||||
|
||||
// stream.WriteBytes((byte*)bytes, num * sizeof(<elementType>)));
|
||||
il.Append(jmp4);
|
||||
il.Emit(OpCodes.Ldloc, varStart + 2);
|
||||
il.Emit(OpCodes.Ldc_I4_0);
|
||||
il.Emit(OpCodes.Ldelema, elementType);
|
||||
il.Emit(OpCodes.Conv_U);
|
||||
il.Emit(OpCodes.Stloc, varStart + 1);
|
||||
il.Append(jmp5);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Ldloc, varStart + 1);
|
||||
il.Emit(OpCodes.Ldloc, varStart + 0);
|
||||
il.Emit(OpCodes.Sizeof, elementType);
|
||||
il.Emit(OpCodes.Mul);
|
||||
m = networkStreamType.GetMethod("WriteBytes", 2);
|
||||
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
|
||||
il.Emit(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Ldnull);
|
||||
il.Emit(OpCodes.Stloc, varStart + 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// int num = stream.ReadInt32();
|
||||
il.Emit(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
var m = networkStreamType.GetMethod("ReadInt32");
|
||||
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
|
||||
il.Emit(OpCodes.Stloc, varStart + 0);
|
||||
|
||||
// System.Array.Resize(ref Array1, num);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldflda, field);
|
||||
il.Emit(OpCodes.Ldloc, varStart + 0);
|
||||
module.TryGetTypeReference("System.Array", out var arrayType);
|
||||
m = arrayType.Resolve().GetMethod("Resize", 2);
|
||||
il.Emit(OpCodes.Call, module.ImportReference(m.InflateGeneric(elementType)));
|
||||
|
||||
// fixed (int* buffer = Array1)
|
||||
il.Emit(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldfld, field);
|
||||
il.Emit(OpCodes.Dup);
|
||||
il.Emit(OpCodes.Stloc, varStart + 2);
|
||||
Instruction jmp1 = il.Create(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Brfalse_S, jmp1);
|
||||
il.Emit(OpCodes.Ldloc, varStart + 2);
|
||||
il.Emit(OpCodes.Ldlen);
|
||||
il.Emit(OpCodes.Conv_I4);
|
||||
Instruction jmp2 = il.Create(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Brtrue_S, jmp2);
|
||||
il.Append(jmp1);
|
||||
il.Emit(OpCodes.Ldc_I4_0);
|
||||
il.Emit(OpCodes.Conv_U);
|
||||
il.Emit(OpCodes.Stloc, varStart + 1);
|
||||
Instruction jmp3 = il.Create(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Br_S, jmp3);
|
||||
|
||||
// stream.ReadBytes((byte*)buffer, num * sizeof(<elementType>));
|
||||
il.Append(jmp2);
|
||||
il.Emit(OpCodes.Ldloc, varStart + 2);
|
||||
il.Emit(OpCodes.Ldc_I4_0);
|
||||
il.Emit(OpCodes.Ldelema, elementType);
|
||||
il.Emit(OpCodes.Conv_U);
|
||||
il.Emit(OpCodes.Stloc, varStart + 1);
|
||||
il.Append(jmp3);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
il.Emit(OpCodes.Ldloc, varStart + 1);
|
||||
il.Emit(OpCodes.Ldloc, varStart + 0);
|
||||
il.Emit(OpCodes.Sizeof, elementType);
|
||||
il.Emit(OpCodes.Mul);
|
||||
m = networkStreamType.GetMethod("ReadBytes", 2);
|
||||
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
|
||||
il.Emit(OpCodes.Nop);
|
||||
il.Emit(OpCodes.Ldnull);
|
||||
il.Emit(OpCodes.Stloc, varStart + 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unknown type
|
||||
Log.Error($"Not supported type '{valueType.FullName}' on {field.Name} in {type.FullName} for automatic replication.");
|
||||
failed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user