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
|
||||
{
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user