// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
namespace EpicGames.Core
{
///
/// Common functionality for dealing with generic type serialization in binary archives
///
public static class BinaryArchive
{
class Instantiator where T : class, new()
{
public static T Instance = new T();
}
sealed class TypeSerializer
{
public MethodInfo ReadMethodInfo { get; }
public Func ReadMethod { get; }
public MethodInfo WriteMethodInfo { get; }
public Action WriteMethod { get; }
public TypeSerializer(MethodInfo readMethodInfo, MethodInfo writeMethodInfo)
{
ReadMethodInfo = readMethodInfo;
WriteMethodInfo = writeMethodInfo;
Type type = readMethodInfo.ReturnType;
ReadMethod = CreateBoxedReadMethod(type, readMethodInfo);
WriteMethod = CreateBoxedWriteMethod(type, writeMethodInfo);
}
static Func CreateBoxedReadMethod(Type type, MethodInfo methodInfo)
{
DynamicMethod readerMethod = new DynamicMethod($"Boxed_{methodInfo.Name}", typeof(object), [typeof(BinaryArchiveReader)]);
ILGenerator generator = readerMethod.GetILGenerator(64);
generator.Emit(OpCodes.Ldarg_0);
generator.EmitCall(OpCodes.Call, methodInfo, null);
if (!type.IsClass)
{
generator.Emit(OpCodes.Box);
}
generator.Emit(OpCodes.Ret);
return (Func)readerMethod.CreateDelegate(typeof(Func));
}
static Action CreateBoxedWriteMethod(Type type, MethodInfo methodInfo)
{
DynamicMethod writerMethod = new DynamicMethod($"Boxed_{methodInfo.Name}", typeof(void), [typeof(BinaryArchiveWriter), typeof(object)]);
ILGenerator generator = writerMethod.GetILGenerator(64);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Unbox_Any, type);
generator.EmitCall(OpCodes.Call, methodInfo, null);
generator.Emit(OpCodes.Ret);
return (Action)writerMethod.CreateDelegate(typeof(Action));
}
public static TypeSerializer Create(Expression> readerExpr, Expression> writerExpr)
{
MethodInfo readMethod = ((MethodCallExpression)readerExpr.Body).Method;
MethodInfo writeMethod = ((MethodCallExpression)writerExpr.Body).Method;
return new TypeSerializer(readMethod, writeMethod);
}
}
static readonly ConcurrentDictionary s_typeToSerializerInfo = new ConcurrentDictionary()
{
[typeof(byte)] = TypeSerializer.Create(reader => reader.ReadByte(), (writer, value) => writer.WriteByte(value)),
[typeof(sbyte)] = TypeSerializer.Create(reader => reader.ReadSignedByte(), (writer, value) => writer.WriteSignedByte(value)),
[typeof(short)] = TypeSerializer.Create(reader => reader.ReadShort(), (writer, value) => writer.WriteShort(value)),
[typeof(ushort)] = TypeSerializer.Create(reader => reader.ReadUnsignedShort(), (writer, value) => writer.WriteUnsignedShort(value)),
[typeof(int)] = TypeSerializer.Create(reader => reader.ReadInt(), (writer, value) => writer.WriteInt(value)),
[typeof(uint)] = TypeSerializer.Create(reader => reader.ReadUnsignedInt(), (writer, value) => writer.WriteUnsignedInt(value)),
[typeof(long)] = TypeSerializer.Create(reader => reader.ReadLong(), (writer, value) => writer.WriteLong(value)),
[typeof(ulong)] = TypeSerializer.Create(reader => reader.ReadUnsignedLong(), (writer, value) => writer.WriteUnsignedLong(value)),
[typeof(string)] = TypeSerializer.Create(reader => reader.ReadString(), (writer, value) => writer.WriteString(value))
};
static readonly ConcurrentDictionary s_typeToDigest = new ConcurrentDictionary();
static readonly ConcurrentDictionary s_digestToType = new ConcurrentDictionary();
static MethodInfo GetMethodInfo(Expression expr)
{
return ((MethodCallExpression)expr.Body).Method;
}
static MethodInfo GetMethodInfo(Expression> expr)
{
return ((MethodCallExpression)expr.Body).Method;
}
static MethodInfo GetGenericMethodInfo(Expression> expr)
{
return ((MethodCallExpression)expr.Body).Method.GetGenericMethodDefinition();
}
static readonly MethodInfo s_readBoolMethodInfo = GetMethodInfo(x => x.ReadBool());
static readonly MethodInfo s_readArrayMethodInfo = GetGenericMethodInfo>((x, y) => x.ReadArray(y));
static readonly MethodInfo s_readPolymorphicObjectMethodInfo = GetMethodInfo(() => ReadObject(null!));
static readonly MethodInfo s_writeBoolMethodInfo = GetMethodInfo(x => x.WriteBool(false));
static readonly MethodInfo s_writeArrayMethodInfo = GetGenericMethodInfo>((x, y) => x.WriteArray(null!, y));
static readonly MethodInfo s_writePolymorphicObjectMethodInfo = GetMethodInfo(() => WritePolymorphicObject(null!, null!));
///
/// Writes an arbitrary type to the given archive. May be an object or value type.
///
/// The type to write
/// Writer to serialize to
/// Value to write
public static void Write(this BinaryArchiveWriter writer, T value)
{
Type type = typeof(T);
if (type.IsClass && !type.IsSealed)
{
writer.WriteObjectReference(value!, () => WriteNewPolymorphicObject(writer, value!));
}
else
{
FindOrAddSerializerInfo(type).WriteMethod(writer, value!);
}
}
///
/// Registers the given type. Allows serializing/deserializing it through calls to ReadType/WriteType.
///
/// Type to register
public static void RegisterType(Type type)
{
ContentHash digest = ContentHash.MD5($"{type.Assembly.FullName}\n{type.FullName}");
s_typeToDigest.TryAdd(type, digest);
s_digestToType.TryAdd(digest, type);
}
///
/// Registers all types in the given assembly with the attribute for serialization
///
/// Assembly to search in
public static void RegisterTypes(Assembly assembly)
{
foreach (Type type in assembly.GetTypes())
{
if (type.GetCustomAttribute() != null)
{
RegisterType(type);
}
}
}
///
/// Reads a tyoe from the archive
///
/// Reader to deserializer the type from
/// The matching type
public static Type? ReadType(this BinaryArchiveReader reader)
{
return reader.ReadObjectReference(() => s_digestToType[reader.ReadContentHash()!]);
}
///
/// Writes a type to an archive
///
/// Writer to serialize to
/// The type to serialize
public static void WriteType(this BinaryArchiveWriter writer, Type type)
{
writer.WriteObjectReference(type, () => writer.WriteContentHash(s_typeToDigest[type]));
}
static object? ReadNewPolymorphicObject(BinaryArchiveReader reader)
{
Type? finalType = reader.ReadType();
if (finalType == null)
{
return null;
}
TypeSerializer info = FindOrAddSerializerInfo(finalType);
return info.ReadMethod(reader)!;
}
///
/// Reads an object from the archive
///
/// Reader to deserialize from
/// The deserialized object
public static object? ReadObject(this BinaryArchiveReader reader)
{
return reader.ReadUntypedObjectReference(() => ReadNewPolymorphicObject(reader));
}
static void WriteNewPolymorphicObject(BinaryArchiveWriter writer, object value)
{
Type actualType = value.GetType();
writer.WriteType(actualType);
TypeSerializer info = FindOrAddSerializerInfo(actualType);
info.WriteMethod(writer, value);
}
static void WritePolymorphicObject(this BinaryArchiveWriter writer, object value)
{
writer.WriteObjectReference(value, () => WriteNewPolymorphicObject(writer, value));
}
///
/// Finds an existing serializer for the given type, or creates one
///
/// Type to create a serializer for
/// Type of the converter to use
/// Instance of the type serializer
static TypeSerializer FindOrAddSerializerInfo(Type type, Type? converterType = null)
{
Type serializerKey = converterType ?? type;
// Get the serializer info
if (!s_typeToSerializerInfo.TryGetValue(serializerKey, out TypeSerializer? serializerInfo))
{
lock (s_typeToSerializerInfo)
{
serializerInfo = CreateTypeSerializer(type, converterType);
s_typeToSerializerInfo[serializerKey] = serializerInfo;
}
}
return serializerInfo;
}
///
/// Creates a serializer for the given type
///
/// Type to create a serializer from
/// Converter for the type
/// New instance of a type serializer
static TypeSerializer CreateTypeSerializer(Type type, Type? converterType = null)
{
// If there's a specific converter defined, generate serialization methods using that
if (converterType != null)
{
// Make sure the converter is derived from IBinaryConverter
Type interfaceType = typeof(IBinaryConverter<>).MakeGenericType(type);
if (!interfaceType.IsAssignableFrom(converterType))
{
throw new NotImplementedException($"Converter does not implement IBinaryArchiveConverter<{type.Name}>");
}
// Instantiate the converter, and store it in a static variable
Type instantiatorType = typeof(Instantiator<>).MakeGenericType(converterType);
FieldInfo converterField = instantiatorType.GetField(nameof(Instantiator