Files
RobustToolbox/Robust.Shared/Serialization/NetComponentDeltaStateSerializer.cs
T

165 lines
6.2 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);
var writeField = WriteFieldMethod.MakeGenericMethod(field.FieldType);
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);
var readField = ReadFieldMethod.MakeGenericMethod(field.FieldType);
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 ReadFieldMethod = typeof(NetComponentDeltaStateSerializer)
.GetMethod(nameof(ReadField), BindingFlags.Static | BindingFlags.NonPublic)!;
[UsedImplicitly]
private static void WriteField<T>(Serializer serializer, Stream stream, T value)
{
serializer.SerializeDirect(stream, value);
}
[UsedImplicitly]
private static void ReadField<T>(Serializer serializer, Stream stream, out T value)
{
serializer.DeserializeDirect(stream, out value);
}
}