diff --git a/Source/Client/Debug/DebugActions.cs b/Source/Client/Debug/DebugActions.cs index 8459db96..b42d95f3 100644 --- a/Source/Client/Debug/DebugActions.cs +++ b/Source/Client/Debug/DebugActions.cs @@ -134,17 +134,18 @@ public static void SaveGame() public static void DumpSyncTypes() { var dict = new Dictionary() { - {"ThingComp", RwImplSerialization.thingCompTypes}, - {"AbilityComp", RwImplSerialization.abilityCompTypes}, {"Designator", RwImplSerialization.designatorTypes}, - {"WorldObjectComp", RwImplSerialization.worldObjectCompTypes}, - {"HediffComp", RwImplSerialization.hediffCompTypes}, {"IStoreSettingsParent", RwImplSerialization.storageParents}, {"IPlantToGrowSettable", RwImplSerialization.plantToGrowSettables}, - {"GameComponent", RwImplSerialization.gameCompTypes}, - {"WorldComponent", RwImplSerialization.worldCompTypes}, - {"MapComponent", RwImplSerialization.mapCompTypes}, + {"ThingComp", CompSerialization.thingCompTypes}, + {"AbilityComp", CompSerialization.abilityCompTypes}, + {"WorldObjectComp", CompSerialization.worldObjectCompTypes}, + {"HediffComp", CompSerialization.hediffCompTypes}, + + {"GameComponent", CompSerialization.gameCompTypes}, + {"WorldComponent", CompSerialization.worldCompTypes}, + {"MapComponent", CompSerialization.mapCompTypes}, }; foreach(var kv in dict) { diff --git a/Source/Client/EarlyInit.cs b/Source/Client/EarlyInit.cs index 4dddeb97..0c23bcd1 100644 --- a/Source/Client/EarlyInit.cs +++ b/Source/Client/EarlyInit.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reflection; using HarmonyLib; using Multiplayer.Client.Patches; @@ -55,10 +56,12 @@ internal static void EarlyPatches(Harmony harmony) internal static void InitSync() { - using (DeepProfilerWrapper.Section("Multiplayer SyncSerialization.Init")) - SyncSerialization.Init(); + MpReflection.allAssembliesHook = RwAllAssemblies; - using (DeepProfilerWrapper.Section("Multiplayer SyncGame")) + using (DeepProfilerWrapper.Section("Multiplayer RwSerialization.Init")) + RwSerialization.Init(); + + using (DeepProfilerWrapper.Section("Multiplayer SyncGame.Init")) SyncGame.Init(); using (DeepProfilerWrapper.Section("Multiplayer Sync register attributes")) @@ -68,6 +71,18 @@ internal static void InitSync() Sync.ValidateAll(); } + private static IEnumerable RwAllAssemblies() + { + yield return Assembly.GetAssembly(typeof(Game)); + + foreach (ModContentPack mod in LoadedModManager.RunningMods) + foreach (Assembly assembly in mod.assemblies.loadedAssemblies) + yield return assembly; + + if (Assembly.GetEntryAssembly() != null) + yield return Assembly.GetEntryAssembly(); + } + internal static void LatePatches() { if (MpVersion.IsDebug) diff --git a/Source/Client/Multiplayer.csproj b/Source/Client/Multiplayer.csproj index a964b3a9..e0e40849 100644 --- a/Source/Client/Multiplayer.csproj +++ b/Source/Client/Multiplayer.csproj @@ -3,7 +3,7 @@ net472 true - 11 + 12 false bin false diff --git a/Source/Client/MultiplayerData.cs b/Source/Client/MultiplayerData.cs index 4ac9cd54..6c9add4a 100644 --- a/Source/Client/MultiplayerData.cs +++ b/Source/Client/MultiplayerData.cs @@ -120,20 +120,20 @@ internal static void CollectDefInfos() int TypeHash(Type type) => GenText.StableStringHash(type.FullName); - dict["ThingComp"] = GetDefInfo(RwImplSerialization.thingCompTypes, TypeHash); - dict["AbilityComp"] = GetDefInfo(RwImplSerialization.abilityCompTypes, TypeHash); - dict["Designator"] = GetDefInfo(RwImplSerialization.designatorTypes, TypeHash); - dict["WorldObjectComp"] = GetDefInfo(RwImplSerialization.worldObjectCompTypes, TypeHash); - dict["HediffComp"] = GetDefInfo(RwImplSerialization.hediffCompTypes, TypeHash); + dict["ThingComp"] = GetDefInfo(CompSerialization.thingCompTypes, TypeHash); + dict["AbilityComp"] = GetDefInfo(CompSerialization.abilityCompTypes, TypeHash); + dict["WorldObjectComp"] = GetDefInfo(CompSerialization.worldObjectCompTypes, TypeHash); + dict["HediffComp"] = GetDefInfo(CompSerialization.hediffCompTypes, TypeHash); dict["IStoreSettingsParent"] = GetDefInfo(RwImplSerialization.storageParents, TypeHash); dict["IPlantToGrowSettable"] = GetDefInfo(RwImplSerialization.plantToGrowSettables, TypeHash); + dict["Designator"] = GetDefInfo(RwImplSerialization.designatorTypes, TypeHash); dict["DefTypes"] = GetDefInfo(DefSerialization.DefTypes, TypeHash); - dict["GameComponent"] = GetDefInfo(RwImplSerialization.gameCompTypes, TypeHash); - dict["WorldComponent"] = GetDefInfo(RwImplSerialization.worldCompTypes, TypeHash); - dict["MapComponent"] = GetDefInfo(RwImplSerialization.mapCompTypes, TypeHash); - dict["ISyncSimple"] = GetDefInfo(ImplSerialization.syncSimples, TypeHash); - dict["ISession"] = GetDefInfo(ImplSerialization.sessions, TypeHash); + dict["GameComponent"] = GetDefInfo(CompSerialization.gameCompTypes, TypeHash); + dict["WorldComponent"] = GetDefInfo(CompSerialization.worldCompTypes, TypeHash); + dict["MapComponent"] = GetDefInfo(CompSerialization.mapCompTypes, TypeHash); + dict["ISyncSimple"] = GetDefInfo(ApiSerialization.syncSimples, TypeHash); + dict["ISession"] = GetDefInfo(ApiSerialization.sessions, TypeHash); dict["PawnBio"] = GetDefInfo(SolidBioDatabase.allBios, b => b.name.GetHashCode()); diff --git a/Source/Client/Persistent/RitualData.cs b/Source/Client/Persistent/RitualData.cs index a982b387..2827e942 100644 --- a/Source/Client/Persistent/RitualData.cs +++ b/Source/Client/Persistent/RitualData.cs @@ -28,9 +28,9 @@ public void Sync(SyncWorker sync) sync.Bind(ref extraInfos); if (sync is WritingSyncWorker writer1) - DelegateSerialization.WriteDelegate(writer1.writer, action); + DelegateSerialization.WriteDelegate(writer1.Writer, action); else if (sync is ReadingSyncWorker reader) - action = (ActionCallback)DelegateSerialization.ReadDelegate(reader.reader); + action = (ActionCallback)DelegateSerialization.ReadDelegate(reader.Reader); sync.Bind(ref ritualLabel); sync.Bind(ref confirmText); diff --git a/Source/Client/Persistent/SessionManager.cs b/Source/Client/Persistent/SessionManager.cs index bf0d1100..04485599 100644 --- a/Source/Client/Persistent/SessionManager.cs +++ b/Source/Client/Persistent/SessionManager.cs @@ -156,7 +156,7 @@ public void WriteSessionData(ByteWriter data) foreach (var session in semiPersistentSessions) { - data.WriteUShort((ushort)ImplSerialization.sessions.FindIndex(session.GetType())); + data.WriteUShort((ushort)ApiSerialization.sessions.FindIndex(session.GetType())); data.WriteInt32(session.SessionId); try @@ -181,13 +181,13 @@ public void ReadSessionData(ByteReader data) ushort typeIndex = data.ReadUShort(); int sessionId = data.ReadInt32(); - if (typeIndex >= ImplSerialization.sessions.Length) + if (typeIndex >= ApiSerialization.sessions.Length) { - Log.Error($"Received data for ISession type with index out of range: {typeIndex}, session types count: {ImplSerialization.sessions.Length}"); + Log.Error($"Received data for ISession type with index out of range: {typeIndex}, session types count: {ApiSerialization.sessions.Length}"); continue; } - var objType = ImplSerialization.sessions[typeIndex]; + var objType = ApiSerialization.sessions[typeIndex]; try { diff --git a/Source/Client/Syncing/ImplSerialization.cs b/Source/Client/Syncing/ApiSerialization.cs similarity index 90% rename from Source/Client/Syncing/ImplSerialization.cs rename to Source/Client/Syncing/ApiSerialization.cs index 2109eb54..5de092eb 100644 --- a/Source/Client/Syncing/ImplSerialization.cs +++ b/Source/Client/Syncing/ApiSerialization.cs @@ -4,7 +4,7 @@ namespace Multiplayer.Client; -public static class ImplSerialization +public static class ApiSerialization { public static Type[] syncSimples; public static Type[] sessions; diff --git a/Source/Client/Syncing/CompSerialization.cs b/Source/Client/Syncing/CompSerialization.cs new file mode 100644 index 00000000..8e6bac68 --- /dev/null +++ b/Source/Client/Syncing/CompSerialization.cs @@ -0,0 +1,31 @@ +using System; +using Multiplayer.Client.Util; +using RimWorld; +using RimWorld.Planet; +using Verse; + +namespace Multiplayer.Client; + +public static class CompSerialization +{ + public static Type[] gameCompTypes; + public static Type[] worldCompTypes; + public static Type[] mapCompTypes; + + public static Type[] thingCompTypes; + public static Type[] hediffCompTypes; + public static Type[] abilityCompTypes; + public static Type[] worldObjectCompTypes; + + public static void Init() + { + thingCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(ThingComp)); + hediffCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(HediffComp)); + abilityCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(AbilityComp)); + worldObjectCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(WorldObjectComp)); + + gameCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(GameComponent)); + worldCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(WorldComponent)); + mapCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(MapComponent)); + } +} diff --git a/Source/Client/Syncing/Dict/SyncDict.cs b/Source/Client/Syncing/Dict/SyncDict.cs index 1baebe07..0f235fba 100644 --- a/Source/Client/Syncing/Dict/SyncDict.cs +++ b/Source/Client/Syncing/Dict/SyncDict.cs @@ -1,17 +1,18 @@ -namespace Multiplayer.Client +namespace Multiplayer.Client; + +public static class SyncDict { - public static class SyncDict + internal static SyncWorkerDictionaryTree syncWorkers; + + public static void Init() { - internal static SyncWorkerDictionaryTree syncWorkers; + syncWorkers = SyncWorkerDictionaryTree.Merge( + SyncDictMisc.syncWorkers, + SyncDictRimWorld.syncWorkers, + SyncDictDlc.syncWorkers, + SyncDictMultiplayer.syncWorkers + ); - public static void Init() - { - syncWorkers = SyncWorkerDictionaryTree.Merge( - SyncDictMisc.syncWorkers, - SyncDictRimWorld.syncWorkers, - SyncDictDlc.syncWorkers, - SyncDictMultiplayer.syncWorkers - ); - } + SyncSerialization.syncTree = syncWorkers; } } diff --git a/Source/Client/Syncing/Dict/SyncDictDlc.cs b/Source/Client/Syncing/Dict/SyncDictDlc.cs index b3eb6011..7bc237d9 100644 --- a/Source/Client/Syncing/Dict/SyncDictDlc.cs +++ b/Source/Client/Syncing/Dict/SyncDictDlc.cs @@ -13,7 +13,7 @@ namespace Multiplayer.Client { public static class SyncDictDlc { - internal static SyncWorkerDictionaryTree syncWorkers = new SyncWorkerDictionaryTree() + internal static SyncWorkerDictionaryTree syncWorkers = new() { #region Royalty { diff --git a/Source/Client/Syncing/Dict/SyncDictFast.cs b/Source/Client/Syncing/Dict/SyncDictFast.cs deleted file mode 100644 index 8c3b2abf..00000000 --- a/Source/Client/Syncing/Dict/SyncDictFast.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Multiplayer.API; -using Multiplayer.Common; -using UnityEngine; -using Verse; - -namespace Multiplayer.Client -{ - public static class SyncDictFast - { - internal static SyncWorkerDictionary syncWorkers = new SyncWorkerDictionary() - { - // missing decimal and char, good? - #region Built-in - - { (SyncWorker sync, ref bool b ) => sync.Bind(ref b) }, - { (SyncWorker sync, ref byte b ) => sync.Bind(ref b) }, - { (SyncWorker sync, ref sbyte b ) => sync.Bind(ref b) }, - { (SyncWorker sync, ref double d ) => sync.Bind(ref d) }, - { (SyncWorker sync, ref float f ) => sync.Bind(ref f) }, - { (SyncWorker sync, ref int i ) => sync.Bind(ref i) }, - { (SyncWorker sync, ref uint i ) => sync.Bind(ref i) }, - { (SyncWorker sync, ref long l ) => sync.Bind(ref l) }, - { (SyncWorker sync, ref ulong l ) => sync.Bind(ref l) }, - { (SyncWorker sync, ref short s ) => sync.Bind(ref s) }, - { (SyncWorker sync, ref ushort s ) => sync.Bind(ref s) }, - { (SyncWorker sync, ref string t ) => sync.Bind(ref t) }, - - #endregion - - #region Structs - { - (ByteWriter data, Rot4 rot) => data.WriteByte(rot.AsByte), - (ByteReader data) => new Rot4(data.ReadByte()) - }, - { - (ByteWriter data, IntVec3 vec) => { - if (vec.y < 0) { - data.WriteShort(-1); - } - else { - data.WriteShort((short)vec.y); - data.WriteShort((short)vec.x); - data.WriteShort((short)vec.z); - } - }, - (ByteReader data) => { - short y = data.ReadShort(); - if (y < 0) - return IntVec3.Invalid; - - short x = data.ReadShort(); - short z = data.ReadShort(); - - return new IntVec3(x, y, z); - } - }, - { - (SyncWorker sync, ref Vector2 vec) => { - sync.Bind(ref vec.x); - sync.Bind(ref vec.y); - } - }, - { - (SyncWorker sync, ref Vector3 vec) => { - sync.Bind(ref vec.x); - sync.Bind(ref vec.y); - sync.Bind(ref vec.z); - } - }, - #endregion - - #region Templates - /* - { (SyncWorker sync, ref object obj) => { } }, - { - (ByteWriter data, object obj) => { - - }, - (ByteReader data) => { - return null; - } - }, - */ - #endregion - }; - } -} diff --git a/Source/Client/Syncing/Dict/SyncDictMisc.cs b/Source/Client/Syncing/Dict/SyncDictMisc.cs index 956c45df..ef02dc04 100644 --- a/Source/Client/Syncing/Dict/SyncDictMisc.cs +++ b/Source/Client/Syncing/Dict/SyncDictMisc.cs @@ -114,6 +114,48 @@ public static class SyncDictMisc } }, #endregion + + #region Structs + { + (ByteWriter data, Rot4 rot) => data.WriteByte(rot.AsByte), + (ByteReader data) => new Rot4(data.ReadByte()) + }, + { + (ByteWriter data, IntVec3 vec) => { + if (vec.y < 0) { + data.WriteShort(-1); + } + else { + data.WriteShort((short)vec.y); + data.WriteShort((short)vec.x); + data.WriteShort((short)vec.z); + } + }, + (ByteReader data) => { + short y = data.ReadShort(); + if (y < 0) + return IntVec3.Invalid; + + short x = data.ReadShort(); + short z = data.ReadShort(); + + return new IntVec3(x, y, z); + } + }, + { + (SyncWorker sync, ref Vector2 vec) => { + sync.Bind(ref vec.x); + sync.Bind(ref vec.y); + } + }, + { + (SyncWorker sync, ref Vector3 vec) => { + sync.Bind(ref vec.x); + sync.Bind(ref vec.y); + sync.Bind(ref vec.z); + } + }, + #endregion }; } } diff --git a/Source/Client/Syncing/Dict/SyncDictRimWorld.cs b/Source/Client/Syncing/Dict/SyncDictRimWorld.cs index 75c234b7..f2ebe5e7 100644 --- a/Source/Client/Syncing/Dict/SyncDictRimWorld.cs +++ b/Source/Client/Syncing/Dict/SyncDictRimWorld.cs @@ -11,6 +11,7 @@ using Verse.AI.Group; using static Multiplayer.Client.SyncSerialization; using static Multiplayer.Client.RwImplSerialization; +using static Multiplayer.Client.CompSerialization; // ReSharper disable RedundantLambdaParameterType namespace Multiplayer.Client @@ -701,16 +702,16 @@ public static class SyncDictRimWorld holder = thingComp; else if (ThingOwnerUtility.GetFirstSpawnedParentThing(thing) is { } parentThing) holder = parentThing; - else if (GetAnyParent(thing) is { } worldObj) + else if (RwSerialization.GetAnyParent(thing) is { } worldObj) holder = worldObj; - else if (GetAnyParent(thing) is { } worldObjComp) + else if (RwSerialization.GetAnyParent(thing) is { } worldObjComp) holder = worldObjComp; GetImpl(holder, supportedThingHolders, out Type implType, out int index); if (index == -1) { data.WriteByte(byte.MaxValue); - Log.Error($"Thing {ThingHolderString(thing)} is inaccessible"); + Log.Error($"Thing {RwSerialization.ThingHolderString(thing)} is inaccessible"); return; } @@ -753,7 +754,7 @@ public static class SyncDictRimWorld } return ThingsById.thingsById.GetValueSafe(thingId); - } + }, true }, { (SyncWorker data, ref ThingComp comp) => { @@ -1118,7 +1119,6 @@ public static class SyncDictRimWorld }, (ByteReader data) => (IStorageGroupMember)ReadSync(data) }, - #endregion #region Storage diff --git a/Source/Client/Syncing/Game/RwImplSerialization.cs b/Source/Client/Syncing/RwImplSerialization.cs similarity index 72% rename from Source/Client/Syncing/Game/RwImplSerialization.cs rename to Source/Client/Syncing/RwImplSerialization.cs index 7016ab11..64e4e40e 100644 --- a/Source/Client/Syncing/Game/RwImplSerialization.cs +++ b/Source/Client/Syncing/RwImplSerialization.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Multiplayer.API; using Multiplayer.Client.Util; using Multiplayer.Common; using RimWorld; @@ -12,16 +13,7 @@ public static class RwImplSerialization { public static Type[] storageParents; public static Type[] plantToGrowSettables; - - public static Type[] thingCompTypes; - public static Type[] hediffCompTypes; - public static Type[] abilityCompTypes; public static Type[] designatorTypes; - public static Type[] worldObjectCompTypes; - - public static Type[] gameCompTypes; - public static Type[] worldCompTypes; - public static Type[] mapCompTypes; internal static Type[] supportedThingHolders = { @@ -47,16 +39,7 @@ public static void Init() { storageParents = TypeUtil.AllImplementationsOrdered(typeof(IStoreSettingsParent)); plantToGrowSettables = TypeUtil.AllImplementationsOrdered(typeof(IPlantToGrowSettable)); - - thingCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(ThingComp)); - hediffCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(HediffComp)); - abilityCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(AbilityComp)); designatorTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(Designator)); - worldObjectCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(WorldObjectComp)); - - gameCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(GameComponent)); - worldCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(WorldComponent)); - mapCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(MapComponent)); } internal static T ReadWithImpl(ByteReader data, IList impls) where T : class diff --git a/Source/Client/Syncing/RwSerialization.cs b/Source/Client/Syncing/RwSerialization.cs new file mode 100644 index 00000000..c951ed95 --- /dev/null +++ b/Source/Client/Syncing/RwSerialization.cs @@ -0,0 +1,225 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using HarmonyLib; +using Multiplayer.API; +using Multiplayer.Common; +using Verse; + +namespace Multiplayer.Client; + +public static class RwSerialization +{ + public static void Init() + { + // CanHandle hooks + SyncSerialization.canHandleHooks.Add(syncType => + { + var type = syncType.type; + if (type.IsGenericType && type.GetGenericTypeDefinition() is { } gtd) + if (gtd == typeof(Pair<,>)) + return SyncSerialization.CanHandleGenericArgs(type); + + if (syncType.expose) + return typeof(IExposable).IsAssignableFrom(type); + if (type == typeof(ISyncSimple)) + return true; + if (typeof(ISyncSimple).IsAssignableFrom(type)) + return ApiSerialization.syncSimples. + Where(t => type.IsAssignableFrom(t)). + SelectMany(AccessTools.GetDeclaredFields). + All(f => SyncSerialization.CanHandle(f.FieldType)); + if (typeof(Def).IsAssignableFrom(type)) + return true; + if (typeof(Designator).IsAssignableFrom(type)) + return true; + + return SyncDict.syncWorkers.TryGetValue(type, out _); + }); + + // Verse.Pair<,> serialization + SyncSerialization.AddSerializationHook( + syncType => syncType.type.IsGenericType && syncType.type.GetGenericTypeDefinition() is { } gtd && gtd == typeof(Pair<,>), + (data, syncType) => + { + Type[] arguments = syncType.type.GetGenericArguments(); + object[] parameters = + { + SyncSerialization.ReadSyncObject(data, arguments[0]), + SyncSerialization.ReadSyncObject(data, arguments[1]), + }; + return syncType.type.GetConstructors().First().Invoke(parameters); + }, + (data, obj, syncType) => + { + var type = syncType.type; + Type[] arguments = type.GetGenericArguments(); + + SyncSerialization.WriteSyncObject(data, AccessTools.DeclaredField(type, "first").GetValue(obj), arguments[0]); + SyncSerialization.WriteSyncObject(data, AccessTools.DeclaredField(type, "second").GetValue(obj), arguments[1]); + } + ); + + // IExposable serialization + SyncSerialization.AddSerializationHook( + syncType => syncType.expose, + (data, syncType) => + { + if (!typeof(IExposable).IsAssignableFrom(syncType.type)) + throw new SerializationException($"Type {syncType.type} can't be exposed because it isn't IExposable"); + + byte[] exposableData = data.ReadPrefixedBytes(); + return ExposableSerialization.ReadExposable(syncType.type, exposableData); + }, + (data, obj, syncType) => + { + if (!typeof(IExposable).IsAssignableFrom(syncType.type)) + throw new SerializationException($"Type {syncType} can't be exposed because it isn't IExposable"); + + var log = (data as LoggingByteWriter)?.Log; + IExposable exposable = obj as IExposable; + byte[] xmlData = ScribeUtil.WriteExposable(exposable); + LogXML(log, xmlData); + data.WritePrefixedBytes(xmlData); + } + ); + + // ISyncSimple serialization + // todo null handling for ISyncSimple? + SyncSerialization.AddSerializationHook( + syncType => typeof(ISyncSimple).IsAssignableFrom(syncType.type), + (data, _) => + { + ushort typeIndex = data.ReadUShort(); + var objType = ApiSerialization.syncSimples[typeIndex]; + var obj = MpUtil.NewObjectNoCtor(objType); + foreach (var field in AccessTools.GetDeclaredFields(objType)) + field.SetValue(obj, SyncSerialization.ReadSyncObject(data, field.FieldType)); + return obj; + }, + (data, obj, _) => + { + data.WriteUShort((ushort)ApiSerialization.syncSimples.FindIndex(obj!.GetType())); + foreach (var field in AccessTools.GetDeclaredFields(obj.GetType())) + SyncSerialization.WriteSyncObject(data, field.GetValue(obj), field.FieldType); + } + ); + + // Def serialization + SyncSerialization.AddSerializationHook( + syncType => typeof(Def).IsAssignableFrom(syncType.type), + (data, _) => + { + ushort defTypeIndex = data.ReadUShort(); + if (defTypeIndex == ushort.MaxValue) + return null; + + ushort shortHash = data.ReadUShort(); + + var defType = DefSerialization.DefTypes[defTypeIndex]; + var def = DefSerialization.GetDef(defType, shortHash); + + if (def == null) + throw new SerializationException($"Couldn't find {defType} with short hash {shortHash}"); + + return def; + }, + (data, obj, _) => + { + if (obj is not Def def) + { + data.WriteUShort(ushort.MaxValue); + return; + } + + var defTypeIndex = Array.IndexOf(DefSerialization.DefTypes, def.GetType()); + if (defTypeIndex == -1) + throw new SerializationException($"Unknown def type {def.GetType()}"); + + data.WriteUShort((ushort)defTypeIndex); + data.WriteUShort(def.shortHash); + } + ); + + // Designator type changer + // todo handle null? + SyncSerialization.AddTypeChanger( + syncType => typeof(Designator).IsAssignableFrom(syncType.type), + (data, _) => + { + ushort desId = SyncSerialization.ReadSync(data); + return RwImplSerialization.designatorTypes[desId]; + }, + (data, obj, _) => + { + data.WriteUShort((ushort) Array.IndexOf(RwImplSerialization.designatorTypes, obj!.GetType())); + } + ); + + RwImplSerialization.Init(); + CompSerialization.Init(); + ApiSerialization.Init(); + DefSerialization.Init(); + + RwTypeHelper.Init(); + SyncWorkerTypeHelper.GetType = RwTypeHelper.GetType; + SyncWorkerTypeHelper.GetTypeIndex = RwTypeHelper.GetTypeIndex; + } + + internal static T GetAnyParent(Thing thing) where T : class + { + if (thing is T t) + return t; + + for (var parentHolder = thing.ParentHolder; parentHolder != null; parentHolder = parentHolder.ParentHolder) + if (parentHolder is T t2) + return t2; + + return null; + } + + internal static string ThingHolderString(Thing thing) + { + StringBuilder builder = new StringBuilder(thing.ToString()); + + for (var parentHolder = thing.ParentHolder; parentHolder != null; parentHolder = parentHolder.ParentHolder) + { + builder.Insert(0, "=>"); + builder.Insert(0, parentHolder.ToString()); + } + + return builder.ToString(); + } + + private static void LogXML(SyncLogger log, byte[] xmlData) + { + if (log == null) return; + + var reader = XmlReader.Create(new MemoryStream(xmlData)); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + string name = reader.Name; + if (reader.GetAttribute("IsNull") == "True") + name += " (IsNull)"; + + if (reader.IsEmptyElement) + log.Node(name); + else + log.Enter(name); + } + else if (reader.NodeType == XmlNodeType.EndElement) + { + log.Exit(); + } + else if (reader.NodeType == XmlNodeType.Text) + { + log.AppendToCurrentName($": {reader.Value}"); + } + } + } +} diff --git a/Source/Client/Syncing/RwTypeHelper.cs b/Source/Client/Syncing/RwTypeHelper.cs new file mode 100644 index 00000000..990ebac4 --- /dev/null +++ b/Source/Client/Syncing/RwTypeHelper.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Multiplayer.Common; +using RimWorld; +using RimWorld.Planet; +using Verse; + +namespace Multiplayer.Client; + +internal static class RwTypeHelper +{ + private static Dictionary cache = new(); + + public static void Init() + { + cache[typeof(IStoreSettingsParent)] = RwImplSerialization.storageParents; + cache[typeof(IPlantToGrowSettable)] = RwImplSerialization.plantToGrowSettables; + cache[typeof(Designator)] = RwImplSerialization.designatorTypes; + + cache[typeof(ThingComp)] = CompSerialization.thingCompTypes; + cache[typeof(AbilityComp)] = CompSerialization.abilityCompTypes; + cache[typeof(WorldObjectComp)] = CompSerialization.worldObjectCompTypes; + cache[typeof(HediffComp)] = CompSerialization.hediffCompTypes; + + cache[typeof(GameComponent)] = CompSerialization.gameCompTypes; + cache[typeof(WorldComponent)] = CompSerialization.worldCompTypes; + cache[typeof(MapComponent)] = CompSerialization.mapCompTypes; + } + + internal static void FlushCache() + { + cache.Clear(); + } + + internal static Type[] GenTypeCache(Type type) + { + var types = GenTypes.AllTypes + .Where(t => t != type && type.IsAssignableFrom(t)) + .OrderBy(t => t.IsInterface) + .ToArray(); + + cache[type] = types; + return types; + } + + internal static Type GetType(ushort index, Type baseType) + { + if (!cache.TryGetValue(baseType, out Type[] types)) + types = GenTypeCache(baseType); + + return types[index]; + } + + internal static ushort GetTypeIndex(Type type, Type baseType) + { + if (!cache.TryGetValue(baseType, out Type[] types)) + types = GenTypeCache(baseType); + + return (ushort) types.FindIndex(type); + } +} diff --git a/Source/Client/Syncing/Sync.cs b/Source/Client/Syncing/Sync.cs index f6a1c631..e62706ed 100644 --- a/Source/Client/Syncing/Sync.cs +++ b/Source/Client/Syncing/Sync.cs @@ -141,9 +141,9 @@ internal static void RegisterAllAttributes(Assembly asm) RegisterSyncMethod(method, sma); else if (method.TryGetAttribute(out SyncWorkerAttribute swa)) RegisterSyncWorker(method, isImplicit: swa.isImplicit, shouldConstruct: swa.shouldConstruct); - else if (method.TryGetAttribute(out SyncDialogNodeTreeAttribute sdnta)) + else if (method.TryGetAttribute(out SyncDialogNodeTreeAttribute _)) RegisterSyncDialogNodeTree(method); - else if (method.TryGetAttribute(out PauseLockAttribute pea)) + else if (method.TryGetAttribute(out PauseLockAttribute _)) RegisterPauseLock(method); } catch (Exception e) @@ -176,7 +176,7 @@ private static void RegisterSyncMethod(MethodInfo method, SyncMethodAttribute at if (exposeParameters != null && exposeParameters.Any(p => p < 0 || p >= paramNum)) { - Log.Error($"Failed to register a method: One or more indexes of parameters to expose in SyncMethod attribute applied to {method.DeclaringType.FullName}::{method} is invalid."); + Log.Error($"Failed to register a method: One or more indexes of parameters to expose in SyncMethod attribute applied to {method.DeclaringType?.FullName}::{method} is invalid."); return; } @@ -206,7 +206,7 @@ private static void RegisterSyncMethod(MethodInfo method, SyncMethodAttribute at sm.ExposeParameter(exposeParameters[i]); } } catch (Exception exc) { - Log.Error($"An exception occurred while exposing parameter {i} ({method.GetParameters()[i]}) for method {method.DeclaringType.FullName}::{method}: {exc}"); + Log.Error($"An exception occurred while exposing parameter {i} ({method.GetParameters()[i]}) for method {method.DeclaringType?.FullName}::{method}: {exc}"); } } } @@ -275,27 +275,27 @@ public static void RegisterSyncWorker(MethodInfo method, Type targetType = null, Type[] parameters = method.GetParameters().Select(p => p.ParameterType).ToArray(); if (!method.IsStatic) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker method has to be static."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker method has to be static."); return; } if (parameters.Length != 2) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker method has an invalid number of parameters."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker method has an invalid number of parameters."); return; } if (parameters[0] != typeof(SyncWorker)) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker method has an invalid first parameter (got {parameters[0]}, expected ISyncWorker)."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker method has an invalid first parameter (got {parameters[0]}, expected ISyncWorker)."); return; } if (targetType != null && parameters[1].IsAssignableFrom(targetType)) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker method has an invalid second parameter (got {parameters[1]}, expected {targetType} or assignable)."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker method has an invalid second parameter (got {parameters[1]}, expected {targetType} or assignable)."); return; } if (!parameters[1].IsByRef) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker method has an invalid second parameter, should be a ref."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker method has an invalid second parameter, should be a ref."); return; } @@ -303,50 +303,46 @@ public static void RegisterSyncWorker(MethodInfo method, Type targetType = null, if (isImplicit) { if (method.ReturnType != typeof(bool)) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker set as implicit (or the argument type is an interface) requires bool type as a return value."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker set as implicit (or the argument type is an interface) requires bool type as a return value."); return; } } else if (method.ReturnType != typeof(void)) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker set as explicit should have void as a return value."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker set as explicit should have void as a return value."); return; } SyncWorkerEntry entry = SyncDict.syncWorkers.GetOrAddEntry(type, isImplicit: isImplicit, shouldConstruct: shouldConstruct); - entry.Add(method); if (!(isImplicit || type.IsInterface) && entry.SyncWorkerCount > 1) { - Log.Warning($"Warning in {method.DeclaringType.FullName}::{method}: type {type} has already registered an explicit SyncWorker, the code in this method may be not used."); + Log.Warning($"Warning in {method.DeclaringType?.FullName}::{method}: type {type} has already registered an explicit SyncWorker, the code in this method may be not used."); } - Log.Message($"Registered a SyncWorker {method.DeclaringType.FullName}::{method} for type {type} in assembly {method.DeclaringType.Assembly.GetName().Name}"); + Log.Message($"Registered a SyncWorker {method.DeclaringType?.FullName}::{method} for type {type} in assembly {method.DeclaringType?.Assembly.GetName().Name}"); } public static void RegisterSyncWorker(SyncWorkerDelegate syncWorkerDelegate, Type targetType = null, bool isImplicit = false, bool shouldConstruct = false) { MethodInfo method = syncWorkerDelegate.Method; - Type[] parameters = method.GetParameters().Select(p => p.ParameterType).ToArray(); if (targetType != null && parameters[1].IsAssignableFrom(targetType)) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker method has an invalid second parameter (got {parameters[1]}, expected {targetType} or assignable)."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker method has an invalid second parameter (got {parameters[1]}, expected {targetType} or assignable)."); return; } var type = targetType ?? typeof(T); - SyncWorkerEntry entry = SyncDict.syncWorkers.GetOrAddEntry(type, isImplicit: isImplicit, shouldConstruct: shouldConstruct); - entry.Add(syncWorkerDelegate); if (!(isImplicit || type.IsInterface) && entry.SyncWorkerCount > 1) { - Log.Warning($"Warning in {method.DeclaringType.FullName}::{method}: type {type} has already registered an explicit SyncWorker, the code in this method may be not used."); + Log.Warning($"Warning in {method.DeclaringType?.FullName}::{method}: type {type} has already registered an explicit SyncWorker, the code in this method may be not used."); } } public static void RegisterSyncDialogNodeTree(Type type, string methodOrPropertyName, SyncType[] argTypes = null) { - MethodInfo method = AccessTools.Method(type, methodOrPropertyName, argTypes != null ? argTypes.Select(t => t.type).ToArray() : null); + MethodInfo method = AccessTools.Method(type, methodOrPropertyName, argTypes?.Select(t => t.type).ToArray()); if (method == null) { diff --git a/Source/Client/Syncing/Worker/SyncWorkers.cs b/Source/Client/Syncing/Worker/SyncWorkers.cs deleted file mode 100644 index 64602c29..00000000 --- a/Source/Client/Syncing/Worker/SyncWorkers.cs +++ /dev/null @@ -1,473 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; - -using Multiplayer.API; -using Multiplayer.Common; - -using RimWorld; -using RimWorld.Planet; - -using Verse; - -namespace Multiplayer.Client -{ - public class SyncWorkerEntry - { - delegate bool SyncWorkerDelegate(SyncWorker sync, ref object obj); - - public Type type; - public bool shouldConstruct; - private List syncWorkers; - private List subclasses; - private SyncWorkerEntry parent; - - public int SyncWorkerCount => syncWorkers.Count(); - - public SyncWorkerEntry(Type type, bool shouldConstruct = false) - { - this.type = type; - this.syncWorkers = new List(); - this.shouldConstruct = shouldConstruct; - } - - public SyncWorkerEntry(SyncWorkerEntry other) - { - type = other.type; - syncWorkers = other.syncWorkers; - subclasses = other.subclasses; - shouldConstruct = other.shouldConstruct; - } - - public void Add(MethodInfo method) - { - // todo: Find a way to do this without DynDelegate - Add(DynDelegate.DynamicDelegate.Create(method), method.ReturnType == typeof(void)); - } - - public void Add(SyncWorkerDelegate func) - { - Add(new SyncWorkerDelegate((SyncWorker sync, ref object obj) => { - var obj2 = (T) obj; - func(sync, ref obj2); - obj = obj2; - return true; - }), true); - } - - private void Add(SyncWorkerDelegate sync, bool append = true) - { - if (append) - syncWorkers.Add(sync); - else - syncWorkers.Insert(0, sync); - } - - public bool Invoke(SyncWorker worker, ref object obj) - { - if (parent != null) { - parent.Invoke(worker, ref obj); - } - - for (int i = 0; i < syncWorkers.Count; i++) { - if (syncWorkers[i](worker, ref obj)) - return true; - - if (worker is ReadingSyncWorker reader) { - reader.Reset(); - } else if (worker is WritingSyncWorker writer) { - writer.Reset(); - } - } - - return false; - } - - public SyncWorkerEntry Add(SyncWorkerEntry other) - { - SyncWorkerEntry newEntry = Add(other.type, other, other.shouldConstruct); - - newEntry.subclasses = other.subclasses; - - return newEntry; - } - - public SyncWorkerEntry Add(Type type, bool shouldConstruct = false) - { - return Add(type, null, shouldConstruct); - } - - private SyncWorkerEntry Add(Type type, SyncWorkerEntry parent, bool shouldConstruct) - { - if (type == this.type) { - if (shouldConstruct) { - this.shouldConstruct = true; - } - - return this; - } - - if (type.IsAssignableFrom(this.type)) // Is parent - { - SyncWorkerEntry newEntry; - - if (parent != null) { - List ps = parent.subclasses; - newEntry = new SyncWorkerEntry(type, shouldConstruct); - - newEntry.subclasses.Add(this); - - ps[ps.IndexOf(this)] = newEntry; - return newEntry; - } else { - newEntry = new SyncWorkerEntry(this); - - this.type = type; - - this.shouldConstruct = shouldConstruct; - - syncWorkers = new List(); - subclasses = new List() { newEntry }; - return this; - } - - - } - - if (this.type.IsAssignableFrom(type)) // Is child - { - if (subclasses != null) { - for (int i = 0; i < subclasses.Count; i++) { - SyncWorkerEntry res = subclasses[i].Add(type, this, shouldConstruct); - if (res != null) - return res; - } - } else { - subclasses = new List(); - } - - var newEntry = new SyncWorkerEntry(type, shouldConstruct); - newEntry.parent = this; - subclasses.Add(newEntry); - - return newEntry; - } - - return null; - } - - public SyncWorkerEntry GetClosest(Type type) - { - if (this.type.IsAssignableFrom(type)) { - - if (subclasses == null) - return this; - - int len = subclasses.Count; - - if (len == 0) - return this; - - for (int i = 0; i < len; i++) { - SyncWorkerEntry res = subclasses[i].GetClosest(type); - - if (res != null) - return res; - } - - return this; - } - - return null; - } - - internal void PrintStructureInternal(int level, StringBuilder str) - { - str.Append(' ', 4 * level); - str.Append(type.ToString()); - - if (subclasses == null) { - str.AppendLine(); - return; - } - - str.AppendLine(" ┓ "); - - for (int i = 0; i < subclasses.Count; i++) - subclasses[i].PrintStructureInternal(level + 1, str); - } - } - - class SyncWorkerDictionary : IEnumerable - { - protected readonly Dictionary explicitEntries = new Dictionary(); - - public SyncWorkerEntry GetOrAddEntry(Type type, bool shouldConstruct = false) - { - if (explicitEntries.TryGetValue(type, out SyncWorkerEntry explicitEntry)) { - return explicitEntry; - } - - return AddExplicit(type, shouldConstruct); - } - - protected SyncWorkerEntry AddExplicit(Type type, bool shouldConstruct = false) - { - var explicitEntry = new SyncWorkerEntry(type, shouldConstruct); - - explicitEntries.Add(type, explicitEntry); - - return explicitEntry; - } - - internal void Add(SyncWorkerDelegate action) - { - var entry = GetOrAddEntry(typeof(T), shouldConstruct: false); - entry.Add(action); - } - - internal void Add(Action writer, Func reader) - { - var entry = GetOrAddEntry(typeof(T), shouldConstruct: false); - entry.Add(GetDelegate(writer, reader)); - } - - protected static SyncWorkerDelegate GetDelegate(Action writer, Func reader) - { - return (SyncWorker sync, ref T obj) => - { - if (sync.isWriting) - writer(((WritingSyncWorker)sync).writer, obj); - else - obj = reader(((ReadingSyncWorker)sync).reader); - }; - } - - public SyncWorkerEntry this[Type key] { - get { - TryGetValue(key, out SyncWorkerEntry entry); - return entry; - } - } - - public virtual IEnumerator GetEnumerator() - { - return explicitEntries.Values.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public virtual bool TryGetValue(Type type, out SyncWorkerEntry syncWorkerEntry) - { - explicitEntries.TryGetValue(type, out syncWorkerEntry); - - if (syncWorkerEntry != null) - return true; - - return false; - } - - public virtual string PrintStructure() - { - StringBuilder str = new StringBuilder(); - str.AppendLine("Explicit: "); - foreach (var e in explicitEntries.Values) { - e.PrintStructureInternal(0, str); - } - - return str.ToString(); - } - } - - class SyncWorkerDictionaryTree : SyncWorkerDictionary - { - protected readonly List implicitEntries = new List(); - protected readonly List interfaceEntries = new List(); - - public SyncWorkerEntry GetOrAddEntry(Type type, bool isImplicit = false, bool shouldConstruct = false) - { - if (explicitEntries.TryGetValue(type, out SyncWorkerEntry explicitEntry)) { - return explicitEntry; - } - - if (!isImplicit) { - return AddExplicit(type, shouldConstruct); - } - - if (type.IsInterface) { - - var interfaceEntry = interfaceEntries.FirstOrDefault(i => i.type == type); - - if (interfaceEntry == null) { - interfaceEntry = new SyncWorkerEntry(type, shouldConstruct); - - interfaceEntries.Add(interfaceEntry); - } - - return interfaceEntry; - } - - var entry = implicitEntries.FirstOrDefault(i => i.type == type); - - Stack toRemove = new Stack(); - - foreach (var e in implicitEntries) { - - if (type.IsAssignableFrom(e.type) || e.type.IsAssignableFrom(type)) { - if (entry != null) { - entry.Add(e); - toRemove.Push(e); - continue; - } - entry = e.Add(type, shouldConstruct); - } - } - - if (entry == null) { - entry = new SyncWorkerEntry(type, shouldConstruct); - implicitEntries.Add(entry); - return entry; - } - - foreach (var e in toRemove) { - implicitEntries.Remove(e); - } - - return entry; - } - - internal void Add(SyncWorkerDelegate action, bool isImplicit = false, bool shouldConstruct = false) - { - var entry = GetOrAddEntry(typeof(T), isImplicit: isImplicit, shouldConstruct: shouldConstruct); - - entry.Add(action); - } - - internal void Add(Action writer, Func reader, bool isImplicit = false, bool shouldConstruct = false) - { - var entry = GetOrAddEntry(typeof(T), isImplicit: isImplicit, shouldConstruct: shouldConstruct); - - entry.Add(GetDelegate(writer, reader)); - } - - public override bool TryGetValue(Type type, out SyncWorkerEntry syncWorkerEntry) - { - if (explicitEntries.TryGetValue(type, out syncWorkerEntry)) - return true; - - foreach (var e in implicitEntries) { - syncWorkerEntry = e.GetClosest(type); - - if (syncWorkerEntry != null) - return true; - } - - foreach (var e in interfaceEntries) { - syncWorkerEntry = e.GetClosest(type); - - if (syncWorkerEntry != null) - return true; - } - - return false; - } - - public override IEnumerator GetEnumerator() - { - return explicitEntries.Values.Union(implicitEntries).Union(interfaceEntries).GetEnumerator(); - } - - public override string PrintStructure() - { - StringBuilder str = new StringBuilder(); - str.AppendLine("Explicit: "); - foreach (var e in explicitEntries.Values) { - e.PrintStructureInternal(0, str); - } - str.AppendLine(); - str.AppendLine("Interface: "); - foreach (var e in interfaceEntries) { - e.PrintStructureInternal(0, str); - } - str.AppendLine(); - str.AppendLine("Implicit: "); - foreach (var e in implicitEntries) { - e.PrintStructureInternal(0, str); - } - - return str.ToString(); - } - - public static SyncWorkerDictionaryTree Merge(params SyncWorkerDictionaryTree[] trees) - { - var tree = new SyncWorkerDictionaryTree(); - - foreach (var t in trees) { - tree.explicitEntries.AddRange(t.explicitEntries); - tree.implicitEntries.AddRange(t.implicitEntries); - tree.interfaceEntries.AddRange(t.interfaceEntries); - } - - return tree; - } - } - - internal static class RwTypeHelper - { - private static Dictionary cache = new Dictionary(); - - static RwTypeHelper() - { - cache[typeof(IStoreSettingsParent)] = RwImplSerialization.storageParents; - cache[typeof(IPlantToGrowSettable)] = RwImplSerialization.plantToGrowSettables; - - cache[typeof(ThingComp)] = RwImplSerialization.thingCompTypes; - cache[typeof(AbilityComp)] = RwImplSerialization.abilityCompTypes; - cache[typeof(Designator)] = RwImplSerialization.designatorTypes; - cache[typeof(WorldObjectComp)] = RwImplSerialization.worldObjectCompTypes; - cache[typeof(HediffComp)] = RwImplSerialization.hediffCompTypes; - - cache[typeof(GameComponent)] = RwImplSerialization.gameCompTypes; - cache[typeof(WorldComponent)] = RwImplSerialization.worldCompTypes; - cache[typeof(MapComponent)] = RwImplSerialization.mapCompTypes; - } - - internal static void FlushCache() - { - cache.Clear(); - } - - internal static Type[] GenTypeCache(Type type) - { - var types = GenTypes.AllTypes - .Where(t => t != type && type.IsAssignableFrom(t)) - .OrderBy(t => t.IsInterface) - .ToArray(); - - cache[type] = types; - return types; - } - - internal static Type GetType(ushort index, Type baseType) - { - if (!cache.TryGetValue(baseType, out Type[] types)) - types = GenTypeCache(baseType); - - return types[index]; - } - - internal static ushort GetTypeIndex(Type type, Type baseType) - { - if (!cache.TryGetValue(baseType, out Type[] types)) - types = GenTypeCache(baseType); - - return (ushort) types.FindIndex(type); - } - } -} diff --git a/Source/Client/Util/CollectionExtensions.cs b/Source/Client/Util/CollectionExtensions.cs index 3c65e2e0..788db219 100644 --- a/Source/Client/Util/CollectionExtensions.cs +++ b/Source/Client/Util/CollectionExtensions.cs @@ -50,25 +50,6 @@ public static T RemoveFirst(this List list) return elem; } - static bool ArraysEqual(T[] a1, T[] a2) - { - if (ReferenceEquals(a1, a2)) - return true; - - if (a1 == null || a2 == null) - return false; - - if (a1.Length != a2.Length) - return false; - - EqualityComparer comparer = EqualityComparer.Default; - for (int i = 0; i < a1.Length; i++) - if (!comparer.Equals(a1[i], a2[i])) - return false; - - return true; - } - public static int RemoveAll(this Dictionary dictionary, Func predicate) { List list = null; diff --git a/Source/Common/Common.csproj b/Source/Common/Common.csproj index d58f09fc..5eb0de09 100644 --- a/Source/Common/Common.csproj +++ b/Source/Common/Common.csproj @@ -4,7 +4,7 @@ net472 true enable - 11 + 12 false false Multiplayer.Common @@ -16,6 +16,7 @@ + diff --git a/Source/Client/Syncing/Logger/IHasLogger.cs b/Source/Common/Syncing/Logger/IHasLogger.cs similarity index 100% rename from Source/Client/Syncing/Logger/IHasLogger.cs rename to Source/Common/Syncing/Logger/IHasLogger.cs diff --git a/Source/Client/Syncing/Logger/LogNode.cs b/Source/Common/Syncing/Logger/LogNode.cs similarity index 76% rename from Source/Client/Syncing/Logger/LogNode.cs rename to Source/Common/Syncing/Logger/LogNode.cs index 87670b2b..77630858 100644 --- a/Source/Client/Syncing/Logger/LogNode.cs +++ b/Source/Common/Syncing/Logger/LogNode.cs @@ -4,12 +4,12 @@ namespace Multiplayer.Client { public class LogNode { - public LogNode parent; + public LogNode? parent; public List children = new(); public string text; public bool expand; - public LogNode(string text, LogNode parent = null) + public LogNode(string text, LogNode? parent = null) { this.text = text; this.parent = parent; diff --git a/Source/Client/Syncing/Logger/LoggingByteReader.cs b/Source/Common/Syncing/Logger/LoggingByteReader.cs similarity index 86% rename from Source/Client/Syncing/Logger/LoggingByteReader.cs rename to Source/Common/Syncing/Logger/LoggingByteReader.cs index b0fd1c4b..5f580e49 100644 --- a/Source/Client/Syncing/Logger/LoggingByteReader.cs +++ b/Source/Common/Syncing/Logger/LoggingByteReader.cs @@ -4,11 +4,10 @@ namespace Multiplayer.Client { public class LoggingByteReader : ByteReader, IHasLogger { - public SyncLogger Log { get; } = new SyncLogger(); + public SyncLogger Log { get; } = new(); public LoggingByteReader(byte[] array) : base(array) { - } public override bool ReadBool() @@ -51,7 +50,7 @@ public override short ReadShort() return Log.NodePassthrough("short: ", base.ReadShort()); } - public override string ReadStringNullable(int maxLen = 32767) + public override string? ReadStringNullable(int maxLen = 32767) { return Log.NodePassthrough("string?: ", base.ReadStringNullable(maxLen)); } @@ -76,12 +75,12 @@ public override ushort ReadUShort() return Log.NodePassthrough("ushort: ", base.ReadUShort()); } - public override byte[] ReadPrefixedBytes(int maxLen = int.MaxValue) + public override byte[]? ReadPrefixedBytes(int maxLen = int.MaxValue) { Log.Pause(); - byte[] array = base.ReadPrefixedBytes(); + byte[]? array = base.ReadPrefixedBytes(maxLen); Log.Resume(); - Log.Node($"byte[{array.Length}]"); + Log.Node($"byte[{array?.Length}]"); return array; } } diff --git a/Source/Client/Syncing/Logger/LoggingByteWriter.cs b/Source/Common/Syncing/Logger/LoggingByteWriter.cs similarity index 100% rename from Source/Client/Syncing/Logger/LoggingByteWriter.cs rename to Source/Common/Syncing/Logger/LoggingByteWriter.cs diff --git a/Source/Client/Syncing/Logger/SyncLogger.cs b/Source/Common/Syncing/Logger/SyncLogger.cs similarity index 60% rename from Source/Client/Syncing/Logger/SyncLogger.cs rename to Source/Common/Syncing/Logger/SyncLogger.cs index 31e7a7f3..e2b4ca57 100644 --- a/Source/Client/Syncing/Logger/SyncLogger.cs +++ b/Source/Common/Syncing/Logger/SyncLogger.cs @@ -1,13 +1,10 @@ -using Verse; - namespace Multiplayer.Client { public class SyncLogger { public const string RootNodeName = "Root"; - public LogNode current = new LogNode(RootNodeName); - + public LogNode current = new(RootNodeName); private int stopped; public T NodePassthrough(string text, T val) @@ -19,28 +16,22 @@ public T NodePassthrough(string text, T val) public LogNode Node(string text) { if (stopped > 0) - { return current; - } LogNode logNode = new LogNode(text, current); current.children.Add(logNode); return logNode; } - public void Enter(string text) + public void Enter(string? text) { if (stopped <= 0) - { - current = Node(text); - } + current = Node(text ?? ""); } public void Exit() { if (stopped <= 0) - { - current = current.parent; - } + current = current.parent!; } public void Pause() @@ -57,20 +48,6 @@ public void AppendToCurrentName(string append) { current.text += append; } - - public void Print() - { - Print(current, 1); - } - - private void Print(LogNode node, int depth) - { - Log.Message(new string(' ', depth) + node.text); - foreach (LogNode child in node.children) - { - Print(child, depth + 1); - } - } } } diff --git a/Source/Client/Syncing/SerializationException.cs b/Source/Common/Syncing/SerializationException.cs similarity index 100% rename from Source/Client/Syncing/SerializationException.cs rename to Source/Common/Syncing/SerializationException.cs diff --git a/Source/Common/Syncing/SyncDictPrimitives.cs b/Source/Common/Syncing/SyncDictPrimitives.cs new file mode 100644 index 00000000..15756469 --- /dev/null +++ b/Source/Common/Syncing/SyncDictPrimitives.cs @@ -0,0 +1,23 @@ +using Multiplayer.API; + +namespace Multiplayer.Client; + +public static class SyncDictPrimitives +{ + internal static SyncWorkerDictionary syncWorkers = new() + { + // missing decimal and char, good? + { (SyncWorker sync, ref bool b ) => sync.Bind(ref b) }, + { (SyncWorker sync, ref byte b ) => sync.Bind(ref b) }, + { (SyncWorker sync, ref sbyte b ) => sync.Bind(ref b) }, + { (SyncWorker sync, ref double d ) => sync.Bind(ref d) }, + { (SyncWorker sync, ref float f ) => sync.Bind(ref f) }, + { (SyncWorker sync, ref int i ) => sync.Bind(ref i) }, + { (SyncWorker sync, ref uint i ) => sync.Bind(ref i) }, + { (SyncWorker sync, ref long l ) => sync.Bind(ref l) }, + { (SyncWorker sync, ref ulong l ) => sync.Bind(ref l) }, + { (SyncWorker sync, ref short s ) => sync.Bind(ref s) }, + { (SyncWorker sync, ref ushort s ) => sync.Bind(ref s) }, + { (SyncWorker sync, ref string t ) => sync.Bind(ref t) }, + }; +} diff --git a/Source/Client/Syncing/SyncSerialization.cs b/Source/Common/Syncing/SyncSerialization.cs similarity index 57% rename from Source/Client/Syncing/SyncSerialization.cs rename to Source/Common/Syncing/SyncSerialization.cs index 54ab699d..1ba6438a 100644 --- a/Source/Client/Syncing/SyncSerialization.cs +++ b/Source/Common/Syncing/SyncSerialization.cs @@ -4,24 +4,43 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Runtime.CompilerServices; -using System.Text; -using System.Xml; -using Verse; namespace Multiplayer.Client { public static class SyncSerialization { - public static void Init() + public delegate bool CanHandleHook(SyncType syncType); + public delegate bool SyncTypeMatcher(SyncType syncType); + + public delegate object? SerializationReader(ByteReader data, SyncType syncType); + public delegate void SerializationWriter(ByteWriter data, object? obj, SyncType syncType); + + public delegate Type TypeChangerReader(ByteReader data, SyncType syncType); + public delegate void TypeChangerWriter(ByteWriter data, object? obj, SyncType syncType); + + public static List canHandleHooks = []; + private static List<(SyncTypeMatcher, SerializationReader, SerializationWriter)> serializationHooks = []; + private static List<(SyncTypeMatcher, TypeChangerReader, TypeChangerWriter)> typeChangerHooks = []; + + public static Action errorLogger = msg => Console.WriteLine($"Sync Error: {msg}"); + + public static void AddSerializationHook(SyncTypeMatcher matcher, SerializationReader reader, SerializationWriter writer) + { + serializationHooks.Add((matcher, reader, writer)); + } + + public static void AddTypeChanger(SyncTypeMatcher matcher, TypeChangerReader reader, TypeChangerWriter writer) { - RwImplSerialization.Init(); - ImplSerialization.Init(); - DefSerialization.Init(); + typeChangerHooks.Add((matcher, reader, writer)); } + public static SyncWorkerDictionaryTree? syncTree; + + private static Type[] supportedSystemGtds = + [typeof(List<>), typeof(IEnumerable<>), typeof(Nullable<>), typeof(Dictionary<,>), typeof(HashSet<>)]; + public static bool CanHandle(SyncType syncType) { var type = syncType.type; @@ -30,51 +49,37 @@ public static bool CanHandle(SyncType syncType) return true; if (type.IsByRef) return true; - if (SyncDictFast.syncWorkers.TryGetValue(type, out _)) + if (SyncDictPrimitives.syncWorkers.TryGetValue(type, out _)) return true; - if (syncType.expose) - return typeof(IExposable).IsAssignableFrom(type); if (typeof(ISynchronizable).IsAssignableFrom(type)) return true; if (type.IsEnum) return CanHandle(Enum.GetUnderlyingType(type)); if (type.IsArray) return type.GetArrayRank() == 1 && CanHandle(type.GetElementType()); - if (type.IsGenericType && type.GetGenericTypeDefinition() is { } gtd) - return - (false - || gtd == typeof(List<>) - || gtd == typeof(IEnumerable<>) - || gtd == typeof(Nullable<>) - || gtd == typeof(Dictionary<,>) - || gtd == typeof(Pair<,>) - || gtd == typeof(HashSet<>) - || typeof(ITuple).IsAssignableFrom(gtd)) - && CanHandleGenericArgs(type); - if (typeof(ISyncSimple).IsAssignableFrom(type)) - return ImplSerialization.syncSimples. - Where(t => type.IsAssignableFrom(t)). - SelectMany(AccessTools.GetDeclaredFields). - All(f => CanHandle(f.FieldType)); - if (typeof(Def).IsAssignableFrom(type)) - return true; - if (typeof(Designator).IsAssignableFrom(type)) + + if (type.IsGenericType && + type.GetGenericTypeDefinition() is { } gtd&& + (supportedSystemGtds.Contains(gtd) || typeof(ITuple).IsAssignableFrom(gtd))) + return CanHandleGenericArgs(type); + + if (Enumerable.Any(canHandleHooks, hook => hook(syncType))) return true; - return SyncDict.syncWorkers.TryGetValue(type, out _); + return syncTree != null && syncTree.TryGetValue(type, out _); } - private static bool CanHandleGenericArgs(Type genericType) + public static bool CanHandleGenericArgs(Type genericType) { return genericType.GetGenericArguments().All(arg => CanHandle(arg)); } - public static T ReadSync(ByteReader data) + public static T? ReadSync(ByteReader data) { - return (T)ReadSyncObject(data, typeof(T)); + return (T?)ReadSyncObject(data, typeof(T)); } - public static object ReadSyncObject(ByteReader data, SyncType syncType) + public static object? ReadSyncObject(ByteReader data, SyncType syncType) { var log = (data as LoggingByteReader)?.Log; Type type = syncType.type; @@ -83,7 +88,7 @@ public static object ReadSyncObject(ByteReader data, SyncType syncType) try { - object val = ReadSyncObjectInternal(data, syncType); + object? val = ReadSyncObjectInternal(data, syncType); log?.AppendToCurrentName($": {val}"); return val; } @@ -93,7 +98,7 @@ public static object ReadSyncObject(ByteReader data, SyncType syncType) } } - private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) + private static object? ReadSyncObjectInternal(ByteReader data, SyncType syncType) { Type type = syncType.type; @@ -109,8 +114,8 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) return null; } - if (SyncDictFast.syncWorkers.TryGetValue(type, out SyncWorkerEntry syncWorkerEntryEarly)) { - object res = null; + if (SyncDictPrimitives.syncWorkers.TryGetValue(type, out SyncWorkerEntry syncWorkerEntryEarly)) { + object? res = null; if (syncWorkerEntryEarly.shouldConstruct || type.IsValueType) res = Activator.CreateInstance(type); @@ -120,27 +125,10 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) return res; } - if (syncType.expose) - { - if (!typeof(IExposable).IsAssignableFrom(type)) - throw new SerializationException($"Type {type} can't be exposed because it isn't IExposable"); - - byte[] exposableData = data.ReadPrefixedBytes(); - return ExposableSerialization.ReadExposable(type, exposableData); - } - - if (typeof(ISynchronizable).IsAssignableFrom(type)) - { - var obj = Activator.CreateInstance(type); - - ((ISynchronizable) obj).Sync(new ReadingSyncWorker(data)); - return obj; - } - if (type.IsEnum) { Type underlyingType = Enum.GetUnderlyingType(type); - return Enum.ToObject(type, ReadSyncObject(data, underlyingType)); + return Enum.ToObject(type, ReadSyncObject(data, underlyingType)!); } if (type.IsArray) @@ -152,7 +140,7 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) if (length == ushort.MaxValue) return null; - Type elementType = type.GetElementType(); + Type elementType = type.GetElementType()!; Array arr = Array.CreateInstance(elementType, length); for (int i = 0; i < length; i++) @@ -198,11 +186,11 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) { Type[] arguments = type.GetGenericArguments(); - Array keys = (Array)ReadSyncObject(data, arguments[0].MakeArrayType()); + Array? keys = (Array?)ReadSyncObject(data, arguments[0].MakeArrayType()); if (keys == null) return null; - Array values = (Array)ReadSyncObject(data, arguments[1].MakeArrayType()); + Array values = (Array)ReadSyncObject(data, arguments[1].MakeArrayType())!; IDictionary dictionary = (IDictionary)Activator.CreateInstance(type); for (int i = 0; i < keys.Length; i++) @@ -215,14 +203,14 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) return dictionary; } - if (genericTypeDefinition == typeof(Pair<,>) || genericTypeDefinition == typeof(ValueTuple<,>)) + if (genericTypeDefinition == typeof(ValueTuple<,>)) // Binary ValueTuple { Type[] arguments = type.GetGenericArguments(); - object[] parameters = - { + object?[] parameters = + [ ReadSyncObject(data, arguments[0]), - ReadSyncObject(data, arguments[1]), - }; + ReadSyncObject(data, arguments[1]) + ]; return type.GetConstructors().First().Invoke(parameters); } @@ -230,16 +218,17 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) if (genericTypeDefinition == typeof(HashSet<>)) { Type element = type.GetGenericArguments()[0]; - object list = ReadSyncObject(data, typeof(List<>).MakeGenericType(element)); - return Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(element), list); + object? list = ReadSyncObject(data, typeof(List<>).MakeGenericType(element)); + return list == null ? null : Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(element), list); } + // todo handle null non-value Tuples? if (typeof(ITuple).IsAssignableFrom(genericTypeDefinition)) // ValueTuple or Tuple { Type[] arguments = type.GetGenericArguments(); int size = data.ReadInt32(); - object[] values = new object[size]; + object?[] values = new object?[size]; for (int i = 0; i < size; i++) values[i] = ReadSyncObject(data, arguments[i]); @@ -248,45 +237,26 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) } } - if (typeof(ISyncSimple).IsAssignableFrom(type)) - { - ushort typeIndex = data.ReadUShort(); - var objType = ImplSerialization.syncSimples[typeIndex]; - var obj = MpUtil.NewObjectNoCtor(objType); - foreach (var field in AccessTools.GetDeclaredFields(objType)) - field.SetValue(obj, ReadSyncObject(data, field.FieldType)); - return obj; - } + foreach (var hook in serializationHooks) + if (hook.Item1(syncType)) + return hook.Item2(data, type); - // Def is a special case until the workers can read their own type - if (typeof(Def).IsAssignableFrom(type)) + if (typeof(ISynchronizable).IsAssignableFrom(type)) { - ushort defTypeIndex = data.ReadUShort(); - if (defTypeIndex == ushort.MaxValue) - return null; - - ushort shortHash = data.ReadUShort(); - - var defType = DefSerialization.DefTypes[defTypeIndex]; - var def = DefSerialization.GetDef(defType, shortHash); - - if (def == null) - throw new SerializationException($"Couldn't find {defType} with short hash {shortHash}"); + var obj = Activator.CreateInstance(type); - return def; + ((ISynchronizable) obj).Sync(new ReadingSyncWorker(data)); + return obj; } - // Designators can't be handled by SyncWorkers due to the type change - if (typeof(Designator).IsAssignableFrom(type)) - { - ushort desId = ReadSync(data); - type = RwImplSerialization.designatorTypes[desId]; // Replaces the type! - } + foreach (var hook in typeChangerHooks) + if (hook.Item1(syncType)) + syncType = type = hook.Item2(data, syncType); // Where the magic happens - if (SyncDict.syncWorkers.TryGetValue(type, out var syncWorkerEntry)) + if (syncTree != null && syncTree.TryGetValue(type, out var syncWorkerEntry)) { - object res = null; + object? res = null; if (syncWorkerEntry.shouldConstruct || type.IsValueType) res = Activator.CreateInstance(type); @@ -300,17 +270,17 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) } catch { - Log.Error($"Multiplayer: Error reading type: {type}"); + errorLogger($"Multiplayer: Error reading type: {type}"); throw; } } - public static void WriteSync(ByteWriter data, T obj) + public static void WriteSync(ByteWriter data, T? obj) { WriteSyncObject(data, obj, typeof(T)); } - public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncType) + public static void WriteSyncObject(ByteWriter data, object? obj, SyncType syncType) { Type type = syncType.type; var log = (data as LoggingByteWriter)?.Log; @@ -331,31 +301,12 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp return; } - if (SyncDictFast.syncWorkers.TryGetValue(type, out var syncWorkerEntryEarly)) { + if (SyncDictPrimitives.syncWorkers.TryGetValue(type, out var syncWorkerEntryEarly)) { syncWorkerEntryEarly.Invoke(new WritingSyncWorker(data), ref obj); return; } - if (syncType.expose) - { - if (!typeof(IExposable).IsAssignableFrom(type)) - throw new SerializationException($"Type {type} can't be exposed because it isn't IExposable"); - - IExposable exposable = obj as IExposable; - byte[] xmlData = ScribeUtil.WriteExposable(exposable); - LogXML(log, xmlData); - data.WritePrefixedBytes(xmlData); - - return; - } - - if (typeof(ISynchronizable).IsAssignableFrom(type)) - { - ((ISynchronizable) obj).Sync(new WritingSyncWorker(data)); - return; - } - if (type.IsEnum) { Type enumType = Enum.GetUnderlyingType(type); @@ -368,8 +319,8 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp if (type.GetArrayRank() != 1) throw new SerializationException("Multi dimensional arrays aren't supported."); - Type elementType = type.GetElementType(); - Array arr = (Array)obj; + Type elementType = type.GetElementType()!; + Array? arr = (Array?)obj; if (arr == null) { @@ -392,7 +343,7 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp if (genericTypeDefinition == typeof(List<>)) { - IList list = (IList)obj; + IList? list = (IList?)obj; Type listObjType = type.GetGenericArguments()[0]; if (list == null) @@ -413,7 +364,7 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp if (genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(HashSet<>)) { - IEnumerable e = (IEnumerable)obj; + IEnumerable? e = (IEnumerable?)obj; Type elementType = type.GetGenericArguments()[0]; var listType = typeof(List<>).MakeGenericType(elementType); @@ -446,7 +397,7 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp if (genericTypeDefinition == typeof(Dictionary<,>)) { - IDictionary dictionary = (IDictionary)obj; + IDictionary? dictionary = (IDictionary?)obj; Type[] arguments = type.GetGenericArguments(); if (dictionary == null) @@ -467,16 +418,6 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp return; } - if (genericTypeDefinition == typeof(Pair<,>)) - { - Type[] arguments = type.GetGenericArguments(); - - WriteSyncObject(data, AccessTools.DeclaredField(type, "first").GetValue(obj), arguments[0]); - WriteSyncObject(data, AccessTools.DeclaredField(type, "second").GetValue(obj), arguments[1]); - - return; - } - if (genericTypeDefinition == typeof(ValueTuple<,>)) { Type[] arguments = type.GetGenericArguments(); @@ -490,7 +431,7 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp if (typeof(ITuple).IsAssignableFrom(genericTypeDefinition)) // ValueTuple or Tuple { Type[] arguments = type.GetGenericArguments(); - ITuple tuple = (ITuple)obj; + ITuple tuple = (ITuple)obj!; data.WriteInt32(tuple.Length); @@ -501,44 +442,29 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp } } - if (typeof(ISyncSimple).IsAssignableFrom(type)) - { - data.WriteUShort((ushort)ImplSerialization.syncSimples.FindIndex(obj.GetType())); - foreach (var field in AccessTools.GetDeclaredFields(obj.GetType())) - WriteSyncObject(data, field.GetValue(obj), field.FieldType); - return; - } - - // Special case - if (typeof(Def).IsAssignableFrom(type)) + foreach (var hook in serializationHooks) { - if (obj is not Def def) + if (hook.Item1(syncType)) { - data.WriteUShort(ushort.MaxValue); + hook.Item3(data, obj, syncType); return; } - - var defTypeIndex = Array.IndexOf(DefSerialization.DefTypes, def.GetType()); - if (defTypeIndex == -1) - throw new SerializationException($"Unknown def type {def.GetType()}"); - - data.WriteUShort((ushort)defTypeIndex); - data.WriteUShort(def.shortHash); - - return; } - // Special case for Designators to change the type - if (typeof(Designator).IsAssignableFrom(type)) + if (typeof(ISynchronizable).IsAssignableFrom(type)) { - data.WriteUShort((ushort) Array.IndexOf(RwImplSerialization.designatorTypes, obj.GetType())); + ((ISynchronizable) obj!).Sync(new WritingSyncWorker(data)); + return; } + foreach (var hook in typeChangerHooks) + if (hook.Item1(syncType)) + hook.Item3(data, obj, syncType); + // Where the magic happens - if (SyncDict.syncWorkers.TryGetValue(type, out var syncWorkerEntry)) + if (syncTree != null && syncTree.TryGetValue(type, out var syncWorkerEntry)) { syncWorkerEntry.Invoke(new WritingSyncWorker(data), ref obj); - return; } @@ -547,7 +473,7 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp } catch { - Log.Error($"Multiplayer: Error writing type: {type}, obj: {obj}, obj type: {obj?.GetType()}"); + errorLogger($"Multiplayer: Error writing type: {type}, obj: {obj}, obj type: {obj?.GetType()}"); throw; } finally @@ -555,60 +481,5 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp log?.Exit(); } } - - internal static T GetAnyParent(Thing thing) where T : class - { - if (thing is T t) - return t; - - for (var parentHolder = thing.ParentHolder; parentHolder != null; parentHolder = parentHolder.ParentHolder) - if (parentHolder is T t2) - return t2; - - return null; - } - - internal static string ThingHolderString(Thing thing) - { - StringBuilder builder = new StringBuilder(thing.ToString()); - - for (var parentHolder = thing.ParentHolder; parentHolder != null; parentHolder = parentHolder.ParentHolder) - { - builder.Insert(0, "=>"); - builder.Insert(0, parentHolder.ToString()); - } - - return builder.ToString(); - } - - private static void LogXML(SyncLogger log, byte[] xmlData) - { - if (log == null) return; - - var reader = XmlReader.Create(new MemoryStream(xmlData)); - - while (reader.Read()) - { - if (reader.NodeType == XmlNodeType.Element) - { - string name = reader.Name; - if (reader.GetAttribute("IsNull") == "True") - name += " (IsNull)"; - - if (reader.IsEmptyElement) - log.Node(name); - else - log.Enter(name); - } - else if (reader.NodeType == XmlNodeType.EndElement) - { - log.Exit(); - } - else if (reader.NodeType == XmlNodeType.Text) - { - log.AppendToCurrentName($": {reader.Value}"); - } - } - } } } diff --git a/Source/Client/Syncing/Worker/ReadingSyncWorker.cs b/Source/Common/Syncing/Worker/ReadingSyncWorker.cs similarity index 90% rename from Source/Client/Syncing/Worker/ReadingSyncWorker.cs rename to Source/Common/Syncing/Worker/ReadingSyncWorker.cs index 60bc3320..7d2ab8b2 100644 --- a/Source/Client/Syncing/Worker/ReadingSyncWorker.cs +++ b/Source/Common/Syncing/Worker/ReadingSyncWorker.cs @@ -10,7 +10,7 @@ public class ReadingSyncWorker : SyncWorker internal readonly ByteReader reader; readonly int initialPos; - internal ByteReader Reader => reader; + public ByteReader Reader => reader; public ReadingSyncWorker(ByteReader reader) : base(false) { @@ -25,7 +25,7 @@ public override void Bind(ref T obj, SyncType type) public override void Bind(ref T obj) { - obj = (T) SyncSerialization.ReadSyncObject(reader, typeof(T)); + obj = (T)SyncSerialization.ReadSyncObject(reader, typeof(T)); } public override void Bind(object obj, string name) @@ -33,9 +33,7 @@ public override void Bind(object obj, string name) object value = MpReflection.GetValue(obj, name); Type type = value.GetType(); - var res = SyncSerialization.ReadSyncObject(reader, type); - MpReflection.SetValue(obj, name, res); } @@ -94,14 +92,14 @@ public override void Bind(ref bool obj) obj = reader.ReadBool(); } - public override void Bind(ref string obj) + public override void Bind(ref string? obj) { obj = reader.ReadStringNullable(); } public override void BindType(ref Type type) { - type = RwTypeHelper.GetType(reader.ReadUShort(), typeof(T)); + type = SyncWorkerTypeHelper.GetType(reader.ReadUShort(), typeof(T)); } internal void Reset() diff --git a/Source/Common/Syncing/Worker/SyncWorkerDictionary.cs b/Source/Common/Syncing/Worker/SyncWorkerDictionary.cs new file mode 100644 index 00000000..c2d1caa0 --- /dev/null +++ b/Source/Common/Syncing/Worker/SyncWorkerDictionary.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using Multiplayer.API; +using Multiplayer.Common; + +namespace Multiplayer.Client; + +public class SyncWorkerDictionary : IEnumerable +{ + protected readonly Dictionary explicitEntries = new(); + + public SyncWorkerEntry GetOrAddEntry(Type type, bool shouldConstruct = false) + { + if (explicitEntries.TryGetValue(type, out SyncWorkerEntry explicitEntry)) + return explicitEntry; + + return AddExplicit(type, shouldConstruct); + } + + protected SyncWorkerEntry AddExplicit(Type type, bool shouldConstruct = false) + { + var explicitEntry = new SyncWorkerEntry(type, shouldConstruct); + explicitEntries.Add(type, explicitEntry); + return explicitEntry; + } + + public void Add(SyncWorkerDelegate action) + { + var entry = GetOrAddEntry(typeof(T), shouldConstruct: false); + entry.Add(action); + } + + public void Add(Action writer, Func reader) + { + var entry = GetOrAddEntry(typeof(T), shouldConstruct: false); + entry.Add(GetDelegate(writer, reader)); + } + + protected static SyncWorkerDelegate GetDelegate(Action writer, Func reader) + { + return (SyncWorker sync, ref T obj) => + { + if (sync.isWriting) + writer(((WritingSyncWorker)sync).writer, obj); + else + obj = reader(((ReadingSyncWorker)sync).reader); + }; + } + + public SyncWorkerEntry this[Type key] { + get { + TryGetValue(key, out SyncWorkerEntry entry); + return entry; + } + } + + public virtual IEnumerator GetEnumerator() + { + return explicitEntries.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public virtual bool TryGetValue(Type type, out SyncWorkerEntry syncWorkerEntry) + { + explicitEntries.TryGetValue(type, out syncWorkerEntry); + return syncWorkerEntry != null; + } + + public virtual string PrintStructure() + { + StringBuilder str = new StringBuilder(); + str.AppendLine("Explicit: "); + foreach (var e in explicitEntries.Values) { + e.PrintStructureInternal(0, str); + } + + return str.ToString(); + } +} diff --git a/Source/Common/Syncing/Worker/SyncWorkerDictionaryTree.cs b/Source/Common/Syncing/Worker/SyncWorkerDictionaryTree.cs new file mode 100644 index 00000000..fbc84459 --- /dev/null +++ b/Source/Common/Syncing/Worker/SyncWorkerDictionaryTree.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Multiplayer.API; +using Multiplayer.Common; +using Multiplayer.Common.Util; + +namespace Multiplayer.Client; + +public class SyncWorkerDictionaryTree : SyncWorkerDictionary +{ + protected readonly List implicitEntries = []; + protected readonly List interfaceEntries = []; + + public SyncWorkerEntry GetOrAddEntry(Type type, bool isImplicit = false, bool shouldConstruct = false) + { + if (explicitEntries.TryGetValue(type, out SyncWorkerEntry explicitEntry)) { + return explicitEntry; + } + + if (!isImplicit) { + return AddExplicit(type, shouldConstruct); + } + + if (type.IsInterface) { + var interfaceEntry = interfaceEntries.FirstOrDefault(i => i.type == type); + + if (interfaceEntry == null) { + interfaceEntry = new SyncWorkerEntry(type, shouldConstruct); + interfaceEntries.Add(interfaceEntry); + } + + return interfaceEntry; + } + + var entry = implicitEntries.FirstOrDefault(i => i.type == type); + Stack toRemove = new Stack(); + + foreach (var e in implicitEntries) { + + if (type.IsAssignableFrom(e.type) || e.type.IsAssignableFrom(type)) { + if (entry != null) { + entry.Add(e); + toRemove.Push(e); + continue; + } + entry = e.Add(type, shouldConstruct); + } + } + + if (entry == null) { + entry = new SyncWorkerEntry(type, shouldConstruct); + implicitEntries.Add(entry); + return entry; + } + + foreach (var e in toRemove) { + implicitEntries.Remove(e); + } + + return entry; + } + + public void Add(SyncWorkerDelegate action, bool isImplicit = false, bool shouldConstruct = false) + { + var entry = GetOrAddEntry(typeof(T), isImplicit: isImplicit, shouldConstruct: shouldConstruct); + entry.Add(action); + } + + public void Add(Action writer, Func reader, bool isImplicit = false, bool shouldConstruct = false) + { + var entry = GetOrAddEntry(typeof(T), isImplicit: isImplicit, shouldConstruct: shouldConstruct); + entry.Add(GetDelegate(writer, reader)); + } + + public override bool TryGetValue(Type type, out SyncWorkerEntry syncWorkerEntry) + { + if (explicitEntries.TryGetValue(type, out syncWorkerEntry)) + return true; + + foreach (var e in implicitEntries) { + syncWorkerEntry = e.GetClosest(type); + + if (syncWorkerEntry != null) + return true; + } + + foreach (var e in interfaceEntries) { + syncWorkerEntry = e.GetClosest(type); + + if (syncWorkerEntry != null) + return true; + } + + return false; + } + + public override IEnumerator GetEnumerator() + { + return explicitEntries.Values.Union(implicitEntries).Union(interfaceEntries).GetEnumerator(); + } + + public override string PrintStructure() + { + StringBuilder str = new StringBuilder(); + str.AppendLine("Explicit: "); + foreach (var e in explicitEntries.Values) { + e.PrintStructureInternal(0, str); + } + str.AppendLine(); + str.AppendLine("Implicit: "); + foreach (var e in implicitEntries) { + e.PrintStructureInternal(0, str); + } + str.AppendLine(); + str.AppendLine("Interface: "); + foreach (var e in interfaceEntries) { + e.PrintStructureInternal(0, str); + } + + return str.ToString(); + } + + public static SyncWorkerDictionaryTree Merge(params SyncWorkerDictionaryTree[] trees) + { + var tree = new SyncWorkerDictionaryTree(); + + foreach (var t in trees) { + tree.explicitEntries.AddRange(t.explicitEntries); + tree.implicitEntries.AddRange(t.implicitEntries); + tree.interfaceEntries.AddRange(t.interfaceEntries); + } + + return tree; + } +} diff --git a/Source/Common/Syncing/Worker/SyncWorkerEntry.cs b/Source/Common/Syncing/Worker/SyncWorkerEntry.cs new file mode 100644 index 00000000..ada01cfc --- /dev/null +++ b/Source/Common/Syncing/Worker/SyncWorkerEntry.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using Multiplayer.API; + +namespace Multiplayer.Client; + +public class SyncWorkerEntry +{ + delegate bool SyncWorkerDelegate(SyncWorker sync, ref object? obj); + + public Type type; + public bool shouldConstruct; + private List syncWorkers; + private List? subclasses; + private SyncWorkerEntry? parent; + + public int SyncWorkerCount => syncWorkers.Count; + + public SyncWorkerEntry(Type type, bool shouldConstruct = false) + { + this.type = type; + this.syncWorkers = new List(); + this.shouldConstruct = shouldConstruct; + } + + public SyncWorkerEntry(SyncWorkerEntry other) + { + type = other.type; + syncWorkers = other.syncWorkers; + subclasses = other.subclasses; + shouldConstruct = other.shouldConstruct; + } + + public void Add(MethodInfo method) + { + // todo: Find a way to do this without DynDelegate + Add(DynDelegate.DynamicDelegate.Create(method), method.ReturnType == typeof(void)); + } + + public void Add(SyncWorkerDelegate func) + { + Add((SyncWorker sync, ref object? obj) => { + var obj2 = (T?) obj; + func(sync, ref obj2); + obj = obj2; + return true; + }, true); + } + + private void Add(SyncWorkerDelegate sync, bool append = true) + { + if (append) + syncWorkers.Add(sync); + else + syncWorkers.Insert(0, sync); + } + + public bool Invoke(SyncWorker worker, ref object? obj) + { + parent?.Invoke(worker, ref obj); + + for (int i = 0; i < syncWorkers.Count; i++) { + if (syncWorkers[i](worker, ref obj)) + return true; + + if (worker is ReadingSyncWorker reader) { + reader.Reset(); + } else if (worker is WritingSyncWorker writer) { + writer.Reset(); + } + } + + return false; + } + + public SyncWorkerEntry Add(SyncWorkerEntry other) + { + SyncWorkerEntry newEntry = Add(other.type, other, other.shouldConstruct); + newEntry.subclasses = other.subclasses; + return newEntry; + } + + public SyncWorkerEntry? Add(Type type, bool shouldConstruct = false) + { + return Add(type, null, shouldConstruct); + } + + private SyncWorkerEntry? Add(Type type, SyncWorkerEntry? parent, bool shouldConstruct) + { + if (type == this.type) { + if (shouldConstruct) { + this.shouldConstruct = true; + } + + return this; + } + + if (type.IsAssignableFrom(this.type)) // Is parent + { + SyncWorkerEntry newEntry; + + if (parent != null) { + List? ps = parent.subclasses; + newEntry = new SyncWorkerEntry(type, shouldConstruct); + + newEntry.subclasses.Add(this); + + ps[ps.IndexOf(this)] = newEntry; + return newEntry; + } else { + newEntry = new SyncWorkerEntry(this); + + this.type = type; + this.shouldConstruct = shouldConstruct; + syncWorkers = new List(); + subclasses = new List() { newEntry }; + + return this; + } + } + + if (this.type.IsAssignableFrom(type)) // Is child + { + if (subclasses != null) { + for (int i = 0; i < subclasses.Count; i++) { + SyncWorkerEntry? res = subclasses[i].Add(type, this, shouldConstruct); + if (res != null) + return res; + } + } else { + subclasses = new List(); + } + + var newEntry = new SyncWorkerEntry(type, shouldConstruct); + newEntry.parent = this; + subclasses.Add(newEntry); + + return newEntry; + } + + return null; + } + + public SyncWorkerEntry? GetClosest(Type queryType) + { + if (type.IsAssignableFrom(queryType)) { + if (subclasses == null) + return this; + + int len = subclasses.Count; + + for (int i = 0; i < len; i++) { + SyncWorkerEntry? res = subclasses[i].GetClosest(queryType); + + if (res != null) + return res; + } + + return this; + } + + return null; + } + + internal void PrintStructureInternal(int level, StringBuilder str) + { + str.Append(' ', 4 * level); + str.Append(type); + + if (subclasses == null) { + str.AppendLine(); + return; + } + + str.AppendLine(" ┓ "); + + for (int i = 0; i < subclasses.Count; i++) + subclasses[i].PrintStructureInternal(level + 1, str); + } +} diff --git a/Source/Common/Syncing/Worker/SyncWorkerTypeHelper.cs b/Source/Common/Syncing/Worker/SyncWorkerTypeHelper.cs new file mode 100644 index 00000000..023eee41 --- /dev/null +++ b/Source/Common/Syncing/Worker/SyncWorkerTypeHelper.cs @@ -0,0 +1,9 @@ +using System; + +namespace Multiplayer.Client; + +public static class SyncWorkerTypeHelper +{ + public new static Func GetType = (_, _) => throw new Exception("No implementation"); + public static Func GetTypeIndex = (_, _) => throw new Exception("No implementation"); +} diff --git a/Source/Client/Syncing/Worker/WritingSyncWorker.cs b/Source/Common/Syncing/Worker/WritingSyncWorker.cs similarity index 94% rename from Source/Client/Syncing/Worker/WritingSyncWorker.cs rename to Source/Common/Syncing/Worker/WritingSyncWorker.cs index e801a3f0..0b2d1220 100644 --- a/Source/Client/Syncing/Worker/WritingSyncWorker.cs +++ b/Source/Common/Syncing/Worker/WritingSyncWorker.cs @@ -10,7 +10,7 @@ public class WritingSyncWorker : SyncWorker internal readonly ByteWriter writer; readonly int initialPos; - internal ByteWriter Writer { get; } + public ByteWriter Writer => writer; public WritingSyncWorker(ByteWriter writer) : base(true) { @@ -98,7 +98,7 @@ public override void Bind(ref string obj) public override void BindType(ref Type type) { - writer.WriteUShort(RwTypeHelper.GetTypeIndex(type, typeof(T))); + writer.WriteUShort(SyncWorkerTypeHelper.GetTypeIndex(type, typeof(T))); } internal void Reset() diff --git a/Source/Common/Util/CollectionExtensions.cs b/Source/Common/Util/CollectionExtensions.cs new file mode 100644 index 00000000..e7e3d896 --- /dev/null +++ b/Source/Common/Util/CollectionExtensions.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Multiplayer.Common.Util; + +public static class CollectionExtensions +{ + public static void AddRange(this IDictionary dest, IDictionary source) + { + foreach (KeyValuePair item in source) + { + dest.Add(item.Key, item.Value); + } + } +} diff --git a/Source/Common/Util/CompilerTypes.cs b/Source/Common/Util/CompilerTypes.cs index 5ce8d9f1..2e5e626e 100644 --- a/Source/Common/Util/CompilerTypes.cs +++ b/Source/Common/Util/CompilerTypes.cs @@ -16,6 +16,17 @@ public AsyncMethodBuilderAttribute(Type builderType) BuilderType = builderType; } } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Struct, Inherited = false)] + public sealed class RequiredMemberAttribute : Attribute + { + } + + [AttributeUsage(System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + public sealed class CompilerFeatureRequiredAttribute : Attribute + { + public CompilerFeatureRequiredAttribute(string name) { } + } } namespace System.Diagnostics.CodeAnalysis diff --git a/Source/Client/Util/MpReflection.cs b/Source/Common/Util/MpReflection.cs similarity index 78% rename from Source/Client/Util/MpReflection.cs rename to Source/Common/Util/MpReflection.cs index 7e14e351..32385d31 100644 --- a/Source/Client/Util/MpReflection.cs +++ b/Source/Common/Util/MpReflection.cs @@ -4,40 +4,28 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; -using Verse; namespace Multiplayer.Client { public static class MpReflection { - public delegate object Getter(object instance, object index); - public delegate void Setter(object instance, object value, object index); + public static Func> allAssembliesHook; - public static IEnumerable AllAssemblies - { - get - { - yield return Assembly.GetAssembly(typeof(Game)); - - foreach (ModContentPack mod in LoadedModManager.RunningMods) - foreach (Assembly assembly in mod.assemblies.loadedAssemblies) - yield return assembly; + public delegate object Getter(object? instance, object? index); + public delegate void Setter(object? instance, object? value, object? index); - if (Assembly.GetEntryAssembly() != null) - yield return Assembly.GetEntryAssembly(); - } - } + public static IEnumerable AllAssemblies => allAssembliesHook(); private static Dictionary types = new(); private static Dictionary pathTypes = new(); private static Dictionary indexTypes = new(); private static Dictionary getters = new(); - private static Dictionary setters = new(); + private static Dictionary setters = new(); /// /// Get the value of a static property/field in type specified by memberPath /// - public static object GetValueStatic(Type type, string memberPath, object index = null) + public static object GetValueStatic(Type type, string memberPath, object? index = null) { return GetValue(null, type + "/" + memberPath, index); } @@ -45,7 +33,7 @@ public static object GetValueStatic(Type type, string memberPath, object index = /// /// Get the value of a static property/field specified by memberPath /// - public static object GetValueStatic(string memberPath, object index = null) + public static object GetValueStatic(string memberPath, object? index = null) { return GetValue(null, memberPath, index); } @@ -54,7 +42,7 @@ public static object GetValueStatic(string memberPath, object index = null) /// Get the value of a property/field specified by memberPath /// Type specification in path is not required if instance is provided /// - public static object GetValue(object instance, string memberPath, object index = null) + public static object GetValue(object? instance, string memberPath, object? index = null) { if (instance != null) memberPath = AppendType(memberPath, instance.GetType()); @@ -63,21 +51,23 @@ public static object GetValue(object instance, string memberPath, object index = return getters[memberPath](instance, index); } - public static void SetValueStatic(Type type, string memberPath, object value, object index = null) + public static void SetValueStatic(Type type, string memberPath, object? value, object? index = null) { SetValue(null, type + "/" + memberPath, value, index); } - public static void SetValue(object instance, string memberPath, object value, object index = null) + public static void SetValue(object? instance, string memberPath, object? value, object? index = null) { if (instance != null) memberPath = AppendType(memberPath, instance.GetType()); InitPropertyOrField(memberPath); - if (setters[memberPath] == null) + + var setter = setters[memberPath]; + if (setter == null) throw new Exception($"The value of {memberPath} can't be set"); - setters[memberPath](instance, value, index); + setter(instance, value, index); } public static Type PathType(string memberPath) @@ -86,10 +76,10 @@ public static Type PathType(string memberPath) return pathTypes[memberPath]; } - public static Type IndexType(string memberPath) + public static Type? IndexType(string memberPath) { InitPropertyOrField(memberPath); - return indexTypes.TryGetValue(memberPath, out Type indexType) ? indexType : null; + return indexTypes.GetValueOrDefault(memberPath); } /// @@ -122,7 +112,7 @@ private static void InitPropertyOrField(string memberPath) if (parts.Length < 2) throw new Exception($"Path requires at least the type and one member: {memberPath}"); - Type type = GetTypeByName(parts[0]); + Type? type = GetTypeByName(parts[0]); if (type == null) throw new Exception($"Type {parts[0]} not found for path: {memberPath}"); @@ -133,14 +123,14 @@ private static void InitPropertyOrField(string memberPath) for (int i = 1; i < parts.Length; i++) { string part = parts[i]; - MemberInfo memberFound = null; + MemberInfo? memberFound = null; if (part == "[]") { if (currentType.IsArray || currentType == typeof(Array)) { - currentType = currentType.GetElementType(); - memberFound = new ArrayAccess() { ElementType = currentType }; + currentType = currentType.GetElementType()!; + memberFound = new ArrayAccess { ElementType = currentType }; members.Add(memberFound); hasSetter = true; indexTypes[memberPath] = typeof(int); @@ -148,7 +138,8 @@ private static void InitPropertyOrField(string memberPath) continue; } - PropertyInfo indexer = currentType.GetProperties().FirstOrDefault(p => p.GetIndexParameters().Length == 1); + PropertyInfo? indexer = + currentType.GetProperties().FirstOrDefault(p => p.GetIndexParameters().Length == 1); if (indexer == null) continue; Type indexType = indexer.GetIndexParameters()[0].ParameterType; @@ -240,10 +231,7 @@ private static void InitPropertyOrField(string memberPath) if (lastMember is FieldInfo field) { - if (field.IsStatic) - setterGen.Emit(OpCodes.Stsfld, field); - else - setterGen.Emit(OpCodes.Stfld, field); + setterGen.Emit(field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld, field); } else if (lastMember is PropertyInfo prop) { @@ -325,57 +313,57 @@ private static void EmitAccess(Type type, List members, int count, I } } - public static Type GetTypeByName(string name) + public static Type? GetTypeByName(string name) { if (types.TryGetValue(name, out Type cached)) return cached; - Type type = - AllAssemblies.Select(a => a.GetType(name)).AllNotNull().FirstOrDefault() ?? + Type? type = + AllAssemblies.Select(a => a.GetType(name)).FirstOrDefault(t => t != null) ?? Type.GetType(name) ?? - AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetType(name)).AllNotNull().FirstOrDefault(); + AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetType(name)).FirstOrDefault(t => t != null); types[name] = type; return type; } - } - public class ArrayAccess : MemberInfo - { - public Type ElementType { get; set; } + private class ArrayAccess : MemberInfo + { + public required Type ElementType { get; init; } - public override MemberTypes MemberType => throw new NotImplementedException(); + public override MemberTypes MemberType => throw new NotImplementedException(); - public override string Name => throw new NotImplementedException(); + public override string Name => throw new NotImplementedException(); - public override Type DeclaringType => throw new NotImplementedException(); + public override Type DeclaringType => throw new NotImplementedException(); - public override Type ReflectedType => throw new NotImplementedException(); + public override Type ReflectedType => throw new NotImplementedException(); - public override object[] GetCustomAttributes(bool inherit) - { - throw new NotImplementedException(); - } + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotImplementedException(); + } - public override object[] GetCustomAttributes(Type attributeType, bool inherit) - { - throw new NotImplementedException(); - } + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } - public override bool IsDefined(Type attributeType, bool inherit) - { - throw new NotImplementedException(); + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } } } public static class ReflectionExtensions { - public static object GetPropertyOrField(this object obj, string memberPath, object index = null) + public static object GetPropertyOrField(this object? obj, string memberPath, object? index = null) { return MpReflection.GetValue(obj, memberPath, index); } - public static void SetPropertyOrField(this object obj, string memberPath, object value, object index = null) + public static void SetPropertyOrField(this object? obj, string memberPath, object? value, object? index = null) { MpReflection.SetValue(obj, memberPath, value, index); } @@ -385,7 +373,7 @@ public static void SetPropertyOrField(this object obj, string memberPath, object FieldInfo field => field.IsStatic, PropertyInfo prop => prop.GetGetMethod(true).IsStatic, MethodInfo method => method.IsStatic, - TypeInfo type => type.IsAbstract && type.IsSealed, + TypeInfo type => type is { IsAbstract: true, IsSealed: true }, _ => false, }; } diff --git a/Source/MultiplayerLoader/MultiplayerLoader.csproj b/Source/MultiplayerLoader/MultiplayerLoader.csproj index caf2227f..101493ba 100644 --- a/Source/MultiplayerLoader/MultiplayerLoader.csproj +++ b/Source/MultiplayerLoader/MultiplayerLoader.csproj @@ -3,7 +3,7 @@ net472 true - 10 + 12 false false MultiplayerLoader diff --git a/Source/Tests/Helper/TestJoiningState.cs b/Source/Tests/Helper/TestJoiningState.cs index 0563e224..a507e8f6 100644 --- a/Source/Tests/Helper/TestJoiningState.cs +++ b/Source/Tests/Helper/TestJoiningState.cs @@ -24,6 +24,7 @@ protected override async Task RunState() RwVersion, Array.Empty(), Array.Empty(), + RoundModeEnum.ToNearest, RoundModeEnum.ToNearest, Array.Empty() ); diff --git a/Source/Tests/SerializationTest.cs b/Source/Tests/SerializationTest.cs new file mode 100644 index 00000000..7bf1a1cc --- /dev/null +++ b/Source/Tests/SerializationTest.cs @@ -0,0 +1,94 @@ +using Multiplayer.API; +using Multiplayer.Client; +using Multiplayer.Common; + +namespace Tests; + +public class SerializationTest +{ + private static T? RoundtripSerialization(T? obj) + { + var writer = new ByteWriter(); + SyncSerialization.WriteSync(writer, obj); + return SyncSerialization.ReadSync(new ByteReader(writer.ToArray())); + } + + [Test] + public void TestString() + { + Assert.That(RoundtripSerialization("abc"), Is.EqualTo("abc")); + } + + [Test] + public void TestStringList() + { + List input = ["abc", "def"]; + Assert.That(RoundtripSerialization(input), Is.EqualTo(input)); + } + + [Test] + public void TestSyncWorkers() + { + SyncSerialization.syncTree = new SyncWorkerDictionaryTree + { + { + (ByteWriter writer, IA a) => + { + SyncSerialization.WriteSync(writer, a is A); + SyncSerialization.WriteSyncObject(writer, a, a.GetType()); + }, + (ByteReader reader) => + { + if (SyncSerialization.ReadSync(reader)) + return SyncSerialization.ReadSync(reader)!; + return SyncSerialization.ReadSync(reader)!; + } + }, + { + (SyncWorker worker, ref A a) => + { + worker.Bind(ref a.a); + }, true, true + }, + { + (SyncWorker worker, ref Z z) => + { + worker.Bind(ref z.z); + }, true, true + }, + }; + + { + var input = new A { a = 1 }; + Assert.That( + RoundtripSerialization(input), + Is.EqualTo(input) + ); + Assert.That( + RoundtripSerialization(input), + Is.EqualTo(input) + ); + } + + { + var input = new Z { z = 2 }; + Assert.That( + RoundtripSerialization(input), + Is.EqualTo(input) + ); + } + } + + public interface IA; + + // Using structs so that equality is by value which simplifies the test code + public struct A : IA + { + public int a; + } + + public struct Z : IA + { + public int z; + } +} diff --git a/Source/Tests/Tests.csproj b/Source/Tests/Tests.csproj index 64444ef5..5dd8fb59 100644 --- a/Source/Tests/Tests.csproj +++ b/Source/Tests/Tests.csproj @@ -4,7 +4,7 @@ net6.0 enable enable - 11 + 12 false