mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
296 lines
12 KiB
C#
296 lines
12 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Globalization;
|
|
using System.Linq.Expressions;
|
|
using Robust.Shared.Serialization.Manager.Definition;
|
|
using Robust.Shared.Serialization.Manager.Exceptions;
|
|
using Robust.Shared.Serialization.Markdown;
|
|
using Robust.Shared.Serialization.Markdown.Sequence;
|
|
using Robust.Shared.Serialization.Markdown.Value;
|
|
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Shared.Serialization.Manager;
|
|
|
|
public sealed partial class SerializationManager
|
|
{
|
|
#region DelegateElements
|
|
|
|
private delegate DataNode WriteBoxingDelegate(
|
|
object value,
|
|
bool alwaysWrite,
|
|
ISerializationContext? context);
|
|
|
|
private delegate DataNode WriteGenericDelegate<T>(
|
|
T value,
|
|
bool alwaysWrite,
|
|
ISerializationContext? context);
|
|
|
|
private readonly ConcurrentDictionary<(Type, bool), WriteBoxingDelegate> _writeBoxingDelegates = new();
|
|
private readonly ConcurrentDictionary<(Type baseType, Type actualType, bool), object> _writeGenericBaseDelegates = new();
|
|
private readonly ConcurrentDictionary<(Type, bool), object> _writeGenericDelegates = new();
|
|
|
|
private WriteBoxingDelegate GetOrCreateWriteBoxingDelegate(Type type, bool notNullableOverride)
|
|
{
|
|
return _writeBoxingDelegates.GetOrAdd((type, notNullableOverride), static (tuple, manager) =>
|
|
{
|
|
var type = tuple.Item1;
|
|
var managerConst = Expression.Constant(manager);
|
|
|
|
var valueParam = Expression.Variable(typeof(object));
|
|
var alwaysWrite = Expression.Variable(typeof(bool));
|
|
var contextParam = Expression.Variable(typeof(ISerializationContext));
|
|
|
|
var call = Expression.Call(
|
|
managerConst,
|
|
nameof(WriteValue),
|
|
new[] { type },
|
|
Expression.Convert(valueParam, type),
|
|
alwaysWrite,
|
|
contextParam,
|
|
Expression.Constant(tuple.Item2));
|
|
|
|
return Expression.Lambda<WriteBoxingDelegate>(
|
|
call,
|
|
valueParam,
|
|
alwaysWrite,
|
|
contextParam).Compile();
|
|
}, this);
|
|
|
|
}
|
|
|
|
private WriteGenericDelegate<T> GetOrCreateWriteGenericDelegate<T>(T value, bool notNullableOverride)
|
|
{
|
|
static object ValueFactory(Type baseType, Type actualType, bool notNullableOverride, SerializationManager serializationManager)
|
|
{
|
|
var instanceParam = Expression.Constant(serializationManager);
|
|
var objParam = Expression.Parameter(baseType, "value");
|
|
var alwaysWriteParam = Expression.Parameter(typeof(bool), "alwaysWrite");
|
|
var contextParam = Expression.Parameter(typeof(ISerializationContext), "context");
|
|
|
|
actualType = actualType.EnsureNotNullableType();
|
|
var sameType = baseType.EnsureNotNullableType() == actualType;
|
|
|
|
Expression objAccess = baseType.IsNullable()
|
|
? Expression.Convert(objParam, sameType ? baseType.EnsureNotNullableType() : actualType)
|
|
: sameType ? objParam : Expression.Convert(objParam, actualType);
|
|
|
|
Expression call;
|
|
if (serializationManager._regularSerializerProvider.TryGetTypeSerializer(typeof(ITypeWriter<>), actualType, out var serializer))
|
|
{
|
|
var serializerConst = Expression.Constant(serializer);
|
|
call = Expression.Call(
|
|
instanceParam,
|
|
nameof(WriteValue),
|
|
new []{actualType},
|
|
serializerConst,
|
|
objAccess,
|
|
alwaysWriteParam,
|
|
contextParam,
|
|
Expression.Constant(notNullableOverride)
|
|
);
|
|
}
|
|
else if (actualType.IsEnum)
|
|
{
|
|
// When writing generic enums, we want to use the enum serializer.
|
|
// Otherwise, we fall back to the default IConvertible behaviour.
|
|
|
|
if (baseType != typeof(Enum) ||
|
|
!serializationManager._regularSerializerProvider.TryGetTypeSerializer(typeof(ITypeWriter<>),
|
|
typeof(Enum), out serializer))
|
|
{
|
|
call = Expression.Call(
|
|
instanceParam,
|
|
nameof(WriteConvertible),
|
|
Type.EmptyTypes,
|
|
Expression.Convert(objParam, typeof(IConvertible)));
|
|
}
|
|
else
|
|
{
|
|
var serializerConst = Expression.Constant(serializer);
|
|
call = Expression.Call(
|
|
instanceParam,
|
|
nameof(WriteValue),
|
|
new []{typeof(Enum)},
|
|
serializerConst,
|
|
Expression.Convert(objParam, typeof(Enum)),
|
|
alwaysWriteParam,
|
|
contextParam,
|
|
Expression.Constant(notNullableOverride)
|
|
);
|
|
}
|
|
}
|
|
else if (actualType.IsArray)
|
|
{
|
|
call = Expression.Call(
|
|
instanceParam,
|
|
nameof(WriteArray),
|
|
Type.EmptyTypes,
|
|
Expression.Convert(objParam, typeof(Array)),
|
|
alwaysWriteParam,
|
|
contextParam);
|
|
}
|
|
else if (typeof(ISelfSerialize).IsAssignableFrom(actualType))
|
|
{
|
|
call = Expression.Call(
|
|
instanceParam,
|
|
nameof(WriteSelfSerializable),
|
|
Type.EmptyTypes,
|
|
Expression.Convert(objParam, typeof(ISelfSerialize)));
|
|
}
|
|
else
|
|
{
|
|
call = Expression.Call(
|
|
instanceParam,
|
|
nameof(WriteValueInternal),
|
|
new[] { actualType },
|
|
objAccess,
|
|
Expression.Constant(serializationManager.GetDefinition(actualType), typeof(DataDefinition<>).MakeGenericType(actualType)),
|
|
alwaysWriteParam,
|
|
contextParam);
|
|
|
|
if (!sameType)
|
|
{
|
|
var nodeVar = Expression.Variable(typeof(DataNode));
|
|
call = Expression.Block(
|
|
new[] { nodeVar },
|
|
Expression.Assign(nodeVar, call),
|
|
Expression.Assign(Expression.Field(nodeVar, "Tag"), Expression.Constant($"!type:{actualType.Name}")),
|
|
nodeVar);
|
|
}
|
|
}
|
|
|
|
// check for customtypeserializer before anything
|
|
var serializerType = typeof(ITypeWriter<>).MakeGenericType(actualType);
|
|
var serializerVar = Expression.Variable(serializerType);
|
|
call = Expression.Block(
|
|
new[] { serializerVar },
|
|
Expression.Condition(
|
|
Expression.AndAlso(
|
|
Expression.ReferenceNotEqual(contextParam,
|
|
Expression.Constant(null, typeof(ISerializationContext))),
|
|
Expression.Call(
|
|
Expression.Property(contextParam, "SerializerProvider"),
|
|
"TryGetTypeSerializer",
|
|
new[] { serializerType, actualType },
|
|
serializerVar)),
|
|
Expression.Call(
|
|
instanceParam,
|
|
nameof(WriteValue),
|
|
new []{actualType},
|
|
serializerVar,
|
|
objAccess,
|
|
alwaysWriteParam,
|
|
contextParam,
|
|
Expression.Constant(notNullableOverride)),
|
|
call));
|
|
|
|
return Expression.Lambda<WriteGenericDelegate<T>>(
|
|
call,
|
|
objParam,
|
|
alwaysWriteParam,
|
|
contextParam).Compile();
|
|
}
|
|
|
|
var type = typeof(T);
|
|
if (type.IsAbstract || type.IsInterface)
|
|
{
|
|
return (WriteGenericDelegate<T>)_writeGenericBaseDelegates.GetOrAdd((type, value!.GetType(), notNullableOverride),
|
|
static (tuple, manager) => ValueFactory(tuple.baseType, tuple.actualType, tuple.Item3, manager), this);
|
|
}
|
|
|
|
return (WriteGenericDelegate<T>) _writeGenericDelegates
|
|
.GetOrAdd((type, notNullableOverride), static (tuple, manager) => ValueFactory(tuple.Item1, tuple.Item1, tuple.Item2, manager), this);
|
|
}
|
|
|
|
private DataNode WriteConvertible(IConvertible obj)
|
|
{
|
|
return new ValueDataNode(obj.ToString(CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
private DataNode WriteSelfSerializable(ISelfSerialize obj)
|
|
{
|
|
return new ValueDataNode(obj.Serialize());
|
|
}
|
|
|
|
private DataNode WriteArray(Array obj, bool alwaysWrite, ISerializationContext? context)
|
|
{
|
|
var sequenceNode = new SequenceDataNode();
|
|
|
|
foreach (var val in obj)
|
|
{
|
|
var serializedVal = WriteValue(val.GetType(), val, alwaysWrite, context);
|
|
sequenceNode.Add(serializedVal);
|
|
}
|
|
|
|
return sequenceNode;
|
|
}
|
|
|
|
private DataNode WriteValueInternal<T>(
|
|
T value,
|
|
DataDefinition<T>? definition,
|
|
bool alwaysWrite,
|
|
ISerializationContext? context)
|
|
where T : notnull
|
|
{
|
|
//this check is in here on purpose. we cannot check this during expression tree generation due to the value maybe being handled by a custom typeserializer
|
|
if(definition == null)
|
|
throw new InvalidOperationException($"No data definition found for type {typeof(T)} when writing");
|
|
|
|
var mapping = definition.Serialize(value, context, alwaysWrite);
|
|
|
|
return mapping;
|
|
}
|
|
|
|
#endregion
|
|
|
|
public DataNode WriteValue<T>(T value, bool alwaysWrite = false, ISerializationContext? context = null, bool notNullableOverride = false)
|
|
{
|
|
if(value == null)
|
|
{
|
|
CanWriteNullCheck(typeof(T), notNullableOverride);
|
|
return ValueDataNode.Null();
|
|
}
|
|
|
|
return GetOrCreateWriteGenericDelegate(value, notNullableOverride)(value, alwaysWrite, context);
|
|
}
|
|
|
|
public DataNode WriteValue<T>(ITypeWriter<T> writer, T value, bool alwaysWrite = false,
|
|
ISerializationContext? context = null, bool notNullableOverride = false)
|
|
{
|
|
if (value == null)
|
|
{
|
|
CanWriteNullCheck(typeof(T), notNullableOverride);
|
|
return NullNode();
|
|
}
|
|
|
|
return writer.Write(this, value, DependencyCollection, alwaysWrite, context);
|
|
}
|
|
|
|
public DataNode WriteValue<T, TWriter>(T value, bool alwaysWrite = false, ISerializationContext? context = null, bool notNullableOverride = false)
|
|
where TWriter : ITypeWriter<T>
|
|
{
|
|
return WriteValue(GetOrCreateCustomTypeSerializer<TWriter>(), value, alwaysWrite, context, notNullableOverride);
|
|
}
|
|
|
|
public DataNode WriteValue(Type type, object? value, bool alwaysWrite = false, ISerializationContext? context = null, bool notNullableOverride = false)
|
|
{
|
|
if (value == null)
|
|
{
|
|
CanWriteNullCheck(type, notNullableOverride);
|
|
|
|
return NullNode();
|
|
}
|
|
|
|
return GetOrCreateWriteBoxingDelegate(type, notNullableOverride)(value, alwaysWrite, context);
|
|
}
|
|
|
|
private void CanWriteNullCheck(Type type, bool notNullableOverride)
|
|
{
|
|
if (!type.IsNullable() || notNullableOverride)
|
|
{
|
|
throw new NullNotAllowedException();
|
|
}
|
|
}
|
|
}
|