Files
Nitrox/NitroxModel/Packets/Packet.cs
2025-07-06 00:23:46 +02:00

145 lines
5.7 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using BinaryPack;
using BinaryPack.Attributes;
using NitroxModel.Networking;
namespace NitroxModel.Packets
{
[Serializable]
public abstract class Packet
{
private static readonly Dictionary<Type, PropertyInfo[]> cachedPropertiesByType = new();
private static readonly StringBuilder toStringBuilder = new();
private static readonly object lockObject = new();
public static void InitSerializer()
{
static IEnumerable<Type> FindTypesInModelAssemblies()
{
return AppDomain.CurrentDomain.GetAssemblies()
.Where(assembly => new[] { nameof(NitroxModel), "NitroxModel-Subnautica" }
.Contains(assembly.GetName().Name))
.SelectMany(assembly =>
{
try
{
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
return e.Types.Where(t => t != null);
}
});
}
static IEnumerable<Type> FindUnionBaseTypes() => FindTypesInModelAssemblies()
.Where(t => t.IsAbstract && !t.IsSealed && (!t.BaseType?.IsAbstract ?? true) && !t.ContainsGenericParameters);
lock (lockObject)
{
foreach (Type type in FindUnionBaseTypes())
{
BinaryConverter.RegisterUnion(type, FindTypesInModelAssemblies()
.Where(t => type.IsAssignableFrom(t) && !t.IsAbstract && !t.IsInterface)
.OrderByDescending(t =>
{
Type current = t;
int levels = 0;
while (current != type && current != null)
{
current = current.BaseType;
levels++;
}
return levels;
})
.ThenBy(t => t.FullName, StringComparer.Ordinal)
.ToArray());
}
// This will initialize the processor for Wrapper which will initialize all the others
_ = BinaryConverter.Serialize(new Wrapper());
}
}
[IgnoredMember]
public NitroxDeliveryMethod.DeliveryMethod DeliveryMethod { get; protected set; } = NitroxDeliveryMethod.DeliveryMethod.RELIABLE_ORDERED;
[IgnoredMember]
public UdpChannelId UdpChannel { get; protected set; } = UdpChannelId.DEFAULT;
public enum UdpChannelId : byte
{
DEFAULT = 0,
MOVEMENTS = 1,
}
public byte[] Serialize()
{
return BinaryConverter.Serialize(new Wrapper(this));
}
public static Packet Deserialize(byte[] data)
{
return BinaryConverter.Deserialize<Wrapper>(data).Packet;
}
public override string ToString()
{
Type packetType = GetType();
if (!cachedPropertiesByType.TryGetValue(packetType, out PropertyInfo[] properties))
{
properties = packetType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.Name is not nameof(DeliveryMethod) and not nameof(UdpChannel)).ToArray();
cachedPropertiesByType.Add(packetType, properties);
}
toStringBuilder.Clear();
toStringBuilder.Append($"[{packetType.Name}: ");
foreach (PropertyInfo property in properties)
{
object propertyValue = property.GetValue(this);
if (propertyValue is IList propertyList)
{
toStringBuilder.Append($"{property.Name}: {propertyList.Count}, ");
}
else
{
toStringBuilder.Append($"{property.Name}: {propertyValue}, ");
}
}
toStringBuilder.Remove(toStringBuilder.Length - 2, 2);
toStringBuilder.Append(']');
return toStringBuilder.ToString();
}
/// <summary>
/// Wrapper which is used to serialize packets in BinaryPack.
/// We cannot serialize Packets directly because
/// <p>
/// 1) We will not know what type to deserialize to and
/// 2) The root object must have a callable constructor so it can't be abstract
/// </p>
/// This type solves both problems and only adds a single byte to the data.
/// </summary>
public readonly struct Wrapper
{
public Packet Packet { get; init; } = null;
public Wrapper(Packet packet)
{
Packet = packet;
}
}
}
}