mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-15 14:52:35 +02:00
191 lines
7.4 KiB
C#
191 lines
7.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using JetBrains.Annotations;
|
|
using NetSerializer;
|
|
using Robust.Shared.GameObjects;
|
|
|
|
namespace Robust.Shared.Serialization;
|
|
|
|
/// <summary>
|
|
/// Generated delta states store ONE <see cref="IAutoGeneratedComponentDeltaState.ChangedFields"/> bitmask and ONE
|
|
/// field per networked component field. This serializer writes the mask first, then only writes fields whose bits are
|
|
/// set. This allows a single generated delta state to represent any dirty-field combination without serializing the
|
|
/// unchanged fields.
|
|
/// If you misuse this your networked states will probably explode!
|
|
/// </summary>
|
|
internal sealed class NetComponentDeltaStateSerializer : IDynamicTypeSerializer
|
|
{
|
|
public bool Handles(Type type)
|
|
{
|
|
return typeof(IAutoGeneratedComponentDeltaState).IsAssignableFrom(type);
|
|
}
|
|
|
|
public IEnumerable<Type> GetSubtypes(Type type)
|
|
{
|
|
yield return typeof(ulong);
|
|
|
|
foreach (var field in GetDeltaFields(type))
|
|
{
|
|
yield return field.FieldType;
|
|
}
|
|
}
|
|
|
|
public void GenerateWriterMethod(Serializer serializer, Type type, ILGenerator il)
|
|
{
|
|
// arg0: Serializer, arg1: Stream, arg2: value
|
|
var getChangedFields = typeof(IAutoGeneratedComponentDeltaState)
|
|
.GetProperty(nameof(IAutoGeneratedComponentDeltaState.ChangedFields))!
|
|
.GetMethod!;
|
|
var writeMask = typeof(Primitives)
|
|
.GetMethod(nameof(Primitives.WritePrimitive), [typeof(Stream), typeof(ulong)])!;
|
|
|
|
var changedFields = il.DeclareLocal(typeof(ulong));
|
|
|
|
il.Emit(OpCodes.Ldarg_2);
|
|
il.Emit(OpCodes.Castclass, typeof(IAutoGeneratedComponentDeltaState));
|
|
il.Emit(OpCodes.Callvirt, getChangedFields);
|
|
il.Emit(OpCodes.Stloc, changedFields);
|
|
|
|
il.Emit(OpCodes.Ldarg_1);
|
|
il.Emit(OpCodes.Ldloc, changedFields);
|
|
il.Emit(OpCodes.Call, writeMask);
|
|
|
|
foreach (var field in GetDeltaFields(type))
|
|
{
|
|
var endField = il.DefineLabel();
|
|
var mask = 1UL << field.GetCustomAttribute<NetworkedDeltaFieldAttribute>()!.Index;
|
|
|
|
il.Emit(OpCodes.Ldloc, changedFields);
|
|
il.Emit(OpCodes.Ldc_I8, unchecked((long) mask));
|
|
il.Emit(OpCodes.And);
|
|
il.Emit(OpCodes.Brfalse, endField);
|
|
|
|
// Primitive types count as sealed
|
|
// We need this to handle inheritors of abstract types, such as SoundSpecifier
|
|
var writeField = field.FieldType.IsSealed
|
|
? WriteFieldMethod.MakeGenericMethod(field.FieldType)
|
|
: WriteFieldObjectMethod;
|
|
il.Emit(OpCodes.Ldarg_0);
|
|
il.Emit(OpCodes.Ldarg_1);
|
|
il.Emit(OpCodes.Ldarg_2);
|
|
il.Emit(OpCodes.Ldfld, field);
|
|
il.Emit(OpCodes.Call, writeField);
|
|
|
|
il.MarkLabel(endField);
|
|
}
|
|
|
|
il.Emit(OpCodes.Ret);
|
|
}
|
|
|
|
public void GenerateReaderMethod(Serializer serializer, Type type, ILGenerator il)
|
|
{
|
|
// arg0: Serializer, arg1: Stream, arg2: out value
|
|
var setChangedFields = typeof(IAutoGeneratedComponentDeltaState)
|
|
.GetProperty(nameof(IAutoGeneratedComponentDeltaState.ChangedFields))!
|
|
.SetMethod!;
|
|
var readMask = typeof(Primitives)
|
|
.GetMethod(nameof(Primitives.ReadPrimitive), [typeof(Stream), typeof(ulong).MakeByRefType()])!;
|
|
|
|
var changedFields = il.DeclareLocal(typeof(ulong));
|
|
|
|
il.Emit(OpCodes.Ldarg_2);
|
|
il.Emit(OpCodes.Ldtoken, type);
|
|
il.Emit(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle), BindingFlags.Public | BindingFlags.Static)!);
|
|
il.Emit(
|
|
OpCodes.Call,
|
|
// Beats me but matches the other serializers.
|
|
#pragma warning disable SYSLIB0050
|
|
typeof(System.Runtime.Serialization.FormatterServices)
|
|
.GetMethod(nameof(System.Runtime.Serialization.FormatterServices.GetUninitializedObject), BindingFlags.Public | BindingFlags.Static)!);
|
|
#pragma warning restore SYSLIB0050
|
|
il.Emit(OpCodes.Castclass, type);
|
|
il.Emit(OpCodes.Stind_Ref);
|
|
|
|
il.Emit(OpCodes.Ldarg_1);
|
|
il.Emit(OpCodes.Ldloca_S, changedFields);
|
|
il.Emit(OpCodes.Call, readMask);
|
|
|
|
il.Emit(OpCodes.Ldarg_2);
|
|
il.Emit(OpCodes.Ldind_Ref);
|
|
il.Emit(OpCodes.Castclass, typeof(IAutoGeneratedComponentDeltaState));
|
|
il.Emit(OpCodes.Ldloc, changedFields);
|
|
il.Emit(OpCodes.Callvirt, setChangedFields);
|
|
|
|
foreach (var field in GetDeltaFields(type))
|
|
{
|
|
var endField = il.DefineLabel();
|
|
var mask = 1UL << field.GetCustomAttribute<NetworkedDeltaFieldAttribute>()!.Index;
|
|
|
|
il.Emit(OpCodes.Ldloc, changedFields);
|
|
il.Emit(OpCodes.Ldc_I8, unchecked((long) mask));
|
|
il.Emit(OpCodes.And);
|
|
il.Emit(OpCodes.Brfalse, endField);
|
|
|
|
// Primitive types count as sealed
|
|
// We need this to handle inheritors of abstract types, such as SoundSpecifier
|
|
var readField = field.FieldType.IsSealed
|
|
? ReadFieldMethod.MakeGenericMethod(field.FieldType)
|
|
: ReadFieldObjectMethod;
|
|
il.Emit(OpCodes.Ldarg_0);
|
|
il.Emit(OpCodes.Ldarg_1);
|
|
il.Emit(OpCodes.Ldarg_2);
|
|
il.Emit(OpCodes.Ldind_Ref);
|
|
il.Emit(OpCodes.Ldflda, field);
|
|
il.Emit(OpCodes.Call, readField);
|
|
|
|
il.MarkLabel(endField);
|
|
}
|
|
|
|
il.Emit(OpCodes.Ret);
|
|
}
|
|
|
|
private static IEnumerable<FieldInfo> GetDeltaFields(Type type)
|
|
{
|
|
return type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
|
|
.Select(field => (Field: field, Attribute: field.GetCustomAttribute<NetworkedDeltaFieldAttribute>()))
|
|
.Where(tuple => tuple.Attribute != null)
|
|
.OrderBy(tuple => tuple.Attribute!.Index)
|
|
.Select(tuple => tuple.Field);
|
|
}
|
|
|
|
private static readonly MethodInfo WriteFieldMethod = typeof(NetComponentDeltaStateSerializer)
|
|
.GetMethod(nameof(WriteField), BindingFlags.Static | BindingFlags.NonPublic)!;
|
|
|
|
private static readonly MethodInfo WriteFieldObjectMethod = typeof(NetComponentDeltaStateSerializer)
|
|
.GetMethod(nameof(WriteFieldObject), BindingFlags.Static | BindingFlags.NonPublic)!;
|
|
|
|
private static readonly MethodInfo ReadFieldMethod = typeof(NetComponentDeltaStateSerializer)
|
|
.GetMethod(nameof(ReadField), BindingFlags.Static | BindingFlags.NonPublic)!;
|
|
|
|
private static readonly MethodInfo ReadFieldObjectMethod = typeof(NetComponentDeltaStateSerializer)
|
|
.GetMethod(nameof(ReadFieldObject), BindingFlags.Static | BindingFlags.NonPublic)!;
|
|
|
|
[UsedImplicitly]
|
|
private static void WriteField<T>(Serializer serializer, Stream stream, T value)
|
|
{
|
|
serializer.SerializeDirect(stream, value);
|
|
}
|
|
|
|
[UsedImplicitly]
|
|
private static void WriteFieldObject(Serializer serializer, Stream stream, object value)
|
|
{
|
|
serializer.Serialize(stream, value);
|
|
}
|
|
|
|
[UsedImplicitly]
|
|
private static void ReadField<T>(Serializer serializer, Stream stream, out T value)
|
|
{
|
|
serializer.DeserializeDirect(stream, out value);
|
|
}
|
|
|
|
[UsedImplicitly]
|
|
private static void ReadFieldObject(Serializer serializer, Stream stream, out object value)
|
|
{
|
|
serializer.Deserialize(stream, out value);
|
|
}
|
|
}
|