Unstable 0.17.1.0

This commit is contained in:
Markus Isberg
2022-03-17 01:25:04 +09:00
parent 3974067915
commit 6d410cc1b7
302 changed files with 5878 additions and 3317 deletions
@@ -5,6 +5,8 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
@@ -42,6 +44,13 @@ namespace Barotrauma
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;
}
}
/// <summary>
@@ -52,6 +61,7 @@ namespace Barotrauma
public readonly struct ReadWriteBehavior
{
public delegate dynamic? ReadDelegate(IReadMessage inc, Type type, NetworkSerialize attribute);
public delegate void WriteDelegate(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg);
public readonly ReadDelegate ReadAction;
@@ -64,6 +74,58 @@ namespace Barotrauma
}
}
public readonly struct CachedReflectedVariable
{
public delegate object? GetValueDelegate(object? obj);
public delegate void SetValueDelegate(object? obj, object? value);
public readonly Type Type;
public readonly ReadWriteBehavior Behavior;
public readonly NetworkSerialize Attribute;
public readonly SetValueDelegate SetValue;
public readonly GetValueDelegate GetValue;
public readonly bool HasOwnAttribute;
public CachedReflectedVariable(MemberInfo info, ReadWriteBehavior behavior, Type baseClassType)
{
Behavior = behavior;
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<NetworkSerialize>() is { } ownAttriute)
{
HasOwnAttribute = true;
Attribute = ownAttriute;
}
else if (baseClassType.GetCustomAttribute<NetworkSerialize>() 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<Type, ImmutableArray<CachedReflectedVariable>> CachedVariables = new Dictionary<Type, ImmutableArray<CachedReflectedVariable>>();
private static readonly ImmutableDictionary<Type, ReadWriteBehavior> TypeBehaviors = new Dictionary<Type, ReadWriteBehavior>
{
{ typeof(Boolean), new ReadWriteBehavior(ReadBoolean, WriteDynamic) },
@@ -82,8 +144,6 @@ namespace Barotrauma
{ typeof(Vector2), new ReadWriteBehavior(ReadVector2, WriteVector2) }
}.ToImmutableDictionary();
private static readonly ReadWriteBehavior InvalidReadWriteBehavior = new ReadWriteBehavior(ReadInvalid, WriteInvalid);
private static readonly ImmutableDictionary<Predicate<Type>, ReadWriteBehavior> TypePredicates = new Dictionary<Predicate<Type>, ReadWriteBehavior>
{
// Arrays
@@ -99,15 +159,18 @@ namespace Barotrauma
{ type => Nullable.GetUnderlyingType(type) != null, new ReadWriteBehavior(ReadNullable, WriteNullable) },
// Option
{ type => type.GetGenericTypeDefinition() == typeof(Option<>), new ReadWriteBehavior(ReadOption, WriteOption) }
{ type => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Option<>), new ReadWriteBehavior(ReadOption, WriteOption) }
}.ToImmutableDictionary();
private static readonly ReadWriteBehavior InvalidReadWriteBehavior = new ReadWriteBehavior(ReadInvalid, WriteInvalid);
private static readonly Dictionary<Type, MethodInfo> cachedSomeCreateMethods = new Dictionary<Type, MethodInfo>();
private static readonly Dictionary<Type, MethodInfo> cachedNoneCreateMethod = new Dictionary<Type, MethodInfo>();
private static void WriteInvalid(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg) => throw new InvalidOperationException($"Type {obj?.GetType()} cannot be serialized. Did you forget to implement INetSerializableStruct?");
private static void WriteInvalid(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg) =>
throw new SerializationException($"Type {obj?.GetType()} cannot be serialized. Did you forget to implement {nameof(INetSerializableStruct)}?");
private static dynamic ReadInvalid(IReadMessage inc, Type type, NetworkSerialize attribute) => throw new InvalidOperationException($"Type {type} cannot be deserialized. Did you forget to implement INetSerializableStruct?");
private static dynamic ReadInvalid(IReadMessage inc, Type type, NetworkSerialize attribute) => throw new SerializationException($"Type {type} cannot be deserialized. Did you forget to implement {nameof(INetSerializableStruct)}?");
private static void WriteOption(dynamic? obj, NetworkSerialize attribute, IWriteMessage msg)
{
@@ -131,7 +194,7 @@ namespace Barotrauma
}
else
{
throw new InvalidOperationException("Option type was neither None<> or Some<>");
throw new ArgumentOutOfRangeException(nameof(obj), "Option type was neither None or Some");
}
}
@@ -147,7 +210,7 @@ namespace Barotrauma
if (TryFindBehavior(underlyingType, out ReadWriteBehavior behavior))
{
dynamic? value = behavior.ReadAction(inc, underlyingType, attribute);
return GetCreateMethod(typeof(Some<>), underlyingType, cachedSomeCreateMethods).Invoke(null, new []{ value });
return GetCreateMethod(typeof(Some<>), underlyingType, cachedSomeCreateMethods).Invoke(null, new[] { value });
}
throw new InvalidOperationException($"Could not find suitable behavior for type {underlyingType} in {nameof(ReadOption)}");
@@ -349,7 +412,7 @@ namespace Barotrauma
private static dynamic ReadDouble(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadDouble();
private static dynamic ReadString(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadString();
private static dynamic ReadIdentifier(IReadMessage inc, Type type, NetworkSerialize attribute) => inc.ReadIdentifier();
private static dynamic ReadColor(IReadMessage inc, Type type, NetworkSerialize attribute) => attribute.IncludeColorAlpha ? inc.ReadColorR8G8B8A8() : inc.ReadColorR8G8B8();
@@ -411,7 +474,7 @@ namespace Barotrauma
return new Range<int>(values.Min(), values.Max());
}
public static bool TryFindBehavior(Type type, out ReadWriteBehavior behavior)
private static bool TryFindBehavior(Type type, out ReadWriteBehavior behavior)
{
if (TypeBehaviors.TryGetValue(type, out behavior)) { return true; }
@@ -427,6 +490,46 @@ namespace Barotrauma
behavior = InvalidReadWriteBehavior;
return false;
}
public static ImmutableArray<CachedReflectedVariable> GetPropertiesAndFields(Type type, Type baseClassType)
{
if (CachedVariables.TryGetValue(type, out var cached)) { return cached; }
List<CachedReflectedVariable> variables = new List<CachedReflectedVariable>();
IEnumerable<PropertyInfo> propertyInfos = type.GetProperties().Where(HasAttribute);
IEnumerable<FieldInfo> fieldInfos = type.GetFields().Where(HasAttribute);
foreach (PropertyInfo info in propertyInfos)
{
if (TryFindBehavior(info.PropertyType, out ReadWriteBehavior behavior))
{
variables.Add(new CachedReflectedVariable(info, behavior, baseClassType));
}
else
{
throw new SerializationException($"Unable to serialize type \"{type}\".");
}
}
foreach (FieldInfo info in fieldInfos)
{
if (TryFindBehavior(info.FieldType, out ReadWriteBehavior behavior))
{
variables.Add(new CachedReflectedVariable(info, behavior, baseClassType));
}
else
{
throw new SerializationException($"Unable to serialize type \"{type}\".");
}
}
ImmutableArray<CachedReflectedVariable> array = variables.All(v => v.HasOwnAttribute) ? variables.OrderBy(v => v.Attribute.OrderKey).ToImmutableArray() : variables.ToImmutableArray();
CachedVariables.Add(type, array);
return array;
bool HasAttribute(MemberInfo info) => (info.GetCustomAttribute<NetworkSerialize>() ?? baseClassType.GetCustomAttribute<NetworkSerialize>()) != null;
}
}
/// <summary>
@@ -512,38 +615,11 @@ namespace Barotrauma
object? newObject = Activator.CreateInstance(type);
if (newObject is null) { return default!; }
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo info in properties)
var properties = NetSerializableProperties.GetPropertiesAndFields(type, type);
foreach (NetSerializableProperties.CachedReflectedVariable property in properties)
{
NetworkSerialize? attribute = GetAttribute(info, newObject);
if (attribute is null) { continue; }
if (NetSerializableProperties.TryFindBehavior(info.PropertyType, out var behavior))
{
object? value = behavior.ReadAction(inc, info.PropertyType, attribute);
info.SetValue(newObject, value);
}
else
{
DebugConsole.ThrowError($"Unsupported property type \"{info.PropertyType}\" in {newObject}!");
}
}
FieldInfo[] fields = type.GetFields();
foreach (FieldInfo info in fields)
{
NetworkSerialize? attribute = GetAttribute(info, newObject);
if (attribute is null) { continue; }
if (NetSerializableProperties.TryFindBehavior(info.FieldType, out var behavior))
{
object? value = behavior.ReadAction(inc, info.FieldType, attribute);
info.SetValue(newObject, value);
}
else
{
DebugConsole.ThrowError($"Unsupported field type \"{info.FieldType}\" in {newObject}!");
}
NetworkSerialize attribute = property.Attribute;
property.SetValue(newObject, property.Behavior.ReadAction(inc, property.Type, attribute));
}
return newObject;
@@ -575,39 +651,34 @@ namespace Barotrauma
/// <param name="msg">Outgoing network message</param>
public void Write(IWriteMessage msg)
{
PropertyInfo[] properties = GetType().GetProperties();
foreach (PropertyInfo info in properties)
Type type = GetType();
var properties = NetSerializableProperties.GetPropertiesAndFields(type, type);
foreach (NetSerializableProperties.CachedReflectedVariable property in properties)
{
NetworkSerialize? attribute = GetAttribute(info, this);
if (attribute is null) { continue; }
if (NetSerializableProperties.TryFindBehavior(info.PropertyType, out var behavior))
{
behavior.WriteAction(info.GetValue(this), attribute, msg);
}
else
{
throw new InvalidOperationException($"Unsupported property type \"{info.PropertyType}\" in {this}");
}
}
FieldInfo[] fields = GetType().GetFields();
foreach (FieldInfo info in fields)
{
NetworkSerialize? attribute = GetAttribute(info, this);
if (attribute is null) { continue; }
if (NetSerializableProperties.TryFindBehavior(info.FieldType, out var behavior))
{
behavior.WriteAction(info.GetValue(this), attribute, msg);
}
else
{
throw new InvalidOperationException($"Unsupported field type \"{info.FieldType}\" in {this}");
}
NetworkSerialize attribute = property.Attribute;
property.Behavior.WriteAction(property.GetValue(this), attribute, msg);
}
}
}
private static NetworkSerialize? GetAttribute(MemberInfo info, object baseClass) => info.GetCustomAttribute<NetworkSerialize>() ?? baseClass.GetType().GetCustomAttribute<NetworkSerialize>();
public static class WriteOnlyMessageExtensions
{
#if CLIENT
public static IWriteMessage WithHeader(this IWriteMessage msg, ClientPacketHeader header)
{
msg.Write((byte)header);
return msg;
}
#elif SERVER
public static IWriteMessage WithHeader(this IWriteMessage msg, ServerPacketHeader header)
{
msg.Write((byte)header);
return msg;
}
#endif
public static void Write(this IWriteMessage msg, INetSerializableStruct serializableStruct)
{
serializableStruct.Write(msg);
}
}
}