first commit

This commit is contained in:
2025-07-06 00:23:46 +02:00
commit 38f50c8819
1788 changed files with 112878 additions and 0 deletions

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NitroxModel_Subnautica.Logger;
using NitroxModel.Packets;
using NitroxModel.Packets.Processors.Abstract;
using NitroxServer;
using NitroxServer_Subnautica;
using NitroxServer.ConsoleCommands.Abstract;
namespace Nitrox.Test.Helper.Faker;
public class NitroxAbstractFaker : NitroxFaker, INitroxFaker
{
private static readonly Dictionary<Type, Type[]> subtypesByBaseType;
static NitroxAbstractFaker()
{
Assembly[] assemblies = { typeof(Packet).Assembly, typeof(SubnauticaInGameLogger).Assembly, typeof(ServerAutoFacRegistrar).Assembly, typeof(SubnauticaServerAutoFacRegistrar).Assembly };
HashSet<Type> blacklistedTypes = new() { typeof(Packet), typeof(CorrelatedPacket), typeof(Command), typeof(PacketProcessor) };
List<Type> types = new();
foreach (Assembly assembly in assemblies)
{
types.AddRange(assembly.GetTypes());
}
subtypesByBaseType = types.Where(type => type.IsAbstract && !type.IsSealed && !blacklistedTypes.Contains(type))
.ToDictionary(type => type, type => types.Where(t => type.IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface).ToArray())
.Where(dict => dict.Value.Length > 0)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
public readonly int AssignableTypesCount;
private readonly Queue<INitroxFaker> assignableFakers = new();
public NitroxAbstractFaker(Type type)
{
if (!type.IsAbstract)
{
throw new ArgumentException("Argument is not abstract", nameof(type));
}
if (!subtypesByBaseType.TryGetValue(type, out Type[] subTypes))
{
throw new ArgumentException($"Argument is not contained in {nameof(subtypesByBaseType)}", nameof(type));
}
OutputType = type;
AssignableTypesCount = subTypes.Length;
FakerByType.Add(type, this);
foreach (Type subType in subTypes)
{
assignableFakers.Enqueue(GetOrCreateFaker(subType));
}
}
public INitroxFaker[] GetSubFakers() => assignableFakers.ToArray();
/// <summary>
/// Selects an implementing type in a round-robin fashion of the abstract type of this faker. Then creates an instance of it.
/// </summary>
public object GenerateUnsafe(HashSet<Type> typeTree)
{
INitroxFaker assignableFaker = assignableFakers.Dequeue();
assignableFakers.Enqueue(assignableFaker);
return assignableFaker.GenerateUnsafe(typeTree);
}
}

View File

@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
namespace Nitrox.Test.Helper.Faker;
public class NitroxActionFaker : NitroxFaker, INitroxFaker
{
private readonly Func<Bogus.Faker, object> generateAction;
public NitroxActionFaker(Type type, Func<Bogus.Faker, object> action)
{
OutputType = type;
generateAction = action;
}
public INitroxFaker[] GetSubFakers() => Array.Empty<INitroxFaker>();
public object GenerateUnsafe(HashSet<Type> _) => generateAction.Invoke(Faker);
}

View File

@@ -0,0 +1,239 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using BinaryPack.Attributes;
using NitroxModel.Logger;
namespace Nitrox.Test.Helper.Faker;
public class NitroxAutoFaker<T> : NitroxFaker, INitroxFaker
{
private readonly ConstructorInfo constructor;
private readonly MemberInfo[] memberInfos;
private readonly INitroxFaker[] parameterFakers;
public NitroxAutoFaker()
{
Type type = typeof(T);
if (!IsValidType(type))
{
throw new InvalidOperationException($"{type.Name} is not a valid type for {nameof(NitroxAutoFaker<T>)}");
}
OutputType = type;
FakerByType.Add(type, this);
if (type.GetCustomAttributes(typeof(DataContractAttribute), false).Length > 0)
{
memberInfos = type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(member => member.GetCustomAttributes<DataMemberAttribute>().Any()).ToArray();
}
else
{
memberInfos = type.GetMembers(BindingFlags.Public | BindingFlags.Instance)
.Where(member => member.MemberType is MemberTypes.Field or MemberTypes.Property &&
!member.GetCustomAttributes<IgnoredMemberAttribute>().Any())
.ToArray();
}
if (!TryGetConstructorForType(type, memberInfos, out constructor) &&
!TryGetConstructorForType(type, Array.Empty<MemberInfo>(), out constructor))
{
throw new NullReferenceException($"Could not find a constructor with no parameters for {type}");
}
parameterFakers = new INitroxFaker[memberInfos.Length];
Type[] constructorArgumentTypes = constructor.GetParameters().Select(p => p.ParameterType).ToArray();
for (int i = 0; i < memberInfos.Length; i++)
{
Type dataMemberType = constructorArgumentTypes.Length == memberInfos.Length ? constructorArgumentTypes[i] : memberInfos[i].GetMemberType();
if (FakerByType.TryGetValue(dataMemberType, out INitroxFaker memberFaker))
{
parameterFakers[i] = memberFaker;
}
else
{
parameterFakers[i] = CreateFaker(dataMemberType);
}
}
}
private void ValidateFakerTree()
{
List<INitroxFaker> fakerTree = new();
void ValidateFaker(INitroxFaker nitroxFaker)
{
if (fakerTree.Contains(nitroxFaker))
{
return;
}
fakerTree.Add(nitroxFaker);
if (nitroxFaker is NitroxAbstractFaker abstractFaker)
{
NitroxCollectionFaker collectionFaker = (NitroxCollectionFaker)fakerTree.LastOrDefault(f => f.GetType() == typeof(NitroxCollectionFaker));
if (collectionFaker != null)
{
collectionFaker.GenerateSize = Math.Max(collectionFaker.GenerateSize, abstractFaker.AssignableTypesCount);
}
}
foreach (INitroxFaker subFaker in nitroxFaker.GetSubFakers())
{
ValidateFaker(subFaker);
}
fakerTree.Remove(nitroxFaker);
}
foreach (INitroxFaker parameterFaker in parameterFakers)
{
ValidateFaker(parameterFaker);
}
}
public INitroxFaker[] GetSubFakers() => parameterFakers;
public T Generate()
{
ValidateFakerTree();
return (T)GenerateUnsafe(new HashSet<Type>());
}
public object GenerateUnsafe(HashSet<Type> typeTree)
{
object[] parameterValues = new object[parameterFakers.Length];
for (int i = 0; i < parameterValues.Length; i++)
{
INitroxFaker parameterFaker = parameterFakers[i];
if (typeTree.Contains(parameterFaker.OutputType))
{
if (parameterFaker is NitroxCollectionFaker collectionFaker)
{
parameterValues[i] = Activator.CreateInstance(collectionFaker.OutputCollectionType);
}
else
{
parameterValues[i] = null;
}
}
else
{
typeTree.Add(parameterFaker.OutputType);
parameterValues[i] = parameterFakers[i].GenerateUnsafe(typeTree);
typeTree.Remove(parameterFaker.OutputType);
}
}
if (constructor.GetParameters().Length == parameterValues.Length)
{
return (T)constructor.Invoke(parameterValues);
}
T obj = (T)constructor.Invoke(Array.Empty<object>());
for (int index = 0; index < memberInfos.Length; index++)
{
MemberInfo memberInfo = memberInfos[index];
switch (memberInfo.MemberType)
{
case MemberTypes.Field:
((FieldInfo)memberInfo).SetValue(obj, parameterValues[index]);
break;
case MemberTypes.Property:
PropertyInfo propertyInfo = (PropertyInfo)memberInfo;
if (!propertyInfo.CanWrite &&
NitroxCollectionFaker.IsCollection(parameterValues[index].GetType(), out NitroxCollectionFaker.CollectionType collectionType))
{
dynamic origColl = propertyInfo.GetValue(obj);
switch (collectionType)
{
case NitroxCollectionFaker.CollectionType.ARRAY:
for (int i = 0; i < ((Array)parameterValues[index]).Length; i++)
{
origColl[i] = ((Array)parameterValues[index]).GetValue(i);
}
break;
case NitroxCollectionFaker.CollectionType.LIST:
case NitroxCollectionFaker.CollectionType.DICTIONARY:
case NitroxCollectionFaker.CollectionType.SET:
foreach (dynamic createdValue in ((IEnumerable)parameterValues[index]))
{
origColl.Add(createdValue);
}
break;
case NitroxCollectionFaker.CollectionType.QUEUE:
foreach (dynamic createdValue in ((IEnumerable)parameterValues[index]))
{
origColl.Enqueue(createdValue);
}
break;
case NitroxCollectionFaker.CollectionType.NONE:
default:
throw new ArgumentOutOfRangeException();
}
}
else if(propertyInfo.CanWrite)
{
propertyInfo.SetValue(obj, parameterValues[index]);
}
else
{
Regex backingFieldNameRegex = new($"\\A<{propertyInfo.Name}>k__BackingField\\Z");
FieldInfo backingField = propertyInfo.DeclaringType.GetRuntimeFields().FirstOrDefault(a => backingFieldNameRegex.IsMatch(a.Name));
if (backingField == null)
{
throw new InvalidOperationException($"{propertyInfo.DeclaringType}.{propertyInfo.Name} is not accessible for writing");
}
backingField.SetValue(obj, parameterValues[index]);
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
return obj;
}
private static bool TryGetConstructorForType(Type type, MemberInfo[] dataMembers, out ConstructorInfo constructorInfo)
{
foreach (ConstructorInfo constructor in type.GetConstructors())
{
if (constructor.GetParameters().Length != dataMembers.Length)
{
continue;
}
bool parameterValid = constructor.GetParameters()
.All(parameter => dataMembers.Any(d => d.GetMemberType() == parameter.ParameterType &&
d.Name.Equals(parameter.Name, StringComparison.OrdinalIgnoreCase)));
if (parameterValid)
{
constructorInfo = constructor;
return true;
}
}
constructorInfo = null;
return false;
}
}

View File

@@ -0,0 +1,202 @@
using NitroxModel.DataStructures;
namespace Nitrox.Test.Helper.Faker;
public class NitroxCollectionFaker : NitroxFaker, INitroxFaker
{
public enum CollectionType
{
NONE,
ARRAY,
LIST,
DICTIONARY,
SET,
QUEUE
}
private const int DEFAULT_SIZE = 2;
public static bool IsCollection(Type t, out CollectionType collectionType)
{
if (t.IsArray && t.GetArrayRank() == 1)
{
collectionType = CollectionType.ARRAY;
return true;
}
if (t.IsGenericType)
{
Type[] genericInterfacesDefinition = t.GetInterfaces()
.Where(i => i.IsGenericType)
.Select(i => i.GetGenericTypeDefinition())
.ToArray();
if (genericInterfacesDefinition.Any(i => i == typeof(IList<>)))
{
collectionType = CollectionType.LIST;
return true;
}
if (genericInterfacesDefinition.Any(i => i == typeof(IDictionary<,>)))
{
collectionType = CollectionType.DICTIONARY;
return true;
}
if (genericInterfacesDefinition.Any(i => i == typeof(ISet<>)))
{
collectionType = CollectionType.SET;
return true;
}
Type genericTypeDefinition = t.GetGenericTypeDefinition();
if (genericTypeDefinition == typeof(Queue<>) || genericTypeDefinition == typeof(ThreadSafeQueue<>)) // Queue has no defining interface
{
collectionType = CollectionType.QUEUE;
return true;
}
}
collectionType = CollectionType.NONE;
return false;
}
public static bool TryGetCollectionTypes(Type type, out Type[] types)
{
if (!IsCollection(type, out CollectionType collectionType))
{
types = [];
return false;
}
if (collectionType == CollectionType.ARRAY)
{
types = [type.GetElementType()];
return true;
}
types = type.GenericTypeArguments;
return true;
}
public int GenerateSize = DEFAULT_SIZE;
public Type OutputCollectionType;
private readonly INitroxFaker[] subFakers;
private readonly Func<HashSet<Type>, object> generateAction;
public NitroxCollectionFaker(Type type, CollectionType collectionType)
{
OutputCollectionType = type;
INitroxFaker elementFaker;
switch (collectionType)
{
case CollectionType.ARRAY:
Type arrayType = OutputType = type.GetElementType();
elementFaker = GetOrCreateFaker(arrayType);
subFakers = [elementFaker];
generateAction = typeTree =>
{
typeTree.Add(arrayType);
Array array = Array.CreateInstance(arrayType, GenerateSize);
for (int i = 0; i < GenerateSize; i++)
{
array.SetValue(elementFaker.GenerateUnsafe(typeTree), i);
}
typeTree.Remove(arrayType);
return array;
};
break;
case CollectionType.LIST:
case CollectionType.SET:
Type listType = OutputType = type.GenericTypeArguments[0];
elementFaker = GetOrCreateFaker(listType);
subFakers = [elementFaker];
generateAction = typeTree =>
{
typeTree.Add(listType);
dynamic list = Activator.CreateInstance(type);
for (int i = 0; i < GenerateSize; i++)
{
MethodInfo castMethod = CastMethodBase.MakeGenericMethod(OutputType);
dynamic castedObject = castMethod.Invoke(null, [elementFaker.GenerateUnsafe(typeTree)]);
list.Add(castedObject);
}
typeTree.Remove(listType);
return list;
};
break;
case CollectionType.DICTIONARY:
Type[] dicType = type.GenericTypeArguments;
OutputType = dicType[1]; // A little hacky but should work as we don't use circular dependencies as keys
INitroxFaker keyFaker = GetOrCreateFaker(dicType[0]);
INitroxFaker valueFaker = GetOrCreateFaker(dicType[1]);
subFakers = [keyFaker, valueFaker];
generateAction = typeTree =>
{
typeTree.Add(dicType[0]);
typeTree.Add(dicType[1]);
IDictionary dict = (IDictionary)Activator.CreateInstance(type);
for (int i = 0; i < GenerateSize; i++)
{
for (int tries = 0; tries < 10; tries++)
{
object key = keyFaker.GenerateUnsafe(typeTree);
if (!dict.Contains(key))
{
dict.Add(key, valueFaker.GenerateUnsafe(typeTree));
break;
}
if (tries == 9)
{
throw new InvalidOperationException($"While generating action for filling Dictionary an unique key of {dicType[0]} couldn't be generated even after 10 tries");
}
}
}
typeTree.Remove(dicType[0]);
typeTree.Remove(dicType[1]);
return dict;
};
break;
case CollectionType.QUEUE:
Type queueType = OutputType = type.GenericTypeArguments[0];
elementFaker = GetOrCreateFaker(queueType);
subFakers = [elementFaker];
generateAction = typeTree =>
{
typeTree.Add(queueType);
dynamic queue = Activator.CreateInstance(type);
for (int i = 0; i < GenerateSize; i++)
{
MethodInfo castMethod = CastMethodBase.MakeGenericMethod(OutputType);
dynamic castedObject = castMethod.Invoke(null, [elementFaker.GenerateUnsafe(typeTree)]);
queue!.Enqueue(castedObject);
}
typeTree.Remove(queueType);
return queue;
};
break;
case CollectionType.NONE:
default:
throw new ArgumentOutOfRangeException(nameof(collectionType), collectionType, null);
}
}
public INitroxFaker[] GetSubFakers() => subFakers;
public object GenerateUnsafe(HashSet<Type> typeTree) => generateAction.Invoke(typeTree);
}

View File

@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.Util;
namespace Nitrox.Test.Helper.Faker;
public interface INitroxFaker
{
public Type OutputType { get; }
public INitroxFaker[] GetSubFakers();
public object GenerateUnsafe(HashSet<Type> typeTree);
}
public abstract class NitroxFaker
{
public Type OutputType { get; protected init; }
protected static readonly Bogus.Faker Faker;
static NitroxFaker()
{
Faker = new Bogus.Faker();
}
protected static readonly Dictionary<Type, INitroxFaker> FakerByType = new()
{
// Basic types
{ typeof(bool), new NitroxActionFaker(typeof(bool), f => f.Random.Bool()) },
{ typeof(byte), new NitroxActionFaker(typeof(byte), f => f.Random.Byte()) },
{ typeof(sbyte), new NitroxActionFaker(typeof(sbyte), f => f.Random.SByte()) },
{ typeof(short), new NitroxActionFaker(typeof(short), f => f.Random.Short()) },
{ typeof(ushort), new NitroxActionFaker(typeof(ushort), f => f.Random.UShort()) },
{ typeof(int), new NitroxActionFaker(typeof(int), f => f.Random.Int()) },
{ typeof(uint), new NitroxActionFaker(typeof(uint), f => f.Random.UInt()) },
{ typeof(long), new NitroxActionFaker(typeof(long), f => f.Random.Long()) },
{ typeof(ulong), new NitroxActionFaker(typeof(ulong), f => f.Random.ULong()) },
{ typeof(decimal), new NitroxActionFaker(typeof(decimal), f => f.Random.Decimal()) },
{ typeof(float), new NitroxActionFaker(typeof(float), f => f.Random.Float()) },
{ typeof(double), new NitroxActionFaker(typeof(double), f => f.Random.Double()) },
{ typeof(char), new NitroxActionFaker(typeof(char), f => f.Random.Char()) },
{ typeof(string), new NitroxActionFaker(typeof(string), f => f.Random.Word()) },
// Nitrox types
{ typeof(NitroxTechType), new NitroxActionFaker(typeof(NitroxTechType), f => new NitroxTechType(f.PickRandom<TechType>().ToString())) },
{ typeof(NitroxId), new NitroxActionFaker(typeof(NitroxId), f => new NitroxId(f.Random.Guid())) },
};
public static INitroxFaker GetOrCreateFaker(Type t)
{
return FakerByType.TryGetValue(t, out INitroxFaker nitroxFaker) ? nitroxFaker : CreateFaker(t);
}
protected static INitroxFaker CreateFaker(Type type)
{
if (type.IsAbstract)
{
return new NitroxAbstractFaker(type);
}
if (type.IsEnum)
{
return new NitroxActionFaker(type, f =>
{
string[] selection = Enum.GetNames(type);
if (selection.Length == 0)
{
throw new ArgumentException("There are no enum values after exclusion to choose from.");
}
string val = f.Random.ArrayElement(selection);
return Enum.Parse(type, val);
});
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Optional<>))
{
return new NitroxOptionalFaker(type);
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return new NitroxNullableFaker(type);
}
if (NitroxCollectionFaker.IsCollection(type, out NitroxCollectionFaker.CollectionType collectionType))
{
return new NitroxCollectionFaker(type, collectionType);
}
ConstructorInfo constructor = typeof(NitroxAutoFaker<>).MakeGenericType(type).GetConstructor(Array.Empty<Type>());
if (constructor == null)
{
throw new NullReferenceException($"Could not get generic constructor for {type}");
}
return (INitroxFaker)constructor.Invoke(Array.Empty<object>());
}
protected static bool IsValidType(Type type)
{
return FakerByType.ContainsKey(type) ||
type.GetCustomAttributes(typeof(DataContractAttribute), false).Length >= 1 ||
type.GetCustomAttributes(typeof(SerializableAttribute), false).Length >= 1 ||
(NitroxCollectionFaker.TryGetCollectionTypes(type, out Type[] collectionTypes) && collectionTypes.All(IsValidType)) ||
type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
}
protected static readonly MethodInfo CastMethodBase = typeof(NitroxFaker).GetMethod(nameof(Cast), BindingFlags.NonPublic | BindingFlags.Static);
protected static T Cast<T>(object o)
{
return (T)o;
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Nitrox.Test.Helper.Faker;
public class NitroxNullableFaker : NitroxFaker, INitroxFaker
{
private readonly Func<HashSet<Type>, object> generateAction;
public NitroxNullableFaker(Type type)
{
OutputType = type.GenericTypeArguments[0];
generateAction = (typeTree) =>
{
MethodInfo castMethod = CastMethodBase.MakeGenericMethod(OutputType);
object castedObject = castMethod.Invoke(null, new[] { GetOrCreateFaker(OutputType).GenerateUnsafe(typeTree) });
Type nullableType = typeof(Nullable<>).MakeGenericType(OutputType);
return Activator.CreateInstance(nullableType, castedObject);
};
}
public INitroxFaker[] GetSubFakers() => new[] { GetOrCreateFaker(OutputType) };
public object GenerateUnsafe(HashSet<Type> typeTree) => generateAction.Invoke(typeTree);
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using NitroxModel.DataStructures.Util;
namespace Nitrox.Test.Helper.Faker;
public class NitroxOptionalFaker : NitroxFaker, INitroxFaker
{
private readonly Func<HashSet<Type>, object> generateAction;
public NitroxOptionalFaker(Type type)
{
OutputType = type.GenericTypeArguments[0];
generateAction = (typeTree) =>
{
MethodInfo castMethod = CastMethodBase.MakeGenericMethod(OutputType);
object castedObject = castMethod.Invoke(null, new[] { GetOrCreateFaker(OutputType).GenerateUnsafe(typeTree) });
Type optionalType = typeof(Optional<>).MakeGenericType(OutputType);
return Activator.CreateInstance(optionalType, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.CreateInstance, null, new[] { castedObject }, null);
};
}
public INitroxFaker[] GetSubFakers() => new[] { GetOrCreateFaker(OutputType) };
public object GenerateUnsafe(HashSet<Type> typeTree) => generateAction.Invoke(typeTree);
}