Client LuaCsForBarotrauma
INetSerializableStruct.cs
1 #nullable enable
2 
3 using System;
4 using System.Collections.Generic;
5 using System.Collections.Immutable;
6 using System.Diagnostics.CodeAnalysis;
7 using System.Linq;
8 using System.Reflection;
9 using System.Runtime.CompilerServices;
10 using System.Text;
12 using Microsoft.Xna.Framework;
13 
14 namespace Barotrauma
15 {
38  [AttributeUsage(AttributeTargets.Field | AttributeTargets.Struct | AttributeTargets.Property)]
39  public sealed class NetworkSerialize : Attribute
40  {
41  public int MaxValueInt = int.MaxValue;
42  public int MinValueInt = int.MinValue;
43  public float MaxValueFloat = float.MaxValue;
44  public float MinValueFloat = float.MinValue;
45  public int NumberOfBits = 8;
46  public bool IncludeColorAlpha = false;
47  public int ArrayMaxSize = ushort.MaxValue;
48 
49  public readonly int OrderKey;
50 
51  public NetworkSerialize([CallerLineNumber] int lineNumber = 0)
52  {
53  OrderKey = lineNumber;
54  }
55  }
56 
60  [SuppressMessage("ReSharper", "RedundantTypeArgumentsOfMethod")]
61  static class NetSerializableProperties
62  {
63  public interface IReadWriteBehavior
64  {
65  public delegate object? ReadDelegate(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField);
66 
67  public delegate void WriteDelegate(object? obj, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField);
68 
69  public ReadDelegate ReadAction { get; }
70  public WriteDelegate WriteAction { get; }
71  }
72 
73  public readonly struct ReadWriteBehavior<T> : IReadWriteBehavior
74  {
75  public delegate T ReadDelegate(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField);
76 
77  public delegate void WriteDelegate(T obj, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField);
78 
81 
84 
85  public ReadWriteBehavior(ReadDelegate readAction, WriteDelegate writeAction)
86  {
87  ReadAction = (inc, attribute, bitField) => readAction(inc, attribute, bitField);
88  WriteAction = (o, attribute, msg, bitField) => writeAction((T)o!, attribute, msg, bitField);
89  ReadActionDirect = readAction;
90  WriteActionDirect = writeAction;
91  }
92  }
93 
94  public readonly struct CachedReflectedVariable
95  {
96  public delegate object? GetValueDelegate(object? obj);
97 
98  public delegate void SetValueDelegate(object? obj, object? value);
99 
100  public readonly string Name;
101  public readonly Type Type;
102  public readonly IReadWriteBehavior Behavior;
103  public readonly NetworkSerialize Attribute;
104  public readonly SetValueDelegate SetValue;
105  public readonly GetValueDelegate GetValue;
106  public readonly bool HasOwnAttribute;
107 
108  public CachedReflectedVariable(MemberInfo info, IReadWriteBehavior behavior, Type baseClassType)
109  {
110  Behavior = behavior;
111  Name = info.Name;
112  switch (info)
113  {
114  case PropertyInfo pi:
115  Type = pi.PropertyType;
116  GetValue = pi.GetValue;
117  SetValue = pi.SetValue;
118  break;
119  case FieldInfo fi:
120  Type = fi.FieldType;
121  GetValue = fi.GetValue;
122  SetValue = fi.SetValue;
123  break;
124  default:
125  throw new ArgumentException($"Expected {nameof(FieldInfo)} or {nameof(PropertyInfo)} but found {info.GetType()}.", nameof(info));
126  }
127 
128  if (info.GetCustomAttribute<NetworkSerialize>() is { } ownAttriute)
129  {
130  HasOwnAttribute = true;
131  Attribute = ownAttriute;
132  }
133  else if (baseClassType.GetCustomAttribute<NetworkSerialize>() is { } globalAttribute)
134  {
135  HasOwnAttribute = false;
136  Attribute = globalAttribute;
137  }
138  else
139  {
140  throw new InvalidOperationException($"Unable to serialize \"{Type}\" in \"{baseClassType}\" because it has no {nameof(NetworkSerialize)} attribute.");
141  }
142  }
143  }
144 
145  private static readonly Dictionary<Type, ImmutableArray<CachedReflectedVariable>> CachedVariables = new Dictionary<Type, ImmutableArray<CachedReflectedVariable>>();
146 
147  private static readonly Dictionary<Type, IReadWriteBehavior> TypeBehaviors
148  = new Dictionary<Type, IReadWriteBehavior>
149  {
150  { typeof(Boolean), new ReadWriteBehavior<Boolean>(ReadBoolean, WriteBoolean) },
151  { typeof(Byte), new ReadWriteBehavior<Byte>(ReadByte, WriteByte) },
152  { typeof(UInt16), new ReadWriteBehavior<UInt16>(ReadUInt16, WriteUInt16) },
153  { typeof(Int16), new ReadWriteBehavior<Int16>(ReadInt16, WriteInt16) },
154  { typeof(UInt32), new ReadWriteBehavior<UInt32>(ReadUInt32, WriteUInt32) },
155  { typeof(Int32), new ReadWriteBehavior<Int32>(ReadInt32, WriteInt32) },
156  { typeof(UInt64), new ReadWriteBehavior<UInt64>(ReadUInt64, WriteUInt64) },
157  { typeof(Int64), new ReadWriteBehavior<Int64>(ReadInt64, WriteInt64) },
158  { typeof(Single), new ReadWriteBehavior<Single>(ReadSingle, WriteSingle) },
159  { typeof(Double), new ReadWriteBehavior<Double>(ReadDouble, WriteDouble) },
160  { typeof(String), new ReadWriteBehavior<String>(ReadString, WriteString) },
161  { typeof(Identifier), new ReadWriteBehavior<Identifier>(ReadIdentifier, WriteIdentifier) },
162  { typeof(AccountId), new ReadWriteBehavior<AccountId>(ReadAccountId, WriteAccountId) },
163  { typeof(Color), new ReadWriteBehavior<Color>(ReadColor, WriteColor) },
164  { typeof(Vector2), new ReadWriteBehavior<Vector2>(ReadVector2, WriteVector2) },
165  { typeof(SerializableDateTime), new ReadWriteBehavior<SerializableDateTime>(ReadSerializableDateTime, WriteSerializableDateTime) },
166  { typeof(NetLimitedString), new ReadWriteBehavior<NetLimitedString>(ReadNetLString, WriteNetLString) }
167  };
168 
169  private static readonly ImmutableDictionary<Predicate<Type>, Func<Type, IReadWriteBehavior>> BehaviorFactories = new Dictionary<Predicate<Type>, Func<Type, IReadWriteBehavior>>
170  {
171  // Arrays
172  { type => type.IsArray, CreateArrayBehavior },
173 
174  // Nested INetSerializableStructs
175  { type => typeof(INetSerializableStruct).IsAssignableFrom(type), CreateINetSerializableStructBehavior },
176 
177  // Enums
178  { type => type.IsEnum, CreateEnumBehavior },
179 
180  // Nullable
181  { type => Nullable.GetUnderlyingType(type) != null, CreateNullableStructBehavior },
182 
183  // ImmutableArray
184  { type => IsOfGenericType(type, typeof(ImmutableArray<>)), CreateImmutableArrayBehavior },
185 
186  // Option
187  { type => IsOfGenericType(type, typeof(Option<>)), CreateOptionBehavior }
188  }.ToImmutableDictionary();
189 
198  private static IReadWriteBehavior CreateBehavior<TDelegateBase>(Type behaviorGenericParam,
199  Type funcGenericParam,
200  ReadWriteBehavior<TDelegateBase>.ReadDelegate readFunc,
201  ReadWriteBehavior<TDelegateBase>.WriteDelegate writeFunc)
202  {
203  var behaviorType = typeof(ReadWriteBehavior<>).MakeGenericType(behaviorGenericParam);
204 
205  var readDelegateType = typeof(ReadWriteBehavior<>.ReadDelegate).MakeGenericType(behaviorGenericParam);
206  var writeDelegateType = typeof(ReadWriteBehavior<>.WriteDelegate).MakeGenericType(behaviorGenericParam);
207 
208  var constructor = behaviorType.GetConstructor(new[]
209  {
210  readDelegateType, writeDelegateType
211  });
212 
213  return (constructor!.Invoke(new object[]
214  {
215  readFunc.Method.GetGenericMethodDefinition().MakeGenericMethod(funcGenericParam).CreateDelegate(readDelegateType),
216  writeFunc.Method.GetGenericMethodDefinition().MakeGenericMethod(funcGenericParam).CreateDelegate(writeDelegateType)
217  }) as IReadWriteBehavior)!;
218  }
219 
220  private static IReadWriteBehavior CreateArrayBehavior(Type arrayType) =>
221  CreateBehavior(
222  arrayType,
223  arrayType.GetElementType()!,
224  ReadArray<object>,
225  WriteArray<object>);
226 
227  private static IReadWriteBehavior CreateINetSerializableStructBehavior(Type structType) =>
228  CreateBehavior(
229  structType,
230  structType,
231  ReadINetSerializableStruct<INetSerializableStruct>,
232  WriteINetSerializableStruct<INetSerializableStruct>);
233 
234  private static IReadWriteBehavior CreateEnumBehavior(Type enumType) =>
235  CreateBehavior(
236  enumType,
237  enumType,
238  ReadEnum<Enum>,
239  WriteEnum<Enum>);
240 
241  private static IReadWriteBehavior CreateNullableStructBehavior(Type nullableType) =>
242  CreateBehavior(
243  nullableType,
244  Nullable.GetUnderlyingType(nullableType)!,
245  ReadNullable<int>,
246  WriteNullable<int>);
247 
248  private static IReadWriteBehavior CreateOptionBehavior(Type optionType) =>
249  CreateBehavior(
250  optionType,
251  optionType.GetGenericArguments()[0],
252  ReadOption<object>,
253  WriteOption<object>);
254 
255  private static IReadWriteBehavior CreateImmutableArrayBehavior(Type arrayType) =>
256  CreateBehavior(
257  arrayType,
258  arrayType.GetGenericArguments()[0],
259  ReadImmutableArray<object>,
260  WriteImmutableArray<object>);
261 
262  private static ImmutableArray<T> ReadImmutableArray<T>(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) where T : notnull
263  {
264  return ReadArray<T>(inc, attribute, bitField).ToImmutableArray();
265  }
266 
267  private static void WriteImmutableArray<T>(ImmutableArray<T> array, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) where T : notnull
268  {
269  ToolBox.ThrowIfNull(array);
270  WriteIReadOnlyCollection<T>(array, attribute, msg, bitField);
271  }
272 
273  private static T[] ReadArray<T>(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) where T : notnull
274  {
275  int length = bitField.ReadInteger(0, attribute.ArrayMaxSize);
276 
277  T[] array = new T[length];
278 
279  if (!TryFindBehavior(out ReadWriteBehavior<T> behavior))
280  {
281  throw new InvalidOperationException($"Could not find suitable behavior for type {typeof(T)} in {nameof(ReadArray)}");
282  }
283 
284  for (int i = 0; i < length; i++)
285  {
286  array[i] = behavior.ReadActionDirect(inc, attribute, bitField);
287  }
288 
289  return array;
290  }
291 
292  private static void WriteArray<T>(T[] array, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) where T : notnull
293  {
294  ToolBox.ThrowIfNull(array);
295  WriteIReadOnlyCollection(array, attribute, msg, bitField);
296  }
297 
298  private static void WriteIReadOnlyCollection<T>(IReadOnlyCollection<T> array, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) where T : notnull
299  {
300  bitField.WriteInteger(array.Count, 0, attribute.ArrayMaxSize);
301 
302  if (!TryFindBehavior(out ReadWriteBehavior<T> behavior))
303  {
304  throw new InvalidOperationException($"Could not find suitable behavior for type {typeof(T)} in {nameof(WriteArray)}");
305  }
306 
307  foreach (T o in array)
308  {
309  behavior.WriteActionDirect(o, attribute, msg, bitField);
310  }
311  }
312 
313  private static T ReadINetSerializableStruct<T>(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) where T : INetSerializableStruct
314  {
315  return INetSerializableStruct.ReadInternal<T>(inc, bitField);
316  }
317 
318  private static void WriteINetSerializableStruct<T>(T serializableStruct, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) where T : INetSerializableStruct
319  {
320  ToolBox.ThrowIfNull(serializableStruct);
321  serializableStruct.WriteInternal(msg, bitField);
322  }
323 
324  private static T ReadEnum<T>(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) where T : Enum
325  {
326  var type = typeof(T);
327 
328  Range<int> range = GetEnumRange(type);
329  int enumIndex = bitField.ReadInteger(range.Start, range.End);
330 
331  if (typeof(T).GetCustomAttribute<FlagsAttribute>() != null)
332  {
333  return (T)(object)enumIndex;
334  }
335 
336  foreach (T e in (T[])Enum.GetValues(type))
337  {
338  if (((int)(object)e) == enumIndex) { return e; }
339  }
340 
341  throw new InvalidOperationException($"An enum {type} with value {enumIndex} could not be found in {nameof(ReadEnum)}");
342  }
343 
344  private static void WriteEnum<T>(T value, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) where T : Enum
345  {
346  ToolBox.ThrowIfNull(value);
347 
348  Range<int> range = GetEnumRange(typeof(T));
349  bitField.WriteInteger((int)Convert.ChangeType(value, value.GetTypeCode()), range.Start, range.End);
350  }
351 
352  private static T? ReadNullable<T>(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) where T : struct =>
353  ReadOption<T>(inc, attribute, bitField).TryUnwrap(out var value) ? value : null;
354 
355  private static void WriteNullable<T>(T? value, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) where T : struct =>
356  WriteOption<T>(value.HasValue ? Option<T>.Some(value.Value) : Option<T>.None(), attribute, msg, bitField);
357 
358  private static Option<T> ReadOption<T>(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) where T : notnull
359  {
360  bool hasValue = bitField.ReadBoolean();
361  if (!hasValue)
362  {
363  return Option<T>.None();
364  }
365 
366  if (TryFindBehavior(out ReadWriteBehavior<T> behavior))
367  {
368  return Option<T>.Some(behavior.ReadActionDirect(inc, attribute, bitField));
369  }
370 
371  throw new InvalidOperationException($"Could not find suitable behavior for type {typeof(T)} in {nameof(ReadOption)}");
372  }
373 
374  private static void WriteOption<T>(Option<T> option, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) where T : notnull
375  {
376  ToolBox.ThrowIfNull(option);
377 
378  if (option.TryUnwrap(out T? value))
379  {
380  bitField.WriteBoolean(true);
381  if (TryFindBehavior(out ReadWriteBehavior<T> behavior))
382  {
383  behavior.WriteActionDirect(value, attribute, msg, bitField);
384  }
385  }
386  else
387  {
388  bitField.WriteBoolean(false);
389  }
390  }
391 
392  private static bool ReadBoolean(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) => bitField.ReadBoolean();
393  private static void WriteBoolean(bool b, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) { bitField.WriteBoolean(b); }
394 
395  private static byte ReadByte(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) => inc.ReadByte();
396  private static void WriteByte(byte b, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) { msg.WriteByte(b); }
397 
398  private static ushort ReadUInt16(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) => inc.ReadUInt16();
399  private static void WriteUInt16(ushort b, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) { msg.WriteUInt16(b); }
400 
401  private static short ReadInt16(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) => inc.ReadInt16();
402  private static void WriteInt16(short b, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) { msg.WriteInt16(b); }
403 
404  private static uint ReadUInt32(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) => inc.ReadUInt32();
405  private static void WriteUInt32(uint b, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) { msg.WriteUInt32(b); }
406 
407  private static int ReadInt32(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField)
408  {
409  if (IsRanged(attribute.MinValueInt, attribute.MaxValueInt))
410  {
411  return bitField.ReadInteger(attribute.MinValueInt, attribute.MaxValueInt);
412  }
413 
414  return inc.ReadInt32();
415  }
416 
417  private static void WriteInt32(int i, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField)
418  {
419  ToolBox.ThrowIfNull(i);
420 
421  if (IsRanged(attribute.MinValueInt, attribute.MaxValueInt))
422  {
423  bitField.WriteInteger(i, attribute.MinValueInt, attribute.MaxValueInt);
424  return;
425  }
426 
427  msg.WriteInt32(i);
428  }
429 
430  private static ulong ReadUInt64(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) => inc.ReadUInt64();
431  private static void WriteUInt64(ulong b, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) { msg.WriteUInt64(b); }
432 
433  private static long ReadInt64(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) => inc.ReadInt64();
434  private static void WriteInt64(long b, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) { msg.WriteInt64(b); }
435 
436  private static float ReadSingle(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField)
437  {
438  if (IsRanged(attribute.MinValueFloat, attribute.MaxValueFloat))
439  {
440  return bitField.ReadFloat(attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
441  }
442 
443  return inc.ReadSingle();
444  }
445 
446  private static void WriteSingle(float f, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField)
447  {
448  ToolBox.ThrowIfNull(f);
449 
450  if (IsRanged(attribute.MinValueFloat, attribute.MaxValueFloat))
451  {
452  bitField.WriteFloat(f, attribute.MinValueFloat, attribute.MaxValueFloat, attribute.NumberOfBits);
453  return;
454  }
455 
456  msg.WriteSingle(f);
457  }
458 
459  private static double ReadDouble(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) => inc.ReadDouble();
460  private static void WriteDouble(double b, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) { msg.WriteDouble(b); }
461 
462  // We do not validate that the string read is within the max length, but do we need to?
463  // Modified client could send a network message with a really long string when we use NetLimitedString
464  // but they could also just do that for any other network message.
465  private static NetLimitedString ReadNetLString(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) => new NetLimitedString(inc.ReadString());
466  private static void WriteNetLString(NetLimitedString b, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) { msg.WriteString(b.Value); }
467 
468  private static string ReadString(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) => inc.ReadString();
469  private static void WriteString(string b, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) { msg.WriteString(b); }
470 
471  private static Identifier ReadIdentifier(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) => inc.ReadIdentifier();
472  private static void WriteIdentifier(Identifier b, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField) { msg.WriteIdentifier(b); }
473 
474  private static AccountId ReadAccountId(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField)
475  {
476  string str = inc.ReadString();
477  return AccountId.Parse(str).TryUnwrap(out var accountId)
478  ? accountId
479  : throw new InvalidCastException($"Could not parse \"{str}\" as an {nameof(AccountId)}");
480  }
481 
482  private static void WriteAccountId(AccountId accountId, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField)
483  {
484  msg.WriteString(accountId.StringRepresentation);
485  }
486 
487  private static Color ReadColor(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField) => attribute.IncludeColorAlpha ? inc.ReadColorR8G8B8A8() : inc.ReadColorR8G8B8();
488 
489  private static void WriteColor(Color color, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField)
490  {
491  ToolBox.ThrowIfNull(color);
492 
493  if (attribute.IncludeColorAlpha)
494  {
495  msg.WriteColorR8G8B8A8(color);
496  return;
497  }
498 
499  msg.WriteColorR8G8B8(color);
500  }
501 
502  private static Vector2 ReadVector2(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField)
503  {
504  float x = ReadSingle(inc, attribute, bitField);
505  float y = ReadSingle(inc, attribute, bitField);
506 
507  return new Vector2(x, y);
508  }
509 
510  private static void WriteVector2(Vector2 vector2, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField)
511  {
512  ToolBox.ThrowIfNull(vector2);
513 
514  var (x, y) = vector2;
515  WriteSingle(x, attribute, msg, bitField);
516  WriteSingle(y, attribute, msg, bitField);
517  }
518 
519  private static readonly Range<Int64> ValidTickRange
520  = new Range<Int64>(
521  start: DateTime.MinValue.Ticks,
522  end: DateTime.MaxValue.Ticks);
523  private static readonly Range<Int16> ValidTimeZoneMinuteRange
524  = new Range<Int16>(
525  start: (Int16)TimeSpan.FromHours(-12).TotalMinutes,
526  end: (Int16)TimeSpan.FromHours(14).TotalMinutes);
527 
528  private static SerializableDateTime ReadSerializableDateTime(
529  IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField)
530  {
531  var ticks = inc.ReadInt64();
532  var timezone = inc.ReadInt16();
533 
534  if (!ValidTickRange.Contains(ticks))
535  {
536  throw new Exception($"Incoming SerializableDateTime ticks out of range (ticks: {ticks}, timezone: {timezone})");
537  }
538  if (!ValidTimeZoneMinuteRange.Contains(timezone))
539  {
540  throw new Exception($"Incoming SerializableDateTime timezone out of range (ticks: {ticks}, timezone: {timezone})");
541  }
542 
543  return new SerializableDateTime(new DateTime(ticks),
544  new SerializableTimeZone(TimeSpan.FromMinutes(timezone)));
545  }
546 
547  private static void WriteSerializableDateTime(
548  SerializableDateTime dateTime, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField)
549  {
550  msg.WriteInt64(dateTime.Ticks);
551  msg.WriteInt16((Int16)(dateTime.TimeZone.Value.Ticks / TimeSpan.TicksPerMinute));
552  }
553 
554  private static bool IsRanged(float minValue, float maxValue) => minValue > float.MinValue || maxValue < float.MaxValue;
555  private static bool IsRanged(int minValue, int maxValue) => minValue > int.MinValue || maxValue < int.MaxValue;
556 
557  private static Range<int> GetEnumRange(Type type)
558  {
559  ImmutableArray<int> values = Enum.GetValues(type).Cast<int>().ToImmutableArray();
560  return new Range<int>(values.Min(), values.Max());
561  }
562 
563  private static bool TryFindBehavior<T>(out ReadWriteBehavior<T> behavior) where T : notnull
564  {
565  bool found = TryFindBehavior(typeof(T), out var bhvr);
566  behavior = found ? (ReadWriteBehavior<T>)bhvr : default;
567  return found;
568  }
569 
570  private static bool TryFindBehavior(Type type, out IReadWriteBehavior behavior)
571  {
572  if (TypeBehaviors.TryGetValue(type, out var outBehavior))
573  {
574  behavior = outBehavior;
575  return true;
576  }
577 
578  foreach (var (predicate, factory) in BehaviorFactories)
579  {
580  if (!predicate(type)) { continue; }
581 
582  behavior = factory(type);
583  TypeBehaviors.Add(type, behavior);
584  return true;
585  }
586 
587  behavior = default!;
588  return false;
589  }
590 
591  public static ImmutableArray<CachedReflectedVariable> GetPropertiesAndFields(Type type)
592  {
593  if (CachedVariables.TryGetValue(type, out var cached)) { return cached; }
594 
595  List<CachedReflectedVariable> variables = new List<CachedReflectedVariable>();
596 
597  IEnumerable<PropertyInfo> propertyInfos = type.GetProperties().Where(HasAttribute).Where(NotStatic);
598  IEnumerable<FieldInfo> fieldInfos = type.GetFields().Where(HasAttribute).Where(NotStatic);
599 
600  foreach (PropertyInfo info in propertyInfos)
601  {
602  if (info.SetMethod is null)
603  {
604  //skip get-only properties, because it's
605  //useful to have them but their value
606  //cannot be set when reading a struct
607  continue;
608  }
609  if (TryFindBehavior(info.PropertyType, out IReadWriteBehavior behavior))
610  {
611  variables.Add(new CachedReflectedVariable(info, behavior, type));
612  }
613  else
614  {
615  throw new Exception($"Unable to serialize type \"{type}\".");
616  }
617  }
618 
619  foreach (FieldInfo info in fieldInfos)
620  {
621  if (TryFindBehavior(info.FieldType, out IReadWriteBehavior behavior))
622  {
623  variables.Add(new CachedReflectedVariable(info, behavior, type));
624  }
625  else
626  {
627  throw new Exception($"Unable to serialize type \"{type}\".");
628  }
629  }
630 
631  ImmutableArray<CachedReflectedVariable> array = variables.All(v => v.HasOwnAttribute) ? variables.OrderBy(v => v.Attribute.OrderKey).ToImmutableArray() : variables.ToImmutableArray();
632  CachedVariables.Add(type, array);
633  return array;
634 
635  bool HasAttribute(MemberInfo info) => (info.GetCustomAttribute<NetworkSerialize>() ?? type.GetCustomAttribute<NetworkSerialize>()) != null;
636 
637  static bool NotStatic(MemberInfo info)
638  => info switch
639  {
640  PropertyInfo property => property.GetGetMethod() is { IsStatic: false },
641  FieldInfo field => !field.IsStatic,
642  _ => false
643  };
644  }
645 
646  private static bool IsOfGenericType(Type type, Type comparedTo)
647  {
648  return type.IsGenericType && type.GetGenericTypeDefinition() == comparedTo;
649  }
650  }
651 
704  internal interface INetSerializableStruct
705  {
731  public static T Read<T>(IReadMessage inc) where T : INetSerializableStruct
732  {
733  ReadOnlyBitField bitField = new ReadOnlyBitField(inc);
734  return ReadInternal<T>(inc, bitField);
735  }
736 
737  public static T ReadInternal<T>(IReadMessage inc, ReadOnlyBitField bitField) where T : INetSerializableStruct
738  {
739  object? newObject = Activator.CreateInstance(typeof(T));
740  if (newObject is null) { return default!; }
741 
742  var properties = NetSerializableProperties.GetPropertiesAndFields(typeof(T));
743  foreach (NetSerializableProperties.CachedReflectedVariable property in properties)
744  {
745  object? value = property.Behavior.ReadAction(inc, property.Attribute, bitField);
746  try
747  {
748  property.SetValue(newObject, value);
749  }
750  catch (Exception exception)
751  {
752  throw new Exception($"Failed to assign" +
753  $" {value ?? "[NULL]"} ({value?.GetType().Name ?? "[NULL]"})" +
754  $" to {typeof(T).Name}.{property.Name} ({property.Type.Name})", exception);
755  }
756  }
757 
758  return (T)newObject;
759  }
760 
785  public void Write(IWriteMessage msg)
786  {
787  WriteOnlyBitField bitField = new WriteOnlyBitField();
788  IWriteMessage structWriteMsg = new WriteOnlyMessage();
789  WriteInternal(structWriteMsg, bitField);
790  bitField.WriteToMessage(msg);
791  msg.WriteBytes(structWriteMsg.Buffer, 0, structWriteMsg.LengthBytes);
792  }
793 
794  public void WriteInternal(IWriteMessage msg, WriteOnlyBitField bitField)
795  {
796  var properties = NetSerializableProperties.GetPropertiesAndFields(GetType());
797 
798  foreach (NetSerializableProperties.CachedReflectedVariable property in properties)
799  {
800  object? value = property.GetValue(this);
801  property.Behavior.WriteAction(value!, property.Attribute, msg, bitField);
802  }
803  }
804 
805  public static bool TryRead<T>(IReadMessage inc, AccountInfo sender, [NotNullWhen(true)] out T? data) where T : INetSerializableStruct
806  {
807  try
808  {
809  data = Read<T>(inc);
810  return true;
811  }
812  catch (Exception e)
813  {
814  LogError(e);
815  data = default;
816  return false;
817  }
818 
819  void LogError(Exception e)
820  {
821  int prevPos = inc.BitPosition;
822 
823  StringBuilder hexData = new();
824  inc.BitPosition = 0;
825  while (inc.BitPosition < inc.LengthBits)
826  {
827  byte b = inc.ReadByte();
828  hexData.Append($"{b:X2} ");
829  }
830  // trim the last space if there is one
831  if (hexData.Length > 0) { hexData.Length--; }
832 
833  inc.BitPosition = prevPos;
834 
835  //only log the error once per sender, so this can't be abused by spamming the server with malformed data to fill up the console with errors
836  //note that the name is "Unknown" if the client hasn't properly joined yet, so errors when first joining are only logged once
837  string accountInfoName = AccountInfoToName(sender);
838  DebugConsole.ThrowErrorOnce(
839  identifier: $"INetSerializableStruct.TryRead:{accountInfoName}",
840  errorMsg: $"Failed to read a message by {accountInfoName}. Data: \"{hexData}\"", e);
841 
842  static string AccountInfoToName(AccountInfo info)
843  {
844  var connectedClients =
845  GameMain.NetworkMember?.ConnectedClients ?? Array.Empty<Client>();
846 
847  foreach (Client c in connectedClients)
848  {
849  if (c.AccountInfo == info)
850  {
851  return c.Name;
852  }
853  }
854 
855  return info.AccountId.TryUnwrap(out var accountId) ? accountId.StringRepresentation : "Unknown";
856  }
857  }
858  }
859  }
860 }
Marks fields and properties as to be serialized and deserialized by INetSerializableStruct....
NetworkSerialize([CallerLineNumber] int lineNumber=0)
delegate void WriteDelegate(object? obj, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField)
delegate? object ReadDelegate(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField)
Microsoft.Xna.Framework.Color ReadColorR8G8B8A8()
Microsoft.Xna.Framework.Color ReadColorR8G8B8()
void WriteBytes(byte[] val, int startIndex, int length)
void WriteColorR8G8B8A8(Microsoft.Xna.Framework.Color val)
void WriteIdentifier(Identifier val)
void WriteColorR8G8B8(Microsoft.Xna.Framework.Color val)
delegate void SetValueDelegate(object? obj, object? value)
CachedReflectedVariable(MemberInfo info, IReadWriteBehavior behavior, Type baseClassType)
delegate void WriteDelegate(T obj, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField)
ReadWriteBehavior(ReadDelegate readAction, WriteDelegate writeAction)
delegate T ReadDelegate(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField)
readonly Option< AccountId > AccountId
The primary ID for a given user
Definition: AccountInfo.cs:15