mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.IO;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user