#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
///
/// Marks fields and properties as to be serialized and deserialized by .
/// Also contains settings for some types like maximum and minimum values for numbers to reduce bits used.
///
///
///
/// struct NetPurchasedItem : INetSerializableStruct
/// {
/// [NetworkSerialize]
/// public string Identifier;
///
/// [NetworkSerialize(ArrayMaxSize = 16)]
/// public string[] Tags;
///
/// [NetworkSerialize(MinValueInt = 0, MaxValueInt = 8)]
/// public int Amount;
/// }
///
///
///
/// Using the attribute on the struct will make all fields and properties serialized
///
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Struct | AttributeTargets.Property)]
public sealed class NetworkSerialize : Attribute
{
public int MaxValueInt = int.MaxValue;
public int MinValueInt = int.MinValue;
public float MaxValueFloat = float.MaxValue;
public float MinValueFloat = float.MinValue;
public int NumberOfBits = 8;
public bool IncludeColorAlpha = false;
public int ArrayMaxSize = ushort.MaxValue;
public readonly int OrderKey;
public NetworkSerialize([CallerLineNumber] int lineNumber = 0)
{
OrderKey = lineNumber;
}
}
///
/// Static class that contains serialize and deserialize functions for different types used in
///
[SuppressMessage("ReSharper", "RedundantTypeArgumentsOfMethod")]
static class NetSerializableProperties
{
public interface IReadWriteBehavior
{
public delegate object? ReadDelegate(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField);
public delegate void WriteDelegate(object? obj, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField);
public ReadDelegate ReadAction { get; }
public WriteDelegate WriteAction { get; }
}
public readonly struct ReadWriteBehavior : IReadWriteBehavior
{
public delegate T ReadDelegate(IReadMessage inc, NetworkSerialize attribute, ReadOnlyBitField bitField);
public delegate void WriteDelegate(T obj, NetworkSerialize attribute, IWriteMessage msg, WriteOnlyBitField bitField);
public IReadWriteBehavior.ReadDelegate ReadAction { get; }
public IReadWriteBehavior.WriteDelegate WriteAction { get; }
public ReadDelegate ReadActionDirect { get; }
public WriteDelegate WriteActionDirect { get; }
public ReadWriteBehavior(ReadDelegate readAction, WriteDelegate writeAction)
{
ReadAction = (inc, attribute, bitField) => readAction(inc, attribute, bitField);
WriteAction = (o, attribute, msg, bitField) => writeAction((T)o!, attribute, msg, bitField);
ReadActionDirect = readAction;
WriteActionDirect = writeAction;
}
}
public readonly struct CachedReflectedVariable
{
public delegate object? GetValueDelegate(object? obj);
public delegate void SetValueDelegate(object? obj, object? value);
public readonly string Name;
public readonly Type Type;
public readonly IReadWriteBehavior Behavior;
public readonly NetworkSerialize Attribute;
public readonly SetValueDelegate SetValue;
public readonly GetValueDelegate GetValue;
public readonly bool HasOwnAttribute;
public CachedReflectedVariable(MemberInfo info, IReadWriteBehavior behavior, Type baseClassType)
{
Behavior = behavior;
Name = info.Name;
switch (info)
{
case PropertyInfo pi:
Type = pi.PropertyType;
GetValue = pi.GetValue;
SetValue = pi.SetValue;
break;
case FieldInfo fi:
Type = fi.FieldType;
GetValue = fi.GetValue;
SetValue = fi.SetValue;
break;
default:
throw new ArgumentException($"Expected {nameof(FieldInfo)} or {nameof(PropertyInfo)} but found {info.GetType()}.", nameof(info));
}
if (info.GetCustomAttribute() is { } ownAttriute)
{
HasOwnAttribute = true;
Attribute = ownAttriute;
}
else if (baseClassType.GetCustomAttribute() is { } globalAttribute)
{
HasOwnAttribute = false;
Attribute = globalAttribute;
}
else
{
throw new InvalidOperationException($"Unable to serialize \"{Type}\" in \"{baseClassType}\" because it has no {nameof(NetworkSerialize)} attribute.");
}
}
}
private static readonly Dictionary> CachedVariables = new Dictionary>();
private static readonly Dictionary TypeBehaviors
= new Dictionary
{
{ typeof(Boolean), new ReadWriteBehavior(ReadBoolean, WriteBoolean) },
{ typeof(Byte), new ReadWriteBehavior(ReadByte, WriteByte) },
{ typeof(UInt16), new ReadWriteBehavior(ReadUInt16, WriteUInt16) },
{ typeof(Int16), new ReadWriteBehavior(ReadInt16, WriteInt16) },
{ typeof(UInt32), new ReadWriteBehavior(ReadUInt32, WriteUInt32) },
{ typeof(Int32), new ReadWriteBehavior(ReadInt32, WriteInt32) },
{ typeof(UInt64), new ReadWriteBehavior(ReadUInt64, WriteUInt64) },
{ typeof(Int64), new ReadWriteBehavior(ReadInt64, WriteInt64) },
{ typeof(Single), new ReadWriteBehavior(ReadSingle, WriteSingle) },
{ typeof(Double), new ReadWriteBehavior(ReadDouble, WriteDouble) },
{ typeof(String), new ReadWriteBehavior(ReadString, WriteString) },
{ typeof(Identifier), new ReadWriteBehavior(ReadIdentifier, WriteIdentifier) },
{ typeof(AccountId), new ReadWriteBehavior(ReadAccountId, WriteAccountId) },
{ typeof(Color), new ReadWriteBehavior(ReadColor, WriteColor) },
{ typeof(Vector2), new ReadWriteBehavior(ReadVector2, WriteVector2) },
{ typeof(SerializableDateTime), new ReadWriteBehavior(ReadSerializableDateTime, WriteSerializableDateTime) },
{ typeof(NetLimitedString), new ReadWriteBehavior(ReadNetLString, WriteNetLString) }
};
private static readonly ImmutableDictionary, Func> BehaviorFactories = new Dictionary, Func>
{
// Arrays
{ type => type.IsArray, CreateArrayBehavior },
// Nested INetSerializableStructs
{ type => typeof(INetSerializableStruct).IsAssignableFrom(type), CreateINetSerializableStructBehavior },
// Enums
{ type => type.IsEnum, CreateEnumBehavior },
// Nullable
{ type => Nullable.GetUnderlyingType(type) != null, CreateNullableStructBehavior },
// ImmutableArray
{ type => IsOfGenericType(type, typeof(ImmutableArray<>)), CreateImmutableArrayBehavior },
// Option
{ type => IsOfGenericType(type, typeof(Option<>)), CreateOptionBehavior }
}.ToImmutableDictionary();
/// The type that the behavior handles
/// The type that will be used as the generic parameter for the read/write methods
/// The read method.
/// It must have a generic parameter.
/// The return type must be such that if the generic parameter is replaced with funcGenericParam, you get behaviorGenericParam.
/// The write method. The first parameter's type must be the same as readFunc's return type.
/// Ideally the least specific type possible, because it's replaced by behaviorGenericParam
/// A ReadWriteBehavior<behaviorGenericParam>
private static IReadWriteBehavior CreateBehavior(Type behaviorGenericParam,
Type funcGenericParam,
ReadWriteBehavior.ReadDelegate readFunc,
ReadWriteBehavior.WriteDelegate writeFunc)
{
var behaviorType = typeof(ReadWriteBehavior<>).MakeGenericType(behaviorGenericParam);
var readDelegateType = typeof(ReadWriteBehavior<>.ReadDelegate).MakeGenericType(behaviorGenericParam);
var writeDelegateType = typeof(ReadWriteBehavior<>.WriteDelegate).MakeGenericType(behaviorGenericParam);
var constructor = behaviorType.GetConstructor(new[]
{
readDelegateType, writeDelegateType
});
return (constructor!.Invoke(new object[]
{
readFunc.Method.GetGenericMethodDefinition().MakeGenericMethod(funcGenericParam).CreateDelegate(readDelegateType),
writeFunc.Method.GetGenericMethodDefinition().MakeGenericMethod(funcGenericParam).CreateDelegate(writeDelegateType)
}) as IReadWriteBehavior)!;
}
private static IReadWriteBehavior CreateArrayBehavior(Type arrayType) =>
CreateBehavior(
arrayType,
arrayType.GetElementType()!,
ReadArray