Add serialization copy benchmark, optimize copying and reading with IL generators (#1729)

* Create serialization copy benchmark

* Optimize copy generic method call

Before
|                             Method |         Mean |      Error |     StdDev |       Median |
|----------------------------------- |-------------:|-----------:|-----------:|-------------:|
|                   CreateCopyString |     43.54 ns |   0.918 ns |   0.859 ns |     43.74 ns |
|                  CreateCopyInteger |     19.33 ns |   0.388 ns |   0.860 ns |     19.24 ns |
| CreateCopyDataDefinitionWithString |  3,971.42 ns |  78.974 ns | 213.512 ns |  3,902.54 ns |
|       CreateCopySeedDataDefinition | 49,916.69 ns | 264.992 ns | 221.280 ns | 49,950.53 ns |

After
|                             Method |         Mean |      Error |     StdDev |
|----------------------------------- |-------------:|-----------:|-----------:|
|                   CreateCopyString |     43.15 ns |   0.917 ns |   1.766 ns |
|                  CreateCopyInteger |     17.91 ns |   0.366 ns |   0.305 ns |
| CreateCopyDataDefinitionWithString |    544.20 ns |   8.137 ns |   7.611 ns |
|       CreateCopySeedDataDefinition | 18,233.34 ns | 357.861 ns | 397.761 ns |

* Change populate delegate to use IL

|                       Method |        Mean |     Error |    StdDev |
|----------------------------- |------------:|----------:|----------:|
|                   ReadString |    190.7 ns |   3.48 ns |   5.63 ns |
|                  ReadInteger |    218.8 ns |   4.29 ns |   6.01 ns |
| ReadDataDefinitionWithString |    677.5 ns |  13.34 ns |  24.06 ns |
|       ReadSeedDataDefinition | 11,067.0 ns | 219.19 ns | 260.93 ns |

|                             Method |         Mean |      Error |     StdDev |
|----------------------------------- |-------------:|-----------:|-----------:|
|                   CreateCopyString |     42.69 ns |   0.926 ns |   1.414 ns |
|                  CreateCopyInteger |     18.02 ns |   0.268 ns |   0.237 ns |
| CreateCopyDataDefinitionWithString |    556.80 ns |  11.206 ns |  21.589 ns |
|       CreateCopySeedDataDefinition | 13,797.07 ns | 256.011 ns | 367.163 ns |

* Make copying use IL to get values

|                               Method |        Mean |      Error |     StdDev |
|------------------------------------- |------------:|-----------:|-----------:|
|                     CreateCopyString |    42.55 ns |   0.889 ns |   1.357 ns |
|                    CreateCopyInteger |    18.72 ns |   0.394 ns |   0.740 ns |
|   CreateCopyDataDefinitionWithString |   398.98 ns |   7.853 ns |  12.682 ns |
|         CreateCopySeedDataDefinition | 5,996.60 ns | 118.724 ns | 181.304 ns |
| BaselineCreateCopySeedDataDefinition |   340.85 ns |   6.811 ns |  12.281 ns |

* Rest in peace the fast path
For now

* Fix serialization copying not passing the target ref properly

* Fix field assigners for structs
This commit is contained in:
DrSmugleaf
2021-05-02 12:09:12 +02:00
committed by GitHub
parent 8fd98c75a9
commit f9cd9ac12a
11 changed files with 526 additions and 268 deletions

View File

@@ -0,0 +1,140 @@
using System;
using System.IO;
using System.Linq;
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Server;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Copy
{
public class SerializationCopyBenchmark
{
public SerializationCopyBenchmark()
{
IoCManager.InitThread();
ServerIoC.RegisterIoC();
IoCManager.BuildGraph();
var assemblies = new[]
{
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Shared"),
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Server"),
AppDomain.CurrentDomain.GetAssemblyByName("Robust.Benchmarks")
};
foreach (var assembly in assemblies)
{
IoCManager.Resolve<IConfigurationManagerInternal>().LoadCVarsFromAssembly(assembly);
}
IoCManager.Resolve<IReflectionManager>().LoadAssemblies(assemblies);
SerializationManager = IoCManager.Resolve<ISerializationManager>();
SerializationManager.Initialize();
DataDefinitionWithString = new DataDefinitionWithString {StringField = "ABC"};
var yamlStream = new YamlStream();
yamlStream.Load(new StringReader(SeedDataDefinition.Prototype));
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
}
private ISerializationManager SerializationManager { get; }
private const string String = "ABC";
private const int Integer = 1;
private DataDefinitionWithString DataDefinitionWithString { get; }
private SeedDataDefinition Seed { get; }
[Benchmark]
public string? CreateCopyString()
{
return SerializationManager.CreateCopy(String);
}
[Benchmark]
public int? CreateCopyInteger()
{
return SerializationManager.CreateCopy(Integer);
}
[Benchmark]
public DataDefinitionWithString? CreateCopyDataDefinitionWithString()
{
return SerializationManager.CreateCopy(DataDefinitionWithString);
}
[Benchmark]
public SeedDataDefinition? CreateCopySeedDataDefinition()
{
return SerializationManager.CreateCopy(Seed);
}
[Benchmark]
public SeedDataDefinition BaselineCreateCopySeedDataDefinition()
{
// ReSharper disable once UseObjectOrCollectionInitializer
var copy = new SeedDataDefinition();
copy.ID = Seed.ID;
copy.Name = Seed.Name;
copy.SeedName = Seed.SeedName;
copy.SeedNoun = Seed.SeedNoun;
copy.DisplayName = Seed.DisplayName;
copy.RoundStart = Seed.RoundStart;
copy.Mysterious = Seed.Mysterious;
copy.Immutable = Seed.Immutable;
copy.ProductPrototypes = Seed.ProductPrototypes.ToList();
copy.Chemicals = Seed.Chemicals.ToDictionary(p => p.Key, p => p.Value);
copy.ConsumeGasses = Seed.ConsumeGasses.ToDictionary(p => p.Key, p => p.Value);
copy.ExudeGasses = Seed.ExudeGasses.ToDictionary(p => p.Key, p => p.Value);
copy.NutrientConsumption = Seed.NutrientConsumption;
copy.WaterConsumption = Seed.WaterConsumption;
copy.IdealHeat = Seed.IdealHeat;
copy.HeatTolerance = Seed.HeatTolerance;
copy.IdealLight = Seed.IdealLight;
copy.LightTolerance = Seed.LightTolerance;
copy.ToxinsTolerance = Seed.ToxinsTolerance;
copy.LowPressureTolerance = Seed.LowPressureTolerance;
copy.HighPressureTolerance = Seed.HighPressureTolerance;
copy.PestTolerance = Seed.PestTolerance;
copy.WeedTolerance = Seed.WeedTolerance;
copy.Endurance = Seed.Endurance;
copy.Yield = Seed.Yield;
copy.Lifespan = Seed.Lifespan;
copy.Maturation = Seed.Maturation;
copy.Production = Seed.Production;
copy.GrowthStages = Seed.GrowthStages;
copy.HarvestRepeat = Seed.HarvestRepeat;
copy.Potency = Seed.Potency;
copy.Ligneous = Seed.Ligneous;
copy.PlantRsi = Seed.PlantRsi == null
? null!
: new ResourcePath(Seed.PlantRsi.ToString(), Seed.PlantRsi.Separator);
copy.PlantIconState = Seed.PlantIconState;
copy.Bioluminescent = Seed.Bioluminescent;
copy.BioluminescentColor = Seed.BioluminescentColor;
copy.SplatPrototype = Seed.SplatPrototype;
return copy;
}
}
}

View File

@@ -1,11 +1,11 @@
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Benchmarks.Serialization
namespace Robust.Benchmarks.Serialization.Definitions
{
[DataDefinition]
public class DataDefinitionWithString
{
[field: DataField("string")]
private string StringField { get; } = default!;
public string StringField { get; init; } = default!;
}
}

View File

@@ -0,0 +1,113 @@
using System.Collections.Generic;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
namespace Robust.Benchmarks.Serialization.Definitions
{
[Prototype("seed")]
public class SeedDataDefinition : IPrototype
{
public const string Prototype = @"
- type: seed
id: tobacco
name: tobacco
seedName: tobacco
displayName: tobacco plant
productPrototypes:
- LeavesTobacco
harvestRepeat: Repeat
lifespan: 75
maturation: 5
production: 5
yield: 2
potency: 20
growthStages: 3
idealLight: 9
idealHeat: 298
chemicals:
chem.Nicotine:
Min: 1
Max: 10
PotencyDivisor: 10";
[DataField("id", required: true)] public string ID { get; set; } = default!;
#region Tracking
[DataField("name")] public string Name { get; set; } = string.Empty;
[DataField("seedName")] public string SeedName { get; set; } = string.Empty;
[DataField("seedNoun")] public string SeedNoun { get; set; } = "seeds";
[DataField("displayName")] public string DisplayName { get; set; } = string.Empty;
[DataField("roundStart")] public bool RoundStart { get; set; } = true;
[DataField("mysterious")] public bool Mysterious { get; set; }
[DataField("immutable")] public bool Immutable { get; set; }
#endregion
#region Output
[DataField("productPrototypes")]
public List<string> ProductPrototypes { get; set; } = new();
[DataField("chemicals")]
public Dictionary<string, SeedChemQuantity> Chemicals { get; set; } = new();
[DataField("consumeGasses")]
public Dictionary<Gas, float> ConsumeGasses { get; set; } = new();
[DataField("exudeGasses")]
public Dictionary<Gas, float> ExudeGasses { get; set; } = new();
#endregion
#region Tolerances
[DataField("nutrientConsumption")] public float NutrientConsumption { get; set; } = 0.25f;
[DataField("waterConsumption")] public float WaterConsumption { get; set; } = 3f;
[DataField("idealHeat")] public float IdealHeat { get; set; } = 293f;
[DataField("heatTolerance")] public float HeatTolerance { get; set; } = 20f;
[DataField("idealLight")] public float IdealLight { get; set; } = 7f;
[DataField("lightTolerance")] public float LightTolerance { get; set; } = 5f;
[DataField("toxinsTolerance")] public float ToxinsTolerance { get; set; } = 4f;
[DataField("lowPressureTolerance")] public float LowPressureTolerance { get; set; } = 25f;
[DataField("highPressureTolerance")] public float HighPressureTolerance { get; set; } = 200f;
[DataField("pestTolerance")] public float PestTolerance { get; set; } = 5f;
[DataField("weedTolerance")] public float WeedTolerance { get; set; } = 5f;
#endregion
#region General traits
[DataField("endurance")] public float Endurance { get; set; } = 100f;
[DataField("yield")] public int Yield { get; set; }
[DataField("lifespan")] public float Lifespan { get; set; }
[DataField("maturation")] public float Maturation { get; set; }
[DataField("production")] public float Production { get; set; }
[DataField("growthStages")] public int GrowthStages { get; set; } = 6;
[DataField("harvestRepeat")] public HarvestType HarvestRepeat { get; set; } = HarvestType.NoRepeat;
[DataField("potency")] public float Potency { get; set; } = 1f;
[DataField("ligneous")] public bool Ligneous { get; set; }
#endregion
#region Cosmetics
[DataField("plantRsi", required: true)] public ResourcePath PlantRsi { get; set; } = default!;
[DataField("plantIconState")] public string PlantIconState { get; set; } = "produce";
[DataField("bioluminescent")] public bool Bioluminescent { get; set; }
[DataField("bioluminescentColor")] public Color BioluminescentColor { get; set; } = Color.White;
[DataField("splatPrototype")] public string? SplatPrototype { get; set; }
#endregion
}
public enum HarvestType
{
NoRepeat,
Repeat
}
public enum Gas {}
[DataDefinition]
public struct SeedChemQuantity
{
[DataField("Min")]
public int Min { get; }
[DataField("Max")]
public int Max;
[DataField("PotencyDivisor")]
public int PotencyDivisor;
}
}

View File

@@ -1,5 +1,6 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Serialization.Markdown;
using YamlDotNet.RepresentationModel;

View File

@@ -1,184 +0,0 @@
using System.Collections.Generic;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Benchmarks.Serialization
{
[Prototype("seed")]
public class SeedDataDefinition : IPrototype
{
public const string Prototype = @"
- type: seed
id: tobacco
name: tobacco
seedName: tobacco
displayName: tobacco plant
productPrototypes:
- LeavesTobacco
harvestRepeat: Repeat
lifespan: 75
maturation: 5
production: 5
yield: 2
potency: 20
growthStages: 3
idealLight: 9
idealHeat: 298
chemicals:
chem.Nicotine:
Min: 1
Max: 10
PotencyDivisor: 10";
private const string SeedPrototype = "SeedBase";
[ViewVariables]
[field: DataField("id", required: true)]
public string ID { get; private init; } = default!;
/// <summary>
/// Unique identifier of this seed. Do NOT set this.
/// </summary>
public int Uid { get; internal set; } = -1;
#region Tracking
[ViewVariables] [DataField("name")] public string Name { get; set; } = string.Empty;
[ViewVariables] [DataField("seedName")] public string SeedName { get; set; } = string.Empty;
[ViewVariables]
[DataField("seedNoun")]
public string SeedNoun { get; set; } = "seeds";
[ViewVariables] [DataField("displayName")] public string DisplayName { get; set; } = string.Empty;
[ViewVariables]
[DataField("roundStart")]
public bool RoundStart { get; private set; } = true;
[ViewVariables] [DataField("mysterious")] public bool Mysterious { get; set; }
[ViewVariables] [DataField("immutable")] public bool Immutable { get; set; }
#endregion
#region Output
[ViewVariables]
[DataField("productPrototypes")]
public List<string> ProductPrototypes { get; set; } = new();
[ViewVariables]
[DataField("chemicals")]
public Dictionary<string, SeedChemQuantity> Chemicals { get; set; } = new();
[ViewVariables]
[DataField("consumeGasses")]
public Dictionary<Gas, float> ConsumeGasses { get; set; } = new();
[ViewVariables]
[DataField("exudeGasses")]
public Dictionary<Gas, float> ExudeGasses { get; set; } = new();
#endregion
#region Tolerances
[ViewVariables]
[DataField("nutrientConsumption")]
public float NutrientConsumption { get; set; } = 0.25f;
[ViewVariables] [DataField("waterConsumption")] public float WaterConsumption { get; set; } = 3f;
[ViewVariables] [DataField("idealHeat")] public float IdealHeat { get; set; } = 293f;
[ViewVariables] [DataField("heatTolerance")] public float HeatTolerance { get; set; } = 20f;
[ViewVariables] [DataField("idealLight")] public float IdealLight { get; set; } = 7f;
[ViewVariables] [DataField("lightTolerance")] public float LightTolerance { get; set; } = 5f;
[ViewVariables] [DataField("toxinsTolerance")] public float ToxinsTolerance { get; set; } = 4f;
[ViewVariables]
[DataField("lowPressureTolerance")]
public float LowPressureTolerance { get; set; } = 25f;
[ViewVariables]
[DataField("highPressureTolerance")]
public float HighPressureTolerance { get; set; } = 200f;
[ViewVariables]
[DataField("pestTolerance")]
public float PestTolerance { get; set; } = 5f;
[ViewVariables]
[DataField("weedTolerance")]
public float WeedTolerance { get; set; } = 5f;
#endregion
#region General traits
[ViewVariables]
[DataField("endurance")]
public float Endurance { get; set; } = 100f;
[ViewVariables] [DataField("yield")] public int Yield { get; set; }
[ViewVariables] [DataField("lifespan")] public float Lifespan { get; set; }
[ViewVariables] [DataField("maturation")] public float Maturation { get; set; }
[ViewVariables] [DataField("production")] public float Production { get; set; }
[ViewVariables] [DataField("growthStages")] public int GrowthStages { get; set; } = 6;
[ViewVariables] [DataField("harvestRepeat")] public HarvestType HarvestRepeat { get; set; } = HarvestType.NoRepeat;
[ViewVariables] [DataField("potency")] public float Potency { get; set; } = 1f;
// No, I'm not removing these.
//public PlantSpread Spread { get; set; }
//public PlantMutation Mutation { get; set; }
//public float AlterTemperature { get; set; }
//public PlantCarnivorous Carnivorous { get; set; }
//public bool Parasite { get; set; }
//public bool Hematophage { get; set; }
//public bool Thorny { get; set; }
//public bool Stinging { get; set; }
[DataField("ligneous")]
public bool Ligneous { get; set; }
// public bool Teleporting { get; set; }
// public PlantJuicy Juicy { get; set; }
#endregion
#region Cosmetics
[ViewVariables]
[DataField("plantRsi", required: true)]
public ResourcePath PlantRsi { get; set; } = default!;
[ViewVariables]
[DataField("plantIconState")]
public string PlantIconState { get; set; } = "produce";
[ViewVariables]
[DataField("bioluminescent")]
public bool Bioluminescent { get; set; }
[ViewVariables]
[DataField("bioluminescentColor")]
public Color BioluminescentColor { get; set; } = Color.White;
[ViewVariables]
[DataField("splatPrototype")]
public string? SplatPrototype { get; set; }
#endregion
}
public enum HarvestType
{
NoRepeat,
Repeat
}
public enum Gas {}
[DataDefinition]
public struct SeedChemQuantity
{
[DataField("Min")]
public int Min;
[DataField("Max")]
public int Max;
[DataField("PotencyDivisor")]
public int PotencyDivisor;
}
}

View File

@@ -4,6 +4,8 @@ using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
@@ -45,36 +47,22 @@ namespace Robust.Shared.Serialization.Manager
object value,
DeserializedFieldEntry[] mappings);
private delegate TValue AccessField<TTarget, TValue>(ref TTarget target);
private delegate void AssignField<TTarget, TValue>(ref TTarget target, TValue? value);
public readonly Type Type;
private readonly string[] _duplicates;
private readonly object?[] _defaultValues;
private readonly DeserializeDelegate _deserializeDelegate;
private readonly PopulateDelegateSignature _populateDelegate;
private readonly SerializeDelegateSignature _serializeDelegate;
private readonly CopyDelegateSignature _copyDelegate;
public DeserializationResult InvokePopulateDelegate(object target, DeserializedFieldEntry[] fields) =>
_populateDelegate(target, fields, _defaultValues);
public DeserializationResult InvokePopulateDelegate(object target, MappingDataNode mappingDataNode, ISerializationManager serializationManager,
ISerializationContext? context, bool skipHook)
{
var fields = _deserializeDelegate(mappingDataNode, serializationManager, context, skipHook);
return _populateDelegate(target, fields, _defaultValues);
}
public MappingDataNode InvokeSerializeDelegate(object obj, ISerializationManager serializationManager, ISerializationContext? context, bool alwaysWrite) =>
_serializeDelegate(obj, serializationManager, context, alwaysWrite, _defaultValues);
public object InvokeCopyDelegate(object source, object target, ISerializationManager serializationManager, ISerializationContext? context) =>
_copyDelegate(source, target, serializationManager, context);
public bool CanCallWith(object obj) => Type.IsInstanceOfType(obj);
private readonly AccessField<object, object?>[] _fieldAccessors;
private readonly AssignField<object, object?>[] _fieldAssigners;
public SerializationDataDefinition(Type type)
{
@@ -157,10 +145,153 @@ namespace Robust.Shared.Serialization.Manager
_populateDelegate = EmitPopulateDelegate();
_serializeDelegate = EmitSerializeDelegate();
_copyDelegate = EmitCopyDelegate();
_fieldAccessors = new AccessField<object, object?>[BaseFieldDefinitions.Length];
for (var i = 0; i < BaseFieldDefinitions.Length; i++)
{
var fieldDefinition = BaseFieldDefinitions[i];
var dm = new DynamicMethod(
"AccessField",
typeof(object),
new[] {typeof(object).MakeByRefType()},
true);
dm.DefineParameter(1, ParameterAttributes.Out, "target");
var generator = dm.GetRobustGen();
if (Type.IsValueType)
{
generator.DeclareLocal(Type);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldind_Ref);
generator.Emit(OpCodes.Unbox_Any, Type);
generator.Emit(OpCodes.Stloc_0);
generator.Emit(OpCodes.Ldloca_S, 0);
}
else
{
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldind_Ref);
generator.Emit(OpCodes.Castclass, Type);
}
switch (fieldDefinition.BackingField)
{
case SpecificFieldInfo field:
generator.Emit(OpCodes.Ldfld, field.FieldInfo);
break;
case SpecificPropertyInfo property:
var getter = property.PropertyInfo.GetGetMethod(true) ?? throw new NullReferenceException();
var opCode = Type.IsValueType ? OpCodes.Call : OpCodes.Callvirt;
generator.Emit(opCode, getter);
break;
}
var returnType = fieldDefinition.BackingField.FieldType;
if (returnType.IsValueType)
{
generator.Emit(OpCodes.Box, returnType);
}
generator.Emit(OpCodes.Ret);
_fieldAccessors[i] = dm.CreateDelegate<AccessField<object, object?>>();
}
_fieldAssigners = new AssignField<object, object?>[BaseFieldDefinitions.Length];
for (var i = 0; i < BaseFieldDefinitions.Length; i++)
{
var fieldDefinition = BaseFieldDefinitions[i];
var dm = new DynamicMethod(
"AssignField",
typeof(void),
new[] {typeof(object).MakeByRefType(), typeof(object)},
true);
dm.DefineParameter(1, ParameterAttributes.Out, "target");
dm.DefineParameter(2, ParameterAttributes.None, "value");
var generator = dm.GetRobustGen();
if (Type.IsValueType)
{
generator.DeclareLocal(Type);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldind_Ref);
generator.Emit(OpCodes.Unbox_Any, Type);
generator.Emit(OpCodes.Stloc_0);
generator.Emit(OpCodes.Ldloca, 0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Unbox_Any, fieldDefinition.FieldType);
EmitSetField(generator, fieldDefinition.BackingField);
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Box, Type);
generator.Emit(OpCodes.Stind_Ref);
generator.Emit(OpCodes.Ret);
}
else
{
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldind_Ref);
generator.Emit(OpCodes.Castclass, Type);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Unbox_Any, fieldDefinition.FieldType);
EmitSetField(generator, fieldDefinition.BackingField);
generator.Emit(OpCodes.Ret);
}
_fieldAssigners[i] = dm.CreateDelegate<AssignField<object, object?>>();
}
}
internal ImmutableArray<FieldDefinition> BaseFieldDefinitions { get; private set; }
private void EmitSetField(RobustILGenerator rGenerator, AbstractFieldInfo info)
{
switch (info)
{
case SpecificFieldInfo field:
rGenerator.Emit(OpCodes.Stfld, field.FieldInfo);
break;
case SpecificPropertyInfo property:
var setter = property.PropertyInfo.GetSetMethod(true) ?? throw new NullReferenceException();
var opCode = info.DeclaringType?.IsValueType ?? false
? OpCodes.Call
: OpCodes.Callvirt;
rGenerator.Emit(opCode, setter);
break;
}
}
public DeserializationResult InvokePopulateDelegate(object target, DeserializedFieldEntry[] fields) =>
_populateDelegate(target, fields, _defaultValues);
public DeserializationResult InvokePopulateDelegate(object target, MappingDataNode mappingDataNode, ISerializationManager serializationManager,
ISerializationContext? context, bool skipHook)
{
var fields = _deserializeDelegate(mappingDataNode, serializationManager, context, skipHook);
return _populateDelegate(target, fields, _defaultValues);
}
public MappingDataNode InvokeSerializeDelegate(object obj, ISerializationManager serializationManager, ISerializationContext? context, bool alwaysWrite) =>
_serializeDelegate(obj, serializationManager, context, alwaysWrite, _defaultValues);
public object InvokeCopyDelegate(object source, object target, ISerializationManager serializationManager, ISerializationContext? context) =>
_copyDelegate(source, target, serializationManager, context);
public bool CanCallWith(object obj) => Type.IsInstanceOfType(obj);
public bool TryGetDuplicates([NotNullWhen(true)] out string[] duplicates)
{
duplicates = _duplicates;
@@ -246,7 +377,6 @@ namespace Robust.Shared.Serialization.Manager
return DeserializationDelegate;
}
// TODO PAUL SERV3: Turn this back into IL once it is fixed
private PopulateDelegateSignature EmitPopulateDelegate()
{
//todo validate mappings array count
@@ -272,8 +402,6 @@ namespace Robust.Shared.Serialization.Manager
var res = deserializedFields[i];
if (!res.Mapped) continue;
var fieldDefinition = BaseFieldDefinitions[i];
var defValue = defaultValues[i];
if (Equals(res.Result?.RawValue, defValue))
@@ -281,7 +409,7 @@ namespace Robust.Shared.Serialization.Manager
continue;
}
fieldDefinition.SetValue(target, res.Result?.RawValue);
_fieldAssigners[i](ref target, res.Result?.RawValue);
}
return createDefinitionDelegate(target, deserializedFields);
@@ -350,43 +478,46 @@ namespace Robust.Shared.Serialization.Manager
// todo paul add skiphooks
private CopyDelegateSignature EmitCopyDelegate()
{
object PopulateDelegate(
object CopyDelegate(
object source,
object target,
ISerializationManager manager,
ISerializationContext? context)
{
foreach (var field in BaseFieldDefinitions)
for (var i = 0; i < BaseFieldDefinitions.Length; i++)
{
var sourceValue = field.GetValue(source);
var targetValue = field.GetValue(target);
var field = BaseFieldDefinitions[i];
var accessor = _fieldAccessors[i];
var sourceValue = accessor(ref source);
var targetValue = accessor(ref target);
object? copy;
if (sourceValue != null && targetValue != null && TypeHelpers.SelectCommonType(sourceValue.GetType(), targetValue.GetType()) == null)
if (sourceValue != null &&
targetValue != null &&
TypeHelpers.SelectCommonType(sourceValue.GetType(), targetValue.GetType()) == null)
{
copy = manager.CreateCopy(sourceValue, context);
}else
}
else
{
copy = field.Attribute.CustomTypeSerializer != null
? manager.CopyWithTypeSerializer(field.Attribute.CustomTypeSerializer, sourceValue, targetValue,
? manager.CopyWithTypeSerializer(field.Attribute.CustomTypeSerializer, sourceValue,
targetValue,
context)
: manager.Copy(sourceValue, targetValue, context);
}
field.SetValue(target, copy);
_fieldAssigners[i](ref target, copy);
}
return target;
}
return PopulateDelegate;
return CopyDelegate;
}
public class FieldDefinition
{
private readonly AbstractFieldInfo _backingField;
private readonly AbstractFieldInfo _fieldInfo;
public readonly DataFieldAttribute Attribute;
public readonly object? DefaultValue;
public readonly InheritanceBehaviour InheritanceBehaviour;
@@ -398,23 +529,27 @@ namespace Robust.Shared.Serialization.Manager
AbstractFieldInfo backingField,
InheritanceBehaviour inheritanceBehaviour)
{
_backingField = backingField;
BackingField = backingField;
Attribute = attr;
DefaultValue = defaultValue;
_fieldInfo = fieldInfo;
FieldInfo = fieldInfo;
InheritanceBehaviour = inheritanceBehaviour;
}
public Type FieldType => _fieldInfo.FieldType;
public AbstractFieldInfo BackingField { get; }
public AbstractFieldInfo FieldInfo { get; }
public Type FieldType => FieldInfo.FieldType;
public object? GetValue(object? obj)
{
return _backingField.GetValue(obj);
return BackingField.GetValue(obj);
}
public void SetValue(object? obj, object? value)
{
_backingField.SetValue(obj, value);
BackingField.SetValue(obj, value);
}
}

