diff --git a/Extensions/BepInExShared.cs b/Extensions/BepInExShared.cs index 3c6c042..9afd7bd 100644 --- a/Extensions/BepInExShared.cs +++ b/Extensions/BepInExShared.cs @@ -8,28 +8,28 @@ public static class BepInExExtensions public static void SyncConfigEntry(this Messenger messenger, ConfigEntry configEntry) where T : unmanaged { + messenger.ReceiveConfigEntry(configEntry); _syncStates[configEntry] = true; - if (Messenger.IsAuthority) + if (messenger.IsAuthority == true) messenger.SendConfigEntry(configEntry); configEntry.SettingChanged += (sender, args) => { if (_syncStates.TryGetValue(configEntry, out bool value) && value == true) messenger.SendConfigEntry(configEntry); }; - messenger.ReceiveConfigEntry(configEntry); } public static void SyncConfigEntry(this Messenger messenger, ConfigEntry configEntry) { + messenger.ReceiveConfigEntry(configEntry); _syncStates[configEntry] = true; - if (Messenger.IsAuthority) + if (messenger.IsAuthority == true) messenger.SendConfigEntry(configEntry); configEntry.SettingChanged += (sender, args) => { if (_syncStates.TryGetValue(configEntry, out bool value) && value == true) messenger.SendConfigEntry(configEntry); }; - messenger.ReceiveConfigEntry(configEntry); } public static void SendConfigEntry(this Messenger messenger, ConfigEntry configEntry) where T : unmanaged diff --git a/Extensions/InterprocessLib.BepInEx_Extensions/InterprocessLib.BepInEx_Extensions.csproj b/Extensions/InterprocessLib.BepInEx_Extensions/InterprocessLib.BepInEx_Extensions.csproj index cb4dffe..edb1ffb 100644 --- a/Extensions/InterprocessLib.BepInEx_Extensions/InterprocessLib.BepInEx_Extensions.csproj +++ b/Extensions/InterprocessLib.BepInEx_Extensions/InterprocessLib.BepInEx_Extensions.csproj @@ -4,7 +4,7 @@ 1.0.0 Nytra net472 - 12 + 12 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.BepInEx_Extensions InterprocessLib.BepInEx_Extensions @@ -16,13 +16,13 @@ $(ResonitePath)/ $(MSBuildProgramFiles32)\Steam\steamapps\common\Resonite\ $(HOME)/.steam/steam/steamapps/common/Resonite/ - G:\SteamLibrary\steamapps\common\Resonite\ - $(HOME)/snap/steam/common/.local/share/Steam/steamapps/common/Resonite/ + G:\SteamLibrary\steamapps\common\Resonite\ + $(HOME)/snap/steam/common/.local/share/Steam/steamapps/common/Resonite/ Debug;Release;Tests - + $(GamePath)Renderer\BepInEx\core\0Harmony.dll @@ -34,7 +34,7 @@ $(GamePath)Renderer\Renderite.Renderer_Data\Managed\Renderite.Unity.dll - + $(GamePath)Renderer\Renderite.Renderer_Data\Managed\Renderite.Shared.dll @@ -43,7 +43,7 @@ $(GamePath)Renderer\Renderite.Renderer_Data\Managed\UnityEngine.CoreModule.dll - + @@ -51,7 +51,7 @@ - + diff --git a/Extensions/InterprocessLib.BepisLoader_Extensions/InterprocessLib.BepisLoader_Extensions.csproj b/Extensions/InterprocessLib.BepisLoader_Extensions/InterprocessLib.BepisLoader_Extensions.csproj index e8fb992..840b216 100644 --- a/Extensions/InterprocessLib.BepisLoader_Extensions/InterprocessLib.BepisLoader_Extensions.csproj +++ b/Extensions/InterprocessLib.BepisLoader_Extensions/InterprocessLib.BepisLoader_Extensions.csproj @@ -3,8 +3,8 @@ 1.0.0 Nytra - net9.0 - 13 + net10.0 + 14 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.BepisLoader_Extensions InterprocessLib.BepisLoader_Extensions @@ -16,7 +16,8 @@ $(ResonitePath)/ $(MSBuildProgramFiles32)\Steam\steamapps\common\Resonite\ $(HOME)/.steam/steam/steamapps/common/Resonite/ - G:\SteamLibrary\steamapps\common\Resonite\ + G:\SteamLibrary\steamapps\common\Resonite\ + $(HOME)/snap/steam/common/.local/share/Steam/steamapps/common/Resonite/ https://nuget-modding.resonite.net/v3/index.json; @@ -49,7 +50,7 @@ - + @@ -62,8 +63,8 @@ - - + + diff --git a/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj b/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj index 5c40f1a..4855418 100644 --- a/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj +++ b/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj @@ -3,8 +3,8 @@ 1.0.0 Nytra - net9.0 - 13 + net10.0 + 14 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.RML_Extensions InterprocessLib.RML_Extensions @@ -17,8 +17,9 @@ $(ResonitePath)/ $(MSBuildProgramFiles32)\Steam\steamapps\common\Resonite\ $(HOME)/.steam/steam/steamapps/common/Resonite/ - G:\SteamLibrary\steamapps\common\Resonite\ - Debug;Release;Tests + G:\SteamLibrary\steamapps\common\Resonite\ + $(HOME)/snap/steam/common/.local/share/Steam/steamapps/common/Resonite/ + Debug;Release;Tests @@ -39,7 +40,7 @@ $(GamePath)Renderite.Shared.dll False - + $(GamePath)Libraries/ResoniteModLoader.dll False @@ -55,8 +56,8 @@ - - + + diff --git a/Extensions/InterprocessLib.RML_Extensions/RML_Extensions.cs b/Extensions/InterprocessLib.RML_Extensions/RML_Extensions.cs index bade922..1fce37f 100644 --- a/Extensions/InterprocessLib.RML_Extensions/RML_Extensions.cs +++ b/Extensions/InterprocessLib.RML_Extensions/RML_Extensions.cs @@ -8,28 +8,28 @@ public static class RML_Extensions public static void SyncConfigEntry(this Messenger messenger, ModConfigurationKey configEntry) where T : unmanaged { + messenger.ReceiveConfigEntry(configEntry); _syncStates[configEntry] = true; - if (Messenger.IsAuthority) + if (messenger.IsAuthority == true) messenger.SendConfigEntry(configEntry); configEntry.OnChanged += (object? newValue) => { if (_syncStates.TryGetValue(configEntry, out bool value) && value == true) messenger.SendConfigEntry(configEntry); }; - messenger.ReceiveConfigEntry(configEntry); } public static void SyncConfigEntry(this Messenger messenger, ModConfigurationKey configEntry) { + messenger.ReceiveConfigEntry(configEntry); _syncStates[configEntry] = true; - if (Messenger.IsAuthority) + if (messenger.IsAuthority == true) messenger.SendConfigEntry(configEntry); configEntry.OnChanged += (object? newValue) => { if (_syncStates.TryGetValue(configEntry, out bool value) && value == true) messenger.SendConfigEntry(configEntry); }; - messenger.ReceiveConfigEntry(configEntry); } public static void SendConfigEntry(this Messenger messenger, ModConfigurationKey configEntry) where T : unmanaged diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index 9515fdc..8e39ecb 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -1,54 +1,88 @@ using Elements.Core; -using FrooxEngine; using Renderite.Shared; -using System.Reflection; namespace InterprocessLib; -internal static class FrooxEngineInit +internal class FrooxEnginePool : IMemoryPackerEntityPool { - private static void CommandHandler(RendererCommand command, int messageSize) + public static readonly FrooxEnginePool Instance = new(); + + T IMemoryPackerEntityPool.Borrow() + { + return Pool.Borrow(); + } + + void IMemoryPackerEntityPool.Return(T value) { + Pool.ReturnCleaned(ref value); } +} + +internal static class FrooxEngineInit +{ public static void Init() { - if (Messenger.Host is not null) - throw new InvalidOperationException("Messenger has already been initialized!"); + if (Messenger.DefaultInitStarted) + throw new InvalidOperationException("Messenger default backend initialization has already been started!"); + + Messenger.DefaultInitStarted = true; - Task.Run(InitLoop); + InnerInit(); } - private static async void InitLoop() + private static void InnerInit() { - if (Engine.Current?.RenderSystem?.Engine is null) + var args = Environment.GetCommandLineArgs(); + string? queueName = null; + for (int i = 0; i < args.Length; i++) { - await Task.Delay(1); - InitLoop(); + if (args[i].Equals("-shmprefix", StringComparison.InvariantCultureIgnoreCase)) + { + queueName = args[i + 1]; + break; + } } - else + + Messenger.OnWarning = (msg) => + { + UniLog.Warning($"[InterprocessLib] [WARN] {msg}"); + }; + Messenger.OnFailure = (ex) => + { + UniLog.Error($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Backend!\n{ex}"); + }; +#if DEBUG + Messenger.OnDebug = (msg) => { - await Task.Delay(100); + UniLog.Log($"[InterprocessLib] [DEBUG] {msg}"); + }; +#endif - var renderSystemMessagingHost = (RenderiteMessagingHost?)typeof(RenderSystem).GetField("_messagingHost", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(Engine.Current!.RenderSystem); - if (renderSystemMessagingHost is null) - throw new InvalidOperationException("Engine is not configured to use a renderer!"); + MessagingSystem? system = null; - Messenger.OnWarning = (msg) => - { - UniLog.Warning($"[InterprocessLib] [WARN] {msg}"); - }; - Messenger.OnFailure = (ex) => - { - UniLog.Error($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Host!\n{ex}"); - }; - #if DEBUG - Messenger.OnDebug = (msg) => + if (queueName is null) + { + Messenger.OnDebug?.Invoke("Shared memory queue name is null! Attempting to use fallback..."); + var task = Messenger.GetFallbackSystem("Resonite", true, MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + task.Wait(); + system = task.Result; + if (system is null) { - UniLog.Log($"[InterprocessLib] [DEBUG] {msg}"); - }; - #endif - Messenger.IsAuthority = true; - Messenger.Host = new MessagingHost(Messenger.IsAuthority, renderSystemMessagingHost!.QueueName, renderSystemMessagingHost.QueueCapacity, renderSystemMessagingHost, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); - Messenger.FinishInitialization(); + throw new EntryPointNotFoundException("Unable to get fallback messaging system!"); + } + } + else + { + system = new MessagingSystem(true, $"InterprocessLib-{queueName}", MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + system.Connect(); + } + + lock (Messenger.LockObj) + { + Messenger.PreInit(system); + Messenger.SetDefaultSystem(system); + system.Initialize(); } + + //Engine.Current.OnShutdown += system.Dispose; } } \ No newline at end of file diff --git a/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj b/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj index f7be331..6ab9139 100644 --- a/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj +++ b/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj @@ -1,23 +1,25 @@ - 2.0.1 + 3.0.0 Nytra - net9.0 - 13 + net10.0 + 14 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.FrooxEngine InterprocessLib.FrooxEngine InterprocessLib enable enable + Nullable true false $(ResonitePath)/ $(MSBuildProgramFiles32)\Steam\steamapps\common\Resonite\ $(HOME)/.steam/steam/steamapps/common/Resonite/ - G:\SteamLibrary\steamapps\common\Resonite\ - Debug;Release;Tests + G:\SteamLibrary\steamapps\common\Resonite\ + $(HOME)/snap/steam/common/.local/share/Steam/steamapps/common/Resonite/ + Debug;Release;Tests @@ -30,26 +32,26 @@ $(GamePath)Renderite.Shared.dll False - + $(GamePath)FrooxEngine.dll False - + $(GamePath)Elements.Core.dll False - + - - + + diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index bf28a35..cb814ad 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -6,26 +6,21 @@ namespace InterprocessLib; // IMPORTANT: // RendererCommand derived classes MUST NOT have constructors because it breaks Unity for some reason -internal abstract class IdentifiableCommand : RendererCommand +internal abstract class IdentifiableCommand : IMemoryPackable { - internal string Owner = ""; - public string Id = ""; + public string? Owner; + public string? Id; - public static void InitNewTypes(List types) - { - InitTypes(types); - } - - public override void Pack(ref MemoryPacker packer) + public virtual void Pack(ref MemoryPacker packer) { - packer.Write(Owner); - packer.Write(Id); + packer.Write(Owner!); + packer.Write(Id!); } - public override void Unpack(ref MemoryUnpacker unpacker) + public virtual void Unpack(ref MemoryUnpacker unpacker) { - unpacker.Read(ref Owner); - unpacker.Read(ref Id); + unpacker.Read(ref Owner!); + unpacker.Read(ref Id!); } public override string ToString() @@ -37,12 +32,12 @@ public override string ToString() internal abstract class CollectionCommand : IdentifiableCommand { public abstract IEnumerable? UntypedCollection { get; } - public abstract Type InnerDataType { get; } + public abstract Type StoredType { get; } public abstract Type CollectionType { get; } public override string ToString() { - return $"CollectionCommand:{CollectionType.Name}<{InnerDataType.Name}>:{Owner}:{Id}:{UntypedCollection?.ToString() ?? "NULL"}"; + return $"CollectionCommand:{CollectionType.Name}<{StoredType.Name}>:{Owner}:{Id}:{UntypedCollection?.ToString() ?? "NULL"}"; } } @@ -70,90 +65,407 @@ public override string ToString() internal sealed class EmptyCommand : IdentifiableCommand { - // owo - public override string ToString() { return $"EmptyCommand:{Owner}:{Id}"; } } -internal sealed class ValueCollectionCommand : CollectionCommand where C : ICollection, new() where T : unmanaged +internal sealed class ValueCollectionCommand : CollectionCommand where C : ICollection?, new() where T : unmanaged { public C? Values; public override IEnumerable? UntypedCollection => Values; - public override Type InnerDataType => typeof(T); + + public override Type StoredType => typeof(T); + public override Type CollectionType => typeof(C); public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); -#pragma warning disable CS8604 - packer.WriteValueList(Values); -#pragma warning restore + var len = Values?.Count ?? -1; + packer.Write(len); + if (Values != null) + { + foreach (var value in Values) + { + packer.Write(value); + } + } } public override void Unpack(ref MemoryUnpacker unpacker) { base.Unpack(ref unpacker); -#pragma warning disable CS8601 - unpacker.ReadValueList(ref Values); -#pragma warning restore + int len = 0; + unpacker.Read(ref len); + if (len == -1) + { + Values = default; + return; + } + Values = new C(); // ToDo: use pool borrowing here? + for (int i = 0; i < len; i++) + { + T val = default; + unpacker.Read(ref val); + Values.Add(val); + } } } -internal sealed class StringListCommand : CollectionCommand +internal sealed class ValueArrayCommand : CollectionCommand where T : unmanaged { - public List? Values; + public T[]? Values; public override IEnumerable? UntypedCollection => Values; - public override Type InnerDataType => typeof(string); - public override Type CollectionType => typeof(List); + + public override Type StoredType => typeof(T); + + public override Type CollectionType => typeof(T[]); public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); -#pragma warning disable CS8604 - packer.WriteStringList(Values); -#pragma warning restore + var len = Values?.Length ?? -1; + packer.Write(len); + if (Values != null) + { + Span data = packer.Access(len); + Values.CopyTo(data); + } } public override void Unpack(ref MemoryUnpacker unpacker) { base.Unpack(ref unpacker); -#pragma warning disable CS8601 - unpacker.ReadStringList(ref Values); -#pragma warning restore + int len = 0; + unpacker.Read(ref len); + if (len == -1) + { + Values = null; + return; + } + Values = new T[len]; // ToDo: use pool borrowing here? + ReadOnlySpan data = unpacker.Access(len); + data.CopyTo(Values); + } +} + +internal sealed class TypeRegistrationCommand : TypeCommand +{ + public override string ToString() + { + return "TypeRegistrationCommand: " + Type?.FullName ?? "NULL"; } } -internal sealed class ObjectListCommand : CollectionCommand where T : class, IMemoryPackable, new() +internal class TypeCommand : IMemoryPackable { - public List? Values; + public Type? Type; + private static Dictionary _typeCache = new(); - public override IEnumerable? UntypedCollection => Values; - public override Type InnerDataType => typeof(T); - public override Type CollectionType => typeof(List); + public void Pack(ref MemoryPacker packer) + { + PackType(Type, ref packer); + } + + public void Unpack(ref MemoryUnpacker unpacker) + { + Type = UnpackType(ref unpacker); + } + + private void PackType(Type? type, ref MemoryPacker packer) + { + if (type is null) + { + packer.Write(false); + } + else + { + packer.Write(true); + if (type.IsGenericType) + { + packer.Write(true); + var genericTypeDefinition = type.GetGenericTypeDefinition(); + packer.Write(genericTypeDefinition.FullName!); + var typeArgs = type.GetGenericArguments(); + packer.Write(typeArgs.Length); + foreach (var typeArg in typeArgs) + { + PackType(typeArg, ref packer); + } + } + else + { + packer.Write(false); + packer.Write(type.FullName!); + } + } + } + + private Type? UnpackType(ref MemoryUnpacker unpacker) + { + var hasType = unpacker.Read(); + if (!hasType) return null; + + var isGenericType = unpacker.Read(); + if (isGenericType) + { + var genericTypeDefinitionName = unpacker.ReadString(); + int numTypeArgs = unpacker.Read(); + var typeArgs = new Type?[numTypeArgs]; + for (int i = 0; i < numTypeArgs; i++) + { + typeArgs[i] = UnpackType(ref unpacker); + } + + if (typeArgs.Any(t => t is null)) return null; + + var genericTypeDefinition = FindType(genericTypeDefinitionName); + if (genericTypeDefinition != null) + { + return genericTypeDefinition.MakeGenericType(typeArgs!); + } + else + { + return null; + } + } + else + { + var typeString = unpacker.ReadString(); + return FindType(typeString); + } + } + + private Type? FindType(string typeString) + { + //Messenger.OnDebug?.Invoke($"Looking for Type: {typeString}"); + if (_typeCache.TryGetValue(typeString, out var type)) + { + Messenger.OnDebug?.Invoke($"Found Type in cache: {type.FullName}"); + return type; + } + + type = Type.GetType(typeString); + if (type is null) + { + foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) + { + type = asm.GetType(typeString); + if (type != null) break; + } + } + if (type != null) + { + Messenger.OnDebug?.Invoke($"Found Type to add to cache: {type.FullName}"); + _typeCache[typeString] = type; + } + else + { + Messenger.OnWarning?.Invoke($"Could not find the Type: {typeString}"); + } + return type; + } + + public override string ToString() + { + return $"TypeCommand: {Type?.FullName ?? "NULL"}"; + } +} + +internal sealed class StringArrayCommand : CollectionCommand +{ + public string?[]? Strings; + + public override IEnumerable? UntypedCollection => Strings; + public override Type StoredType => typeof(string); + public override Type CollectionType => typeof(string[]); + + public override void Pack(ref MemoryPacker packer) + { + base.Pack(ref packer); + if (Strings is null) + { + packer.Write(-1); + return; + } + int len = Strings.Length; + packer.Write(len); + foreach (var str in Strings) + { + packer.Write(str!); + } + } + + public override void Unpack(ref MemoryUnpacker unpacker) + { + base.Unpack(ref unpacker); + int len = 0; + unpacker.Read(ref len); + if (len == -1) + { + Strings = null; + return; + } + Strings = new string[len]; // ToDo: use pool borrowing here? + for (int i = 0; i < len; i++) + { + unpacker.Read(ref Strings[i]!); + } + } +} + +internal sealed class StringCollectionCommand : CollectionCommand where C : ICollection?, new() +{ + public IReadOnlyCollection? Strings; // IReadOnlyCollection is required for covariance of string? and string + + public override IEnumerable? UntypedCollection => Strings; + public override Type StoredType => typeof(string); + public override Type CollectionType => typeof(C); + + public override void Pack(ref MemoryPacker packer) + { + base.Pack(ref packer); + if (Strings is null) + { + packer.Write(-1); + return; + } + int len = Strings.Count; + packer.Write(len); + foreach (var str in Strings) + { + packer.Write(str!); + } + } + + public override void Unpack(ref MemoryUnpacker unpacker) + { + base.Unpack(ref unpacker); + int len = 0; + unpacker.Read(ref len); + if (len == -1) + { + Strings = default; + return; + } + var collection = new C(); // ToDo: use pool borrowing here? + for (int i = 0; i < len; i++) + { + string? str = default; + unpacker.Read(ref str!); + collection.Add(str); + } + Strings = (IReadOnlyCollection?)collection; + } +} + +internal sealed class ObjectCollectionCommand : CollectionCommand where C : ICollection?, new() where T : class?, IMemoryPackable?, new() +{ + public C? Objects; + + public override IEnumerable? UntypedCollection => Objects; + public override Type StoredType => typeof(T); + public override Type CollectionType => typeof(C); + + public override void Pack(ref MemoryPacker packer) + { + base.Pack(ref packer); + if (Objects is null) + { + packer.Write(-1); + return; + } + int len = Objects.Count; + packer.Write(len); + foreach (var obj in Objects) + { +#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type. +#pragma warning disable CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. + packer.WriteObject(obj); +#pragma warning restore CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. +#pragma warning restore CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type. + } + } + + public override void Unpack(ref MemoryUnpacker unpacker) + { + base.Unpack(ref unpacker); + int len = 0; + unpacker.Read(ref len); + if (len == -1) + { + Objects = default; + return; + } + Objects = new C(); // ToDo: use pool borrowing here? + for (int i = 0; i < len; i++) + { + T obj = new(); +#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type. +#pragma warning disable CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. + unpacker.ReadObject(ref obj!); +#pragma warning restore CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. +#pragma warning restore CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type. + Objects.Add(obj); + } + } +} + +internal sealed class ObjectArrayCommand : CollectionCommand where T : class?, IMemoryPackable?, new() +{ + public T[]? Objects; + + public override IEnumerable? UntypedCollection => Objects; + public override Type StoredType => typeof(T); + public override Type CollectionType => typeof(T[]); public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); -#pragma warning disable CS8604 - packer.WriteObjectList(Values); -#pragma warning restore + if (Objects is null) + { + packer.Write(-1); + return; + } + int len = Objects.Length; + packer.Write(len); + foreach (var obj in Objects) + { +#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type. +#pragma warning disable CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. + packer.WriteObject(obj); +#pragma warning restore CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. +#pragma warning restore CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type. + } } public override void Unpack(ref MemoryUnpacker unpacker) { base.Unpack(ref unpacker); -#pragma warning disable CS8601 - unpacker.ReadObjectList(ref Values); -#pragma warning restore + int len = 0; + unpacker.Read(ref len); + if (len == -1) + { + Objects = null; + return; + } + Objects = new T[len]; // ToDo: use pool borrowing here? + for (int i = 0; i < len; i++) + { +#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type. +#pragma warning disable CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. + unpacker.ReadObject(ref Objects[i]!); +#pragma warning restore CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. +#pragma warning restore CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type. + } } } -internal sealed class ObjectCommand : ObjectCommand where T : class, IMemoryPackable, new() +internal sealed class ObjectCommand : ObjectCommand where T : class?, IMemoryPackable?, new() { public T? Object; @@ -163,14 +475,22 @@ public override void Unpack(ref MemoryUnpacker unpacker) public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); - packer.WriteObject(Object); - } +#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type. +#pragma warning disable CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. + packer.WriteObject(Object); +#pragma warning restore CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. +#pragma warning restore CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type. + } public override void Unpack(ref MemoryUnpacker unpacker) { base.Unpack(ref unpacker); - unpacker.ReadObject(ref Object); - } +#pragma warning disable CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type. +#pragma warning disable CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. + unpacker.ReadObject(ref Object); +#pragma warning restore CS8634 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match 'class' constraint. +#pragma warning restore CS8631 // The type cannot be used as type parameter in the generic type or method. Nullability of type argument doesn't match constraint type. + } } internal sealed class ValueCommand : ValueCommand where T : unmanaged @@ -200,17 +520,13 @@ internal sealed class StringCommand : IdentifiableCommand public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); -#pragma warning disable CS8604 - packer.Write(String); -#pragma warning restore + packer.Write(String!); } public override void Unpack(ref MemoryUnpacker unpacker) { base.Unpack(ref unpacker); -#pragma warning disable CS8601 - unpacker.Read(ref String); -#pragma warning restore + unpacker.Read(ref String!); } public override string ToString() @@ -219,18 +535,84 @@ public override string ToString() } } -internal sealed class MessengerReadyCommand : RendererCommand +internal sealed class MessengerReadyCommand : IMemoryPackable { - public override void Pack(ref MemoryPacker packer) + public void Pack(ref MemoryPacker packer) { } - public override void Unpack(ref MemoryUnpacker unpacker) + public void Unpack(ref MemoryUnpacker unpacker) + { + } +} + +internal sealed class PingCommand : IMemoryPackable +{ + public DateTime SentTime; + public DateTime? ReceivedTime; + public void Pack(ref MemoryPacker packer) { + packer.Write(SentTime); + packer.Write(ReceivedTime); } - public override string ToString() + public void Unpack(ref MemoryUnpacker unpacker) + { + unpacker.Read(ref SentTime); + unpacker.Read(ref ReceivedTime); + } +} + +internal sealed class WrapperCommand : RendererCommand +{ + public int TypeIndex; + public string? QueueName; + public IMemoryPackable? Packable; + + public static void InitNewTypes(List types) + { + InitTypes(types); + } + + public override void Pack(ref MemoryPacker packer) { - return $"MessengerReadyCommand"; + if (QueueName is null) throw new ArgumentNullException(nameof(QueueName)); + + var system = MessagingSystem.TryGetRegisteredSystem(QueueName!); + + if (system is null) throw new InvalidOperationException($"MessagingSystem with QueueName: {QueueName} is not registered."); + + if (Packable is null) + { + packer.Write(-1); + return; + } + + var packedType = Packable.GetType(); + packer.Write(system.OutgoingTypeManager.GetTypeIndex(packedType)); + packer.Write(QueueName!); + Packable.Pack(ref packer); + system.OutgoingTypeManager.Return(packedType, Packable); + } + + public override void Unpack(ref MemoryUnpacker unpacker) + { + unpacker.Read(ref TypeIndex); + + if (TypeIndex == -1) + { + Packable = null; + return; + } + + unpacker.Read(ref QueueName!); + + var backend = MessagingSystem.TryGetRegisteredSystem(QueueName); + + if (backend is null) throw new InvalidDataException($"MessagingSystem with QueueName: {QueueName} is not registered."); + + var type = backend!.IncomingTypeManager.GetTypeFromIndex(TypeIndex); + Packable = backend.IncomingTypeManager.Borrow(type); + Packable.Unpack(ref unpacker); } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Host.cs b/InterprocessLib.Shared/Host.cs deleted file mode 100644 index 41626f2..0000000 --- a/InterprocessLib.Shared/Host.cs +++ /dev/null @@ -1,301 +0,0 @@ -using Renderite.Shared; -using System.Reflection; - -namespace InterprocessLib; - -internal class MessagingHost -{ - private struct OwnerData - { - public readonly Dictionary ValueCallbacks = new(); - - public readonly Dictionary?> StringCallbacks = new(); - - public readonly Dictionary EmptyCallbacks = new(); - - public readonly Dictionary ObjectCallbacks = new(); - - public readonly Dictionary ValueCollectionCallbacks = new(); - - public readonly Dictionary?>?> StringListCallbacks = new(); - - public readonly Dictionary ObjectListCallbacks = new(); - - public OwnerData() - { - } - } - - public bool IsAuthority { get; } - - public string QueueName { get; } - - public long QueueCapacity { get; } - - private MessagingManager _primary; - - private static MethodInfo? _handleValueCommandMethod = typeof(MessagingHost).GetMethod(nameof(HandleValueCommand), BindingFlags.Instance | BindingFlags.NonPublic); - - private static MethodInfo? _handleValueCollectionCommandMethod = typeof(MessagingHost).GetMethod(nameof(HandleValueCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic); - - private static MethodInfo? _handleObjectCommandMethod = typeof(MessagingHost).GetMethod(nameof(HandleObjectCommand), BindingFlags.Instance | BindingFlags.NonPublic); - - private static MethodInfo? _handleObjectListCommandMethod = typeof(MessagingHost).GetMethod(nameof(HandleObjectListCommand), BindingFlags.Instance | BindingFlags.NonPublic); - - private RenderCommandHandler? OnCommandReceived { get; } - - private Action? OnWarning { get; } - - private Action? OnDebug { get; } - - private Action? OnFailure { get; } - - private Dictionary _ownerData = new(); - - public void RegisterOwner(string ownerName) - { - var ownerData = new OwnerData(); - _ownerData.Add(ownerName, ownerData); - } - - public void RegisterValueCallback(string owner, string id, Action callback) where T : unmanaged - { - _ownerData[owner].ValueCallbacks[id] = callback; - } - - public void RegisterValueCollectionCallback(string owner, string id, Action callback) where C : ICollection, new() where T : unmanaged - { - _ownerData[owner].ValueCollectionCallbacks[id] = callback; - } - - public void RegisterStringCallback(string owner, string id, Action callback) - { - _ownerData[owner].StringCallbacks[id] = callback; - } - - public void RegisterStringListCallback(string owner, string id, Action?>? callback) - { - _ownerData[owner].StringListCallbacks[id] = callback; - } - - public void RegisterEmptyCallback(string owner, string id, Action callback) - { - _ownerData[owner].EmptyCallbacks[id] = callback; - } - - public void RegisterObjectCallback(string owner, string id, Action callback) where T : class, IMemoryPackable, new() - { - _ownerData[owner].ObjectCallbacks[id] = callback; - } - - public void RegisterObjectListCallback(string owner, string id, Action> callback) where T : class, IMemoryPackable, new() - { - _ownerData[owner].ObjectListCallbacks[id] = callback; - } - - static MessagingHost() - { - TypeManager.InitializeCoreTypes(); - } - - public MessagingHost(bool isAuthority, string queueName, long queueCapacity, IMemoryPackerEntityPool pool, RenderCommandHandler? commandHandler, Action? failhandler, Action? warnHandler, Action? debugHandler) - { - IsAuthority = isAuthority; - QueueName = queueName + "InterprocessLib"; - QueueCapacity = queueCapacity; - - OnDebug = debugHandler; - OnWarning = warnHandler; - OnFailure = failhandler; - OnCommandReceived = commandHandler; - - _primary = new MessagingManager(pool); - _primary.CommandHandler = CommandHandler; - _primary.FailureHandler = (ex) => - { - OnFailure?.Invoke(ex); - }; - _primary.WarningHandler = (msg) => - { - OnWarning?.Invoke(msg); - }; - - _primary.Connect(queueName, isAuthority, queueCapacity); - } - - private void HandleValueCommand(ValueCommand command) where T : unmanaged - { - if (_ownerData[command.Owner].ValueCallbacks.TryGetValue(command.Id, out var callback)) - { - if (callback != null) - { - ((Action)callback).Invoke(command.Value); - } - } - else - { - OnWarning?.Invoke($"ValueCommand<{typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); - } - } - - private void HandleValueCollectionCommand(ValueCollectionCommand command) where C : ICollection, new() where T : unmanaged - { - if (_ownerData[command.Owner].ValueCollectionCallbacks.TryGetValue(command.Id, out var callback)) - { - if (callback != null) - { - ((Action)callback).Invoke(command.Values); - } - } - else - { - OnWarning?.Invoke($"ValueCollectionCommand<{typeof(C).Name}, {typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); - } - } - - private void HandleStringCommand(StringCommand command) - { - if (_ownerData[command.Owner].StringCallbacks.TryGetValue(command.Id, out var callback)) - { - if (callback != null) - { - callback.Invoke(command.String); - } - } - else - { - OnWarning?.Invoke($"StringCommand with Id \"{command.Id}\" is not registered to receive a callback!"); - } - } - - private void HandleStringListCommand(StringListCommand command) - { - if (_ownerData[command.Owner].StringListCallbacks.TryGetValue(command.Id, out var callback)) - { - if (callback != null) - { - callback.Invoke(command.Values); - } - } - else - { - OnWarning?.Invoke($"StringListCommand with Id \"{command.Id}\" is not registered to receive a callback!"); - } - } - - private void HandleEmptyCommand(EmptyCommand command) - { - if (_ownerData[command.Owner].EmptyCallbacks.TryGetValue(command.Id, out var callback)) - { - if (callback != null) - { - callback.Invoke(); - } - } - else - { - OnWarning?.Invoke($"EmptyCommand with Id \"{command.Id}\" is not registered to receive a callback!"); - } - } - - private void HandleObjectCommand(ObjectCommand command) where T : class, IMemoryPackable, new() - { - if (_ownerData[command.Owner].ObjectCallbacks.TryGetValue(command.Id, out var callback)) - { - if (callback != null) - { - ((Action)callback).Invoke((T?)command.UntypedObject); - } - } - else - { - OnWarning?.Invoke($"ObjectCommand<{command.ObjectType.Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); - } - } - - private void HandleObjectListCommand(ObjectListCommand command) where T : class, IMemoryPackable, new() - { - if (_ownerData[command.Owner].ObjectListCallbacks.TryGetValue(command.Id, out var callback)) - { - if (callback != null) - { - ((Action?>)callback).Invoke(command.Values); - } - } - else - { - OnWarning?.Invoke($"ObjectListCommand<{typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); - } - } - - private void CommandHandler(RendererCommand command, int messageSize) - { - OnDebug?.Invoke($"Received {command}"); - - OnCommandReceived?.Invoke(command, messageSize); - - if (command is IdentifiableCommand identifiableCommand) - { - if (!_ownerData.TryGetValue(identifiableCommand.Owner, out var data)) - { - OnWarning?.Invoke($"Owner \"{identifiableCommand.Owner}\" is not registered!"); - return; - } - } - - if (command is ValueCommand valueCommand) - { - var valueType = valueCommand.ValueType; - var typedMethod = _handleValueCommandMethod!.MakeGenericMethod(valueType); - typedMethod.Invoke(this, [command]); - } - else if (command is CollectionCommand collectionCommand) - { - var innerDataType = collectionCommand.InnerDataType; - if (innerDataType == typeof(string)) - { - HandleStringListCommand((StringListCommand)collectionCommand); - } - else if (innerDataType.IsValueType) - { - var collectionType = collectionCommand.CollectionType; - var typedMethod = _handleValueCollectionCommandMethod!.MakeGenericMethod(collectionType, innerDataType); - typedMethod.Invoke(this, [command]); - } - else - { - var typedMethod = _handleObjectListCommandMethod!.MakeGenericMethod(innerDataType); - typedMethod.Invoke(this, [command]); - } - } - else if (command is ObjectCommand objectCommand) - { - var objectType = objectCommand.ObjectType; - var typedMethod = _handleObjectCommandMethod!.MakeGenericMethod(objectType); - typedMethod.Invoke(this, [command]); - } - else - { - switch (command) - { - case StringCommand: - HandleStringCommand((StringCommand)command); - break; - case EmptyCommand: - HandleEmptyCommand((EmptyCommand)command); - break; - case IdentifiableCommand unknownCommand: - OnWarning?.Invoke($"Received unrecognized IdentifiableCommand of type {command.GetType().Name}: {unknownCommand.Owner}:{unknownCommand.Id}"); - break; - default: - break; - } - } - } - - public void SendCommand(RendererCommand command) - { - OnDebug?.Invoke($"Sending {command}"); - _primary.SendCommand(command); - } -} \ No newline at end of file diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index b42723f..b168463 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -1,53 +1,153 @@ using Renderite.Shared; -using System.Runtime.InteropServices; namespace InterprocessLib; /// /// Simple interprocess messaging API. /// -public class Messenger +public class Messenger : IDisposable { - internal static MessagingHost? Host; + private static MessagingSystem? _defaultSystem; + + private MessagingSystem? _customSystem; + + private MessagingSystem? CurrentSystem => _customSystem ?? _defaultSystem; + + /// + /// The underlying interprocess queue name for this instance + /// + public string? QueueName => CurrentSystem?.QueueName; + + /// + /// The capacity of the underlying interprocess queue for this instance + /// + public long? QueueCapacity => CurrentSystem?.QueueCapacity; + + /// + /// If true the messenger will send commands immediately, otherwise commands will wait in a queue until the non-authority process initializes its interprocess connection. + /// + public bool IsInitialized => CurrentSystem?.IsInitialized ?? false; /// - /// If true the messenger will send commands immediately, otherwise commands will wait in a queue until the authority process sends the . + /// Does this process have authority over the other process? Might be null if the library has not fully initialized yet /// - public static bool IsInitialized => Host is not null && _postInitActions is null; + public bool? IsAuthority => CurrentSystem?.IsAuthority; /// - /// Does this process have authority over the other process. + /// Is the interprocess connection available? this might be false if the library has not fully initialized, or if there has been a failure in the interprocess queue /// - public static bool IsAuthority { get; internal set; } + public bool IsConnected => CurrentSystem?.IsConnected ?? false; - internal static bool InitStarted = false; + internal static bool DefaultInitStarted = false; + + /// + /// Called when the backend connection has a critical error + /// + public static Action? OnFailure; - internal static Action? OnFailure; + /// + /// Called when something potentially bad/unexpected happens + /// + public static Action? OnWarning; - internal static Action? OnWarning; + /// + /// Called with additional debugging information + /// + public static Action? OnDebug; -#pragma warning disable CS0649 - internal static Action? OnDebug; -#pragma warning restore + private static List? _defaultPostInitActions = new(); - private static List? _postInitActions = new(); + private static List>? _defaultPreInitActions = new(); private string _ownerId; - private static HashSet _registeredOwnerIds = new(); + private static MessagingSystem? _fallbackSystem = null; + + private static bool _runningFallbackSystemInit = false; + + internal static readonly object LockObj = new(); + + internal static async Task GetFallbackSystem(string ownerId, bool isAuthority, long queueCapacity, IMemoryPackerEntityPool? pool = null, RenderCommandHandler? commandHandler = null, Action? failhandler = null, Action? warnHandler = null, Action? debugHandler = null, Action? postInitCallback = null) + { + OnDebug?.Invoke("GetFallbackSystem called"); + + var startTime = DateTime.UtcNow; + int waitTimeMs = 5000; + while (_runningFallbackSystemInit && (DateTime.UtcNow - startTime).TotalMilliseconds < waitTimeMs * 2) + await Task.Delay(1); - private List? _additionalObjectTypes; + if (_fallbackSystem is not null) return _fallbackSystem; - private List? _additionalValueTypes; + _runningFallbackSystemInit = true; + + var now = DateTime.UtcNow; + int minuteInDay = now.Hour * 60 + now.Minute; + var system1 = new MessagingSystem(isAuthority, $"InterprocessLib-{ownerId}{minuteInDay}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); + system1.Connect(); + if (isAuthority) + { + _fallbackSystem = system1; + _runningFallbackSystemInit = false; + return system1; + } + var cancel1 = new CancellationTokenSource(); + system1.PingCallback = (ping) => + { + cancel1.Cancel(); + }; + system1.SendPackable(new PingCommand() { SentTime = now }); + try + { + await Task.Delay(waitTimeMs, cancel1.Token); + } + catch (TaskCanceledException) + { + } + if (cancel1.IsCancellationRequested) + { + _fallbackSystem = system1; + } + else + { + // try the previous minute, in case the other process started just before the minute ticked over (too bad if it ticked over from 1439 to 0) + system1.Dispose(); + var cancel2 = new CancellationTokenSource(); + var system2 = new MessagingSystem(isAuthority, $"InterprocessLib-{ownerId}{minuteInDay - 1}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); + system2.Connect(); + system2.PingCallback = (ping) => + { + cancel2.Cancel(); + }; + system2.SendPackable(new PingCommand() { SentTime = now }); + try + { + await Task.Delay(waitTimeMs, cancel2.Token); + } + catch (TaskCanceledException) + { + } + if (cancel2.IsCancellationRequested) + { + _fallbackSystem = system2; + } + else + { + system2.Dispose(); + } + } + _runningFallbackSystemInit = false; + return _fallbackSystem; + } /// /// Creates an instance with a unique owner /// /// Unique identifier for this instance in this process. Should match the other process. - /// Optional list of additional class types you want to be able to send or receieve. Types you want to use that are vanilla go in here too. - /// Optional list of additional unmanaged types you want to be able to send or receieve. + /// Unused parameter kept for backwards compatibility. + /// Unused parameter kept for backwards compatibility. /// - /// + /// + [Obsolete("Use the other constructors that don't take Type lists", false)] public Messenger(string ownerId, List? additionalObjectTypes = null, List? additionalValueTypes = null) { if (ownerId is null) @@ -55,37 +155,38 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< _ownerId = ownerId; - _additionalObjectTypes = additionalObjectTypes; + DefaultInit(); + } - _additionalValueTypes = additionalValueTypes; + /// + /// Creates an instance with a unique owner + /// + /// Unique identifier for this instance in this process. Should match the other process. + /// + /// + public Messenger(string ownerId) + { + if (ownerId is null) + throw new ArgumentNullException(nameof(ownerId)); - if (_additionalObjectTypes is not null) - { - TypeManager.InitObjectTypeList(_additionalObjectTypes.Where(t => !TypeManager.IsObjectTypeInitialized(t)).ToList()); - } - if (_additionalValueTypes is not null) - { - TypeManager.InitValueTypeList(_additionalValueTypes.Where(t => !TypeManager.IsValueTypeInitialized(t)).ToList()); - } + _ownerId = ownerId; - if (!_registeredOwnerIds.Contains(ownerId)) - { - _registeredOwnerIds.Add(ownerId); + DefaultInit(); + } - if (IsInitialized) - Register(); - else - RunPostInit(Register); + private void DefaultInit() + { + if (_defaultSystem is null) + { + DefaultRunPreInit(Register); } else { - OnWarning?.Invoke($"A messenger with id {ownerId} has already been created in this process!"); + Register(); } - if (Host is null && !InitStarted) + if (_defaultSystem is null && !DefaultInitStarted) { - InitStarted = true; - var frooxEngineInitType = Type.GetType("InterprocessLib.FrooxEngineInit"); if (frooxEngineInitType is not null) { @@ -100,45 +201,155 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< } else { - throw new EntryPointNotFoundException("Could not find InterprocessLib initialization type!"); + // An external process will almost definitely not be an authority + var fallbackSystemTask = GetFallbackSystem(_ownerId, false, MessagingManager.DEFAULT_CAPACITY, FallbackPool.Instance, null, OnFailure, OnWarning, OnDebug, null); + fallbackSystemTask.Wait(); + if (fallbackSystemTask.Result is not MessagingSystem fallbackSystem) + throw new EntryPointNotFoundException("Could not find InterprocessLib initialization type!"); + else + _defaultSystem = fallbackSystemTask.Result; } } } } - private void Register() + /// + /// Creates an instance with a unique owner and connects to a custom queue so you can talk to any process + /// + /// Unique identifier for this instance in this process. Should match the other process. + /// Does this process have authority over the other process? The authority process should always be started first. + /// Custom queue name. Should match the other process. + /// Custom pool for borrowing and returning memory-packable types. + /// Capacity for the custom queue in bytes. + /// + /// + public Messenger(string ownerId, bool isAuthority, string queueName, IMemoryPackerEntityPool? pool = null, long queueCapacity = 1024*1024) { - Host!.RegisterOwner(_ownerId); + if (ownerId is null) + throw new ArgumentNullException(nameof(ownerId)); + + if (queueName is null) + throw new ArgumentNullException(nameof(queueName)); + + IMemoryPackerEntityPool? actualPool = pool; + if (actualPool is null) + { + var frooxEnginePoolType = Type.GetType("InterprocessLib.FrooxEnginePool"); + if (frooxEnginePoolType is not null) + { + actualPool = (IMemoryPackerEntityPool)frooxEnginePoolType.GetField("Instance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)!.GetValue(null)!; + } + else + { + var unityPoolType = Type.GetType("Renderite.Unity.PackerMemoryPool"); + if (unityPoolType is not null) + { + actualPool = (IMemoryPackerEntityPool)unityPoolType.GetField("Instance", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)!.GetValue(null)!; + } + else + { + actualPool = FallbackPool.Instance; + } + } + } + + if (MessagingSystem.TryGetRegisteredSystem(queueName) is not MessagingSystem existingSystem) + { + _customSystem = new MessagingSystem(isAuthority, queueName, queueCapacity, actualPool, null, OnFailure, OnWarning, OnDebug); + } + else + { + _customSystem = existingSystem; + } + + _ownerId = ownerId; + + Register(); + + if (!_customSystem.IsConnected) + { + _customSystem.Connect(); + } + + if (!_customSystem!.IsInitialized) + { + _customSystem.Initialize(); + } } - internal static void FinishInitialization() + internal static void PreInit(MessagingSystem system) { - if (IsAuthority) - Host!.SendCommand(new MessengerReadyCommand()); + system.SetPostInitActions(_defaultPostInitActions); + _defaultPostInitActions = null; - var actions = _postInitActions!.ToArray(); - _postInitActions = null; - foreach (var action in actions) + foreach (var act in _defaultPreInitActions!) { try { - action(); + act(system); } catch (Exception ex) { - OnWarning?.Invoke($"Exception running post-init action:\n{ex}"); + OnWarning?.Invoke($"Exception running pre-init action:\n{ex}"); } } + _defaultPreInitActions = null; } - private static void RunPostInit(Action act) + internal static void SetDefaultSystem(MessagingSystem system) { - if (!IsInitialized) + _defaultSystem = system; + } + + private void RunPostInit(Action act) + { + lock (LockObj) + { + if (IsInitialized) + { + act(); + return; + } + if (CurrentSystem is null) + DefaultRunPostInit(act); + else + CurrentSystem.RunPostInit(act); + } + } + + private void Register(MessagingSystem system) + { + if (system.HasOwner(_ownerId)) + { + OnWarning?.Invoke($"Owner {_ownerId} has already been registered in this process for messaging backend with queue name: {system.QueueName}"); + } + else + system.RegisterOwner(_ownerId); + } + + private void Register() + { + Register(CurrentSystem!); + } + + private static void DefaultRunPreInit(Action act) + { + if (_defaultSystem is null) + { + _defaultPreInitActions!.Add(act); + } + else + throw new InvalidOperationException("Default host already did pre-init!"); + } + + private static void DefaultRunPostInit(Action act) + { + if (_defaultSystem is null) { - _postInitActions!.Add(act); + _defaultPostInitActions!.Add(act); } else - throw new InvalidOperationException("Already initialized!"); + throw new InvalidOperationException("Default host already initialized!"); } public void SendValue(string id, T value) where T : unmanaged @@ -152,59 +363,68 @@ public void SendValue(string id, T value) where T : unmanaged return; } - if (!TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {value.GetType().Name} needs to be registered first!"); + CurrentSystem!.EnsureValueTypeInitialized(); var command = new ValueCommand(); command.Owner = _ownerId; command.Id = id; command.Value = value; - Host!.SendCommand(command); + CurrentSystem!.SendPackable(command); } - public void SendValueList(string id, List list) where T : unmanaged + [Obsolete("Use SendValueCollection instead.")] + public void SendValueList(string id, List? list) where T : unmanaged + { + SendValueCollection, T>(id, list); + } + + [Obsolete("Use SendValueCollection instead.")] + public void SendValueHashSet(string id, HashSet? hashSet) where T : unmanaged + { + SendValueCollection, T>(id, hashSet); + } + + public void SendValueCollection(string id, C? collection) where C : ICollection?, new() where T : unmanaged { if (id is null) throw new ArgumentNullException(nameof(id)); if (!IsInitialized) { - RunPostInit(() => SendValueList(id, list)); + RunPostInit(() => SendValueCollection(id, collection)); return; } - if (!TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.EnsureValueCollectionTypeInitialized(); - var command = new ValueCollectionCommand, T>(); + var command = new ValueCollectionCommand(); command.Owner = _ownerId; command.Id = id; - command.Values = list; - Host!.SendCommand(command); + command.Values = collection; + CurrentSystem!.SendPackable(command); } - public void SendValueHashSet(string id, HashSet hashSet) where T : unmanaged + public void SendValueArray(string id, T[]? array) where T : unmanaged { if (id is null) throw new ArgumentNullException(nameof(id)); if (!IsInitialized) { - RunPostInit(() => SendValueHashSet(id, hashSet)); + RunPostInit(() => SendValueArray(id, array)); return; } - if (!TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.EnsureValueArrayTypeInitialized(); - var command = new ValueCollectionCommand, T>(); + var command = new ValueArrayCommand(); command.Owner = _ownerId; command.Id = id; - command.Values = hashSet; - Host!.SendCommand(command); + command.Values = array; + CurrentSystem!.SendPackable(command); } - public void SendString(string id, string str) + public void SendString(string id, string? str) { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -219,25 +439,51 @@ public void SendString(string id, string str) command.Owner = _ownerId; command.Id = id; command.String = str; - Host!.SendCommand(command); + CurrentSystem!.SendPackable(command); + } + + [Obsolete("Use SendStringCollection instead.")] + public void SendStringList(string id, List? list) + { + SendStringCollection>(id, list); + } + + public void SendStringCollection(string id, IReadOnlyCollection? collection) where C : ICollection?, new() + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (!IsInitialized) + { + RunPostInit(() => SendStringCollection(id, collection)); + return; + } + + CurrentSystem!.EnsureStringCollectionTypeInitialized(); + + var command = new StringCollectionCommand(); + command.Owner = _ownerId; + command.Id = id; + command.Strings = collection; + CurrentSystem!.SendPackable(command); } - public void SendStringList(string id, List list) + public void SendStringArray(string id, string?[]? array) { if (id is null) throw new ArgumentNullException(nameof(id)); if (!IsInitialized) { - RunPostInit(() => SendStringList(id, list)); + RunPostInit(() => SendStringArray(id, array)); return; } - var command = new StringListCommand(); + var command = new StringArrayCommand(); command.Owner = _ownerId; command.Id = id; - command.Values = list; - Host!.SendCommand(command); + command.Strings = array; + CurrentSystem!.SendPackable(command); } public void SendEmptyCommand(string id) @@ -254,10 +500,10 @@ public void SendEmptyCommand(string id) var command = new EmptyCommand(); command.Owner = _ownerId; command.Id = id; - Host!.SendCommand(command); + CurrentSystem!.SendPackable(command); } - public void SendObject(string id, T? obj) where T : class, IMemoryPackable, new() + public void SendObject(string id, T obj) where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -268,90 +514,117 @@ public void SendEmptyCommand(string id) return; } - if (!TypeManager.IsObjectTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.EnsureObjectTypeInitialized(); - var wrapper = new ObjectCommand(); - wrapper.Object = obj; - wrapper.Owner = _ownerId; - wrapper.Id = id; + var command = new ObjectCommand(); + command.Object = obj; + command.Owner = _ownerId; + command.Id = id; - Host!.SendCommand(wrapper); + CurrentSystem!.SendPackable(command); + } + + [Obsolete("Use SendObjectCollection instead.")] + public void SendObjectList(string id, List? list) where T : class?, IMemoryPackable?, new() + { + SendObjectCollection, T>(id, list); } - public void SendObjectList(string id, List list) where T : class, IMemoryPackable, new() + public void SendObjectCollection(string id, C? collection) where C : ICollection?, new() where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); if (!IsInitialized) { - RunPostInit(() => SendObjectList(id, list)); + RunPostInit(() => SendObjectCollection(id, collection)); return; } - if (!TypeManager.IsObjectTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.EnsureObjectCollectionTypeInitialized(); - var command = new ObjectListCommand(); + var command = new ObjectCollectionCommand(); command.Owner = _ownerId; command.Id = id; - command.Values = list; - Host!.SendCommand(command); + command.Objects = collection; + CurrentSystem!.SendPackable(command); } - public void ReceiveValue(string id, Action callback) where T : unmanaged + public void SendObjectArray(string id, T[]? array) where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); if (!IsInitialized) { - RunPostInit(() => ReceiveValue(id, callback)); + RunPostInit(() => SendObjectArray(id, array)); return; } - if (!TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.EnsureObjectArrayTypeInitialized(); - Host!.RegisterValueCallback(_ownerId, id, callback); + var command = new ObjectArrayCommand(); + command.Owner = _ownerId; + command.Id = id; + command.Objects = array; + CurrentSystem!.SendPackable(command); } - public void ReceiveValueList(string id, Action> callback) where T : unmanaged + public void ReceiveValue(string id, Action? callback) where T : unmanaged { if (id is null) throw new ArgumentNullException(nameof(id)); if (!IsInitialized) { - RunPostInit(() => ReceiveValueList(id, callback)); + RunPostInit(() => ReceiveValue(id, callback)); return; } - if (!TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.RegisterValueCallback(_ownerId, id, callback); + } - Host!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); + [Obsolete("Use ReceiveValueCollection instead.")] + public void ReceiveValueList(string id, Action?>? callback) where T : unmanaged + { + ReceiveValueCollection, T>(id, callback); } - public void ReceiveValueHashSet(string id, Action> callback) where T : unmanaged + [Obsolete("Use ReceiveValueCollection instead.")] + public void ReceiveValueHashSet(string id, Action?>? callback) where T : unmanaged + { + ReceiveValueCollection, T>(id, callback); + } + + public void ReceiveValueCollection(string id, Action? callback) where C : ICollection?, new() where T : unmanaged { if (id is null) throw new ArgumentNullException(nameof(id)); if (!IsInitialized) { - RunPostInit(() => ReceiveValueHashSet(id, callback)); + RunPostInit(() => ReceiveValueCollection(id, callback)); return; } - if (!TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.RegisterValueCollectionCallback(_ownerId, id, callback); + } + + public void ReceiveValueArray(string id, Action? callback) where T : unmanaged + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (!IsInitialized) + { + RunPostInit(() => ReceiveValueArray(id, callback)); + return; + } - Host!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); + CurrentSystem!.RegisterValueArrayCallback(_ownerId, id, callback); } - public void ReceiveString(string id, Action callback) + public void ReceiveString(string id, Action? callback) { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -362,24 +635,44 @@ public void ReceiveString(string id, Action callback) return; } - Host!.RegisterStringCallback(_ownerId, id, callback); + CurrentSystem!.RegisterStringCallback(_ownerId, id, callback); } + [Obsolete("Use ReceiveStringCollection instead.")] public void ReceiveStringList(string id, Action?>? callback) + { + ReceiveStringCollection(id, callback); + } + + public void ReceiveStringCollection(string id, Action? callback) where C : ICollection?, new() + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (!IsInitialized) + { + RunPostInit(() => ReceiveStringCollection(id, callback)); + return; + } + + CurrentSystem!.RegisterStringCollectionCallback(_ownerId, id, callback); + } + + public void ReceiveStringArray(string id, Action? callback) { if (id is null) throw new ArgumentNullException(nameof(id)); if (!IsInitialized) { - RunPostInit(() => ReceiveStringList(id, callback)); + RunPostInit(() => ReceiveStringArray(id, callback)); return; } - Host!.RegisterStringListCallback(_ownerId, id, callback); + CurrentSystem!.RegisterStringArrayCallback(_ownerId, id, callback!); } - public void ReceiveEmptyCommand(string id, Action callback) + public void ReceiveEmptyCommand(string id, Action? callback) { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -390,10 +683,10 @@ public void ReceiveEmptyCommand(string id, Action callback) return; } - Host!.RegisterEmptyCallback(_ownerId, id, callback); + CurrentSystem!.RegisterEmptyCallback(_ownerId, id, callback); } - public void ReceiveObject(string id, Action callback) where T : class, IMemoryPackable, new() + public void ReceiveObject(string id, Action? callback) where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -404,26 +697,76 @@ public void ReceiveEmptyCommand(string id, Action callback) return; } - if (!TypeManager.IsObjectTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.RegisterObjectCallback(_ownerId, id, callback); + } - Host!.RegisterObjectCallback(_ownerId, id, callback); + [Obsolete("Use ReceiveObjectCollection instead.")] + public void ReceiveObjectList(string id, Action?>? callback) where T : class?, IMemoryPackable?, new() + { + ReceiveObjectCollection, T>(id, callback); } - public void ReceiveObjectList(string id, Action> callback) where T : class, IMemoryPackable, new() + public void ReceiveObjectCollection(string id, Action? callback) where C : ICollection?, new() where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); if (!IsInitialized) { - RunPostInit(() => ReceiveObjectList(id, callback)); + RunPostInit(() => ReceiveObjectCollection(id, callback)); return; } - if (!TypeManager.IsObjectTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.RegisterObjectCollectionCallback(_ownerId, id, callback); + } + + public void ReceiveObjectArray(string id, Action? callback) where T : class?, IMemoryPackable?, new() + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (!IsInitialized) + { + RunPostInit(() => ReceiveObjectArray(id, callback)); + return; + } + + CurrentSystem!.RegisterObjectArrayCallback(_ownerId, id, callback); + } + + // A dirty hack because I'm lazy + public void SendType(string id, Type? type) + { + var typeCmd = new TypeCommand(); + typeCmd.Type = type; - Host!.RegisterObjectListCallback(_ownerId, id, callback); + SendObject(id, typeCmd); } + + // A dirty hack because I'm lazy + public void ReceiveType(string id, Action? callback) + { + ReceiveObject(id, (typeCmd) => callback?.Invoke(typeCmd.Type)); + } + + public void CheckLatency(Action callback) + { + if (!IsInitialized) + { + RunPostInit(() => CheckLatency(callback)); + return; + } + + CurrentSystem!.PingCallback = (ping) => callback?.Invoke(ping.ReceivedTime!.Value - ping.SentTime, DateTime.UtcNow - ping.ReceivedTime!.Value); + + var pingCommand = new PingCommand(); + pingCommand.SentTime = DateTime.UtcNow; + CurrentSystem!.SendPackable(pingCommand); + } + + public void Dispose() + { + CurrentSystem!.UnregisterOwner(_ownerId); + _customSystem = null; + } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Pool.cs b/InterprocessLib.Shared/Pool.cs new file mode 100644 index 0000000..77559bd --- /dev/null +++ b/InterprocessLib.Shared/Pool.cs @@ -0,0 +1,21 @@ +using Renderite.Shared; + +namespace InterprocessLib; + +// This is used as a last resort +// It just creates new objects every time +// Could be improved to actually use pooling + +internal class FallbackPool : IMemoryPackerEntityPool +{ + public static IMemoryPackerEntityPool Instance = new FallbackPool(); + + T IMemoryPackerEntityPool.Borrow() + { + return new T(); + } + + void IMemoryPackerEntityPool.Return(T value) + { + } +} \ No newline at end of file diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs new file mode 100644 index 0000000..51479f6 --- /dev/null +++ b/InterprocessLib.Shared/System.cs @@ -0,0 +1,646 @@ +using Renderite.Shared; +using System.Reflection; + +namespace InterprocessLib; + +internal class MessagingSystem : IDisposable +{ + private struct OwnerData + { + public readonly Dictionary ValueCallbacks = new(); + + public readonly Dictionary?> StringCallbacks = new(); + + public readonly Dictionary EmptyCallbacks = new(); + + public readonly Dictionary ObjectCallbacks = new(); + + public readonly Dictionary ValueArrayCallbacks = new(); + + public readonly Dictionary ValueCollectionCallbacks = new(); + + public readonly Dictionary?> StringArrayCallbacks = new(); + + public readonly Dictionary StringCollectionCallbacks = new(); + + public readonly Dictionary ObjectArrayCallbacks = new(); + + public readonly Dictionary ObjectCollectionCallbacks = new(); + + public OwnerData() + { + } + } + + public bool IsAuthority { get; } + + public string QueueName { get; } + + public long QueueCapacity { get; } + + private MessagingManager? _primary; + + private static readonly MethodInfo _handleValueCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleValueCommand)); + + private static readonly MethodInfo _handleValueCollectionCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleValueCollectionCommand)); + + private static readonly MethodInfo _handleValueArrayCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueArrayCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleValueArrayCommand)); + + private static readonly MethodInfo _handleObjectCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleObjectCommand)); + + private static readonly MethodInfo _handleObjectCollectionCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleObjectCollectionCommand)); + + private static readonly MethodInfo _handleObjectArrayCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectArrayCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleObjectArrayCommand)); + + private static readonly MethodInfo _handleStringCollectionCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleStringCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleStringCollectionCommand)); + + private RenderCommandHandler? _onCommandReceived { get; } + + private Action? _onWarning { get; } + + private Action? _onDebug { get; } + + private Action? _onFailure { get; } + + private readonly Dictionary _ownerData = new(); + + private Action? _postInitCallback; + + public bool IsConnected { get; private set; } + + public bool IsInitialized => _postInitActions is null; + + private List? _postInitActions = new(); + + internal TypeManager OutgoingTypeManager; + + internal TypeManager IncomingTypeManager; + + private static readonly Dictionary _backends = new(); + + internal Action? PingCallback; + + private readonly IMemoryPackerEntityPool _pool; + + private bool _messengerReadyCommandReceived = false; + + internal void SetPostInitActions(List? actions) + { + if (IsInitialized) + throw new InvalidOperationException("Already initialized!"); + + _postInitActions = actions; + } + + public void Connect() + { + if (IsConnected) + throw new InvalidOperationException("Already connected!"); + + _primary!.Connect(QueueName, IsAuthority, QueueCapacity); + IsConnected = true; + } + + public void Initialize() + { + if (IsInitialized) + throw new InvalidOperationException("Already initialized!"); + + SendPackable(new MessengerReadyCommand()); + + var actions = _postInitActions!.ToArray(); + _postInitActions = null; + + foreach (var action in actions) + { + try + { + action(); + } + catch (Exception ex) + { + _onWarning?.Invoke($"Exception running post-init action:\n{ex}"); + } + } + + _postInitCallback?.Invoke(); + _postInitCallback = null; + } + + internal void RunPostInit(Action act) + { + if (!IsInitialized) + { + _postInitActions!.Add(act); + } + else + throw new InvalidOperationException("Already initialized!"); + } + + public void RegisterOwner(string ownerName) + { + var ownerData = new OwnerData(); + _ownerData.Add(ownerName, ownerData); + } + + public bool HasOwner(string ownerName) + { + return _ownerData.ContainsKey(ownerName); + } + + public void RegisterValueCallback(string owner, string id, Action? callback) where T : unmanaged + { + _ownerData[owner].ValueCallbacks[id] = callback; + } + + public void RegisterValueCollectionCallback(string owner, string id, Action? callback) where C : ICollection?, new() where T : unmanaged + { + _ownerData[owner].ValueCollectionCallbacks[id] = callback; + } + + public void RegisterValueArrayCallback(string owner, string id, Action? callback) where T : unmanaged + { + _ownerData[owner].ValueArrayCallbacks[id] = callback; + } + + public void RegisterStringCallback(string owner, string id, Action? callback) + { + _ownerData[owner].StringCallbacks[id] = callback; + } + + public void RegisterStringArrayCallback(string owner, string id, Action? callback) + { + _ownerData[owner].StringArrayCallbacks[id] = callback; + } + + public void RegisterStringCollectionCallback(string owner, string id, Action? callback) where C : ICollection?, new() + { + _ownerData[owner].StringCollectionCallbacks[id] = callback; + } + + public void RegisterEmptyCallback(string owner, string id, Action? callback) + { + _ownerData[owner].EmptyCallbacks[id] = callback; + } + + public void RegisterObjectCallback(string owner, string id, Action? callback) where T : class?, IMemoryPackable?, new() + { + _ownerData[owner].ObjectCallbacks[id] = callback; + } + + public void RegisterObjectArrayCallback(string owner, string id, Action? callback) where T : class?, IMemoryPackable?, new() + { + _ownerData[owner].ObjectArrayCallbacks[id] = callback; + } + + public void RegisterObjectCollectionCallback(string owner, string id, Action? callback) where C : ICollection?, new() where T : class?, IMemoryPackable?, new() + { + _ownerData[owner].ObjectCollectionCallbacks[id] = callback; + } + + public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, IMemoryPackerEntityPool pool, RenderCommandHandler? commandHandler = null, Action? failhandler = null, Action? warnHandler = null, Action? debugHandler = null, Action? postInitCallback = null) + { + if (queueName is null) throw new ArgumentNullException(nameof(queueName)); + if (pool is null) throw new ArgumentNullException(nameof(pool)); + + IsAuthority = isAuthority; + QueueName = queueName; + QueueCapacity = queueCapacity; + + _onDebug = debugHandler; + _onWarning = warnHandler; + _onFailure = failhandler; + _onCommandReceived = commandHandler; + + _postInitCallback = postInitCallback; + + _pool = pool; + + OutgoingTypeManager = new(_pool, OnOutgoingTypeRegistered); + IncomingTypeManager = new(_pool, null); + + _primary = new MessagingManager(pool); + _primary.CommandHandler = CommandHandler; + _primary.FailureHandler = (ex) => + { + if (ex is OperationCanceledException) return; // this happens when you call Dispose + Dispose(); + _onFailure?.Invoke(ex); + }; + _primary.WarningHandler = (msg) => + { + _onWarning?.Invoke(msg); + }; + + _backends.Add(QueueName, this); + } + + public void Dispose() + { + _primary?.Dispose(); + _primary = null; + IsConnected = false; + } + + internal static MessagingSystem? TryGetRegisteredSystem(string queueName) + { + if (_backends.TryGetValue(queueName, out var backend)) return backend; + return null; + } + + private void HandleValueCommand(ValueCommand command) where T : unmanaged + { + if (_ownerData[command.Owner!].ValueCallbacks.TryGetValue(command.Id!, out var callback)) + { + if (callback != null) + { + ((Action)callback).Invoke(command.Value); + } + } + else + { + _onWarning?.Invoke($"ValueCommand<{typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + } + } + + private void HandleValueCollectionCommand(ValueCollectionCommand command) where C : ICollection?, new() where T : unmanaged + { + if (_ownerData[command.Owner!].ValueCollectionCallbacks.TryGetValue(command.Id!, out var callback)) + { + if (callback != null) + { + ((Action)callback).Invoke(command.Values); + } + } + else + { + _onWarning?.Invoke($"ValueCollectionCommand<{typeof(C).Name}, {typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + } + } + + private void HandleValueArrayCommand(ValueArrayCommand command) where T : unmanaged + { + if (_ownerData[command.Owner!].ValueArrayCallbacks.TryGetValue(command.Id!, out var callback)) + { + if (callback != null) + { + ((Action)callback).Invoke(command.Values); + } + } + else + { + _onWarning?.Invoke($"ValueArrayCommand<{typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + } + } + + private void HandleStringCommand(StringCommand command) + { + if (_ownerData[command.Owner!].StringCallbacks.TryGetValue(command.Id!, out var callback)) + { + if (callback != null) + { + callback.Invoke(command.String!); + } + } + else + { + _onWarning?.Invoke($"StringCommand with Id \"{command.Id}\" is not registered to receive a callback!"); + } + } + + private void HandleStringArrayCommand(StringArrayCommand command) + { + if (_ownerData[command.Owner!].StringArrayCallbacks.TryGetValue(command.Id!, out var callback)) + { + if (callback != null) + { + callback.Invoke(command.Strings); + } + } + else + { + _onWarning?.Invoke($"StringArrayCommand with Id \"{command.Id}\" is not registered to receive a callback!"); + } + } + + private void HandleStringCollectionCommand(StringCollectionCommand command) where C : ICollection?, new() + { + if (_ownerData[command.Owner!].StringCollectionCallbacks.TryGetValue(command.Id!, out var callback)) + { + if (callback != null) + { + ((Action)callback).Invoke((C?)command.Strings); + } + } + else + { + _onWarning?.Invoke($"StringArrayCommand with Id \"{command.Id}\" is not registered to receive a callback!"); + } + } + + private void HandleEmptyCommand(EmptyCommand command) + { + if (_ownerData[command.Owner!].EmptyCallbacks.TryGetValue(command.Id!, out var callback)) + { + if (callback != null) + { + callback.Invoke(); + } + } + else + { + _onWarning?.Invoke($"EmptyCommand with Id \"{command.Id}\" is not registered to receive a callback!"); + } + } + + private void HandleObjectCommand(ObjectCommand command) where T : class?, IMemoryPackable?, new() + { + if (_ownerData[command.Owner!].ObjectCallbacks.TryGetValue(command.Id!, out var callback)) + { + if (callback != null) + { + ((Action)callback).Invoke((T?)command.UntypedObject); + } + } + else + { + _onWarning?.Invoke($"ObjectCommand<{command.ObjectType.Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + } + } + + private void HandleObjectArrayCommand(ObjectArrayCommand command) where T : class?, IMemoryPackable?, new() + { + if (_ownerData[command.Owner!].ObjectArrayCallbacks.TryGetValue(command.Id!, out var callback)) + { + if (callback != null) + { + ((Action)callback).Invoke(command.Objects); + } + } + else + { + _onWarning?.Invoke($"ObjectArrayCommand<{typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + } + } + + private void HandleObjectCollectionCommand(ObjectCollectionCommand command) where C : ICollection?, new() where T : class?, IMemoryPackable?, new() + { + if (_ownerData[command.Owner!].ObjectCollectionCallbacks.TryGetValue(command.Id!, out var callback)) + { + if (callback != null) + { + ((Action)callback).Invoke(command.Objects); + } + } + else + { + _onWarning?.Invoke($"ObjectCollectionCommand<{typeof(C).Name}, {typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + } + } + + private void HandlePingCommand(PingCommand ping) + { + if (!ping.ReceivedTime.HasValue) + { + ping.ReceivedTime = DateTime.UtcNow; + SendPackable(ping); + } + else + { + PingCallback?.Invoke(ping); + PingCallback = null; + } + } + + private void CommandHandler(RendererCommand command, int messageSize) + { + _onCommandReceived?.Invoke(command, messageSize); + + IMemoryPackable? packable = null; + if (command is WrapperCommand wrapperCommand) + { + packable = wrapperCommand.Packable; + _onDebug?.Invoke($"{QueueName}: Received {packable?.ToString() ?? packable?.GetType().Name ?? "NULL"}"); + } + else + { + _onWarning?.Invoke($"{QueueName}: Received an unexpected RendererCommand type! {command?.ToString() ?? command?.GetType().Name ?? "NULL"}"); + return; + } + + // ping command before ready command is okay (to check if the queue is active) + if (packable is PingCommand pingCommand) + { + HandlePingCommand(pingCommand); + return; + } + + if (packable is MessengerReadyCommand) + { + if (_messengerReadyCommandReceived) + { + OutgoingTypeManager = new(_pool, OnOutgoingTypeRegistered); + IncomingTypeManager = new(_pool, null); + } + else + { + _messengerReadyCommandReceived = true; + } + return; + } + + if (!_messengerReadyCommandReceived) + { + throw new InvalidDataException("MessengerReadyCommand needs to be first!"); + } + + if (packable is TypeRegistrationCommand typeRegCommand) + { + if (typeRegCommand.Type is not null) + { + IncomingTypeManager.InitDirectCommandType(typeRegCommand.Type); + } + else + { + throw new InvalidDataException("Other process tried to register a type that could not be found in this process!"); + } + return; + } + + if (packable is IdentifiableCommand identifiableCommand) + { + if (identifiableCommand.Owner is null) throw new InvalidDataException("Received IdentifiableCommand with null Owner!"); + if (identifiableCommand.Id is null) throw new InvalidDataException("Received IdentifiableCommand with null Id!"); + if (!_ownerData.TryGetValue(identifiableCommand.Owner, out var data)) + { + _onWarning?.Invoke($"Owner \"{identifiableCommand.Owner}\" is not registered!"); + return; + } + if (packable is ValueCommand valueCommand) + { + var valueType = valueCommand.ValueType; + var typedMethod = _handleValueCommandMethod.MakeGenericMethod(valueType); + typedMethod.Invoke(this, [packable]); + } + else if (packable is ObjectCommand objectCommand) + { + var objectType = objectCommand.ObjectType; + var typedMethod = _handleObjectCommandMethod.MakeGenericMethod(objectType); + typedMethod.Invoke(this, [packable]); + } + else if (packable is EmptyCommand emptyCommand) + { + HandleEmptyCommand(emptyCommand); + } + else if (packable is StringCommand stringCommand) + { + HandleStringCommand(stringCommand); + } + else if (packable is StringArrayCommand stringArrayCommand) + { + HandleStringArrayCommand(stringArrayCommand); + } + else if (packable is CollectionCommand collectionCommand) + { + var collectionType = collectionCommand.CollectionType; + var innerDataType = collectionCommand.StoredType; + if (innerDataType == typeof(string)) + { + var typedMethod = _handleStringCollectionCommandMethod.MakeGenericMethod(collectionType); + typedMethod.Invoke(this, [packable]); + } + else if (innerDataType.IsValueType) + { + if (collectionType.IsArray) + { + var typedMethod = _handleValueArrayCommandMethod.MakeGenericMethod(innerDataType); + typedMethod.Invoke(this, [packable]); + } + else + { + var typedMethod = _handleValueCollectionCommandMethod.MakeGenericMethod(collectionType, innerDataType); + typedMethod.Invoke(this, [packable]); + } + } + else + { + if (collectionType.IsArray) + { + var typedMethod = _handleObjectArrayCommandMethod.MakeGenericMethod(innerDataType); + typedMethod.Invoke(this, [packable]); + } + else + { + var typedMethod = _handleObjectCollectionCommandMethod.MakeGenericMethod(collectionType, innerDataType); + typedMethod.Invoke(this, [packable]); + } + } + } + else + { + throw new InvalidDataException($"Received unrecognized IdentifiableCommand of type {identifiableCommand.GetType().Name}: {identifiableCommand.Owner}:{identifiableCommand.Id}"); + } + } + else + { + // packable is not identifiable, has no owner + // right now this should never happen + // but in the future maybe it can be handled with a custom user-supplied callback + throw new InvalidDataException($"Received unexpected wrapped packable of type {packable?.GetType().Name ?? "NULL"}"); + } + } + + public void SendPackable(IMemoryPackable packable) + { + if (packable is null) throw new ArgumentNullException(nameof(packable)); + + if (!IsConnected) throw new InvalidOperationException("Not connected!"); + + _onDebug?.Invoke($"Sending packable: {packable}"); + + var wrapper = new WrapperCommand(); + wrapper.QueueName = QueueName; + wrapper.Packable = packable; + + _primary!.SendCommand(wrapper); + } + + internal void EnsureValueTypeInitialized() where T : unmanaged + { + if (!OutgoingTypeManager.IsDirectCommandTypeInitialized>()) + { + OutgoingTypeManager.RegisterDirectCommandType>(); + } + } + + internal void EnsureObjectTypeInitialized() where T : class?, IMemoryPackable?, new() + { + if (!OutgoingTypeManager.IsDirectCommandTypeInitialized>()) + { + OutgoingTypeManager.RegisterDirectCommandType>(); + } + } + + internal void EnsureValueArrayTypeInitialized() where T : unmanaged + { + if (!OutgoingTypeManager.IsDirectCommandTypeInitialized>()) + { + OutgoingTypeManager.RegisterDirectCommandType>(); + } + } + + internal void EnsureObjectArrayTypeInitialized() where T : class?, IMemoryPackable?, new() + { + if (!OutgoingTypeManager.IsDirectCommandTypeInitialized>()) + { + OutgoingTypeManager.RegisterDirectCommandType>(); + } + } + + internal void EnsureObjectCollectionTypeInitialized() where C : ICollection?, new() where T : class?, IMemoryPackable?, new() + { + if (!OutgoingTypeManager.IsDirectCommandTypeInitialized>()) + { + OutgoingTypeManager.RegisterDirectCommandType>(); + } + } + + internal void EnsureValueCollectionTypeInitialized() where C : ICollection?, new() where T : unmanaged + { + if (!OutgoingTypeManager.IsDirectCommandTypeInitialized>()) + { + OutgoingTypeManager.RegisterDirectCommandType>(); + } + } + + internal void EnsureStringCollectionTypeInitialized() where C : ICollection?, new() + { + if (!OutgoingTypeManager.IsDirectCommandTypeInitialized>()) + { + OutgoingTypeManager.RegisterDirectCommandType>(); + } + } + + private void OnOutgoingTypeRegistered(Type type) + { + var typeRegCommand = new TypeRegistrationCommand(); + typeRegCommand.Type = type; + SendPackable(typeRegCommand); + } + + public void UnregisterOwner(string ownerId) + { + if (HasOwner(ownerId)) + { + _ownerData.Remove(ownerId); + } + else + { + _onWarning?.Invoke($"Tried to unregister owner that was not registered: {ownerId}"); + } + if (_ownerData.Count == 0) + { + Dispose(); + } + } +} \ No newline at end of file diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index df12d61..caf1db0 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -8,16 +8,13 @@ namespace InterprocessLib.Tests; public static class Tests { private static Messenger? _messenger; - private static Messenger? _unknownMessenger; private static Action? _logCallback; - public static void RunTests(Messenger messenger, Messenger unknownMessenger, Action logCallback) + public static void RunTests(Messenger messenger, Action logCallback) { _messenger = messenger; - _unknownMessenger = unknownMessenger; _logCallback = logCallback; - TestUnknownMessenger(); TestUnknownCommandId(); TestNullString(); TestEmptyCommand(); @@ -30,52 +27,55 @@ public static void RunTests(Messenger messenger, Messenger unknownMessenger, Act TestNestedStruct(); TestValueList(); TestValueHashSet(); - TestStringList(); + TestStringCollection(); TestObjectList(); TestVanillaObject(); TestVanillaStruct(); TestVanillaEnum(); + TestValueArray(); + TestObjectArray(); + TestObjectHashSet(); + TestStringArray(); + TestTypeCommand(); + } - try - { - TestUnregisteredCommand(); - } - catch (Exception ex) - { - logCallback($"TestUnregisteredCommand threw an exception: {ex.Message}"); - } - try - { - TestUnregisteredPackable(); - } - catch (Exception ex) - { - logCallback($"TestUnregisteredPackable threw an exception: {ex.Message}"); - } - try - { - TestUnregisteredStruct(); - } - catch (Exception ex) - { - logCallback($"TestUnregisteredStruct threw an exception: {ex.Message}"); - } - try - { - TestUnregisteredVanillaObject(); - } - catch (Exception ex) + static void TestTypeCommand() + { + _messenger!.ReceiveType("TestTypeCommand", (type) => { - logCallback($"TestUnregisteredVanillaObject threw an exception: {ex.Message}"); - } - try + _logCallback!($"TestTypeCommand: {type?.FullName ?? "NULL"}"); + }); + _messenger!.SendType("TestTypeCommand", typeof(Dictionary, float>)); + } + + static void TestValueArray() + { + _messenger!.ReceiveValueArray("TestValueArray", (arr) => { - TestUnregisteredVanillaValue(); - } - catch (Exception ex) + _logCallback!($"TestValueArray: {string.Join(",", arr!)}"); + }); + var arr = new int[3]; + arr[0] = 4; + arr[1] = 7; + arr[2] = -8; + _messenger.SendValueArray("TestValueArray", arr); + } + + static void TestObjectArray() + { + _messenger!.ReceiveObjectArray("TestObjectArray", (arr) => { - logCallback($"TestUnregisteredVanillaValue threw an exception: {ex.Message}"); - } + _logCallback!($"TestObjectArray: {string.Join(",", arr!)}"); + }); + var arr = new TestCommand?[3]; + arr[0] = new TestCommand(); + arr[0]!.Value = 64; + arr[0]!.Text = "Pizza"; + arr[0]!.Time = DateTime.Now; + arr[1] = null; + arr[2] = new TestCommand(); + arr[2]!.Value = 247; + _messenger.SendObjectArray("TestObjectArray", arr); } static void TestVanillaStruct() @@ -104,22 +104,6 @@ static void TestVanillaEnum() _messenger.SendValue("TestVanillaEnum", val); } - static void TestUnregisteredVanillaValue() - { - _messenger!.ReceiveValue("TestUnregisteredVanillaValue", (val) => - { - _logCallback!($"TestUnregisteredVanillaValue: {val}"); - - }); - var val = Chirality.Right; - _messenger.SendValue("TestUnregisteredVanillaValue", val); - } - - static void TestUnknownMessenger() - { - _unknownMessenger!.SendEmptyCommand("UnknownMessengerTest"); - } - static void TestUnknownCommandId() { _messenger!.SendEmptyCommand("UnknownIdTest"); @@ -132,7 +116,7 @@ static void TestNullString() _logCallback!($"NullStr: {str}"); }); - _messenger.SendString("NullStr", null!); + _messenger.SendString("NullStr", null); } static void TestEmptyCommand() @@ -235,42 +219,9 @@ static void TestNestedStruct() _messenger!.SendValue("TestNestedStruct", testNestedSruct); } - static void TestUnregisteredCommand() - { - _messenger!.ReceiveObject("UnregisteredCommand", (recv) => - { - _logCallback!($"UnregisteredCommand"); - }); - - var unregistered = new UnregisteredCommand(); - _messenger.SendObject("UnregisteredCommand", unregistered); - } - - static void TestUnregisteredPackable() - { - _messenger!.ReceiveValue("UnregisteredPackable", (recv) => - { - _logCallback!($"UnregisteredPackable"); - }); - - var unregistered = new UnregisteredPackable(); - _messenger.SendValue("UnregisteredPackable", unregistered); - } - - static void TestUnregisteredStruct() - { - _messenger!.ReceiveValue("UnregisteredStruct", (recv) => - { - _logCallback!($"UnregisteredStruct"); - }); - - var unregistered = new UnregisteredStruct(); - _messenger.SendValue("UnregisteredStruct", unregistered); - } - static void TestValueList() { - _messenger!.ReceiveValueList("TestValueList", (list) => + _messenger!.ReceiveValueCollection, float>("TestValueList", (list) => { _logCallback!($"TestValueList: {string.Join(",", list!)}"); }); @@ -279,12 +230,12 @@ static void TestValueList() list.Add(2f); list.Add(7f); list.Add(21f); - _messenger.SendValueList("TestValueList", list); + _messenger.SendValueCollection, float>("TestValueList", list); } static void TestValueHashSet() { - _messenger!.ReceiveValueHashSet("TestValueHashSet", (list) => + _messenger!.ReceiveValueCollection, float>("TestValueHashSet", (list) => { _logCallback!($"TestValueHashSet: {string.Join(",", list!)}"); }); @@ -293,28 +244,42 @@ static void TestValueHashSet() set.Add(99.92f); set.Add(127.2f); set.Add(-4.32f); - _messenger.SendValueHashSet("TestValueHashSet", set); + _messenger.SendValueCollection, float>("TestValueHashSet", set); } static void TestObjectList() { - _messenger!.ReceiveObjectList("TestObjectList", (list) => + _messenger!.ReceiveObjectCollection, TestPackable>("TestObjectList", (list) => { - _logCallback!($"TestObjectList: {string.Join(",", list)}"); + _logCallback!($"TestObjectList: {string.Join(",", list!)}"); }); var list = new List(); list.Add(new() { Value = 7 }); list.Add(new() { Value = 15 }); list.Add(new() { Value = 83 }); - _messenger.SendObjectList("TestObjectList", list); + _messenger.SendObjectCollection, TestPackable>("TestObjectList", list); } - static void TestStringList() + static void TestObjectHashSet() { - _messenger!.ReceiveStringList("TestStringList", (list) => + _messenger!.ReceiveObjectCollection, TestCommand?>("TestObjectHashSet", (list) => { - _logCallback!($"TestStringList: {string.Join(",", list!.Select(s => s ?? "NULL"))}"); + _logCallback!($"TestObjectHashSet: {string.Join(",", list!)}"); + }); + + var set = new HashSet(); + set.Add(new TestCommand()); + set.Add(null); + set.Add(new TestCommand() { Value = 9 }); + _messenger.SendObjectCollection, TestCommand?>("TestObjectHashSet", set); + } + + static void TestStringCollection() + { + _messenger!.ReceiveStringCollection>("TestStringCollection", (list) => + { + _logCallback!($"TestStringCollection: {string.Join(",", list!.Select(s => s ?? "NULL"))}"); }); var list = new List(); @@ -323,29 +288,36 @@ static void TestStringList() list.Add("owo"); list.Add(null!); list.Add("x3"); - _messenger.SendStringList("TestStringList", list); + _messenger.SendStringCollection>("TestStringCollection", list); } - static void TestVanillaObject() + static void TestStringArray() { - _messenger!.ReceiveObject("TestVanillaObject", (recv) => + _messenger!.ReceiveStringArray("TestStringArray", (arr) => { - _logCallback!($"TestVanillaObject: {recv.sharedMemoryPrefix} {recv.uniqueSessionId} {recv.mainProcessId} {recv.debugFramePacing} {recv.outputDevice} {recv.setWindowIcon} {recv.splashScreenOverride}"); + _logCallback!($"TestStringArray: {string.Join(",", arr!.Select(s => s ?? "NULL"))}"); }); - var obj = new RendererInitData(); - _messenger.SendObject("TestVanillaObject", obj); + var arr = new string?[] + { + "Hello", + "World", + "owo", + null, + "x3" + }; + _messenger.SendStringArray("TestStringArray", arr); } - static void TestUnregisteredVanillaObject() + static void TestVanillaObject() { - _messenger!.ReceiveObject("TestUnregisteredVanillaObject", (recv) => + _messenger!.ReceiveObject("TestVanillaObject", (recv) => { - _logCallback!($"TestUnregisteredVanillaObject: {recv.perPixelLights} {recv.shadowCascades} {recv.shadowResolution} {recv.shadowDistance} {recv.skinWeightMode}"); + _logCallback!($"TestVanillaObject: {recv!.sharedMemoryPrefix} {recv.uniqueSessionId} {recv.mainProcessId} {recv.debugFramePacing} {recv.outputDevice} {recv.setWindowIcon} {recv.splashScreenOverride}"); }); - var obj = new QualityConfig(); - _messenger.SendObject("TestUnregisteredVanillaObject", obj); + var obj = new RendererInitData(); + _messenger.SendObject("TestVanillaObject", obj); } #if TEST_COMPILATION @@ -399,6 +371,11 @@ public override void Unpack(ref MemoryUnpacker unpacker) unpacker.Read(ref Text); unpacker.Read(ref Time); } + + public override string ToString() + { + return $"TestCommand: {Value}, {Text}, {Time}"; + } } public class TestNestedPackable : IMemoryPackable @@ -457,31 +434,4 @@ public class InvalidType public struct StructWithObject { public Assembly Assembly; -} - -public struct UnregisteredPackable : IMemoryPackable -{ - public void Pack(ref MemoryPacker packer) - { - } - - public void Unpack(ref MemoryUnpacker unpacker) - { - } -} - -public class UnregisteredCommand : RendererCommand -{ - public override void Pack(ref MemoryPacker packer) - { - } - - public override void Unpack(ref MemoryUnpacker unpacker) - { - } -} - -public struct UnregisteredStruct -{ - public byte Value; } \ No newline at end of file diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 5e2181a..f9e0a52 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -3,163 +3,130 @@ namespace InterprocessLib; -internal static class TypeManager +internal class TypeManager { - private static readonly HashSet _registeredObjectTypes = new(); + private bool _initializedCoreTypes = false; - private static readonly HashSet _registeredValueTypes = new(); + private static readonly MethodInfo _registerDirectCommandTypeMethod = typeof(TypeManager).GetMethod(nameof(RegisterDirectCommandType), BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new MissingMethodException(nameof(RegisterDirectCommandType)); - private static bool _initializedCoreTypes = false; + private readonly List _newTypes = new(); - internal static MethodInfo? RegisterValueTypeMethod = typeof(TypeManager).GetMethod(nameof(TypeManager.RegisterAdditionalValueType), BindingFlags.NonPublic | BindingFlags.Static); + private static List CurrentRendererCommandTypes => (List)typeof(PolymorphicMemoryPackableEntity).GetField("types", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)! ?? throw new MissingFieldException("types"); - internal static MethodInfo? RegisterObjectTypeMethod = typeof(TypeManager).GetMethod(nameof(TypeManager.RegisterAdditionalObjectType), BindingFlags.NonPublic | BindingFlags.Static); + private readonly List> _borrowers = new(); - internal static List NewTypes = new(); + private readonly List> _returners = new(); - private static List RegisteredTypesList => (List)typeof(PolymorphicMemoryPackableEntity).GetField("types", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!; + private readonly Dictionary _typeToIndex = new(); - private static Type[] _valueTypes = - { - typeof(bool), - typeof(byte), - typeof(ushort), - typeof(uint), - typeof(ulong), - typeof(sbyte), - typeof(short), - typeof(int), - typeof(long), - typeof(float), - typeof(double), - typeof(decimal), - typeof(char), - typeof(DateTime), - typeof(TimeSpan) - }; - - // Make sure this always happens first before adding other types? - internal static void InitializeCoreTypes() - { - if (_initializedCoreTypes) return; + private IMemoryPackerEntityPool _pool; - RegisterAdditionalObjectType(); - RegisterAdditionalObjectType(); - RegisterAdditionalObjectType(); - RegisterAdditionalObjectType(); + private static readonly MethodInfo _borrowMethod = typeof(TypeManager).GetMethod(nameof(Borrow), BindingFlags.Instance | BindingFlags.NonPublic, null, [], null) ?? throw new MissingMethodException(nameof(Borrow)); + private static readonly MethodInfo _returnMethod = typeof(TypeManager).GetMethod(nameof(Return), BindingFlags.Instance | BindingFlags.NonPublic, null, [typeof(IMemoryPackable)], null) ?? throw new MissingMethodException(nameof(Return)); - foreach (var valueType in TypeManager._valueTypes) - { - try - { - RegisterValueTypeMethod!.MakeGenericMethod(valueType).Invoke(null, null); - } - catch (Exception ex) - { - Messenger.OnWarning?.Invoke($"Could not register additional value type {valueType.Name}!\n{ex}"); - } - } + private static readonly List _coreTypes = + [ + typeof(MessengerReadyCommand), + typeof(TypeRegistrationCommand), + typeof(EmptyCommand), + typeof(StringCommand), + typeof(StringArrayCommand), + typeof(TypeCommand), + typeof(PingCommand), + ]; - _initializedCoreTypes = true; - } + private Action? _onRegisteredCallback; - private static void PushNewTypes() + static TypeManager() { // Trigger RendererCommand static constructor - var cmd = new EmptyCommand(); + new WrapperCommand(); var list = new List(); - list.AddRange(RegisteredTypesList); - foreach (var type in TypeManager.NewTypes) - { - if (!list.Contains(type)) - list.Add(type); - } - IdentifiableCommand.InitNewTypes(list); + list.AddRange(CurrentRendererCommandTypes); + var wrapperType = typeof(WrapperCommand); + if (!list.Contains(wrapperType)) + list.Add(wrapperType); + + WrapperCommand.InitNewTypes(list); } - internal static void InitValueTypeList(List types) + internal TypeManager(IMemoryPackerEntityPool pool, Action? onRegisteredCallback) { - foreach (var type in types) - { - RegisterValueTypeMethod!.MakeGenericMethod(type).Invoke(null, null); - } + _pool = pool; + _onRegisteredCallback = onRegisteredCallback; + InitializeCoreTypes(); } - internal static void InitObjectTypeList(List types) + internal void InitializeCoreTypes() { - foreach (var type in types) - { - RegisterObjectTypeMethod!.MakeGenericMethod(type).Invoke(null, null); - } + if (_initializedCoreTypes) return; + + PushNewTypes(_coreTypes); + + _initializedCoreTypes = true; } - internal static bool IsValueTypeInitialized() where T : unmanaged + internal Type GetTypeFromIndex(int index) { - return _registeredValueTypes.Contains(typeof(T)); + return _newTypes[index]; } - internal static bool IsValueTypeInitialized(Type t) + internal int GetTypeIndex(Type type) { - return _registeredValueTypes.Contains(t); + return _typeToIndex[type]; } - internal static bool IsObjectTypeInitialized() where T : class, IMemoryPackable, new() + internal bool IsDirectCommandTypeInitialized() where T : class?, IMemoryPackable?, new() { - return _registeredObjectTypes.Contains(typeof(T)); + return _typeToIndex.ContainsKey(typeof(T)); } - internal static bool IsObjectTypeInitialized(Type t) + internal void InitDirectCommandType(Type type) { - return _registeredObjectTypes.Contains(t); + _registerDirectCommandTypeMethod!.MakeGenericMethod(type).Invoke(this, null); } - internal static void RegisterAdditionalValueType() where T : unmanaged + internal void RegisterDirectCommandType() where T : class, IMemoryPackable, new() { var type = typeof(T); - if (_registeredValueTypes.Contains(type)) - throw new InvalidOperationException($"Type {type.Name} is already registered!"); - - if (type.ContainsGenericParameters) - throw new ArgumentException($"Type must be a concrete type!"); + Messenger.OnDebug?.Invoke($"Registering direct command type: {type.Name}"); - var valueCommandType = typeof(ValueCommand<>).MakeGenericType(type); - - var valueListCommandType = typeof(ValueCollectionCommand<,>).MakeGenericType(typeof(List), type); - - var valueHashSetCommandType = typeof(ValueCollectionCommand<,>).MakeGenericType(typeof(HashSet), type); - - NewTypes.AddRange([valueCommandType, valueListCommandType, valueHashSetCommandType]); - - _registeredValueTypes.Add(type); + PushNewTypes([type]); + } - PushNewTypes(); + private IMemoryPackable? Borrow() where T : class, IMemoryPackable, new() + { + return _pool.Borrow(); } - internal static void RegisterAdditionalObjectType() where T : class, IMemoryPackable, new() + private void Return(IMemoryPackable obj) where T : class, IMemoryPackable, new() { - var type = typeof(T); + _pool.Return((T)obj); + } - if (_registeredObjectTypes.Contains(type)) - throw new InvalidOperationException($"Type {type.Name} is already registered!"); + internal IMemoryPackable Borrow(Type type) + { + return _borrowers[_typeToIndex[type]](); + } - if (type.ContainsGenericParameters) - throw new ArgumentException($"Type must be a concrete type!"); + internal void Return(Type type, IMemoryPackable obj) + { + _returners[_typeToIndex[type]](obj); + } - if (type.IsSubclassOf(typeof(PolymorphicMemoryPackableEntity))) + private void PushNewTypes(List types) + { + foreach (var type in types) { - NewTypes.Add(type); - } + _newTypes.Add(type); + _borrowers.Add((Func)_borrowMethod!.MakeGenericMethod(type).CreateDelegate(typeof(Func), this)); + _returners.Add((Action)_returnMethod!.MakeGenericMethod(type).CreateDelegate(typeof(Action), this)); + _typeToIndex[type] = _newTypes.Count - 1; - var objectCommandType = typeof(ObjectCommand<>).MakeGenericType(type); - - var objectListCommandType = typeof(ObjectListCommand<>).MakeGenericType(type); - - NewTypes.AddRange([objectCommandType, objectListCommandType]); - - _registeredObjectTypes.Add(type); - - PushNewTypes(); + if (!_coreTypes.Contains(type)) + _onRegisteredCallback?.Invoke(type); + } } } \ No newline at end of file diff --git a/InterprocessLib.Standalone/InterprocessLib.Standalone.csproj b/InterprocessLib.Standalone/InterprocessLib.Standalone.csproj new file mode 100644 index 0000000..e75aab2 --- /dev/null +++ b/InterprocessLib.Standalone/InterprocessLib.Standalone.csproj @@ -0,0 +1,48 @@ + + + + 3.0.0 + Nytra + net10.0 + 14 + https://github.com/Nytra/ResoniteInterprocessLib + Nytra.InterprocessLib.Standalone + InterprocessLib.Standalone + InterprocessLib + enable + enable + Nullable + true + false + $(ResonitePath)/ + $(MSBuildProgramFiles32)\Steam\steamapps\common\Resonite\ + $(HOME)/.steam/steam/steamapps/common/Resonite/ + G:\SteamLibrary\steamapps\common\Resonite\ + $(HOME)/snap/steam/common/.local/share/Steam/steamapps/common/Resonite/ + Debug;Release;Tests + + + + + + + + + + $(GamePath)Renderite.Shared.dll + + + + + + + + + + + + + + + + diff --git a/InterprocessLib.Standalone/Properties/launchSettings.json b/InterprocessLib.Standalone/Properties/launchSettings.json new file mode 100644 index 0000000..1020231 --- /dev/null +++ b/InterprocessLib.Standalone/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Launch": { + "commandName": "Executable", + "executablePath": "$(GamePath)Renderite.Host.exe", + "commandLineArgs": "-Screen --hookfxr-enable", + "workingDirectory": "$(GamePath)" + } + } +} \ No newline at end of file diff --git a/InterprocessLib.Unity/InterprocessLib.Unity.csproj b/InterprocessLib.Unity/InterprocessLib.Unity.csproj index abd685f..ef7674e 100644 --- a/InterprocessLib.Unity/InterprocessLib.Unity.csproj +++ b/InterprocessLib.Unity/InterprocessLib.Unity.csproj @@ -1,31 +1,32 @@ - 2.0.1 + 3.0.0 Nytra net472 - 12 + 12 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.Unity InterprocessLib.Unity InterprocessLib enable enable + Nullable true false $(ResonitePath)/ $(MSBuildProgramFiles32)\Steam\steamapps\common\Resonite\ $(HOME)/.steam/steam/steamapps/common/Resonite/ - G:\SteamLibrary\steamapps\common\Resonite\ - $(HOME)/snap/steam/common/.local/share/Steam/steamapps/common/Resonite/ - Debug;Release;Tests + G:\SteamLibrary\steamapps\common\Resonite\ + $(HOME)/snap/steam/common/.local/share/Steam/steamapps/common/Resonite/ + Debug;Release;Tests $(GamePath)Renderer\Renderite.Renderer_Data\Managed\Renderite.Unity.dll - + $(GamePath)Renderer\Renderite.Renderer_Data\Managed\Renderite.Shared.dll @@ -34,15 +35,19 @@ $(GamePath)Renderer\Renderite.Renderer_Data\Managed\UnityEngine.CoreModule.dll - + + + + + - - + + diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index 7e160e4..1b4cbcd 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -1,62 +1,78 @@ using Renderite.Shared; using Renderite.Unity; -using System.Reflection; + +#if DEBUG +using UnityEngine; +#endif namespace InterprocessLib; internal static class UnityInit { - private static void CommandHandler(RendererCommand command, int messageSize) - { - if (Messenger.IsInitialized) return; - - if (command is MessengerReadyCommand) - { - Messenger.FinishInitialization(); - } - } public static void Init() { - if (Messenger.Host is not null) - throw new InvalidOperationException("Messenger has already been initialized!"); + if (Messenger.DefaultInitStarted) + throw new InvalidOperationException("Messenger default host initialization has already been started!"); - Task.Run(InitLoop); + Messenger.DefaultInitStarted = true; + + InnerInit(); } - private static async void InitLoop() + private static void InnerInit() { - if (RenderingManager.Instance is null) + Messenger.OnWarning = (msg) => { - await Task.Delay(1); - InitLoop(); - } - else + Debug.LogWarning($"[InterprocessLib] [WARN] {msg}"); + }; + Messenger.OnFailure = (ex) => { - var getConnectionParametersMethod = typeof(RenderingManager).GetMethod("GetConnectionParameters", BindingFlags.Instance | BindingFlags.NonPublic); + Debug.LogError($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Host!\n{ex}"); + }; +#if DEBUG + Messenger.OnDebug = (msg) => + { + Debug.Log($"[InterprocessLib] [DEBUG] {msg}"); + }; +#endif - object[] parameters = { "", 0L }; + //UnityEngine.Debug.Log("Init"); - if (!(bool)getConnectionParametersMethod.Invoke(RenderingManager.Instance, parameters)) + var args = Environment.GetCommandLineArgs(); + string? fullQueueName = null; + for (int i = 0; i < args.Length; i++) + { + if (args[i].Equals("-QueueName", StringComparison.InvariantCultureIgnoreCase)) { - throw new ArgumentException("Could not get connection parameters from RenderingManager!"); + fullQueueName = args[i + 1]; + break; } + } - Messenger.OnWarning = (msg) => - { - UnityEngine.Debug.LogWarning($"[InterprocessLib] [WARN] {msg}"); - }; - Messenger.OnFailure = (ex) => - { - UnityEngine.Debug.LogError($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Host!\n{ex}"); - }; -#if DEBUG - Messenger.OnDebug = (msg) => - { - UnityEngine.Debug.Log($"[InterprocessLib] [DEBUG] {msg}"); - }; -#endif + MessagingSystem? system = null; + + var engineSharedMemoryPrefix = fullQueueName?.Substring(0, fullQueueName.IndexOf('_')); + if (fullQueueName is null || engineSharedMemoryPrefix!.Length == 0) + { + var fallbackTask = Messenger.GetFallbackSystem("Resonite", false, MessagingManager.DEFAULT_CAPACITY, PackerMemoryPool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + fallbackTask.Wait(); + system = fallbackTask.Result; + if (system is null) + throw new EntryPointNotFoundException("Unable to get fallback messaging system!"); + } + else + { + + system = new MessagingSystem(false, $"InterprocessLib-{engineSharedMemoryPrefix}", MessagingManager.DEFAULT_CAPACITY, PackerMemoryPool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + system.Connect(); + } - Messenger.IsAuthority = false; - Messenger.Host = new(Messenger.IsAuthority, (string)parameters[0], (long)parameters[1], PackerMemoryPool.Instance, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + lock (Messenger.LockObj) + { + Messenger.PreInit(system); + Messenger.SetDefaultSystem(system); + system.Initialize(); } + + //UnityEngine.Debug.Log("DONE"); } } \ No newline at end of file diff --git a/InterprocessLib.sln b/InterprocessLib.sln deleted file mode 100644 index b384f3d..0000000 --- a/InterprocessLib.sln +++ /dev/null @@ -1,168 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.BepInEx_Extensions", "Extensions\InterprocessLib.BepInEx_Extensions\InterprocessLib.BepInEx_Extensions.csproj", "{D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3}" - ProjectSection(ProjectDependencies) = postProject - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5} = {1774BEE3-2419-C311-9DA4-42F1CA4A83F5} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.BepisLoader_Extensions", "Extensions\InterprocessLib.BepisLoader_Extensions\InterprocessLib.BepisLoader_Extensions.csproj", "{2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69}" - ProjectSection(ProjectDependencies) = postProject - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} = {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.BepInEx.Tests", "Tests\InterprocessLib.BepInEx.Tests\InterprocessLib.BepInEx.Tests.csproj", "{D0C50EF1-CD09-FBD2-502E-08C6C6F18C73}" - ProjectSection(ProjectDependencies) = postProject - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5} = {1774BEE3-2419-C311-9DA4-42F1CA4A83F5} - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3} = {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.BepisLoader.Tests", "Tests\InterprocessLib.BepisLoader.Tests\InterprocessLib.BepisLoader.Tests.csproj", "{B1748D08-069D-E580-F642-C3E4455443D9}" - ProjectSection(ProjectDependencies) = postProject - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69} = {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69} - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} = {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.RML_Extensions", "Extensions\InterprocessLib.RML_Extensions\InterprocessLib.RML_Extensions.csproj", "{1A93CD67-C9D3-B833-12F8-1687F715D547}" - ProjectSection(ProjectDependencies) = postProject - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} = {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.RML.Tests", "Tests\InterprocessLib.RML.Tests\InterprocessLib.RML.Tests.csproj", "{4652D03F-A2E8-0D4C-10D0-45DECD8C06F9}" - ProjectSection(ProjectDependencies) = postProject - {1A93CD67-C9D3-B833-12F8-1687F715D547} = {1A93CD67-C9D3-B833-12F8-1687F715D547} - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} = {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.FrooxEngine", "InterprocessLib.FrooxEngine\InterprocessLib.FrooxEngine.csproj", "{AE5C3E04-176A-4F33-B13A-3D5E83BC9379}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.Unity", "InterprocessLib.Unity\InterprocessLib.Unity.csproj", "{1774BEE3-2419-C311-9DA4-42F1CA4A83F5}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{C0C1080B-8A1A-4C9F-AA98-B0BED60A802A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3}.Debug|x64.ActiveCfg = Debug|Any CPU - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3}.Debug|x64.Build.0 = Debug|Any CPU - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3}.Debug|x86.ActiveCfg = Debug|Any CPU - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3}.Debug|x86.Build.0 = Debug|Any CPU - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3}.Release|Any CPU.Build.0 = Release|Any CPU - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3}.Release|x64.ActiveCfg = Release|Any CPU - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3}.Release|x64.Build.0 = Release|Any CPU - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3}.Release|x86.ActiveCfg = Release|Any CPU - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3}.Release|x86.Build.0 = Release|Any CPU - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69}.Debug|x64.ActiveCfg = Debug|Any CPU - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69}.Debug|x64.Build.0 = Debug|Any CPU - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69}.Debug|x86.ActiveCfg = Debug|Any CPU - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69}.Debug|x86.Build.0 = Debug|Any CPU - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69}.Release|Any CPU.Build.0 = Release|Any CPU - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69}.Release|x64.ActiveCfg = Release|Any CPU - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69}.Release|x64.Build.0 = Release|Any CPU - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69}.Release|x86.ActiveCfg = Release|Any CPU - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69}.Release|x86.Build.0 = Release|Any CPU - {D0C50EF1-CD09-FBD2-502E-08C6C6F18C73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D0C50EF1-CD09-FBD2-502E-08C6C6F18C73}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D0C50EF1-CD09-FBD2-502E-08C6C6F18C73}.Debug|x64.ActiveCfg = Debug|Any CPU - {D0C50EF1-CD09-FBD2-502E-08C6C6F18C73}.Debug|x64.Build.0 = Debug|Any CPU - {D0C50EF1-CD09-FBD2-502E-08C6C6F18C73}.Debug|x86.ActiveCfg = Debug|Any CPU - {D0C50EF1-CD09-FBD2-502E-08C6C6F18C73}.Debug|x86.Build.0 = Debug|Any CPU - {D0C50EF1-CD09-FBD2-502E-08C6C6F18C73}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D0C50EF1-CD09-FBD2-502E-08C6C6F18C73}.Release|Any CPU.Build.0 = Release|Any CPU - {D0C50EF1-CD09-FBD2-502E-08C6C6F18C73}.Release|x64.ActiveCfg = Release|Any CPU - {D0C50EF1-CD09-FBD2-502E-08C6C6F18C73}.Release|x64.Build.0 = Release|Any CPU - {D0C50EF1-CD09-FBD2-502E-08C6C6F18C73}.Release|x86.ActiveCfg = Release|Any CPU - {D0C50EF1-CD09-FBD2-502E-08C6C6F18C73}.Release|x86.Build.0 = Release|Any CPU - {B1748D08-069D-E580-F642-C3E4455443D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B1748D08-069D-E580-F642-C3E4455443D9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B1748D08-069D-E580-F642-C3E4455443D9}.Debug|x64.ActiveCfg = Debug|Any CPU - {B1748D08-069D-E580-F642-C3E4455443D9}.Debug|x64.Build.0 = Debug|Any CPU - {B1748D08-069D-E580-F642-C3E4455443D9}.Debug|x86.ActiveCfg = Debug|Any CPU - {B1748D08-069D-E580-F642-C3E4455443D9}.Debug|x86.Build.0 = Debug|Any CPU - {B1748D08-069D-E580-F642-C3E4455443D9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B1748D08-069D-E580-F642-C3E4455443D9}.Release|Any CPU.Build.0 = Release|Any CPU - {B1748D08-069D-E580-F642-C3E4455443D9}.Release|x64.ActiveCfg = Release|Any CPU - {B1748D08-069D-E580-F642-C3E4455443D9}.Release|x64.Build.0 = Release|Any CPU - {B1748D08-069D-E580-F642-C3E4455443D9}.Release|x86.ActiveCfg = Release|Any CPU - {B1748D08-069D-E580-F642-C3E4455443D9}.Release|x86.Build.0 = Release|Any CPU - {1A93CD67-C9D3-B833-12F8-1687F715D547}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1A93CD67-C9D3-B833-12F8-1687F715D547}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1A93CD67-C9D3-B833-12F8-1687F715D547}.Debug|x64.ActiveCfg = Debug|Any CPU - {1A93CD67-C9D3-B833-12F8-1687F715D547}.Debug|x64.Build.0 = Debug|Any CPU - {1A93CD67-C9D3-B833-12F8-1687F715D547}.Debug|x86.ActiveCfg = Debug|Any CPU - {1A93CD67-C9D3-B833-12F8-1687F715D547}.Debug|x86.Build.0 = Debug|Any CPU - {1A93CD67-C9D3-B833-12F8-1687F715D547}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1A93CD67-C9D3-B833-12F8-1687F715D547}.Release|Any CPU.Build.0 = Release|Any CPU - {1A93CD67-C9D3-B833-12F8-1687F715D547}.Release|x64.ActiveCfg = Release|Any CPU - {1A93CD67-C9D3-B833-12F8-1687F715D547}.Release|x64.Build.0 = Release|Any CPU - {1A93CD67-C9D3-B833-12F8-1687F715D547}.Release|x86.ActiveCfg = Release|Any CPU - {1A93CD67-C9D3-B833-12F8-1687F715D547}.Release|x86.Build.0 = Release|Any CPU - {4652D03F-A2E8-0D4C-10D0-45DECD8C06F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4652D03F-A2E8-0D4C-10D0-45DECD8C06F9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4652D03F-A2E8-0D4C-10D0-45DECD8C06F9}.Debug|x64.ActiveCfg = Debug|Any CPU - {4652D03F-A2E8-0D4C-10D0-45DECD8C06F9}.Debug|x64.Build.0 = Debug|Any CPU - {4652D03F-A2E8-0D4C-10D0-45DECD8C06F9}.Debug|x86.ActiveCfg = Debug|Any CPU - {4652D03F-A2E8-0D4C-10D0-45DECD8C06F9}.Debug|x86.Build.0 = Debug|Any CPU - {4652D03F-A2E8-0D4C-10D0-45DECD8C06F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4652D03F-A2E8-0D4C-10D0-45DECD8C06F9}.Release|Any CPU.Build.0 = Release|Any CPU - {4652D03F-A2E8-0D4C-10D0-45DECD8C06F9}.Release|x64.ActiveCfg = Release|Any CPU - {4652D03F-A2E8-0D4C-10D0-45DECD8C06F9}.Release|x64.Build.0 = Release|Any CPU - {4652D03F-A2E8-0D4C-10D0-45DECD8C06F9}.Release|x86.ActiveCfg = Release|Any CPU - {4652D03F-A2E8-0D4C-10D0-45DECD8C06F9}.Release|x86.Build.0 = Release|Any CPU - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379}.Debug|x64.ActiveCfg = Debug|Any CPU - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379}.Debug|x64.Build.0 = Debug|Any CPU - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379}.Debug|x86.ActiveCfg = Debug|Any CPU - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379}.Debug|x86.Build.0 = Debug|Any CPU - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379}.Release|Any CPU.Build.0 = Release|Any CPU - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379}.Release|x64.ActiveCfg = Release|Any CPU - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379}.Release|x64.Build.0 = Release|Any CPU - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379}.Release|x86.ActiveCfg = Release|Any CPU - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379}.Release|x86.Build.0 = Release|Any CPU - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5}.Debug|x64.ActiveCfg = Debug|Any CPU - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5}.Debug|x64.Build.0 = Debug|Any CPU - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5}.Debug|x86.ActiveCfg = Debug|Any CPU - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5}.Debug|x86.Build.0 = Debug|Any CPU - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5}.Release|Any CPU.Build.0 = Release|Any CPU - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5}.Release|x64.ActiveCfg = Release|Any CPU - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5}.Release|x64.Build.0 = Release|Any CPU - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5}.Release|x86.ActiveCfg = Release|Any CPU - {1774BEE3-2419-C311-9DA4-42F1CA4A83F5}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {D4B316B6-5C72-BFDF-94F5-8D99AA3BDDB3} = {C0C1080B-8A1A-4C9F-AA98-B0BED60A802A} - {2BD188AD-CC1E-D615-BEE5-C3B48ADF6F69} = {C0C1080B-8A1A-4C9F-AA98-B0BED60A802A} - {D0C50EF1-CD09-FBD2-502E-08C6C6F18C73} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {B1748D08-069D-E580-F642-C3E4455443D9} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {1A93CD67-C9D3-B833-12F8-1687F715D547} = {C0C1080B-8A1A-4C9F-AA98-B0BED60A802A} - {4652D03F-A2E8-0D4C-10D0-45DECD8C06F9} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {B512A2C4-BDEE-469F-9FBA-5A8B2BC50B08} - EndGlobalSection -EndGlobal diff --git a/InterprocessLib.slnx b/InterprocessLib.slnx new file mode 100644 index 0000000..68f2a8e --- /dev/null +++ b/InterprocessLib.slnx @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs b/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs index 83eb024..c2afaa0 100644 --- a/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs +++ b/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs @@ -1,7 +1,8 @@ -using BepInEx; +//#define TEST_OBSOLETE_CONSTRUCTOR + +using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; -using Renderite.Shared; namespace InterprocessLib.Tests; @@ -11,20 +12,31 @@ public class UnityPlugin : BaseUnityPlugin public static ManualLogSource? Log; private static Messenger? _messenger; private static Messenger? _unknownMessenger; - private static Messenger? _anotherOne; + +#if TEST_OBSOLETE_CONSTRUCTOR + private static Messenger? _testObsoleteConstructor; +#endif + public static ConfigEntry? SyncTest; void Awake() { Log = base.Logger; - _messenger = new("InterprocessLib.Tests", [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); - _anotherOne = new("InterprocessLib.Tests.Another", [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); + _messenger = new("InterprocessLib.Tests"); +#if TEST_OBSOLETE_CONSTRUCTOR + _testObsoleteConstructor = new("InterprocessLib.Tests.ObsoleteConstructor", [], []); +#endif _unknownMessenger = new("InterprocessLib.Tests.UnknownMessengerUnity"); SyncTest = Config.Bind("General", "SyncTest", 34); _messenger.SyncConfigEntry(SyncTest); _messenger!.ReceiveEmptyCommand("RunTests", () => { - Tests.RunTests(_messenger, _unknownMessenger!, Log!.LogInfo); + Tests.RunTests(_messenger, Log!.LogInfo); + Tests.RunTests(_unknownMessenger, Log!.LogInfo); + +#if TEST_OBSOLETE_CONSTRUCTOR + Tests.RunTests(_testObsoleteConstructor, Log!.LogInfo); +#endif }); _messenger.ReceiveEmptyCommand("CheckSync", () => { @@ -34,6 +46,10 @@ void Awake() { SyncTest.Value = 0; }); - Tests.RunTests(_messenger, _unknownMessenger!, Log!.LogInfo); + Tests.RunTests(_messenger, Log!.LogInfo); + Tests.RunTests(_unknownMessenger, Log!.LogInfo); +#if TEST_OBSOLETE_CONSTRUCTOR + Tests.RunTests(_testObsoleteConstructor, Log!.LogInfo); +#endif } } \ No newline at end of file diff --git a/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj b/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj index 4620c87..97998bb 100644 --- a/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj +++ b/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj @@ -4,7 +4,7 @@ 1.0.1 Nytra net472 - 12 + 12 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.BepInEx.Tests InterprocessLib.BepInEx.Tests @@ -18,14 +18,15 @@ $(ResonitePath)/ $(MSBuildProgramFiles32)\Steam\steamapps\common\Resonite\ $(HOME)/.steam/steam/steamapps/common/Resonite/ - G:\SteamLibrary\steamapps\common\Resonite\ - $(HOME)/snap/steam/common/.local/share/Steam/steamapps/common/Resonite/ - $(GamePath)Renderer\BepInEx\plugins\$(AssemblyName) + G:\SteamLibrary\steamapps\common\Resonite\ + $(HOME)/snap/steam/common/.local/share/Steam/steamapps/common/Resonite/ + $(GamePath)Renderer\BepInEx\plugins\$(AssemblyName) + $(GamePath)Renderer\BepInEx\plugins\Nytra-InterprocessLib\InterprocessLib.BepInEx Debug;Release;Tests - + $(GamePath)Renderer\BepInEx\core\0Harmony.dll @@ -40,7 +41,7 @@ $(GamePath)Renderer\Renderite.Renderer_Data\Managed\Renderite.Unity.dll - + $(GamePath)Renderer\Renderite.Renderer_Data\Managed\Renderite.Shared.dll @@ -56,16 +57,16 @@ - + - - - - - - - - - + + + + + + + + + diff --git a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs index 870c908..e17813f 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs +++ b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs @@ -1,9 +1,13 @@ -using BepInEx; +//#define TEST_SPAWN_PROCESS +//#define TEST_OBSOLETE_CONSTRUCTOR + +using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BepInEx.NET.Common; using Elements.Core; -using Renderite.Shared; +using System.Diagnostics; +using System.Runtime.InteropServices; namespace InterprocessLib.Tests; @@ -15,11 +19,61 @@ public class Plugin : BasePlugin public static ConfigEntry? RunTestsToggle; public static Messenger? _messenger; public static Messenger? _unknownMessenger; - public static Messenger? _another; + +#if TEST_OBSOLETE_CONSTRUCTOR + public static Messenger? _testObsoleteConstructor; +#endif + public static ConfigEntry? SyncTest; public static ConfigEntry? CheckSyncToggle; public static ConfigEntry? SyncTestOutput; public static ConfigEntry? ResetToggle; + public static ConfigEntry? SendLatencyMilliseconds; + public static ConfigEntry? RecvLatencyMilliseconds; + +#if TEST_SPAWN_PROCESS + public static Messenger? _customMessenger; + public static ConfigEntry? SpawnProcessToggle; + public static ConfigEntry? LastProcessHeartbeat; + private static Random _rand = new(); + private static string? _customQueueName; + private static Process? _customProcess; +#endif + +#if TEST_SPAWN_PROCESS + private static void SpawnProcess() + { + _customProcess?.Kill(); + _customQueueName = $"MyCustomQueue{_rand.Next()}"; + Log!.LogInfo("Child process queue name: " + _customQueueName); + _customMessenger = new Messenger("InterprocessLib.Tests", true, _customQueueName); + _customMessenger!.ReceiveEmptyCommand("Heartbeat", () => + { + LastProcessHeartbeat!.Value = DateTime.Now; + _customMessenger.SendEmptyCommand("HeartbeatResponse"); + }); + _customProcess = new Process(); + + string projectConfiguration; + +#if DEBUG + projectConfiguration = "Debug"; +#else + projectConfiguration = "Release"; +#endif + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + _customProcess.StartInfo.FileName = @$"S:\Projects\ResoniteModDev\_THUNDERSTORE\InterprocessLib\Tests\InterprocessLib.Standalone.Tests\bin\{projectConfiguration}\net10.0\InterprocessLib.Standalone.Tests.exe"; + else + _customProcess.StartInfo.FileName = @$"/home/nytra/code/ResoniteInterprocessLib/Tests/InterprocessLib.Standalone.Tests/bin/{projectConfiguration}/net10.0/InterprocessLib.Standalone.Tests"; + + _customProcess.StartInfo.Arguments = $"{_customQueueName}"; + //_customProcess.StartInfo.UseShellExecute = true; // Run in a new window + _customProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal; + _customProcess.Start(); + Tests.RunTests(_customMessenger, Log!.LogInfo); + } +#endif public override void Load() { @@ -39,11 +93,30 @@ public override void Load() break; } }; - _messenger = new Messenger("InterprocessLib.Tests", [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); - _another = new("InterprocessLib.Tests.Another", [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); + + _messenger = new Messenger("InterprocessLib.Tests"); _unknownMessenger = new Messenger("InterprocessLib.Tests.UnknownMessengerFrooxEngine"); - Tests.RunTests(_messenger, _unknownMessenger!, Log!.LogInfo); +#if TEST_OBSOLETE_CONSTRUCTOR + _testObsoleteConstructor = new("InterprocessLib.Tests.ObsoleteConstructor", [], []); +#endif + + Tests.RunTests(_messenger, Log!.LogInfo); + Tests.RunTests(_unknownMessenger, Log!.LogInfo); + +#if TEST_OBSOLETE_CONSTRUCTOR + Tests.RunTests(_testObsoleteConstructor, Log!.LogInfo); +#endif + +#if TEST_SPAWN_PROCESS + SpawnProcess(); + SpawnProcessToggle = Config.Bind("General", "SpawnChildProcess", false); + SpawnProcessToggle.SettingChanged += (sender, args) => + { + SpawnProcess(); + }; + LastProcessHeartbeat = Config.Bind("General", "LastProcessHeartbeat", DateTime.MinValue); +#endif SyncTest = Config.Bind("General", "SyncTest", 34); _messenger.SyncConfigEntry(SyncTest); @@ -52,10 +125,36 @@ public override void Load() CheckSyncToggle = Config.Bind("General", "CheckSync", false); SyncTestOutput = Config.Bind("General", "SyncTestOutput", 0); ResetToggle = Config.Bind("General", "ResetToggle", false); + SendLatencyMilliseconds = Config.Bind("General", "SendLatencyMilliseconds", -1.0); + RecvLatencyMilliseconds = Config.Bind("General", "RecvLatencyMilliseconds", -1.0); + + _messenger.CheckLatency((send, recv) => + { + SendLatencyMilliseconds.Value = send.TotalMilliseconds; + RecvLatencyMilliseconds.Value = recv.TotalMilliseconds; + }); + RunTestsToggle!.SettingChanged += (sender, args) => { _messenger!.SendEmptyCommand("RunTests"); - Tests.RunTests(_messenger, _unknownMessenger!, Log!.LogInfo); + Tests.RunTests(_messenger, Log!.LogInfo); + Tests.RunTests(_unknownMessenger, Log!.LogInfo); + +#if TEST_OBSOLETE_CONSTRUCTOR + Tests.RunTests(_testObsoleteConstructor, Log!.LogInfo); +#endif + _messenger.CheckLatency((send, recv) => + { + SendLatencyMilliseconds.Value = send.TotalMilliseconds; + RecvLatencyMilliseconds.Value = recv.TotalMilliseconds; + }); + +#if TEST_SPAWN_PROCESS + if (_customMessenger is not null && _customProcess != null && !_customProcess.HasExited) + { + Tests.RunTests(_customMessenger, Log!.LogInfo); + } +#endif }; CheckSyncToggle!.SettingChanged += (sender, args) => { diff --git a/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj b/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj index 809703a..57e7fe0 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj +++ b/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj @@ -3,8 +3,8 @@ 1.0.1 Nytra - net9.0 - 13 + net10.0 + 14 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.BepisLoader.Tests InterprocessLib.BepisLoader.Tests @@ -18,8 +18,10 @@ $(ResonitePath)/ $(MSBuildProgramFiles32)\Steam\steamapps\common\Resonite\ $(HOME)/.steam/steam/steamapps/common/Resonite/ - G:\SteamLibrary\steamapps\common\Resonite\ + G:\SteamLibrary\steamapps\common\Resonite\ + $(HOME)/snap/steam/common/.local/share/Steam/steamapps/common/Resonite/ $(GamePath)BepInEx\plugins\$(AssemblyName) + $(GamePath)BepInEx\plugins\Nytra-InterprocessLib\InterprocessLib.BepisLoader https://nuget-modding.resonite.net/v3/index.json; @@ -54,29 +56,29 @@ - - ..\..\out\InterprocessLib.BepisLoader_Extensions.dll - - - ..\..\out\InterprocessLib.FrooxEngine.dll - + + ..\..\out\InterprocessLib.BepisLoader_Extensions.dll + + + ..\..\out\InterprocessLib.FrooxEngine.dll + - - + + - - - - - - + + + + + + diff --git a/Tests/InterprocessLib.RML.Tests/InterprocessLib.RML.Tests.csproj b/Tests/InterprocessLib.RML.Tests/InterprocessLib.RML.Tests.csproj index b121ee6..6355a98 100644 --- a/Tests/InterprocessLib.RML.Tests/InterprocessLib.RML.Tests.csproj +++ b/Tests/InterprocessLib.RML.Tests/InterprocessLib.RML.Tests.csproj @@ -3,8 +3,8 @@ 1.0.1 Nytra - net9.0 - 13 + net10.0 + 14 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.RML.Tests InterprocessLib.RML.Tests @@ -18,9 +18,10 @@ $(ResonitePath)/ $(MSBuildProgramFiles32)\Steam\steamapps\common\Resonite\ $(HOME)/.steam/steam/steamapps/common/Resonite/ - G:\SteamLibrary\steamapps\common\Resonite\ - $(GamePath)rml_mods - Debug;Release;Tests + G:\SteamLibrary\steamapps\common\Resonite\ + $(HOME)/snap/steam/common/.local/share/Steam/steamapps/common/Resonite/ + $(GamePath)rml_mods + Debug;Release;Tests @@ -42,20 +43,20 @@ $(GamePath)Renderite.Shared.dll False - + $(GamePath)Libraries/ResoniteModLoader.dll False - + $(GamePath)rml_libs/0Harmony.dll False - - ..\..\out\InterprocessLib.FrooxEngine.dll - - + + ..\..\out\InterprocessLib.FrooxEngine.dll + + ..\..\out\InterprocessLib.RML_Extensions.dll @@ -64,14 +65,14 @@ - - + + - - + + diff --git a/Tests/InterprocessLib.RML.Tests/RML_Tests.cs b/Tests/InterprocessLib.RML.Tests/RML_Tests.cs index e6da37e..dab4848 100644 --- a/Tests/InterprocessLib.RML.Tests/RML_Tests.cs +++ b/Tests/InterprocessLib.RML.Tests/RML_Tests.cs @@ -1,4 +1,5 @@ -using Renderite.Shared; +//#define TEST_OBSOLETE_CONSTRUCTOR + using ResoniteModLoader; namespace InterprocessLib.Tests; @@ -23,26 +24,56 @@ public class RML_Tests : ResoniteMod private static ModConfigurationKey SyncTestOutput = new ModConfigurationKey("SyncTestOutput", "SyncTestOutput:", () => 0); [AutoRegisterConfigKey] private static ModConfigurationKey ResetToggle = new ModConfigurationKey("ResetToggle", "ResetToggle:", () => false); - + [AutoRegisterConfigKey] + private static ModConfigurationKey SendLatencyMilliseconds = new ModConfigurationKey("SendLatencyMilliseconds", "SendLatencyMilliseconds:", () => -1.0); + [AutoRegisterConfigKey] + private static ModConfigurationKey RecvLatencyMilliseconds = new ModConfigurationKey("RecvLatencyMilliseconds", "RecvLatencyMilliseconds:", () => -1.0); public static Messenger? _messenger; public static Messenger? _unknownMessenger; - public static Messenger? _another; + +#if TEST_OBSOLETE_CONSTRUCTOR + public static Messenger? _testObsoleteConstructor; +#endif public override void OnEngineInit() { - _messenger = new Messenger("InterprocessLib.Tests", [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); - _another = new("InterprocessLib.Tests.Another", [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); + _messenger = new Messenger("InterprocessLib.Tests"); _unknownMessenger = new Messenger("InterprocessLib.Tests.UnknownMessengerFrooxEngine"); - Tests.RunTests(_messenger, _unknownMessenger!, Msg); +#if TEST_OBSOLETE_CONSTRUCTOR + _testObsoleteConstructor = new("InterprocessLib.Tests.ObsoleteConstructor", [], []); +#endif + + Tests.RunTests(_messenger, Msg); + Tests.RunTests(_unknownMessenger, Msg); + +#if TEST_OBSOLETE_CONSTRUCTOR + Tests.RunTests(_testObsoleteConstructor, Msg); +#endif + + _messenger.CheckLatency((send, recv) => + { + SendLatencyMilliseconds.Value = send.TotalMilliseconds; + RecvLatencyMilliseconds.Value = recv.TotalMilliseconds; + }); _messenger.SyncConfigEntry(SyncTest); RunTestsToggle!.OnChanged += (object? newValue) => { _messenger!.SendEmptyCommand("RunTests"); - Tests.RunTests(_messenger, _unknownMessenger!, Msg); + Tests.RunTests(_messenger, Msg); + Tests.RunTests(_unknownMessenger, Msg); + +#if TEST_OBSOLETE_CONSTRUCTOR + Tests.RunTests(_testObsoleteConstructor, Msg); +#endif + _messenger.CheckLatency((send, recv) => + { + SendLatencyMilliseconds.Value = send.TotalMilliseconds; + RecvLatencyMilliseconds.Value = recv.TotalMilliseconds; + }); }; CheckSyncToggle!.OnChanged += (object? newValue) => { diff --git a/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj b/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj new file mode 100644 index 0000000..89d90cf --- /dev/null +++ b/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj @@ -0,0 +1,24 @@ + + + + Exe + net10.0 + enable + enable + $(ResonitePath)/ + $(MSBuildProgramFiles32)\Steam\steamapps\common\Resonite\ + $(HOME)/.steam/steam/steamapps/common/Resonite/ + G:\SteamLibrary\steamapps\common\Resonite\ + $(HOME)/snap/steam/common/.local/share/Steam/steamapps/common/Resonite/ + + + + + ..\..\out\InterprocessLib.Standalone.dll + + + $(GamePath)Renderer/Renderite.Renderer_Data/Managed/Renderite.Shared.dll + + + + \ No newline at end of file diff --git a/Tests/InterprocessLib.Standalone.Tests/Program.cs b/Tests/InterprocessLib.Standalone.Tests/Program.cs new file mode 100644 index 0000000..d829586 --- /dev/null +++ b/Tests/InterprocessLib.Standalone.Tests/Program.cs @@ -0,0 +1,72 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; +using InterprocessLib; +using InterprocessLib.Tests; +using Renderite.Shared; + +namespace InterprocessLibStandaloneTest +{ + internal class Program + { + private static CancellationTokenSource _cancel = new(); + private static void CommandHandler(RendererCommand command, int messageSize) + { + } + + private static void FailHandler(Exception ex) + { + Console.WriteLine($"[InterprocessLib.Tests] [ERROR] Exception in custom messaging backend: {ex}"); + } + + private static void WarnHandler(string msg) + { + Console.WriteLine($"[InterprocessLib.Tests] [WARN] {msg}"); + } + + private static void DebugHandler(string msg) + { +#if DEBUG + Console.WriteLine($"[InterprocessLib.Tests] [DEBUG] {msg}"); +#endif + } + + static void Main(string[] args) + { + string? queueName; + if (args.Length > 0) + { + queueName = args[0]; + Console.WriteLine("Queue name from args: " + queueName); + } + else + { + Console.WriteLine("Queue name:"); + queueName = Console.ReadLine(); + } + + Messenger.OnWarning = WarnHandler; + Messenger.OnFailure = FailHandler; + Messenger.OnDebug = DebugHandler; + + var messenger = new Messenger("InterprocessLib.Tests", false, queueName!); + + Tests.RunTests(messenger, Console.WriteLine); + + messenger.ReceiveEmptyCommand("HeartbeatResponse", () => + { + _cancel.CancelAfter(5000); + }); + + _cancel.CancelAfter(10000); + + Task.Run(async () => + { + while (!_cancel.IsCancellationRequested) + { + messenger.SendEmptyCommand("Heartbeat"); + await Task.Delay(2500); + } + }).Wait(); + } + } +} diff --git a/thunderstore.toml b/thunderstore.toml index 67c52e7..00a52ff 100644 --- a/thunderstore.toml +++ b/thunderstore.toml @@ -6,7 +6,7 @@ schemaVersion = "0.0.1" [package] namespace = "Nytra" # TODO: Change this to the team you're uploading the mod to on Thunderstore name = "InterprocessLib" # Change this to your mod's name, no spaces or special characters, max 128 characters -versionNumber = "2.0.1" # Change this to your mod's version, must be in semantic versioning format (e.g. 1.0.0, 2.1.3, 0.1.0-alpha.1), Only read during manual usage of tcli. +versionNumber = "3.0.0" # Change this to your mod's version, must be in semantic versioning format (e.g. 1.0.0, 2.1.3, 0.1.0-alpha.1), Only read during manual usage of tcli. description = "Library for mods to send data to the renderer and back." # TODO: Change this to your mod's description, max 250 characters websiteUrl = "https://github.com/Nytra/ResoniteInterprocessLib" # TODO: Change this to your mod's website/repository, or leave blank containsNsfwContent = false