// Copyright Epic Games, Inc. All Rights Reserved. using EpicGames.Core; using EpicGames.Serialization.Converters; using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; namespace EpicGames.Serialization { /// /// Attribute used to mark a property that should be serialized to compact binary /// [AttributeUsage(AttributeTargets.Property)] public class CbFieldAttribute : Attribute { /// /// Name of the serialized field /// public string? Name { get; set; } /// /// Default constructor /// public CbFieldAttribute() { } /// /// Constructor /// /// public CbFieldAttribute(string Name) { this.Name = Name; } } /// /// Exception thrown when serializing cb objects /// public class CbException : Exception { /// public CbException(string Message) : base(Message) { } /// public CbException(string Message, Exception Inner) : base(Message, Inner) { } } /// /// Attribute-driven compact binary serializer /// public static class CbSerializer { /// /// Object used for locking access to shared objects /// static object LockObject = new object(); /// /// Cache of type to converter /// public static Dictionary TypeToConverter = new Dictionary() { [typeof(CbField)] = new CbFieldConverter(), [typeof(CbObject)] = new CbObjectConverter() }; /// /// List of converter factories. Must be /// public static List ConverterFactories = new List { new CbDefaultConverterFactory(), new CbListConverterFactory(), new CbDictionaryConverterFactory(), new CbNullableConverterFactory() }; /// /// Class used to cache values returned by CreateConverterInfo without having to do a dictionary lookup. /// /// The type to be converted class CbConverterCache { /// /// The converter instance /// public static CbConverter Instance { get; } = CreateConverter(); /// /// Create a typed converter /// /// static CbConverter CreateConverter() { CbConverter Converter = GetConverter(typeof(T)); return (Converter as CbConverter) ?? new CbConverterWrapper(Converter); } /// /// Wrapper class to convert an untyped converter into a typed one /// class CbConverterWrapper : CbConverter { CbConverter Inner; public CbConverterWrapper(CbConverter Inner) => this.Inner = Inner; /// public override T Read(CbField Field) => (T)Inner.ReadObject(Field)!; /// public override void Write(CbWriter Writer, T Value) => Inner.WriteObject(Writer, Value); /// public override void WriteNamed(CbWriter Writer, Utf8String Name, T Value) => Inner.WriteNamedObject(Writer, Name, Value); } } /// /// Gets the converter for a particular type /// /// /// public static CbConverter GetConverter(Type Type) { CbConverter? Converter; lock (LockObject) { if (!TypeToConverter.TryGetValue(Type, out Converter)) { CbConverterAttribute? ConverterAttribute = Type.GetCustomAttribute(); if (ConverterAttribute != null) { Type ConverterType = ConverterAttribute.ConverterType; if (Type.IsGenericType && ConverterType.IsGenericTypeDefinition) { ConverterType = ConverterType.MakeGenericType(Type.GetGenericArguments()); } Converter = (CbConverter?)Activator.CreateInstance(ConverterType)!; } else { for (int Idx = ConverterFactories.Count - 1; Idx >= 0 && Converter == null; Idx--) { Converter = ConverterFactories[Idx].CreateConverter(Type); } if (Converter == null) { throw new CbException($"Unable to create converter for {Type.Name}"); } } TypeToConverter.Add(Type, Converter!); } } return Converter; } /// /// Gets the converter for a given type /// /// Type to retrieve the converter for /// public static CbConverter GetConverter() { return CbConverterCache.Instance; } /// /// Serialize an object /// /// Type of the object to serialize /// /// public static CbObject Serialize(Type Type, object Value) { CbWriter Writer = new CbWriter(); GetConverter(Type).WriteObject(Writer, Value); return Writer.ToObject(); } /// /// Serialize an object /// /// /// /// public static CbObject Serialize(T Value) { CbWriter Writer = new CbWriter(); GetConverter().Write(Writer, Value); return Writer.ToObject(); } /// /// Serialize a property to a given writer /// /// /// /// public static void Serialize(CbWriter Writer, T Value) { GetConverter().Write(Writer, Value); } /// /// Serialize a named property to the given writer /// /// /// /// /// public static void Serialize(CbWriter Writer, Utf8String Name, T Value) { GetConverter().WriteNamed(Writer, Name, Value); } /// /// Deserialize an object from a /// /// /// Type of the object to read /// public static object? Deserialize(CbField Field, Type Type) { return GetConverter(Type).ReadObject(Field); } /// /// Deserialize an object from a /// /// /// /// public static T Deserialize(CbField Field) { return GetConverter().Read(Field); } /// /// Deserialize an object from a /// /// /// /// public static T Deserialize(CbObject Object) => Deserialize(Object.AsField()); /// /// Deserialize an object from a block of memory /// /// /// /// public static T Deserialize(ReadOnlyMemory Data) => Deserialize(new CbField(Data)); } }