View File

@@ -1,32 +1,80 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Shared.Serialization.Manager
{
public partial class SerializationManager
{
private bool TryCopyWithTypeCopier(Type type, object source, ref object target, bool skipHook, ISerializationContext? context = null)
private delegate bool CopyDelegate(
object source,
ref object target,
bool skipHook,
ISerializationContext? context = null);
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, CopyDelegate>> _copyDelegates = new();
private CopyDelegate GetOrCreateCopyDelegate(Type commonType, Type sourceType, Type targetType)
{
//TODO Paul: do this shit w/ delegates
var method = typeof(SerializationManager).GetRuntimeMethods().First(m =>
m.Name == nameof(TryCopyWithTypeCopier) && m.GetParameters().Length == 4).MakeGenericMethod(type, source.GetType(), target.GetType());
return _copyDelegates
.GetOrAdd(commonType, _ => new ConcurrentDictionary<Type, CopyDelegate>())
.GetOrAdd(commonType, (t, tuple) =>
{
var instanceParam = Expression.Constant(this);
var sourceParam = Expression.Parameter(typeof(object), "source");
var targetParam = Expression.Parameter(typeof(object).MakeByRefType(), "target");
var skipHookParam = Expression.Parameter(typeof(bool), "skipHook");
var contextParam = Expression.Parameter(typeof(ISerializationContext), "context");
var arr = new[] {source, target, skipHook, context};
var res = method.Invoke(this, arr);
var targetCastVariable = Expression.Variable(tuple.targetType, "targetCastVariable");
if (res as bool? ?? false)
{
target = arr[1]!;
return true;
}
var returnVariable = Expression.Parameter(typeof(bool), "return");
var returnLabel = Expression.Label(typeof(bool));
var returnExpression = Expression.Label(returnLabel, returnVariable);
return false;
var call = Expression.Call(
instanceParam,
nameof(TryCopy),
new[] {t, tuple.sourceType, tuple.targetType},
Expression.Convert(sourceParam, tuple.sourceType),
targetCastVariable,
skipHookParam,
contextParam);
var block = Expression.Block(
new[] {targetCastVariable, returnVariable},
Expression.Assign(
targetCastVariable,
Expression.Convert(targetParam, tuple.targetType)),
Expression.Assign(returnVariable, call),
Expression.IfThen(
returnVariable,
Expression.Assign(targetParam, targetCastVariable)),
Expression.Return(returnLabel, returnVariable),
returnExpression);
return Expression.Lambda<CopyDelegate>(
block,
sourceParam,
targetParam,
skipHookParam,
contextParam).Compile();
}, (sourceType, targetType));
}
private bool TryCopyWithTypeCopier<TCommon, TSource, TTarget>(
private bool TryCopyRaw(
Type type,
object source,
ref object target,
bool skipHook,
ISerializationContext? context = null)
{
return GetOrCreateCopyDelegate(type, source.GetType(), target.GetType())(source, ref target, skipHook, context);
}
private bool TryCopy<TCommon, TSource, TTarget>(
TSource source,
ref TTarget target,
bool skipHook,

View File

@@ -21,7 +21,7 @@ namespace Robust.Shared.Serialization.Manager
private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, ReadDelegate>> _readDelegates = new();
private ReadDelegate GetOrCreateDelegate(Type type, Type nodeType)
private ReadDelegate GetOrCreateReadDelegate(Type type, Type nodeType)
{
return _readDelegates
.GetOrAdd(type, _ => new ConcurrentDictionary<Type, ReadDelegate>())
@@ -105,7 +105,7 @@ namespace Robust.Shared.Serialization.Manager
bool skipHook,
ISerializationContext? context = null)
{
return GetOrCreateDelegate(type, node.GetType())(type, node, dependencies, out obj, skipHook, context);
return GetOrCreateReadDelegate(type, node.GetType())(type, node, dependencies, out obj, skipHook, context);
}
private bool TryGetGenericReader<T, TNode>(

View File

@@ -28,7 +28,7 @@ namespace Robust.Shared.Serialization.Manager
private bool _initialized;
private readonly Dictionary<Type, SerializationDataDefinition> _dataDefinitions = new();
private readonly List<Type> _copyByRefRegistrations = new();
private readonly HashSet<Type> _copyByRefRegistrations = new();
public IDependencyCollection DependencyCollection { get; private set; } = default!;
@@ -502,7 +502,7 @@ namespace Robust.Shared.Serialization.Manager
return source;
}
if (source.GetType().IsValueType != target.GetType().IsValueType)
if (sourceType.IsValueType != targetType.IsValueType)
{
throw new InvalidOperationException(
$"Source and target do not match. Source ({sourceType}) is value type? {sourceType.IsValueType}. Target ({targetType}) is value type? {targetType.IsValueType}");
@@ -524,7 +524,7 @@ namespace Robust.Shared.Serialization.Manager
newArray = (Array) Activator.CreateInstance(sourceArray.GetType(), sourceArray.Length)!;
}
for (int i = 0; i < sourceArray.Length; i++)
for (var i = 0; i < sourceArray.Length; i++)
{
newArray.SetValue(CreateCopy(sourceArray.GetValue(i), context, skipHook), i);
}
@@ -532,13 +532,13 @@ namespace Robust.Shared.Serialization.Manager
return newArray;
}
if (source.GetType().IsArray != target.GetType().IsArray)
if (sourceType.IsArray != targetType.IsArray)
{
throw new InvalidOperationException(
$"Source and target do not match. Source ({sourceType}) is array type? {sourceType.IsArray}. Target ({targetType}) is array type? {targetType.IsArray}");
}
var commonType = TypeHelpers.SelectCommonType(source.GetType(), target.GetType());
var commonType = TypeHelpers.SelectCommonType(sourceType, targetType);
if (commonType == null)
{
throw new InvalidOperationException("Could not find common type in Copy!");
@@ -549,7 +549,7 @@ namespace Robust.Shared.Serialization.Manager
return source;
}
if (TryCopyWithTypeCopier(commonType, source, ref target, skipHook, context))
if (TryCopyRaw(commonType, source, ref target, skipHook, context))
{
return target;
}
@@ -605,10 +605,8 @@ namespace Robust.Shared.Serialization.Manager
private object? CreateCopyInternal(Type type, object? source, ISerializationContext? context = null, bool skipHook = false)
{
if (source == null) return source;
if (type.IsPrimitive ||
if (source == null ||
type.IsPrimitive ||
type.IsEnum ||
source is string ||
_copyByRefRegistrations.Contains(type))

View File

@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
namespace Robust.Shared.Utility
{
@@ -107,19 +106,22 @@ namespace Robust.Shared.Utility
Log(opcode, meth);
}
/*public virtual void EmitCalli(OpCode opcode, CallingConventions callingConvention,
Type? returnType, Type[]? parameterTypes, Type[]? optionalParameterTypes)
{
}
// public virtual void EmitCalli(OpCode opcode, CallingConventions callingConvention,
// Type? returnType, Type[]? parameterTypes, Type[]? optionalParameterTypes)
// {
// }
//
// public virtual void EmitCalli(OpCode opcode, CallingConvention unmanagedCallConv, Type? returnType, Type[]? parameterTypes)
// {
// }
//
public virtual void EmitCalli(OpCode opcode, CallingConvention unmanagedCallConv, Type? returnType, Type[]? parameterTypes)
public virtual void EmitCall(OpCode opcode, MethodInfo methodInfo, params Type[]? optionalParameterTypes)
{
_generator.EmitCall(opcode, methodInfo, optionalParameterTypes);
Log(opcode, methodInfo);
}
public virtual void EmitCall(OpCode opcode, MethodInfo methodInfo, Type[]? optionalParameterTypes)
{
}*/
public virtual void Emit(OpCode opcode, SignatureHelper signature)
{
_generator.Emit(opcode, signature);

View File

@@ -332,6 +332,7 @@ namespace Robust.Shared.Utility
public abstract class AbstractFieldInfo
{
public abstract string Name { get; }
internal abstract MemberInfo MemberInfo { get; }
public abstract Type FieldType { get; }
public abstract Type? DeclaringType { get; }
@@ -350,9 +351,11 @@ namespace Robust.Shared.Utility
public class SpecificFieldInfo : AbstractFieldInfo
{
public readonly FieldInfo FieldInfo;
public override string Name { get; }
public readonly FieldInfo FieldInfo;
internal override MemberInfo MemberInfo => FieldInfo;
public override Type FieldType => FieldInfo.FieldType;
public override Type? DeclaringType => FieldInfo.DeclaringType;
@@ -417,9 +420,11 @@ namespace Robust.Shared.Utility
public class SpecificPropertyInfo : AbstractFieldInfo
{
public readonly PropertyInfo PropertyInfo;
public override string Name { get; }
public readonly PropertyInfo PropertyInfo;
internal override MemberInfo MemberInfo => PropertyInfo;
public override Type FieldType => PropertyInfo.PropertyType;
public override Type? DeclaringType => PropertyInfo.DeclaringType;