From b3ed63b56cc03e4ff5d64cc32110e29efa147c75 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sun, 5 Oct 2025 00:31:08 +0100 Subject: [PATCH 01/35] Start of custom hosts --- Extensions/BepInExShared.cs | 4 +- .../RML_Extensions.cs | 4 +- .../FrooxEngineInit.cs | 12 +- InterprocessLib.Shared/Host.cs | 7 +- InterprocessLib.Shared/Messenger.cs | 140 ++++++++++++------ InterprocessLib.Unity/UnityInit.cs | 7 +- 6 files changed, 112 insertions(+), 62 deletions(-) diff --git a/Extensions/BepInExShared.cs b/Extensions/BepInExShared.cs index 3c6c042..c332f0d 100644 --- a/Extensions/BepInExShared.cs +++ b/Extensions/BepInExShared.cs @@ -9,7 +9,7 @@ public static class BepInExExtensions public static void SyncConfigEntry(this Messenger messenger, ConfigEntry configEntry) where T : unmanaged { _syncStates[configEntry] = true; - if (Messenger.IsAuthority) + if (messenger.IsAuthority == true) messenger.SendConfigEntry(configEntry); configEntry.SettingChanged += (sender, args) => { @@ -22,7 +22,7 @@ public static void SyncConfigEntry(this Messenger messenger, ConfigEntry c public static void SyncConfigEntry(this Messenger messenger, ConfigEntry configEntry) { _syncStates[configEntry] = true; - if (Messenger.IsAuthority) + if (messenger.IsAuthority == true) messenger.SendConfigEntry(configEntry); configEntry.SettingChanged += (sender, args) => { diff --git a/Extensions/InterprocessLib.RML_Extensions/RML_Extensions.cs b/Extensions/InterprocessLib.RML_Extensions/RML_Extensions.cs index bade922..1f35d7c 100644 --- a/Extensions/InterprocessLib.RML_Extensions/RML_Extensions.cs +++ b/Extensions/InterprocessLib.RML_Extensions/RML_Extensions.cs @@ -9,7 +9,7 @@ public static class RML_Extensions public static void SyncConfigEntry(this Messenger messenger, ModConfigurationKey configEntry) where T : unmanaged { _syncStates[configEntry] = true; - if (Messenger.IsAuthority) + if (messenger.IsAuthority == true) messenger.SendConfigEntry(configEntry); configEntry.OnChanged += (object? newValue) => { @@ -22,7 +22,7 @@ public static void SyncConfigEntry(this Messenger messenger, ModConfiguration public static void SyncConfigEntry(this Messenger messenger, ModConfigurationKey configEntry) { _syncStates[configEntry] = true; - if (Messenger.IsAuthority) + if (messenger.IsAuthority == true) messenger.SendConfigEntry(configEntry); configEntry.OnChanged += (object? newValue) => { diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index 9515fdc..038886b 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -12,7 +12,7 @@ private static void CommandHandler(RendererCommand command, int messageSize) } public static void Init() { - if (Messenger.Host is not null) + if (Messenger.DefaultHost is not null) throw new InvalidOperationException("Messenger has already been initialized!"); Task.Run(InitLoop); @@ -40,15 +40,15 @@ private static async void InitLoop() { UniLog.Error($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Host!\n{ex}"); }; - #if DEBUG +#if DEBUG Messenger.OnDebug = (msg) => { 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(); +#endif + + Messenger.DefaultHost = new MessagingHost(true, renderSystemMessagingHost!.QueueName, renderSystemMessagingHost.QueueCapacity, renderSystemMessagingHost, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + Messenger.FinishDefaultHostInitialization(); } } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Host.cs b/InterprocessLib.Shared/Host.cs index 41626f2..3ba0569 100644 --- a/InterprocessLib.Shared/Host.cs +++ b/InterprocessLib.Shared/Host.cs @@ -3,7 +3,7 @@ namespace InterprocessLib; -internal class MessagingHost +public class MessagingHost { private struct OwnerData { @@ -58,6 +58,11 @@ public void RegisterOwner(string ownerName) _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; diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index b42723f..e7dc697 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -8,19 +8,30 @@ namespace InterprocessLib; /// public class Messenger { - internal static MessagingHost? Host; + internal static MessagingHost? DefaultHost; + + private MessagingHost? CustomHost; + + private MessagingHost? Host => CustomHost ?? DefaultHost; /// /// If true the messenger will send commands immediately, otherwise commands will wait in a queue until the authority process sends the . /// - public static bool IsInitialized => Host is not null && _postInitActions is null; + public static bool IsInitialized => DefaultHost is not null && _postInitActions is null; + + private bool IsNotReady => CustomHost is null && !Messenger.IsInitialized; /// /// Does this process have authority over the other process. /// - public static bool IsAuthority { get; internal set; } + //public static bool IsAuthority { get; internal set; } - internal static bool InitStarted = false; + /// + /// Does this process have authority over the other process. + /// + public bool? IsAuthority => Host?.IsAuthority; + + internal static bool DefaultHostInitStarted = false; internal static Action? OnFailure; @@ -73,18 +84,18 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< _registeredOwnerIds.Add(ownerId); if (IsInitialized) - Register(); + RegisterWithDefaultHost(); else - RunPostInit(Register); + RunPostDefaultHostInit(RegisterWithDefaultHost); } else { OnWarning?.Invoke($"A messenger with id {ownerId} has already been created in this process!"); } - if (Host is null && !InitStarted) + if (DefaultHost is null && !DefaultHostInitStarted) { - InitStarted = true; + DefaultHostInitStarted = true; var frooxEngineInitType = Type.GetType("InterprocessLib.FrooxEngineInit"); if (frooxEngineInitType is not null) @@ -106,15 +117,50 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< } } - private void Register() + internal Messenger(string ownerId, MessagingHost customHost, List? additionalObjectTypes = null, List? additionalValueTypes = null) + { + if (ownerId is null) + throw new ArgumentNullException(nameof(ownerId)); + + if (customHost is null) + throw new ArgumentNullException(nameof(customHost)); + + CustomHost = customHost; + + _ownerId = ownerId; + + _additionalObjectTypes = additionalObjectTypes; + + _additionalValueTypes = additionalValueTypes; + + 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()); + } + + if (!CustomHost.HasOwner(ownerId)) + { + CustomHost!.RegisterOwner(_ownerId); + } + else + { + OnWarning?.Invoke($"A messenger with id {ownerId} has already been created in this process for a custom host with queue name: {CustomHost.QueueName}"); + } + } + + private void RegisterWithDefaultHost() { - Host!.RegisterOwner(_ownerId); + DefaultHost!.RegisterOwner(_ownerId); } - internal static void FinishInitialization() + internal static void FinishDefaultHostInitialization() { - if (IsAuthority) - Host!.SendCommand(new MessengerReadyCommand()); + if (DefaultHost!.IsAuthority) + DefaultHost.SendCommand(new MessengerReadyCommand()); var actions = _postInitActions!.ToArray(); _postInitActions = null; @@ -131,9 +177,9 @@ internal static void FinishInitialization() } } - private static void RunPostInit(Action act) + private static void RunPostDefaultHostInit(Action act) { - if (!IsInitialized) + if (!Messenger.IsInitialized) { _postInitActions!.Add(act); } @@ -146,9 +192,9 @@ public void SendValue(string id, T value) where T : unmanaged if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => SendValue(id, value)); + RunPostDefaultHostInit(() => SendValue(id, value)); return; } @@ -167,9 +213,9 @@ public void SendValueList(string id, List list) where T : unmanaged if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => SendValueList(id, list)); + RunPostDefaultHostInit(() => SendValueList(id, list)); return; } @@ -188,9 +234,9 @@ public void SendValueHashSet(string id, HashSet hashSet) where T : unmanag if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => SendValueHashSet(id, hashSet)); + RunPostDefaultHostInit(() => SendValueHashSet(id, hashSet)); return; } @@ -209,9 +255,9 @@ public void SendString(string id, string str) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => SendString(id, str)); + RunPostDefaultHostInit(() => SendString(id, str)); return; } @@ -227,9 +273,9 @@ public void SendStringList(string id, List list) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => SendStringList(id, list)); + RunPostDefaultHostInit(() => SendStringList(id, list)); return; } @@ -245,9 +291,9 @@ public void SendEmptyCommand(string id) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => SendEmptyCommand(id)); + RunPostDefaultHostInit(() => SendEmptyCommand(id)); return; } @@ -262,9 +308,9 @@ public void SendEmptyCommand(string id) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => SendObject(id, obj)); + RunPostDefaultHostInit(() => SendObject(id, obj)); return; } @@ -284,9 +330,9 @@ public void SendEmptyCommand(string id) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => SendObjectList(id, list)); + RunPostDefaultHostInit(() => SendObjectList(id, list)); return; } @@ -305,9 +351,9 @@ public void ReceiveValue(string id, Action callback) where T : unmanaged if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => ReceiveValue(id, callback)); + RunPostDefaultHostInit(() => ReceiveValue(id, callback)); return; } @@ -322,9 +368,9 @@ public void ReceiveValueList(string id, Action> callback) where T : u if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => ReceiveValueList(id, callback)); + RunPostDefaultHostInit(() => ReceiveValueList(id, callback)); return; } @@ -339,9 +385,9 @@ public void ReceiveValueHashSet(string id, Action> callback) where if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => ReceiveValueHashSet(id, callback)); + RunPostDefaultHostInit(() => ReceiveValueHashSet(id, callback)); return; } @@ -356,9 +402,9 @@ public void ReceiveString(string id, Action callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => ReceiveString(id, callback)); + RunPostDefaultHostInit(() => ReceiveString(id, callback)); return; } @@ -370,9 +416,9 @@ public void ReceiveStringList(string id, Action?>? callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => ReceiveStringList(id, callback)); + RunPostDefaultHostInit(() => ReceiveStringList(id, callback)); return; } @@ -384,9 +430,9 @@ public void ReceiveEmptyCommand(string id, Action callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => ReceiveEmptyCommand(id, callback)); + RunPostDefaultHostInit(() => ReceiveEmptyCommand(id, callback)); return; } @@ -398,9 +444,9 @@ public void ReceiveEmptyCommand(string id, Action callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => ReceiveObject(id, callback)); + RunPostDefaultHostInit(() => ReceiveObject(id, callback)); return; } @@ -415,9 +461,9 @@ public void ReceiveEmptyCommand(string id, Action callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsNotReady) { - RunPostInit(() => ReceiveObjectList(id, callback)); + RunPostDefaultHostInit(() => ReceiveObjectList(id, callback)); return; } diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index 7e160e4..a1eb254 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -12,12 +12,12 @@ private static void CommandHandler(RendererCommand command, int messageSize) if (command is MessengerReadyCommand) { - Messenger.FinishInitialization(); + Messenger.FinishDefaultHostInitialization(); } } public static void Init() { - if (Messenger.Host is not null) + if (Messenger.DefaultHost is not null) throw new InvalidOperationException("Messenger has already been initialized!"); Task.Run(InitLoop); @@ -55,8 +55,7 @@ private static async void InitLoop() }; #endif - Messenger.IsAuthority = false; - Messenger.Host = new(Messenger.IsAuthority, (string)parameters[0], (long)parameters[1], PackerMemoryPool.Instance, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + Messenger.DefaultHost = new(false, (string)parameters[0], (long)parameters[1], PackerMemoryPool.Instance, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); } } } \ No newline at end of file From 15f68bffea59100ff0440980ceed7426c35f9a56 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sun, 5 Oct 2025 20:00:24 +0100 Subject: [PATCH 02/35] Custom queues work --- .../FrooxEngineInit.cs | 11 +- InterprocessLib.Shared/Host.cs | 70 +++++- InterprocessLib.Shared/Messenger.cs | 222 +++++++++--------- InterprocessLib.Shared/Tests.cs | 10 +- InterprocessLib.Shared/TypeManager.cs | 47 ++-- InterprocessLib.Unity/UnityInit.cs | 16 +- InterprocessLib.sln | 18 ++ .../BepInExTests.cs | 8 +- .../BepisLoaderTests.cs | 74 +++++- Tests/InterprocessLib.RML.Tests/RML_Tests.cs | 8 +- .../InterprocessLib.Standalone.Tests.csproj | 26 ++ .../Program.cs | 72 ++++++ 12 files changed, 419 insertions(+), 163 deletions(-) create mode 100644 Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj create mode 100644 Tests/InterprocessLib.Standalone.Tests/Program.cs diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index 038886b..6456ea0 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -12,8 +12,10 @@ private static void CommandHandler(RendererCommand command, int messageSize) } public static void Init() { - if (Messenger.DefaultHost is not null) - throw new InvalidOperationException("Messenger has already been initialized!"); + if (Messenger.DefaultBackendInitStarted) + throw new InvalidOperationException("Messenger default host initialization has already been started!"); + + Messenger.DefaultBackendInitStarted = true; Task.Run(InitLoop); } @@ -47,8 +49,9 @@ private static async void InitLoop() }; #endif - Messenger.DefaultHost = new MessagingHost(true, renderSystemMessagingHost!.QueueName, renderSystemMessagingHost.QueueCapacity, renderSystemMessagingHost, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); - Messenger.FinishDefaultHostInitialization(); + var host = new MessagingBackend(true, renderSystemMessagingHost!.QueueName, renderSystemMessagingHost.QueueCapacity, renderSystemMessagingHost, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + Messenger.SetDefaultBackend(host); + // The authority process automatically initializes when it receives a MessengerReadyCommand from the non-authority process } } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Host.cs b/InterprocessLib.Shared/Host.cs index 3ba0569..9852770 100644 --- a/InterprocessLib.Shared/Host.cs +++ b/InterprocessLib.Shared/Host.cs @@ -3,7 +3,7 @@ namespace InterprocessLib; -public class MessagingHost +public class MessagingBackend { private struct OwnerData { @@ -34,13 +34,13 @@ public OwnerData() private MessagingManager _primary; - private static MethodInfo? _handleValueCommandMethod = typeof(MessagingHost).GetMethod(nameof(HandleValueCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static MethodInfo? _handleValueCommandMethod = typeof(MessagingBackend).GetMethod(nameof(HandleValueCommand), BindingFlags.Instance | BindingFlags.NonPublic); - private static MethodInfo? _handleValueCollectionCommandMethod = typeof(MessagingHost).GetMethod(nameof(HandleValueCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static MethodInfo? _handleValueCollectionCommandMethod = typeof(MessagingBackend).GetMethod(nameof(HandleValueCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic); - private static MethodInfo? _handleObjectCommandMethod = typeof(MessagingHost).GetMethod(nameof(HandleObjectCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static MethodInfo? _handleObjectCommandMethod = typeof(MessagingBackend).GetMethod(nameof(HandleObjectCommand), BindingFlags.Instance | BindingFlags.NonPublic); - private static MethodInfo? _handleObjectListCommandMethod = typeof(MessagingHost).GetMethod(nameof(HandleObjectListCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static MethodInfo? _handleObjectListCommandMethod = typeof(MessagingBackend).GetMethod(nameof(HandleObjectListCommand), BindingFlags.Instance | BindingFlags.NonPublic); private RenderCommandHandler? OnCommandReceived { get; } @@ -52,6 +52,50 @@ public OwnerData() private Dictionary _ownerData = new(); + private Action? _postInitCallback; + + public bool IsAlive { get; private set; } + + public bool IsInitialized => _postInitActions is null; + + internal List? _postInitActions = new(); + + public void Initialize() + { + if (IsInitialized) + throw new InvalidOperationException("Already initialized!"); + + if (!IsAuthority) + SendCommand(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(); @@ -98,12 +142,12 @@ public void RegisterEmptyCallback(string owner, string id, Action callback) _ownerData[owner].ObjectListCallbacks[id] = callback; } - static MessagingHost() + static MessagingBackend() { TypeManager.InitializeCoreTypes(); } - public MessagingHost(bool isAuthority, string queueName, long queueCapacity, IMemoryPackerEntityPool pool, RenderCommandHandler? commandHandler, Action? failhandler, Action? warnHandler, Action? debugHandler) + public MessagingBackend(bool isAuthority, string queueName, long queueCapacity, IMemoryPackerEntityPool pool, RenderCommandHandler? commandHandler = null, Action? failhandler = null, Action? warnHandler = null, Action? debugHandler = null, Action? postInitCallback = null) { IsAuthority = isAuthority; QueueName = queueName + "InterprocessLib"; @@ -114,10 +158,13 @@ public MessagingHost(bool isAuthority, string queueName, long queueCapacity, IMe OnFailure = failhandler; OnCommandReceived = commandHandler; + _postInitCallback = postInitCallback; + _primary = new MessagingManager(pool); _primary.CommandHandler = CommandHandler; _primary.FailureHandler = (ex) => - { + { + IsAlive = false; OnFailure?.Invoke(ex); }; _primary.WarningHandler = (msg) => @@ -126,6 +173,7 @@ public MessagingHost(bool isAuthority, string queueName, long queueCapacity, IMe }; _primary.Connect(queueName, isAuthority, queueCapacity); + IsAlive = true; } private void HandleValueCommand(ValueCommand command) where T : unmanaged @@ -237,6 +285,12 @@ private void CommandHandler(RendererCommand command, int messageSize) { OnDebug?.Invoke($"Received {command}"); + if (!IsInitialized && command is MessengerReadyCommand) + { + Initialize(); + return; + } + OnCommandReceived?.Invoke(command, messageSize); if (command is IdentifiableCommand identifiableCommand) diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index e7dc697..2d6177c 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -1,5 +1,4 @@ using Renderite.Shared; -using System.Runtime.InteropServices; namespace InterprocessLib; @@ -8,44 +7,51 @@ namespace InterprocessLib; /// public class Messenger { - internal static MessagingHost? DefaultHost; + private static MessagingBackend? _defaultBackend; - private MessagingHost? CustomHost; + private MessagingBackend? _customBackend; - private MessagingHost? Host => CustomHost ?? DefaultHost; + private MessagingBackend? Backend => _customBackend ?? _defaultBackend; /// - /// If true the messenger will send commands immediately, otherwise commands will wait in a queue until the authority process sends the . + /// If true the messenger will send commands immediately, otherwise commands will wait in a queue until the non-authority process sends the . /// - public static bool IsInitialized => DefaultHost is not null && _postInitActions is null; - - private bool IsNotReady => CustomHost is null && !Messenger.IsInitialized; + public bool IsInitialized => Backend?.IsInitialized == true; /// /// Does this process have authority over the other process. /// - //public static bool IsAuthority { get; internal set; } + public bool? IsAuthority => Backend?.IsAuthority; /// - /// Does this process have authority over the other process. + /// Is the interprocess connection still available /// - public bool? IsAuthority => Host?.IsAuthority; + public bool? IsAlive => Backend?.IsAlive; - internal static bool DefaultHostInitStarted = false; + internal static bool DefaultBackendInitStarted = false; - internal static Action? OnFailure; + /// + /// Called when the backend connection has a critical error + /// + public static Action? OnFailure; - internal static Action? OnWarning; + /// + /// Called when something potentially bad/unexpected happens + /// + public static Action? OnWarning; -#pragma warning disable CS0649 - internal static Action? OnDebug; + /// + /// Called with additional debugging information + /// +#pragma warning disable CS0649 + public static Action? OnDebug; #pragma warning restore - private static List? _postInitActions = new(); + internal static List? _defaultBackendPostInitActions = new(); private string _ownerId; - private static HashSet _registeredOwnerIds = new(); + private static HashSet _defaultBackendRegisteredOwnerIds = new(); private List? _additionalObjectTypes; @@ -58,7 +64,7 @@ public class Messenger /// 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. /// - /// + /// public Messenger(string ownerId, List? additionalObjectTypes = null, List? additionalValueTypes = null) { if (ownerId is null) @@ -79,24 +85,20 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< TypeManager.InitValueTypeList(_additionalValueTypes.Where(t => !TypeManager.IsValueTypeInitialized(t)).ToList()); } - if (!_registeredOwnerIds.Contains(ownerId)) + if (_defaultBackend?.IsInitialized != true) { - _registeredOwnerIds.Add(ownerId); - - if (IsInitialized) - RegisterWithDefaultHost(); - else - RunPostDefaultHostInit(RegisterWithDefaultHost); + DefaultBackendRunPostInit(() => + { + Register(); + }); } else { - OnWarning?.Invoke($"A messenger with id {ownerId} has already been created in this process!"); + Register(); } - if (DefaultHost is null && !DefaultHostInitStarted) + if (_defaultBackend is null && !DefaultBackendInitStarted) { - DefaultHostInitStarted = true; - var frooxEngineInitType = Type.GetType("InterprocessLib.FrooxEngineInit"); if (frooxEngineInitType is not null) { @@ -117,15 +119,30 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< } } - internal Messenger(string ownerId, MessagingHost customHost, List? additionalObjectTypes = null, List? additionalValueTypes = null) + internal static void SetDefaultBackend(MessagingBackend backend) + { + _defaultBackend = backend; + _defaultBackend._postInitActions = _defaultBackendPostInitActions; + _defaultBackendPostInitActions = null; + } + + /// + /// Creates an instance with a unique owner and a custom backend + /// + /// Unique identifier for this instance in this process. Should match the other process. + /// /// Custom messaging backend. Allows connecting to any custom 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. + /// + public Messenger(string ownerId, MessagingBackend customBackend, List? additionalObjectTypes = null, List? additionalValueTypes = null) { if (ownerId is null) throw new ArgumentNullException(nameof(ownerId)); - if (customHost is null) - throw new ArgumentNullException(nameof(customHost)); + if (customBackend is null) + throw new ArgumentNullException(nameof(customBackend)); - CustomHost = customHost; + _customBackend = customBackend; _ownerId = ownerId; @@ -142,49 +159,42 @@ internal Messenger(string ownerId, MessagingHost customHost, List? additio TypeManager.InitValueTypeList(_additionalValueTypes.Where(t => !TypeManager.IsValueTypeInitialized(t)).ToList()); } - if (!CustomHost.HasOwner(ownerId)) + if (!Backend!.HasOwner(ownerId)) { - CustomHost!.RegisterOwner(_ownerId); + Register(); } else { - OnWarning?.Invoke($"A messenger with id {ownerId} has already been created in this process for a custom host with queue name: {CustomHost.QueueName}"); + OnWarning?.Invoke($"A messenger with id {ownerId} has already been created in this process for a custom backend with queue name: {Backend.QueueName}"); } } - private void RegisterWithDefaultHost() + private void RunPostInit(Action act) { - DefaultHost!.RegisterOwner(_ownerId); + if (Backend is null) + DefaultBackendRunPostInit(act); + else + Backend.RunPostInit(act); } - internal static void FinishDefaultHostInitialization() + private void Register() { - if (DefaultHost!.IsAuthority) - DefaultHost.SendCommand(new MessengerReadyCommand()); - - var actions = _postInitActions!.ToArray(); - _postInitActions = null; - foreach (var action in actions) + if (Backend!.HasOwner(_ownerId)) { - try - { - action(); - } - catch (Exception ex) - { - OnWarning?.Invoke($"Exception running post-init action:\n{ex}"); - } + OnWarning?.Invoke($"Owner {_ownerId} has already been registered in this process for messaging backend with queue name: {Backend.QueueName}"); } + else + Backend.RegisterOwner(_ownerId); } - private static void RunPostDefaultHostInit(Action act) + private static void DefaultBackendRunPostInit(Action act) { - if (!Messenger.IsInitialized) + if (_defaultBackend?.IsInitialized != true) { - _postInitActions!.Add(act); + _defaultBackendPostInitActions!.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 @@ -192,9 +202,9 @@ public void SendValue(string id, T value) where T : unmanaged if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => SendValue(id, value)); + RunPostInit(() => SendValue(id, value)); return; } @@ -205,7 +215,7 @@ public void SendValue(string id, T value) where T : unmanaged command.Owner = _ownerId; command.Id = id; command.Value = value; - Host!.SendCommand(command); + Backend!.SendCommand(command); } public void SendValueList(string id, List list) where T : unmanaged @@ -213,9 +223,9 @@ public void SendValueList(string id, List list) where T : unmanaged if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => SendValueList(id, list)); + RunPostInit(() => SendValueList(id, list)); return; } @@ -226,7 +236,7 @@ public void SendValueList(string id, List list) where T : unmanaged command.Owner = _ownerId; command.Id = id; command.Values = list; - Host!.SendCommand(command); + Backend!.SendCommand(command); } public void SendValueHashSet(string id, HashSet hashSet) where T : unmanaged @@ -234,9 +244,9 @@ public void SendValueHashSet(string id, HashSet hashSet) where T : unmanag if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => SendValueHashSet(id, hashSet)); + RunPostInit(() => SendValueHashSet(id, hashSet)); return; } @@ -247,7 +257,7 @@ public void SendValueHashSet(string id, HashSet hashSet) where T : unmanag command.Owner = _ownerId; command.Id = id; command.Values = hashSet; - Host!.SendCommand(command); + Backend!.SendCommand(command); } public void SendString(string id, string str) @@ -255,9 +265,9 @@ public void SendString(string id, string str) if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => SendString(id, str)); + RunPostInit(() => SendString(id, str)); return; } @@ -265,7 +275,7 @@ public void SendString(string id, string str) command.Owner = _ownerId; command.Id = id; command.String = str; - Host!.SendCommand(command); + Backend!.SendCommand(command); } public void SendStringList(string id, List list) @@ -273,9 +283,9 @@ public void SendStringList(string id, List list) if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => SendStringList(id, list)); + RunPostInit(() => SendStringList(id, list)); return; } @@ -283,7 +293,7 @@ public void SendStringList(string id, List list) command.Owner = _ownerId; command.Id = id; command.Values = list; - Host!.SendCommand(command); + Backend!.SendCommand(command); } public void SendEmptyCommand(string id) @@ -291,16 +301,16 @@ public void SendEmptyCommand(string id) if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => SendEmptyCommand(id)); + RunPostInit(() => SendEmptyCommand(id)); return; } var command = new EmptyCommand(); command.Owner = _ownerId; command.Id = id; - Host!.SendCommand(command); + Backend!.SendCommand(command); } public void SendObject(string id, T? obj) where T : class, IMemoryPackable, new() @@ -308,9 +318,9 @@ public void SendEmptyCommand(string id) if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => SendObject(id, obj)); + RunPostInit(() => SendObject(id, obj)); return; } @@ -322,7 +332,7 @@ public void SendEmptyCommand(string id) wrapper.Owner = _ownerId; wrapper.Id = id; - Host!.SendCommand(wrapper); + Backend!.SendCommand(wrapper); } public void SendObjectList(string id, List list) where T : class, IMemoryPackable, new() @@ -330,9 +340,9 @@ public void SendEmptyCommand(string id) if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => SendObjectList(id, list)); + RunPostInit(() => SendObjectList(id, list)); return; } @@ -343,7 +353,7 @@ public void SendEmptyCommand(string id) command.Owner = _ownerId; command.Id = id; command.Values = list; - Host!.SendCommand(command); + Backend!.SendCommand(command); } public void ReceiveValue(string id, Action callback) where T : unmanaged @@ -351,16 +361,16 @@ public void ReceiveValue(string id, Action callback) where T : unmanaged if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => ReceiveValue(id, callback)); + RunPostInit(() => ReceiveValue(id, callback)); return; } if (!TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - Host!.RegisterValueCallback(_ownerId, id, callback); + Backend!.RegisterValueCallback(_ownerId, id, callback); } public void ReceiveValueList(string id, Action> callback) where T : unmanaged @@ -368,16 +378,16 @@ public void ReceiveValueList(string id, Action> callback) where T : u if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => ReceiveValueList(id, callback)); + RunPostInit(() => ReceiveValueList(id, callback)); return; } if (!TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - Host!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); + Backend!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); } public void ReceiveValueHashSet(string id, Action> callback) where T : unmanaged @@ -385,16 +395,16 @@ public void ReceiveValueHashSet(string id, Action> callback) where if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => ReceiveValueHashSet(id, callback)); + RunPostInit(() => ReceiveValueHashSet(id, callback)); return; } if (!TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - Host!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); + Backend!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); } public void ReceiveString(string id, Action callback) @@ -402,13 +412,13 @@ public void ReceiveString(string id, Action callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if(!IsInitialized) { - RunPostDefaultHostInit(() => ReceiveString(id, callback)); + RunPostInit(() => ReceiveString(id, callback)); return; } - Host!.RegisterStringCallback(_ownerId, id, callback); + Backend!.RegisterStringCallback(_ownerId, id, callback); } public void ReceiveStringList(string id, Action?>? callback) @@ -416,13 +426,13 @@ public void ReceiveStringList(string id, Action?>? callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => ReceiveStringList(id, callback)); + RunPostInit(() => ReceiveStringList(id, callback)); return; } - Host!.RegisterStringListCallback(_ownerId, id, callback); + Backend!.RegisterStringListCallback(_ownerId, id, callback); } public void ReceiveEmptyCommand(string id, Action callback) @@ -430,13 +440,13 @@ public void ReceiveEmptyCommand(string id, Action callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => ReceiveEmptyCommand(id, callback)); + RunPostInit(() => ReceiveEmptyCommand(id, callback)); return; } - Host!.RegisterEmptyCallback(_ownerId, id, callback); + Backend!.RegisterEmptyCallback(_ownerId, id, callback); } public void ReceiveObject(string id, Action callback) where T : class, IMemoryPackable, new() @@ -444,16 +454,16 @@ public void ReceiveEmptyCommand(string id, Action callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => ReceiveObject(id, callback)); + RunPostInit(() => ReceiveObject(id, callback)); return; } if (!TypeManager.IsObjectTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - Host!.RegisterObjectCallback(_ownerId, id, callback); + Backend!.RegisterObjectCallback(_ownerId, id, callback); } public void ReceiveObjectList(string id, Action> callback) where T : class, IMemoryPackable, new() @@ -461,15 +471,15 @@ public void ReceiveEmptyCommand(string id, Action callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsNotReady) + if (!IsInitialized) { - RunPostDefaultHostInit(() => ReceiveObjectList(id, callback)); + RunPostInit(() => ReceiveObjectList(id, callback)); return; } if (!TypeManager.IsObjectTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - Host!.RegisterObjectListCallback(_ownerId, id, callback); + Backend!.RegisterObjectListCallback(_ownerId, id, callback); } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index df12d61..82fccf2 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(); @@ -115,11 +112,6 @@ static void TestUnregisteredVanillaValue() _messenger.SendValue("TestUnregisteredVanillaValue", val); } - static void TestUnknownMessenger() - { - _unknownMessenger!.SendEmptyCommand("UnknownMessengerTest"); - } - static void TestUnknownCommandId() { _messenger!.SendEmptyCommand("UnknownIdTest"); diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 5e2181a..83065ba 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -11,13 +11,13 @@ internal static class TypeManager private static bool _initializedCoreTypes = false; - internal static MethodInfo? RegisterValueTypeMethod = typeof(TypeManager).GetMethod(nameof(TypeManager.RegisterAdditionalValueType), BindingFlags.NonPublic | BindingFlags.Static); + private static MethodInfo? _registerValueTypeMethod = typeof(TypeManager).GetMethod(nameof(TypeManager.RegisterAdditionalValueType), BindingFlags.NonPublic | BindingFlags.Static); - internal static MethodInfo? RegisterObjectTypeMethod = typeof(TypeManager).GetMethod(nameof(TypeManager.RegisterAdditionalObjectType), BindingFlags.NonPublic | BindingFlags.Static); + private static MethodInfo? _registerObjectTypeMethod = typeof(TypeManager).GetMethod(nameof(TypeManager.RegisterAdditionalObjectType), BindingFlags.NonPublic | BindingFlags.Static); - internal static List NewTypes = new(); + private static List _newTypes = new(); - private static List RegisteredTypesList => (List)typeof(PolymorphicMemoryPackableEntity).GetField("types", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!; + private static List CurrentRendererCommandTypes => (List)typeof(PolymorphicMemoryPackableEntity).GetField("types", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!; private static Type[] _valueTypes = { @@ -38,7 +38,11 @@ internal static class TypeManager typeof(TimeSpan) }; - // Make sure this always happens first before adding other types? + static TypeManager() + { + InitializeCoreTypes(); + } + internal static void InitializeCoreTypes() { if (_initializedCoreTypes) return; @@ -52,7 +56,7 @@ internal static void InitializeCoreTypes() { try { - RegisterValueTypeMethod!.MakeGenericMethod(valueType).Invoke(null, null); + _registerValueTypeMethod!.MakeGenericMethod(valueType).Invoke(null, null); } catch (Exception ex) { @@ -60,38 +64,45 @@ internal static void InitializeCoreTypes() } } + PushNewTypes(); + _initializedCoreTypes = true; } private static void PushNewTypes() { // Trigger RendererCommand static constructor - var cmd = new EmptyCommand(); + new EmptyCommand(); var list = new List(); - list.AddRange(RegisteredTypesList); - foreach (var type in TypeManager.NewTypes) + list.AddRange(CurrentRendererCommandTypes); + foreach (var type in _newTypes) { if (!list.Contains(type)) list.Add(type); } + IdentifiableCommand.InitNewTypes(list); } internal static void InitValueTypeList(List types) { + Messenger.OnDebug?.Invoke($"Registering additional value types: {string.Join(",", types.Select(t => t.Name))}"); foreach (var type in types) { - RegisterValueTypeMethod!.MakeGenericMethod(type).Invoke(null, null); + _registerValueTypeMethod!.MakeGenericMethod(type).Invoke(null, null); } + PushNewTypes(); } internal static void InitObjectTypeList(List types) { + Messenger.OnDebug?.Invoke($"Registering additional object types: {string.Join(",", types.Select(t => t.Name))}"); foreach (var type in types) { - RegisterObjectTypeMethod!.MakeGenericMethod(type).Invoke(null, null); + _registerObjectTypeMethod!.MakeGenericMethod(type).Invoke(null, null); } + PushNewTypes(); } internal static bool IsValueTypeInitialized() where T : unmanaged @@ -114,7 +125,7 @@ internal static bool IsObjectTypeInitialized(Type t) return _registeredObjectTypes.Contains(t); } - internal static void RegisterAdditionalValueType() where T : unmanaged + private static void RegisterAdditionalValueType() where T : unmanaged { var type = typeof(T); @@ -130,14 +141,12 @@ internal static void RegisterAdditionalValueType() where T : unmanaged var valueHashSetCommandType = typeof(ValueCollectionCommand<,>).MakeGenericType(typeof(HashSet), type); - NewTypes.AddRange([valueCommandType, valueListCommandType, valueHashSetCommandType]); + _newTypes.AddRange([valueCommandType, valueListCommandType, valueHashSetCommandType]); _registeredValueTypes.Add(type); - - PushNewTypes(); } - internal static void RegisterAdditionalObjectType() where T : class, IMemoryPackable, new() + private static void RegisterAdditionalObjectType() where T : class, IMemoryPackable, new() { var type = typeof(T); @@ -149,17 +158,15 @@ internal static void RegisterAdditionalValueType() where T : unmanaged if (type.IsSubclassOf(typeof(PolymorphicMemoryPackableEntity))) { - NewTypes.Add(type); + _newTypes.Add(type); } var objectCommandType = typeof(ObjectCommand<>).MakeGenericType(type); var objectListCommandType = typeof(ObjectListCommand<>).MakeGenericType(type); - NewTypes.AddRange([objectCommandType, objectListCommandType]); + _newTypes.AddRange([objectCommandType, objectListCommandType]); _registeredObjectTypes.Add(type); - - PushNewTypes(); } } \ No newline at end of file diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index a1eb254..3b954a8 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -8,17 +8,13 @@ internal static class UnityInit { private static void CommandHandler(RendererCommand command, int messageSize) { - if (Messenger.IsInitialized) return; - - if (command is MessengerReadyCommand) - { - Messenger.FinishDefaultHostInitialization(); - } } public static void Init() { - if (Messenger.DefaultHost is not null) - throw new InvalidOperationException("Messenger has already been initialized!"); + if (Messenger.DefaultBackendInitStarted) + throw new InvalidOperationException("Messenger default host initialization has already been started!"); + + Messenger.DefaultBackendInitStarted = true; Task.Run(InitLoop); } @@ -55,7 +51,9 @@ private static async void InitLoop() }; #endif - Messenger.DefaultHost = new(false, (string)parameters[0], (long)parameters[1], PackerMemoryPool.Instance, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + var host = new MessagingBackend(false, (string)parameters[0], (long)parameters[1], PackerMemoryPool.Instance, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + Messenger.SetDefaultBackend(host); + host.Initialize(); } } } \ No newline at end of file diff --git a/InterprocessLib.sln b/InterprocessLib.sln index b384f3d..1f7c7a8 100644 --- a/InterprocessLib.sln +++ b/InterprocessLib.sln @@ -44,6 +44,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{02EA681E EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{C0C1080B-8A1A-4C9F-AA98-B0BED60A802A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.Standalone.Tests", "Tests\InterprocessLib.Standalone.Tests\InterprocessLib.Standalone.Tests.csproj", "{2C33CB8C-DE73-DE88-6485-020522AD24C1}" + ProjectSection(ProjectDependencies) = postProject + {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} = {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -150,6 +155,18 @@ Global {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 + {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|x64.ActiveCfg = Debug|Any CPU + {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|x64.Build.0 = Debug|Any CPU + {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|x86.ActiveCfg = Debug|Any CPU + {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|x86.Build.0 = Debug|Any CPU + {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Release|Any CPU.Build.0 = Release|Any CPU + {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Release|x64.ActiveCfg = Release|Any CPU + {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Release|x64.Build.0 = Release|Any CPU + {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Release|x86.ActiveCfg = Release|Any CPU + {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -161,6 +178,7 @@ Global {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} + {2C33CB8C-DE73-DE88-6485-020522AD24C1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B512A2C4-BDEE-469F-9FBA-5A8B2BC50B08} diff --git a/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs b/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs index 83eb024..cb06646 100644 --- a/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs +++ b/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs @@ -24,7 +24,9 @@ void Awake() _messenger.SyncConfigEntry(SyncTest); _messenger!.ReceiveEmptyCommand("RunTests", () => { - Tests.RunTests(_messenger, _unknownMessenger!, Log!.LogInfo); + Tests.RunTests(_messenger, Log!.LogInfo); + Tests.RunTests(_unknownMessenger, Log!.LogInfo); + Tests.RunTests(_anotherOne, Log!.LogInfo); }); _messenger.ReceiveEmptyCommand("CheckSync", () => { @@ -34,6 +36,8 @@ void Awake() { SyncTest.Value = 0; }); - Tests.RunTests(_messenger, _unknownMessenger!, Log!.LogInfo); + Tests.RunTests(_messenger, Log!.LogInfo); + Tests.RunTests(_unknownMessenger, Log!.LogInfo); + Tests.RunTests(_anotherOne, Log!.LogInfo); } } \ No newline at end of file diff --git a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs index 870c908..1901ea1 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs +++ b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs @@ -1,12 +1,28 @@ -using BepInEx; +//#define TEST_SPAWN_PROCESS + +using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BepInEx.NET.Common; using Elements.Core; using Renderite.Shared; +using System.Diagnostics; namespace InterprocessLib.Tests; +class MyPool : IMemoryPackerEntityPool +{ + T IMemoryPackerEntityPool.Borrow() + { + return Pool.Borrow(); + } + + void IMemoryPackerEntityPool.Return(T value) + { + Pool.ReturnCleaned(ref value); + } +} + [BepInExResoniteShim.ResonitePlugin(PluginMetadata.GUID, PluginMetadata.NAME, PluginMetadata.VERSION, PluginMetadata.AUTHORS, PluginMetadata.REPOSITORY_URL)] [BepInDependency(BepInExResoniteShim.PluginMetadata.GUID, BepInDependency.DependencyFlags.HardDependency)] public class Plugin : BasePlugin @@ -16,11 +32,52 @@ public class Plugin : BasePlugin public static Messenger? _messenger; public static Messenger? _unknownMessenger; public static Messenger? _another; + public static ConfigEntry? SyncTest; public static ConfigEntry? CheckSyncToggle; public static ConfigEntry? SyncTestOutput; public static ConfigEntry? ResetToggle; + public static ConfigEntry? SpawnProcessToggle; + private static Random _rand = new(); + private static string? _customQueueName; + + private static void CommandHandler(RendererCommand command, int messageSize) + { + } + + private static void FailHandler(Exception ex) + { + Log!.LogError($"[Child Process Messaging Host] Exception in custom messaging host: {ex}"); + } + + private static void WarnHandler(string msg) + { + Log!.LogWarning($"[Child Process Messaging Host] {msg}"); + } + + private static void DebugHandler(string msg) + { + Log!.LogDebug($"[Child Process Messaging Host] {msg}"); + } + + private static void SpawnProcess() + { +#if TEST_SPAWN_PROCESS + _customQueueName ??= $"MyCustomQueue{_rand.Next()}"; + Log!.LogInfo("Child process queue name: " + _customQueueName); + var customHost = new MessagingBackend(true, _customQueueName, 1024 * 1024, new MyPool(), CommandHandler, FailHandler, WarnHandler, DebugHandler); + var customHostMessenger = new Messenger("InterprocessLib.Tests", customHost, [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); + var process = new Process(); + process.StartInfo.FileName = @"S:\Projects\ResoniteModDev\_THUNDERSTORE\InterprocessLib\Tests\InterprocessLib.Standalone.Tests\bin\Release\net9.0\InterprocessLib.Standalone.Tests.exe"; + process.StartInfo.Arguments = _customQueueName; + process.StartInfo.UseShellExecute = true; // Run in a new window + process.StartInfo.WindowStyle = ProcessWindowStyle.Normal; + process.Start(); + Tests.RunTests(customHostMessenger, Log!.LogInfo); +#endif + } + public override void Load() { Log = base.Log; @@ -39,11 +96,15 @@ 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)]); _unknownMessenger = new Messenger("InterprocessLib.Tests.UnknownMessengerFrooxEngine"); - Tests.RunTests(_messenger, _unknownMessenger!, Log!.LogInfo); + Tests.RunTests(_messenger, Log!.LogInfo); + Tests.RunTests(_unknownMessenger, Log!.LogInfo); + Tests.RunTests(_another, Log!.LogInfo); + SpawnProcess(); SyncTest = Config.Bind("General", "SyncTest", 34); _messenger.SyncConfigEntry(SyncTest); @@ -52,10 +113,17 @@ public override void Load() CheckSyncToggle = Config.Bind("General", "CheckSync", false); SyncTestOutput = Config.Bind("General", "SyncTestOutput", 0); ResetToggle = Config.Bind("General", "ResetToggle", false); + SpawnProcessToggle = Config.Bind("General", "SpawnChildProcess", false); + SpawnProcessToggle.SettingChanged += (sender, args) => + { + SpawnProcess(); + }; RunTestsToggle!.SettingChanged += (sender, args) => { _messenger!.SendEmptyCommand("RunTests"); - Tests.RunTests(_messenger, _unknownMessenger!, Log!.LogInfo); + Tests.RunTests(_messenger, Log!.LogInfo); + Tests.RunTests(_unknownMessenger, Log!.LogInfo); + Tests.RunTests(_another, Log!.LogInfo); }; CheckSyncToggle!.SettingChanged += (sender, args) => { diff --git a/Tests/InterprocessLib.RML.Tests/RML_Tests.cs b/Tests/InterprocessLib.RML.Tests/RML_Tests.cs index e6da37e..0290dbd 100644 --- a/Tests/InterprocessLib.RML.Tests/RML_Tests.cs +++ b/Tests/InterprocessLib.RML.Tests/RML_Tests.cs @@ -35,14 +35,18 @@ public override void OnEngineInit() _another = new("InterprocessLib.Tests.Another", [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); _unknownMessenger = new Messenger("InterprocessLib.Tests.UnknownMessengerFrooxEngine"); - Tests.RunTests(_messenger, _unknownMessenger!, Msg); + Tests.RunTests(_messenger, Msg); + Tests.RunTests(_unknownMessenger, Msg); + Tests.RunTests(_another, Msg); _messenger.SyncConfigEntry(SyncTest); RunTestsToggle!.OnChanged += (object? newValue) => { _messenger!.SendEmptyCommand("RunTests"); - Tests.RunTests(_messenger, _unknownMessenger!, Msg); + Tests.RunTests(_messenger, Msg); + Tests.RunTests(_unknownMessenger, Msg); + Tests.RunTests(_another, Msg); }; 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..e6656a6 --- /dev/null +++ b/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj @@ -0,0 +1,26 @@ + + + + Exe + net9.0 + enable + enable + $(ResonitePath)/ + $(MSBuildProgramFiles32)\Steam\steamapps\common\Resonite\ + $(HOME)/.steam/steam/steamapps/common/Resonite/ + G:\SteamLibrary\steamapps\common\Resonite\ + + + + + $(GamePath)Elements.Core.dll + + + ..\..\out\InterprocessLib.FrooxEngine.dll + + + $(GamePath)Renderite.Shared.dll + + + + diff --git a/Tests/InterprocessLib.Standalone.Tests/Program.cs b/Tests/InterprocessLib.Standalone.Tests/Program.cs new file mode 100644 index 0000000..a662db0 --- /dev/null +++ b/Tests/InterprocessLib.Standalone.Tests/Program.cs @@ -0,0 +1,72 @@ +using Elements.Core; +using InterprocessLib; +using InterprocessLib.Tests; +using Renderite.Shared; + +namespace InterprocessLibStandaloneTest +{ + class MyPool : IMemoryPackerEntityPool + { + T IMemoryPackerEntityPool.Borrow() + { + return Pool.Borrow(); + } + + void IMemoryPackerEntityPool.Return(T value) + { + Pool.ReturnCleaned(ref value); + } + } + + internal class Program + { + private static void CommandHandler(RendererCommand command, int messageSize) + { + } + + private static void FailHandler(Exception ex) + { + Console.WriteLine($"[Custom Messaging Host] [ERROR] Exception in custom messaging host: {ex}"); + } + + private static void WarnHandler(string msg) + { + Console.WriteLine($"[Custom Messaging Host] [WARN] {msg}"); + } + + private static void DebugHandler(string msg) + { +#if DEBUG + Console.WriteLine($"[Custom Messaging Host] [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 customHost = new MessagingBackend(false, queueName!, 1024 * 1024, new MyPool(), CommandHandler, FailHandler, WarnHandler, DebugHandler); + customHost.Initialize(); + + var messenger = new Messenger("InterprocessLib.Tests", customHost, [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); + + Tests.RunTests(messenger, Console.WriteLine); + + Thread.Sleep(15000); + } + } +} From d4641ca9c1abf49426e188f2108e4cfbdf55b069 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sun, 5 Oct 2025 20:05:35 +0100 Subject: [PATCH 03/35] Small changes to logging --- InterprocessLib.FrooxEngine/FrooxEngineInit.cs | 4 ++-- InterprocessLib.Shared/Messenger.cs | 2 +- Tests/InterprocessLib.Standalone.Tests/Program.cs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index 6456ea0..f574580 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -13,7 +13,7 @@ private static void CommandHandler(RendererCommand command, int messageSize) public static void Init() { if (Messenger.DefaultBackendInitStarted) - throw new InvalidOperationException("Messenger default host initialization has already been started!"); + throw new InvalidOperationException("Messenger default backend initialization has already been started!"); Messenger.DefaultBackendInitStarted = true; @@ -40,7 +40,7 @@ private static async void InitLoop() }; Messenger.OnFailure = (ex) => { - UniLog.Error($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Host!\n{ex}"); + UniLog.Error($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Backend!\n{ex}"); }; #if DEBUG Messenger.OnDebug = (msg) => diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index 2d6177c..a25e51a 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -51,7 +51,7 @@ public class Messenger private string _ownerId; - private static HashSet _defaultBackendRegisteredOwnerIds = new(); + //private static HashSet _defaultBackendRegisteredOwnerIds = new(); private List? _additionalObjectTypes; diff --git a/Tests/InterprocessLib.Standalone.Tests/Program.cs b/Tests/InterprocessLib.Standalone.Tests/Program.cs index a662db0..584d51b 100644 --- a/Tests/InterprocessLib.Standalone.Tests/Program.cs +++ b/Tests/InterprocessLib.Standalone.Tests/Program.cs @@ -26,18 +26,18 @@ private static void CommandHandler(RendererCommand command, int messageSize) private static void FailHandler(Exception ex) { - Console.WriteLine($"[Custom Messaging Host] [ERROR] Exception in custom messaging host: {ex}"); + Console.WriteLine($"[InterprocessLib.Tests] [ERROR] Exception in custom messaging backend: {ex}"); } private static void WarnHandler(string msg) { - Console.WriteLine($"[Custom Messaging Host] [WARN] {msg}"); + Console.WriteLine($"[InterprocessLib.Tests] [WARN] {msg}"); } private static void DebugHandler(string msg) { #if DEBUG - Console.WriteLine($"[Custom Messaging Host] [DEBUG] {msg}"); + Console.WriteLine($"[InterprocessLib.Tests] [DEBUG] {msg}"); #endif } From d287b2617169dfb2fae578f3371a3997f6c2ac0a Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sun, 5 Oct 2025 22:56:10 +0100 Subject: [PATCH 04/35] give each backend a unique type manager --- .../FrooxEngineInit.cs | 2 +- .../{Host.cs => Backend.cs} | 50 +++++++---- InterprocessLib.Shared/Commands.cs | 74 +++++++++++++--- InterprocessLib.Shared/Messenger.cs | 60 ++++++------- InterprocessLib.Shared/TypeManager.cs | 85 +++++++++++-------- InterprocessLib.Unity/UnityInit.cs | 2 +- .../BepisLoaderTests.cs | 9 +- 7 files changed, 179 insertions(+), 103 deletions(-) rename InterprocessLib.Shared/{Host.cs => Backend.cs} (89%) diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index f574580..67107c2 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -49,7 +49,7 @@ private static async void InitLoop() }; #endif - var host = new MessagingBackend(true, renderSystemMessagingHost!.QueueName, renderSystemMessagingHost.QueueCapacity, renderSystemMessagingHost, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + var host = new MessagingBackend(true, renderSystemMessagingHost!.QueueName + "InterprocessLib", renderSystemMessagingHost.QueueCapacity, renderSystemMessagingHost, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); Messenger.SetDefaultBackend(host); // The authority process automatically initializes when it receives a MessengerReadyCommand from the non-authority process } diff --git a/InterprocessLib.Shared/Host.cs b/InterprocessLib.Shared/Backend.cs similarity index 89% rename from InterprocessLib.Shared/Host.cs rename to InterprocessLib.Shared/Backend.cs index 9852770..acfed59 100644 --- a/InterprocessLib.Shared/Host.cs +++ b/InterprocessLib.Shared/Backend.cs @@ -1,5 +1,6 @@ using Renderite.Shared; using System.Reflection; +using System.Runtime.CompilerServices; namespace InterprocessLib; @@ -42,7 +43,7 @@ public OwnerData() private static MethodInfo? _handleObjectListCommandMethod = typeof(MessagingBackend).GetMethod(nameof(HandleObjectListCommand), BindingFlags.Instance | BindingFlags.NonPublic); - private RenderCommandHandler? OnCommandReceived { get; } + //private RenderCommandHandler? OnCommandReceived { get; } private Action? OnWarning { get; } @@ -60,13 +61,17 @@ public OwnerData() internal List? _postInitActions = new(); + internal TypeManager TypeManager; + public void Initialize() { if (IsInitialized) throw new InvalidOperationException("Already initialized!"); if (!IsAuthority) + { SendCommand(new MessengerReadyCommand()); + } var actions = _postInitActions!.ToArray(); _postInitActions = null; @@ -142,24 +147,26 @@ public void RegisterEmptyCallback(string owner, string id, Action callback) _ownerData[owner].ObjectListCallbacks[id] = callback; } - static MessagingBackend() - { - TypeManager.InitializeCoreTypes(); - } + //static MessagingBackend() + //{ + // TypeManager.InitializeCoreTypes(); + //} public MessagingBackend(bool isAuthority, string queueName, long queueCapacity, IMemoryPackerEntityPool pool, RenderCommandHandler? commandHandler = null, Action? failhandler = null, Action? warnHandler = null, Action? debugHandler = null, Action? postInitCallback = null) { IsAuthority = isAuthority; - QueueName = queueName + "InterprocessLib"; + QueueName = queueName; QueueCapacity = queueCapacity; OnDebug = debugHandler; OnWarning = warnHandler; OnFailure = failhandler; - OnCommandReceived = commandHandler; + //OnCommandReceived = commandHandler; _postInitCallback = postInitCallback; + TypeManager = new(QueueName); + _primary = new MessagingManager(pool); _primary.CommandHandler = CommandHandler; _primary.FailureHandler = (ex) => @@ -236,7 +243,7 @@ private void HandleStringListCommand(StringListCommand command) } } - private void HandleEmptyCommand(EmptyCommand command) + private void HandleEmptyCommand(IdentifiableCommand command) { if (_ownerData[command.Owner].EmptyCallbacks.TryGetValue(command.Id, out var callback)) { @@ -281,9 +288,11 @@ private void HandleEmptyCommand(EmptyCommand command) } } - private void CommandHandler(RendererCommand command, int messageSize) + private void CommandHandler(RendererCommand wrappedCommand, int messageSize) { - OnDebug?.Invoke($"Received {command}"); + var command = ((WrapperCommand)wrappedCommand).Wrapped; + + OnDebug?.Invoke($"Received {command?.ToString() ?? command?.GetType().Name ?? "NULL"}"); if (!IsInitialized && command is MessengerReadyCommand) { @@ -291,7 +300,7 @@ private void CommandHandler(RendererCommand command, int messageSize) return; } - OnCommandReceived?.Invoke(command, messageSize); + //OnCommandReceived?.Invoke(command, messageSize); if (command is IdentifiableCommand identifiableCommand) { @@ -340,21 +349,26 @@ private void CommandHandler(RendererCommand command, int messageSize) 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}"); + case IdentifiableCommand: + HandleEmptyCommand((IdentifiableCommand)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) + public void SendCommand(IMemoryPackable command) { OnDebug?.Invoke($"Sending {command}"); - _primary.SendCommand(command); + + var wrapper = new WrapperCommand(); + wrapper.QueueName = QueueName; + wrapper.Wrapped = command; + + _primary.SendCommand(wrapper); } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index bf28a35..9adc38e 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -1,20 +1,23 @@ using Renderite.Shared; using System.Collections; +using System.Linq.Expressions; +using System.Reflection; namespace InterprocessLib; // IMPORTANT: // RendererCommand derived classes MUST NOT have constructors because it breaks Unity for some reason -internal abstract class IdentifiableCommand : RendererCommand +public class IdentifiableCommand : PolymorphicMemoryPackableEntity { internal string Owner = ""; public string Id = ""; + public int Type; - public static void InitNewTypes(List types) - { - InitTypes(types); - } + //public static void InitNewTypes(List types) + //{ + // InitTypes(types); + //} public override void Pack(ref MemoryPacker packer) { @@ -68,15 +71,15 @@ public override string ToString() } } -internal sealed class EmptyCommand : IdentifiableCommand -{ - // owo +//internal sealed class EmptyCommand : IdentifiableCommand +//{ +// // owo - public override string ToString() - { - return $"EmptyCommand:{Owner}:{Id}"; - } -} +// public override string ToString() +// { +// return $"EmptyCommand:{Owner}:{Id}"; +// } +//} internal sealed class ValueCollectionCommand : CollectionCommand where C : ICollection, new() where T : unmanaged { @@ -219,7 +222,7 @@ public override string ToString() } } -internal sealed class MessengerReadyCommand : RendererCommand +internal sealed class MessengerReadyCommand : IdentifiableCommand { public override void Pack(ref MemoryPacker packer) { @@ -233,4 +236,47 @@ public override string ToString() { return $"MessengerReadyCommand"; } +} + +internal class WrapperCommand : RendererCommand +{ + public string? QueueName; + public int Type; + public IMemoryPackable? Wrapped; + + public static void InitNewTypes(List types) + { + InitTypes(types); + } + + public override void Pack(ref MemoryPacker packer) + { +#pragma warning disable CS8604 + packer.Write(QueueName); +#pragma warning restore + packer.Write(Wrapped is null ? -1 : TypeManager.GetTypeManager(QueueName).GetTypeIndex(Wrapped.GetType())); + packer.WriteObject(Wrapped); + } + + public override void Unpack(ref MemoryUnpacker unpacker) + { +#pragma warning disable CS8601 + unpacker.Read(ref QueueName); +#pragma warning restore + unpacker.Read(ref Type); + + if (Type == -1) + { + Wrapped = null; + return; + } + + var type = TypeManager.GetTypeManager(QueueName).GetTypeFromIndex(Type); + + if (unpacker.Read()) + { + Wrapped = (IMemoryPackable?)Activator.CreateInstance(type); + Wrapped?.Unpack(ref unpacker); + } + } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index a25e51a..e35c600 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -49,6 +49,8 @@ public class Messenger internal static List? _defaultBackendPostInitActions = new(); + private static object _lockObj = new(); + private string _ownerId; //private static HashSet _defaultBackendRegisteredOwnerIds = new(); @@ -76,16 +78,7 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< _additionalValueTypes = additionalValueTypes; - 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()); - } - - if (_defaultBackend?.IsInitialized != true) + if (_defaultBackend is null) { DefaultBackendRunPostInit(() => { @@ -150,22 +143,22 @@ public Messenger(string ownerId, MessagingBackend customBackend, List? add _additionalValueTypes = additionalValueTypes; - if (_additionalObjectTypes is not null) + if (!_customBackend.HasOwner(ownerId)) { - TypeManager.InitObjectTypeList(_additionalObjectTypes.Where(t => !TypeManager.IsObjectTypeInitialized(t)).ToList()); + Register(); } - if (_additionalValueTypes is not null) + else { - TypeManager.InitValueTypeList(_additionalValueTypes.Where(t => !TypeManager.IsValueTypeInitialized(t)).ToList()); + OnWarning?.Invoke($"A messenger with id {ownerId} has already been created in this process for a custom backend with queue name: {_customBackend.QueueName}"); } - if (!Backend!.HasOwner(ownerId)) + if (_additionalObjectTypes is not null) { - Register(); + _customBackend.TypeManager.InitObjectTypeList(_additionalObjectTypes.Where(t => !_customBackend.TypeManager.IsObjectTypeInitialized(t)).ToList()); } - else + if (_additionalValueTypes is not null) { - OnWarning?.Invoke($"A messenger with id {ownerId} has already been created in this process for a custom backend with queue name: {Backend.QueueName}"); + _customBackend.TypeManager.InitValueTypeList(_additionalValueTypes.Where(t => !_customBackend.TypeManager.IsValueTypeInitialized(t)).ToList()); } } @@ -185,6 +178,15 @@ private void Register() } else Backend.RegisterOwner(_ownerId); + + if (_additionalObjectTypes is not null) + { + Backend.TypeManager.InitObjectTypeList(_additionalObjectTypes.Where(t => !Backend.TypeManager.IsObjectTypeInitialized(t)).ToList()); + } + if (_additionalValueTypes is not null) + { + Backend.TypeManager.InitValueTypeList(_additionalValueTypes.Where(t => !Backend.TypeManager.IsValueTypeInitialized(t)).ToList()); + } } private static void DefaultBackendRunPostInit(Action act) @@ -208,7 +210,7 @@ public void SendValue(string id, T value) where T : unmanaged return; } - if (!TypeManager.IsValueTypeInitialized()) + if (!Backend!.TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {value.GetType().Name} needs to be registered first!"); var command = new ValueCommand(); @@ -229,7 +231,7 @@ public void SendValueList(string id, List list) where T : unmanaged return; } - if (!TypeManager.IsValueTypeInitialized()) + if (!Backend!.TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); var command = new ValueCollectionCommand, T>(); @@ -250,7 +252,7 @@ public void SendValueHashSet(string id, HashSet hashSet) where T : unmanag return; } - if (!TypeManager.IsValueTypeInitialized()) + if (!Backend!.TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); var command = new ValueCollectionCommand, T>(); @@ -307,7 +309,7 @@ public void SendEmptyCommand(string id) return; } - var command = new EmptyCommand(); + var command = new IdentifiableCommand(); command.Owner = _ownerId; command.Id = id; Backend!.SendCommand(command); @@ -324,7 +326,7 @@ public void SendEmptyCommand(string id) return; } - if (!TypeManager.IsObjectTypeInitialized()) + if (!Backend!.TypeManager.IsObjectTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); var wrapper = new ObjectCommand(); @@ -346,7 +348,7 @@ public void SendEmptyCommand(string id) return; } - if (!TypeManager.IsObjectTypeInitialized()) + if (!Backend!.TypeManager.IsObjectTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); var command = new ObjectListCommand(); @@ -367,7 +369,7 @@ public void ReceiveValue(string id, Action callback) where T : unmanaged return; } - if (!TypeManager.IsValueTypeInitialized()) + if (!Backend!.TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); Backend!.RegisterValueCallback(_ownerId, id, callback); @@ -384,7 +386,7 @@ public void ReceiveValueList(string id, Action> callback) where T : u return; } - if (!TypeManager.IsValueTypeInitialized()) + if (!Backend!.TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); Backend!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); @@ -401,7 +403,7 @@ public void ReceiveValueHashSet(string id, Action> callback) where return; } - if (!TypeManager.IsValueTypeInitialized()) + if (!Backend!.TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); Backend!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); @@ -460,7 +462,7 @@ public void ReceiveEmptyCommand(string id, Action callback) return; } - if (!TypeManager.IsObjectTypeInitialized()) + if (!Backend!.TypeManager.IsObjectTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); Backend!.RegisterObjectCallback(_ownerId, id, callback); @@ -477,7 +479,7 @@ public void ReceiveEmptyCommand(string id, Action callback) return; } - if (!TypeManager.IsObjectTypeInitialized()) + if (!Backend!.TypeManager.IsObjectTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); Backend!.RegisterObjectListCallback(_ownerId, id, callback); diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 83065ba..2b35585 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -1,24 +1,27 @@ using Renderite.Shared; +using System.Collections.Generic; using System.Reflection; namespace InterprocessLib; -internal static class TypeManager +internal class TypeManager { - private static readonly HashSet _registeredObjectTypes = new(); + private readonly HashSet _registeredObjectTypes = new(); - private static readonly HashSet _registeredValueTypes = new(); + private readonly HashSet _registeredValueTypes = new(); - private static bool _initializedCoreTypes = false; + private bool _initializedCoreTypes = false; - private static MethodInfo? _registerValueTypeMethod = typeof(TypeManager).GetMethod(nameof(TypeManager.RegisterAdditionalValueType), BindingFlags.NonPublic | BindingFlags.Static); + private static MethodInfo? _registerValueTypeMethod = typeof(TypeManager).GetMethod(nameof(TypeManager.RegisterAdditionalValueType), BindingFlags.NonPublic | BindingFlags.Instance); - private static MethodInfo? _registerObjectTypeMethod = typeof(TypeManager).GetMethod(nameof(TypeManager.RegisterAdditionalObjectType), BindingFlags.NonPublic | BindingFlags.Static); + private static MethodInfo? _registerObjectTypeMethod = typeof(TypeManager).GetMethod(nameof(TypeManager.RegisterAdditionalObjectType), BindingFlags.NonPublic | BindingFlags.Instance); - private static List _newTypes = new(); + private List _newTypes = new(); private static List CurrentRendererCommandTypes => (List)typeof(PolymorphicMemoryPackableEntity).GetField("types", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!; + private static Dictionary _typeManagers = new(); + private static Type[] _valueTypes = { typeof(bool), @@ -40,15 +43,33 @@ internal static class TypeManager static TypeManager() { + // Trigger RendererCommand static constructor + new WrapperCommand(); + + var list = new List(); + list.AddRange(CurrentRendererCommandTypes); + list.Add(typeof(WrapperCommand)); + + WrapperCommand.InitNewTypes(list); + } + + internal TypeManager(string queueName) + { + _typeManagers.Add(queueName, this); InitializeCoreTypes(); } - internal static void InitializeCoreTypes() + internal static TypeManager GetTypeManager(string queueName) + { + return _typeManagers[queueName]; + } + + internal void InitializeCoreTypes() { if (_initializedCoreTypes) return; RegisterAdditionalObjectType(); - RegisterAdditionalObjectType(); + RegisterAdditionalObjectType(); RegisterAdditionalObjectType(); RegisterAdditionalObjectType(); @@ -56,7 +77,7 @@ internal static void InitializeCoreTypes() { try { - _registerValueTypeMethod!.MakeGenericMethod(valueType).Invoke(null, null); + _registerValueTypeMethod!.MakeGenericMethod(valueType).Invoke(this, null); } catch (Exception ex) { @@ -64,68 +85,58 @@ internal static void InitializeCoreTypes() } } - PushNewTypes(); - _initializedCoreTypes = true; } - private static void PushNewTypes() + internal Type GetTypeFromIndex(int index) { - // Trigger RendererCommand static constructor - new EmptyCommand(); - - var list = new List(); - list.AddRange(CurrentRendererCommandTypes); - foreach (var type in _newTypes) - { - if (!list.Contains(type)) - list.Add(type); - } + return _newTypes[index]; + } - IdentifiableCommand.InitNewTypes(list); + internal int GetTypeIndex(Type type) + { + return _newTypes.IndexOf(type); } - internal static void InitValueTypeList(List types) + internal void InitValueTypeList(List types) { Messenger.OnDebug?.Invoke($"Registering additional value types: {string.Join(",", types.Select(t => t.Name))}"); foreach (var type in types) { - _registerValueTypeMethod!.MakeGenericMethod(type).Invoke(null, null); + _registerValueTypeMethod!.MakeGenericMethod(type).Invoke(this, null); } - PushNewTypes(); } - internal static void InitObjectTypeList(List types) + internal void InitObjectTypeList(List types) { Messenger.OnDebug?.Invoke($"Registering additional object types: {string.Join(",", types.Select(t => t.Name))}"); foreach (var type in types) { - _registerObjectTypeMethod!.MakeGenericMethod(type).Invoke(null, null); + _registerObjectTypeMethod!.MakeGenericMethod(type).Invoke(this, null); } - PushNewTypes(); } - internal static bool IsValueTypeInitialized() where T : unmanaged + internal bool IsValueTypeInitialized() where T : unmanaged { return _registeredValueTypes.Contains(typeof(T)); } - internal static bool IsValueTypeInitialized(Type t) + internal bool IsValueTypeInitialized(Type t) { return _registeredValueTypes.Contains(t); } - internal static bool IsObjectTypeInitialized() where T : class, IMemoryPackable, new() + internal bool IsObjectTypeInitialized() where T : class, IMemoryPackable, new() { return _registeredObjectTypes.Contains(typeof(T)); } - internal static bool IsObjectTypeInitialized(Type t) + internal bool IsObjectTypeInitialized(Type t) { return _registeredObjectTypes.Contains(t); } - private static void RegisterAdditionalValueType() where T : unmanaged + private void RegisterAdditionalValueType() where T : unmanaged { var type = typeof(T); @@ -146,7 +157,7 @@ private static void RegisterAdditionalValueType() where T : unmanaged _registeredValueTypes.Add(type); } - private static void RegisterAdditionalObjectType() where T : class, IMemoryPackable, new() + private void RegisterAdditionalObjectType() where T : class, IMemoryPackable, new() { var type = typeof(T); @@ -156,7 +167,7 @@ private static void RegisterAdditionalValueType() where T : unmanaged if (type.ContainsGenericParameters) throw new ArgumentException($"Type must be a concrete type!"); - if (type.IsSubclassOf(typeof(PolymorphicMemoryPackableEntity))) + if (type.IsSubclassOf(typeof(PolymorphicMemoryPackableEntity))) { _newTypes.Add(type); } diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index 3b954a8..156324e 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -51,7 +51,7 @@ private static async void InitLoop() }; #endif - var host = new MessagingBackend(false, (string)parameters[0], (long)parameters[1], PackerMemoryPool.Instance, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + var host = new MessagingBackend(false, (string)parameters[0] + "InterprocessLib", (long)parameters[1], PackerMemoryPool.Instance, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); Messenger.SetDefaultBackend(host); host.Initialize(); } diff --git a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs index 1901ea1..368b34d 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs +++ b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs @@ -1,4 +1,4 @@ -//#define TEST_SPAWN_PROCESS +#define TEST_SPAWN_PROCESS using BepInEx; using BepInEx.Configuration; @@ -38,9 +38,12 @@ public class Plugin : BasePlugin public static ConfigEntry? SyncTestOutput; public static ConfigEntry? ResetToggle; + private static MessagingBackend? _customBackend; public static ConfigEntry? SpawnProcessToggle; private static Random _rand = new(); +#pragma warning disable CS0169 private static string? _customQueueName; +#pragma warning restore private static void CommandHandler(RendererCommand command, int messageSize) { @@ -66,8 +69,8 @@ private static void SpawnProcess() #if TEST_SPAWN_PROCESS _customQueueName ??= $"MyCustomQueue{_rand.Next()}"; Log!.LogInfo("Child process queue name: " + _customQueueName); - var customHost = new MessagingBackend(true, _customQueueName, 1024 * 1024, new MyPool(), CommandHandler, FailHandler, WarnHandler, DebugHandler); - var customHostMessenger = new Messenger("InterprocessLib.Tests", customHost, [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); + _customBackend ??= new MessagingBackend(true, _customQueueName, 1024 * 1024, new MyPool(), CommandHandler, FailHandler, WarnHandler, DebugHandler); + var customHostMessenger = new Messenger("InterprocessLib.Tests", _customBackend, [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); var process = new Process(); process.StartInfo.FileName = @"S:\Projects\ResoniteModDev\_THUNDERSTORE\InterprocessLib\Tests\InterprocessLib.Standalone.Tests\bin\Release\net9.0\InterprocessLib.Standalone.Tests.exe"; process.StartInfo.Arguments = _customQueueName; From 3648baaf0cd0d6cdf5798893a64f0a4e0935a19d Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sun, 5 Oct 2025 23:34:35 +0100 Subject: [PATCH 05/35] Optimize and fix some stuff --- InterprocessLib.Shared/Backend.cs | 4 +- InterprocessLib.Shared/Commands.cs | 62 +++++++++++---------------- InterprocessLib.Shared/Messenger.cs | 20 +-------- InterprocessLib.Shared/TypeManager.cs | 9 ++-- 4 files changed, 32 insertions(+), 63 deletions(-) diff --git a/InterprocessLib.Shared/Backend.cs b/InterprocessLib.Shared/Backend.cs index acfed59..6a2524a 100644 --- a/InterprocessLib.Shared/Backend.cs +++ b/InterprocessLib.Shared/Backend.cs @@ -290,7 +290,7 @@ private void HandleEmptyCommand(IdentifiableCommand command) private void CommandHandler(RendererCommand wrappedCommand, int messageSize) { - var command = ((WrapperCommand)wrappedCommand).Wrapped; + var command = ((WrapperCommand)wrappedCommand).PackedObject; OnDebug?.Invoke($"Received {command?.ToString() ?? command?.GetType().Name ?? "NULL"}"); @@ -367,7 +367,7 @@ public void SendCommand(IMemoryPackable command) var wrapper = new WrapperCommand(); wrapper.QueueName = QueueName; - wrapper.Wrapped = command; + wrapper.PackedObject = command; _primary.SendCommand(wrapper); } diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 9adc38e..68a9e97 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -8,24 +8,18 @@ namespace InterprocessLib; // IMPORTANT: // RendererCommand derived classes MUST NOT have constructors because it breaks Unity for some reason -public class IdentifiableCommand : PolymorphicMemoryPackableEntity +public class IdentifiableCommand : IMemoryPackable { internal string Owner = ""; public string Id = ""; - public int Type; - //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); } - public override void Unpack(ref MemoryUnpacker unpacker) + public virtual void Unpack(ref MemoryUnpacker unpacker) { unpacker.Read(ref Owner); unpacker.Read(ref Id); @@ -71,16 +65,6 @@ 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 { public C? Values; @@ -238,11 +222,11 @@ public override string ToString() } } -internal class WrapperCommand : RendererCommand +internal class WrapperCommand : RendererCommand { + public int TypeIndex; public string? QueueName; - public int Type; - public IMemoryPackable? Wrapped; + public IMemoryPackable? PackedObject; public static void InitNewTypes(List types) { @@ -251,32 +235,36 @@ public static void InitNewTypes(List types) public override void Pack(ref MemoryPacker packer) { + var type = PackedObject is null ? -1 : TypeManager.GetTypeManager(QueueName!).GetTypeIndex(PackedObject.GetType()); + packer.Write(type); + + if (type == -1) + return; + #pragma warning disable CS8604 packer.Write(QueueName); #pragma warning restore - packer.Write(Wrapped is null ? -1 : TypeManager.GetTypeManager(QueueName).GetTypeIndex(Wrapped.GetType())); - packer.WriteObject(Wrapped); + + PackedObject!.Pack(ref packer); } public override void Unpack(ref MemoryUnpacker unpacker) { -#pragma warning disable CS8601 - unpacker.Read(ref QueueName); -#pragma warning restore - unpacker.Read(ref Type); + unpacker.Read(ref TypeIndex); - if (Type == -1) + if (TypeIndex == -1) { - Wrapped = null; + PackedObject = null; return; } - var type = TypeManager.GetTypeManager(QueueName).GetTypeFromIndex(Type); - - if (unpacker.Read()) - { - Wrapped = (IMemoryPackable?)Activator.CreateInstance(type); - Wrapped?.Unpack(ref unpacker); - } +#pragma warning disable CS8601 + unpacker.Read(ref QueueName); +#pragma warning restore + + var type = TypeManager.GetTypeManager(QueueName).GetTypeFromIndex(TypeIndex); + + PackedObject = (IMemoryPackable?)Activator.CreateInstance(type); + PackedObject!.Unpack(ref unpacker); } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index e35c600..dc7356c 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -49,8 +49,6 @@ public class Messenger internal static List? _defaultBackendPostInitActions = new(); - private static object _lockObj = new(); - private string _ownerId; //private static HashSet _defaultBackendRegisteredOwnerIds = new(); @@ -143,23 +141,7 @@ public Messenger(string ownerId, MessagingBackend customBackend, List? add _additionalValueTypes = additionalValueTypes; - if (!_customBackend.HasOwner(ownerId)) - { - Register(); - } - else - { - OnWarning?.Invoke($"A messenger with id {ownerId} has already been created in this process for a custom backend with queue name: {_customBackend.QueueName}"); - } - - if (_additionalObjectTypes is not null) - { - _customBackend.TypeManager.InitObjectTypeList(_additionalObjectTypes.Where(t => !_customBackend.TypeManager.IsObjectTypeInitialized(t)).ToList()); - } - if (_additionalValueTypes is not null) - { - _customBackend.TypeManager.InitValueTypeList(_additionalValueTypes.Where(t => !_customBackend.TypeManager.IsValueTypeInitialized(t)).ToList()); - } + Register(); } private void RunPostInit(Action act) diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 2b35585..ad8c76c 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -48,7 +48,9 @@ static TypeManager() var list = new List(); list.AddRange(CurrentRendererCommandTypes); - list.Add(typeof(WrapperCommand)); + var wrapperType = typeof(WrapperCommand); + if (!list.Contains(wrapperType)) + list.Add(wrapperType); WrapperCommand.InitNewTypes(list); } @@ -167,10 +169,7 @@ private void RegisterAdditionalValueType() where T : unmanaged if (type.ContainsGenericParameters) throw new ArgumentException($"Type must be a concrete type!"); - if (type.IsSubclassOf(typeof(PolymorphicMemoryPackableEntity))) - { - _newTypes.Add(type); - } + _newTypes.Add(type); var objectCommandType = typeof(ObjectCommand<>).MakeGenericType(type); From 23e605c2c5f5ca5f703e4d9b010f8811b3908e36 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Tue, 7 Oct 2025 05:03:47 +0100 Subject: [PATCH 06/35] A lot of changes --- .../FrooxEngineInit.cs | 9 +- InterprocessLib.Shared/Backend.cs | 142 +++++++++------- InterprocessLib.Shared/Commands.cs | 35 +++- InterprocessLib.Shared/Messenger.cs | 160 ++++++++++-------- InterprocessLib.Unity/UnityInit.cs | 10 +- .../BepisLoaderTests.cs | 20 ++- .../Program.cs | 9 +- 7 files changed, 223 insertions(+), 162 deletions(-) diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index 67107c2..c1ad74e 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -12,10 +12,10 @@ private static void CommandHandler(RendererCommand command, int messageSize) } public static void Init() { - if (Messenger.DefaultBackendInitStarted) + if (Messenger.DefaultInitStarted) throw new InvalidOperationException("Messenger default backend initialization has already been started!"); - Messenger.DefaultBackendInitStarted = true; + Messenger.DefaultInitStarted = true; Task.Run(InitLoop); } @@ -49,8 +49,9 @@ private static async void InitLoop() }; #endif - var host = new MessagingBackend(true, renderSystemMessagingHost!.QueueName + "InterprocessLib", renderSystemMessagingHost.QueueCapacity, renderSystemMessagingHost, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); - Messenger.SetDefaultBackend(host); + var host = new MessagingSystem(true, renderSystemMessagingHost!.QueueName + "InterprocessLib", renderSystemMessagingHost.QueueCapacity, renderSystemMessagingHost, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + Messenger.SetDefaultSystem(host); + host.Connect(); // The authority process automatically initializes when it receives a MessengerReadyCommand from the non-authority process } } diff --git a/InterprocessLib.Shared/Backend.cs b/InterprocessLib.Shared/Backend.cs index 6a2524a..89f61f1 100644 --- a/InterprocessLib.Shared/Backend.cs +++ b/InterprocessLib.Shared/Backend.cs @@ -4,7 +4,7 @@ namespace InterprocessLib; -public class MessagingBackend +internal class MessagingSystem { private struct OwnerData { @@ -35,43 +35,59 @@ public OwnerData() private MessagingManager _primary; - private static MethodInfo? _handleValueCommandMethod = typeof(MessagingBackend).GetMethod(nameof(HandleValueCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static MethodInfo? _handleValueCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueCommand), BindingFlags.Instance | BindingFlags.NonPublic); - private static MethodInfo? _handleValueCollectionCommandMethod = typeof(MessagingBackend).GetMethod(nameof(HandleValueCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static MethodInfo? _handleValueCollectionCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic); - private static MethodInfo? _handleObjectCommandMethod = typeof(MessagingBackend).GetMethod(nameof(HandleObjectCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static MethodInfo? _handleObjectCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectCommand), BindingFlags.Instance | BindingFlags.NonPublic); - private static MethodInfo? _handleObjectListCommandMethod = typeof(MessagingBackend).GetMethod(nameof(HandleObjectListCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static MethodInfo? _handleObjectListCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectListCommand), BindingFlags.Instance | BindingFlags.NonPublic); - //private RenderCommandHandler? OnCommandReceived { get; } + private RenderCommandHandler? _onCommandReceived { get; } - private Action? OnWarning { get; } + private Action? _onWarning { get; } - private Action? OnDebug { get; } + private Action? _onDebug { get; } - private Action? OnFailure { get; } + private Action? _onFailure { get; } private Dictionary _ownerData = new(); private Action? _postInitCallback; - public bool IsAlive { get; private set; } + public bool IsConnected { get; private set; } public bool IsInitialized => _postInitActions is null; - internal List? _postInitActions = new(); + private List? _postInitActions = new(); internal TypeManager TypeManager; - public void Initialize() + private static Dictionary _backends = new(); + + internal IMemoryPackerEntityPool Pool { get; private set; } + + internal void SetPostInitActions(List? actions) + { + _postInitActions = actions; + } + + public void Connect() + { + _primary.Connect(QueueName, IsAuthority, QueueCapacity); + IsConnected = true; + + if (!IsAuthority) + Initialize(); + } + + private void Initialize() { if (IsInitialized) throw new InvalidOperationException("Already initialized!"); if (!IsAuthority) - { - SendCommand(new MessengerReadyCommand()); - } + SendPackable(new MessengerReadyCommand()); var actions = _postInitActions!.ToArray(); _postInitActions = null; @@ -83,7 +99,7 @@ public void Initialize() } catch (Exception ex) { - OnWarning?.Invoke($"Exception running post-init action:\n{ex}"); + _onWarning?.Invoke($"Exception running post-init action:\n{ex}"); } } @@ -147,40 +163,42 @@ public void RegisterEmptyCallback(string owner, string id, Action callback) _ownerData[owner].ObjectListCallbacks[id] = callback; } - //static MessagingBackend() - //{ - // TypeManager.InitializeCoreTypes(); - //} - - public MessagingBackend(bool isAuthority, string queueName, long queueCapacity, IMemoryPackerEntityPool pool, RenderCommandHandler? commandHandler = null, Action? failhandler = null, Action? warnHandler = null, Action? debugHandler = null, Action? postInitCallback = null) + 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) { IsAuthority = isAuthority; QueueName = queueName; QueueCapacity = queueCapacity; - OnDebug = debugHandler; - OnWarning = warnHandler; - OnFailure = failhandler; - //OnCommandReceived = commandHandler; + _onDebug = debugHandler; + _onWarning = warnHandler; + _onFailure = failhandler; + _onCommandReceived = commandHandler; _postInitCallback = postInitCallback; TypeManager = new(QueueName); + Pool = pool; + _primary = new MessagingManager(pool); _primary.CommandHandler = CommandHandler; _primary.FailureHandler = (ex) => { - IsAlive = false; - OnFailure?.Invoke(ex); + IsConnected = false; + _onFailure?.Invoke(ex); }; _primary.WarningHandler = (msg) => { - OnWarning?.Invoke(msg); + _onWarning?.Invoke(msg); }; - _primary.Connect(queueName, isAuthority, queueCapacity); - IsAlive = true; + _backends.Add(QueueName, this); + } + + internal static MessagingSystem? TryGet(string queueName) + { + if (_backends.TryGetValue(queueName, out var backend)) return backend; + return null; } private void HandleValueCommand(ValueCommand command) where T : unmanaged @@ -194,7 +212,7 @@ private void HandleValueCommand(ValueCommand command) where T : unmanaged } else { - OnWarning?.Invoke($"ValueCommand<{typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + _onWarning?.Invoke($"ValueCommand<{typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); } } @@ -209,7 +227,7 @@ private void HandleValueCommand(ValueCommand command) where T : unmanaged } else { - OnWarning?.Invoke($"ValueCollectionCommand<{typeof(C).Name}, {typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + _onWarning?.Invoke($"ValueCollectionCommand<{typeof(C).Name}, {typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); } } @@ -224,7 +242,7 @@ private void HandleStringCommand(StringCommand command) } else { - OnWarning?.Invoke($"StringCommand with Id \"{command.Id}\" is not registered to receive a callback!"); + _onWarning?.Invoke($"StringCommand with Id \"{command.Id}\" is not registered to receive a callback!"); } } @@ -239,7 +257,7 @@ private void HandleStringListCommand(StringListCommand command) } else { - OnWarning?.Invoke($"StringListCommand with Id \"{command.Id}\" is not registered to receive a callback!"); + _onWarning?.Invoke($"StringListCommand with Id \"{command.Id}\" is not registered to receive a callback!"); } } @@ -254,7 +272,7 @@ private void HandleEmptyCommand(IdentifiableCommand command) } else { - OnWarning?.Invoke($"EmptyCommand with Id \"{command.Id}\" is not registered to receive a callback!"); + _onWarning?.Invoke($"EmptyCommand with Id \"{command.Id}\" is not registered to receive a callback!"); } } @@ -269,7 +287,7 @@ private void HandleEmptyCommand(IdentifiableCommand command) } else { - OnWarning?.Invoke($"ObjectCommand<{command.ObjectType.Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + _onWarning?.Invoke($"ObjectCommand<{command.ObjectType.Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); } } @@ -284,40 +302,43 @@ private void HandleEmptyCommand(IdentifiableCommand command) } else { - OnWarning?.Invoke($"ObjectListCommand<{typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + _onWarning?.Invoke($"ObjectListCommand<{typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); } } - private void CommandHandler(RendererCommand wrappedCommand, int messageSize) + private void CommandHandler(RendererCommand command, int messageSize) { - var command = ((WrapperCommand)wrappedCommand).PackedObject; + _onCommandReceived?.Invoke(command, messageSize); - OnDebug?.Invoke($"Received {command?.ToString() ?? command?.GetType().Name ?? "NULL"}"); + IMemoryPackable? packable = null; + if (command is WrapperCommand wrapperCommand) + { + packable = wrapperCommand.Packable; + _onDebug?.Invoke($"Received {packable?.ToString() ?? packable?.GetType().Name ?? "NULL"}"); + } - if (!IsInitialized && command is MessengerReadyCommand) + if (!IsInitialized && packable is MessengerReadyCommand) { Initialize(); return; } - //OnCommandReceived?.Invoke(command, messageSize); - - if (command is IdentifiableCommand identifiableCommand) + if (packable is IdentifiableCommand identifiableCommand) { if (!_ownerData.TryGetValue(identifiableCommand.Owner, out var data)) { - OnWarning?.Invoke($"Owner \"{identifiableCommand.Owner}\" is not registered!"); + _onWarning?.Invoke($"Owner \"{identifiableCommand.Owner}\" is not registered!"); return; } } - if (command is ValueCommand valueCommand) + if (packable is ValueCommand valueCommand) { var valueType = valueCommand.ValueType; var typedMethod = _handleValueCommandMethod!.MakeGenericMethod(valueType); - typedMethod.Invoke(this, [command]); + typedMethod.Invoke(this, [packable]); } - else if (command is CollectionCommand collectionCommand) + else if (packable is CollectionCommand collectionCommand) { var innerDataType = collectionCommand.InnerDataType; if (innerDataType == typeof(string)) @@ -328,46 +349,43 @@ private void CommandHandler(RendererCommand wrappedCommand, int messageSize) { var collectionType = collectionCommand.CollectionType; var typedMethod = _handleValueCollectionCommandMethod!.MakeGenericMethod(collectionType, innerDataType); - typedMethod.Invoke(this, [command]); + typedMethod.Invoke(this, [packable]); } else { var typedMethod = _handleObjectListCommandMethod!.MakeGenericMethod(innerDataType); - typedMethod.Invoke(this, [command]); + typedMethod.Invoke(this, [packable]); } } - else if (command is ObjectCommand objectCommand) + else if (packable is ObjectCommand objectCommand) { var objectType = objectCommand.ObjectType; var typedMethod = _handleObjectCommandMethod!.MakeGenericMethod(objectType); - typedMethod.Invoke(this, [command]); + typedMethod.Invoke(this, [packable]); } else { - switch (command) + switch (packable) { case StringCommand: - HandleStringCommand((StringCommand)command); + HandleStringCommand((StringCommand)packable); break; case IdentifiableCommand: - HandleEmptyCommand((IdentifiableCommand)command); + HandleEmptyCommand((IdentifiableCommand)packable); break; - //case IdentifiableCommand unknownCommand: - // OnWarning?.Invoke($"Received unrecognized IdentifiableCommand of type {command.GetType().Name}: {unknownCommand.Owner}:{unknownCommand.Id}"); - // break; default: break; } } } - public void SendCommand(IMemoryPackable command) + public void SendPackable(IMemoryPackable? packable) { - OnDebug?.Invoke($"Sending {command}"); + _onDebug?.Invoke($"Sending packable: {packable?.ToString() ?? packable?.GetType().Name ?? "NULL"}"); var wrapper = new WrapperCommand(); wrapper.QueueName = QueueName; - wrapper.PackedObject = command; + wrapper.Packable = packable; _primary.SendCommand(wrapper); } diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 68a9e97..9a1ec14 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -222,11 +222,14 @@ public override string ToString() } } -internal class WrapperCommand : RendererCommand +internal sealed class WrapperCommand : RendererCommand { public int TypeIndex; public string? QueueName; - public IMemoryPackable? PackedObject; + public IMemoryPackable? Packable; + + static MethodInfo? _borrowMethod = typeof(WrapperCommand).GetMethod("Borrow", BindingFlags.Static | BindingFlags.NonPublic); + static MethodInfo? _returnMethod = typeof(WrapperCommand).GetMethod("Return", BindingFlags.Static | BindingFlags.NonPublic); public static void InitNewTypes(List types) { @@ -235,7 +238,9 @@ public static void InitNewTypes(List types) public override void Pack(ref MemoryPacker packer) { - var type = PackedObject is null ? -1 : TypeManager.GetTypeManager(QueueName!).GetTypeIndex(PackedObject.GetType()); + var packedType = Packable?.GetType(); + var backend = MessagingSystem.TryGet(QueueName!); + var type = Packable is null ? -1 : backend!.TypeManager.GetTypeIndex(packedType!); packer.Write(type); if (type == -1) @@ -245,7 +250,9 @@ public override void Pack(ref MemoryPacker packer) packer.Write(QueueName); #pragma warning restore - PackedObject!.Pack(ref packer); + Packable!.Pack(ref packer); + + _returnMethod!.MakeGenericMethod(packedType!).Invoke(null, [backend!.Pool, Packable]); } public override void Unpack(ref MemoryUnpacker unpacker) @@ -254,7 +261,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) if (TypeIndex == -1) { - PackedObject = null; + Packable = null; return; } @@ -262,9 +269,21 @@ public override void Unpack(ref MemoryUnpacker unpacker) unpacker.Read(ref QueueName); #pragma warning restore - var type = TypeManager.GetTypeManager(QueueName).GetTypeFromIndex(TypeIndex); + var backend = MessagingSystem.TryGet(QueueName); + var type = backend!.TypeManager.GetTypeFromIndex(TypeIndex); + + Packable = (IMemoryPackable?)_borrowMethod!.MakeGenericMethod(type).Invoke(null, [backend.Pool]); + + Packable!.Unpack(ref unpacker); + } - PackedObject = (IMemoryPackable?)Activator.CreateInstance(type); - PackedObject!.Unpack(ref unpacker); + private static IMemoryPackable? Borrow(IMemoryPackerEntityPool pool) where T : class, IMemoryPackable, new() + { + return pool.Borrow(); + } + + private static void Return(IMemoryPackerEntityPool pool, T obj) where T : class, IMemoryPackable, new() + { + pool.Return(obj); } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index dc7356c..e7a433c 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -7,28 +7,33 @@ namespace InterprocessLib; /// public class Messenger { - private static MessagingBackend? _defaultBackend; + private static MessagingSystem? _defaultSystem; - private MessagingBackend? _customBackend; + private MessagingSystem? _customSystem; - private MessagingBackend? Backend => _customBackend ?? _defaultBackend; + private MessagingSystem? CurrentSystem => _customSystem ?? _defaultSystem; + + /// + /// If this messenger has a underlying messaging system assigned to it, or has it not been created yet + /// + public bool HasSystem => CurrentSystem is not null; /// /// If true the messenger will send commands immediately, otherwise commands will wait in a queue until the non-authority process sends the . /// - public bool IsInitialized => Backend?.IsInitialized == true; + private bool IsInitialized => CurrentSystem?.IsInitialized ?? false; /// /// Does this process have authority over the other process. /// - public bool? IsAuthority => Backend?.IsAuthority; + public bool IsAuthority => CurrentSystem?.IsAuthority ?? false; /// /// Is the interprocess connection still available /// - public bool? IsAlive => Backend?.IsAlive; + public bool IsConnected => CurrentSystem?.IsConnected ?? false; - internal static bool DefaultBackendInitStarted = false; + internal static bool DefaultInitStarted = false; /// /// Called when the backend connection has a critical error @@ -47,7 +52,7 @@ public class Messenger public static Action? OnDebug; #pragma warning restore - internal static List? _defaultBackendPostInitActions = new(); + internal static List? _defaultPostInitActions = new(); private string _ownerId; @@ -76,9 +81,9 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< _additionalValueTypes = additionalValueTypes; - if (_defaultBackend is null) + if (_defaultSystem is null) { - DefaultBackendRunPostInit(() => + DefaultRunPostInit(() => { Register(); }); @@ -88,7 +93,7 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< Register(); } - if (_defaultBackend is null && !DefaultBackendInitStarted) + if (_defaultSystem is null && !DefaultInitStarted) { var frooxEngineInitType = Type.GetType("InterprocessLib.FrooxEngineInit"); if (frooxEngineInitType is not null) @@ -110,30 +115,34 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< } } - internal static void SetDefaultBackend(MessagingBackend backend) + internal static void SetDefaultSystem(MessagingSystem system) { - _defaultBackend = backend; - _defaultBackend._postInitActions = _defaultBackendPostInitActions; - _defaultBackendPostInitActions = null; + _defaultSystem = system; + _defaultSystem.SetPostInitActions(_defaultPostInitActions); + _defaultPostInitActions = null; } /// - /// Creates an instance with a unique owner and a custom backend + /// Creates an instance with a unique owner and a custom backend that can connect to any process /// /// Unique identifier for this instance in this process. Should match the other process. /// /// Custom messaging backend. Allows connecting to any custom 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. /// - public Messenger(string ownerId, MessagingBackend customBackend, List? additionalObjectTypes = null, List? additionalValueTypes = null) + public Messenger(string ownerId, bool isAuthority, string queueName, long queueCapacity, IMemoryPackerEntityPool pool, List? additionalObjectTypes = null, List? additionalValueTypes = null) { if (ownerId is null) throw new ArgumentNullException(nameof(ownerId)); - if (customBackend is null) - throw new ArgumentNullException(nameof(customBackend)); - - _customBackend = customBackend; + if (MessagingSystem.TryGet(queueName) is not MessagingSystem existingSystem) + { + _customSystem = new MessagingSystem(isAuthority, queueName, queueCapacity, pool, null, OnFailure, OnWarning, OnDebug); + } + else + { + _customSystem = existingSystem; + } _ownerId = ownerId; @@ -142,40 +151,43 @@ public Messenger(string ownerId, MessagingBackend customBackend, List? add _additionalValueTypes = additionalValueTypes; Register(); + + if (!_customSystem!.IsConnected) + _customSystem.Connect(); } private void RunPostInit(Action act) { - if (Backend is null) - DefaultBackendRunPostInit(act); + if (CurrentSystem is null) + DefaultRunPostInit(act); else - Backend.RunPostInit(act); + CurrentSystem.RunPostInit(act); } private void Register() { - if (Backend!.HasOwner(_ownerId)) + if (CurrentSystem!.HasOwner(_ownerId)) { - OnWarning?.Invoke($"Owner {_ownerId} has already been registered in this process for messaging backend with queue name: {Backend.QueueName}"); + OnWarning?.Invoke($"Owner {_ownerId} has already been registered in this process for messaging backend with queue name: {CurrentSystem.QueueName}"); } else - Backend.RegisterOwner(_ownerId); + CurrentSystem.RegisterOwner(_ownerId); if (_additionalObjectTypes is not null) { - Backend.TypeManager.InitObjectTypeList(_additionalObjectTypes.Where(t => !Backend.TypeManager.IsObjectTypeInitialized(t)).ToList()); + CurrentSystem.TypeManager.InitObjectTypeList(_additionalObjectTypes.Where(t => !CurrentSystem.TypeManager.IsObjectTypeInitialized(t)).ToList()); } if (_additionalValueTypes is not null) { - Backend.TypeManager.InitValueTypeList(_additionalValueTypes.Where(t => !Backend.TypeManager.IsValueTypeInitialized(t)).ToList()); + CurrentSystem.TypeManager.InitValueTypeList(_additionalValueTypes.Where(t => !CurrentSystem.TypeManager.IsValueTypeInitialized(t)).ToList()); } } - private static void DefaultBackendRunPostInit(Action act) + private static void DefaultRunPostInit(Action act) { - if (_defaultBackend?.IsInitialized != true) + if (_defaultSystem is null) { - _defaultBackendPostInitActions!.Add(act); + _defaultPostInitActions!.Add(act); } else throw new InvalidOperationException("Default host already initialized!"); @@ -186,20 +198,20 @@ public void SendValue(string id, T value) where T : unmanaged if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => SendValue(id, value)); return; } - if (!Backend!.TypeManager.IsValueTypeInitialized()) + if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {value.GetType().Name} needs to be registered first!"); var command = new ValueCommand(); command.Owner = _ownerId; command.Id = id; command.Value = value; - Backend!.SendCommand(command); + CurrentSystem!.SendPackable(command); } public void SendValueList(string id, List list) where T : unmanaged @@ -207,20 +219,20 @@ public void SendValueList(string id, List list) where T : unmanaged if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => SendValueList(id, list)); return; } - if (!Backend!.TypeManager.IsValueTypeInitialized()) + if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); var command = new ValueCollectionCommand, T>(); command.Owner = _ownerId; command.Id = id; command.Values = list; - Backend!.SendCommand(command); + CurrentSystem!.SendPackable(command); } public void SendValueHashSet(string id, HashSet hashSet) where T : unmanaged @@ -228,20 +240,20 @@ public void SendValueHashSet(string id, HashSet hashSet) where T : unmanag if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => SendValueHashSet(id, hashSet)); return; } - if (!Backend!.TypeManager.IsValueTypeInitialized()) + if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); var command = new ValueCollectionCommand, T>(); command.Owner = _ownerId; command.Id = id; command.Values = hashSet; - Backend!.SendCommand(command); + CurrentSystem!.SendPackable(command); } public void SendString(string id, string str) @@ -249,7 +261,7 @@ public void SendString(string id, string str) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => SendString(id, str)); return; @@ -259,7 +271,7 @@ public void SendString(string id, string str) command.Owner = _ownerId; command.Id = id; command.String = str; - Backend!.SendCommand(command); + CurrentSystem!.SendPackable(command); } public void SendStringList(string id, List list) @@ -267,7 +279,7 @@ public void SendStringList(string id, List list) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => SendStringList(id, list)); return; @@ -277,7 +289,7 @@ public void SendStringList(string id, List list) command.Owner = _ownerId; command.Id = id; command.Values = list; - Backend!.SendCommand(command); + CurrentSystem!.SendPackable(command); } public void SendEmptyCommand(string id) @@ -285,7 +297,7 @@ public void SendEmptyCommand(string id) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => SendEmptyCommand(id)); return; @@ -294,7 +306,7 @@ public void SendEmptyCommand(string id) var command = new IdentifiableCommand(); command.Owner = _ownerId; command.Id = id; - Backend!.SendCommand(command); + CurrentSystem!.SendPackable(command); } public void SendObject(string id, T? obj) where T : class, IMemoryPackable, new() @@ -302,13 +314,13 @@ public void SendEmptyCommand(string id) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => SendObject(id, obj)); return; } - if (!Backend!.TypeManager.IsObjectTypeInitialized()) + if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); var wrapper = new ObjectCommand(); @@ -316,7 +328,7 @@ public void SendEmptyCommand(string id) wrapper.Owner = _ownerId; wrapper.Id = id; - Backend!.SendCommand(wrapper); + CurrentSystem!.SendPackable(wrapper); } public void SendObjectList(string id, List list) where T : class, IMemoryPackable, new() @@ -324,20 +336,20 @@ public void SendEmptyCommand(string id) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => SendObjectList(id, list)); return; } - if (!Backend!.TypeManager.IsObjectTypeInitialized()) + if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); var command = new ObjectListCommand(); command.Owner = _ownerId; command.Id = id; command.Values = list; - Backend!.SendCommand(command); + CurrentSystem!.SendPackable(command); } public void ReceiveValue(string id, Action callback) where T : unmanaged @@ -345,16 +357,16 @@ public void ReceiveValue(string id, Action callback) where T : unmanaged if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => ReceiveValue(id, callback)); return; } - if (!Backend!.TypeManager.IsValueTypeInitialized()) + if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - Backend!.RegisterValueCallback(_ownerId, id, callback); + CurrentSystem!.RegisterValueCallback(_ownerId, id, callback); } public void ReceiveValueList(string id, Action> callback) where T : unmanaged @@ -362,16 +374,16 @@ public void ReceiveValueList(string id, Action> callback) where T : u if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => ReceiveValueList(id, callback)); return; } - if (!Backend!.TypeManager.IsValueTypeInitialized()) + if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - Backend!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); + CurrentSystem!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); } public void ReceiveValueHashSet(string id, Action> callback) where T : unmanaged @@ -379,16 +391,16 @@ public void ReceiveValueHashSet(string id, Action> callback) where if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => ReceiveValueHashSet(id, callback)); return; } - if (!Backend!.TypeManager.IsValueTypeInitialized()) + if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - Backend!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); + CurrentSystem!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); } public void ReceiveString(string id, Action callback) @@ -396,13 +408,13 @@ public void ReceiveString(string id, Action callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if(!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => ReceiveString(id, callback)); return; } - Backend!.RegisterStringCallback(_ownerId, id, callback); + CurrentSystem!.RegisterStringCallback(_ownerId, id, callback); } public void ReceiveStringList(string id, Action?>? callback) @@ -410,13 +422,13 @@ public void ReceiveStringList(string id, Action?>? callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => ReceiveStringList(id, callback)); return; } - Backend!.RegisterStringListCallback(_ownerId, id, callback); + CurrentSystem!.RegisterStringListCallback(_ownerId, id, callback); } public void ReceiveEmptyCommand(string id, Action callback) @@ -424,13 +436,13 @@ public void ReceiveEmptyCommand(string id, Action callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => ReceiveEmptyCommand(id, callback)); return; } - Backend!.RegisterEmptyCallback(_ownerId, id, callback); + CurrentSystem!.RegisterEmptyCallback(_ownerId, id, callback); } public void ReceiveObject(string id, Action callback) where T : class, IMemoryPackable, new() @@ -438,16 +450,16 @@ public void ReceiveEmptyCommand(string id, Action callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => ReceiveObject(id, callback)); return; } - if (!Backend!.TypeManager.IsObjectTypeInitialized()) + if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - Backend!.RegisterObjectCallback(_ownerId, id, callback); + CurrentSystem!.RegisterObjectCallback(_ownerId, id, callback); } public void ReceiveObjectList(string id, Action> callback) where T : class, IMemoryPackable, new() @@ -455,15 +467,15 @@ public void ReceiveEmptyCommand(string id, Action callback) if (id is null) throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) + if (IsInitialized != true) { RunPostInit(() => ReceiveObjectList(id, callback)); return; } - if (!Backend!.TypeManager.IsObjectTypeInitialized()) + if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - Backend!.RegisterObjectListCallback(_ownerId, id, callback); + CurrentSystem!.RegisterObjectListCallback(_ownerId, id, callback); } } \ No newline at end of file diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index 156324e..dc0f6d9 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -11,10 +11,10 @@ private static void CommandHandler(RendererCommand command, int messageSize) } public static void Init() { - if (Messenger.DefaultBackendInitStarted) + if (Messenger.DefaultInitStarted) throw new InvalidOperationException("Messenger default host initialization has already been started!"); - Messenger.DefaultBackendInitStarted = true; + Messenger.DefaultInitStarted = true; Task.Run(InitLoop); } @@ -51,9 +51,9 @@ private static async void InitLoop() }; #endif - var host = new MessagingBackend(false, (string)parameters[0] + "InterprocessLib", (long)parameters[1], PackerMemoryPool.Instance, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); - Messenger.SetDefaultBackend(host); - host.Initialize(); + var host = new MessagingSystem(false, (string)parameters[0] + "InterprocessLib", (long)parameters[1], PackerMemoryPool.Instance, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + Messenger.SetDefaultSystem(host); + host.Connect(); } } } \ No newline at end of file diff --git a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs index 368b34d..700aad5 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs +++ b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs @@ -38,7 +38,8 @@ public class Plugin : BasePlugin public static ConfigEntry? SyncTestOutput; public static ConfigEntry? ResetToggle; - private static MessagingBackend? _customBackend; + //private static MessagingBackend? _customBackend; + public static Messenger? _customMessenger; public static ConfigEntry? SpawnProcessToggle; private static Random _rand = new(); #pragma warning disable CS0169 @@ -69,15 +70,24 @@ private static void SpawnProcess() #if TEST_SPAWN_PROCESS _customQueueName ??= $"MyCustomQueue{_rand.Next()}"; Log!.LogInfo("Child process queue name: " + _customQueueName); - _customBackend ??= new MessagingBackend(true, _customQueueName, 1024 * 1024, new MyPool(), CommandHandler, FailHandler, WarnHandler, DebugHandler); - var customHostMessenger = new Messenger("InterprocessLib.Tests", _customBackend, [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); + //_customBackend ??= new MessagingBackend(true, _customQueueName, 1024 * 1024, new MyPool(), CommandHandler, FailHandler, WarnHandler, DebugHandler); + //_customBackend.Connect(); + _customMessenger ??= new Messenger("InterprocessLib.Tests", true, _customQueueName, 1024*1024, new MyPool(), [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); var process = new Process(); - process.StartInfo.FileName = @"S:\Projects\ResoniteModDev\_THUNDERSTORE\InterprocessLib\Tests\InterprocessLib.Standalone.Tests\bin\Release\net9.0\InterprocessLib.Standalone.Tests.exe"; + + string projectConfiguration; +#if DEBUG + projectConfiguration = "Debug"; +#else + projectConfiguration = "Release"; +#endif + + process.StartInfo.FileName = @$"S:\Projects\ResoniteModDev\_THUNDERSTORE\InterprocessLib\Tests\InterprocessLib.Standalone.Tests\bin\{projectConfiguration}\net9.0\InterprocessLib.Standalone.Tests.exe"; process.StartInfo.Arguments = _customQueueName; process.StartInfo.UseShellExecute = true; // Run in a new window process.StartInfo.WindowStyle = ProcessWindowStyle.Normal; process.Start(); - Tests.RunTests(customHostMessenger, Log!.LogInfo); + Tests.RunTests(_customMessenger, Log!.LogInfo); #endif } diff --git a/Tests/InterprocessLib.Standalone.Tests/Program.cs b/Tests/InterprocessLib.Standalone.Tests/Program.cs index 584d51b..1af9eb7 100644 --- a/Tests/InterprocessLib.Standalone.Tests/Program.cs +++ b/Tests/InterprocessLib.Standalone.Tests/Program.cs @@ -59,14 +59,15 @@ static void Main(string[] args) Messenger.OnFailure = FailHandler; Messenger.OnDebug = DebugHandler; - var customHost = new MessagingBackend(false, queueName!, 1024 * 1024, new MyPool(), CommandHandler, FailHandler, WarnHandler, DebugHandler); - customHost.Initialize(); + //var customHost = new MessagingBackend(false, queueName!, 1024 * 1024, new MyPool(), CommandHandler, FailHandler, WarnHandler, DebugHandler); + + var messenger = new Messenger("InterprocessLib.Tests", false, queueName!, 1024*1024, new MyPool(), [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); - var messenger = new Messenger("InterprocessLib.Tests", customHost, [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); + //customHost.Connect(); Tests.RunTests(messenger, Console.WriteLine); - Thread.Sleep(15000); + Thread.Sleep(30000); } } } From adf88d43c563f98cf303b69fe402c122bfae5c9e Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Wed, 8 Oct 2025 07:22:40 +0100 Subject: [PATCH 07/35] Move pool borrow and return to TypeManager --- InterprocessLib.Shared/Commands.cs | 20 ++----- .../{Backend.cs => System.cs} | 2 +- InterprocessLib.Shared/TypeManager.cs | 58 ++++++++++++++++--- 3 files changed, 55 insertions(+), 25 deletions(-) rename InterprocessLib.Shared/{Backend.cs => System.cs} (99%) diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 9a1ec14..7aa1bda 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -8,7 +8,7 @@ namespace InterprocessLib; // IMPORTANT: // RendererCommand derived classes MUST NOT have constructors because it breaks Unity for some reason -public class IdentifiableCommand : IMemoryPackable +internal class IdentifiableCommand : IMemoryPackable { internal string Owner = ""; public string Id = ""; @@ -228,8 +228,8 @@ internal sealed class WrapperCommand : RendererCommand public string? QueueName; public IMemoryPackable? Packable; - static MethodInfo? _borrowMethod = typeof(WrapperCommand).GetMethod("Borrow", BindingFlags.Static | BindingFlags.NonPublic); - static MethodInfo? _returnMethod = typeof(WrapperCommand).GetMethod("Return", BindingFlags.Static | BindingFlags.NonPublic); + //static MethodInfo? _borrowMethod = typeof(WrapperCommand).GetMethod("Borrow", BindingFlags.Static | BindingFlags.NonPublic); + //static MethodInfo? _returnMethod = typeof(WrapperCommand).GetMethod("Return", BindingFlags.Static | BindingFlags.NonPublic); public static void InitNewTypes(List types) { @@ -252,7 +252,7 @@ public override void Pack(ref MemoryPacker packer) Packable!.Pack(ref packer); - _returnMethod!.MakeGenericMethod(packedType!).Invoke(null, [backend!.Pool, Packable]); + backend!.TypeManager.Return(packedType!, Packable); } public override void Unpack(ref MemoryUnpacker unpacker) @@ -272,18 +272,8 @@ public override void Unpack(ref MemoryUnpacker unpacker) var backend = MessagingSystem.TryGet(QueueName); var type = backend!.TypeManager.GetTypeFromIndex(TypeIndex); - Packable = (IMemoryPackable?)_borrowMethod!.MakeGenericMethod(type).Invoke(null, [backend.Pool]); + Packable = backend.TypeManager.Borrow(type); Packable!.Unpack(ref unpacker); } - - private static IMemoryPackable? Borrow(IMemoryPackerEntityPool pool) where T : class, IMemoryPackable, new() - { - return pool.Borrow(); - } - - private static void Return(IMemoryPackerEntityPool pool, T obj) where T : class, IMemoryPackable, new() - { - pool.Return(obj); - } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Backend.cs b/InterprocessLib.Shared/System.cs similarity index 99% rename from InterprocessLib.Shared/Backend.cs rename to InterprocessLib.Shared/System.cs index 89f61f1..5fb148e 100644 --- a/InterprocessLib.Shared/Backend.cs +++ b/InterprocessLib.Shared/System.cs @@ -176,7 +176,7 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I _postInitCallback = postInitCallback; - TypeManager = new(QueueName); + TypeManager = new(QueueName, pool); Pool = pool; diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index ad8c76c..1062b7f 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -1,5 +1,4 @@ using Renderite.Shared; -using System.Collections.Generic; using System.Reflection; namespace InterprocessLib; @@ -22,6 +21,17 @@ internal class TypeManager private static Dictionary _typeManagers = new(); + private List> _borrowers = new(); + + private List> _returners = new(); + + private Dictionary _typeToIndex = new(); + + private IMemoryPackerEntityPool _pool; + + private static MethodInfo? _borrowMethod = typeof(TypeManager).GetMethod("Borrow", BindingFlags.Instance | BindingFlags.NonPublic, null, [], null); + private static MethodInfo? _returnMethod = typeof(TypeManager).GetMethod("Return", BindingFlags.Instance | BindingFlags.NonPublic, null, [typeof(IMemoryPackable)], null); + private static Type[] _valueTypes = { typeof(bool), @@ -55,9 +65,10 @@ static TypeManager() WrapperCommand.InitNewTypes(list); } - internal TypeManager(string queueName) + internal TypeManager(string queueName, IMemoryPackerEntityPool pool) { _typeManagers.Add(queueName, this); + _pool = pool; InitializeCoreTypes(); } @@ -97,7 +108,7 @@ internal Type GetTypeFromIndex(int index) internal int GetTypeIndex(Type type) { - return _newTypes.IndexOf(type); + return _typeToIndex[type]; } internal void InitValueTypeList(List types) @@ -154,9 +165,9 @@ private void RegisterAdditionalValueType() where T : unmanaged var valueHashSetCommandType = typeof(ValueCollectionCommand<,>).MakeGenericType(typeof(HashSet), type); - _newTypes.AddRange([valueCommandType, valueListCommandType, valueHashSetCommandType]); - _registeredValueTypes.Add(type); + + PushNewTypes([valueCommandType, valueListCommandType, valueHashSetCommandType]); } private void RegisterAdditionalObjectType() where T : class, IMemoryPackable, new() @@ -169,14 +180,43 @@ private void RegisterAdditionalValueType() where T : unmanaged if (type.ContainsGenericParameters) throw new ArgumentException($"Type must be a concrete type!"); - _newTypes.Add(type); - var objectCommandType = typeof(ObjectCommand<>).MakeGenericType(type); var objectListCommandType = typeof(ObjectListCommand<>).MakeGenericType(type); - _newTypes.AddRange([objectCommandType, objectListCommandType]); - _registeredObjectTypes.Add(type); + + PushNewTypes([type, objectCommandType, objectListCommandType]); + } + + private IMemoryPackable? Borrow() where T : class, IMemoryPackable, new() + { + return _pool.Borrow(); + } + + private void Return(IMemoryPackable obj) where T : class, IMemoryPackable, new() + { + _pool.Return((T)obj); + } + + internal IMemoryPackable Borrow(Type type) + { + return _borrowers[_typeToIndex[type]](); + } + + internal void Return(Type type, object obj) + { + _returners[_typeToIndex[type]]((IMemoryPackable)obj); + } + + private void PushNewTypes(List types) + { + foreach (var type in types) + { + _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; + } } } \ No newline at end of file From 46057e48ee4ea9398eea89264b076772a7f8e45d Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sat, 11 Oct 2025 00:21:15 +0100 Subject: [PATCH 08/35] Add default memory pools and other stuff --- .../FrooxEngineInit.cs | 17 +++- InterprocessLib.Shared/Commands.cs | 16 +++- InterprocessLib.Shared/Messenger.cs | 81 +++++++++++++++---- InterprocessLib.Shared/System.cs | 15 ++-- InterprocessLib.Shared/TypeManager.cs | 6 +- .../BepisLoaderTests.cs | 20 +---- .../Program.cs | 19 +---- 7 files changed, 111 insertions(+), 63 deletions(-) diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index c1ad74e..6a090ba 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -5,6 +5,21 @@ namespace InterprocessLib; +internal class FrooxEnginePool : IMemoryPackerEntityPool +{ + 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 { private static void CommandHandler(RendererCommand command, int messageSize) @@ -49,7 +64,7 @@ private static async void InitLoop() }; #endif - var host = new MessagingSystem(true, renderSystemMessagingHost!.QueueName + "InterprocessLib", renderSystemMessagingHost.QueueCapacity, renderSystemMessagingHost, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + var host = new MessagingSystem(true, renderSystemMessagingHost!.QueueName + "InterprocessLib", renderSystemMessagingHost.QueueCapacity, FrooxEnginePool.Instance, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); Messenger.SetDefaultSystem(host); host.Connect(); // The authority process automatically initializes when it receives a MessengerReadyCommand from the non-authority process diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 7aa1bda..a7c0182 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -8,7 +8,7 @@ namespace InterprocessLib; // IMPORTANT: // RendererCommand derived classes MUST NOT have constructors because it breaks Unity for some reason -internal class IdentifiableCommand : IMemoryPackable +internal abstract class IdentifiableCommand : IMemoryPackable { internal string Owner = ""; public string Id = ""; @@ -65,6 +65,16 @@ 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 { public C? Values; @@ -239,7 +249,7 @@ public static void InitNewTypes(List types) public override void Pack(ref MemoryPacker packer) { var packedType = Packable?.GetType(); - var backend = MessagingSystem.TryGet(QueueName!); + var backend = MessagingSystem.TryGetRegisteredSystem(QueueName!); var type = Packable is null ? -1 : backend!.TypeManager.GetTypeIndex(packedType!); packer.Write(type); @@ -269,7 +279,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) unpacker.Read(ref QueueName); #pragma warning restore - var backend = MessagingSystem.TryGet(QueueName); + var backend = MessagingSystem.TryGetRegisteredSystem(QueueName); var type = backend!.TypeManager.GetTypeFromIndex(TypeIndex); Packable = backend.TypeManager.Borrow(type); diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index e7a433c..5cf92a8 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -21,7 +21,7 @@ public class Messenger /// /// If true the messenger will send commands immediately, otherwise commands will wait in a queue until the non-authority process sends the . /// - private bool IsInitialized => CurrentSystem?.IsInitialized ?? false; + public bool IsInitialized => CurrentSystem?.IsInitialized ?? false; /// /// Does this process have authority over the other process. @@ -52,7 +52,9 @@ public class Messenger public static Action? OnDebug; #pragma warning restore - internal static List? _defaultPostInitActions = new(); + private static List? _defaultPostInitActions = new(); + + private static List? _defaultPreInitActions = new(); private string _ownerId; @@ -83,7 +85,7 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< if (_defaultSystem is null) { - DefaultRunPostInit(() => + DefaultRunPreInit(() => { Register(); }); @@ -115,29 +117,47 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< } } - internal static void SetDefaultSystem(MessagingSystem system) - { - _defaultSystem = system; - _defaultSystem.SetPostInitActions(_defaultPostInitActions); - _defaultPostInitActions = null; - } - /// - /// Creates an instance with a unique owner and a custom backend that can connect to any process + /// 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. - /// /// Custom messaging backend. Allows connecting to any custom 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. /// 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. /// - public Messenger(string ownerId, bool isAuthority, string queueName, long queueCapacity, IMemoryPackerEntityPool pool, List? additionalObjectTypes = null, List? additionalValueTypes = null) + public Messenger(string ownerId, bool isAuthority, string queueName, IMemoryPackerEntityPool? pool = null, long queueCapacity = 1024*1024, List? additionalObjectTypes = null, List? additionalValueTypes = null) { if (ownerId is null) throw new ArgumentNullException(nameof(ownerId)); - if (MessagingSystem.TryGet(queueName) is not MessagingSystem existingSystem) + 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 + { + throw new EntryPointNotFoundException("Could not find default IMemoryPackerEntityPool!"); + } + } + } + + if (MessagingSystem.TryGetRegisteredSystem(queueName) is not MessagingSystem existingSystem) { - _customSystem = new MessagingSystem(isAuthority, queueName, queueCapacity, pool, null, OnFailure, OnWarning, OnDebug); + _customSystem = new MessagingSystem(isAuthority, queueName, queueCapacity, actualPool, null, OnFailure, OnWarning, OnDebug); } else { @@ -156,6 +176,25 @@ public Messenger(string ownerId, bool isAuthority, string queueName, long queueC _customSystem.Connect(); } + internal static void SetDefaultSystem(MessagingSystem system) + { + _defaultSystem = system; + _defaultSystem.SetPostInitActions(_defaultPostInitActions); + _defaultPostInitActions = null; + foreach (var act in _defaultPreInitActions!) + { + try + { + act(); + } + catch (Exception ex) + { + OnWarning?.Invoke($"Exception running pre-init action:\n{ex}"); + } + } + _defaultPreInitActions = null; + } + private void RunPostInit(Action act) { if (CurrentSystem is null) @@ -183,6 +222,16 @@ private void Register() } } + 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) @@ -303,7 +352,7 @@ public void SendEmptyCommand(string id) return; } - var command = new IdentifiableCommand(); + var command = new EmptyCommand(); command.Owner = _ownerId; command.Id = id; CurrentSystem!.SendPackable(command); diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 5fb148e..0c3fd3a 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -1,6 +1,5 @@ using Renderite.Shared; using System.Reflection; -using System.Runtime.CompilerServices; namespace InterprocessLib; @@ -74,6 +73,9 @@ internal void SetPostInitActions(List? actions) public void Connect() { + if (IsConnected) + throw new InvalidOperationException("Already connected!"); + _primary.Connect(QueueName, IsAuthority, QueueCapacity); IsConnected = true; @@ -195,7 +197,7 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I _backends.Add(QueueName, this); } - internal static MessagingSystem? TryGet(string queueName) + internal static MessagingSystem? TryGetRegisteredSystem(string queueName) { if (_backends.TryGetValue(queueName, out var backend)) return backend; return null; @@ -261,7 +263,7 @@ private void HandleStringListCommand(StringListCommand command) } } - private void HandleEmptyCommand(IdentifiableCommand command) + private void HandleEmptyCommand(EmptyCommand command) { if (_ownerData[command.Owner].EmptyCallbacks.TryGetValue(command.Id, out var callback)) { @@ -370,8 +372,11 @@ private void CommandHandler(RendererCommand command, int messageSize) case StringCommand: HandleStringCommand((StringCommand)packable); break; - case IdentifiableCommand: - HandleEmptyCommand((IdentifiableCommand)packable); + case EmptyCommand: + HandleEmptyCommand((EmptyCommand)packable); + break; + case IdentifiableCommand unknownCommand: + _onWarning?.Invoke($"Received unrecognized IdentifiableCommand of type {unknownCommand.GetType().Name}: {unknownCommand.Owner}:{unknownCommand.Id}"); break; default: break; diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 1062b7f..05ce4fa 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -82,7 +82,7 @@ internal void InitializeCoreTypes() if (_initializedCoreTypes) return; RegisterAdditionalObjectType(); - RegisterAdditionalObjectType(); + RegisterAdditionalObjectType(); RegisterAdditionalObjectType(); RegisterAdditionalObjectType(); @@ -204,9 +204,9 @@ internal IMemoryPackable Borrow(Type type) return _borrowers[_typeToIndex[type]](); } - internal void Return(Type type, object obj) + internal void Return(Type type, IMemoryPackable obj) { - _returners[_typeToIndex[type]]((IMemoryPackable)obj); + _returners[_typeToIndex[type]](obj); } private void PushNewTypes(List types) diff --git a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs index 700aad5..b7f9743 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs +++ b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs @@ -1,4 +1,4 @@ -#define TEST_SPAWN_PROCESS +//#define TEST_SPAWN_PROCESS using BepInEx; using BepInEx.Configuration; @@ -10,19 +10,6 @@ namespace InterprocessLib.Tests; -class MyPool : IMemoryPackerEntityPool -{ - T IMemoryPackerEntityPool.Borrow() - { - return Pool.Borrow(); - } - - void IMemoryPackerEntityPool.Return(T value) - { - Pool.ReturnCleaned(ref value); - } -} - [BepInExResoniteShim.ResonitePlugin(PluginMetadata.GUID, PluginMetadata.NAME, PluginMetadata.VERSION, PluginMetadata.AUTHORS, PluginMetadata.REPOSITORY_URL)] [BepInDependency(BepInExResoniteShim.PluginMetadata.GUID, BepInDependency.DependencyFlags.HardDependency)] public class Plugin : BasePlugin @@ -70,9 +57,7 @@ private static void SpawnProcess() #if TEST_SPAWN_PROCESS _customQueueName ??= $"MyCustomQueue{_rand.Next()}"; Log!.LogInfo("Child process queue name: " + _customQueueName); - //_customBackend ??= new MessagingBackend(true, _customQueueName, 1024 * 1024, new MyPool(), CommandHandler, FailHandler, WarnHandler, DebugHandler); - //_customBackend.Connect(); - _customMessenger ??= new Messenger("InterprocessLib.Tests", true, _customQueueName, 1024*1024, new MyPool(), [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); + _customMessenger ??= new Messenger("InterprocessLib.Tests", true, _customQueueName, additionalObjectTypes: [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], additionalValueTypes: [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); var process = new Process(); string projectConfiguration; @@ -117,6 +102,7 @@ public override void Load() Tests.RunTests(_messenger, Log!.LogInfo); Tests.RunTests(_unknownMessenger, Log!.LogInfo); Tests.RunTests(_another, Log!.LogInfo); + SpawnProcess(); SyncTest = Config.Bind("General", "SyncTest", 34); diff --git a/Tests/InterprocessLib.Standalone.Tests/Program.cs b/Tests/InterprocessLib.Standalone.Tests/Program.cs index 1af9eb7..8d7492b 100644 --- a/Tests/InterprocessLib.Standalone.Tests/Program.cs +++ b/Tests/InterprocessLib.Standalone.Tests/Program.cs @@ -5,19 +5,6 @@ namespace InterprocessLibStandaloneTest { - class MyPool : IMemoryPackerEntityPool - { - T IMemoryPackerEntityPool.Borrow() - { - return Pool.Borrow(); - } - - void IMemoryPackerEntityPool.Return(T value) - { - Pool.ReturnCleaned(ref value); - } - } - internal class Program { private static void CommandHandler(RendererCommand command, int messageSize) @@ -58,12 +45,8 @@ static void Main(string[] args) Messenger.OnWarning = WarnHandler; Messenger.OnFailure = FailHandler; Messenger.OnDebug = DebugHandler; - - //var customHost = new MessagingBackend(false, queueName!, 1024 * 1024, new MyPool(), CommandHandler, FailHandler, WarnHandler, DebugHandler); - var messenger = new Messenger("InterprocessLib.Tests", false, queueName!, 1024*1024, new MyPool(), [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); - - //customHost.Connect(); + var messenger = new Messenger("InterprocessLib.Tests", false, queueName!, additionalObjectTypes: [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], additionalValueTypes: [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); Tests.RunTests(messenger, Console.WriteLine); From dd368409008113eb73d7ddaf9bb97a1cf18eb16d Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Tue, 28 Oct 2025 20:22:58 +0000 Subject: [PATCH 09/35] Copy lib DLLs to the correct folder and fix Thunderstore package folder structure --- InterprocessLib.Shared/System.cs | 3 ++- .../InterprocessLib.BepInEx.Tests.csproj | 9 +++++---- .../InterprocessLib.BepisLoader.Tests.csproj | 9 +++++---- thunderstore.toml | 4 ++-- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 0c3fd3a..7ca366b 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -17,6 +17,7 @@ private struct OwnerData public readonly Dictionary ValueCollectionCallbacks = new(); + // not string?, because FrooxEngine just takes string public readonly Dictionary?>?> StringListCallbacks = new(); public readonly Dictionary ObjectListCallbacks = new(); @@ -60,7 +61,7 @@ public OwnerData() private List? _postInitActions = new(); - internal TypeManager TypeManager; + internal TypeManager TypeManager; private static Dictionary _backends = new(); diff --git a/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj b/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj index d893738..bf545cf 100644 --- a/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj +++ b/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj @@ -20,6 +20,7 @@ $(HOME)/.steam/steam/steamapps/common/Resonite/ G:\SteamLibrary\steamapps\common\Resonite\ $(GamePath)Renderer\BepInEx\plugins\$(AssemblyName) + $(GamePath)Renderer\BepInEx\plugins\Nytra-InterprocessLib\plugins\InterprocessLib.BepInEx Debug;Release;Tests @@ -61,10 +62,10 @@ - - + + - - + + diff --git a/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj b/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj index 809703a..acf88f2 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj +++ b/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj @@ -20,6 +20,7 @@ $(HOME)/.steam/steam/steamapps/common/Resonite/ G:\SteamLibrary\steamapps\common\Resonite\ $(GamePath)BepInEx\plugins\$(AssemblyName) + $(GamePath)BepInEx\plugins\Nytra-InterprocessLib\InterprocessLib.BepisLoader https://nuget-modding.resonite.net/v3/index.json; @@ -73,10 +74,10 @@ - - + + - - + + diff --git a/thunderstore.toml b/thunderstore.toml index 0a53761..8f5f480 100644 --- a/thunderstore.toml +++ b/thunderstore.toml @@ -33,11 +33,11 @@ target = "plugins/InterprocessLib.BepisLoader/" [[build.copy]] source = "./out/InterprocessLib.Unity.dll" -target = "Renderer/plugins/InterprocessLib.BepInEx/" +target = "Renderer/BepInEx/plugins/InterprocessLib.BepInEx/" [[build.copy]] source = "./out/InterprocessLib.BepInEx_Extensions.dll" -target = "Renderer/plugins/InterprocessLib.BepInEx/" +target = "Renderer/BepInEx/plugins/InterprocessLib.BepInEx/" # Uncomment the following lines to include CHANGELOG file in your mod package. https://wiki.thunderstore.io/mods/updating-a-package#changelog [[build.copy]] From ece1ea6c1f3ad93a2e354d6675ed0945ccfad295 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Tue, 4 Nov 2025 03:10:47 +0000 Subject: [PATCH 10/35] Push latest --- .../InterprocessLib.FrooxEngine.csproj | 2 +- InterprocessLib.Shared/Commands.cs | 138 +++++++++++++----- InterprocessLib.Shared/Messenger.cs | 13 +- InterprocessLib.Shared/System.cs | 109 ++++++++------ .../InterprocessLib.Unity.csproj | 6 +- thunderstore.toml | 2 +- 6 files changed, 183 insertions(+), 87 deletions(-) diff --git a/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj b/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj index f7be331..f3f4cd8 100644 --- a/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj +++ b/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj @@ -1,7 +1,7 @@ - 2.0.1 + 3.0.0 Nytra net9.0 13 diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index a7c0182..004b63d 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -1,5 +1,7 @@ using Renderite.Shared; +using System; using System.Collections; +using System.Diagnostics.Eventing.Reader; using System.Linq.Expressions; using System.Reflection; @@ -10,6 +12,7 @@ namespace InterprocessLib; internal abstract class IdentifiableCommand : IMemoryPackable { + internal MessagingSystem? System; internal string Owner = ""; public string Id = ""; @@ -86,17 +89,55 @@ public override string ToString() public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); -#pragma warning disable CS8604 - packer.WriteValueList(Values); -#pragma warning restore + packer.WriteValueList(Values!); } public override void Unpack(ref MemoryUnpacker unpacker) { base.Unpack(ref unpacker); -#pragma warning disable CS8601 - unpacker.ReadValueList(ref Values); -#pragma warning restore + unpacker.ReadValueList(ref Values!); + } +} + +internal sealed class ValueArrayCommand : CollectionCommand where T : unmanaged +{ + public T[]? Values; + + public override IEnumerable? UntypedCollection => Values; + public override Type InnerDataType => typeof(T); + public override Type CollectionType => typeof(T[]); + + public override void Pack(ref MemoryPacker packer) + { + base.Pack(ref packer); + if (Values is null) + { + packer.Write(-1); + return; + } + int len = Values.Length; + packer.Write(len); + Span data = packer.Access(len); + Values.CopyTo(data); + } + + public override void Unpack(ref MemoryUnpacker unpacker) + { + base.Unpack(ref unpacker); + int len = 0; + unpacker.Read(ref len); + if (len == -1) + { + Values = null; + return; + } + Values = new T[len]; // ToDo: use pool borrowing here? + for (int i = 0; i < len; i++) + { + T val = default; + unpacker.Read(ref val); + Values[i] = val; + } } } @@ -111,17 +152,13 @@ internal sealed class StringListCommand : CollectionCommand public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); -#pragma warning disable CS8604 - packer.WriteStringList(Values); -#pragma warning restore + packer.WriteStringList(Values!); } public override void Unpack(ref MemoryUnpacker unpacker) { base.Unpack(ref unpacker); -#pragma warning disable CS8601 - unpacker.ReadStringList(ref Values); -#pragma warning restore + unpacker.ReadStringList(ref Values!); } } @@ -136,17 +173,57 @@ public override void Unpack(ref MemoryUnpacker unpacker) public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); -#pragma warning disable CS8604 - packer.WriteObjectList(Values); -#pragma warning restore + packer.WriteObjectList(Values!); + } + + public override void Unpack(ref MemoryUnpacker unpacker) + { + base.Unpack(ref unpacker); + unpacker.ReadObjectList(ref Values!); + } +} + +internal sealed class ObjectArrayCommand : CollectionCommand where T : class, IMemoryPackable, new() +{ + public T[]? Objects; + + public override IEnumerable? UntypedCollection => Objects; + public override Type InnerDataType => typeof(T); + public override Type CollectionType => typeof(T[]); + + public override void Pack(ref MemoryPacker packer) + { + base.Pack(ref packer); + if (Objects is null) + { + packer.Write(-1); + return; + } + int len = Objects.Length; + packer.Write(len); + foreach (var obj in Objects) + { + obj.Pack(ref packer); + } } 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++) + { + T obj = (T)(System?.TypeManager.Borrow(typeof(T)) ?? new T()); + obj.Unpack(ref unpacker); + Objects[i] = obj; + } } } @@ -197,17 +274,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() @@ -238,9 +311,6 @@ internal sealed class WrapperCommand : RendererCommand public string? QueueName; public IMemoryPackable? Packable; - //static MethodInfo? _borrowMethod = typeof(WrapperCommand).GetMethod("Borrow", BindingFlags.Static | BindingFlags.NonPublic); - //static MethodInfo? _returnMethod = typeof(WrapperCommand).GetMethod("Return", BindingFlags.Static | BindingFlags.NonPublic); - public static void InitNewTypes(List types) { InitTypes(types); @@ -256,9 +326,10 @@ public override void Pack(ref MemoryPacker packer) if (type == -1) return; -#pragma warning disable CS8604 - packer.Write(QueueName); -#pragma warning restore + packer.Write(QueueName!); + + if (Packable is IdentifiableCommand identifiableCommand) + identifiableCommand.System = backend; Packable!.Pack(ref packer); @@ -275,15 +346,16 @@ public override void Unpack(ref MemoryUnpacker unpacker) return; } -#pragma warning disable CS8601 - unpacker.Read(ref QueueName); -#pragma warning restore + unpacker.Read(ref QueueName!); var backend = MessagingSystem.TryGetRegisteredSystem(QueueName); var type = backend!.TypeManager.GetTypeFromIndex(TypeIndex); Packable = backend.TypeManager.Borrow(type); + if (Packable is IdentifiableCommand identifiableCommand) + identifiableCommand.System = backend; + Packable!.Unpack(ref unpacker); } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index 5cf92a8..9c74ac4 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -16,22 +16,22 @@ public class Messenger /// /// If this messenger has a underlying messaging system assigned to it, or has it not been created yet /// - public bool HasSystem => CurrentSystem is not null; + //public bool HasSystem => CurrentSystem is not null; /// - /// If true the messenger will send commands immediately, otherwise commands will wait in a queue until the non-authority process sends the . + /// 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; /// /// Does this process have authority over the other process. /// - public bool IsAuthority => CurrentSystem?.IsAuthority ?? false; + public bool? IsAuthority => CurrentSystem?.IsAuthority; /// - /// Is the interprocess connection still available + /// Is the interprocess connection available, this will be false if there has been a fatal error in the interprocess queue /// - public bool IsConnected => CurrentSystem?.IsConnected ?? false; + public bool? IsConnected => CurrentSystem?.IsConnected; internal static bool DefaultInitStarted = false; @@ -48,9 +48,7 @@ public class Messenger /// /// Called with additional debugging information /// -#pragma warning disable CS0649 public static Action? OnDebug; -#pragma warning restore private static List? _defaultPostInitActions = new(); @@ -128,6 +126,7 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< /// 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. /// + /// public Messenger(string ownerId, bool isAuthority, string queueName, IMemoryPackerEntityPool? pool = null, long queueCapacity = 1024*1024, List? additionalObjectTypes = null, List? additionalValueTypes = null) { if (ownerId is null) diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 7ca366b..4020e7a 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -17,7 +17,7 @@ private struct OwnerData public readonly Dictionary ValueCollectionCallbacks = new(); - // not string?, because FrooxEngine just takes string + // not List, because FrooxEngine just takes List public readonly Dictionary?>?> StringListCallbacks = new(); public readonly Dictionary ObjectListCallbacks = new(); @@ -65,10 +65,13 @@ public OwnerData() private static Dictionary _backends = new(); - internal IMemoryPackerEntityPool Pool { get; private set; } + private IMemoryPackerEntityPool _pool; internal void SetPostInitActions(List? actions) { + if (IsInitialized) + throw new InvalidOperationException("Already initialized!"); + _postInitActions = actions; } @@ -94,6 +97,7 @@ private void Initialize() var actions = _postInitActions!.ToArray(); _postInitActions = null; + foreach (var action in actions) { try @@ -181,7 +185,7 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I TypeManager = new(QueueName, pool); - Pool = pool; + _pool = pool; _primary = new MessagingManager(pool); _primary.CommandHandler = CommandHandler; @@ -319,69 +323,86 @@ private void CommandHandler(RendererCommand command, int messageSize) packable = wrapperCommand.Packable; _onDebug?.Invoke($"Received {packable?.ToString() ?? packable?.GetType().Name ?? "NULL"}"); } - - if (!IsInitialized && packable is MessengerReadyCommand) + else { - Initialize(); + _onWarning?.Invoke($"Received an unexpected RendererCommand type! {command?.ToString() ?? command?.GetType().Name ?? "NULL"}"); return; } - if (packable is IdentifiableCommand identifiableCommand) + if (!IsInitialized) { - if (!_ownerData.TryGetValue(identifiableCommand.Owner, out var data)) + if (packable is MessengerReadyCommand) { - _onWarning?.Invoke($"Owner \"{identifiableCommand.Owner}\" is not registered!"); + Initialize(); return; } + else + { + throw new InvalidDataException($"The first command needs to be the MessengerReadyCommand when not initialized!"); + } } - if (packable is ValueCommand valueCommand) - { - var valueType = valueCommand.ValueType; - var typedMethod = _handleValueCommandMethod!.MakeGenericMethod(valueType); - typedMethod.Invoke(this, [packable]); - } - else if (packable is CollectionCommand collectionCommand) + if (packable is IdentifiableCommand identifiableCommand) { - var innerDataType = collectionCommand.InnerDataType; - if (innerDataType == typeof(string)) + if (!_ownerData.TryGetValue(identifiableCommand.Owner, out var data)) { - HandleStringListCommand((StringListCommand)collectionCommand); + _onWarning?.Invoke($"Owner \"{identifiableCommand.Owner}\" is not registered!"); + return; } - else if (innerDataType.IsValueType) + if (packable is ValueCommand valueCommand) { - var collectionType = collectionCommand.CollectionType; - var typedMethod = _handleValueCollectionCommandMethod!.MakeGenericMethod(collectionType, innerDataType); + var valueType = valueCommand.ValueType; + var typedMethod = _handleValueCommandMethod!.MakeGenericMethod(valueType); typedMethod.Invoke(this, [packable]); } - else + else if (packable 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, [packable]); + } + else + { + var typedMethod = _handleObjectListCommandMethod!.MakeGenericMethod(innerDataType); + typedMethod.Invoke(this, [packable]); + } + } + else if (packable is ObjectCommand objectCommand) { - var typedMethod = _handleObjectListCommandMethod!.MakeGenericMethod(innerDataType); + var objectType = objectCommand.ObjectType; + var typedMethod = _handleObjectCommandMethod!.MakeGenericMethod(objectType); typedMethod.Invoke(this, [packable]); } - } - else if (packable is ObjectCommand objectCommand) - { - var objectType = objectCommand.ObjectType; - var typedMethod = _handleObjectCommandMethod!.MakeGenericMethod(objectType); - typedMethod.Invoke(this, [packable]); + else + { + switch (packable) + { + case StringCommand: + HandleStringCommand((StringCommand)packable); + break; + case EmptyCommand: + HandleEmptyCommand((EmptyCommand)packable); + break; + case IdentifiableCommand unknownCommand: + _onWarning?.Invoke($"Received unrecognized IdentifiableCommand of type {unknownCommand.GetType().Name}: {unknownCommand.Owner}:{unknownCommand.Id}"); + break; + default: + break; + } + } } else { - switch (packable) - { - case StringCommand: - HandleStringCommand((StringCommand)packable); - break; - case EmptyCommand: - HandleEmptyCommand((EmptyCommand)packable); - break; - case IdentifiableCommand unknownCommand: - _onWarning?.Invoke($"Received unrecognized IdentifiableCommand of type {unknownCommand.GetType().Name}: {unknownCommand.Owner}:{unknownCommand.Id}"); - break; - default: - break; - } + // 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 } } diff --git a/InterprocessLib.Unity/InterprocessLib.Unity.csproj b/InterprocessLib.Unity/InterprocessLib.Unity.csproj index 3bdb858..641d876 100644 --- a/InterprocessLib.Unity/InterprocessLib.Unity.csproj +++ b/InterprocessLib.Unity/InterprocessLib.Unity.csproj @@ -1,7 +1,7 @@ - 2.0.1 + 3.0.0 Nytra net472 12 @@ -36,6 +36,10 @@ + + + + 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 From 9f82b06330ef4e16c2257b0e6985a277bf07e1f6 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:30:52 +0000 Subject: [PATCH 11/35] Fix lib target dir for BepInEx tests --- .../InterprocessLib.BepInEx.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj b/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj index bf545cf..c00a16a 100644 --- a/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj +++ b/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj @@ -20,7 +20,7 @@ $(HOME)/.steam/steam/steamapps/common/Resonite/ G:\SteamLibrary\steamapps\common\Resonite\ $(GamePath)Renderer\BepInEx\plugins\$(AssemblyName) - $(GamePath)Renderer\BepInEx\plugins\Nytra-InterprocessLib\plugins\InterprocessLib.BepInEx + $(GamePath)Renderer\BepInEx\plugins\Nytra-InterprocessLib\InterprocessLib.BepInEx Debug;Release;Tests From c64dfae8ebd77f22191a2d82cba3bbbb9e79a97b Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:45:50 +0000 Subject: [PATCH 12/35] Pushing latest, untested --- .../FrooxEngineInit.cs | 1 + InterprocessLib.Shared/Commands.cs | 35 +++++++++++---- InterprocessLib.Shared/Messenger.cs | 2 +- InterprocessLib.Shared/System.cs | 44 ++++++++++++++++--- InterprocessLib.Shared/Tests.cs | 6 +++ InterprocessLib.Unity/UnityInit.cs | 1 + 6 files changed, 75 insertions(+), 14 deletions(-) diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index 6a090ba..f758fea 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -66,6 +66,7 @@ private static async void InitLoop() var host = new MessagingSystem(true, renderSystemMessagingHost!.QueueName + "InterprocessLib", renderSystemMessagingHost.QueueCapacity, FrooxEnginePool.Instance, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); Messenger.SetDefaultSystem(host); + Engine.Current.OnShutdown += host.Dispose; host.Connect(); // The authority process automatically initializes when it receives a MessengerReadyCommand from the non-authority process } diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 004b63d..8ca52b0 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -78,7 +78,22 @@ public override string ToString() } } -internal sealed class ValueCollectionCommand : CollectionCommand where C : ICollection, new() where T : unmanaged +internal sealed class EnumerableCommand : CollectionCommand +{ + public IEnumerable? Enumerable; + + public override IEnumerable? UntypedCollection => Enumerable; + + public override Type InnerDataType => Enumerable!.GetEnumerator().Current.GetType(); + + public override Type CollectionType => Enumerable!.GetType(); +} + +internal abstract class ValueCollectionCommand : CollectionCommand +{ +} + +internal sealed class ValueCollectionCommand : ValueCollectionCommand where C : ICollection, new() where T : unmanaged { public C? Values; @@ -99,7 +114,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) } } -internal sealed class ValueArrayCommand : CollectionCommand where T : unmanaged +internal sealed class ValueArrayCommand : ValueCollectionCommand where T : unmanaged { public T[]? Values; @@ -162,28 +177,32 @@ public override void Unpack(ref MemoryUnpacker unpacker) } } -internal sealed class ObjectListCommand : CollectionCommand where T : class, IMemoryPackable, new() +internal abstract class ObjectCollectionCommand : CollectionCommand { - public List? Values; +} - public override IEnumerable? UntypedCollection => Values; +internal sealed class ObjectListCommand : ObjectCollectionCommand where T : class, IMemoryPackable, new() +{ + public List? Objects; + + public override IEnumerable? UntypedCollection => Objects; public override Type InnerDataType => typeof(T); public override Type CollectionType => typeof(List); public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); - packer.WriteObjectList(Values!); + packer.WriteObjectList(Objects!); } public override void Unpack(ref MemoryUnpacker unpacker) { base.Unpack(ref unpacker); - unpacker.ReadObjectList(ref Values!); + unpacker.ReadObjectList(ref Objects!); } } -internal sealed class ObjectArrayCommand : CollectionCommand where T : class, IMemoryPackable, new() +internal sealed class ObjectArrayCommand : ObjectCollectionCommand where T : class, IMemoryPackable, new() { public T[]? Objects; diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index 9c74ac4..fb9f584 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -396,7 +396,7 @@ public void SendEmptyCommand(string id) var command = new ObjectListCommand(); command.Owner = _ownerId; command.Id = id; - command.Values = list; + command.Objects = list; CurrentSystem!.SendPackable(command); } diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 4020e7a..8c0b3b5 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -3,7 +3,7 @@ namespace InterprocessLib; -internal class MessagingSystem +internal class MessagingSystem : IDisposable { private struct OwnerData { @@ -20,7 +20,11 @@ private struct OwnerData // not List, because FrooxEngine just takes List public readonly Dictionary?>?> StringListCallbacks = new(); - public readonly Dictionary ObjectListCallbacks = new(); + //public readonly Dictionary ObjectListCallbacks = new(); + + //public readonly Dictionary ObjectArrayCallbacks = new(); + + public readonly Dictionary ObjectCollectionCallbacks = new(); public OwnerData() { @@ -145,6 +149,11 @@ public void RegisterValueCallback(string owner, string id, Action callback _ownerData[owner].ValueCollectionCallbacks[id] = callback; } + public void RegisterValueArrayCallback(string owner, string id, Action callback) where T : unmanaged + { + _ownerData[owner].ValueCollectionCallbacks[id] = callback; + } + public void RegisterStringCallback(string owner, string id, Action callback) { _ownerData[owner].StringCallbacks[id] = callback; @@ -167,7 +176,12 @@ public void RegisterEmptyCallback(string owner, string id, Action callback) public void RegisterObjectListCallback(string owner, string id, Action> callback) where T : class, IMemoryPackable, new() { - _ownerData[owner].ObjectListCallbacks[id] = callback; + _ownerData[owner].ObjectCollectionCallbacks[id] = callback; + } + + public void RegisterObjectArrayCallback(string owner, string id, Action callback) 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) @@ -202,6 +216,11 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I _backends.Add(QueueName, this); } + public void Dispose() + { + _primary.Dispose(); + } + internal static MessagingSystem? TryGetRegisteredSystem(string queueName) { if (_backends.TryGetValue(queueName, out var backend)) return backend; @@ -300,11 +319,11 @@ private void HandleEmptyCommand(EmptyCommand command) private void HandleObjectListCommand(ObjectListCommand command) where T : class, IMemoryPackable, new() { - if (_ownerData[command.Owner].ObjectListCallbacks.TryGetValue(command.Id, out var callback)) + if (_ownerData[command.Owner].ObjectCollectionCallbacks.TryGetValue(command.Id, out var callback)) { if (callback != null) { - ((Action?>)callback).Invoke(command.Values); + ((Action?>)callback).Invoke(command.Objects); } } else @@ -313,6 +332,21 @@ private void HandleEmptyCommand(EmptyCommand command) } } + private void HandleObjectArrayCommand(ObjectArrayCommand command) 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($"ObjectArrayCommand<{typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + } + } + private void CommandHandler(RendererCommand command, int messageSize) { _onCommandReceived?.Invoke(command, messageSize); diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index 82fccf2..7952cee 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -75,6 +75,12 @@ public static void RunTests(Messenger messenger, Action logCallback) } } + static void TestValueArray() + { + //var test = new ValueCollectionCommand(); + //_messenger.ReceiveValueList<> + } + static void TestVanillaStruct() { _messenger!.ReceiveValue("TestVanillaStruct", (val) => diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index dc0f6d9..e39808e 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -1,6 +1,7 @@ using Renderite.Shared; using Renderite.Unity; using System.Reflection; +using UnityEngine; namespace InterprocessLib; From 67978746097814c12dca7b739a246d3f70447cbe Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:16:21 +0000 Subject: [PATCH 13/35] Push latest stuff --- InterprocessLib.Shared/Commands.cs | 322 ++++++++++++++++++++++---- InterprocessLib.Shared/Messenger.cs | 199 +++++++++++++++- InterprocessLib.Shared/System.cs | 111 +++++++-- InterprocessLib.Shared/Tests.cs | 77 +++++- InterprocessLib.Shared/TypeManager.cs | 13 +- 5 files changed, 637 insertions(+), 85 deletions(-) diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 8ca52b0..2743a23 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -1,9 +1,11 @@ using Renderite.Shared; using System; using System.Collections; +using System.Collections.ObjectModel; using System.Diagnostics.Eventing.Reader; using System.Linq.Expressions; using System.Reflection; +using System.Runtime.CompilerServices; namespace InterprocessLib; @@ -12,7 +14,6 @@ namespace InterprocessLib; internal abstract class IdentifiableCommand : IMemoryPackable { - internal MessagingSystem? System; internal string Owner = ""; public string Id = ""; @@ -78,62 +79,70 @@ public override string ToString() } } -internal sealed class EnumerableCommand : CollectionCommand -{ - public IEnumerable? Enumerable; - - public override IEnumerable? UntypedCollection => Enumerable; - - public override Type InnerDataType => Enumerable!.GetEnumerator().Current.GetType(); - - public override Type CollectionType => Enumerable!.GetType(); -} - -internal abstract class ValueCollectionCommand : CollectionCommand -{ -} - -internal sealed class ValueCollectionCommand : ValueCollectionCommand 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 CollectionType => typeof(C); public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); - packer.WriteValueList(Values!); + 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); - unpacker.ReadValueList(ref Values!); + 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 ValueArrayCommand : ValueCollectionCommand where T : unmanaged +internal sealed class ValueArrayCommand : CollectionCommand where T : unmanaged { public T[]? Values; public override IEnumerable? UntypedCollection => Values; + public override Type InnerDataType => typeof(T); + public override Type CollectionType => typeof(T[]); public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); - if (Values is null) + var len = Values?.Length ?? -1; + packer.Write(len); + if (Values != null) { - packer.Write(-1); - return; + Span data = packer.Access(len); + Values.CopyTo(data); } - int len = Values.Length; - packer.Write(len); - Span data = packer.Access(len); - Values.CopyTo(data); } public override void Unpack(ref MemoryUnpacker unpacker) @@ -147,15 +156,215 @@ public override void Unpack(ref MemoryUnpacker unpacker) return; } Values = new T[len]; // ToDo: use pool borrowing here? - for (int i = 0; i < len; i++) + ReadOnlySpan data = unpacker.Access(len); + data.CopyTo(Values); + + //for (int i = 0; i < len; i++) + //{ + // T val = default; + // unpacker.Read(ref val); + // Values[i] = val; + //} + } +} + +//internal sealed class ValueDictionaryCommand : CollectionCommand where TKey : unmanaged where TValue : unmanaged +//{ +// public Dictionary? Dict; + +// public override IEnumerable? UntypedCollection => Dict; + +// public override Type InnerDataType => typeof(KeyValuePair); + +// public override Type CollectionType => typeof(Dictionary); + +// public override void Pack(ref MemoryPacker packer) +// { +// base.Pack(ref packer); +// var len = Dict?.Count ?? -1; +// packer.Write(len); +// if (Dict != null) +// { +// foreach (var kvp in Dict) +// { +// packer.Write(kvp.Key); +// packer.Write(kvp.Value); +// } +// } +// } + +// public override void Unpack(ref MemoryUnpacker unpacker) +// { +// base.Unpack(ref unpacker); +// int len = 0; +// unpacker.Read(ref len); +// if (len == -1) +// { +// Dict = null; +// return; +// } +// Dict = new(); // ToDo: use pool borrowing here? +// for (int i = 0; i < len; i++) +// { +// TKey key = default; +// unpacker.Read(ref key); +// TValue val = default; +// unpacker.Read(ref val); +// Dict[key] = val; +// } +// } +//} + +internal sealed class TypeCommand : IMemoryPackable +{ + public Type? Type; + private static Dictionary _typeCache = new(); + + 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!.IsGenericType) { - T val = default; - unpacker.Read(ref val); - Values[i] = val; + 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 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) + { + if (_typeCache.TryGetValue(typeString, out var type)) + { + return type; + } + + Messenger.OnDebug?.Invoke($"Looking for Type: {typeString}"); + 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: {type.FullName}"); + _typeCache[typeString] = type; + } + else + { + Messenger.OnDebug?.Invoke($"Could not find type."); + } + return type; + } + + public override string ToString() + { + return "TypeCommand: " + Type?.FullName ?? "NULL"; + } } +//internal sealed class ObjectDictionaryCommand : CollectionCommand where TKey : class, IMemoryPackable, new() where TValue : class, IMemoryPackable, new() +//{ +// public Dictionary? Dict; + +// public override IEnumerable? UntypedCollection => Dict; + +// public override Type InnerDataType => typeof(KeyValuePair); + +// public override Type CollectionType => typeof(Dictionary); + +// public override void Pack(ref MemoryPacker packer) +// { +// base.Pack(ref packer); +// var len = Dict?.Count ?? -1; +// packer.Write(len); +// if (Dict != null) +// { +// foreach (var kvp in Dict) +// { +// packer.WriteObject(kvp.Key); +// packer.WriteObject(kvp.Value); +// } +// } +// } + +// public override void Unpack(ref MemoryUnpacker unpacker) +// { +// base.Unpack(ref unpacker); +// int len = 0; +// unpacker.Read(ref len); +// if (len == -1) +// { +// Dict = null; +// return; +// } +// Dict = new(); // ToDo: use pool borrowing here? +// for (int i = 0; i < len; i++) +// { +// TKey key = default!; +// unpacker.ReadObject(ref key!); +// TValue val = default!; +// unpacker.ReadObject(ref val!); +// Dict[key] = val; +// } +// } +//} + internal sealed class StringListCommand : CollectionCommand { public List? Values; @@ -177,32 +386,51 @@ public override void Unpack(ref MemoryUnpacker unpacker) } } -internal abstract class ObjectCollectionCommand : CollectionCommand +internal sealed class ObjectCollectionCommand : CollectionCommand where C : ICollection, new() where T : class, IMemoryPackable, new() { -} - -internal sealed class ObjectListCommand : ObjectCollectionCommand where T : class, IMemoryPackable, new() -{ - public List? Objects; + public C? Objects; public override IEnumerable? UntypedCollection => Objects; public override Type InnerDataType => typeof(T); - public override Type CollectionType => typeof(List); + public override Type CollectionType => typeof(C); public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); - packer.WriteObjectList(Objects!); + if (Objects is null) + { + packer.Write(-1); + return; + } + int len = Objects.Count; + packer.Write(len); + foreach (var obj in Objects) + { + packer.WriteObject(obj); + } } public override void Unpack(ref MemoryUnpacker unpacker) { base.Unpack(ref unpacker); - unpacker.ReadObjectList(ref Objects!); + 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 = default!; + unpacker.ReadObject(ref obj!); + Objects.Add(obj); + } } } -internal sealed class ObjectArrayCommand : ObjectCollectionCommand where T : class, IMemoryPackable, new() +internal sealed class ObjectArrayCommand : CollectionCommand where T : class, IMemoryPackable, new() { public T[]? Objects; @@ -222,7 +450,7 @@ public override void Pack(ref MemoryPacker packer) packer.Write(len); foreach (var obj in Objects) { - obj.Pack(ref packer); + packer.WriteObject(obj); } } @@ -239,9 +467,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) Objects = new T[len]; // ToDo: use pool borrowing here? for (int i = 0; i < len; i++) { - T obj = (T)(System?.TypeManager.Borrow(typeof(T)) ?? new T()); - obj.Unpack(ref unpacker); - Objects[i] = obj; + unpacker.ReadObject(ref Objects[i]!); } } } @@ -347,9 +573,6 @@ public override void Pack(ref MemoryPacker packer) packer.Write(QueueName!); - if (Packable is IdentifiableCommand identifiableCommand) - identifiableCommand.System = backend; - Packable!.Pack(ref packer); backend!.TypeManager.Return(packedType!, Packable); @@ -372,9 +595,6 @@ public override void Unpack(ref MemoryUnpacker unpacker) Packable = backend.TypeManager.Borrow(type); - if (Packable is IdentifiableCommand identifiableCommand) - identifiableCommand.System = backend; - Packable!.Unpack(ref unpacker); } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index fb9f584..4a929fa 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -29,9 +29,9 @@ public class Messenger public bool? IsAuthority => CurrentSystem?.IsAuthority; /// - /// Is the interprocess connection available, this will be false if there has been a fatal error in the interprocess queue + /// Is the interprocess connection available? this might be false before initialization or if there has been a fatal error in the interprocess queue /// - public bool? IsConnected => CurrentSystem?.IsConnected; + public bool IsConnected => CurrentSystem?.IsConnected ?? false; internal static bool DefaultInitStarted = false; @@ -304,6 +304,27 @@ public void SendValueHashSet(string id, HashSet hashSet) where T : unmanag CurrentSystem!.SendPackable(command); } + public void SendValueArray(string id, T[] array) where T : unmanaged + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (IsInitialized != true) + { + RunPostInit(() => SendValueArray(id, array)); + return; + } + + if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) + throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + + var command = new ValueArrayCommand(); + command.Owner = _ownerId; + command.Id = id; + command.Values = array; + CurrentSystem!.SendPackable(command); + } + public void SendString(string id, string str) { if (id is null) @@ -393,13 +414,76 @@ public void SendEmptyCommand(string id) if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - var command = new ObjectListCommand(); + var command = new ObjectCollectionCommand, T>(); command.Owner = _ownerId; command.Id = id; command.Objects = list; CurrentSystem!.SendPackable(command); } + public void SendObjectHashSet(string id, HashSet hashSet) where T : class, IMemoryPackable, new() + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (IsInitialized != true) + { + RunPostInit(() => SendObjectHashSet(id, hashSet)); + return; + } + + if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) + throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + + var command = new ObjectCollectionCommand, T>(); + command.Owner = _ownerId; + command.Id = id; + command.Objects = hashSet; + CurrentSystem!.SendPackable(command); + } + + public void SendObjectArray(string id, T[] array) where T : class, IMemoryPackable, new() + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (IsInitialized != true) + { + RunPostInit(() => SendObjectArray(id, array)); + return; + } + + if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) + throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + + var command = new ObjectArrayCommand(); + command.Owner = _ownerId; + command.Id = id; + command.Objects = array; + CurrentSystem!.SendPackable(command); + } + + //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 != true) + // { + // RunPostInit(() => SendObjectCollection(id, collection)); + // return; + // } + + // if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) + // throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + + // var command = new ObjectCollectionCommand(); + // command.Owner = _ownerId; + // command.Id = id; + // command.Objects = collection; + // CurrentSystem!.SendPackable(command); + //} + public void ReceiveValue(string id, Action callback) where T : unmanaged { if (id is null) @@ -451,6 +535,41 @@ public void ReceiveValueHashSet(string id, Action> callback) where CurrentSystem!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); } + public void ReceiveValueArray(string id, Action callback) where T : unmanaged + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (IsInitialized != true) + { + RunPostInit(() => ReceiveValueArray(id, callback)); + return; + } + + if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) + throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + + CurrentSystem!.RegisterValueArrayCallback(_ownerId, id, callback); + } + + // This won't work because we can't possibly register every type of collection ahead of time + //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 != true) + // { + // RunPostInit(() => ReceiveValueCollection(id, callback)); + // return; + // } + + // if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) + // throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + + // CurrentSystem!.RegisterValueCollectionCallback(_ownerId, id, callback); + //} + public void ReceiveString(string id, Action callback) { if (id is null) @@ -524,6 +643,78 @@ public void ReceiveEmptyCommand(string id, Action callback) if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - CurrentSystem!.RegisterObjectListCallback(_ownerId, id, callback); + CurrentSystem!.RegisterObjectCollectionCallback, T>(_ownerId, id, callback); + } + + public void ReceiveObjectHashSet(string id, Action> callback) where T : class, IMemoryPackable, new() + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (IsInitialized != true) + { + RunPostInit(() => ReceiveObjectHashSet(id, callback)); + return; + } + + if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) + throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + + CurrentSystem!.RegisterObjectCollectionCallback, T>(_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 != true) + { + RunPostInit(() => ReceiveObjectArray(id, callback)); + return; + } + + if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) + throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + + CurrentSystem!.RegisterObjectArrayCallback(_ownerId, id, callback); + } + +#if DEBUG + public void SendTypeCommand(Type type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + if (IsInitialized != true) + { + RunPostInit(() => SendTypeCommand(type)); + return; + } + + var typeCommand = new TypeCommand(); + typeCommand.Type = type; + + Messenger.OnDebug?.Invoke($"Sending new type to register: {type.FullName}"); + CurrentSystem!.SendPackable(typeCommand); + } +#endif + + // This won't work because we can't possibly register every type of collection ahead of time + //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 != true) + // { + // RunPostInit(() => ReceiveObjectCollection(id, callback)); + // return; + // } + + // if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) + // throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + + // CurrentSystem!.RegisterObjectCollectionCallback(_ownerId, id, callback); + //} } \ No newline at end of file diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 8c0b3b5..785a0c9 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -15,14 +15,14 @@ private struct OwnerData public readonly Dictionary ObjectCallbacks = new(); + public readonly Dictionary ValueArrayCallbacks = new(); + public readonly Dictionary ValueCollectionCallbacks = new(); // not List, because FrooxEngine just takes List public readonly Dictionary?>?> StringListCallbacks = new(); - //public readonly Dictionary ObjectListCallbacks = new(); - - //public readonly Dictionary ObjectArrayCallbacks = new(); + public readonly Dictionary ObjectArrayCallbacks = new(); public readonly Dictionary ObjectCollectionCallbacks = new(); @@ -37,15 +37,19 @@ public OwnerData() public long QueueCapacity { get; } - private MessagingManager _primary; + private MessagingManager? _primary; private static MethodInfo? _handleValueCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueCommand), BindingFlags.Instance | BindingFlags.NonPublic); private static MethodInfo? _handleValueCollectionCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static MethodInfo? _handleValueArrayCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueArrayCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static MethodInfo? _handleObjectCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectCommand), BindingFlags.Instance | BindingFlags.NonPublic); - private static MethodInfo? _handleObjectListCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectListCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static MethodInfo? _handleObjectCollectionCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic); + + private static MethodInfo? _handleObjectArrayCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectArrayCommand), BindingFlags.Instance | BindingFlags.NonPublic); private RenderCommandHandler? _onCommandReceived { get; } @@ -84,7 +88,7 @@ public void Connect() if (IsConnected) throw new InvalidOperationException("Already connected!"); - _primary.Connect(QueueName, IsAuthority, QueueCapacity); + _primary!.Connect(QueueName, IsAuthority, QueueCapacity); IsConnected = true; if (!IsAuthority) @@ -151,7 +155,7 @@ public void RegisterValueCallback(string owner, string id, Action callback public void RegisterValueArrayCallback(string owner, string id, Action callback) where T : unmanaged { - _ownerData[owner].ValueCollectionCallbacks[id] = callback; + _ownerData[owner].ValueArrayCallbacks[id] = callback; } public void RegisterStringCallback(string owner, string id, Action callback) @@ -174,12 +178,17 @@ public void RegisterEmptyCallback(string owner, string id, Action callback) _ownerData[owner].ObjectCallbacks[id] = callback; } - public void RegisterObjectListCallback(string owner, string id, Action> callback) where T : class, IMemoryPackable, new() + //public void RegisterObjectListCallback(string owner, string id, Action> callback) where T : class, IMemoryPackable, new() + //{ + // _ownerData[owner].ObjectCollectionCallbacks[id] = callback; + //} + + public void RegisterObjectArrayCallback(string owner, string id, Action callback) where T : class, IMemoryPackable, new() { - _ownerData[owner].ObjectCollectionCallbacks[id] = callback; + _ownerData[owner].ObjectArrayCallbacks[id] = callback; } - public void RegisterObjectArrayCallback(string owner, string id, Action callback) where T : class, IMemoryPackable, new() + public void RegisterObjectCollectionCallback(string owner, string id, Action callback) where C : ICollection, new() where T : class, IMemoryPackable, new() { _ownerData[owner].ObjectCollectionCallbacks[id] = callback; } @@ -205,7 +214,7 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I _primary.CommandHandler = CommandHandler; _primary.FailureHandler = (ex) => { - IsConnected = false; + Dispose(); _onFailure?.Invoke(ex); }; _primary.WarningHandler = (msg) => @@ -218,7 +227,9 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I public void Dispose() { - _primary.Dispose(); + _primary?.Dispose(); + _primary = null!; + IsConnected = false; } internal static MessagingSystem? TryGetRegisteredSystem(string queueName) @@ -257,6 +268,21 @@ private void HandleValueCommand(ValueCommand command) where T : unmanaged } } + 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)) @@ -317,33 +343,33 @@ private void HandleEmptyCommand(EmptyCommand command) } } - private void HandleObjectListCommand(ObjectListCommand command) where T : class, IMemoryPackable, new() + private void HandleObjectArrayCommand(ObjectArrayCommand command) where T : class, IMemoryPackable, new() { - if (_ownerData[command.Owner].ObjectCollectionCallbacks.TryGetValue(command.Id, out var callback)) + if (_ownerData[command.Owner].ObjectArrayCallbacks.TryGetValue(command.Id, out var callback)) { if (callback != null) { - ((Action?>)callback).Invoke(command.Objects); + ((Action)callback).Invoke(command.Objects); } } else { - _onWarning?.Invoke($"ObjectListCommand<{typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + _onWarning?.Invoke($"ObjectArrayCommand<{typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); } } - private void HandleObjectArrayCommand(ObjectArrayCommand command) where T : class, IMemoryPackable, new() + 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); + ((Action)callback).Invoke(command.Objects); } } else { - _onWarning?.Invoke($"ObjectArrayCommand<{typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + _onWarning?.Invoke($"ObjectCollectionCommand<{typeof(C).Name}, {typeof(T).Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); } } @@ -376,6 +402,23 @@ private void CommandHandler(RendererCommand command, int messageSize) } } + if (packable is TypeCommand typeCommand) + { + _onDebug?.Invoke($"Received new type to register: {typeCommand.Type?.FullName ?? "NULL"}"); + //if (typeCommand.Type is not null) + //{ + // if (typeCommand.Type.IsValueType) + // { + // TypeManager.InitValueTypeList([typeCommand.Type]); + // } + // else + // { + // TypeManager.InitObjectTypeList([typeCommand.Type]); + // } + //} + return; + } + if (packable is IdentifiableCommand identifiableCommand) { if (!_ownerData.TryGetValue(identifiableCommand.Owner, out var data)) @@ -391,6 +434,7 @@ private void CommandHandler(RendererCommand command, int messageSize) } else if (packable is CollectionCommand collectionCommand) { + var collectionType = collectionCommand.CollectionType; var innerDataType = collectionCommand.InnerDataType; if (innerDataType == typeof(string)) { @@ -398,14 +442,29 @@ private void CommandHandler(RendererCommand command, int messageSize) } else if (innerDataType.IsValueType) { - var collectionType = collectionCommand.CollectionType; - var typedMethod = _handleValueCollectionCommandMethod!.MakeGenericMethod(collectionType, innerDataType); - typedMethod.Invoke(this, [packable]); + if (collectionType.IsArray) + { + var typedMethod = _handleValueArrayCommandMethod!.MakeGenericMethod(innerDataType); + typedMethod.Invoke(this, [packable]); + } + else + { + var typedMethod = _handleValueCollectionCommandMethod!.MakeGenericMethod(collectionType, innerDataType); + typedMethod.Invoke(this, [packable]); + } } else { - var typedMethod = _handleObjectListCommandMethod!.MakeGenericMethod(innerDataType); - typedMethod.Invoke(this, [packable]); + if (collectionType.IsArray) + { + var typedMethod = _handleObjectArrayCommandMethod!.MakeGenericMethod(innerDataType); + typedMethod.Invoke(this, [packable]); + } + else + { + var typedMethod = _handleObjectCollectionCommandMethod!.MakeGenericMethod(collectionType, innerDataType); + typedMethod.Invoke(this, [packable]); + } } } else if (packable is ObjectCommand objectCommand) @@ -442,12 +501,14 @@ private void CommandHandler(RendererCommand command, int messageSize) public void SendPackable(IMemoryPackable? packable) { + if (!IsConnected) return; + _onDebug?.Invoke($"Sending packable: {packable?.ToString() ?? packable?.GetType().Name ?? "NULL"}"); var wrapper = new WrapperCommand(); wrapper.QueueName = QueueName; wrapper.Packable = packable; - _primary.SendCommand(wrapper); + _primary!.SendCommand(wrapper); } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index 7952cee..242a346 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -1,6 +1,7 @@ //#define TEST_COMPILATION using Renderite.Shared; +using System.Collections.Generic; using System.Reflection; namespace InterprocessLib.Tests; @@ -32,6 +33,13 @@ public static void RunTests(Messenger messenger, Action logCallback) TestVanillaObject(); TestVanillaStruct(); TestVanillaEnum(); + TestValueArray(); + TestObjectArray(); + TestObjectHashSet(); + +#if DEBUG + TestTypeCommand(); +#endif try { @@ -75,10 +83,45 @@ public static void RunTests(Messenger messenger, Action logCallback) } } +#if DEBUG + static void TestTypeCommand() + { + _messenger!.SendTypeCommand(typeof(TestNewTypeToRegister)); + _messenger!.SendTypeCommand(typeof(ColorProfile)); + _messenger!.SendTypeCommand(typeof(ValueCollectionCommand, float>)); + } +#endif + static void TestValueArray() { - //var test = new ValueCollectionCommand(); - //_messenger.ReceiveValueList<> + var test = new ValueArrayCommand(); + _messenger!.ReceiveValueArray("TestValueArray", (arr) => + { + _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() + { + var test = new ObjectArrayCommand(); + _messenger!.ReceiveObjectArray("TestObjectArray", (arr) => + { + _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() @@ -308,6 +351,20 @@ static void TestObjectList() _messenger.SendObjectList("TestObjectList", list); } + static void TestObjectHashSet() + { + _messenger!.ReceiveObjectHashSet("TestObjectHashSet", (list) => + { + _logCallback!($"TestObjectHashSet: {string.Join(",", list!)}"); + }); + + var set = new HashSet(); + set.Add(new TestCommand()); + set.Add(null!); + set.Add(new TestCommand() { Value = 9 }); + _messenger.SendObjectHashSet("TestObjectHashSet", set); + } + static void TestStringList() { _messenger!.ReceiveStringList("TestStringList", (list) => @@ -397,6 +454,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 @@ -482,4 +544,15 @@ public override void Unpack(ref MemoryUnpacker unpacker) public struct UnregisteredStruct { public byte Value; +} + +public class TestNewTypeToRegister : IMemoryPackable +{ + public void Pack(ref MemoryPacker packer) + { + } + + public void Unpack(ref MemoryUnpacker unpacker) + { + } } \ No newline at end of file diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 05ce4fa..d7e94e2 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -85,6 +85,7 @@ internal void InitializeCoreTypes() RegisterAdditionalObjectType(); RegisterAdditionalObjectType(); RegisterAdditionalObjectType(); + RegisterAdditionalObjectType(); foreach (var valueType in TypeManager._valueTypes) { @@ -161,13 +162,15 @@ private void RegisterAdditionalValueType() where T : unmanaged var valueCommandType = typeof(ValueCommand<>).MakeGenericType(type); + var valueArrayCommandType = typeof(ValueArrayCommand<>).MakeGenericType(type); + var valueListCommandType = typeof(ValueCollectionCommand<,>).MakeGenericType(typeof(List), type); var valueHashSetCommandType = typeof(ValueCollectionCommand<,>).MakeGenericType(typeof(HashSet), type); _registeredValueTypes.Add(type); - PushNewTypes([valueCommandType, valueListCommandType, valueHashSetCommandType]); + PushNewTypes([valueCommandType, valueArrayCommandType, valueListCommandType, valueHashSetCommandType]); } private void RegisterAdditionalObjectType() where T : class, IMemoryPackable, new() @@ -182,11 +185,15 @@ private void RegisterAdditionalValueType() where T : unmanaged var objectCommandType = typeof(ObjectCommand<>).MakeGenericType(type); - var objectListCommandType = typeof(ObjectListCommand<>).MakeGenericType(type); + var objectArrayCommandType = typeof(ObjectArrayCommand<>).MakeGenericType(type); + + var objectListCommandType = typeof(ObjectCollectionCommand<,>).MakeGenericType(typeof(List), type); + + var objectHashSetCommandType = typeof(ObjectCollectionCommand<,>).MakeGenericType(typeof(HashSet), type); _registeredObjectTypes.Add(type); - PushNewTypes([type, objectCommandType, objectListCommandType]); + PushNewTypes([type, objectCommandType, objectArrayCommandType, objectListCommandType, objectHashSetCommandType]); } private IMemoryPackable? Borrow() where T : class, IMemoryPackable, new() From c130fe5eecf661d4d29c0e370a00a83896dc2e67 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sat, 8 Nov 2025 20:24:48 +0000 Subject: [PATCH 14/35] Improvements and add a way to get a fallback messaging system --- .../FrooxEngineInit.cs | 53 +++++----- InterprocessLib.Shared/Commands.cs | 39 ++++---- InterprocessLib.Shared/Messenger.cs | 97 +++++++++++++++++-- InterprocessLib.Shared/Pool.cs | 17 ++++ InterprocessLib.Shared/System.cs | 40 ++++++-- InterprocessLib.Shared/Tests.cs | 1 - InterprocessLib.Shared/TypeManager.cs | 24 ++--- InterprocessLib.Unity/UnityInit.cs | 65 +++++++------ .../BepisLoaderTests.cs | 42 +++++--- .../Program.cs | 2 +- 10 files changed, 255 insertions(+), 125 deletions(-) create mode 100644 InterprocessLib.Shared/Pool.cs diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index f758fea..bcd0c6c 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -22,9 +22,6 @@ void IMemoryPackerEntityPool.Return(T value) internal static class FrooxEngineInit { - private static void CommandHandler(RendererCommand command, int messageSize) - { - } public static void Init() { if (Messenger.DefaultInitStarted) @@ -36,27 +33,20 @@ public static void Init() } private static async void InitLoop() { - if (Engine.Current?.RenderSystem?.Engine is null) + // Engine.SharedMemoryPrefix is assigned just before the RenderSystem is created + while (Engine.Current?.RenderSystem is null) { await Task.Delay(1); - InitLoop(); } - else - { - await Task.Delay(100); - - 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!"); - Messenger.OnWarning = (msg) => - { - UniLog.Warning($"[InterprocessLib] [WARN] {msg}"); - }; - Messenger.OnFailure = (ex) => - { - UniLog.Error($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Backend!\n{ex}"); - }; + 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) => { @@ -64,11 +54,24 @@ private static async void InitLoop() }; #endif - var host = new MessagingSystem(true, renderSystemMessagingHost!.QueueName + "InterprocessLib", renderSystemMessagingHost.QueueCapacity, FrooxEnginePool.Instance, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); - Messenger.SetDefaultSystem(host); - Engine.Current.OnShutdown += host.Dispose; - host.Connect(); - // The authority process automatically initializes when it receives a MessengerReadyCommand from the non-authority process + MessagingSystem? system = null; + string uniqueId = Engine.Current.SharedMemoryPrefix; + + if (uniqueId is null) + { + system = await Messenger.GetFallbackSystem(true, MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + if (system is null) + { + throw new EntryPointNotFoundException("Unable to get fallback messaging system!"); + } } + else + { + system = new MessagingSystem(true, $"InterprocessLib-{uniqueId}", MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + } + + Messenger.SetDefaultSystem(system); + Engine.Current.OnShutdown += system.Dispose; // this might fix the rare occurence that Renderite.Host stays open after exiting Resonite + system.Connect(); } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 2743a23..63ac0f1 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -1,11 +1,5 @@ using Renderite.Shared; -using System; using System.Collections; -using System.Collections.ObjectModel; -using System.Diagnostics.Eventing.Reader; -using System.Linq.Expressions; -using System.Reflection; -using System.Runtime.CompilerServices; namespace InterprocessLib; @@ -38,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"}"; } } @@ -85,7 +79,7 @@ public override string ToString() public override IEnumerable? UntypedCollection => Values; - public override Type InnerDataType => typeof(T); + public override Type StoredType => typeof(T); public override Type CollectionType => typeof(C); @@ -129,7 +123,7 @@ internal sealed class ValueArrayCommand : CollectionCommand where T : unmanag public override IEnumerable? UntypedCollection => Values; - public override Type InnerDataType => typeof(T); + public override Type StoredType => typeof(T); public override Type CollectionType => typeof(T[]); @@ -370,7 +364,7 @@ internal sealed class StringListCommand : CollectionCommand public List? Values; public override IEnumerable? UntypedCollection => Values; - public override Type InnerDataType => typeof(string); + public override Type StoredType => typeof(string); public override Type CollectionType => typeof(List); public override void Pack(ref MemoryPacker packer) @@ -391,7 +385,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) public C? Objects; public override IEnumerable? UntypedCollection => Objects; - 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) @@ -435,7 +429,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) public T[]? Objects; public override IEnumerable? UntypedCollection => Objects; - public override Type InnerDataType => typeof(T); + public override Type StoredType => typeof(T); public override Type CollectionType => typeof(T[]); public override void Pack(ref MemoryPacker packer) @@ -534,19 +528,28 @@ public override string ToString() } } -internal sealed class MessengerReadyCommand : IdentifiableCommand +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) { } +} - public override string ToString() +internal sealed class PingCommand : IMemoryPackable +{ + public DateTime Time; + public void Pack(ref MemoryPacker packer) + { + packer.Write(Time); + } + + public void Unpack(ref MemoryUnpacker unpacker) { - return $"MessengerReadyCommand"; + unpacker.Read(ref Time); } } diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index 4a929fa..9aa1e1b 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -14,9 +14,14 @@ public class Messenger private MessagingSystem? CurrentSystem => _customSystem ?? _defaultSystem; /// - /// If this messenger has a underlying messaging system assigned to it, or has it not been created yet + /// The underlying interprocess queue name for this instance /// - //public bool HasSystem => CurrentSystem is not null; + 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. @@ -24,12 +29,12 @@ public class Messenger public bool IsInitialized => CurrentSystem?.IsInitialized ?? false; /// - /// Does this process have authority over the other process. + /// Does this process have authority over the other process? Might be null if the library has not fully initialized yet /// public bool? IsAuthority => CurrentSystem?.IsAuthority; /// - /// Is the interprocess connection available? this might be false before initialization or if there has been a fatal error in the interprocess queue + /// 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 bool IsConnected => CurrentSystem?.IsConnected ?? false; @@ -62,6 +67,65 @@ public class Messenger private List? _additionalValueTypes; + private static MessagingSystem? _fallbackSystem = null; + + private static bool _runningFallbackSystemInit = false; + + internal static async Task GetFallbackSystem(bool isAuthority, long queueCapacity, IMemoryPackerEntityPool? pool = null, RenderCommandHandler? commandHandler = null, Action? failhandler = null, Action? warnHandler = null, Action? debugHandler = null, Action? postInitCallback = null) + { + while (_runningFallbackSystemInit) + await Task.Delay(1); + + if (_fallbackSystem is not null) return _fallbackSystem; + + _runningFallbackSystemInit = true; + + var now = DateTime.UtcNow; + int waitTimeMs = 2500; + var system1 = new MessagingSystem(isAuthority, $"InterprocessLib-{now.Minute}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); + system1.Connect(); + if (isAuthority) + { + _fallbackSystem = system1; + _runningFallbackSystemInit = false; + return system1; + } + var cancel1 = new CancellationTokenSource(); + system1.RegisterPingCallback((dateTime) => + { + cancel1.Cancel(); + }); + system1.SendPackable(new PingCommand()); + await Task.Delay(waitTimeMs, cancel1.Token); + if (cancel1.IsCancellationRequested) + { + _fallbackSystem = system1; + } + else + { + system1.Dispose(); + var cancel2 = new CancellationTokenSource(); + var system2 = new MessagingSystem(isAuthority, $"InterprocessLib-{(now.Minute - 1) % 60}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); + system2.Connect(); + system2.RegisterPingCallback((dateTime) => + { + cancel2.Cancel(); + }); + system2.SendPackable(new PingCommand()); + await Task.Delay(waitTimeMs, cancel2.Token); + if (cancel2.IsCancellationRequested) + { + _fallbackSystem = system2; + } + else + { + system2.Dispose(); + } + } + _runningFallbackSystemInit = false; + return _fallbackSystem; + } + /// /// Creates an instance with a unique owner /// @@ -109,7 +173,12 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< } else { - throw new EntryPointNotFoundException("Could not find InterprocessLib initialization type!"); + var fallbackSystemTask = GetFallbackSystem(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; } } } @@ -584,7 +653,7 @@ public void ReceiveString(string id, Action callback) CurrentSystem!.RegisterStringCallback(_ownerId, id, callback); } - public void ReceiveStringList(string id, Action?>? callback) + public void ReceiveStringList(string id, Action?> callback) { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -700,6 +769,22 @@ public void SendTypeCommand(Type type) } #endif + internal void SendPing() + { + if (!IsInitialized) + { + RunPostInit(() => SendPing()); + return; + } + + CurrentSystem!.SendPackable(new PingCommand()); + } + + internal void ReceivePing(Action callback) + { + CurrentSystem!.RegisterPingCallback(callback); + } + // This won't work because we can't possibly register every type of collection ahead of time //public void ReceiveObjectCollection(string id, Action callback) where C : ICollection, new() where T : class, IMemoryPackable, new() //{ diff --git a/InterprocessLib.Shared/Pool.cs b/InterprocessLib.Shared/Pool.cs new file mode 100644 index 0000000..c3af9c1 --- /dev/null +++ b/InterprocessLib.Shared/Pool.cs @@ -0,0 +1,17 @@ +using Renderite.Shared; + +namespace InterprocessLib; + +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 index 785a0c9..0ca0e92 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -73,7 +73,9 @@ public OwnerData() private static Dictionary _backends = new(); - private IMemoryPackerEntityPool _pool; + //private IMemoryPackerEntityPool _pool; + + private Action? _pingCallback; internal void SetPostInitActions(List? actions) { @@ -163,7 +165,7 @@ public void RegisterStringCallback(string owner, string id, Action call _ownerData[owner].StringCallbacks[id] = callback; } - public void RegisterStringListCallback(string owner, string id, Action?>? callback) + public void RegisterStringListCallback(string owner, string id, Action?> callback) { _ownerData[owner].StringListCallbacks[id] = callback; } @@ -178,11 +180,6 @@ public void RegisterEmptyCallback(string owner, string id, Action callback) _ownerData[owner].ObjectCallbacks[id] = callback; } - //public void RegisterObjectListCallback(string owner, string id, Action> callback) where T : class, IMemoryPackable, new() - //{ - // _ownerData[owner].ObjectCollectionCallbacks[id] = callback; - //} - public void RegisterObjectArrayCallback(string owner, string id, Action callback) where T : class, IMemoryPackable, new() { _ownerData[owner].ObjectArrayCallbacks[id] = callback; @@ -193,6 +190,11 @@ public void RegisterEmptyCallback(string owner, string id, Action callback) _ownerData[owner].ObjectCollectionCallbacks[id] = callback; } + internal void RegisterPingCallback(Action callback) + { + _pingCallback = 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) { IsAuthority = isAuthority; @@ -206,9 +208,9 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I _postInitCallback = postInitCallback; - TypeManager = new(QueueName, pool); + TypeManager = new(pool); - _pool = pool; + //_pool = pool; _primary = new MessagingManager(pool); _primary.CommandHandler = CommandHandler; @@ -373,6 +375,18 @@ private void HandleEmptyCommand(EmptyCommand command) } } + private void HandlePingCommand(PingCommand ping) + { + if (_pingCallback is not null) + { + _pingCallback.Invoke(ping.Time); + } + else + { + _onWarning?.Invoke($"PingCommand is not registered to receive a callback! QueueName: {QueueName}"); + } + } + private void CommandHandler(RendererCommand command, int messageSize) { _onCommandReceived?.Invoke(command, messageSize); @@ -402,6 +416,12 @@ private void CommandHandler(RendererCommand command, int messageSize) } } + if (packable is PingCommand pingCommand) + { + HandlePingCommand(pingCommand); + return; + } + if (packable is TypeCommand typeCommand) { _onDebug?.Invoke($"Received new type to register: {typeCommand.Type?.FullName ?? "NULL"}"); @@ -435,7 +455,7 @@ private void CommandHandler(RendererCommand command, int messageSize) else if (packable is CollectionCommand collectionCommand) { var collectionType = collectionCommand.CollectionType; - var innerDataType = collectionCommand.InnerDataType; + var innerDataType = collectionCommand.StoredType; if (innerDataType == typeof(string)) { HandleStringListCommand((StringListCommand)collectionCommand); diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index 242a346..52b1202 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -1,7 +1,6 @@ //#define TEST_COMPILATION using Renderite.Shared; -using System.Collections.Generic; using System.Reflection; namespace InterprocessLib.Tests; diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index d7e94e2..08b0135 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -19,8 +19,6 @@ internal class TypeManager private static List CurrentRendererCommandTypes => (List)typeof(PolymorphicMemoryPackableEntity).GetField("types", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!; - private static Dictionary _typeManagers = new(); - private List> _borrowers = new(); private List> _returners = new(); @@ -65,27 +63,17 @@ static TypeManager() WrapperCommand.InitNewTypes(list); } - internal TypeManager(string queueName, IMemoryPackerEntityPool pool) + internal TypeManager(IMemoryPackerEntityPool pool) { - _typeManagers.Add(queueName, this); _pool = pool; InitializeCoreTypes(); } - internal static TypeManager GetTypeManager(string queueName) - { - return _typeManagers[queueName]; - } - internal void InitializeCoreTypes() { if (_initializedCoreTypes) return; - RegisterAdditionalObjectType(); - RegisterAdditionalObjectType(); - RegisterAdditionalObjectType(); - RegisterAdditionalObjectType(); - RegisterAdditionalObjectType(); + PushNewTypes([typeof(MessengerReadyCommand), typeof(EmptyCommand), typeof(StringCommand), typeof(StringListCommand), typeof(TypeCommand), typeof(PingCommand)]); foreach (var valueType in TypeManager._valueTypes) { @@ -168,9 +156,9 @@ private void RegisterAdditionalValueType() where T : unmanaged var valueHashSetCommandType = typeof(ValueCollectionCommand<,>).MakeGenericType(typeof(HashSet), type); - _registeredValueTypes.Add(type); - PushNewTypes([valueCommandType, valueArrayCommandType, valueListCommandType, valueHashSetCommandType]); + + _registeredValueTypes.Add(type); } private void RegisterAdditionalObjectType() where T : class, IMemoryPackable, new() @@ -191,9 +179,9 @@ private void RegisterAdditionalValueType() where T : unmanaged var objectHashSetCommandType = typeof(ObjectCollectionCommand<,>).MakeGenericType(typeof(HashSet), type); - _registeredObjectTypes.Add(type); - PushNewTypes([type, objectCommandType, objectArrayCommandType, objectListCommandType, objectHashSetCommandType]); + + _registeredObjectTypes.Add(type); } private IMemoryPackable? Borrow() where T : class, IMemoryPackable, new() diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index e39808e..3bca32c 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -1,15 +1,14 @@ 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) - { - } public static void Init() { if (Messenger.DefaultInitStarted) @@ -19,32 +18,16 @@ public static void Init() Task.Run(InitLoop); } - private static async void InitLoop() + private static async Task InitLoop() { - if (RenderingManager.Instance is null) + Messenger.OnWarning = (msg) => { - await Task.Delay(1); - InitLoop(); - } - else + UnityEngine.Debug.LogWarning($"[InterprocessLib] [WARN] {msg}"); + }; + Messenger.OnFailure = (ex) => { - var getConnectionParametersMethod = typeof(RenderingManager).GetMethod("GetConnectionParameters", BindingFlags.Instance | BindingFlags.NonPublic); - - object[] parameters = { "", 0L }; - - if (!(bool)getConnectionParametersMethod.Invoke(RenderingManager.Instance, parameters)) - { - throw new ArgumentException("Could not get connection parameters from RenderingManager!"); - } - - Messenger.OnWarning = (msg) => - { - UnityEngine.Debug.LogWarning($"[InterprocessLib] [WARN] {msg}"); - }; - Messenger.OnFailure = (ex) => - { - UnityEngine.Debug.LogError($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Host!\n{ex}"); - }; + UnityEngine.Debug.LogError($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Host!\n{ex}"); + }; #if DEBUG Messenger.OnDebug = (msg) => { @@ -52,9 +35,31 @@ private static async void InitLoop() }; #endif - var host = new MessagingSystem(false, (string)parameters[0] + "InterprocessLib", (long)parameters[1], PackerMemoryPool.Instance, CommandHandler, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); - Messenger.SetDefaultSystem(host); - host.Connect(); + var args = Environment.GetCommandLineArgs(); + string? fullQueueName = null; + for (int i = 0; i < args.Length; i++) + { + if (args[i].EndsWith("QueueName", StringComparison.InvariantCultureIgnoreCase)) + { + fullQueueName = args[i + 1]; + break; + } } + + MessagingSystem? system = null; + if (fullQueueName is null) + { + system = await Messenger.GetFallbackSystem(false, MessagingManager.DEFAULT_CAPACITY, PackerMemoryPool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + if (system is null) + throw new EntryPointNotFoundException("Unable to get fallback messaging system!"); + } + else + { + var engineSharedMemoryPrefix = fullQueueName.Substring(0, fullQueueName.IndexOf('_')); + system = new MessagingSystem(false, $"InterprocessLib-{engineSharedMemoryPrefix}", MessagingManager.DEFAULT_CAPACITY, PackerMemoryPool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + } + + Messenger.SetDefaultSystem(system); + system.Connect(); } } \ No newline at end of file diff --git a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs index b7f9743..d4bb058 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs +++ b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs @@ -25,13 +25,13 @@ public class Plugin : BasePlugin public static ConfigEntry? SyncTestOutput; public static ConfigEntry? ResetToggle; - //private static MessagingBackend? _customBackend; +#if TEST_SPAWN_PROCESS public static Messenger? _customMessenger; public static ConfigEntry? SpawnProcessToggle; private static Random _rand = new(); -#pragma warning disable CS0169 private static string? _customQueueName; -#pragma warning restore + private static Process? _customProcess; +#endif private static void CommandHandler(RendererCommand command, int messageSize) { @@ -52,13 +52,14 @@ private static void DebugHandler(string msg) Log!.LogDebug($"[Child Process Messaging Host] {msg}"); } +#if TEST_SPAWN_PROCESS private static void SpawnProcess() { -#if TEST_SPAWN_PROCESS + if (_customProcess is not null && !_customProcess.HasExited) return; _customQueueName ??= $"MyCustomQueue{_rand.Next()}"; Log!.LogInfo("Child process queue name: " + _customQueueName); _customMessenger ??= new Messenger("InterprocessLib.Tests", true, _customQueueName, additionalObjectTypes: [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], additionalValueTypes: [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); - var process = new Process(); + _customProcess = new Process(); string projectConfiguration; #if DEBUG @@ -67,14 +68,14 @@ private static void SpawnProcess() projectConfiguration = "Release"; #endif - process.StartInfo.FileName = @$"S:\Projects\ResoniteModDev\_THUNDERSTORE\InterprocessLib\Tests\InterprocessLib.Standalone.Tests\bin\{projectConfiguration}\net9.0\InterprocessLib.Standalone.Tests.exe"; - process.StartInfo.Arguments = _customQueueName; - process.StartInfo.UseShellExecute = true; // Run in a new window - process.StartInfo.WindowStyle = ProcessWindowStyle.Normal; - process.Start(); + _customProcess.StartInfo.FileName = @$"S:\Projects\ResoniteModDev\_THUNDERSTORE\InterprocessLib\Tests\InterprocessLib.Standalone.Tests\bin\{projectConfiguration}\net9.0\InterprocessLib.Standalone.Tests.exe"; + _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 } +#endif public override void Load() { @@ -103,7 +104,14 @@ public override void Load() Tests.RunTests(_unknownMessenger, Log!.LogInfo); Tests.RunTests(_another, Log!.LogInfo); +#if TEST_SPAWN_PROCESS SpawnProcess(); + SpawnProcessToggle = Config.Bind("General", "SpawnChildProcess", false); + SpawnProcessToggle.SettingChanged += (sender, args) => + { + SpawnProcess(); + }; +#endif SyncTest = Config.Bind("General", "SyncTest", 34); _messenger.SyncConfigEntry(SyncTest); @@ -112,17 +120,19 @@ public override void Load() CheckSyncToggle = Config.Bind("General", "CheckSync", false); SyncTestOutput = Config.Bind("General", "SyncTestOutput", 0); ResetToggle = Config.Bind("General", "ResetToggle", false); - SpawnProcessToggle = Config.Bind("General", "SpawnChildProcess", false); - SpawnProcessToggle.SettingChanged += (sender, args) => - { - SpawnProcess(); - }; + RunTestsToggle!.SettingChanged += (sender, args) => { _messenger!.SendEmptyCommand("RunTests"); Tests.RunTests(_messenger, Log!.LogInfo); Tests.RunTests(_unknownMessenger, Log!.LogInfo); Tests.RunTests(_another, Log!.LogInfo); +#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.Standalone.Tests/Program.cs b/Tests/InterprocessLib.Standalone.Tests/Program.cs index 8d7492b..b96312a 100644 --- a/Tests/InterprocessLib.Standalone.Tests/Program.cs +++ b/Tests/InterprocessLib.Standalone.Tests/Program.cs @@ -50,7 +50,7 @@ static void Main(string[] args) Tests.RunTests(messenger, Console.WriteLine); - Thread.Sleep(30000); + Console.ReadLine(); // Keeps the window open while also allowing it to continue to receive and display new data } } } From b8a3d0351c30f4e2e0d80c8a08e88967d2e7ae52 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:03:29 +0000 Subject: [PATCH 15/35] Added a way to check the latency of the connection --- InterprocessLib.Shared/Commands.cs | 6 +- InterprocessLib.Shared/Messenger.cs | 56 ++++++++++--------- InterprocessLib.Shared/System.cs | 19 +++---- .../BepisLoaderTests.cs | 13 ++++- Tests/InterprocessLib.RML.Tests/RML_Tests.cs | 6 +- 5 files changed, 56 insertions(+), 44 deletions(-) diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 63ac0f1..eca74b3 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -541,15 +541,15 @@ public void Unpack(ref MemoryUnpacker unpacker) internal sealed class PingCommand : IMemoryPackable { - public DateTime Time; + public DateTime InitialSendTime; public void Pack(ref MemoryPacker packer) { - packer.Write(Time); + packer.Write(InitialSendTime); } public void Unpack(ref MemoryUnpacker unpacker) { - unpacker.Read(ref Time); + unpacker.Read(ref InitialSendTime); } } diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index 9aa1e1b..a3bbb7f 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -73,7 +73,9 @@ public class Messenger internal static async Task GetFallbackSystem(bool isAuthority, long queueCapacity, IMemoryPackerEntityPool? pool = null, RenderCommandHandler? commandHandler = null, Action? failhandler = null, Action? warnHandler = null, Action? debugHandler = null, Action? postInitCallback = null) { - while (_runningFallbackSystemInit) + var startTime = DateTime.UtcNow; + int waitTimeMs = 2500; + while (_runningFallbackSystemInit && (DateTime.UtcNow - startTime).TotalMilliseconds < waitTimeMs * 2) await Task.Delay(1); if (_fallbackSystem is not null) return _fallbackSystem; @@ -81,8 +83,8 @@ public class Messenger _runningFallbackSystemInit = true; var now = DateTime.UtcNow; - int waitTimeMs = 2500; - var system1 = new MessagingSystem(isAuthority, $"InterprocessLib-{now.Minute}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); + int minuteInDay = now.Hour * 60 + now.Minute; + var system1 = new MessagingSystem(isAuthority, $"InterprocessLib-{minuteInDay}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); system1.Connect(); if (isAuthority) { @@ -91,11 +93,11 @@ public class Messenger return system1; } var cancel1 = new CancellationTokenSource(); - system1.RegisterPingCallback((dateTime) => + system1.PingCallback = (latency) => { cancel1.Cancel(); - }); - system1.SendPackable(new PingCommand()); + }; + system1.SendPackable(new PingCommand() { InitialSendTime = now }); await Task.Delay(waitTimeMs, cancel1.Token); if (cancel1.IsCancellationRequested) { @@ -103,15 +105,16 @@ public class Messenger } 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-{(now.Minute - 1) % 60}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); + var cancel2 = new CancellationTokenSource(); + var system2 = new MessagingSystem(isAuthority, $"InterprocessLib-{minuteInDay - 1}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); system2.Connect(); - system2.RegisterPingCallback((dateTime) => + system2.PingCallback = (latency) => { cancel2.Cancel(); - }); - system2.SendPackable(new PingCommand()); + }; + system2.SendPackable(new PingCommand() { InitialSendTime = now }); await Task.Delay(waitTimeMs, cancel2.Token); if (cancel2.IsCancellationRequested) { @@ -749,6 +752,21 @@ public void ReceiveEmptyCommand(string id, Action callback) CurrentSystem!.RegisterObjectArrayCallback(_ownerId, id, callback); } + public void CheckLatency(Action callback) + { + if (!IsInitialized) + { + RunPostInit(() => CheckLatency(callback)); + return; + } + + CurrentSystem!.PingCallback = callback; + + var pingCommand = new PingCommand(); + pingCommand.InitialSendTime = DateTime.UtcNow; + CurrentSystem!.SendPackable(pingCommand); + } + #if DEBUG public void SendTypeCommand(Type type) { @@ -769,22 +787,6 @@ public void SendTypeCommand(Type type) } #endif - internal void SendPing() - { - if (!IsInitialized) - { - RunPostInit(() => SendPing()); - return; - } - - CurrentSystem!.SendPackable(new PingCommand()); - } - - internal void ReceivePing(Action callback) - { - CurrentSystem!.RegisterPingCallback(callback); - } - // This won't work because we can't possibly register every type of collection ahead of time //public void ReceiveObjectCollection(string id, Action callback) where C : ICollection, new() where T : class, IMemoryPackable, new() //{ diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 0ca0e92..c84e4c6 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -75,7 +75,7 @@ public OwnerData() //private IMemoryPackerEntityPool _pool; - private Action? _pingCallback; + internal Action? PingCallback; internal void SetPostInitActions(List? actions) { @@ -94,7 +94,10 @@ public void Connect() IsConnected = true; if (!IsAuthority) + { + SendPackable(new MessengerReadyCommand()); Initialize(); + } } private void Initialize() @@ -102,9 +105,6 @@ private void Initialize() if (IsInitialized) throw new InvalidOperationException("Already initialized!"); - if (!IsAuthority) - SendPackable(new MessengerReadyCommand()); - var actions = _postInitActions!.ToArray(); _postInitActions = null; @@ -190,11 +190,6 @@ public void RegisterEmptyCallback(string owner, string id, Action callback) _ownerData[owner].ObjectCollectionCallbacks[id] = callback; } - internal void RegisterPingCallback(Action callback) - { - _pingCallback = 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) { IsAuthority = isAuthority; @@ -377,13 +372,13 @@ private void HandleEmptyCommand(EmptyCommand command) private void HandlePingCommand(PingCommand ping) { - if (_pingCallback is not null) + if (PingCallback is not null) { - _pingCallback.Invoke(ping.Time); + PingCallback.Invoke(DateTime.UtcNow - ping.InitialSendTime); } else { - _onWarning?.Invoke($"PingCommand is not registered to receive a callback! QueueName: {QueueName}"); + SendPackable(ping); } } diff --git a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs index d4bb058..cf34ea7 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs +++ b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs @@ -24,6 +24,7 @@ public class Plugin : BasePlugin public static ConfigEntry? CheckSyncToggle; public static ConfigEntry? SyncTestOutput; public static ConfigEntry? ResetToggle; + public static ConfigEntry? LatencyMilliseconds; #if TEST_SPAWN_PROCESS public static Messenger? _customMessenger; @@ -120,13 +121,23 @@ public override void Load() CheckSyncToggle = Config.Bind("General", "CheckSync", false); SyncTestOutput = Config.Bind("General", "SyncTestOutput", 0); ResetToggle = Config.Bind("General", "ResetToggle", false); - + LatencyMilliseconds = Config.Bind("General", "LatencyMilliseconds", -1.0); + + _messenger.CheckLatency(latency => + { + LatencyMilliseconds.Value = latency.TotalMilliseconds; + }); + RunTestsToggle!.SettingChanged += (sender, args) => { _messenger!.SendEmptyCommand("RunTests"); Tests.RunTests(_messenger, Log!.LogInfo); Tests.RunTests(_unknownMessenger, Log!.LogInfo); Tests.RunTests(_another, Log!.LogInfo); + _messenger.CheckLatency(latency => + { + LatencyMilliseconds.Value = latency.TotalMilliseconds; + }); #if TEST_SPAWN_PROCESS if (_customMessenger is not null && _customProcess != null && !_customProcess.HasExited) { diff --git a/Tests/InterprocessLib.RML.Tests/RML_Tests.cs b/Tests/InterprocessLib.RML.Tests/RML_Tests.cs index 0290dbd..dd52472 100644 --- a/Tests/InterprocessLib.RML.Tests/RML_Tests.cs +++ b/Tests/InterprocessLib.RML.Tests/RML_Tests.cs @@ -23,7 +23,8 @@ 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 LatencyMilliseconds = new ModConfigurationKey("LatencyMilliseconds", "LatencyMilliseconds:", () => -1.0); public static Messenger? _messenger; public static Messenger? _unknownMessenger; @@ -39,6 +40,8 @@ public override void OnEngineInit() Tests.RunTests(_unknownMessenger, Msg); Tests.RunTests(_another, Msg); + _messenger.CheckLatency(latency => LatencyMilliseconds!.Value = latency.TotalMilliseconds); + _messenger.SyncConfigEntry(SyncTest); RunTestsToggle!.OnChanged += (object? newValue) => @@ -47,6 +50,7 @@ public override void OnEngineInit() Tests.RunTests(_messenger, Msg); Tests.RunTests(_unknownMessenger, Msg); Tests.RunTests(_another, Msg); + _messenger.CheckLatency(latency => LatencyMilliseconds!.Value = latency.TotalMilliseconds); }; CheckSyncToggle!.OnChanged += (object? newValue) => { From ed3a03d1c70ad3edccc5930c582104acbaa6ac26 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:32:00 +0000 Subject: [PATCH 16/35] Improvements to the ping command --- InterprocessLib.Shared/Commands.cs | 6 +++--- InterprocessLib.Shared/Messenger.cs | 6 +++--- InterprocessLib.Shared/System.cs | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index eca74b3..63ac0f1 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -541,15 +541,15 @@ public void Unpack(ref MemoryUnpacker unpacker) internal sealed class PingCommand : IMemoryPackable { - public DateTime InitialSendTime; + public DateTime Time; public void Pack(ref MemoryPacker packer) { - packer.Write(InitialSendTime); + packer.Write(Time); } public void Unpack(ref MemoryUnpacker unpacker) { - unpacker.Read(ref InitialSendTime); + unpacker.Read(ref Time); } } diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index a3bbb7f..de8b729 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -97,7 +97,7 @@ public class Messenger { cancel1.Cancel(); }; - system1.SendPackable(new PingCommand() { InitialSendTime = now }); + system1.SendPackable(new PingCommand() { Time = now }); await Task.Delay(waitTimeMs, cancel1.Token); if (cancel1.IsCancellationRequested) { @@ -114,7 +114,7 @@ public class Messenger { cancel2.Cancel(); }; - system2.SendPackable(new PingCommand() { InitialSendTime = now }); + system2.SendPackable(new PingCommand() { Time = now }); await Task.Delay(waitTimeMs, cancel2.Token); if (cancel2.IsCancellationRequested) { @@ -763,7 +763,7 @@ public void CheckLatency(Action callback) CurrentSystem!.PingCallback = callback; var pingCommand = new PingCommand(); - pingCommand.InitialSendTime = DateTime.UtcNow; + pingCommand.Time = DateTime.UtcNow; CurrentSystem!.SendPackable(pingCommand); } diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index c84e4c6..67dbe2b 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -374,7 +374,8 @@ private void HandlePingCommand(PingCommand ping) { if (PingCallback is not null) { - PingCallback.Invoke(DateTime.UtcNow - ping.InitialSendTime); + PingCallback.Invoke(DateTime.UtcNow - ping.Time); + PingCallback = null; } else { From ce6210c1ed0f2cfe2ae9cfee14d0abdf54f706aa Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sun, 9 Nov 2025 21:24:52 +0000 Subject: [PATCH 17/35] Fixes for linux headless --- ...InterprocessLib.BepisLoader_Extensions.csproj | 1 + .../InterprocessLib.RML_Extensions.csproj | 1 + InterprocessLib.FrooxEngine/FrooxEngineInit.cs | 3 ++- .../InterprocessLib.FrooxEngine.csproj | 1 + InterprocessLib.Shared/System.cs | 3 ++- .../InterprocessLib.BepInEx.Tests.csproj | 16 ++++++++-------- .../BepisLoaderTests.cs | 7 ++++++- .../InterprocessLib.BepisLoader.Tests.csproj | 1 + .../InterprocessLib.RML.Tests.csproj | 1 + .../InterprocessLib.Standalone.Tests.csproj | 1 + 10 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Extensions/InterprocessLib.BepisLoader_Extensions/InterprocessLib.BepisLoader_Extensions.csproj b/Extensions/InterprocessLib.BepisLoader_Extensions/InterprocessLib.BepisLoader_Extensions.csproj index e8fb992..2becd9d 100644 --- a/Extensions/InterprocessLib.BepisLoader_Extensions/InterprocessLib.BepisLoader_Extensions.csproj +++ b/Extensions/InterprocessLib.BepisLoader_Extensions/InterprocessLib.BepisLoader_Extensions.csproj @@ -17,6 +17,7 @@ $(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/ https://nuget-modding.resonite.net/v3/index.json; diff --git a/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj b/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj index 5c40f1a..f8d536d 100644 --- a/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj +++ b/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj @@ -18,6 +18,7 @@ $(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 diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index bcd0c6c..aad919e 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -59,6 +59,7 @@ private static async void InitLoop() if (uniqueId is null) { + Messenger.OnDebug?.Invoke("Shared memory unique id is null! Attempting to use fallback..."); system = await Messenger.GetFallbackSystem(true, MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); if (system is null) { @@ -68,10 +69,10 @@ private static async void InitLoop() else { system = new MessagingSystem(true, $"InterprocessLib-{uniqueId}", MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + system.Connect(); } Messenger.SetDefaultSystem(system); Engine.Current.OnShutdown += system.Dispose; // this might fix the rare occurence that Renderite.Host stays open after exiting Resonite - system.Connect(); } } \ No newline at end of file diff --git a/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj b/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj index f3f4cd8..3af38bf 100644 --- a/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj +++ b/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj @@ -17,6 +17,7 @@ $(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 diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 67dbe2b..f68b3c5 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -209,8 +209,9 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I _primary = new MessagingManager(pool); _primary.CommandHandler = CommandHandler; - _primary.FailureHandler = (ex) => + _primary.FailureHandler = (ex) => { + if (ex is OperationCanceledException) return; // this happens when you call Dispose Dispose(); _onFailure?.Invoke(ex); }; diff --git a/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj b/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj index 55c691b..713a283 100644 --- a/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj +++ b/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj @@ -20,8 +20,8 @@ $(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) - $(GamePath)Renderer\BepInEx\plugins\Nytra-InterprocessLib\InterprocessLib.BepInEx + $(GamePath)Renderer\BepInEx\plugins\$(AssemblyName) + $(GamePath)Renderer\BepInEx\plugins\Nytra-InterprocessLib\InterprocessLib.BepInEx Debug;Release;Tests @@ -60,13 +60,13 @@ - - + + - - + + - - + + diff --git a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs index cf34ea7..4cd5465 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs +++ b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs @@ -7,6 +7,7 @@ using Elements.Core; using Renderite.Shared; using System.Diagnostics; +using System.Runtime.InteropServices; namespace InterprocessLib.Tests; @@ -69,7 +70,11 @@ private static void SpawnProcess() projectConfiguration = "Release"; #endif - _customProcess.StartInfo.FileName = @$"S:\Projects\ResoniteModDev\_THUNDERSTORE\InterprocessLib\Tests\InterprocessLib.Standalone.Tests\bin\{projectConfiguration}\net9.0\InterprocessLib.Standalone.Tests.exe"; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + _customProcess.StartInfo.FileName = @$"S:\Projects\ResoniteModDev\_THUNDERSTORE\InterprocessLib\Tests\InterprocessLib.Standalone.Tests\bin\{projectConfiguration}\net9.0\InterprocessLib.Standalone.Tests.exe"; + else + _customProcess.StartInfo.FileName = @$"/home/nytra/code/Resonite/ResoniteInterprocessLib/Tests/InterprocessLib.Standalone.Tests/bin/{projectConfiguration}/net9.0/InterprocessLib.Standalone.Tests"; + _customProcess.StartInfo.Arguments = _customQueueName; _customProcess.StartInfo.UseShellExecute = true; // Run in a new window _customProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal; diff --git a/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj b/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj index acf88f2..46f6aa1 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj +++ b/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj @@ -19,6 +19,7 @@ $(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)BepInEx\plugins\$(AssemblyName) $(GamePath)BepInEx\plugins\Nytra-InterprocessLib\InterprocessLib.BepisLoader diff --git a/Tests/InterprocessLib.RML.Tests/InterprocessLib.RML.Tests.csproj b/Tests/InterprocessLib.RML.Tests/InterprocessLib.RML.Tests.csproj index b121ee6..6d3c42f 100644 --- a/Tests/InterprocessLib.RML.Tests/InterprocessLib.RML.Tests.csproj +++ b/Tests/InterprocessLib.RML.Tests/InterprocessLib.RML.Tests.csproj @@ -19,6 +19,7 @@ $(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)rml_mods Debug;Release;Tests diff --git a/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj b/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj index e6656a6..6bfa7b8 100644 --- a/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj +++ b/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj @@ -9,6 +9,7 @@ $(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/ From c0702a5ac32584f8ef842651b123db4f7f3c2519 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Tue, 11 Nov 2025 23:51:58 +0000 Subject: [PATCH 18/35] Catch task cancelled exception --- InterprocessLib.Shared/Messenger.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index de8b729..750ff27 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -98,7 +98,13 @@ public class Messenger cancel1.Cancel(); }; system1.SendPackable(new PingCommand() { Time = now }); - await Task.Delay(waitTimeMs, cancel1.Token); + try + { + await Task.Delay(waitTimeMs, cancel1.Token); + } + catch (TaskCanceledException) + { + } if (cancel1.IsCancellationRequested) { _fallbackSystem = system1; @@ -115,7 +121,13 @@ public class Messenger cancel2.Cancel(); }; system2.SendPackable(new PingCommand() { Time = now }); - await Task.Delay(waitTimeMs, cancel2.Token); + try + { + await Task.Delay(waitTimeMs, cancel2.Token); + } + catch (TaskCanceledException) + { + } if (cancel2.IsCancellationRequested) { _fallbackSystem = system2; From 825348dd9346174022b2b757138d05272378b517 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sun, 23 Nov 2025 19:19:10 +0000 Subject: [PATCH 19/35] Attempts to fix the order of init steps (not fully done) --- .../FrooxEngineInit.cs | 2 + InterprocessLib.Shared/Messenger.cs | 42 +++++++++++-------- InterprocessLib.Shared/System.cs | 13 +++--- InterprocessLib.Unity/UnityInit.cs | 6 ++- 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index aad919e..eebfed5 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -72,6 +72,8 @@ private static async void InitLoop() system.Connect(); } + Messenger.PreInit(system); + system.Initialize(); Messenger.SetDefaultSystem(system); Engine.Current.OnShutdown += system.Dispose; // this might fix the rare occurence that Renderite.Host stays open after exiting Resonite } diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index 750ff27..de736b8 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -57,7 +57,7 @@ public class Messenger private static List? _defaultPostInitActions = new(); - private static List? _defaultPreInitActions = new(); + private static List>? _defaultPreInitActions = new(); private string _ownerId; @@ -162,10 +162,7 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< if (_defaultSystem is null) { - DefaultRunPreInit(() => - { - Register(); - }); + DefaultRunPreInit(Register); } else { @@ -259,16 +256,16 @@ public Messenger(string ownerId, bool isAuthority, string queueName, IMemoryPack _customSystem.Connect(); } - internal static void SetDefaultSystem(MessagingSystem system) + internal static void PreInit(MessagingSystem system) { - _defaultSystem = system; - _defaultSystem.SetPostInitActions(_defaultPostInitActions); - _defaultPostInitActions = null; + system.SetPostInitActions(_defaultPostInitActions); + //_defaultPostInitActions = null; + foreach (var act in _defaultPreInitActions!) { try { - act(); + act(system); } catch (Exception ex) { @@ -278,6 +275,12 @@ internal static void SetDefaultSystem(MessagingSystem system) _defaultPreInitActions = null; } + internal static void SetDefaultSystem(MessagingSystem system) + { + _defaultSystem = system; + _defaultPostInitActions = null; + } + private void RunPostInit(Action act) { if (CurrentSystem is null) @@ -286,26 +289,31 @@ private void RunPostInit(Action act) CurrentSystem.RunPostInit(act); } - private void Register() + private void Register(MessagingSystem system) { - if (CurrentSystem!.HasOwner(_ownerId)) + if (system.HasOwner(_ownerId)) { - OnWarning?.Invoke($"Owner {_ownerId} has already been registered in this process for messaging backend with queue name: {CurrentSystem.QueueName}"); + OnWarning?.Invoke($"Owner {_ownerId} has already been registered in this process for messaging backend with queue name: {system.QueueName}"); } else - CurrentSystem.RegisterOwner(_ownerId); + system.RegisterOwner(_ownerId); if (_additionalObjectTypes is not null) { - CurrentSystem.TypeManager.InitObjectTypeList(_additionalObjectTypes.Where(t => !CurrentSystem.TypeManager.IsObjectTypeInitialized(t)).ToList()); + system.TypeManager.InitObjectTypeList(_additionalObjectTypes.Where(t => !system.TypeManager.IsObjectTypeInitialized(t)).ToList()); } if (_additionalValueTypes is not null) { - CurrentSystem.TypeManager.InitValueTypeList(_additionalValueTypes.Where(t => !CurrentSystem.TypeManager.IsValueTypeInitialized(t)).ToList()); + system.TypeManager.InitValueTypeList(_additionalValueTypes.Where(t => !system.TypeManager.IsValueTypeInitialized(t)).ToList()); } } - private static void DefaultRunPreInit(Action act) + private void Register() + { + Register(CurrentSystem!); + } + + private static void DefaultRunPreInit(Action act) { if (_defaultSystem is null) { diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index f68b3c5..afc32f3 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -92,19 +92,18 @@ public void Connect() _primary!.Connect(QueueName, IsAuthority, QueueCapacity); IsConnected = true; - - if (!IsAuthority) - { - SendPackable(new MessengerReadyCommand()); - Initialize(); - } } - private void Initialize() + public void Initialize() { if (IsInitialized) throw new InvalidOperationException("Already initialized!"); + if (!IsAuthority) + { + SendPackable(new MessengerReadyCommand()); + } + var actions = _postInitActions!.ToArray(); _postInitActions = null; diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index 3bca32c..649d488 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -57,9 +57,11 @@ private static async Task InitLoop() { var engineSharedMemoryPrefix = fullQueueName.Substring(0, fullQueueName.IndexOf('_')); system = new MessagingSystem(false, $"InterprocessLib-{engineSharedMemoryPrefix}", MessagingManager.DEFAULT_CAPACITY, PackerMemoryPool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + system.Connect(); } - Messenger.SetDefaultSystem(system); - system.Connect(); + Messenger.PreInit(system); + system.Initialize(); + Messenger.SetDefaultSystem(system); // ToDo: figure out the correct order of init steps } } \ No newline at end of file From d9d34c509656dba4d7efefd026d5ff0d1699d99e Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:07:12 +0000 Subject: [PATCH 20/35] Fixed order of init steps hopefully --- .../FrooxEngineInit.cs | 11 +++++--- InterprocessLib.Shared/Messenger.cs | 25 +++++++++++++++---- InterprocessLib.Shared/Pool.cs | 4 +++ InterprocessLib.Shared/System.cs | 7 ++++-- InterprocessLib.Unity/UnityInit.cs | 9 ++++--- 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index eebfed5..6e2a53d 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -72,9 +72,14 @@ private static async void InitLoop() system.Connect(); } - Messenger.PreInit(system); - system.Initialize(); - Messenger.SetDefaultSystem(system); + lock (Messenger.LockObj) + { + Messenger.PreInit(system); + + Messenger.SetDefaultSystem(system); + system.Initialize(); + } + Engine.Current.OnShutdown += system.Dispose; // this might fix the rare occurence that Renderite.Host stays open after exiting Resonite } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index de736b8..89f1948 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -71,6 +71,8 @@ public class Messenger private static bool _runningFallbackSystemInit = false; + internal static object LockObj = new(); + internal static async Task GetFallbackSystem(bool isAuthority, long queueCapacity, IMemoryPackerEntityPool? pool = null, RenderCommandHandler? commandHandler = null, Action? failhandler = null, Action? warnHandler = null, Action? debugHandler = null, Action? postInitCallback = null) { var startTime = DateTime.UtcNow; @@ -86,6 +88,7 @@ public class Messenger int minuteInDay = now.Hour * 60 + now.Minute; var system1 = new MessagingSystem(isAuthority, $"InterprocessLib-{minuteInDay}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); system1.Connect(); + system1.Initialize(); if (isAuthority) { _fallbackSystem = system1; @@ -116,6 +119,7 @@ public class Messenger var cancel2 = new CancellationTokenSource(); var system2 = new MessagingSystem(isAuthority, $"InterprocessLib-{minuteInDay - 1}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); system2.Connect(); + system2.Initialize(); system2.PingCallback = (latency) => { cancel2.Cancel(); @@ -252,8 +256,11 @@ public Messenger(string ownerId, bool isAuthority, string queueName, IMemoryPack Register(); - if (!_customSystem!.IsConnected) + if (!_customSystem!.IsInitialized) + { _customSystem.Connect(); + _customSystem.Initialize(); + } } internal static void PreInit(MessagingSystem system) @@ -283,10 +290,18 @@ internal static void SetDefaultSystem(MessagingSystem system) private void RunPostInit(Action act) { - if (CurrentSystem is null) - DefaultRunPostInit(act); - else - CurrentSystem.RunPostInit(act); + lock (LockObj) + { + if (IsInitialized) + { + act(); + return; + } + if (CurrentSystem is null) + DefaultRunPostInit(act); + else + CurrentSystem.RunPostInit(act); + } } private void Register(MessagingSystem system) diff --git a/InterprocessLib.Shared/Pool.cs b/InterprocessLib.Shared/Pool.cs index c3af9c1..77559bd 100644 --- a/InterprocessLib.Shared/Pool.cs +++ b/InterprocessLib.Shared/Pool.cs @@ -2,6 +2,10 @@ 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(); diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index afc32f3..1ba6e91 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -99,7 +99,7 @@ public void Initialize() if (IsInitialized) throw new InvalidOperationException("Already initialized!"); - if (!IsAuthority) + if (!IsAuthority) // Put back into Connect? { SendPackable(new MessengerReadyCommand()); } @@ -385,6 +385,9 @@ private void HandlePingCommand(PingCommand ping) private void CommandHandler(RendererCommand command, int messageSize) { + while (!IsInitialized && !IsAuthority) + Thread.Sleep(1); + _onCommandReceived?.Invoke(command, messageSize); IMemoryPackable? packable = null; @@ -399,7 +402,7 @@ private void CommandHandler(RendererCommand command, int messageSize) return; } - if (!IsInitialized) + if (!IsInitialized && IsAuthority) { if (packable is MessengerReadyCommand) { diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index 649d488..c8d126e 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -60,8 +60,11 @@ private static async Task InitLoop() system.Connect(); } - Messenger.PreInit(system); - system.Initialize(); - Messenger.SetDefaultSystem(system); // ToDo: figure out the correct order of init steps + lock (Messenger.LockObj) + { + Messenger.PreInit(system); + Messenger.SetDefaultSystem(system); // ToDo: figure out the correct order of init steps + system.Initialize(); + } } } \ No newline at end of file From 2a97db2437a3c78863fc3f4b28cbe73e846f3fb0 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Tue, 25 Nov 2025 00:06:16 +0000 Subject: [PATCH 21/35] .NET 10 --- .../InterprocessLib.BepisLoader_Extensions.csproj | 4 ++-- .../InterprocessLib.RML_Extensions.csproj | 4 ++-- .../InterprocessLib.FrooxEngine.csproj | 4 ++-- InterprocessLib.Shared/Commands.cs | 2 ++ InterprocessLib.Unity/UnityInit.cs | 13 ++++++++++--- .../InterprocessLib.BepisLoader.Tests.csproj | 4 ++-- .../InterprocessLib.RML.Tests.csproj | 4 ++-- .../InterprocessLib.Standalone.Tests.csproj | 2 +- 8 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Extensions/InterprocessLib.BepisLoader_Extensions/InterprocessLib.BepisLoader_Extensions.csproj b/Extensions/InterprocessLib.BepisLoader_Extensions/InterprocessLib.BepisLoader_Extensions.csproj index 2becd9d..03b8b3b 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 diff --git a/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj b/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj index f8d536d..0ee562b 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 diff --git a/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj b/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj index 3af38bf..922d13b 100644 --- a/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj +++ b/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj @@ -3,8 +3,8 @@ 3.0.0 Nytra - net9.0 - 13 + net10.0 + 14 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.FrooxEngine InterprocessLib.FrooxEngine diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 63ac0f1..110eff1 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -380,6 +380,8 @@ public override void Unpack(ref MemoryUnpacker unpacker) } } +// StringArrayCommand ??? + internal sealed class ObjectCollectionCommand : CollectionCommand where C : ICollection, new() where T : class, IMemoryPackable, new() { public C? Objects; diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index c8d126e..e8efaa5 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -16,9 +16,10 @@ public static void Init() Messenger.DefaultInitStarted = true; - Task.Run(InitLoop); + //Task.Run(InitLoop); + InitLoop(); } - private static async Task InitLoop() + private static void InitLoop() { Messenger.OnWarning = (msg) => { @@ -35,6 +36,8 @@ private static async Task InitLoop() }; #endif + //UnityEngine.Debug.Log("Init"); + var args = Environment.GetCommandLineArgs(); string? fullQueueName = null; for (int i = 0; i < args.Length; i++) @@ -49,7 +52,9 @@ private static async Task InitLoop() MessagingSystem? system = null; if (fullQueueName is null) { - system = await Messenger.GetFallbackSystem(false, MessagingManager.DEFAULT_CAPACITY, PackerMemoryPool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + var fallbackTask = Messenger.GetFallbackSystem(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!"); } @@ -66,5 +71,7 @@ private static async Task InitLoop() Messenger.SetDefaultSystem(system); // ToDo: figure out the correct order of init steps system.Initialize(); } + + //UnityEngine.Debug.Log("DONE"); } } \ No newline at end of file diff --git a/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj b/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj index 46f6aa1..357ed4a 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 diff --git a/Tests/InterprocessLib.RML.Tests/InterprocessLib.RML.Tests.csproj b/Tests/InterprocessLib.RML.Tests/InterprocessLib.RML.Tests.csproj index 6d3c42f..227fde9 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 diff --git a/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj b/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj index 6bfa7b8..990fa54 100644 --- a/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj +++ b/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 enable enable $(ResonitePath)/ From e5240c4afb7658512a0c2fbf49fd540852cbb96b Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:20:59 +0000 Subject: [PATCH 22/35] Turn off ping command handling due to infinite loop bug --- InterprocessLib.Shared/System.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 1ba6e91..023963c 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -417,7 +417,7 @@ private void CommandHandler(RendererCommand command, int messageSize) if (packable is PingCommand pingCommand) { - HandlePingCommand(pingCommand); + //HandlePingCommand(pingCommand); return; } From 7d54fc24db3cfd9748a883ad28dc226b3685babf Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Fri, 12 Dec 2025 07:48:43 +0000 Subject: [PATCH 23/35] Fix ping command and comment out type command handling for now --- InterprocessLib.Shared/Commands.cs | 3 ++ InterprocessLib.Shared/Messenger.cs | 36 ++++++++-------- InterprocessLib.Shared/System.cs | 43 +++++++++---------- InterprocessLib.Shared/Tests.cs | 18 +++----- InterprocessLib.Unity/UnityInit.cs | 2 +- .../BepisLoaderTests.cs | 4 +- 6 files changed, 51 insertions(+), 55 deletions(-) diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 110eff1..60455fe 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -544,14 +544,17 @@ public void Unpack(ref MemoryUnpacker unpacker) internal sealed class PingCommand : IMemoryPackable { public DateTime Time; + public bool Received; public void Pack(ref MemoryPacker packer) { packer.Write(Time); + packer.Write(Received); } public void Unpack(ref MemoryUnpacker unpacker) { unpacker.Read(ref Time); + unpacker.Read(ref Received); } } diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index 89f1948..394c9b1 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -802,25 +802,23 @@ public void CheckLatency(Action callback) CurrentSystem!.SendPackable(pingCommand); } -#if DEBUG - public void SendTypeCommand(Type type) - { - if (type is null) - throw new ArgumentNullException(nameof(type)); - - if (IsInitialized != true) - { - RunPostInit(() => SendTypeCommand(type)); - return; - } - - var typeCommand = new TypeCommand(); - typeCommand.Type = type; - - Messenger.OnDebug?.Invoke($"Sending new type to register: {type.FullName}"); - CurrentSystem!.SendPackable(typeCommand); - } -#endif + // public void SendTypeCommand(Type type) + // { + // if (type is null) + // throw new ArgumentNullException(nameof(type)); + + // if (IsInitialized != true) + // { + // RunPostInit(() => SendTypeCommand(type)); + // return; + // } + + // var typeCommand = new TypeCommand(); + // typeCommand.Type = type; + + // Messenger.OnDebug?.Invoke($"Sending new type to register: {type.FullName}"); + // CurrentSystem!.SendPackable(typeCommand); + // } // This won't work because we can't possibly register every type of collection ahead of time //public void ReceiveObjectCollection(string id, Action callback) where C : ICollection, new() where T : class, IMemoryPackable, new() diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 023963c..7f3c8e5 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -372,13 +372,12 @@ private void HandleEmptyCommand(EmptyCommand command) private void HandlePingCommand(PingCommand ping) { - if (PingCallback is not null) - { - PingCallback.Invoke(DateTime.UtcNow - ping.Time); + PingCallback?.Invoke(DateTime.UtcNow - ping.Time); PingCallback = null; - } - else + + if (!ping.Received) { + ping.Received = true; SendPackable(ping); } } @@ -417,26 +416,26 @@ private void CommandHandler(RendererCommand command, int messageSize) if (packable is PingCommand pingCommand) { - //HandlePingCommand(pingCommand); + HandlePingCommand(pingCommand); return; } - if (packable is TypeCommand typeCommand) - { - _onDebug?.Invoke($"Received new type to register: {typeCommand.Type?.FullName ?? "NULL"}"); - //if (typeCommand.Type is not null) - //{ - // if (typeCommand.Type.IsValueType) - // { - // TypeManager.InitValueTypeList([typeCommand.Type]); - // } - // else - // { - // TypeManager.InitObjectTypeList([typeCommand.Type]); - // } - //} - return; - } + // if (packable is TypeCommand typeCommand) + // { + // _onDebug?.Invoke($"Received new type to register: {typeCommand.Type?.FullName ?? "NULL"}"); + // //if (typeCommand.Type is not null) + // //{ + // // if (typeCommand.Type.IsValueType) + // // { + // // TypeManager.InitValueTypeList([typeCommand.Type]); + // // } + // // else + // // { + // // TypeManager.InitObjectTypeList([typeCommand.Type]); + // // } + // //} + // return; + // } if (packable is IdentifiableCommand identifiableCommand) { diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index 52b1202..dedf188 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -36,9 +36,7 @@ public static void RunTests(Messenger messenger, Action logCallback) TestObjectArray(); TestObjectHashSet(); -#if DEBUG - TestTypeCommand(); -#endif + //TestTypeCommand(); try { @@ -82,14 +80,12 @@ public static void RunTests(Messenger messenger, Action logCallback) } } -#if DEBUG - static void TestTypeCommand() - { - _messenger!.SendTypeCommand(typeof(TestNewTypeToRegister)); - _messenger!.SendTypeCommand(typeof(ColorProfile)); - _messenger!.SendTypeCommand(typeof(ValueCollectionCommand, float>)); - } -#endif + // static void TestTypeCommand() + // { + // _messenger!.SendTypeCommand(typeof(TestNewTypeToRegister)); + // _messenger!.SendTypeCommand(typeof(ColorProfile)); + // _messenger!.SendTypeCommand(typeof(ValueCollectionCommand, float>)); + // } static void TestValueArray() { diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index e8efaa5..bbe0d8f 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -68,7 +68,7 @@ private static void InitLoop() lock (Messenger.LockObj) { Messenger.PreInit(system); - Messenger.SetDefaultSystem(system); // ToDo: figure out the correct order of init steps + Messenger.SetDefaultSystem(system); // ToDo: figure out the correct order of init steps (done?) system.Initialize(); } diff --git a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs index 4cd5465..4cca441 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs +++ b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs @@ -71,9 +71,9 @@ private static void SpawnProcess() #endif if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - _customProcess.StartInfo.FileName = @$"S:\Projects\ResoniteModDev\_THUNDERSTORE\InterprocessLib\Tests\InterprocessLib.Standalone.Tests\bin\{projectConfiguration}\net9.0\InterprocessLib.Standalone.Tests.exe"; + _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/Resonite/ResoniteInterprocessLib/Tests/InterprocessLib.Standalone.Tests/bin/{projectConfiguration}/net9.0/InterprocessLib.Standalone.Tests"; + _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 From 8b14925fc8b8f20390762bb2ca2d5946771c9878 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Fri, 12 Dec 2025 08:45:07 +0000 Subject: [PATCH 24/35] Fix indentation in csproj files --- .../InterprocessLib.BepInEx_Extensions.csproj | 14 ++++---- ...erprocessLib.BepisLoader_Extensions.csproj | 12 +++---- .../InterprocessLib.RML_Extensions.csproj | 14 ++++---- .../InterprocessLib.FrooxEngine.csproj | 18 +++++------ .../InterprocessLib.Unity.csproj | 16 +++++----- .../InterprocessLib.BepInEx.Tests.csproj | 24 +++++++------- .../InterprocessLib.BepisLoader.Tests.csproj | 32 +++++++++---------- .../InterprocessLib.RML.Tests.csproj | 30 ++++++++--------- .../InterprocessLib.Standalone.Tests.csproj | 6 ++-- 9 files changed, 83 insertions(+), 83 deletions(-) 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 03b8b3b..840b216 100644 --- a/Extensions/InterprocessLib.BepisLoader_Extensions/InterprocessLib.BepisLoader_Extensions.csproj +++ b/Extensions/InterprocessLib.BepisLoader_Extensions/InterprocessLib.BepisLoader_Extensions.csproj @@ -4,7 +4,7 @@ 1.0.0 Nytra net10.0 - 14 + 14 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.BepisLoader_Extensions InterprocessLib.BepisLoader_Extensions @@ -16,8 +16,8 @@ $(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/ https://nuget-modding.resonite.net/v3/index.json; @@ -50,7 +50,7 @@ - + @@ -63,8 +63,8 @@ - - + + diff --git a/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj b/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj index 0ee562b..4855418 100644 --- a/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj +++ b/Extensions/InterprocessLib.RML_Extensions/InterprocessLib.RML_Extensions.csproj @@ -4,7 +4,7 @@ 1.0.0 Nytra net10.0 - 14 + 14 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.RML_Extensions InterprocessLib.RML_Extensions @@ -17,9 +17,9 @@ $(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 @@ -40,7 +40,7 @@ $(GamePath)Renderite.Shared.dll False - + $(GamePath)Libraries/ResoniteModLoader.dll False @@ -56,8 +56,8 @@ - - + + diff --git a/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj b/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj index 922d13b..85d9512 100644 --- a/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj +++ b/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj @@ -4,7 +4,7 @@ 3.0.0 Nytra net10.0 - 14 + 14 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.FrooxEngine InterprocessLib.FrooxEngine @@ -16,9 +16,9 @@ $(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 @@ -31,26 +31,26 @@ $(GamePath)Renderite.Shared.dll False - + $(GamePath)FrooxEngine.dll False - + $(GamePath)Elements.Core.dll False - + - - + + diff --git a/InterprocessLib.Unity/InterprocessLib.Unity.csproj b/InterprocessLib.Unity/InterprocessLib.Unity.csproj index 6fa923f..559faf9 100644 --- a/InterprocessLib.Unity/InterprocessLib.Unity.csproj +++ b/InterprocessLib.Unity/InterprocessLib.Unity.csproj @@ -4,7 +4,7 @@ 3.0.0 Nytra net472 - 12 + 12 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.Unity InterprocessLib.Unity @@ -16,16 +16,16 @@ $(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,7 +34,7 @@ $(GamePath)Renderer\Renderite.Renderer_Data\Managed\UnityEngine.CoreModule.dll - + @@ -45,8 +45,8 @@ - - + + diff --git a/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj b/Tests/InterprocessLib.BepInEx.Tests/InterprocessLib.BepInEx.Tests.csproj index 713a283..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,15 +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/ + 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 + $(GamePath)Renderer\BepInEx\plugins\Nytra-InterprocessLib\InterprocessLib.BepInEx Debug;Release;Tests - + $(GamePath)Renderer\BepInEx\core\0Harmony.dll @@ -41,7 +41,7 @@ $(GamePath)Renderer\Renderite.Renderer_Data\Managed\Renderite.Unity.dll - + $(GamePath)Renderer\Renderite.Renderer_Data\Managed\Renderite.Shared.dll @@ -57,16 +57,16 @@ - + - + - - + + - - + + diff --git a/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj b/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj index 357ed4a..57e7fe0 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj +++ b/Tests/InterprocessLib.BepisLoader.Tests/InterprocessLib.BepisLoader.Tests.csproj @@ -4,7 +4,7 @@ 1.0.1 Nytra net10.0 - 14 + 14 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.BepisLoader.Tests InterprocessLib.BepisLoader.Tests @@ -18,10 +18,10 @@ $(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/ $(GamePath)BepInEx\plugins\$(AssemblyName) - $(GamePath)BepInEx\plugins\Nytra-InterprocessLib\InterprocessLib.BepisLoader + $(GamePath)BepInEx\plugins\Nytra-InterprocessLib\InterprocessLib.BepisLoader https://nuget-modding.resonite.net/v3/index.json; @@ -56,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 227fde9..6355a98 100644 --- a/Tests/InterprocessLib.RML.Tests/InterprocessLib.RML.Tests.csproj +++ b/Tests/InterprocessLib.RML.Tests/InterprocessLib.RML.Tests.csproj @@ -4,7 +4,7 @@ 1.0.1 Nytra net10.0 - 14 + 14 https://github.com/Nytra/ResoniteInterprocessLib Nytra.InterprocessLib.RML.Tests InterprocessLib.RML.Tests @@ -18,10 +18,10 @@ $(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)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 @@ -43,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 @@ -65,14 +65,14 @@ - - + + - - + + diff --git a/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj b/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj index 990fa54..8b968ef 100644 --- a/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj +++ b/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj @@ -5,11 +5,11 @@ net10.0 enable enable - $(ResonitePath)/ + $(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/ From feac6174cd1404d19fc76b67058d3818d4922318 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Fri, 19 Dec 2025 14:56:42 +0000 Subject: [PATCH 25/35] Cleanup and add StringArray and StringHashSet commands --- .../FrooxEngineInit.cs | 1 - InterprocessLib.Shared/Commands.cs | 101 +++++++++++-- InterprocessLib.Shared/Messenger.cs | 139 +++++++++--------- InterprocessLib.Shared/System.cs | 82 +++++++++-- 4 files changed, 228 insertions(+), 95 deletions(-) diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index 6e2a53d..48bbeb9 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -75,7 +75,6 @@ private static async void InitLoop() lock (Messenger.LockObj) { Messenger.PreInit(system); - Messenger.SetDefaultSystem(system); system.Initialize(); } diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 60455fe..fad61c2 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -152,13 +152,6 @@ public override void Unpack(ref MemoryUnpacker unpacker) Values = new T[len]; // ToDo: use pool borrowing here? ReadOnlySpan data = unpacker.Access(len); data.CopyTo(Values); - - //for (int i = 0; i < len; i++) - //{ - // T val = default; - // unpacker.Read(ref val); - // Values[i] = val; - //} } } @@ -361,26 +354,110 @@ public override string ToString() internal sealed class StringListCommand : CollectionCommand { - public List? Values; + public List? Strings; - public override IEnumerable? UntypedCollection => Values; + public override IEnumerable? UntypedCollection => Strings; public override Type StoredType => typeof(string); public override Type CollectionType => typeof(List); public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); - packer.WriteStringList(Values!); + packer.WriteStringList(Strings!); } public override void Unpack(ref MemoryUnpacker unpacker) { base.Unpack(ref unpacker); - unpacker.ReadStringList(ref Values!); + unpacker.ReadStringList(ref Strings!); } } -// StringArrayCommand ??? +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 StringHashSetCommand : CollectionCommand +{ + public HashSet? Strings; + + public override IEnumerable? UntypedCollection => Strings; + public override Type StoredType => typeof(string); + public override Type CollectionType => typeof(HashSet); + + 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 = null; + return; + } + Strings = new(); // ToDo: use pool borrowing here? + for (int i = 0; i < len; i++) + { + string? str = default; + unpacker.Read(ref str!); + Strings.Add(str); + } + } +} internal sealed class ObjectCollectionCommand : CollectionCommand where C : ICollection, new() where T : class, IMemoryPackable, new() { diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index 394c9b1..6213cb9 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -61,8 +61,6 @@ public class Messenger private string _ownerId; - //private static HashSet _defaultBackendRegisteredOwnerIds = new(); - private List? _additionalObjectTypes; private List? _additionalValueTypes; @@ -88,7 +86,6 @@ public class Messenger int minuteInDay = now.Hour * 60 + now.Minute; var system1 = new MessagingSystem(isAuthority, $"InterprocessLib-{minuteInDay}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); system1.Connect(); - system1.Initialize(); if (isAuthority) { _fallbackSystem = system1; @@ -119,7 +116,6 @@ public class Messenger var cancel2 = new CancellationTokenSource(); var system2 = new MessagingSystem(isAuthority, $"InterprocessLib-{minuteInDay - 1}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); system2.Connect(); - system2.Initialize(); system2.PingCallback = (latency) => { cancel2.Cancel(); @@ -256,9 +252,13 @@ public Messenger(string ownerId, bool isAuthority, string queueName, IMemoryPack Register(); - if (!_customSystem!.IsInitialized) + if (!_customSystem.IsConnected) { _customSystem.Connect(); + } + + if (!_customSystem!.IsInitialized) + { _customSystem.Initialize(); } } @@ -266,7 +266,7 @@ public Messenger(string ownerId, bool isAuthority, string queueName, IMemoryPack internal static void PreInit(MessagingSystem system) { system.SetPostInitActions(_defaultPostInitActions); - //_defaultPostInitActions = null; + _defaultPostInitActions = null; foreach (var act in _defaultPreInitActions!) { @@ -285,7 +285,6 @@ internal static void PreInit(MessagingSystem system) internal static void SetDefaultSystem(MessagingSystem system) { _defaultSystem = system; - _defaultPostInitActions = null; } private void RunPostInit(Action act) @@ -464,7 +463,43 @@ public void SendStringList(string id, List list) var command = new StringListCommand(); command.Owner = _ownerId; command.Id = id; - command.Values = list; + command.Strings = list; + CurrentSystem!.SendPackable(command); + } + + public void SendStringArray(string id, string[] array) + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (IsInitialized != true) + { + RunPostInit(() => SendStringArray(id, array)); + return; + } + + var command = new StringArrayCommand(); + command.Owner = _ownerId; + command.Id = id; + command.Strings = array; + CurrentSystem!.SendPackable(command); + } + + public void SendStringHashSet(string id, HashSet set) + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (IsInitialized != true) + { + RunPostInit(() => SendStringHashSet(id, set)); + return; + } + + var command = new StringHashSetCommand(); + command.Owner = _ownerId; + command.Id = id; + command.Strings = set; CurrentSystem!.SendPackable(command); } @@ -570,27 +605,6 @@ public void SendEmptyCommand(string id) CurrentSystem!.SendPackable(command); } - //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 != true) - // { - // RunPostInit(() => SendObjectCollection(id, collection)); - // return; - // } - - // if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) - // throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - - // var command = new ObjectCollectionCommand(); - // command.Owner = _ownerId; - // command.Id = id; - // command.Objects = collection; - // CurrentSystem!.SendPackable(command); - //} - public void ReceiveValue(string id, Action callback) where T : unmanaged { if (id is null) @@ -659,24 +673,6 @@ public void ReceiveValueArray(string id, Action callback) where T : unma CurrentSystem!.RegisterValueArrayCallback(_ownerId, id, callback); } - // This won't work because we can't possibly register every type of collection ahead of time - //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 != true) - // { - // RunPostInit(() => ReceiveValueCollection(id, callback)); - // return; - // } - - // if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) - // throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - - // CurrentSystem!.RegisterValueCollectionCallback(_ownerId, id, callback); - //} - public void ReceiveString(string id, Action callback) { if (id is null) @@ -705,6 +701,34 @@ public void ReceiveStringList(string id, Action?> callback) CurrentSystem!.RegisterStringListCallback(_ownerId, id, callback); } + public void ReceiveStringArray(string id, Action callback) + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (IsInitialized != true) + { + RunPostInit(() => ReceiveStringArray(id, callback)); + return; + } + + CurrentSystem!.RegisterStringArrayCallback(_ownerId, id, callback); + } + + public void ReceiveStringHashSet(string id, Action?> callback) + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (IsInitialized != true) + { + RunPostInit(() => ReceiveStringHashSet(id, callback)); + return; + } + + CurrentSystem!.RegisterStringHashSetCallback(_ownerId, id, callback); + } + public void ReceiveEmptyCommand(string id, Action callback) { if (id is null) @@ -802,7 +826,7 @@ public void CheckLatency(Action callback) CurrentSystem!.SendPackable(pingCommand); } - // public void SendTypeCommand(Type type) + // internal void SendTypeCommand(Type type) // { // if (type is null) // throw new ArgumentNullException(nameof(type)); @@ -816,25 +840,6 @@ public void CheckLatency(Action callback) // var typeCommand = new TypeCommand(); // typeCommand.Type = type; - // Messenger.OnDebug?.Invoke($"Sending new type to register: {type.FullName}"); // CurrentSystem!.SendPackable(typeCommand); // } - - // This won't work because we can't possibly register every type of collection ahead of time - //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 != true) - // { - // RunPostInit(() => ReceiveObjectCollection(id, callback)); - // return; - // } - - // if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) - // throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - - // CurrentSystem!.RegisterObjectCollectionCallback(_ownerId, id, callback); - //} } \ No newline at end of file diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 7f3c8e5..98beaf0 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -22,6 +22,10 @@ private struct OwnerData // not List, because FrooxEngine just takes List public readonly Dictionary?>?> StringListCallbacks = new(); + public readonly Dictionary?> StringArrayCallbacks = new(); + + public readonly Dictionary?>?> StringHashSetCallbacks = new(); + public readonly Dictionary ObjectArrayCallbacks = new(); public readonly Dictionary ObjectCollectionCallbacks = new(); @@ -73,8 +77,6 @@ public OwnerData() private static Dictionary _backends = new(); - //private IMemoryPackerEntityPool _pool; - internal Action? PingCallback; internal void SetPostInitActions(List? actions) @@ -99,7 +101,7 @@ public void Initialize() if (IsInitialized) throw new InvalidOperationException("Already initialized!"); - if (!IsAuthority) // Put back into Connect? + if (!IsAuthority) { SendPackable(new MessengerReadyCommand()); } @@ -169,6 +171,16 @@ public void RegisterStringListCallback(string owner, string id, Action callback) + { + _ownerData[owner].StringArrayCallbacks[id] = callback; + } + + public void RegisterStringHashSetCallback(string owner, string id, Action?> callback) + { + _ownerData[owner].StringHashSetCallbacks[id] = callback; + } + public void RegisterEmptyCallback(string owner, string id, Action callback) { _ownerData[owner].EmptyCallbacks[id] = callback; @@ -301,7 +313,7 @@ private void HandleStringListCommand(StringListCommand command) { if (callback != null) { - callback.Invoke(command.Values); + callback.Invoke(command.Strings); } } else @@ -310,6 +322,36 @@ private void HandleStringListCommand(StringListCommand command) } } + 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 HandleStringHashSetCommand(StringHashSetCommand command) + { + if (_ownerData[command.Owner].StringHashSetCallbacks.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 HandleEmptyCommand(EmptyCommand command) { if (_ownerData[command.Owner].EmptyCallbacks.TryGetValue(command.Id, out var callback)) @@ -372,19 +414,21 @@ private void HandleEmptyCommand(EmptyCommand command) private void HandlePingCommand(PingCommand ping) { - PingCallback?.Invoke(DateTime.UtcNow - ping.Time); - PingCallback = null; - if (!ping.Received) { ping.Received = true; SendPackable(ping); } + else + { + PingCallback?.Invoke(DateTime.UtcNow - ping.Time); + PingCallback = null; + } } private void CommandHandler(RendererCommand command, int messageSize) { - while (!IsInitialized && !IsAuthority) + while (!IsInitialized && !IsAuthority) // needed? Thread.Sleep(1); _onCommandReceived?.Invoke(command, messageSize); @@ -401,6 +445,12 @@ private void CommandHandler(RendererCommand command, int messageSize) return; } + if (packable is PingCommand pingCommand) + { + HandlePingCommand(pingCommand); + return; + } + if (!IsInitialized && IsAuthority) { if (packable is MessengerReadyCommand) @@ -414,12 +464,6 @@ private void CommandHandler(RendererCommand command, int messageSize) } } - if (packable is PingCommand pingCommand) - { - HandlePingCommand(pingCommand); - return; - } - // if (packable is TypeCommand typeCommand) // { // _onDebug?.Invoke($"Received new type to register: {typeCommand.Type?.FullName ?? "NULL"}"); @@ -454,10 +498,18 @@ private void CommandHandler(RendererCommand command, int messageSize) { var collectionType = collectionCommand.CollectionType; var innerDataType = collectionCommand.StoredType; - if (innerDataType == typeof(string)) + if (collectionType == typeof(List)) { HandleStringListCommand((StringListCommand)collectionCommand); } + else if (collectionType == typeof(string[])) + { + HandleStringArrayCommand((StringArrayCommand)collectionCommand); + } + else if (collectionType == typeof(HashSet)) + { + HandleStringHashSetCommand((StringHashSetCommand)collectionCommand); + } else if (innerDataType.IsValueType) { if (collectionType.IsArray) From b8c58bcad2e977ae90cbdf66795e200c403b7162 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Fri, 19 Dec 2025 19:29:41 +0000 Subject: [PATCH 26/35] Improvements to nullability of types --- InterprocessLib.Shared/Commands.cs | 100 +++++++++++++++---------- InterprocessLib.Shared/Messenger.cs | 104 +++++++++++++------------- InterprocessLib.Shared/System.cs | 65 ++++++++-------- InterprocessLib.Shared/Tests.cs | 42 ++++++++++- InterprocessLib.Shared/TypeManager.cs | 22 +++--- 5 files changed, 193 insertions(+), 140 deletions(-) diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index fad61c2..d7403f9 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -90,7 +90,7 @@ public override void Pack(ref MemoryPacker packer) packer.Write(len); if (Values != null) { - foreach (var value in Values!) + foreach (var value in Values) { packer.Write(value); } @@ -209,7 +209,7 @@ internal sealed class TypeCommand : IMemoryPackable public void Pack(ref MemoryPacker packer) { - PackType(Type!, ref packer); + PackType(Type, ref packer); } public void Unpack(ref MemoryUnpacker unpacker) @@ -217,38 +217,49 @@ public void Unpack(ref MemoryUnpacker unpacker) Type = UnpackType(ref unpacker); } - private void PackType(Type type, ref MemoryPacker packer) + private void PackType(Type? type, ref MemoryPacker packer) { - if (type!.IsGenericType) + if (type is null) { - 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); - } + packer.Write(false); } else { - packer.Write(false); - packer.Write(type!.FullName!); + 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]; + var typeArgs = new Type?[numTypeArgs]; for (int i = 0; i < numTypeArgs; i++) { - typeArgs[i] = UnpackType(ref unpacker)!; + typeArgs[i] = UnpackType(ref unpacker); } if (typeArgs.Any(t => t is null)) return null; @@ -256,7 +267,7 @@ private void PackType(Type type, ref MemoryPacker packer) var genericTypeDefinition = FindType(genericTypeDefinitionName); if (genericTypeDefinition != null) { - return genericTypeDefinition.MakeGenericType(typeArgs); + return genericTypeDefinition.MakeGenericType(typeArgs!); } else { @@ -343,10 +354,10 @@ public override string ToString() // Dict = new(); // ToDo: use pool borrowing here? // for (int i = 0; i < len; i++) // { -// TKey key = default!; -// unpacker.ReadObject(ref key!); -// TValue val = default!; -// unpacker.ReadObject(ref val!); +// TKey key = default; +// unpacker.ReadObject(ref key); +// TValue val = default; +// unpacker.ReadObject(ref val); // Dict[key] = val; // } // } @@ -354,7 +365,7 @@ public override string ToString() internal sealed class StringListCommand : CollectionCommand { - public List? Strings; + public List? Strings; public override IEnumerable? UntypedCollection => Strings; public override Type StoredType => typeof(string); @@ -375,7 +386,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) internal sealed class StringArrayCommand : CollectionCommand { - public string[]? Strings; + public string?[]? Strings; public override IEnumerable? UntypedCollection => Strings; public override Type StoredType => typeof(string); @@ -393,7 +404,7 @@ public override void Pack(ref MemoryPacker packer) packer.Write(len); foreach (var str in Strings) { - packer.Write(str); + packer.Write(str!); } } @@ -417,7 +428,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) internal sealed class StringHashSetCommand : CollectionCommand { - public HashSet? Strings; + public HashSet? Strings; public override IEnumerable? UntypedCollection => Strings; public override Type StoredType => typeof(string); @@ -435,7 +446,7 @@ public override void Pack(ref MemoryPacker packer) packer.Write(len); foreach (var str in Strings) { - packer.Write(str); + packer.Write(str!); } } @@ -496,7 +507,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) Objects = new C(); // ToDo: use pool borrowing here? for (int i = 0; i < len; i++) { - T obj = default!; + T obj = new(); unpacker.ReadObject(ref obj!); Objects.Add(obj); } @@ -505,7 +516,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) internal sealed class ObjectArrayCommand : CollectionCommand where T : class, IMemoryPackable, new() { - public T[]? Objects; + public T?[]? Objects; public override IEnumerable? UntypedCollection => Objects; public override Type StoredType => typeof(T); @@ -540,7 +551,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) Objects = new T[len]; // ToDo: use pool borrowing here? for (int i = 0; i < len; i++) { - unpacker.ReadObject(ref Objects[i]!); + unpacker.ReadObject(ref Objects[i]); } } } @@ -648,19 +659,23 @@ public static void InitNewTypes(List types) public override void Pack(ref MemoryPacker packer) { - var packedType = Packable?.GetType(); - var backend = MessagingSystem.TryGetRegisteredSystem(QueueName!); - var type = Packable is null ? -1 : backend!.TypeManager.GetTypeIndex(packedType!); - packer.Write(type); + if (QueueName is null) throw new ArgumentNullException(nameof(QueueName)); - if (type == -1) - return; + var system = MessagingSystem.TryGetRegisteredSystem(QueueName); - packer.Write(QueueName!); + if (system is null) throw new InvalidOperationException($"MessagingSystem with QueueName: {QueueName} is not registered."); - Packable!.Pack(ref packer); + if (Packable is null) + { + packer.Write(-1); + return; + } - backend!.TypeManager.Return(packedType!, Packable); + var packedType = Packable.GetType(); + packer.Write(system.TypeManager.GetTypeIndex(packedType)); + packer.Write(QueueName); + Packable.Pack(ref packer); + system.TypeManager.Return(packedType, Packable); } public override void Unpack(ref MemoryUnpacker unpacker) @@ -676,10 +691,13 @@ public override void Unpack(ref MemoryUnpacker unpacker) unpacker.Read(ref QueueName!); var backend = MessagingSystem.TryGetRegisteredSystem(QueueName); - var type = backend!.TypeManager.GetTypeFromIndex(TypeIndex); + + if (backend is null) throw new InvalidDataException($"MessagingSystem with QueueName: {QueueName} is not registered."); + + var type = backend.TypeManager.GetTypeFromIndex(TypeIndex); Packable = backend.TypeManager.Borrow(type); - Packable!.Unpack(ref unpacker); + Packable.Unpack(ref unpacker); } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index 6213cb9..3863a92 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -69,7 +69,7 @@ public class Messenger private static bool _runningFallbackSystemInit = false; - internal static object LockObj = new(); + internal static readonly object LockObj = new(); internal static async Task GetFallbackSystem(bool isAuthority, long queueCapacity, IMemoryPackerEntityPool? pool = null, RenderCommandHandler? commandHandler = null, Action? failhandler = null, Action? warnHandler = null, Action? debugHandler = null, Action? postInitCallback = null) { @@ -352,7 +352,7 @@ public void SendValue(string id, T value) where T : unmanaged if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => SendValue(id, value)); return; @@ -368,12 +368,12 @@ public void SendValue(string id, T value) where T : unmanaged CurrentSystem!.SendPackable(command); } - public void SendValueList(string id, List list) where T : unmanaged + public void SendValueList(string id, List? list) where T : unmanaged { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => SendValueList(id, list)); return; @@ -389,12 +389,12 @@ public void SendValueList(string id, List list) where T : unmanaged CurrentSystem!.SendPackable(command); } - public void SendValueHashSet(string id, HashSet hashSet) where T : unmanaged + public void SendValueHashSet(string id, HashSet? hashSet) where T : unmanaged { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => SendValueHashSet(id, hashSet)); return; @@ -410,12 +410,12 @@ public void SendValueHashSet(string id, HashSet hashSet) where T : unmanag CurrentSystem!.SendPackable(command); } - public void SendValueArray(string id, T[] array) where T : unmanaged + public void SendValueArray(string id, T[]? array) where T : unmanaged { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => SendValueArray(id, array)); return; @@ -431,12 +431,12 @@ public void SendValueArray(string id, T[] array) where T : unmanaged 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)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => SendString(id, str)); return; @@ -449,12 +449,12 @@ public void SendString(string id, string str) CurrentSystem!.SendPackable(command); } - public void SendStringList(string id, List list) + public void SendStringList(string id, List? list) { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => SendStringList(id, list)); return; @@ -467,12 +467,12 @@ public void SendStringList(string id, List list) CurrentSystem!.SendPackable(command); } - public void SendStringArray(string id, string[] array) + public void SendStringArray(string id, string?[]? array) { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => SendStringArray(id, array)); return; @@ -485,12 +485,12 @@ public void SendStringArray(string id, string[] array) CurrentSystem!.SendPackable(command); } - public void SendStringHashSet(string id, HashSet set) + public void SendStringHashSet(string id, HashSet? set) { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => SendStringHashSet(id, set)); return; @@ -508,7 +508,7 @@ public void SendEmptyCommand(string id) if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => SendEmptyCommand(id)); return; @@ -520,12 +520,12 @@ public void SendEmptyCommand(string id) 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)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => SendObject(id, obj)); return; @@ -542,12 +542,12 @@ public void SendEmptyCommand(string id) CurrentSystem!.SendPackable(wrapper); } - public void SendObjectList(string id, List list) where T : class, IMemoryPackable, new() + public void SendObjectList(string id, List? list) where T : class, IMemoryPackable, new() { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => SendObjectList(id, list)); return; @@ -563,12 +563,12 @@ public void SendEmptyCommand(string id) CurrentSystem!.SendPackable(command); } - public void SendObjectHashSet(string id, HashSet hashSet) where T : class, IMemoryPackable, new() + public void SendObjectHashSet(string id, HashSet? hashSet) where T : class, IMemoryPackable, new() { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => SendObjectHashSet(id, hashSet)); return; @@ -584,12 +584,12 @@ public void SendEmptyCommand(string id) CurrentSystem!.SendPackable(command); } - public void SendObjectArray(string id, T[] array) where T : class, IMemoryPackable, new() + public void SendObjectArray(string id, T[]? array) where T : class, IMemoryPackable, new() { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => SendObjectArray(id, array)); return; @@ -605,12 +605,12 @@ public void SendEmptyCommand(string id) CurrentSystem!.SendPackable(command); } - public void ReceiveValue(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 != true) + if (!IsInitialized) { RunPostInit(() => ReceiveValue(id, callback)); return; @@ -622,12 +622,12 @@ public void ReceiveValue(string id, Action callback) where T : unmanaged CurrentSystem!.RegisterValueCallback(_ownerId, id, callback); } - public void ReceiveValueList(string id, Action> callback) where T : unmanaged + public void ReceiveValueList(string id, Action?>? callback) where T : unmanaged { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => ReceiveValueList(id, callback)); return; @@ -639,12 +639,12 @@ public void ReceiveValueList(string id, Action> callback) where T : u CurrentSystem!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); } - public void ReceiveValueHashSet(string id, Action> callback) where T : unmanaged + public void ReceiveValueHashSet(string id, Action?>? callback) where T : unmanaged { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => ReceiveValueHashSet(id, callback)); return; @@ -656,12 +656,12 @@ public void ReceiveValueHashSet(string id, Action> callback) where CurrentSystem!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); } - public void ReceiveValueArray(string id, Action callback) where T : unmanaged + public void ReceiveValueArray(string id, Action? callback) where T : unmanaged { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => ReceiveValueArray(id, callback)); return; @@ -673,12 +673,12 @@ public void ReceiveValueArray(string id, Action callback) where T : unma 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)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => ReceiveString(id, callback)); return; @@ -687,12 +687,12 @@ public void ReceiveString(string id, Action callback) CurrentSystem!.RegisterStringCallback(_ownerId, id, callback); } - public void ReceiveStringList(string id, Action?> callback) + public void ReceiveStringList(string id, Action?>? callback) { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => ReceiveStringList(id, callback)); return; @@ -701,12 +701,12 @@ public void ReceiveStringList(string id, Action?> callback) CurrentSystem!.RegisterStringListCallback(_ownerId, id, callback); } - public void ReceiveStringArray(string id, Action callback) + public void ReceiveStringArray(string id, Action? callback) { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => ReceiveStringArray(id, callback)); return; @@ -715,12 +715,12 @@ public void ReceiveStringArray(string id, Action callback) CurrentSystem!.RegisterStringArrayCallback(_ownerId, id, callback); } - public void ReceiveStringHashSet(string id, Action?> callback) + public void ReceiveStringHashSet(string id, Action?>? callback) { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => ReceiveStringHashSet(id, callback)); return; @@ -729,12 +729,12 @@ public void ReceiveStringHashSet(string id, Action?> callback) CurrentSystem!.RegisterStringHashSetCallback(_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)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => ReceiveEmptyCommand(id, callback)); return; @@ -743,12 +743,12 @@ public void ReceiveEmptyCommand(string id, Action 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)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => ReceiveObject(id, callback)); return; @@ -760,12 +760,12 @@ public void ReceiveEmptyCommand(string id, Action callback) CurrentSystem!.RegisterObjectCallback(_ownerId, id, callback); } - public void ReceiveObjectList(string id, Action> callback) where T : class, IMemoryPackable, new() + public void ReceiveObjectList(string id, Action?>? callback) where T : class, IMemoryPackable, new() { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => ReceiveObjectList(id, callback)); return; @@ -777,12 +777,12 @@ public void ReceiveEmptyCommand(string id, Action callback) CurrentSystem!.RegisterObjectCollectionCallback, T>(_ownerId, id, callback); } - public void ReceiveObjectHashSet(string id, Action> callback) where T : class, IMemoryPackable, new() + public void ReceiveObjectHashSet(string id, Action?>? callback) where T : class, IMemoryPackable, new() { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => ReceiveObjectHashSet(id, callback)); return; @@ -794,12 +794,12 @@ public void ReceiveEmptyCommand(string id, Action callback) CurrentSystem!.RegisterObjectCollectionCallback, T>(_ownerId, id, callback); } - public void ReceiveObjectArray(string id, Action callback) where T : class, IMemoryPackable, new() + public void ReceiveObjectArray(string id, Action? callback) where T : class, IMemoryPackable, new() { if (id is null) throw new ArgumentNullException(nameof(id)); - if (IsInitialized != true) + if (!IsInitialized) { RunPostInit(() => ReceiveObjectArray(id, callback)); return; @@ -831,7 +831,7 @@ public void CheckLatency(Action callback) // if (type is null) // throw new ArgumentNullException(nameof(type)); - // if (IsInitialized != true) + // if (!IsInitialized) // { // RunPostInit(() => SendTypeCommand(type)); // return; diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 98beaf0..32509b5 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -19,12 +19,11 @@ private struct OwnerData public readonly Dictionary ValueCollectionCallbacks = new(); - // not List, because FrooxEngine just takes List - public readonly Dictionary?>?> StringListCallbacks = new(); + public readonly Dictionary?>?> StringListCallbacks = new(); - public readonly Dictionary?> StringArrayCallbacks = new(); + public readonly Dictionary?> StringArrayCallbacks = new(); - public readonly Dictionary?>?> StringHashSetCallbacks = new(); + public readonly Dictionary?>?> StringHashSetCallbacks = new(); public readonly Dictionary ObjectArrayCallbacks = new(); @@ -43,17 +42,17 @@ public OwnerData() private MessagingManager? _primary; - private static MethodInfo? _handleValueCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static readonly MethodInfo _handleValueCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleValueCommand)); - private static MethodInfo? _handleValueCollectionCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static readonly MethodInfo _handleValueCollectionCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleValueCollectionCommand)); - private static MethodInfo? _handleValueArrayCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueArrayCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static readonly MethodInfo _handleValueArrayCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleValueArrayCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleValueArrayCommand)); - private static MethodInfo? _handleObjectCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static readonly MethodInfo _handleObjectCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleObjectCommand)); - private static MethodInfo? _handleObjectCollectionCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static readonly MethodInfo _handleObjectCollectionCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleObjectCollectionCommand)); - private static MethodInfo? _handleObjectArrayCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectArrayCommand), BindingFlags.Instance | BindingFlags.NonPublic); + private static readonly MethodInfo _handleObjectArrayCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleObjectArrayCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleObjectArrayCommand)); private RenderCommandHandler? _onCommandReceived { get; } @@ -63,7 +62,7 @@ public OwnerData() private Action? _onFailure { get; } - private Dictionary _ownerData = new(); + private readonly Dictionary _ownerData = new(); private Action? _postInitCallback; @@ -75,7 +74,7 @@ public OwnerData() internal TypeManager TypeManager; - private static Dictionary _backends = new(); + private static readonly Dictionary _backends = new(); internal Action? PingCallback; @@ -146,57 +145,57 @@ public bool HasOwner(string ownerName) return _ownerData.ContainsKey(ownerName); } - public void RegisterValueCallback(string owner, string id, Action callback) where T : unmanaged + 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 + 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 + 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) + public void RegisterStringCallback(string owner, string id, Action? callback) { _ownerData[owner].StringCallbacks[id] = callback; } - public void RegisterStringListCallback(string owner, string id, Action?> callback) + public void RegisterStringListCallback(string owner, string id, Action?>? callback) { _ownerData[owner].StringListCallbacks[id] = callback; } - public void RegisterStringArrayCallback(string owner, string id, Action callback) + public void RegisterStringArrayCallback(string owner, string id, Action? callback) { _ownerData[owner].StringArrayCallbacks[id] = callback; } - public void RegisterStringHashSetCallback(string owner, string id, Action?> callback) + public void RegisterStringHashSetCallback(string owner, string id, Action?>? callback) { _ownerData[owner].StringHashSetCallbacks[id] = callback; } - public void RegisterEmptyCallback(string owner, string id, Action 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() + 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() + 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() + public void RegisterObjectCollectionCallback(string owner, string id, Action? callback) where C : ICollection, new() where T : class, IMemoryPackable, new() { _ownerData[owner].ObjectCollectionCallbacks[id] = callback; } @@ -237,7 +236,7 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I public void Dispose() { _primary?.Dispose(); - _primary = null!; + _primary = null; IsConnected = false; } @@ -388,7 +387,7 @@ private void HandleEmptyCommand(EmptyCommand command) { if (callback != null) { - ((Action)callback).Invoke(command.Objects); + ((Action)callback).Invoke(command.Objects); } } else @@ -428,8 +427,8 @@ private void HandlePingCommand(PingCommand ping) private void CommandHandler(RendererCommand command, int messageSize) { - while (!IsInitialized && !IsAuthority) // needed? - Thread.Sleep(1); + //while (!IsInitialized && !IsAuthority) // needed? + //Thread.Sleep(1); _onCommandReceived?.Invoke(command, messageSize); @@ -491,7 +490,7 @@ private void CommandHandler(RendererCommand command, int messageSize) if (packable is ValueCommand valueCommand) { var valueType = valueCommand.ValueType; - var typedMethod = _handleValueCommandMethod!.MakeGenericMethod(valueType); + var typedMethod = _handleValueCommandMethod.MakeGenericMethod(valueType); typedMethod.Invoke(this, [packable]); } else if (packable is CollectionCommand collectionCommand) @@ -514,12 +513,12 @@ private void CommandHandler(RendererCommand command, int messageSize) { if (collectionType.IsArray) { - var typedMethod = _handleValueArrayCommandMethod!.MakeGenericMethod(innerDataType); + var typedMethod = _handleValueArrayCommandMethod.MakeGenericMethod(innerDataType); typedMethod.Invoke(this, [packable]); } else { - var typedMethod = _handleValueCollectionCommandMethod!.MakeGenericMethod(collectionType, innerDataType); + var typedMethod = _handleValueCollectionCommandMethod.MakeGenericMethod(collectionType, innerDataType); typedMethod.Invoke(this, [packable]); } } @@ -527,12 +526,12 @@ private void CommandHandler(RendererCommand command, int messageSize) { if (collectionType.IsArray) { - var typedMethod = _handleObjectArrayCommandMethod!.MakeGenericMethod(innerDataType); + var typedMethod = _handleObjectArrayCommandMethod.MakeGenericMethod(innerDataType); typedMethod.Invoke(this, [packable]); } else { - var typedMethod = _handleObjectCollectionCommandMethod!.MakeGenericMethod(collectionType, innerDataType); + var typedMethod = _handleObjectCollectionCommandMethod.MakeGenericMethod(collectionType, innerDataType); typedMethod.Invoke(this, [packable]); } } @@ -540,7 +539,7 @@ private void CommandHandler(RendererCommand command, int messageSize) else if (packable is ObjectCommand objectCommand) { var objectType = objectCommand.ObjectType; - var typedMethod = _handleObjectCommandMethod!.MakeGenericMethod(objectType); + var typedMethod = _handleObjectCommandMethod.MakeGenericMethod(objectType); typedMethod.Invoke(this, [packable]); } else diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index dedf188..7cb11a0 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -35,6 +35,8 @@ public static void RunTests(Messenger messenger, Action logCallback) TestValueArray(); TestObjectArray(); TestObjectHashSet(); + TestStringHashSet(); + TestStringArray(); //TestTypeCommand(); @@ -336,7 +338,7 @@ static void TestObjectList() { _messenger!.ReceiveObjectList("TestObjectList", (list) => { - _logCallback!($"TestObjectList: {string.Join(",", list)}"); + _logCallback!($"TestObjectList: {string.Join(",", list!)}"); }); var list = new List(); @@ -367,15 +369,49 @@ static void TestStringList() _logCallback!($"TestStringList: {string.Join(",", list!.Select(s => s ?? "NULL"))}"); }); - var list = new List(); + var list = new List(); list.Add("Hello"); list.Add("World"); list.Add("owo"); - list.Add(null!); + list.Add(null); list.Add("x3"); _messenger.SendStringList("TestStringList", list); } + static void TestStringArray() + { + _messenger!.ReceiveStringArray("TestStringArray", (arr) => + { + _logCallback!($"TestStringArray: {string.Join(",", arr!.Select(s => s ?? "NULL"))}"); + }); + + var arr = new string?[] + { + "Hello", + "World", + "owo", + null, + "x3" + }; + _messenger.SendStringArray("TestStringArray", arr); + } + + static void TestStringHashSet() + { + _messenger!.ReceiveStringHashSet("TestStringHashSet", (set) => + { + _logCallback!($"TestStringHashSet: {string.Join(",", set!.Select(s => s ?? "NULL"))}"); + }); + + var set = new HashSet(); + set.Add("Hello"); + set.Add("World"); + set.Add("owo"); + set.Add(null); + set.Add("x3"); + _messenger.SendStringHashSet("TestStringHashSet", set); + } + static void TestVanillaObject() { _messenger!.ReceiveObject("TestVanillaObject", (recv) => diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 08b0135..6026e2b 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -11,26 +11,26 @@ internal class TypeManager private bool _initializedCoreTypes = false; - private static MethodInfo? _registerValueTypeMethod = typeof(TypeManager).GetMethod(nameof(TypeManager.RegisterAdditionalValueType), BindingFlags.NonPublic | BindingFlags.Instance); + private static readonly MethodInfo _registerValueTypeMethod = typeof(TypeManager).GetMethod(nameof(RegisterAdditionalValueType), BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new MissingMethodException(nameof(RegisterAdditionalValueType)); - private static MethodInfo? _registerObjectTypeMethod = typeof(TypeManager).GetMethod(nameof(TypeManager.RegisterAdditionalObjectType), BindingFlags.NonPublic | BindingFlags.Instance); + private static readonly MethodInfo _registerObjectTypeMethod = typeof(TypeManager).GetMethod(nameof(RegisterAdditionalObjectType), BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new MissingMethodException(nameof(RegisterAdditionalObjectType)); - private List _newTypes = new(); + private readonly List _newTypes = new(); - private static List CurrentRendererCommandTypes => (List)typeof(PolymorphicMemoryPackableEntity).GetField("types", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!; + private static List CurrentRendererCommandTypes => (List)typeof(PolymorphicMemoryPackableEntity).GetField("types", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)! ?? throw new MissingFieldException("types"); - private List> _borrowers = new(); + private readonly List> _borrowers = new(); - private List> _returners = new(); + private readonly List> _returners = new(); - private Dictionary _typeToIndex = new(); + private readonly Dictionary _typeToIndex = new(); private IMemoryPackerEntityPool _pool; - private static MethodInfo? _borrowMethod = typeof(TypeManager).GetMethod("Borrow", BindingFlags.Instance | BindingFlags.NonPublic, null, [], null); - private static MethodInfo? _returnMethod = typeof(TypeManager).GetMethod("Return", BindingFlags.Instance | BindingFlags.NonPublic, null, [typeof(IMemoryPackable)], null); + 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)); - private static Type[] _valueTypes = + private static readonly Type[] _valueTypes = { typeof(bool), typeof(byte), @@ -73,7 +73,7 @@ internal void InitializeCoreTypes() { if (_initializedCoreTypes) return; - PushNewTypes([typeof(MessengerReadyCommand), typeof(EmptyCommand), typeof(StringCommand), typeof(StringListCommand), typeof(TypeCommand), typeof(PingCommand)]); + PushNewTypes([typeof(MessengerReadyCommand), typeof(EmptyCommand), typeof(StringCommand), typeof(StringListCommand), typeof(StringArrayCommand), typeof(StringHashSetCommand), typeof(TypeCommand), typeof(PingCommand)]); foreach (var valueType in TypeManager._valueTypes) { From 393f96521f26f6d863e59803e9b546fa37aa028f Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sat, 20 Dec 2025 15:38:11 +0000 Subject: [PATCH 27/35] Improve nullable-awareness and add StringCollectionCommand (replaces string list and hashset commands) --- .../InterprocessLib.FrooxEngine.csproj | 1 + InterprocessLib.Shared/Commands.cs | 96 ++++++------ InterprocessLib.Shared/Messenger.cs | 33 ++-- InterprocessLib.Shared/System.cs | 144 +++++++++--------- InterprocessLib.Shared/Tests.cs | 26 ++-- InterprocessLib.Shared/TypeManager.cs | 4 +- .../InterprocessLib.Unity.csproj | 1 + InterprocessLib.Unity/UnityInit.cs | 19 ++- .../BepInExTests.cs | 1 + 9 files changed, 170 insertions(+), 155 deletions(-) diff --git a/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj b/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj index 85d9512..6ab9139 100644 --- a/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj +++ b/InterprocessLib.FrooxEngine/InterprocessLib.FrooxEngine.csproj @@ -11,6 +11,7 @@ InterprocessLib enable enable + Nullable true false $(ResonitePath)/ diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index d7403f9..12763ad 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -8,7 +8,7 @@ namespace InterprocessLib; internal abstract class IdentifiableCommand : IMemoryPackable { - internal string Owner = ""; + public string Owner = ""; public string Id = ""; public virtual void Pack(ref MemoryPacker packer) @@ -65,8 +65,6 @@ public override string ToString() internal sealed class EmptyCommand : IdentifiableCommand { - // owo - public override string ToString() { return $"EmptyCommand:{Owner}:{Id}"; @@ -363,27 +361,6 @@ public override string ToString() // } //} -internal sealed class StringListCommand : CollectionCommand -{ - public List? Strings; - - public override IEnumerable? UntypedCollection => Strings; - public override Type StoredType => typeof(string); - public override Type CollectionType => typeof(List); - - public override void Pack(ref MemoryPacker packer) - { - base.Pack(ref packer); - packer.WriteStringList(Strings!); - } - - public override void Unpack(ref MemoryUnpacker unpacker) - { - base.Unpack(ref unpacker); - unpacker.ReadStringList(ref Strings!); - } -} - internal sealed class StringArrayCommand : CollectionCommand { public string?[]? Strings; @@ -426,13 +403,13 @@ public override void Unpack(ref MemoryUnpacker unpacker) } } -internal sealed class StringHashSetCommand : CollectionCommand +internal sealed class StringCollectionCommand : CollectionCommand where C : ICollection?, new() { - public HashSet? Strings; + public IEnumerable? Strings; public override IEnumerable? UntypedCollection => Strings; public override Type StoredType => typeof(string); - public override Type CollectionType => typeof(HashSet); + public override Type CollectionType => typeof(C); public override void Pack(ref MemoryPacker packer) { @@ -442,7 +419,7 @@ public override void Pack(ref MemoryPacker packer) packer.Write(-1); return; } - int len = Strings.Count; + int len = Strings.Count(); packer.Write(len); foreach (var str in Strings) { @@ -457,20 +434,21 @@ public override void Unpack(ref MemoryUnpacker unpacker) unpacker.Read(ref len); if (len == -1) { - Strings = null; + Strings = default; return; } - Strings = new(); // ToDo: use pool borrowing here? + var collection = new C(); // ToDo: use pool borrowing here? for (int i = 0; i < len; i++) { string? str = default; unpacker.Read(ref str!); - Strings.Add(str); + collection.Add(str); } + Strings = collection; } } -internal sealed class ObjectCollectionCommand : CollectionCommand where C : ICollection, new() where T : class, IMemoryPackable, new() +internal sealed class ObjectCollectionCommand : CollectionCommand where C : ICollection?, new() where T : class?, IMemoryPackable?, new() { public C? Objects; @@ -490,8 +468,12 @@ public override void Pack(ref MemoryPacker packer) packer.Write(len); foreach (var obj in Objects) { - packer.WriteObject(obj); - } +#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) @@ -508,15 +490,19 @@ public override void Unpack(ref MemoryUnpacker unpacker) for (int i = 0; i < len; i++) { T obj = new(); - unpacker.ReadObject(ref obj!); - Objects.Add(obj); +#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() +internal sealed class ObjectArrayCommand : CollectionCommand where T : class?, IMemoryPackable?, new() { - public T?[]? Objects; + public T[]? Objects; public override IEnumerable? UntypedCollection => Objects; public override Type StoredType => typeof(T); @@ -534,8 +520,12 @@ public override void Pack(ref MemoryPacker packer) packer.Write(len); foreach (var obj in Objects) { - packer.WriteObject(obj); - } +#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) @@ -551,12 +541,16 @@ public override void Unpack(ref MemoryUnpacker unpacker) Objects = new T[len]; // ToDo: use pool borrowing here? for (int i = 0; i < len; i++) { - unpacker.ReadObject(ref Objects[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; @@ -566,14 +560,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 diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index 3863a92..db3f49d 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -213,6 +213,9 @@ public Messenger(string ownerId, bool isAuthority, string queueName, IMemoryPack 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) { @@ -449,7 +452,7 @@ public void SendString(string id, string? str) CurrentSystem!.SendPackable(command); } - public void SendStringList(string id, List? list) + public void SendStringList(string id, IEnumerable? list) { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -460,7 +463,7 @@ public void SendStringList(string id, List? list) return; } - var command = new StringListCommand(); + var command = new StringCollectionCommand>(); command.Owner = _ownerId; command.Id = id; command.Strings = list; @@ -485,7 +488,7 @@ public void SendStringArray(string id, string?[]? array) CurrentSystem!.SendPackable(command); } - public void SendStringHashSet(string id, HashSet? set) + public void SendStringHashSet(string id, IEnumerable? set) { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -496,7 +499,7 @@ public void SendStringHashSet(string id, HashSet? set) return; } - var command = new StringHashSetCommand(); + var command = new StringCollectionCommand>(); command.Owner = _ownerId; command.Id = id; command.Strings = set; @@ -520,7 +523,7 @@ public void SendEmptyCommand(string id) 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)); @@ -542,7 +545,7 @@ public void SendEmptyCommand(string id) CurrentSystem!.SendPackable(wrapper); } - public void SendObjectList(string id, List? list) where T : class, IMemoryPackable, new() + public void SendObjectList(string id, List? list) where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -563,7 +566,7 @@ public void SendEmptyCommand(string id) CurrentSystem!.SendPackable(command); } - public void SendObjectHashSet(string id, HashSet? hashSet) where T : class, IMemoryPackable, new() + public void SendObjectHashSet(string id, HashSet? hashSet) where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -584,7 +587,7 @@ public void SendEmptyCommand(string id) CurrentSystem!.SendPackable(command); } - public void SendObjectArray(string id, T[]? array) where T : class, IMemoryPackable, new() + public void SendObjectArray(string id, T[]? array) where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -698,7 +701,7 @@ public void ReceiveStringList(string id, Action?>? callback) return; } - CurrentSystem!.RegisterStringListCallback(_ownerId, id, callback); + CurrentSystem!.RegisterStringCollectionCallback>(_ownerId, id, callback); } public void ReceiveStringArray(string id, Action? callback) @@ -712,7 +715,7 @@ public void ReceiveStringArray(string id, Action? callback) return; } - CurrentSystem!.RegisterStringArrayCallback(_ownerId, id, callback); + CurrentSystem!.RegisterStringArrayCallback(_ownerId, id, callback!); } public void ReceiveStringHashSet(string id, Action?>? callback) @@ -726,7 +729,7 @@ public void ReceiveStringHashSet(string id, Action?>? callback) return; } - CurrentSystem!.RegisterStringHashSetCallback(_ownerId, id, callback); + CurrentSystem!.RegisterStringCollectionCallback>(_ownerId, id, callback); } public void ReceiveEmptyCommand(string id, Action? callback) @@ -743,7 +746,7 @@ public void ReceiveEmptyCommand(string id, Action? 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)); @@ -760,7 +763,7 @@ public void ReceiveEmptyCommand(string id, Action? callback) CurrentSystem!.RegisterObjectCallback(_ownerId, id, callback); } - public void ReceiveObjectList(string id, Action?>? callback) where T : class, IMemoryPackable, new() + public void ReceiveObjectList(string id, Action?>? callback) where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -777,7 +780,7 @@ public void ReceiveEmptyCommand(string id, Action? callback) CurrentSystem!.RegisterObjectCollectionCallback, T>(_ownerId, id, callback); } - public void ReceiveObjectHashSet(string id, Action?>? callback) where T : class, IMemoryPackable, new() + public void ReceiveObjectHashSet(string id, Action?>? callback) where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -794,7 +797,7 @@ public void ReceiveEmptyCommand(string id, Action? callback) CurrentSystem!.RegisterObjectCollectionCallback, T>(_ownerId, id, callback); } - public void ReceiveObjectArray(string id, Action? callback) where T : class, IMemoryPackable, new() + public void ReceiveObjectArray(string id, Action? callback) where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 32509b5..00e887e 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -19,11 +19,13 @@ private struct OwnerData public readonly Dictionary ValueCollectionCallbacks = new(); - public readonly Dictionary?>?> StringListCallbacks = new(); + //public readonly Dictionary?>?> StringListCallbacks = new(); public readonly Dictionary?> StringArrayCallbacks = new(); - public readonly Dictionary?>?> StringHashSetCallbacks = new(); + //public readonly Dictionary?>?> StringHashSetCallbacks = new(); + + public readonly Dictionary StringCollectionCallbacks = new(); public readonly Dictionary ObjectArrayCallbacks = new(); @@ -54,6 +56,8 @@ public OwnerData() 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; } @@ -155,7 +159,7 @@ public void RegisterValueCallback(string owner, string id, Action? callbac _ownerData[owner].ValueCollectionCallbacks[id] = callback; } - public void RegisterValueArrayCallback(string owner, string id, Action? callback) where T : unmanaged + public void RegisterValueArrayCallback(string owner, string id, Action? callback) where T : unmanaged { _ownerData[owner].ValueArrayCallbacks[id] = callback; } @@ -165,19 +169,19 @@ public void RegisterStringCallback(string owner, string id, Action? cal _ownerData[owner].StringCallbacks[id] = callback; } - public void RegisterStringListCallback(string owner, string id, Action?>? callback) - { - _ownerData[owner].StringListCallbacks[id] = callback; - } + // public void RegisterStringListCallback(string owner, string id, Action?>? callback) + // { + // _ownerData[owner].StringListCallbacks[id] = callback; + // } public void RegisterStringArrayCallback(string owner, string id, Action? callback) { _ownerData[owner].StringArrayCallbacks[id] = callback; } - public void RegisterStringHashSetCallback(string owner, string id, Action?>? callback) + public void RegisterStringCollectionCallback(string owner, string id, Action? callback) where C : ICollection?, new() { - _ownerData[owner].StringHashSetCallbacks[id] = callback; + _ownerData[owner].StringCollectionCallbacks[id] = callback; } public void RegisterEmptyCallback(string owner, string id, Action? callback) @@ -185,17 +189,17 @@ 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() + 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() + 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() + public void RegisterObjectCollectionCallback(string owner, string id, Action? callback) where C : ICollection?, new() where T : class?, IMemoryPackable?, new() { _ownerData[owner].ObjectCollectionCallbacks[id] = callback; } @@ -297,7 +301,7 @@ private void HandleStringCommand(StringCommand command) { if (callback != null) { - callback.Invoke(command.String); + callback.Invoke(command.String!); } } else @@ -306,20 +310,20 @@ private void HandleStringCommand(StringCommand command) } } - private void HandleStringListCommand(StringListCommand command) - { - if (_ownerData[command.Owner].StringListCallbacks.TryGetValue(command.Id, out var callback)) - { - if (callback != null) - { - callback.Invoke(command.Strings); - } - } - else - { - _onWarning?.Invoke($"StringListCommand 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((List?)command.Strings); + // } + // } + // else + // { + // _onWarning?.Invoke($"StringListCommand with Id \"{command.Id}\" is not registered to receive a callback!"); + // } + // } private void HandleStringArrayCommand(StringArrayCommand command) { @@ -336,13 +340,13 @@ private void HandleStringArrayCommand(StringArrayCommand command) } } - private void HandleStringHashSetCommand(StringHashSetCommand command) + private void HandleStringCollectionCommand(StringCollectionCommand command) where C : ICollection?, new() { - if (_ownerData[command.Owner].StringHashSetCallbacks.TryGetValue(command.Id, out var callback)) + if (_ownerData[command.Owner].StringCollectionCallbacks.TryGetValue(command.Id, out var callback)) { if (callback != null) { - callback.Invoke(command.Strings); + ((Action)callback).Invoke((C?)command.Strings); } } else @@ -427,7 +431,7 @@ private void HandlePingCommand(PingCommand ping) private void CommandHandler(RendererCommand command, int messageSize) { - //while (!IsInitialized && !IsAuthority) // needed? + //while (!IsInitialized && !IsAuthority) // I don't know if this is still needed //Thread.Sleep(1); _onCommandReceived?.Invoke(command, messageSize); @@ -450,18 +454,18 @@ private void CommandHandler(RendererCommand command, int messageSize) return; } - if (!IsInitialized && IsAuthority) - { - if (packable is MessengerReadyCommand) - { - Initialize(); - return; - } - else - { - throw new InvalidDataException($"The first command needs to be the MessengerReadyCommand when not initialized!"); - } - } + // if (!IsInitialized && IsAuthority) + // { + // if (packable is MessengerReadyCommand) + // { + // Initialize(); + // return; + // } + // else + // { + // throw new InvalidDataException($"The first command needs to be the MessengerReadyCommand when not initialized!"); + // } + // } // if (packable is TypeCommand typeCommand) // { @@ -493,21 +497,32 @@ private void CommandHandler(RendererCommand command, int messageSize) 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 (collectionType == typeof(List)) + if (innerDataType == typeof(string)) { - HandleStringListCommand((StringListCommand)collectionCommand); - } - else if (collectionType == typeof(string[])) - { - HandleStringArrayCommand((StringArrayCommand)collectionCommand); - } - else if (collectionType == typeof(HashSet)) - { - HandleStringHashSetCommand((StringHashSetCommand)collectionCommand); + var typedMethod = _handleStringCollectionCommandMethod.MakeGenericMethod(collectionType); + typedMethod.Invoke(this, [packable]); } else if (innerDataType.IsValueType) { @@ -536,28 +551,9 @@ private void CommandHandler(RendererCommand command, int messageSize) } } } - else if (packable is ObjectCommand objectCommand) - { - var objectType = objectCommand.ObjectType; - var typedMethod = _handleObjectCommandMethod.MakeGenericMethod(objectType); - typedMethod.Invoke(this, [packable]); - } else { - switch (packable) - { - case StringCommand: - HandleStringCommand((StringCommand)packable); - break; - case EmptyCommand: - HandleEmptyCommand((EmptyCommand)packable); - break; - case IdentifiableCommand unknownCommand: - _onWarning?.Invoke($"Received unrecognized IdentifiableCommand of type {unknownCommand.GetType().Name}: {unknownCommand.Owner}:{unknownCommand.Id}"); - break; - default: - break; - } + throw new InvalidDataException($"Received unrecognized IdentifiableCommand of type {identifiableCommand.GetType().Name}: {identifiableCommand.Owner}:{identifiableCommand.Id}"); } } else @@ -570,7 +566,7 @@ private void CommandHandler(RendererCommand command, int messageSize) public void SendPackable(IMemoryPackable? packable) { - if (!IsConnected) return; + if (!IsConnected) throw new InvalidOperationException("Not connected!"); _onDebug?.Invoke($"Sending packable: {packable?.ToString() ?? packable?.GetType().Name ?? "NULL"}"); diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index 7cb11a0..f0c875a 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -118,7 +118,7 @@ static void TestObjectArray() arr[1] = null!; arr[2] = new TestCommand(); arr[2]!.Value = 247; - _messenger.SendObjectArray("TestObjectArray", arr!); + _messenger.SendObjectArray("TestObjectArray", arr); } static void TestVanillaStruct() @@ -170,7 +170,7 @@ static void TestNullString() _logCallback!($"NullStr: {str}"); }); - _messenger.SendString("NullStr", null!); + _messenger.SendString("NullStr", null); } static void TestEmptyCommand() @@ -355,11 +355,11 @@ static void TestObjectHashSet() _logCallback!($"TestObjectHashSet: {string.Join(",", list!)}"); }); - var set = new HashSet(); + var set = new HashSet(); set.Add(new TestCommand()); - set.Add(null!); + set.Add(null); set.Add(new TestCommand() { Value = 9 }); - _messenger.SendObjectHashSet("TestObjectHashSet", set); + _messenger.SendObjectHashSet("TestObjectHashSet", set); } static void TestStringList() @@ -369,11 +369,11 @@ static void TestStringList() _logCallback!($"TestStringList: {string.Join(",", list!.Select(s => s ?? "NULL"))}"); }); - var list = new List(); + var list = new List(); list.Add("Hello"); list.Add("World"); list.Add("owo"); - list.Add(null); + //list.Add(null); list.Add("x3"); _messenger.SendStringList("TestStringList", list); } @@ -385,12 +385,12 @@ static void TestStringArray() _logCallback!($"TestStringArray: {string.Join(",", arr!.Select(s => s ?? "NULL"))}"); }); - var arr = new string?[] + var arr = new string[] { "Hello", "World", "owo", - null, + //null, "x3" }; _messenger.SendStringArray("TestStringArray", arr); @@ -403,11 +403,11 @@ static void TestStringHashSet() _logCallback!($"TestStringHashSet: {string.Join(",", set!.Select(s => s ?? "NULL"))}"); }); - var set = new HashSet(); + var set = new HashSet(); set.Add("Hello"); set.Add("World"); set.Add("owo"); - set.Add(null); + set.Add(null!); set.Add("x3"); _messenger.SendStringHashSet("TestStringHashSet", set); } @@ -416,7 +416,7 @@ static void TestVanillaObject() { _messenger!.ReceiveObject("TestVanillaObject", (recv) => { - _logCallback!($"TestVanillaObject: {recv.sharedMemoryPrefix} {recv.uniqueSessionId} {recv.mainProcessId} {recv.debugFramePacing} {recv.outputDevice} {recv.setWindowIcon} {recv.splashScreenOverride}"); + _logCallback!($"TestVanillaObject: {recv!.sharedMemoryPrefix} {recv.uniqueSessionId} {recv.mainProcessId} {recv.debugFramePacing} {recv.outputDevice} {recv.setWindowIcon} {recv.splashScreenOverride}"); }); var obj = new RendererInitData(); @@ -427,7 +427,7 @@ static void TestUnregisteredVanillaObject() { _messenger!.ReceiveObject("TestUnregisteredVanillaObject", (recv) => { - _logCallback!($"TestUnregisteredVanillaObject: {recv.perPixelLights} {recv.shadowCascades} {recv.shadowResolution} {recv.shadowDistance} {recv.skinWeightMode}"); + _logCallback!($"TestUnregisteredVanillaObject: {recv!.perPixelLights} {recv.shadowCascades} {recv.shadowResolution} {recv.shadowDistance} {recv.skinWeightMode}"); }); var obj = new QualityConfig(); diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 6026e2b..23ca5ec 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -73,7 +73,7 @@ internal void InitializeCoreTypes() { if (_initializedCoreTypes) return; - PushNewTypes([typeof(MessengerReadyCommand), typeof(EmptyCommand), typeof(StringCommand), typeof(StringListCommand), typeof(StringArrayCommand), typeof(StringHashSetCommand), typeof(TypeCommand), typeof(PingCommand)]); + PushNewTypes([typeof(MessengerReadyCommand), typeof(EmptyCommand), typeof(StringCommand), typeof(StringCollectionCommand>), typeof(StringCollectionCommand>), typeof(StringArrayCommand), typeof(TypeCommand), typeof(PingCommand)]); foreach (var valueType in TypeManager._valueTypes) { @@ -128,7 +128,7 @@ internal bool IsValueTypeInitialized(Type t) return _registeredValueTypes.Contains(t); } - internal bool IsObjectTypeInitialized() where T : class, IMemoryPackable, new() + internal bool IsObjectTypeInitialized() where T : class?, IMemoryPackable?, new() { return _registeredObjectTypes.Contains(typeof(T)); } diff --git a/InterprocessLib.Unity/InterprocessLib.Unity.csproj b/InterprocessLib.Unity/InterprocessLib.Unity.csproj index 559faf9..ef7674e 100644 --- a/InterprocessLib.Unity/InterprocessLib.Unity.csproj +++ b/InterprocessLib.Unity/InterprocessLib.Unity.csproj @@ -11,6 +11,7 @@ InterprocessLib enable enable + Nullable true false $(ResonitePath)/ diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index bbe0d8f..9427ca7 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -1,3 +1,4 @@ +using System.Reflection; using Renderite.Shared; using Renderite.Unity; @@ -19,7 +20,7 @@ public static void Init() //Task.Run(InitLoop); InitLoop(); } - private static void InitLoop() + private static async void InitLoop() { Messenger.OnWarning = (msg) => { @@ -50,7 +51,9 @@ private static void InitLoop() } MessagingSystem? system = null; - if (fullQueueName is null) + + var engineSharedMemoryPrefix = fullQueueName?.Substring(0, fullQueueName.IndexOf('_')); + if (fullQueueName is null || engineSharedMemoryPrefix!.Length == 0) { var fallbackTask = Messenger.GetFallbackSystem(false, MessagingManager.DEFAULT_CAPACITY, PackerMemoryPool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); fallbackTask.Wait(); @@ -60,7 +63,7 @@ private static void InitLoop() } else { - var engineSharedMemoryPrefix = fullQueueName.Substring(0, fullQueueName.IndexOf('_')); + system = new MessagingSystem(false, $"InterprocessLib-{engineSharedMemoryPrefix}", MessagingManager.DEFAULT_CAPACITY, PackerMemoryPool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); system.Connect(); } @@ -68,10 +71,18 @@ private static void InitLoop() lock (Messenger.LockObj) { Messenger.PreInit(system); - Messenger.SetDefaultSystem(system); // ToDo: figure out the correct order of init steps (done?) + Messenger.SetDefaultSystem(system); system.Initialize(); } + // while (RenderingManager.Instance is null) + // await Task.Delay(1); + + // var initFinalizedField = typeof(RenderingManager).GetField("_initFinalized", BindingFlags.Instance | BindingFlags.NonPublic); + + // while ((bool)initFinalizedField.GetValue(RenderingManager.Instance) != true) + // await Task.Delay(1); + //UnityEngine.Debug.Log("DONE"); } } \ No newline at end of file diff --git a/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs b/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs index cb06646..310c415 100644 --- a/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs +++ b/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs @@ -1,4 +1,5 @@ using BepInEx; +using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using Renderite.Shared; From fc61e3b44cf5610aff5d4d569ca2e03b9bc4ca67 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sat, 20 Dec 2025 18:43:18 +0000 Subject: [PATCH 28/35] On demand type registration --- .../FrooxEngineInit.cs | 8 +- InterprocessLib.Shared/Commands.cs | 62 +++++-- InterprocessLib.Shared/Messenger.cs | 164 ++++++++++-------- InterprocessLib.Shared/System.cs | 121 +++++++------ InterprocessLib.Shared/Tests.cs | 12 +- InterprocessLib.Shared/TypeManager.cs | 67 +++---- InterprocessLib.Unity/UnityInit.cs | 13 +- .../BepInExTests.cs | 1 - .../Program.cs | 2 +- 9 files changed, 257 insertions(+), 193 deletions(-) diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index 48bbeb9..ac64c24 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -48,10 +48,10 @@ private static async void InitLoop() UniLog.Error($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Backend!\n{ex}"); }; #if DEBUG - Messenger.OnDebug = (msg) => - { - UniLog.Log($"[InterprocessLib] [DEBUG] {msg}"); - }; + Messenger.OnDebug = (msg) => + { + UniLog.Log($"[InterprocessLib] [DEBUG] {msg}"); + }; #endif MessagingSystem? system = null; diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 12763ad..5dd26d9 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -200,6 +200,46 @@ public override void Unpack(ref MemoryUnpacker unpacker) // } //} +internal sealed class TypeRegistrationCommand : IMemoryPackable +{ + public Type? Type; + + public void Pack(ref MemoryPacker packer) + { + var typeCommand = new TypeCommand(); + typeCommand.Type = Type; + typeCommand.Pack(ref packer); + } + + public void Unpack(ref MemoryUnpacker unpacker) + { + var typeCommand = new TypeCommand(); + typeCommand.Unpack(ref unpacker); + Type = typeCommand.Type; + } +} + +internal sealed class IdentifiableTypeCommand : IdentifiableCommand +{ + public Type? Type; + + public override void Pack(ref MemoryPacker packer) + { + base.Pack(ref packer); + var typeCommand = new TypeCommand(); + typeCommand.Type = Type; + typeCommand.Pack(ref packer); + } + + public override void Unpack(ref MemoryUnpacker unpacker) + { + base.Unpack(ref unpacker); + var typeCommand = new TypeCommand(); + typeCommand.Unpack(ref unpacker); + Type = typeCommand.Type; + } +} + internal sealed class TypeCommand : IMemoryPackable { public Type? Type; @@ -281,12 +321,14 @@ private void PackType(Type? type, ref MemoryPacker packer) private Type? FindType(string typeString) { + Messenger.OnDebug?.Invoke($"Looking for Type: {typeString}"); + if (_typeCache.TryGetValue(typeString, out var type)) { + Messenger.OnDebug?.Invoke($"Found in cache: {type.FullName}"); return type; } - - Messenger.OnDebug?.Invoke($"Looking for Type: {typeString}"); + type = Type.GetType(typeString); if (type is null) { @@ -298,12 +340,12 @@ private void PackType(Type? type, ref MemoryPacker packer) } if (type != null) { - Messenger.OnDebug?.Invoke($"Found Type: {type.FullName}"); + Messenger.OnDebug?.Invoke($"Found new Type to cache: {type.FullName}"); _typeCache[typeString] = type; } else { - Messenger.OnDebug?.Invoke($"Could not find type."); + Messenger.OnDebug?.Invoke($"Could not find the type."); } return type; } @@ -405,7 +447,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) internal sealed class StringCollectionCommand : CollectionCommand where C : ICollection?, new() { - public IEnumerable? Strings; + public IEnumerable? Strings; // IEnumerable is required for covariance of string? and string public override IEnumerable? UntypedCollection => Strings; public override Type StoredType => typeof(string); @@ -419,7 +461,7 @@ public override void Pack(ref MemoryPacker packer) packer.Write(-1); return; } - int len = Strings.Count(); + int len = Strings.Count(); // could be optimized? packer.Write(len); foreach (var str in Strings) { @@ -674,10 +716,10 @@ public override void Pack(ref MemoryPacker packer) } var packedType = Packable.GetType(); - packer.Write(system.TypeManager.GetTypeIndex(packedType)); + packer.Write(system.OutgoingTypeManager.GetTypeIndex(packedType)); packer.Write(QueueName); Packable.Pack(ref packer); - system.TypeManager.Return(packedType, Packable); + system.OutgoingTypeManager.Return(packedType, Packable); } public override void Unpack(ref MemoryUnpacker unpacker) @@ -696,9 +738,9 @@ public override void Unpack(ref MemoryUnpacker unpacker) if (backend is null) throw new InvalidDataException($"MessagingSystem with QueueName: {QueueName} is not registered."); - var type = backend.TypeManager.GetTypeFromIndex(TypeIndex); + var type = backend.IncomingTypeManager.GetTypeFromIndex(TypeIndex); - Packable = backend.TypeManager.Borrow(type); + Packable = backend.IncomingTypeManager.Borrow(type); Packable.Unpack(ref unpacker); } diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index db3f49d..e91ffb1 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -61,10 +61,6 @@ public class Messenger private string _ownerId; - private List? _additionalObjectTypes; - - private List? _additionalValueTypes; - private static MessagingSystem? _fallbackSystem = null; private static bool _runningFallbackSystemInit = false; @@ -145,8 +141,8 @@ public class Messenger /// 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. /// /// public Messenger(string ownerId, List? additionalObjectTypes = null, List? additionalValueTypes = null) @@ -156,9 +152,54 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< _ownerId = ownerId; - _additionalObjectTypes = additionalObjectTypes; + if (_defaultSystem is null) + { + DefaultRunPreInit(Register); + } + else + { + Register(); + } + + if (_defaultSystem is null && !DefaultInitStarted) + { + var frooxEngineInitType = Type.GetType("InterprocessLib.FrooxEngineInit"); + if (frooxEngineInitType is not null) + { + frooxEngineInitType.GetMethod("Init", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)!.Invoke(null, null); + } + else + { + var unityInitType = Type.GetType("InterprocessLib.UnityInit"); + if (unityInitType is not null) + { + unityInitType.GetMethod("Init", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)!.Invoke(null, null); + } + else + { + var fallbackSystemTask = GetFallbackSystem(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; + } + } + } + } + + /// + /// 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)); - _additionalValueTypes = additionalValueTypes; + _ownerId = ownerId; if (_defaultSystem is null) { @@ -204,11 +245,9 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< /// Custom queue name. Should match the other process. /// Custom pool for borrowing and returning memory-packable types. /// Capacity for the custom queue in bytes. - /// 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. /// /// - public Messenger(string ownerId, bool isAuthority, string queueName, IMemoryPackerEntityPool? pool = null, long queueCapacity = 1024*1024, List? additionalObjectTypes = null, List? additionalValueTypes = null) + public Messenger(string ownerId, bool isAuthority, string queueName, IMemoryPackerEntityPool? pool = null, long queueCapacity = 1024*1024) { if (ownerId is null) throw new ArgumentNullException(nameof(ownerId)); @@ -249,10 +288,6 @@ public Messenger(string ownerId, bool isAuthority, string queueName, IMemoryPack _ownerId = ownerId; - _additionalObjectTypes = additionalObjectTypes; - - _additionalValueTypes = additionalValueTypes; - Register(); if (!_customSystem.IsConnected) @@ -314,15 +349,6 @@ private void Register(MessagingSystem system) } else system.RegisterOwner(_ownerId); - - if (_additionalObjectTypes is not null) - { - system.TypeManager.InitObjectTypeList(_additionalObjectTypes.Where(t => !system.TypeManager.IsObjectTypeInitialized(t)).ToList()); - } - if (_additionalValueTypes is not null) - { - system.TypeManager.InitValueTypeList(_additionalValueTypes.Where(t => !system.TypeManager.IsValueTypeInitialized(t)).ToList()); - } } private void Register() @@ -361,8 +387,7 @@ public void SendValue(string id, T value) where T : unmanaged return; } - if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {value.GetType().Name} needs to be registered first!"); + CurrentSystem!.EnsureValueTypeInitialized(); var command = new ValueCommand(); command.Owner = _ownerId; @@ -382,8 +407,7 @@ public void SendValueList(string id, List? list) where T : unmanaged return; } - if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.EnsureValueTypeInitialized(); var command = new ValueCollectionCommand, T>(); command.Owner = _ownerId; @@ -403,8 +427,7 @@ public void SendValueHashSet(string id, HashSet? hashSet) where T : unmana return; } - if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.EnsureValueTypeInitialized(); var command = new ValueCollectionCommand, T>(); command.Owner = _ownerId; @@ -424,8 +447,7 @@ public void SendValueArray(string id, T[]? array) where T : unmanaged return; } - if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.EnsureValueTypeInitialized(); var command = new ValueArrayCommand(); command.Owner = _ownerId; @@ -534,8 +556,7 @@ public void SendEmptyCommand(string id) return; } - if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.EnsureObjectTypeInitialized(); var wrapper = new ObjectCommand(); wrapper.Object = obj; @@ -556,8 +577,7 @@ public void SendEmptyCommand(string id) return; } - if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.EnsureObjectTypeInitialized(); var command = new ObjectCollectionCommand, T>(); command.Owner = _ownerId; @@ -577,8 +597,7 @@ public void SendEmptyCommand(string id) return; } - if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.EnsureObjectTypeInitialized(); var command = new ObjectCollectionCommand, T>(); command.Owner = _ownerId; @@ -598,8 +617,7 @@ public void SendEmptyCommand(string id) return; } - if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); + CurrentSystem!.EnsureObjectTypeInitialized(); var command = new ObjectArrayCommand(); command.Owner = _ownerId; @@ -619,9 +637,6 @@ public void ReceiveValue(string id, Action? callback) where T : unmanaged return; } - if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - CurrentSystem!.RegisterValueCallback(_ownerId, id, callback); } @@ -636,9 +651,6 @@ public void ReceiveValueList(string id, Action?>? callback) where T : return; } - if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - CurrentSystem!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); } @@ -653,9 +665,6 @@ public void ReceiveValueHashSet(string id, Action?>? callback) whe return; } - if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - CurrentSystem!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); } @@ -670,9 +679,6 @@ public void ReceiveValueArray(string id, Action? callback) where T : un return; } - if (!CurrentSystem!.TypeManager.IsValueTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - CurrentSystem!.RegisterValueArrayCallback(_ownerId, id, callback); } @@ -757,9 +763,6 @@ public void ReceiveEmptyCommand(string id, Action? callback) return; } - if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - CurrentSystem!.RegisterObjectCallback(_ownerId, id, callback); } @@ -774,9 +777,6 @@ public void ReceiveEmptyCommand(string id, Action? callback) return; } - if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - CurrentSystem!.RegisterObjectCollectionCallback, T>(_ownerId, id, callback); } @@ -791,9 +791,6 @@ public void ReceiveEmptyCommand(string id, Action? callback) return; } - if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - CurrentSystem!.RegisterObjectCollectionCallback, T>(_ownerId, id, callback); } @@ -808,9 +805,6 @@ public void ReceiveEmptyCommand(string id, Action? callback) return; } - if (!CurrentSystem!.TypeManager.IsObjectTypeInitialized()) - throw new InvalidOperationException($"Type {typeof(T).Name} needs to be registered first!"); - CurrentSystem!.RegisterObjectArrayCallback(_ownerId, id, callback); } @@ -829,20 +823,36 @@ public void CheckLatency(Action callback) CurrentSystem!.SendPackable(pingCommand); } - // internal void SendTypeCommand(Type type) - // { - // if (type is null) - // throw new ArgumentNullException(nameof(type)); + public void SendType(string id, Type? type) + { + if (id is null) + throw new ArgumentNullException(nameof(id)); + + if (!IsInitialized) + { + RunPostInit(() => SendType(id, type)); + return; + } + + var typeCmd = new IdentifiableTypeCommand(); + typeCmd.Owner = _ownerId; + typeCmd.Id = id; + typeCmd.Type = type; - // if (!IsInitialized) - // { - // RunPostInit(() => SendTypeCommand(type)); - // return; - // } + CurrentSystem!.SendPackable(typeCmd); + } + + public void ReceiveType(string id, Action? callback) + { + if (id is null) + throw new ArgumentNullException(nameof(id)); - // var typeCommand = new TypeCommand(); - // typeCommand.Type = type; + if (!IsInitialized) + { + RunPostInit(() => ReceiveType(id, callback)); + return; + } - // CurrentSystem!.SendPackable(typeCommand); - // } + CurrentSystem!.RegisterTypeCallback(_ownerId, id, callback); + } } \ No newline at end of file diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 00e887e..8401566 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -19,18 +19,16 @@ private struct OwnerData public readonly Dictionary ValueCollectionCallbacks = new(); - //public readonly Dictionary?>?> StringListCallbacks = new(); - public readonly Dictionary?> StringArrayCallbacks = new(); - //public readonly Dictionary?>?> StringHashSetCallbacks = new(); - public readonly Dictionary StringCollectionCallbacks = new(); public readonly Dictionary ObjectArrayCallbacks = new(); public readonly Dictionary ObjectCollectionCallbacks = new(); + public readonly Dictionary?> TypeCallbacks = new(); + public OwnerData() { } @@ -76,7 +74,9 @@ public OwnerData() private List? _postInitActions = new(); - internal TypeManager TypeManager; + internal TypeManager OutgoingTypeManager; + + internal TypeManager IncomingTypeManager; private static readonly Dictionary _backends = new(); @@ -169,11 +169,6 @@ public void RegisterStringCallback(string owner, string id, Action? cal _ownerData[owner].StringCallbacks[id] = callback; } - // public void RegisterStringListCallback(string owner, string id, Action?>? callback) - // { - // _ownerData[owner].StringListCallbacks[id] = callback; - // } - public void RegisterStringArrayCallback(string owner, string id, Action? callback) { _ownerData[owner].StringArrayCallbacks[id] = callback; @@ -204,6 +199,11 @@ public void RegisterEmptyCallback(string owner, string id, Action? callback) _ownerData[owner].ObjectCollectionCallbacks[id] = callback; } + public void RegisterTypeCallback(string owner, string id, Action? callback) + { + _ownerData[owner].TypeCallbacks[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) { IsAuthority = isAuthority; @@ -217,9 +217,9 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I _postInitCallback = postInitCallback; - TypeManager = new(pool); + OutgoingTypeManager = new(pool, OnOutgoingTypeRegistered); - //_pool = pool; + IncomingTypeManager = new(pool, null); _primary = new MessagingManager(pool); _primary.CommandHandler = CommandHandler; @@ -310,21 +310,6 @@ private void HandleStringCommand(StringCommand command) } } - // private void HandleStringListCommand(StringListCommand command) - // { - // if (_ownerData[command.Owner].StringListCallbacks.TryGetValue(command.Id, out var callback)) - // { - // if (callback != null) - // { - // callback.Invoke((List?)command.Strings); - // } - // } - // else - // { - // _onWarning?.Invoke($"StringListCommand 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)) @@ -370,7 +355,7 @@ private void HandleEmptyCommand(EmptyCommand command) } } - private void HandleObjectCommand(ObjectCommand command) where T : class, IMemoryPackable, new() + private void HandleObjectCommand(ObjectCommand command) where T : class?, IMemoryPackable?, new() { if (_ownerData[command.Owner].ObjectCallbacks.TryGetValue(command.Id, out var callback)) { @@ -385,13 +370,13 @@ private void HandleEmptyCommand(EmptyCommand command) } } - private void HandleObjectArrayCommand(ObjectArrayCommand command) where T : class, IMemoryPackable, new() + 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); + ((Action)callback).Invoke(command.Objects); } } else @@ -400,7 +385,7 @@ private void HandleEmptyCommand(EmptyCommand command) } } - private void HandleObjectCollectionCommand(ObjectCollectionCommand command) where C : ICollection, new() where T : class, IMemoryPackable, new() + 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)) { @@ -429,11 +414,23 @@ private void HandlePingCommand(PingCommand ping) } } - private void CommandHandler(RendererCommand command, int messageSize) + private void HandleTypeCommand(IdentifiableTypeCommand command) { - //while (!IsInitialized && !IsAuthority) // I don't know if this is still needed - //Thread.Sleep(1); + if (_ownerData[command.Owner].TypeCallbacks.TryGetValue(command.Id, out var callback)) + { + if (callback != null) + { + callback.Invoke(command.Type); + } + } + else + { + _onWarning?.Invoke($"IdentifiableTypeCommand with Id \"{command.Id}\" is not registered to receive a callback!"); + } + } + private void CommandHandler(RendererCommand command, int messageSize) + { _onCommandReceived?.Invoke(command, messageSize); IMemoryPackable? packable = null; @@ -467,22 +464,19 @@ private void CommandHandler(RendererCommand command, int messageSize) // } // } - // if (packable is TypeCommand typeCommand) - // { - // _onDebug?.Invoke($"Received new type to register: {typeCommand.Type?.FullName ?? "NULL"}"); - // //if (typeCommand.Type is not null) - // //{ - // // if (typeCommand.Type.IsValueType) - // // { - // // TypeManager.InitValueTypeList([typeCommand.Type]); - // // } - // // else - // // { - // // TypeManager.InitObjectTypeList([typeCommand.Type]); - // // } - // //} - // return; - // } + if (packable is TypeRegistrationCommand typeRegCommand) + { + _onDebug?.Invoke($"Received new type to register: {typeRegCommand.Type?.FullName ?? "NULL"}"); + if (typeRegCommand.Type is not null) + { + IncomingTypeManager.InitDirectCommandType(typeRegCommand.Type); + } + else + { + throw new InvalidDataException("Tried to register a type that could not be found in this process!"); + } + return; + } if (packable is IdentifiableCommand identifiableCommand) { @@ -515,6 +509,10 @@ private void CommandHandler(RendererCommand command, int messageSize) { HandleStringArrayCommand(stringArrayCommand); } + else if (packable is IdentifiableTypeCommand identifiableTypeCommand) + { + HandleTypeCommand(identifiableTypeCommand); + } else if (packable is CollectionCommand collectionCommand) { var collectionType = collectionCommand.CollectionType; @@ -576,4 +574,27 @@ public void SendPackable(IMemoryPackable? packable) _primary!.SendCommand(wrapper); } + + internal void EnsureValueTypeInitialized() where T : unmanaged + { + if (!OutgoingTypeManager.IsValueTypeInitialized()) + { + OutgoingTypeManager.InitValueTypeList([typeof(T)]); + } + } + + internal void EnsureObjectTypeInitialized() where T : class?, IMemoryPackable?, new() + { + if (!OutgoingTypeManager.IsObjectTypeInitialized()) + { + OutgoingTypeManager.InitObjectTypeList([typeof(T)]); + } + } + + private void OnOutgoingTypeRegistered(Type type) + { + var typeRegCommand = new TypeRegistrationCommand(); + typeRegCommand.Type = type; + SendPackable(typeRegCommand); + } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index f0c875a..9c91a56 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -38,7 +38,7 @@ public static void RunTests(Messenger messenger, Action logCallback) TestStringHashSet(); TestStringArray(); - //TestTypeCommand(); + TestTypeCommand(); try { @@ -82,12 +82,10 @@ public static void RunTests(Messenger messenger, Action logCallback) } } - // static void TestTypeCommand() - // { - // _messenger!.SendTypeCommand(typeof(TestNewTypeToRegister)); - // _messenger!.SendTypeCommand(typeof(ColorProfile)); - // _messenger!.SendTypeCommand(typeof(ValueCollectionCommand, float>)); - // } + static void TestTypeCommand() + { + _messenger!.SendType("TestTypeCommand", typeof(Dictionary, float>)); + } static void TestValueArray() { diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 23ca5ec..815fd95 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -15,6 +15,8 @@ internal class TypeManager private static readonly MethodInfo _registerObjectTypeMethod = typeof(TypeManager).GetMethod(nameof(RegisterAdditionalObjectType), BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new MissingMethodException(nameof(RegisterAdditionalObjectType)); + private static readonly MethodInfo _registerDirectCommandTypeMethod = typeof(TypeManager).GetMethod(nameof(RegisterDirectCommandType), BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new MissingMethodException(nameof(RegisterDirectCommandType)); + private readonly List _newTypes = new(); private static List CurrentRendererCommandTypes => (List)typeof(PolymorphicMemoryPackableEntity).GetField("types", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)! ?? throw new MissingFieldException("types"); @@ -30,24 +32,20 @@ internal class TypeManager 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)); - private static readonly 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) - }; + private static readonly List _coreTypes = + [ + typeof(MessengerReadyCommand), + typeof(TypeRegistrationCommand), + typeof(EmptyCommand), + typeof(StringCommand), + typeof(StringCollectionCommand>), + typeof(StringCollectionCommand>), + typeof(StringArrayCommand), + typeof(TypeCommand), + typeof(PingCommand) + ]; + + private Action? _onRegisteredCallback; static TypeManager() { @@ -63,9 +61,10 @@ static TypeManager() WrapperCommand.InitNewTypes(list); } - internal TypeManager(IMemoryPackerEntityPool pool) + internal TypeManager(IMemoryPackerEntityPool pool, Action? onRegisteredCallback) { _pool = pool; + _onRegisteredCallback = onRegisteredCallback; InitializeCoreTypes(); } @@ -73,19 +72,7 @@ internal void InitializeCoreTypes() { if (_initializedCoreTypes) return; - PushNewTypes([typeof(MessengerReadyCommand), typeof(EmptyCommand), typeof(StringCommand), typeof(StringCollectionCommand>), typeof(StringCollectionCommand>), typeof(StringArrayCommand), typeof(TypeCommand), typeof(PingCommand)]); - - foreach (var valueType in TypeManager._valueTypes) - { - try - { - _registerValueTypeMethod!.MakeGenericMethod(valueType).Invoke(this, null); - } - catch (Exception ex) - { - Messenger.OnWarning?.Invoke($"Could not register additional value type {valueType.Name}!\n{ex}"); - } - } + PushNewTypes(_coreTypes); _initializedCoreTypes = true; } @@ -184,6 +171,19 @@ private void RegisterAdditionalValueType() where T : unmanaged _registeredObjectTypes.Add(type); } + internal void InitDirectCommandType(Type type) + { + Messenger.OnDebug?.Invoke($"Registering direct command type: {type.Name}"); + _registerDirectCommandTypeMethod!.MakeGenericMethod(type).Invoke(this, null); + } + + private void RegisterDirectCommandType() where T : class, IMemoryPackable, new() + { + var type = typeof(T); + + PushNewTypes([type]); + } + private IMemoryPackable? Borrow() where T : class, IMemoryPackable, new() { return _pool.Borrow(); @@ -212,6 +212,9 @@ private void PushNewTypes(List types) _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; + + if (!_coreTypes.Contains(type)) + _onRegisteredCallback?.Invoke(type); } } } \ No newline at end of file diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index 9427ca7..caa5300 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -17,10 +17,9 @@ public static void Init() Messenger.DefaultInitStarted = true; - //Task.Run(InitLoop); - InitLoop(); + InnerInit(); } - private static async void InitLoop() + private static void InnerInit() { Messenger.OnWarning = (msg) => { @@ -75,14 +74,6 @@ private static async void InitLoop() system.Initialize(); } - // while (RenderingManager.Instance is null) - // await Task.Delay(1); - - // var initFinalizedField = typeof(RenderingManager).GetField("_initFinalized", BindingFlags.Instance | BindingFlags.NonPublic); - - // while ((bool)initFinalizedField.GetValue(RenderingManager.Instance) != true) - // await Task.Delay(1); - //UnityEngine.Debug.Log("DONE"); } } \ No newline at end of file diff --git a/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs b/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs index 310c415..cb06646 100644 --- a/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs +++ b/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs @@ -1,5 +1,4 @@ using BepInEx; -using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using Renderite.Shared; diff --git a/Tests/InterprocessLib.Standalone.Tests/Program.cs b/Tests/InterprocessLib.Standalone.Tests/Program.cs index b96312a..9971275 100644 --- a/Tests/InterprocessLib.Standalone.Tests/Program.cs +++ b/Tests/InterprocessLib.Standalone.Tests/Program.cs @@ -46,7 +46,7 @@ static void Main(string[] args) Messenger.OnFailure = FailHandler; Messenger.OnDebug = DebugHandler; - var messenger = new Messenger("InterprocessLib.Tests", false, queueName!, additionalObjectTypes: [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], additionalValueTypes: [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); + var messenger = new Messenger("InterprocessLib.Tests", false, queueName!); Tests.RunTests(messenger, Console.WriteLine); From c8f32c0a031bb7693223ee0fd467ac3c41f103ec Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sat, 20 Dec 2025 18:49:46 +0000 Subject: [PATCH 29/35] Fix identifiable type command not working --- InterprocessLib.Shared/Tests.cs | 5 ++++- InterprocessLib.Shared/TypeManager.cs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index 9c91a56..d78c836 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -37,7 +37,6 @@ public static void RunTests(Messenger messenger, Action logCallback) TestObjectHashSet(); TestStringHashSet(); TestStringArray(); - TestTypeCommand(); try @@ -84,6 +83,10 @@ public static void RunTests(Messenger messenger, Action logCallback) static void TestTypeCommand() { + _messenger!.ReceiveType("TestTypeCommand", (type) => + { + _logCallback!($"TestTypeCommand: {type?.FullName ?? "NULL"}"); + }); _messenger!.SendType("TestTypeCommand", typeof(Dictionary, float>)); } diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 815fd95..35ba3fc 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -42,7 +42,8 @@ internal class TypeManager typeof(StringCollectionCommand>), typeof(StringArrayCommand), typeof(TypeCommand), - typeof(PingCommand) + typeof(PingCommand), + typeof(IdentifiableTypeCommand) ]; private Action? _onRegisteredCallback; From 3cad3fd6db27badd0db56800dac535c30e0c8659 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:06:45 +0000 Subject: [PATCH 30/35] Speed up init process and have both sides use the ready command --- .../FrooxEngineInit.cs | 26 +++-- InterprocessLib.Shared/Commands.cs | 12 +-- InterprocessLib.Shared/Messenger.cs | 42 ++------ InterprocessLib.Shared/System.cs | 59 ++++++---- InterprocessLib.Shared/Tests.cs | 102 +----------------- InterprocessLib.Shared/TypeManager.cs | 46 +++----- InterprocessLib.Unity/UnityInit.cs | 10 +- Tests/InterprocessLib.RML.Tests/RML_Tests.cs | 4 + 8 files changed, 89 insertions(+), 212 deletions(-) diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index ac64c24..06fa017 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -29,14 +29,19 @@ public static void Init() Messenger.DefaultInitStarted = true; - Task.Run(InitLoop); + InnerInit(); } - private static async void InitLoop() + private static void InnerInit() { - // Engine.SharedMemoryPrefix is assigned just before the RenderSystem is created - while (Engine.Current?.RenderSystem is null) + var args = Environment.GetCommandLineArgs(); + string? queueName = null; + for (int i = 0; i < args.Length; i++) { - await Task.Delay(1); + if (args[i].Equals("-shmprefix", StringComparison.InvariantCultureIgnoreCase)) + { + queueName = args[i + 1]; + break; + } } Messenger.OnWarning = (msg) => @@ -55,12 +60,13 @@ private static async void InitLoop() #endif MessagingSystem? system = null; - string uniqueId = Engine.Current.SharedMemoryPrefix; - if (uniqueId is null) + if (queueName is null) { Messenger.OnDebug?.Invoke("Shared memory unique id is null! Attempting to use fallback..."); - system = await Messenger.GetFallbackSystem(true, MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + var task = Messenger.GetFallbackSystem(true, MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + task.Wait(); + system = task.Result; if (system is null) { throw new EntryPointNotFoundException("Unable to get fallback messaging system!"); @@ -68,7 +74,7 @@ private static async void InitLoop() } else { - system = new MessagingSystem(true, $"InterprocessLib-{uniqueId}", MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + system = new MessagingSystem(true, $"InterprocessLib-{queueName}", MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); system.Connect(); } @@ -79,6 +85,6 @@ private static async void InitLoop() system.Initialize(); } - Engine.Current.OnShutdown += system.Dispose; // this might fix the rare occurence that Renderite.Host stays open after exiting Resonite + //Engine.Current.OnShutdown += system.Dispose; // this might fix the rare occurence that Renderite.Host stays open after exiting Resonite } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 5dd26d9..13eae39 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -71,7 +71,7 @@ public override string ToString() } } -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; @@ -703,9 +703,9 @@ public static void InitNewTypes(List types) public override void Pack(ref MemoryPacker packer) { - if (QueueName is null) throw new ArgumentNullException(nameof(QueueName)); + //if (QueueName is null) throw new ArgumentNullException(nameof(QueueName)); - var system = MessagingSystem.TryGetRegisteredSystem(QueueName); + var system = MessagingSystem.TryGetRegisteredSystem(QueueName!); if (system is null) throw new InvalidOperationException($"MessagingSystem with QueueName: {QueueName} is not registered."); @@ -717,7 +717,7 @@ public override void Pack(ref MemoryPacker packer) var packedType = Packable.GetType(); packer.Write(system.OutgoingTypeManager.GetTypeIndex(packedType)); - packer.Write(QueueName); + packer.Write(QueueName!); Packable.Pack(ref packer); system.OutgoingTypeManager.Return(packedType, Packable); } @@ -736,9 +736,9 @@ public override void Unpack(ref MemoryUnpacker unpacker) var backend = MessagingSystem.TryGetRegisteredSystem(QueueName); - if (backend is null) throw new InvalidDataException($"MessagingSystem with QueueName: {QueueName} is not registered."); + //if (backend is null) throw new InvalidDataException($"MessagingSystem with QueueName: {QueueName} is not registered."); - var type = backend.IncomingTypeManager.GetTypeFromIndex(TypeIndex); + var type = backend!.IncomingTypeManager.GetTypeFromIndex(TypeIndex); Packable = backend.IncomingTypeManager.Borrow(type); diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index e91ffb1..f00aa09 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -70,7 +70,7 @@ public class Messenger internal static async Task GetFallbackSystem(bool isAuthority, long queueCapacity, IMemoryPackerEntityPool? pool = null, RenderCommandHandler? commandHandler = null, Action? failhandler = null, Action? warnHandler = null, Action? debugHandler = null, Action? postInitCallback = null) { var startTime = DateTime.UtcNow; - int waitTimeMs = 2500; + int waitTimeMs = 500; while (_runningFallbackSystemInit && (DateTime.UtcNow - startTime).TotalMilliseconds < waitTimeMs * 2) await Task.Delay(1); @@ -152,40 +152,7 @@ public Messenger(string ownerId, List? additionalObjectTypes = null, List< _ownerId = ownerId; - if (_defaultSystem is null) - { - DefaultRunPreInit(Register); - } - else - { - Register(); - } - - if (_defaultSystem is null && !DefaultInitStarted) - { - var frooxEngineInitType = Type.GetType("InterprocessLib.FrooxEngineInit"); - if (frooxEngineInitType is not null) - { - frooxEngineInitType.GetMethod("Init", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)!.Invoke(null, null); - } - else - { - var unityInitType = Type.GetType("InterprocessLib.UnityInit"); - if (unityInitType is not null) - { - unityInitType.GetMethod("Init", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)!.Invoke(null, null); - } - else - { - var fallbackSystemTask = GetFallbackSystem(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; - } - } - } + DefaultInit(); } /// @@ -201,6 +168,11 @@ public Messenger(string ownerId) _ownerId = ownerId; + DefaultInit(); + } + + private void DefaultInit() + { if (_defaultSystem is null) { DefaultRunPreInit(Register); diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 8401566..f0e428c 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -82,6 +82,10 @@ public OwnerData() internal Action? PingCallback; + private readonly IMemoryPackerEntityPool _pool; + + private bool _messengerReadyCommandReceived = false; + internal void SetPostInitActions(List? actions) { if (IsInitialized) @@ -104,10 +108,7 @@ public void Initialize() if (IsInitialized) throw new InvalidOperationException("Already initialized!"); - if (!IsAuthority) - { - SendPackable(new MessengerReadyCommand()); - } + SendPackable(new MessengerReadyCommand()); var actions = _postInitActions!.ToArray(); _postInitActions = null; @@ -206,6 +207,9 @@ public void RegisterTypeCallback(string owner, string id, Action? callbac 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; @@ -217,9 +221,10 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I _postInitCallback = postInitCallback; - OutgoingTypeManager = new(pool, OnOutgoingTypeRegistered); + _pool = pool; - IncomingTypeManager = new(pool, null); + OutgoingTypeManager = new(_pool, OnOutgoingTypeRegistered); + IncomingTypeManager = new(_pool, null); _primary = new MessagingManager(pool); _primary.CommandHandler = CommandHandler; @@ -265,7 +270,7 @@ private void HandleValueCommand(ValueCommand command) where T : unmanaged } } - private void HandleValueCollectionCommand(ValueCollectionCommand command) where C : ICollection, new() where T : unmanaged + private void HandleValueCollectionCommand(ValueCollectionCommand command) where C : ICollection?, new() where T : unmanaged { if (_ownerData[command.Owner].ValueCollectionCallbacks.TryGetValue(command.Id, out var callback)) { @@ -445,24 +450,32 @@ private void CommandHandler(RendererCommand command, int messageSize) return; } + // ping command before ready command is okay (to check if the queue is active)b if (packable is PingCommand pingCommand) { HandlePingCommand(pingCommand); return; } - // if (!IsInitialized && IsAuthority) - // { - // if (packable is MessengerReadyCommand) - // { - // Initialize(); - // return; - // } - // else - // { - // throw new InvalidDataException($"The first command needs to be the MessengerReadyCommand when not initialized!"); - // } - // } + // it's a bit unexpected if the non-authority process receives more than one of these, but it might be okay? + 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) { @@ -473,7 +486,7 @@ private void CommandHandler(RendererCommand command, int messageSize) } else { - throw new InvalidDataException("Tried to register a type that could not be found in this process!"); + throw new InvalidDataException("Other process tried to register a type that could not be found in this process!"); } return; } @@ -562,7 +575,7 @@ private void CommandHandler(RendererCommand command, int messageSize) } } - public void SendPackable(IMemoryPackable? packable) + public void SendPackable(IMemoryPackable packable) { if (!IsConnected) throw new InvalidOperationException("Not connected!"); @@ -579,7 +592,7 @@ internal void EnsureValueTypeInitialized() where T : unmanaged { if (!OutgoingTypeManager.IsValueTypeInitialized()) { - OutgoingTypeManager.InitValueTypeList([typeof(T)]); + OutgoingTypeManager.RegisterAdditionalValueType(); } } @@ -587,7 +600,7 @@ internal void EnsureValueTypeInitialized() where T : unmanaged { if (!OutgoingTypeManager.IsObjectTypeInitialized()) { - OutgoingTypeManager.InitObjectTypeList([typeof(T)]); + OutgoingTypeManager.RegisterAdditionalObjectType(); } } diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index d78c836..7a37e8b 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -8,9 +8,9 @@ namespace InterprocessLib.Tests; public static class Tests { private static Messenger? _messenger; - private static Action? _logCallback; + private static Action? _logCallback; - public static void RunTests(Messenger messenger, Action logCallback) + public static void RunTests(Messenger messenger, Action logCallback) { _messenger = messenger; _logCallback = logCallback; @@ -38,47 +38,6 @@ public static void RunTests(Messenger messenger, Action logCallback) TestStringHashSet(); 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) - { - logCallback($"TestUnregisteredVanillaObject threw an exception: {ex.Message}"); - } - try - { - TestUnregisteredVanillaValue(); - } - catch (Exception ex) - { - logCallback($"TestUnregisteredVanillaValue threw an exception: {ex.Message}"); - } } static void TestTypeCommand() @@ -119,7 +78,7 @@ static void TestObjectArray() arr[1] = null!; arr[2] = new TestCommand(); arr[2]!.Value = 247; - _messenger.SendObjectArray("TestObjectArray", arr); + _messenger.SendObjectArray("TestObjectArray", arr); } static void TestVanillaStruct() @@ -148,17 +107,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 TestUnknownCommandId() { _messenger!.SendEmptyCommand("UnknownIdTest"); @@ -274,39 +222,6 @@ 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) => @@ -424,17 +339,6 @@ static void TestVanillaObject() _messenger.SendObject("TestVanillaObject", obj); } - static void TestUnregisteredVanillaObject() - { - _messenger!.ReceiveObject("TestUnregisteredVanillaObject", (recv) => - { - _logCallback!($"TestUnregisteredVanillaObject: {recv!.perPixelLights} {recv.shadowCascades} {recv.shadowResolution} {recv.shadowDistance} {recv.skinWeightMode}"); - }); - - var obj = new QualityConfig(); - _messenger.SendObject("TestUnregisteredVanillaObject", obj); - } - #if TEST_COMPILATION //Won't compile static void TestInvalidType() diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 35ba3fc..3b48bee 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -11,10 +11,6 @@ internal class TypeManager private bool _initializedCoreTypes = false; - private static readonly MethodInfo _registerValueTypeMethod = typeof(TypeManager).GetMethod(nameof(RegisterAdditionalValueType), BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new MissingMethodException(nameof(RegisterAdditionalValueType)); - - private static readonly MethodInfo _registerObjectTypeMethod = typeof(TypeManager).GetMethod(nameof(RegisterAdditionalObjectType), BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new MissingMethodException(nameof(RegisterAdditionalObjectType)); - private static readonly MethodInfo _registerDirectCommandTypeMethod = typeof(TypeManager).GetMethod(nameof(RegisterDirectCommandType), BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new MissingMethodException(nameof(RegisterDirectCommandType)); private readonly List _newTypes = new(); @@ -88,48 +84,27 @@ internal int GetTypeIndex(Type type) return _typeToIndex[type]; } - internal void InitValueTypeList(List types) - { - Messenger.OnDebug?.Invoke($"Registering additional value types: {string.Join(",", types.Select(t => t.Name))}"); - foreach (var type in types) - { - _registerValueTypeMethod!.MakeGenericMethod(type).Invoke(this, null); - } - } - - internal void InitObjectTypeList(List types) - { - Messenger.OnDebug?.Invoke($"Registering additional object types: {string.Join(",", types.Select(t => t.Name))}"); - foreach (var type in types) - { - _registerObjectTypeMethod!.MakeGenericMethod(type).Invoke(this, null); - } - } - internal bool IsValueTypeInitialized() where T : unmanaged { return _registeredValueTypes.Contains(typeof(T)); } - internal bool IsValueTypeInitialized(Type t) - { - return _registeredValueTypes.Contains(t); - } - internal bool IsObjectTypeInitialized() where T : class?, IMemoryPackable?, new() { return _registeredObjectTypes.Contains(typeof(T)); } - internal bool IsObjectTypeInitialized(Type t) - { - return _registeredObjectTypes.Contains(t); - } + // internal bool IsDirectCommandTypeInitialized() where T : class?, IMemoryPackable?, new() + // { + // return _typeToIndex.ContainsKey(typeof(T)); + // } - private void RegisterAdditionalValueType() where T : unmanaged + internal void RegisterAdditionalValueType() where T : unmanaged { var type = typeof(T); + Messenger.OnDebug?.Invoke($"Registering additional value type: {type.Name}"); + if (_registeredValueTypes.Contains(type)) throw new InvalidOperationException($"Type {type.Name} is already registered!"); @@ -149,10 +124,12 @@ private void RegisterAdditionalValueType() where T : unmanaged _registeredValueTypes.Add(type); } - private void RegisterAdditionalObjectType() where T : class, IMemoryPackable, new() + internal void RegisterAdditionalObjectType() where T : class?, IMemoryPackable?, new() { var type = typeof(T); + Messenger.OnDebug?.Invoke($"Registering additional object type: {type.Name}"); + if (_registeredObjectTypes.Contains(type)) throw new InvalidOperationException($"Type {type.Name} is already registered!"); @@ -174,7 +151,6 @@ private void RegisterAdditionalValueType() where T : unmanaged internal void InitDirectCommandType(Type type) { - Messenger.OnDebug?.Invoke($"Registering direct command type: {type.Name}"); _registerDirectCommandTypeMethod!.MakeGenericMethod(type).Invoke(this, null); } @@ -182,6 +158,8 @@ internal void InitDirectCommandType(Type type) { var type = typeof(T); + Messenger.OnDebug?.Invoke($"Registering direct command type: {type.Name}"); + PushNewTypes([type]); } diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index caa5300..1e43190 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -30,10 +30,10 @@ private static void InnerInit() UnityEngine.Debug.LogError($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Host!\n{ex}"); }; #if DEBUG - Messenger.OnDebug = (msg) => - { - UnityEngine.Debug.Log($"[InterprocessLib] [DEBUG] {msg}"); - }; + Messenger.OnDebug = (msg) => + { + UnityEngine.Debug.Log($"[InterprocessLib] [DEBUG] {msg}"); + }; #endif //UnityEngine.Debug.Log("Init"); @@ -42,7 +42,7 @@ private static void InnerInit() string? fullQueueName = null; for (int i = 0; i < args.Length; i++) { - if (args[i].EndsWith("QueueName", StringComparison.InvariantCultureIgnoreCase)) + if (args[i].Equals("-QueueName", StringComparison.InvariantCultureIgnoreCase)) { fullQueueName = args[i + 1]; break; diff --git a/Tests/InterprocessLib.RML.Tests/RML_Tests.cs b/Tests/InterprocessLib.RML.Tests/RML_Tests.cs index dd52472..b584f41 100644 --- a/Tests/InterprocessLib.RML.Tests/RML_Tests.cs +++ b/Tests/InterprocessLib.RML.Tests/RML_Tests.cs @@ -36,9 +36,11 @@ public override void OnEngineInit() _another = new("InterprocessLib.Tests.Another", [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); _unknownMessenger = new Messenger("InterprocessLib.Tests.UnknownMessengerFrooxEngine"); +#pragma warning disable CS8622 Tests.RunTests(_messenger, Msg); Tests.RunTests(_unknownMessenger, Msg); Tests.RunTests(_another, Msg); +#pragma warning restore CS8622 _messenger.CheckLatency(latency => LatencyMilliseconds!.Value = latency.TotalMilliseconds); @@ -47,9 +49,11 @@ public override void OnEngineInit() RunTestsToggle!.OnChanged += (object? newValue) => { _messenger!.SendEmptyCommand("RunTests"); +#pragma warning disable CS8622 Tests.RunTests(_messenger, Msg); Tests.RunTests(_unknownMessenger, Msg); Tests.RunTests(_another, Msg); +#pragma warning restore CS8622 _messenger.CheckLatency(latency => LatencyMilliseconds!.Value = latency.TotalMilliseconds); }; CheckSyncToggle!.OnChanged += (object? newValue) => From d7609913fdc972433495a994ec54609aebe0435b Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Sun, 21 Dec 2025 17:01:34 +0000 Subject: [PATCH 31/35] Some small adjustments/improvements --- InterprocessLib.Shared/Commands.cs | 164 ++---------------- InterprocessLib.Shared/Messenger.cs | 13 +- InterprocessLib.Shared/System.cs | 30 ++-- InterprocessLib.Shared/Tests.cs | 54 +----- InterprocessLib.Shared/TypeManager.cs | 1 - .../BepInExTests.cs | 10 +- .../BepisLoaderTests.cs | 10 +- Tests/InterprocessLib.RML.Tests/RML_Tests.cs | 16 +- 8 files changed, 68 insertions(+), 230 deletions(-) diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 13eae39..9c39145 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -8,19 +8,19 @@ namespace InterprocessLib; internal abstract class IdentifiableCommand : IMemoryPackable { - public string Owner = ""; - public string Id = ""; + public string? Owner; + public string? Id; public virtual void Pack(ref MemoryPacker packer) { - packer.Write(Owner); - packer.Write(Id); + packer.Write(Owner!); + packer.Write(Id!); } 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() @@ -153,105 +153,28 @@ public override void Unpack(ref MemoryUnpacker unpacker) } } -//internal sealed class ValueDictionaryCommand : CollectionCommand where TKey : unmanaged where TValue : unmanaged -//{ -// public Dictionary? Dict; - -// public override IEnumerable? UntypedCollection => Dict; - -// public override Type InnerDataType => typeof(KeyValuePair); - -// public override Type CollectionType => typeof(Dictionary); - -// public override void Pack(ref MemoryPacker packer) -// { -// base.Pack(ref packer); -// var len = Dict?.Count ?? -1; -// packer.Write(len); -// if (Dict != null) -// { -// foreach (var kvp in Dict) -// { -// packer.Write(kvp.Key); -// packer.Write(kvp.Value); -// } -// } -// } - -// public override void Unpack(ref MemoryUnpacker unpacker) -// { -// base.Unpack(ref unpacker); -// int len = 0; -// unpacker.Read(ref len); -// if (len == -1) -// { -// Dict = null; -// return; -// } -// Dict = new(); // ToDo: use pool borrowing here? -// for (int i = 0; i < len; i++) -// { -// TKey key = default; -// unpacker.Read(ref key); -// TValue val = default; -// unpacker.Read(ref val); -// Dict[key] = val; -// } -// } -//} - -internal sealed class TypeRegistrationCommand : IMemoryPackable +internal sealed class TypeRegistrationCommand : TypeCommand { - public Type? Type; - - public void Pack(ref MemoryPacker packer) - { - var typeCommand = new TypeCommand(); - typeCommand.Type = Type; - typeCommand.Pack(ref packer); - } - - public void Unpack(ref MemoryUnpacker unpacker) + public override string ToString() { - var typeCommand = new TypeCommand(); - typeCommand.Unpack(ref unpacker); - Type = typeCommand.Type; + return "TypeRegistrationCommand: " + Type?.FullName ?? "NULL"; } } -internal sealed class IdentifiableTypeCommand : IdentifiableCommand +internal class TypeCommand : IdentifiableCommand { public Type? Type; + private static Dictionary _typeCache = new(); public override void Pack(ref MemoryPacker packer) { base.Pack(ref packer); - var typeCommand = new TypeCommand(); - typeCommand.Type = Type; - typeCommand.Pack(ref packer); + PackType(Type, ref packer); } public override void Unpack(ref MemoryUnpacker unpacker) { base.Unpack(ref unpacker); - var typeCommand = new TypeCommand(); - typeCommand.Unpack(ref unpacker); - Type = typeCommand.Type; - } -} - -internal sealed class TypeCommand : IMemoryPackable -{ - public Type? Type; - private static Dictionary _typeCache = new(); - - public void Pack(ref MemoryPacker packer) - { - PackType(Type, ref packer); - } - - public void Unpack(ref MemoryUnpacker unpacker) - { Type = UnpackType(ref unpacker); } @@ -321,11 +244,9 @@ private void PackType(Type? type, ref MemoryPacker packer) private Type? FindType(string typeString) { - Messenger.OnDebug?.Invoke($"Looking for Type: {typeString}"); - if (_typeCache.TryGetValue(typeString, out var type)) { - Messenger.OnDebug?.Invoke($"Found in cache: {type.FullName}"); + Messenger.OnDebug?.Invoke($"Found Type in cache: {type.FullName}"); return type; } @@ -340,69 +261,22 @@ private void PackType(Type? type, ref MemoryPacker packer) } if (type != null) { - Messenger.OnDebug?.Invoke($"Found new Type to cache: {type.FullName}"); + Messenger.OnDebug?.Invoke($"Found Type to add to cache: {type.FullName}"); _typeCache[typeString] = type; } else { - Messenger.OnDebug?.Invoke($"Could not find the type."); + Messenger.OnWarning?.Invoke($"Could not find the Type: {typeString}"); } return type; } public override string ToString() { - return "TypeCommand: " + Type?.FullName ?? "NULL"; + return $"TypeCommand: {Type?.FullName ?? "NULL"}:{Owner}:{Id}"; } } -//internal sealed class ObjectDictionaryCommand : CollectionCommand where TKey : class, IMemoryPackable, new() where TValue : class, IMemoryPackable, new() -//{ -// public Dictionary? Dict; - -// public override IEnumerable? UntypedCollection => Dict; - -// public override Type InnerDataType => typeof(KeyValuePair); - -// public override Type CollectionType => typeof(Dictionary); - -// public override void Pack(ref MemoryPacker packer) -// { -// base.Pack(ref packer); -// var len = Dict?.Count ?? -1; -// packer.Write(len); -// if (Dict != null) -// { -// foreach (var kvp in Dict) -// { -// packer.WriteObject(kvp.Key); -// packer.WriteObject(kvp.Value); -// } -// } -// } - -// public override void Unpack(ref MemoryUnpacker unpacker) -// { -// base.Unpack(ref unpacker); -// int len = 0; -// unpacker.Read(ref len); -// if (len == -1) -// { -// Dict = null; -// return; -// } -// Dict = new(); // ToDo: use pool borrowing here? -// for (int i = 0; i < len; i++) -// { -// TKey key = default; -// unpacker.ReadObject(ref key); -// TValue val = default; -// unpacker.ReadObject(ref val); -// Dict[key] = val; -// } -// } -//} - internal sealed class StringArrayCommand : CollectionCommand { public string?[]? Strings; @@ -461,7 +335,7 @@ public override void Pack(ref MemoryPacker packer) packer.Write(-1); return; } - int len = Strings.Count(); // could be optimized? + int len = ((ICollection)Strings).Count; packer.Write(len); foreach (var str in Strings) { @@ -703,7 +577,7 @@ public static void InitNewTypes(List types) public override void Pack(ref MemoryPacker packer) { - //if (QueueName is null) throw new ArgumentNullException(nameof(QueueName)); + if (QueueName is null) throw new ArgumentNullException(nameof(QueueName)); var system = MessagingSystem.TryGetRegisteredSystem(QueueName!); @@ -736,7 +610,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) var backend = MessagingSystem.TryGetRegisteredSystem(QueueName); - //if (backend is null) throw new InvalidDataException($"MessagingSystem with QueueName: {QueueName} is not registered."); + if (backend is null) throw new InvalidDataException($"MessagingSystem with QueueName: {QueueName} is not registered."); var type = backend!.IncomingTypeManager.GetTypeFromIndex(TypeIndex); diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index f00aa09..694f7f6 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -145,6 +145,7 @@ public class Messenger /// Unused parameter kept for backwards compatibility. /// /// + [Obsolete("Use the other constructors that don't take Type lists")] public Messenger(string ownerId, List? additionalObjectTypes = null, List? additionalValueTypes = null) { if (ownerId is null) @@ -530,12 +531,12 @@ public void SendEmptyCommand(string id) 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; - CurrentSystem!.SendPackable(wrapper); + CurrentSystem!.SendPackable(command); } public void SendObjectList(string id, List? list) where T : class?, IMemoryPackable?, new() @@ -806,7 +807,7 @@ public void SendType(string id, Type? type) return; } - var typeCmd = new IdentifiableTypeCommand(); + var typeCmd = new TypeCommand(); typeCmd.Owner = _ownerId; typeCmd.Id = id; typeCmd.Type = type; diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index f0e428c..dee7207 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -257,7 +257,7 @@ public void Dispose() private void HandleValueCommand(ValueCommand command) where T : unmanaged { - if (_ownerData[command.Owner].ValueCallbacks.TryGetValue(command.Id, out var callback)) + if (_ownerData[command.Owner!].ValueCallbacks.TryGetValue(command.Id!, out var callback)) { if (callback != null) { @@ -272,7 +272,7 @@ private void HandleValueCommand(ValueCommand command) where T : unmanaged private void HandleValueCollectionCommand(ValueCollectionCommand command) where C : ICollection?, new() where T : unmanaged { - if (_ownerData[command.Owner].ValueCollectionCallbacks.TryGetValue(command.Id, out var callback)) + if (_ownerData[command.Owner!].ValueCollectionCallbacks.TryGetValue(command.Id!, out var callback)) { if (callback != null) { @@ -287,7 +287,7 @@ private void HandleValueCommand(ValueCommand command) where T : unmanaged private void HandleValueArrayCommand(ValueArrayCommand command) where T : unmanaged { - if (_ownerData[command.Owner].ValueArrayCallbacks.TryGetValue(command.Id, out var callback)) + if (_ownerData[command.Owner!].ValueArrayCallbacks.TryGetValue(command.Id!, out var callback)) { if (callback != null) { @@ -302,7 +302,7 @@ private void HandleValueArrayCommand(ValueArrayCommand command) where T : private void HandleStringCommand(StringCommand command) { - if (_ownerData[command.Owner].StringCallbacks.TryGetValue(command.Id, out var callback)) + if (_ownerData[command.Owner!].StringCallbacks.TryGetValue(command.Id!, out var callback)) { if (callback != null) { @@ -317,7 +317,7 @@ private void HandleStringCommand(StringCommand command) private void HandleStringArrayCommand(StringArrayCommand command) { - if (_ownerData[command.Owner].StringArrayCallbacks.TryGetValue(command.Id, out var callback)) + if (_ownerData[command.Owner!].StringArrayCallbacks.TryGetValue(command.Id!, out var callback)) { if (callback != null) { @@ -332,7 +332,7 @@ private void HandleStringArrayCommand(StringArrayCommand command) private void HandleStringCollectionCommand(StringCollectionCommand command) where C : ICollection?, new() { - if (_ownerData[command.Owner].StringCollectionCallbacks.TryGetValue(command.Id, out var callback)) + if (_ownerData[command.Owner!].StringCollectionCallbacks.TryGetValue(command.Id!, out var callback)) { if (callback != null) { @@ -347,7 +347,7 @@ private void HandleStringArrayCommand(StringArrayCommand command) private void HandleEmptyCommand(EmptyCommand command) { - if (_ownerData[command.Owner].EmptyCallbacks.TryGetValue(command.Id, out var callback)) + if (_ownerData[command.Owner!].EmptyCallbacks.TryGetValue(command.Id!, out var callback)) { if (callback != null) { @@ -362,7 +362,7 @@ private void HandleEmptyCommand(EmptyCommand command) private void HandleObjectCommand(ObjectCommand command) where T : class?, IMemoryPackable?, new() { - if (_ownerData[command.Owner].ObjectCallbacks.TryGetValue(command.Id, out var callback)) + if (_ownerData[command.Owner!].ObjectCallbacks.TryGetValue(command.Id!, out var callback)) { if (callback != null) { @@ -377,7 +377,7 @@ private void HandleEmptyCommand(EmptyCommand command) private void HandleObjectArrayCommand(ObjectArrayCommand command) where T : class?, IMemoryPackable?, new() { - if (_ownerData[command.Owner].ObjectArrayCallbacks.TryGetValue(command.Id, out var callback)) + if (_ownerData[command.Owner!].ObjectArrayCallbacks.TryGetValue(command.Id!, out var callback)) { if (callback != null) { @@ -392,7 +392,7 @@ private void HandleEmptyCommand(EmptyCommand command) 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 (_ownerData[command.Owner!].ObjectCollectionCallbacks.TryGetValue(command.Id!, out var callback)) { if (callback != null) { @@ -419,9 +419,9 @@ private void HandlePingCommand(PingCommand ping) } } - private void HandleTypeCommand(IdentifiableTypeCommand command) + private void HandleTypeCommand(TypeCommand command) { - if (_ownerData[command.Owner].TypeCallbacks.TryGetValue(command.Id, out var callback)) + if (_ownerData[command.Owner!].TypeCallbacks.TryGetValue(command.Id!, out var callback)) { if (callback != null) { @@ -493,6 +493,8 @@ private void CommandHandler(RendererCommand command, int messageSize) 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!"); @@ -522,9 +524,9 @@ private void CommandHandler(RendererCommand command, int messageSize) { HandleStringArrayCommand(stringArrayCommand); } - else if (packable is IdentifiableTypeCommand identifiableTypeCommand) + else if (packable is TypeCommand typeCommand) { - HandleTypeCommand(identifiableTypeCommand); + HandleTypeCommand(typeCommand); } else if (packable is CollectionCommand collectionCommand) { diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index 7a37e8b..36e2efb 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -70,12 +70,12 @@ static void TestObjectArray() { _logCallback!($"TestObjectArray: {string.Join(",", arr!)}"); }); - var arr = new TestCommand[3]; + 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[1] = null; arr[2] = new TestCommand(); arr[2]!.Value = 247; _messenger.SendObjectArray("TestObjectArray", arr); @@ -285,11 +285,11 @@ static void TestStringList() _logCallback!($"TestStringList: {string.Join(",", list!.Select(s => s ?? "NULL"))}"); }); - var list = new List(); + var list = new List(); list.Add("Hello"); list.Add("World"); list.Add("owo"); - //list.Add(null); + list.Add(null); list.Add("x3"); _messenger.SendStringList("TestStringList", list); } @@ -301,12 +301,12 @@ static void TestStringArray() _logCallback!($"TestStringArray: {string.Join(",", arr!.Select(s => s ?? "NULL"))}"); }); - var arr = new string[] + var arr = new string?[] { "Hello", "World", "owo", - //null, + null, "x3" }; _messenger.SendStringArray("TestStringArray", arr); @@ -319,11 +319,11 @@ static void TestStringHashSet() _logCallback!($"TestStringHashSet: {string.Join(",", set!.Select(s => s ?? "NULL"))}"); }); - var set = new HashSet(); + var set = new HashSet(); set.Add("Hello"); set.Add("World"); set.Add("owo"); - set.Add(null!); + set.Add(null); set.Add("x3"); _messenger.SendStringHashSet("TestStringHashSet", set); } @@ -453,42 +453,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; -} - -public class TestNewTypeToRegister : IMemoryPackable -{ - public void Pack(ref MemoryPacker packer) - { - } - - public void Unpack(ref MemoryUnpacker unpacker) - { - } } \ No newline at end of file diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 3b48bee..44b0b40 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -39,7 +39,6 @@ internal class TypeManager typeof(StringArrayCommand), typeof(TypeCommand), typeof(PingCommand), - typeof(IdentifiableTypeCommand) ]; private Action? _onRegisteredCallback; diff --git a/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs b/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs index cb06646..cd5cee5 100644 --- a/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs +++ b/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs @@ -11,14 +11,14 @@ public class UnityPlugin : BaseUnityPlugin public static ManualLogSource? Log; private static Messenger? _messenger; private static Messenger? _unknownMessenger; - private static Messenger? _anotherOne; + private static Messenger? _testObsoleteConstructor; 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"); + _testObsoleteConstructor = new("InterprocessLib.Tests.ObsoleteConstructor", [], []); _unknownMessenger = new("InterprocessLib.Tests.UnknownMessengerUnity"); SyncTest = Config.Bind("General", "SyncTest", 34); _messenger.SyncConfigEntry(SyncTest); @@ -26,7 +26,7 @@ void Awake() { Tests.RunTests(_messenger, Log!.LogInfo); Tests.RunTests(_unknownMessenger, Log!.LogInfo); - Tests.RunTests(_anotherOne, Log!.LogInfo); + Tests.RunTests(_testObsoleteConstructor, Log!.LogInfo); }); _messenger.ReceiveEmptyCommand("CheckSync", () => { @@ -38,6 +38,6 @@ void Awake() }); Tests.RunTests(_messenger, Log!.LogInfo); Tests.RunTests(_unknownMessenger, Log!.LogInfo); - Tests.RunTests(_anotherOne, Log!.LogInfo); + Tests.RunTests(_testObsoleteConstructor, Log!.LogInfo); } } \ No newline at end of file diff --git a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs index 4cca441..73ae31d 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs +++ b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs @@ -19,7 +19,7 @@ public class Plugin : BasePlugin public static ConfigEntry? RunTestsToggle; public static Messenger? _messenger; public static Messenger? _unknownMessenger; - public static Messenger? _another; + public static Messenger? _testObsoleteConstructor; public static ConfigEntry? SyncTest; public static ConfigEntry? CheckSyncToggle; @@ -102,13 +102,13 @@ public override void Load() } }; - _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"); + _testObsoleteConstructor = new("InterprocessLib.Tests.ObsoleteConstructor", [], []); _unknownMessenger = new Messenger("InterprocessLib.Tests.UnknownMessengerFrooxEngine"); Tests.RunTests(_messenger, Log!.LogInfo); Tests.RunTests(_unknownMessenger, Log!.LogInfo); - Tests.RunTests(_another, Log!.LogInfo); + Tests.RunTests(_testObsoleteConstructor, Log!.LogInfo); #if TEST_SPAWN_PROCESS SpawnProcess(); @@ -138,7 +138,7 @@ public override void Load() _messenger!.SendEmptyCommand("RunTests"); Tests.RunTests(_messenger, Log!.LogInfo); Tests.RunTests(_unknownMessenger, Log!.LogInfo); - Tests.RunTests(_another, Log!.LogInfo); + Tests.RunTests(_testObsoleteConstructor, Log!.LogInfo); _messenger.CheckLatency(latency => { LatencyMilliseconds.Value = latency.TotalMilliseconds; diff --git a/Tests/InterprocessLib.RML.Tests/RML_Tests.cs b/Tests/InterprocessLib.RML.Tests/RML_Tests.cs index b584f41..4ece7df 100644 --- a/Tests/InterprocessLib.RML.Tests/RML_Tests.cs +++ b/Tests/InterprocessLib.RML.Tests/RML_Tests.cs @@ -24,25 +24,25 @@ public class RML_Tests : ResoniteMod [AutoRegisterConfigKey] private static ModConfigurationKey ResetToggle = new ModConfigurationKey("ResetToggle", "ResetToggle:", () => false); [AutoRegisterConfigKey] - private static ModConfigurationKey LatencyMilliseconds = new ModConfigurationKey("LatencyMilliseconds", "LatencyMilliseconds:", () => -1.0); + private static ModConfigurationKey RoundTripLatencyMilliseconds = new ModConfigurationKey("RoundTripLatencyMilliseconds", "RoundTripLatencyMilliseconds:", () => -1.0); public static Messenger? _messenger; public static Messenger? _unknownMessenger; - public static Messenger? _another; + public static Messenger? _testObsoleteConstructor; 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"); + _testObsoleteConstructor = new("InterprocessLib.Tests.ObsoleteConstructor", [], []); _unknownMessenger = new Messenger("InterprocessLib.Tests.UnknownMessengerFrooxEngine"); #pragma warning disable CS8622 Tests.RunTests(_messenger, Msg); Tests.RunTests(_unknownMessenger, Msg); - Tests.RunTests(_another, Msg); + Tests.RunTests(_testObsoleteConstructor, Msg); #pragma warning restore CS8622 - _messenger.CheckLatency(latency => LatencyMilliseconds!.Value = latency.TotalMilliseconds); + _messenger.CheckLatency(latency => RoundTripLatencyMilliseconds!.Value = latency.TotalMilliseconds); _messenger.SyncConfigEntry(SyncTest); @@ -52,9 +52,9 @@ public override void OnEngineInit() #pragma warning disable CS8622 Tests.RunTests(_messenger, Msg); Tests.RunTests(_unknownMessenger, Msg); - Tests.RunTests(_another, Msg); + Tests.RunTests(_testObsoleteConstructor, Msg); #pragma warning restore CS8622 - _messenger.CheckLatency(latency => LatencyMilliseconds!.Value = latency.TotalMilliseconds); + _messenger.CheckLatency(latency => RoundTripLatencyMilliseconds!.Value = latency.TotalMilliseconds); }; CheckSyncToggle!.OnChanged += (object? newValue) => { From 41e5419b9e0fc39e136d52c1be7126f77faaad54 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Mon, 22 Dec 2025 00:11:07 +0000 Subject: [PATCH 32/35] Further improvements, make Messenger disposable, introduce new Standalone project --- Extensions/BepInExShared.cs | 4 +- .../RML_Extensions.cs | 4 +- .../FrooxEngineInit.cs | 2 +- InterprocessLib.Shared/Commands.cs | 14 ++-- InterprocessLib.Shared/Messenger.cs | 38 ++++++---- InterprocessLib.Shared/System.cs | 63 +++++++++++++--- InterprocessLib.Shared/Tests.cs | 4 +- .../InterprocessLib.Standalone.csproj | 48 +++++++++++++ .../Properties/launchSettings.json | 10 +++ InterprocessLib.Unity/UnityInit.cs | 2 +- InterprocessLib.sln | 37 ++++------ .../BepInExTests.cs | 16 ++++- .../BepisLoaderTests.cs | 72 ++++++++++--------- Tests/InterprocessLib.RML.Tests/RML_Tests.cs | 37 +++++++--- .../InterprocessLib.Standalone.Tests.csproj | 11 ++- .../Program.cs | 20 +++++- 16 files changed, 267 insertions(+), 115 deletions(-) create mode 100644 InterprocessLib.Standalone/InterprocessLib.Standalone.csproj create mode 100644 InterprocessLib.Standalone/Properties/launchSettings.json diff --git a/Extensions/BepInExShared.cs b/Extensions/BepInExShared.cs index c332f0d..9afd7bd 100644 --- a/Extensions/BepInExShared.cs +++ b/Extensions/BepInExShared.cs @@ -8,6 +8,7 @@ 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 == true) messenger.SendConfigEntry(configEntry); @@ -16,11 +17,11 @@ public static void SyncConfigEntry(this Messenger messenger, ConfigEntry c 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 == true) messenger.SendConfigEntry(configEntry); @@ -29,7 +30,6 @@ public static void SyncConfigEntry(this Messenger messenger, ConfigEntry 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.RML_Extensions/RML_Extensions.cs b/Extensions/InterprocessLib.RML_Extensions/RML_Extensions.cs index 1f35d7c..1fce37f 100644 --- a/Extensions/InterprocessLib.RML_Extensions/RML_Extensions.cs +++ b/Extensions/InterprocessLib.RML_Extensions/RML_Extensions.cs @@ -8,6 +8,7 @@ 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 == true) messenger.SendConfigEntry(configEntry); @@ -16,11 +17,11 @@ public static void SyncConfigEntry(this Messenger messenger, ModConfiguration 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 == true) messenger.SendConfigEntry(configEntry); @@ -29,7 +30,6 @@ public static void SyncConfigEntry(this Messenger messenger, ModConfigurationKey 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 06fa017..e019056 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -64,7 +64,7 @@ private static void InnerInit() if (queueName is null) { Messenger.OnDebug?.Invoke("Shared memory unique id is null! Attempting to use fallback..."); - var task = Messenger.GetFallbackSystem(true, MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + 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) diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 9c39145..827d725 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -335,7 +335,7 @@ public override void Pack(ref MemoryPacker packer) packer.Write(-1); return; } - int len = ((ICollection)Strings).Count; + int len = Strings.Count(); // Is there a better way to get the Count? packer.Write(len); foreach (var str in Strings) { @@ -549,18 +549,18 @@ public void Unpack(ref MemoryUnpacker unpacker) internal sealed class PingCommand : IMemoryPackable { - public DateTime Time; - public bool Received; + public DateTime SentTime; + public DateTime? ReceivedTime; public void Pack(ref MemoryPacker packer) { - packer.Write(Time); - packer.Write(Received); + packer.Write(SentTime); + packer.Write(ReceivedTime); } public void Unpack(ref MemoryUnpacker unpacker) { - unpacker.Read(ref Time); - unpacker.Read(ref Received); + unpacker.Read(ref SentTime); + unpacker.Read(ref ReceivedTime); } } diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index 694f7f6..f321f78 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -5,7 +5,7 @@ namespace InterprocessLib; /// /// Simple interprocess messaging API. /// -public class Messenger +public class Messenger : IDisposable { private static MessagingSystem? _defaultSystem; @@ -67,10 +67,12 @@ public class Messenger internal static readonly object LockObj = new(); - internal static async Task GetFallbackSystem(bool isAuthority, long queueCapacity, IMemoryPackerEntityPool? pool = null, RenderCommandHandler? commandHandler = null, Action? failhandler = null, Action? warnHandler = null, Action? debugHandler = null, Action? postInitCallback = null) + 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 = 500; + int waitTimeMs = 5000; while (_runningFallbackSystemInit && (DateTime.UtcNow - startTime).TotalMilliseconds < waitTimeMs * 2) await Task.Delay(1); @@ -80,7 +82,7 @@ public class Messenger var now = DateTime.UtcNow; int minuteInDay = now.Hour * 60 + now.Minute; - var system1 = new MessagingSystem(isAuthority, $"InterprocessLib-{minuteInDay}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); + var system1 = new MessagingSystem(isAuthority, $"InterprocessLib-{ownerId}{minuteInDay}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); system1.Connect(); if (isAuthority) { @@ -89,11 +91,11 @@ public class Messenger return system1; } var cancel1 = new CancellationTokenSource(); - system1.PingCallback = (latency) => + system1.PingCallback = (ping) => { cancel1.Cancel(); }; - system1.SendPackable(new PingCommand() { Time = now }); + system1.SendPackable(new PingCommand() { SentTime = now }); try { await Task.Delay(waitTimeMs, cancel1.Token); @@ -110,13 +112,13 @@ public class Messenger // 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-{minuteInDay - 1}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); + var system2 = new MessagingSystem(isAuthority, $"InterprocessLib-{ownerId}{minuteInDay - 1}", queueCapacity, pool ?? FallbackPool.Instance, commandHandler, failhandler, warnHandler, debugHandler, postInitCallback); system2.Connect(); - system2.PingCallback = (latency) => + system2.PingCallback = (ping) => { cancel2.Cancel(); }; - system2.SendPackable(new PingCommand() { Time = now }); + system2.SendPackable(new PingCommand() { SentTime = now }); try { await Task.Delay(waitTimeMs, cancel2.Token); @@ -145,7 +147,7 @@ public class Messenger /// Unused parameter kept for backwards compatibility. /// /// - [Obsolete("Use the other constructors that don't take Type lists")] + [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) @@ -199,7 +201,7 @@ private void DefaultInit() } else { - var fallbackSystemTask = GetFallbackSystem(false, MessagingManager.DEFAULT_CAPACITY, FallbackPool.Instance, null, OnFailure, OnWarning, OnDebug, null); + 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!"); @@ -245,7 +247,7 @@ public Messenger(string ownerId, bool isAuthority, string queueName, IMemoryPack } else { - throw new EntryPointNotFoundException("Could not find default IMemoryPackerEntityPool!"); + actualPool = FallbackPool.Instance; } } } @@ -781,7 +783,7 @@ public void ReceiveEmptyCommand(string id, Action? callback) CurrentSystem!.RegisterObjectArrayCallback(_ownerId, id, callback); } - public void CheckLatency(Action callback) + public void CheckLatency(Action callback) { if (!IsInitialized) { @@ -789,10 +791,10 @@ public void CheckLatency(Action callback) return; } - CurrentSystem!.PingCallback = callback; + CurrentSystem!.PingCallback = (ping) => callback?.Invoke(ping.ReceivedTime!.Value - ping.SentTime, DateTime.UtcNow - ping.ReceivedTime!.Value); var pingCommand = new PingCommand(); - pingCommand.Time = DateTime.UtcNow; + pingCommand.SentTime = DateTime.UtcNow; CurrentSystem!.SendPackable(pingCommand); } @@ -828,4 +830,10 @@ public void ReceiveType(string id, Action? callback) CurrentSystem!.RegisterTypeCallback(_ownerId, id, callback); } + + public void Dispose() + { + CurrentSystem!.UnregisterOwner(_ownerId); + _customSystem = null; + } } \ No newline at end of file diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index dee7207..85a0673 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -80,12 +80,14 @@ public OwnerData() private static readonly Dictionary _backends = new(); - internal Action? PingCallback; + internal Action? PingCallback; private readonly IMemoryPackerEntityPool _pool; private bool _messengerReadyCommandReceived = false; + //private readonly CancellationTokenSource _cancel = new(); + internal void SetPostInitActions(List? actions) { if (IsInitialized) @@ -240,8 +242,16 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I }; _backends.Add(QueueName, this); + + // _primary.StartKeepAlive(2500); + // _cancel.Token.Register(Dispose); } + // private void OnKeepAlive() + // { + // _cancel.CancelAfter(5000); + // } + public void Dispose() { _primary?.Dispose(); @@ -407,14 +417,14 @@ private void HandleEmptyCommand(EmptyCommand command) private void HandlePingCommand(PingCommand ping) { - if (!ping.Received) + if (!ping.ReceivedTime.HasValue) { - ping.Received = true; + ping.ReceivedTime = DateTime.UtcNow; SendPackable(ping); } else { - PingCallback?.Invoke(DateTime.UtcNow - ping.Time); + PingCallback?.Invoke(ping); PingCallback = null; } } @@ -438,26 +448,31 @@ private void CommandHandler(RendererCommand command, int messageSize) { _onCommandReceived?.Invoke(command, messageSize); + // if (command is KeepAlive) + // { + // OnKeepAlive(); + // return; + // } + IMemoryPackable? packable = null; if (command is WrapperCommand wrapperCommand) { packable = wrapperCommand.Packable; - _onDebug?.Invoke($"Received {packable?.ToString() ?? packable?.GetType().Name ?? "NULL"}"); + _onDebug?.Invoke($"{QueueName}: Received {packable?.ToString() ?? packable?.GetType().Name ?? "NULL"}"); } else { - _onWarning?.Invoke($"Received an unexpected RendererCommand type! {command?.ToString() ?? command?.GetType().Name ?? "NULL"}"); + _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)b + // ping command before ready command is okay (to check if the queue is active) if (packable is PingCommand pingCommand) { HandlePingCommand(pingCommand); return; } - // it's a bit unexpected if the non-authority process receives more than one of these, but it might be okay? if (packable is MessengerReadyCommand) { if (_messengerReadyCommandReceived) @@ -479,7 +494,6 @@ private void CommandHandler(RendererCommand command, int messageSize) if (packable is TypeRegistrationCommand typeRegCommand) { - _onDebug?.Invoke($"Received new type to register: {typeRegCommand.Type?.FullName ?? "NULL"}"); if (typeRegCommand.Type is not null) { IncomingTypeManager.InitDirectCommandType(typeRegCommand.Type); @@ -579,9 +593,11 @@ private void CommandHandler(RendererCommand command, int messageSize) 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?.ToString() ?? packable?.GetType().Name ?? "NULL"}"); + _onDebug?.Invoke($"Sending packable: {packable}"); var wrapper = new WrapperCommand(); wrapper.QueueName = QueueName; @@ -590,6 +606,17 @@ public void SendPackable(IMemoryPackable packable) _primary!.SendCommand(wrapper); } + public void SendRendererCommand(RendererCommand command) + { + if (command is null) throw new ArgumentNullException(nameof(command)); + + if (!IsConnected) throw new InvalidOperationException("Not connected!"); + + _onDebug?.Invoke($"Sending RendererCommand: {command}"); + + _primary!.SendCommand(command); + } + internal void EnsureValueTypeInitialized() where T : unmanaged { if (!OutgoingTypeManager.IsValueTypeInitialized()) @@ -612,4 +639,20 @@ private void OnOutgoingTypeRegistered(Type type) 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 36e2efb..cc15c67 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -8,9 +8,9 @@ namespace InterprocessLib.Tests; public static class Tests { private static Messenger? _messenger; - private static Action? _logCallback; + private static Action? _logCallback; - public static void RunTests(Messenger messenger, Action logCallback) + public static void RunTests(Messenger messenger, Action logCallback) { _messenger = messenger; _logCallback = logCallback; 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/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index 1e43190..de92c03 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -54,7 +54,7 @@ private static void InnerInit() var engineSharedMemoryPrefix = fullQueueName?.Substring(0, fullQueueName.IndexOf('_')); if (fullQueueName is null || engineSharedMemoryPrefix!.Length == 0) { - var fallbackTask = Messenger.GetFallbackSystem(false, MessagingManager.DEFAULT_CAPACITY, PackerMemoryPool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + 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) diff --git a/InterprocessLib.sln b/InterprocessLib.sln index 1f7c7a8..24e9cb3 100644 --- a/InterprocessLib.sln +++ b/InterprocessLib.sln @@ -20,10 +20,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.BepInEx.Tes 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 @@ -49,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.Standalone. {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} = {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.Standalone", "InterprocessLib.Standalone\InterprocessLib.Standalone.csproj", "{09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -71,18 +69,6 @@ Global {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 @@ -155,6 +141,18 @@ Global {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 + {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Debug|x64.ActiveCfg = Debug|Any CPU + {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Debug|x64.Build.0 = Debug|Any CPU + {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Debug|x86.ActiveCfg = Debug|Any CPU + {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Debug|x86.Build.0 = Debug|Any CPU + {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Release|Any CPU.Build.0 = Release|Any CPU + {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Release|x64.ActiveCfg = Release|Any CPU + {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Release|x64.Build.0 = Release|Any CPU + {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Release|x86.ActiveCfg = Release|Any CPU + {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Release|x86.Build.0 = Release|Any CPU {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -167,18 +165,13 @@ Global {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Release|x64.Build.0 = Release|Any CPU {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Release|x86.ActiveCfg = Release|Any CPU {2C33CB8C-DE73-DE88-6485-020522AD24C1}.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} - {2C33CB8C-DE73-DE88-6485-020522AD24C1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B512A2C4-BDEE-469F-9FBA-5A8B2BC50B08} diff --git a/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs b/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs index cd5cee5..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,14 +12,20 @@ public class UnityPlugin : BaseUnityPlugin public static ManualLogSource? Log; private static Messenger? _messenger; private static Messenger? _unknownMessenger; + +#if TEST_OBSOLETE_CONSTRUCTOR private static Messenger? _testObsoleteConstructor; +#endif + public static ConfigEntry? SyncTest; void Awake() { Log = base.Logger; _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); @@ -26,7 +33,10 @@ void Awake() { Tests.RunTests(_messenger, Log!.LogInfo); Tests.RunTests(_unknownMessenger, Log!.LogInfo); + +#if TEST_OBSOLETE_CONSTRUCTOR Tests.RunTests(_testObsoleteConstructor, Log!.LogInfo); +#endif }); _messenger.ReceiveEmptyCommand("CheckSync", () => { @@ -38,6 +48,8 @@ void Awake() }); 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.BepisLoader.Tests/BepisLoaderTests.cs b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs index 73ae31d..e17813f 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs +++ b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs @@ -1,11 +1,11 @@ //#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; @@ -19,51 +19,43 @@ public class Plugin : BasePlugin public static ConfigEntry? RunTestsToggle; public static Messenger? _messenger; public static Messenger? _unknownMessenger; + +#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? LatencyMilliseconds; + 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 - private static void CommandHandler(RendererCommand command, int messageSize) - { - } - - private static void FailHandler(Exception ex) - { - Log!.LogError($"[Child Process Messaging Host] Exception in custom messaging host: {ex}"); - } - - private static void WarnHandler(string msg) - { - Log!.LogWarning($"[Child Process Messaging Host] {msg}"); - } - - private static void DebugHandler(string msg) - { - Log!.LogDebug($"[Child Process Messaging Host] {msg}"); - } - #if TEST_SPAWN_PROCESS private static void SpawnProcess() { - if (_customProcess is not null && !_customProcess.HasExited) return; - _customQueueName ??= $"MyCustomQueue{_rand.Next()}"; + _customProcess?.Kill(); + _customQueueName = $"MyCustomQueue{_rand.Next()}"; Log!.LogInfo("Child process queue name: " + _customQueueName); - _customMessenger ??= new Messenger("InterprocessLib.Tests", true, _customQueueName, additionalObjectTypes: [typeof(TestCommand), typeof(TestNestedPackable), typeof(TestPackable), typeof(RendererInitData)], additionalValueTypes: [typeof(TestStruct), typeof(TestNestedStruct), typeof(HapticPointState), typeof(ShadowType)]); + _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 @@ -75,8 +67,8 @@ private static void SpawnProcess() 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.Arguments = $"{_customQueueName}"; + //_customProcess.StartInfo.UseShellExecute = true; // Run in a new window _customProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal; _customProcess.Start(); Tests.RunTests(_customMessenger, Log!.LogInfo); @@ -103,12 +95,18 @@ public override void Load() }; _messenger = new Messenger("InterprocessLib.Tests"); - _testObsoleteConstructor = new("InterprocessLib.Tests.ObsoleteConstructor", [], []); _unknownMessenger = new Messenger("InterprocessLib.Tests.UnknownMessengerFrooxEngine"); +#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(); @@ -117,6 +115,7 @@ public override void Load() { SpawnProcess(); }; + LastProcessHeartbeat = Config.Bind("General", "LastProcessHeartbeat", DateTime.MinValue); #endif SyncTest = Config.Bind("General", "SyncTest", 34); @@ -126,11 +125,13 @@ public override void Load() CheckSyncToggle = Config.Bind("General", "CheckSync", false); SyncTestOutput = Config.Bind("General", "SyncTestOutput", 0); ResetToggle = Config.Bind("General", "ResetToggle", false); - LatencyMilliseconds = Config.Bind("General", "LatencyMilliseconds", -1.0); + SendLatencyMilliseconds = Config.Bind("General", "SendLatencyMilliseconds", -1.0); + RecvLatencyMilliseconds = Config.Bind("General", "RecvLatencyMilliseconds", -1.0); - _messenger.CheckLatency(latency => + _messenger.CheckLatency((send, recv) => { - LatencyMilliseconds.Value = latency.TotalMilliseconds; + SendLatencyMilliseconds.Value = send.TotalMilliseconds; + RecvLatencyMilliseconds.Value = recv.TotalMilliseconds; }); RunTestsToggle!.SettingChanged += (sender, args) => @@ -138,11 +139,16 @@ public override void Load() _messenger!.SendEmptyCommand("RunTests"); Tests.RunTests(_messenger, Log!.LogInfo); Tests.RunTests(_unknownMessenger, Log!.LogInfo); + +#if TEST_OBSOLETE_CONSTRUCTOR Tests.RunTests(_testObsoleteConstructor, Log!.LogInfo); - _messenger.CheckLatency(latency => - { - LatencyMilliseconds.Value = latency.TotalMilliseconds; +#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) { diff --git a/Tests/InterprocessLib.RML.Tests/RML_Tests.cs b/Tests/InterprocessLib.RML.Tests/RML_Tests.cs index 4ece7df..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; @@ -24,37 +25,55 @@ public class RML_Tests : ResoniteMod [AutoRegisterConfigKey] private static ModConfigurationKey ResetToggle = new ModConfigurationKey("ResetToggle", "ResetToggle:", () => false); [AutoRegisterConfigKey] - private static ModConfigurationKey RoundTripLatencyMilliseconds = new ModConfigurationKey("RoundTripLatencyMilliseconds", "RoundTripLatencyMilliseconds:", () => -1.0); + 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; + +#if TEST_OBSOLETE_CONSTRUCTOR public static Messenger? _testObsoleteConstructor; +#endif public override void OnEngineInit() { _messenger = new Messenger("InterprocessLib.Tests"); - _testObsoleteConstructor = new("InterprocessLib.Tests.ObsoleteConstructor", [], []); _unknownMessenger = new Messenger("InterprocessLib.Tests.UnknownMessengerFrooxEngine"); -#pragma warning disable CS8622 +#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); -#pragma warning restore CS8622 +#endif - _messenger.CheckLatency(latency => RoundTripLatencyMilliseconds!.Value = latency.TotalMilliseconds); + _messenger.CheckLatency((send, recv) => + { + SendLatencyMilliseconds.Value = send.TotalMilliseconds; + RecvLatencyMilliseconds.Value = recv.TotalMilliseconds; + }); _messenger.SyncConfigEntry(SyncTest); RunTestsToggle!.OnChanged += (object? newValue) => { _messenger!.SendEmptyCommand("RunTests"); -#pragma warning disable CS8622 Tests.RunTests(_messenger, Msg); Tests.RunTests(_unknownMessenger, Msg); + +#if TEST_OBSOLETE_CONSTRUCTOR Tests.RunTests(_testObsoleteConstructor, Msg); -#pragma warning restore CS8622 - _messenger.CheckLatency(latency => RoundTripLatencyMilliseconds!.Value = latency.TotalMilliseconds); +#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 index 8b968ef..89d90cf 100644 --- a/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj +++ b/Tests/InterprocessLib.Standalone.Tests/InterprocessLib.Standalone.Tests.csproj @@ -13,15 +13,12 @@ - - $(GamePath)Elements.Core.dll - - - ..\..\out\InterprocessLib.FrooxEngine.dll + + ..\..\out\InterprocessLib.Standalone.dll - $(GamePath)Renderite.Shared.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 index 9971275..d829586 100644 --- a/Tests/InterprocessLib.Standalone.Tests/Program.cs +++ b/Tests/InterprocessLib.Standalone.Tests/Program.cs @@ -1,4 +1,5 @@ -using Elements.Core; +using System.Diagnostics; +using System.Runtime.CompilerServices; using InterprocessLib; using InterprocessLib.Tests; using Renderite.Shared; @@ -7,6 +8,7 @@ namespace InterprocessLibStandaloneTest { internal class Program { + private static CancellationTokenSource _cancel = new(); private static void CommandHandler(RendererCommand command, int messageSize) { } @@ -50,7 +52,21 @@ static void Main(string[] args) Tests.RunTests(messenger, Console.WriteLine); - Console.ReadLine(); // Keeps the window open while also allowing it to continue to receive and display new data + messenger.ReceiveEmptyCommand("HeartbeatResponse", () => + { + _cancel.CancelAfter(5000); + }); + + _cancel.CancelAfter(10000); + + Task.Run(async () => + { + while (!_cancel.IsCancellationRequested) + { + messenger.SendEmptyCommand("Heartbeat"); + await Task.Delay(2500); + } + }).Wait(); } } } From 0a9dbbf87c513576fed1b28858901650e47752d8 Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:46:05 +0000 Subject: [PATCH 33/35] Switch to generic send collection commands in Messenger, and migrate project to .slnx --- .../FrooxEngineInit.cs | 6 +- InterprocessLib.Shared/Commands.cs | 11 +- InterprocessLib.Shared/Messenger.cs | 320 ++++++++++++------ InterprocessLib.Shared/System.cs | 38 ++- InterprocessLib.Shared/Tests.cs | 47 +-- InterprocessLib.Shared/TypeManager.cs | 14 +- InterprocessLib.Unity/UnityInit.cs | 7 +- InterprocessLib.sln | 179 ---------- InterprocessLib.slnx | 43 +++ 9 files changed, 318 insertions(+), 347 deletions(-) delete mode 100644 InterprocessLib.sln create mode 100644 InterprocessLib.slnx diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index e019056..8e39ecb 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -1,7 +1,5 @@ using Elements.Core; -using FrooxEngine; using Renderite.Shared; -using System.Reflection; namespace InterprocessLib; @@ -63,7 +61,7 @@ private static void InnerInit() if (queueName is null) { - Messenger.OnDebug?.Invoke("Shared memory unique id is null! Attempting to use fallback..."); + 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; @@ -85,6 +83,6 @@ private static void InnerInit() system.Initialize(); } - //Engine.Current.OnShutdown += system.Dispose; // this might fix the rare occurence that Renderite.Host stays open after exiting Resonite + //Engine.Current.OnShutdown += system.Dispose; } } \ No newline at end of file diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index 827d725..c73ea1f 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -244,6 +244,7 @@ private void PackType(Type? type, ref MemoryPacker packer) 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}"); @@ -319,9 +320,9 @@ public override void Unpack(ref MemoryUnpacker unpacker) } } -internal sealed class StringCollectionCommand : CollectionCommand where C : ICollection?, new() +internal sealed class StringCollectionCommand : CollectionCommand where C : ICollection?, new() { - public IEnumerable? Strings; // IEnumerable is required for covariance of string? and string + public IReadOnlyCollection? Strings; // IReadOnlyCollection is required for covariance of string? and string public override IEnumerable? UntypedCollection => Strings; public override Type StoredType => typeof(string); @@ -335,7 +336,7 @@ public override void Pack(ref MemoryPacker packer) packer.Write(-1); return; } - int len = Strings.Count(); // Is there a better way to get the Count? + int len = Strings.Count; packer.Write(len); foreach (var str in Strings) { @@ -360,7 +361,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) unpacker.Read(ref str!); collection.Add(str); } - Strings = collection; + Strings = (IReadOnlyCollection?)collection; } } @@ -418,7 +419,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) internal sealed class ObjectArrayCommand : CollectionCommand where T : class?, IMemoryPackable?, new() { - public T[]? Objects; + public T?[]? Objects; public override IEnumerable? UntypedCollection => Objects; public override Type StoredType => typeof(T); diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index f321f78..5e64212 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -371,43 +371,63 @@ public void SendValue(string id, T value) where T : unmanaged CurrentSystem!.SendPackable(command); } - public void SendValueList(string id, List? list) where T : unmanaged + // public void SendValueList(string id, List? list) where T : unmanaged + // { + // if (id is null) + // throw new ArgumentNullException(nameof(id)); + + // if (!IsInitialized) + // { + // RunPostInit(() => SendValueList(id, list)); + // return; + // } + + // CurrentSystem!.EnsureValueTypeInitialized(); + + // var command = new ValueCollectionCommand, T>(); + // command.Owner = _ownerId; + // command.Id = id; + // command.Values = list; + // CurrentSystem!.SendPackable(command); + // } + + // public void SendValueHashSet(string id, HashSet? hashSet) where T : unmanaged + // { + // if (id is null) + // throw new ArgumentNullException(nameof(id)); + + // if (!IsInitialized) + // { + // RunPostInit(() => SendValueHashSet(id, hashSet)); + // return; + // } + + // CurrentSystem!.EnsureValueTypeInitialized(); + + // var command = new ValueCollectionCommand, T>(); + // command.Owner = _ownerId; + // command.Id = id; + // command.Values = hashSet; + // CurrentSystem!.SendPackable(command); + // } + + 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; } - CurrentSystem!.EnsureValueTypeInitialized(); + CurrentSystem!.EnsureValueCollectionTypeInitialized(); - var command = new ValueCollectionCommand, T>(); + var command = new ValueCollectionCommand(); command.Owner = _ownerId; command.Id = id; - command.Values = list; - CurrentSystem!.SendPackable(command); - } - - public void SendValueHashSet(string id, HashSet? hashSet) where T : unmanaged - { - if (id is null) - throw new ArgumentNullException(nameof(id)); - - if (!IsInitialized) - { - RunPostInit(() => SendValueHashSet(id, hashSet)); - return; - } - - CurrentSystem!.EnsureValueTypeInitialized(); - - var command = new ValueCollectionCommand, T>(); - command.Owner = _ownerId; - command.Id = id; - command.Values = hashSet; + command.Values = collection; CurrentSystem!.SendPackable(command); } @@ -449,21 +469,41 @@ public void SendString(string id, string? str) CurrentSystem!.SendPackable(command); } - public void SendStringList(string id, IEnumerable? list) + // public void SendStringList(string id, IEnumerable? list) + // { + // if (id is null) + // throw new ArgumentNullException(nameof(id)); + + // if (!IsInitialized) + // { + // RunPostInit(() => SendStringList(id, list)); + // return; + // } + + // var command = new StringCollectionCommand>(); + // command.Owner = _ownerId; + // command.Id = id; + // command.Strings = list; + // CurrentSystem!.SendPackable(command); + // } + + public void SendStringCollection(string id, IReadOnlyCollection? collection) where C : ICollection?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); if (!IsInitialized) { - RunPostInit(() => SendStringList(id, list)); + RunPostInit(() => SendStringCollection(id, collection)); return; } - var command = new StringCollectionCommand>(); + CurrentSystem!.EnsureStringCollectionTypeInitialized(); + + var command = new StringCollectionCommand(); command.Owner = _ownerId; command.Id = id; - command.Strings = list; + command.Strings = collection; CurrentSystem!.SendPackable(command); } @@ -485,23 +525,23 @@ public void SendStringArray(string id, string?[]? array) CurrentSystem!.SendPackable(command); } - public void SendStringHashSet(string id, IEnumerable? set) - { - if (id is null) - throw new ArgumentNullException(nameof(id)); + // public void SendStringHashSet(string id, IEnumerable? set) + // { + // if (id is null) + // throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) - { - RunPostInit(() => SendStringHashSet(id, set)); - return; - } + // if (!IsInitialized) + // { + // RunPostInit(() => SendStringHashSet(id, set)); + // return; + // } - var command = new StringCollectionCommand>(); - command.Owner = _ownerId; - command.Id = id; - command.Strings = set; - CurrentSystem!.SendPackable(command); - } + // var command = new StringCollectionCommand>(); + // command.Owner = _ownerId; + // command.Id = id; + // command.Strings = set; + // CurrentSystem!.SendPackable(command); + // } public void SendEmptyCommand(string id) { @@ -541,43 +581,63 @@ public void SendEmptyCommand(string id) CurrentSystem!.SendPackable(command); } - public void SendObjectList(string id, List? list) where T : class?, IMemoryPackable?, new() - { - if (id is null) - throw new ArgumentNullException(nameof(id)); + // public void SendObjectList(string id, List? list) where T : class?, IMemoryPackable?, new() + // { + // if (id is null) + // throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) - { - RunPostInit(() => SendObjectList(id, list)); - return; - } + // if (!IsInitialized) + // { + // RunPostInit(() => SendObjectList(id, list)); + // return; + // } - CurrentSystem!.EnsureObjectTypeInitialized(); + // CurrentSystem!.EnsureObjectTypeInitialized(); - var command = new ObjectCollectionCommand, T>(); - command.Owner = _ownerId; - command.Id = id; - command.Objects = list; - CurrentSystem!.SendPackable(command); - } + // var command = new ObjectCollectionCommand, T>(); + // command.Owner = _ownerId; + // command.Id = id; + // command.Objects = list; + // CurrentSystem!.SendPackable(command); + // } + + // public void SendObjectHashSet(string id, HashSet? hashSet) where T : class?, IMemoryPackable?, new() + // { + // if (id is null) + // throw new ArgumentNullException(nameof(id)); + + // if (!IsInitialized) + // { + // RunPostInit(() => SendObjectHashSet(id, hashSet)); + // return; + // } + + // CurrentSystem!.EnsureObjectTypeInitialized(); - public void SendObjectHashSet(string id, HashSet? hashSet) where T : class?, IMemoryPackable?, new() + // var command = new ObjectCollectionCommand, T>(); + // command.Owner = _ownerId; + // command.Id = id; + // command.Objects = hashSet; + // CurrentSystem!.SendPackable(command); + // } + + 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(() => SendObjectHashSet(id, hashSet)); + RunPostInit(() => SendObjectCollection(id, collection)); return; } - CurrentSystem!.EnsureObjectTypeInitialized(); + CurrentSystem!.EnsureObjectCollectionTypeInitialized(); - var command = new ObjectCollectionCommand, T>(); + var command = new ObjectCollectionCommand(); command.Owner = _ownerId; command.Id = id; - command.Objects = hashSet; + command.Objects = collection; CurrentSystem!.SendPackable(command); } @@ -615,34 +675,48 @@ public void ReceiveValue(string id, Action? callback) where T : unmanaged CurrentSystem!.RegisterValueCallback(_ownerId, id, callback); } - public void ReceiveValueList(string id, Action?>? callback) where T : unmanaged - { - if (id is null) - throw new ArgumentNullException(nameof(id)); + // public void ReceiveValueList(string id, Action?>? callback) where T : unmanaged + // { + // if (id is null) + // throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) - { - RunPostInit(() => ReceiveValueList(id, callback)); - return; - } + // if (!IsInitialized) + // { + // RunPostInit(() => ReceiveValueList(id, callback)); + // return; + // } - CurrentSystem!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); - } + // CurrentSystem!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); + // } - public void ReceiveValueHashSet(string id, Action?>? callback) where T : unmanaged + 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; } - CurrentSystem!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); + CurrentSystem!.RegisterValueCollectionCallback(_ownerId, id, callback); } + // public void ReceiveValueHashSet(string id, Action?>? callback) where T : unmanaged + // { + // if (id is null) + // throw new ArgumentNullException(nameof(id)); + + // if (!IsInitialized) + // { + // RunPostInit(() => ReceiveValueHashSet(id, callback)); + // return; + // } + + // CurrentSystem!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); + // } + public void ReceiveValueArray(string id, Action? callback) where T : unmanaged { if (id is null) @@ -671,18 +745,32 @@ public void ReceiveString(string id, Action? callback) CurrentSystem!.RegisterStringCallback(_ownerId, id, callback); } - public void ReceiveStringList(string id, Action?>? callback) + // public void ReceiveStringList(string id, Action?>? callback) + // { + // if (id is null) + // throw new ArgumentNullException(nameof(id)); + + // if (!IsInitialized) + // { + // RunPostInit(() => ReceiveStringList(id, callback)); + // return; + // } + + // CurrentSystem!.RegisterStringCollectionCallback>(_ownerId, 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(() => ReceiveStringList(id, callback)); + RunPostInit(() => ReceiveStringCollection(id, callback)); return; } - CurrentSystem!.RegisterStringCollectionCallback>(_ownerId, id, callback); + CurrentSystem!.RegisterStringCollectionCallback(_ownerId, id, callback); } public void ReceiveStringArray(string id, Action? callback) @@ -699,19 +787,19 @@ public void ReceiveStringArray(string id, Action? callback) CurrentSystem!.RegisterStringArrayCallback(_ownerId, id, callback!); } - public void ReceiveStringHashSet(string id, Action?>? callback) - { - if (id is null) - throw new ArgumentNullException(nameof(id)); + // public void ReceiveStringHashSet(string id, Action?>? callback) + // { + // if (id is null) + // throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) - { - RunPostInit(() => ReceiveStringHashSet(id, callback)); - return; - } + // if (!IsInitialized) + // { + // RunPostInit(() => ReceiveStringHashSet(id, callback)); + // return; + // } - CurrentSystem!.RegisterStringCollectionCallback>(_ownerId, id, callback); - } + // CurrentSystem!.RegisterStringCollectionCallback>(_ownerId, id, callback); + // } public void ReceiveEmptyCommand(string id, Action? callback) { @@ -727,7 +815,7 @@ public void ReceiveEmptyCommand(string id, Action? 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)); @@ -741,35 +829,49 @@ public void ReceiveEmptyCommand(string id, Action? callback) CurrentSystem!.RegisterObjectCallback(_ownerId, id, callback); } - public void ReceiveObjectList(string id, Action?>? callback) where T : class?, IMemoryPackable?, new() - { - if (id is null) - throw new ArgumentNullException(nameof(id)); + // public void ReceiveObjectList(string id, Action?>? callback) where T : class?, IMemoryPackable?, new() + // { + // if (id is null) + // throw new ArgumentNullException(nameof(id)); - if (!IsInitialized) - { - RunPostInit(() => ReceiveObjectList(id, callback)); - return; - } + // if (!IsInitialized) + // { + // RunPostInit(() => ReceiveObjectList(id, callback)); + // return; + // } - CurrentSystem!.RegisterObjectCollectionCallback, T>(_ownerId, id, callback); - } + // CurrentSystem!.RegisterObjectCollectionCallback, T>(_ownerId, id, callback); + // } - public void ReceiveObjectHashSet(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(() => ReceiveObjectHashSet(id, callback)); + RunPostInit(() => ReceiveObjectCollection(id, callback)); return; } - CurrentSystem!.RegisterObjectCollectionCallback, T>(_ownerId, id, callback); + CurrentSystem!.RegisterObjectCollectionCallback(_ownerId, id, callback); } - public void ReceiveObjectArray(string id, Action? callback) where T : class?, IMemoryPackable?, new() + // public void ReceiveObjectHashSet(string id, Action?>? callback) where T : class?, IMemoryPackable?, new() + // { + // if (id is null) + // throw new ArgumentNullException(nameof(id)); + + // if (!IsInitialized) + // { + // RunPostInit(() => ReceiveObjectHashSet(id, callback)); + // return; + // } + + // CurrentSystem!.RegisterObjectCollectionCallback, T>(_ownerId, id, callback); + // } + + public void ReceiveObjectArray(string id, Action? callback) where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 85a0673..5e0c92c 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -157,12 +157,12 @@ public void RegisterValueCallback(string owner, string id, Action? callbac _ownerData[owner].ValueCallbacks[id] = callback; } - public void RegisterValueCollectionCallback(string owner, string id, Action? callback) where C : ICollection, new() where T : unmanaged + 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 + public void RegisterValueArrayCallback(string owner, string id, Action? callback) where T : unmanaged { _ownerData[owner].ValueArrayCallbacks[id] = callback; } @@ -177,7 +177,7 @@ public void RegisterStringArrayCallback(string owner, string id, Action(string owner, string id, Action? callback) where C : ICollection?, new() + public void RegisterStringCollectionCallback(string owner, string id, Action? callback) where C : ICollection?, new() { _ownerData[owner].StringCollectionCallbacks[id] = callback; } @@ -187,12 +187,12 @@ 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() + 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() + public void RegisterObjectArrayCallback(string owner, string id, Action? callback) where T : class?, IMemoryPackable?, new() { _ownerData[owner].ObjectArrayCallbacks[id] = callback; } @@ -340,7 +340,7 @@ private void HandleStringArrayCommand(StringArrayCommand command) } } - private void HandleStringCollectionCommand(StringCollectionCommand command) where C : ICollection?, new() + private void HandleStringCollectionCommand(StringCollectionCommand command) where C : ICollection?, new() { if (_ownerData[command.Owner!].StringCollectionCallbacks.TryGetValue(command.Id!, out var callback)) { @@ -391,7 +391,7 @@ private void HandleEmptyCommand(EmptyCommand command) { if (callback != null) { - ((Action)callback).Invoke(command.Objects); + ((Action)callback).Invoke(command.Objects); } } else @@ -633,6 +633,30 @@ internal void EnsureValueTypeInitialized() where T : unmanaged } } + 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(); diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index cc15c67..06c7db1 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -27,7 +27,7 @@ public static void RunTests(Messenger messenger, Action logCallback) TestNestedStruct(); TestValueList(); TestValueHashSet(); - TestStringList(); + TestStringCollection(); TestObjectList(); TestVanillaObject(); TestVanillaStruct(); @@ -35,7 +35,6 @@ public static void RunTests(Messenger messenger, Action logCallback) TestValueArray(); TestObjectArray(); TestObjectHashSet(); - TestStringHashSet(); TestStringArray(); TestTypeCommand(); } @@ -224,7 +223,7 @@ static void TestNestedStruct() static void TestValueList() { - _messenger!.ReceiveValueList("TestValueList", (list) => + _messenger!.ReceiveValueCollection, float>("TestValueList", (list) => { _logCallback!($"TestValueList: {string.Join(",", list!)}"); }); @@ -233,12 +232,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!)}"); }); @@ -247,12 +246,12 @@ 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!)}"); }); @@ -261,12 +260,12 @@ static void TestObjectList() 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 TestObjectHashSet() { - _messenger!.ReceiveObjectHashSet("TestObjectHashSet", (list) => + _messenger!.ReceiveObjectCollection, TestCommand?>("TestObjectHashSet", (list) => { _logCallback!($"TestObjectHashSet: {string.Join(",", list!)}"); }); @@ -275,23 +274,23 @@ static void TestObjectHashSet() set.Add(new TestCommand()); set.Add(null); set.Add(new TestCommand() { Value = 9 }); - _messenger.SendObjectHashSet("TestObjectHashSet", set); + _messenger.SendObjectCollection, TestCommand?>("TestObjectHashSet", set); } - static void TestStringList() + static void TestStringCollection() { - _messenger!.ReceiveStringList("TestStringList", (list) => + _messenger!.ReceiveStringCollection>("TestStringCollection", (list) => { - _logCallback!($"TestStringList: {string.Join(",", list!.Select(s => s ?? "NULL"))}"); + _logCallback!($"TestStringCollection: {string.Join(",", list!.Select(s => s ?? "NULL"))}"); }); - var list = new List(); + var list = new List(); list.Add("Hello"); list.Add("World"); list.Add("owo"); - list.Add(null); + list.Add(null!); list.Add("x3"); - _messenger.SendStringList("TestStringList", list); + _messenger.SendStringCollection>("TestStringCollection", list); } static void TestStringArray() @@ -312,22 +311,6 @@ static void TestStringArray() _messenger.SendStringArray("TestStringArray", arr); } - static void TestStringHashSet() - { - _messenger!.ReceiveStringHashSet("TestStringHashSet", (set) => - { - _logCallback!($"TestStringHashSet: {string.Join(",", set!.Select(s => s ?? "NULL"))}"); - }); - - var set = new HashSet(); - set.Add("Hello"); - set.Add("World"); - set.Add("owo"); - set.Add(null); - set.Add("x3"); - _messenger.SendStringHashSet("TestStringHashSet", set); - } - static void TestVanillaObject() { _messenger!.ReceiveObject("TestVanillaObject", (recv) => diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 44b0b40..60c5179 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -34,8 +34,8 @@ internal class TypeManager typeof(TypeRegistrationCommand), typeof(EmptyCommand), typeof(StringCommand), - typeof(StringCollectionCommand>), - typeof(StringCollectionCommand>), + //typeof(StringCollectionCommand>), + //typeof(StringCollectionCommand>), typeof(StringArrayCommand), typeof(TypeCommand), typeof(PingCommand), @@ -93,10 +93,10 @@ internal bool IsValueTypeInitialized() where T : unmanaged return _registeredObjectTypes.Contains(typeof(T)); } - // internal bool IsDirectCommandTypeInitialized() where T : class?, IMemoryPackable?, new() - // { - // return _typeToIndex.ContainsKey(typeof(T)); - // } + internal bool IsDirectCommandTypeInitialized() where T : class?, IMemoryPackable?, new() + { + return _typeToIndex.ContainsKey(typeof(T)); + } internal void RegisterAdditionalValueType() where T : unmanaged { @@ -153,7 +153,7 @@ internal void InitDirectCommandType(Type type) _registerDirectCommandTypeMethod!.MakeGenericMethod(type).Invoke(this, null); } - private void RegisterDirectCommandType() where T : class, IMemoryPackable, new() + internal void RegisterDirectCommandType() where T : class, IMemoryPackable, new() { var type = typeof(T); diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index de92c03..1b4cbcd 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -1,4 +1,3 @@ -using System.Reflection; using Renderite.Shared; using Renderite.Unity; @@ -23,16 +22,16 @@ private static void InnerInit() { Messenger.OnWarning = (msg) => { - UnityEngine.Debug.LogWarning($"[InterprocessLib] [WARN] {msg}"); + Debug.LogWarning($"[InterprocessLib] [WARN] {msg}"); }; Messenger.OnFailure = (ex) => { - UnityEngine.Debug.LogError($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Host!\n{ex}"); + Debug.LogError($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Host!\n{ex}"); }; #if DEBUG Messenger.OnDebug = (msg) => { - UnityEngine.Debug.Log($"[InterprocessLib] [DEBUG] {msg}"); + Debug.Log($"[InterprocessLib] [DEBUG] {msg}"); }; #endif diff --git a/InterprocessLib.sln b/InterprocessLib.sln deleted file mode 100644 index 24e9cb3..0000000 --- a/InterprocessLib.sln +++ /dev/null @@ -1,179 +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}" -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 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.Standalone.Tests", "Tests\InterprocessLib.Standalone.Tests\InterprocessLib.Standalone.Tests.csproj", "{2C33CB8C-DE73-DE88-6485-020522AD24C1}" - ProjectSection(ProjectDependencies) = postProject - {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} = {AE5C3E04-176A-4F33-B13A-3D5E83BC9379} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InterprocessLib.Standalone", "InterprocessLib.Standalone\InterprocessLib.Standalone.csproj", "{09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}" -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 - {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 - {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Debug|x64.ActiveCfg = Debug|Any CPU - {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Debug|x64.Build.0 = Debug|Any CPU - {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Debug|x86.ActiveCfg = Debug|Any CPU - {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Debug|x86.Build.0 = Debug|Any CPU - {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Release|Any CPU.Build.0 = Release|Any CPU - {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Release|x64.ActiveCfg = Release|Any CPU - {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Release|x64.Build.0 = Release|Any CPU - {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Release|x86.ActiveCfg = Release|Any CPU - {09FB9B8A-2B22-4AC5-8FF1-3701C956CDD7}.Release|x86.Build.0 = Release|Any CPU - {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|x64.ActiveCfg = Debug|Any CPU - {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|x64.Build.0 = Debug|Any CPU - {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|x86.ActiveCfg = Debug|Any CPU - {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Debug|x86.Build.0 = Debug|Any CPU - {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Release|Any CPU.Build.0 = Release|Any CPU - {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Release|x64.ActiveCfg = Release|Any CPU - {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Release|x64.Build.0 = Release|Any CPU - {2C33CB8C-DE73-DE88-6485-020522AD24C1}.Release|x86.ActiveCfg = Release|Any CPU - {2C33CB8C-DE73-DE88-6485-020522AD24C1}.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} - 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d94b091c649e8da094665fb04a8f10a230c974cc Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Mon, 22 Dec 2025 20:28:10 +0000 Subject: [PATCH 34/35] Cleanup, fixes, obsolete old methods --- InterprocessLib.Shared/Commands.cs | 14 +- InterprocessLib.Shared/Messenger.cs | 289 ++++++-------------------- InterprocessLib.Shared/System.cs | 78 ++----- InterprocessLib.Shared/Tests.cs | 4 +- InterprocessLib.Shared/TypeManager.cs | 66 ------ 5 files changed, 87 insertions(+), 364 deletions(-) diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index c73ea1f..cb814ad 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -161,20 +161,18 @@ public override string ToString() } } -internal class TypeCommand : IdentifiableCommand +internal class TypeCommand : IMemoryPackable { public Type? Type; private static Dictionary _typeCache = new(); - public override void Pack(ref MemoryPacker packer) + public void Pack(ref MemoryPacker packer) { - base.Pack(ref packer); PackType(Type, ref packer); } - public override void Unpack(ref MemoryUnpacker unpacker) + public void Unpack(ref MemoryUnpacker unpacker) { - base.Unpack(ref unpacker); Type = UnpackType(ref unpacker); } @@ -274,7 +272,7 @@ private void PackType(Type? type, ref MemoryPacker packer) public override string ToString() { - return $"TypeCommand: {Type?.FullName ?? "NULL"}:{Owner}:{Id}"; + return $"TypeCommand: {Type?.FullName ?? "NULL"}"; } } @@ -419,7 +417,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) internal sealed class ObjectArrayCommand : CollectionCommand where T : class?, IMemoryPackable?, new() { - public T?[]? Objects; + public T[]? Objects; public override IEnumerable? UntypedCollection => Objects; public override Type StoredType => typeof(T); @@ -614,9 +612,7 @@ public override void Unpack(ref MemoryUnpacker unpacker) 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/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index 5e64212..b168463 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -201,6 +201,7 @@ private void DefaultInit() } else { + // 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) @@ -371,47 +372,19 @@ public void SendValue(string id, T value) where T : unmanaged CurrentSystem!.SendPackable(command); } - // public void SendValueList(string id, List? list) where T : unmanaged - // { - // if (id is null) - // throw new ArgumentNullException(nameof(id)); - - // if (!IsInitialized) - // { - // RunPostInit(() => SendValueList(id, list)); - // return; - // } - - // CurrentSystem!.EnsureValueTypeInitialized(); - - // var command = new ValueCollectionCommand, T>(); - // command.Owner = _ownerId; - // command.Id = id; - // command.Values = list; - // CurrentSystem!.SendPackable(command); - // } - - // public void SendValueHashSet(string id, HashSet? hashSet) where T : unmanaged - // { - // if (id is null) - // throw new ArgumentNullException(nameof(id)); - - // if (!IsInitialized) - // { - // RunPostInit(() => SendValueHashSet(id, hashSet)); - // return; - // } - - // CurrentSystem!.EnsureValueTypeInitialized(); + [Obsolete("Use SendValueCollection instead.")] + public void SendValueList(string id, List? list) where T : unmanaged + { + SendValueCollection, T>(id, list); + } - // var command = new ValueCollectionCommand, T>(); - // command.Owner = _ownerId; - // command.Id = id; - // command.Values = hashSet; - // CurrentSystem!.SendPackable(command); - // } + [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 + public void SendValueCollection(string id, C? collection) where C : ICollection?, new() where T : unmanaged { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -442,7 +415,7 @@ public void SendValueArray(string id, T[]? array) where T : unmanaged return; } - CurrentSystem!.EnsureValueTypeInitialized(); + CurrentSystem!.EnsureValueArrayTypeInitialized(); var command = new ValueArrayCommand(); command.Owner = _ownerId; @@ -469,23 +442,11 @@ public void SendString(string id, string? str) CurrentSystem!.SendPackable(command); } - // public void SendStringList(string id, IEnumerable? list) - // { - // if (id is null) - // throw new ArgumentNullException(nameof(id)); - - // if (!IsInitialized) - // { - // RunPostInit(() => SendStringList(id, list)); - // return; - // } - - // var command = new StringCollectionCommand>(); - // command.Owner = _ownerId; - // command.Id = id; - // command.Strings = list; - // 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() { @@ -525,24 +486,6 @@ public void SendStringArray(string id, string?[]? array) CurrentSystem!.SendPackable(command); } - // public void SendStringHashSet(string id, IEnumerable? set) - // { - // if (id is null) - // throw new ArgumentNullException(nameof(id)); - - // if (!IsInitialized) - // { - // RunPostInit(() => SendStringHashSet(id, set)); - // return; - // } - - // var command = new StringCollectionCommand>(); - // command.Owner = _ownerId; - // command.Id = id; - // command.Strings = set; - // CurrentSystem!.SendPackable(command); - // } - public void SendEmptyCommand(string id) { if (id is null) @@ -581,47 +524,13 @@ public void SendEmptyCommand(string id) CurrentSystem!.SendPackable(command); } - // public void SendObjectList(string id, List? list) where T : class?, IMemoryPackable?, new() - // { - // if (id is null) - // throw new ArgumentNullException(nameof(id)); - - // if (!IsInitialized) - // { - // RunPostInit(() => SendObjectList(id, list)); - // return; - // } - - // CurrentSystem!.EnsureObjectTypeInitialized(); - - // var command = new ObjectCollectionCommand, T>(); - // command.Owner = _ownerId; - // command.Id = id; - // command.Objects = list; - // CurrentSystem!.SendPackable(command); - // } - - // public void SendObjectHashSet(string id, HashSet? hashSet) where T : class?, IMemoryPackable?, new() - // { - // if (id is null) - // throw new ArgumentNullException(nameof(id)); - - // if (!IsInitialized) - // { - // RunPostInit(() => SendObjectHashSet(id, hashSet)); - // return; - // } - - // CurrentSystem!.EnsureObjectTypeInitialized(); - - // var command = new ObjectCollectionCommand, T>(); - // command.Owner = _ownerId; - // command.Id = id; - // command.Objects = hashSet; - // 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 SendObjectCollection(string id, C collection) where C : ICollection?, new() 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)); @@ -652,7 +561,7 @@ public void SendEmptyCommand(string id) return; } - CurrentSystem!.EnsureObjectTypeInitialized(); + CurrentSystem!.EnsureObjectArrayTypeInitialized(); var command = new ObjectArrayCommand(); command.Owner = _ownerId; @@ -675,19 +584,17 @@ public void ReceiveValue(string id, Action? callback) where T : unmanaged CurrentSystem!.RegisterValueCallback(_ownerId, id, callback); } - // public void ReceiveValueList(string id, Action?>? callback) where T : unmanaged - // { - // if (id is null) - // throw new ArgumentNullException(nameof(id)); - - // if (!IsInitialized) - // { - // RunPostInit(() => ReceiveValueList(id, callback)); - // return; - // } + [Obsolete("Use ReceiveValueCollection instead.")] + public void ReceiveValueList(string id, Action?>? callback) where T : unmanaged + { + ReceiveValueCollection, T>(id, callback); + } - // CurrentSystem!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); - // } + [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 { @@ -703,20 +610,6 @@ public void ReceiveValue(string id, Action? callback) where T : unmanaged CurrentSystem!.RegisterValueCollectionCallback(_ownerId, id, callback); } - // public void ReceiveValueHashSet(string id, Action?>? callback) where T : unmanaged - // { - // if (id is null) - // throw new ArgumentNullException(nameof(id)); - - // if (!IsInitialized) - // { - // RunPostInit(() => ReceiveValueHashSet(id, callback)); - // return; - // } - - // CurrentSystem!.RegisterValueCollectionCallback, T>(_ownerId, id, callback); - // } - public void ReceiveValueArray(string id, Action? callback) where T : unmanaged { if (id is null) @@ -745,19 +638,11 @@ public void ReceiveString(string id, Action? callback) CurrentSystem!.RegisterStringCallback(_ownerId, id, callback); } - // public void ReceiveStringList(string id, Action?>? callback) - // { - // if (id is null) - // throw new ArgumentNullException(nameof(id)); - - // if (!IsInitialized) - // { - // RunPostInit(() => ReceiveStringList(id, callback)); - // return; - // } - - // CurrentSystem!.RegisterStringCollectionCallback>(_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() { @@ -787,20 +672,6 @@ public void ReceiveStringArray(string id, Action? callback) CurrentSystem!.RegisterStringArrayCallback(_ownerId, id, callback!); } - // public void ReceiveStringHashSet(string id, Action?>? callback) - // { - // if (id is null) - // throw new ArgumentNullException(nameof(id)); - - // if (!IsInitialized) - // { - // RunPostInit(() => ReceiveStringHashSet(id, callback)); - // return; - // } - - // CurrentSystem!.RegisterStringCollectionCallback>(_ownerId, id, callback); - // } - public void ReceiveEmptyCommand(string id, Action? callback) { if (id is null) @@ -815,7 +686,7 @@ public void ReceiveEmptyCommand(string id, Action? 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)); @@ -829,19 +700,11 @@ public void ReceiveEmptyCommand(string id, Action? callback) CurrentSystem!.RegisterObjectCallback(_ownerId, id, callback); } - // public void ReceiveObjectList(string id, Action?>? callback) where T : class?, IMemoryPackable?, new() - // { - // if (id is null) - // throw new ArgumentNullException(nameof(id)); - - // if (!IsInitialized) - // { - // RunPostInit(() => ReceiveObjectList(id, callback)); - // return; - // } - - // CurrentSystem!.RegisterObjectCollectionCallback, T>(_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 ReceiveObjectCollection(string id, Action? callback) where C : ICollection?, new() where T : class?, IMemoryPackable?, new() { @@ -857,21 +720,7 @@ public void ReceiveEmptyCommand(string id, Action? callback) CurrentSystem!.RegisterObjectCollectionCallback(_ownerId, id, callback); } - // public void ReceiveObjectHashSet(string id, Action?>? callback) where T : class?, IMemoryPackable?, new() - // { - // if (id is null) - // throw new ArgumentNullException(nameof(id)); - - // if (!IsInitialized) - // { - // RunPostInit(() => ReceiveObjectHashSet(id, callback)); - // return; - // } - - // CurrentSystem!.RegisterObjectCollectionCallback, T>(_ownerId, id, callback); - // } - - public void ReceiveObjectArray(string id, Action? callback) where T : class?, IMemoryPackable?, new() + public void ReceiveObjectArray(string id, Action? callback) where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -885,52 +734,34 @@ public void ReceiveEmptyCommand(string id, Action? callback) CurrentSystem!.RegisterObjectArrayCallback(_ownerId, id, callback); } - 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); - } - + // A dirty hack because I'm lazy public void SendType(string id, Type? type) { - if (id is null) - throw new ArgumentNullException(nameof(id)); - - if (!IsInitialized) - { - RunPostInit(() => SendType(id, type)); - return; - } - var typeCmd = new TypeCommand(); - typeCmd.Owner = _ownerId; - typeCmd.Id = id; typeCmd.Type = type; - CurrentSystem!.SendPackable(typeCmd); + SendObject(id, typeCmd); } + // A dirty hack because I'm lazy public void ReceiveType(string id, Action? callback) { - if (id is null) - throw new ArgumentNullException(nameof(id)); + ReceiveObject(id, (typeCmd) => callback?.Invoke(typeCmd.Type)); + } + public void CheckLatency(Action callback) + { if (!IsInitialized) { - RunPostInit(() => ReceiveType(id, callback)); + RunPostInit(() => CheckLatency(callback)); return; } - CurrentSystem!.RegisterTypeCallback(_ownerId, id, callback); + 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() diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 5e0c92c..51479f6 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -27,8 +27,6 @@ private struct OwnerData public readonly Dictionary ObjectCollectionCallbacks = new(); - public readonly Dictionary?> TypeCallbacks = new(); - public OwnerData() { } @@ -86,8 +84,6 @@ public OwnerData() private bool _messengerReadyCommandReceived = false; - //private readonly CancellationTokenSource _cancel = new(); - internal void SetPostInitActions(List? actions) { if (IsInitialized) @@ -187,12 +183,12 @@ 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() + 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() + public void RegisterObjectArrayCallback(string owner, string id, Action? callback) where T : class?, IMemoryPackable?, new() { _ownerData[owner].ObjectArrayCallbacks[id] = callback; } @@ -202,11 +198,6 @@ public void RegisterEmptyCallback(string owner, string id, Action? callback) _ownerData[owner].ObjectCollectionCallbacks[id] = callback; } - public void RegisterTypeCallback(string owner, string id, Action? callback) - { - _ownerData[owner].TypeCallbacks[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)); @@ -242,16 +233,8 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I }; _backends.Add(QueueName, this); - - // _primary.StartKeepAlive(2500); - // _cancel.Token.Register(Dispose); } - // private void OnKeepAlive() - // { - // _cancel.CancelAfter(5000); - // } - public void Dispose() { _primary?.Dispose(); @@ -429,31 +412,10 @@ private void HandlePingCommand(PingCommand ping) } } - private void HandleTypeCommand(TypeCommand command) - { - if (_ownerData[command.Owner!].TypeCallbacks.TryGetValue(command.Id!, out var callback)) - { - if (callback != null) - { - callback.Invoke(command.Type); - } - } - else - { - _onWarning?.Invoke($"IdentifiableTypeCommand with Id \"{command.Id}\" is not registered to receive a callback!"); - } - } - private void CommandHandler(RendererCommand command, int messageSize) { _onCommandReceived?.Invoke(command, messageSize); - // if (command is KeepAlive) - // { - // OnKeepAlive(); - // return; - // } - IMemoryPackable? packable = null; if (command is WrapperCommand wrapperCommand) { @@ -538,10 +500,6 @@ private void CommandHandler(RendererCommand command, int messageSize) { HandleStringArrayCommand(stringArrayCommand); } - else if (packable is TypeCommand typeCommand) - { - HandleTypeCommand(typeCommand); - } else if (packable is CollectionCommand collectionCommand) { var collectionType = collectionCommand.CollectionType; @@ -588,6 +546,7 @@ private void CommandHandler(RendererCommand command, int messageSize) // 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"}"); } } @@ -606,30 +565,35 @@ public void SendPackable(IMemoryPackable packable) _primary!.SendCommand(wrapper); } - public void SendRendererCommand(RendererCommand command) + internal void EnsureValueTypeInitialized() where T : unmanaged { - if (command is null) throw new ArgumentNullException(nameof(command)); - - if (!IsConnected) throw new InvalidOperationException("Not connected!"); - - _onDebug?.Invoke($"Sending RendererCommand: {command}"); + if (!OutgoingTypeManager.IsDirectCommandTypeInitialized>()) + { + OutgoingTypeManager.RegisterDirectCommandType>(); + } + } - _primary!.SendCommand(command); + internal void EnsureObjectTypeInitialized() where T : class?, IMemoryPackable?, new() + { + if (!OutgoingTypeManager.IsDirectCommandTypeInitialized>()) + { + OutgoingTypeManager.RegisterDirectCommandType>(); + } } - internal void EnsureValueTypeInitialized() where T : unmanaged + internal void EnsureValueArrayTypeInitialized() where T : unmanaged { - if (!OutgoingTypeManager.IsValueTypeInitialized()) + if (!OutgoingTypeManager.IsDirectCommandTypeInitialized>()) { - OutgoingTypeManager.RegisterAdditionalValueType(); + OutgoingTypeManager.RegisterDirectCommandType>(); } } - internal void EnsureObjectTypeInitialized() where T : class?, IMemoryPackable?, new() + internal void EnsureObjectArrayTypeInitialized() where T : class?, IMemoryPackable?, new() { - if (!OutgoingTypeManager.IsObjectTypeInitialized()) + if (!OutgoingTypeManager.IsDirectCommandTypeInitialized>()) { - OutgoingTypeManager.RegisterAdditionalObjectType(); + OutgoingTypeManager.RegisterDirectCommandType>(); } } diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index 06c7db1..caf1db0 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -50,7 +50,6 @@ static void TestTypeCommand() static void TestValueArray() { - var test = new ValueArrayCommand(); _messenger!.ReceiveValueArray("TestValueArray", (arr) => { _logCallback!($"TestValueArray: {string.Join(",", arr!)}"); @@ -64,8 +63,7 @@ static void TestValueArray() static void TestObjectArray() { - var test = new ObjectArrayCommand(); - _messenger!.ReceiveObjectArray("TestObjectArray", (arr) => + _messenger!.ReceiveObjectArray("TestObjectArray", (arr) => { _logCallback!($"TestObjectArray: {string.Join(",", arr!)}"); }); diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index 60c5179..f9e0a52 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -5,10 +5,6 @@ namespace InterprocessLib; internal class TypeManager { - private readonly HashSet _registeredObjectTypes = new(); - - private readonly HashSet _registeredValueTypes = new(); - private bool _initializedCoreTypes = false; private static readonly MethodInfo _registerDirectCommandTypeMethod = typeof(TypeManager).GetMethod(nameof(RegisterDirectCommandType), BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new MissingMethodException(nameof(RegisterDirectCommandType)); @@ -34,8 +30,6 @@ internal class TypeManager typeof(TypeRegistrationCommand), typeof(EmptyCommand), typeof(StringCommand), - //typeof(StringCollectionCommand>), - //typeof(StringCollectionCommand>), typeof(StringArrayCommand), typeof(TypeCommand), typeof(PingCommand), @@ -83,71 +77,11 @@ internal int GetTypeIndex(Type type) return _typeToIndex[type]; } - internal bool IsValueTypeInitialized() where T : unmanaged - { - return _registeredValueTypes.Contains(typeof(T)); - } - - internal bool IsObjectTypeInitialized() where T : class?, IMemoryPackable?, new() - { - return _registeredObjectTypes.Contains(typeof(T)); - } - internal bool IsDirectCommandTypeInitialized() where T : class?, IMemoryPackable?, new() { return _typeToIndex.ContainsKey(typeof(T)); } - internal void RegisterAdditionalValueType() where T : unmanaged - { - var type = typeof(T); - - Messenger.OnDebug?.Invoke($"Registering additional value type: {type.Name}"); - - 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!"); - - var valueCommandType = typeof(ValueCommand<>).MakeGenericType(type); - - var valueArrayCommandType = typeof(ValueArrayCommand<>).MakeGenericType(type); - - var valueListCommandType = typeof(ValueCollectionCommand<,>).MakeGenericType(typeof(List), type); - - var valueHashSetCommandType = typeof(ValueCollectionCommand<,>).MakeGenericType(typeof(HashSet), type); - - PushNewTypes([valueCommandType, valueArrayCommandType, valueListCommandType, valueHashSetCommandType]); - - _registeredValueTypes.Add(type); - } - - internal void RegisterAdditionalObjectType() where T : class?, IMemoryPackable?, new() - { - var type = typeof(T); - - Messenger.OnDebug?.Invoke($"Registering additional object type: {type.Name}"); - - if (_registeredObjectTypes.Contains(type)) - throw new InvalidOperationException($"Type {type.Name} is already registered!"); - - if (type.ContainsGenericParameters) - throw new ArgumentException($"Type must be a concrete type!"); - - var objectCommandType = typeof(ObjectCommand<>).MakeGenericType(type); - - var objectArrayCommandType = typeof(ObjectArrayCommand<>).MakeGenericType(type); - - var objectListCommandType = typeof(ObjectCollectionCommand<,>).MakeGenericType(typeof(List), type); - - var objectHashSetCommandType = typeof(ObjectCollectionCommand<,>).MakeGenericType(typeof(HashSet), type); - - PushNewTypes([type, objectCommandType, objectArrayCommandType, objectListCommandType, objectHashSetCommandType]); - - _registeredObjectTypes.Add(type); - } - internal void InitDirectCommandType(Type type) { _registerDirectCommandTypeMethod!.MakeGenericMethod(type).Invoke(this, null); From 2dde492cfa148b4ce5f8bfb6c2278e1e2da02a5d Mon Sep 17 00:00:00 2001 From: Nytra <14206961+Nytra@users.noreply.github.com> Date: Tue, 23 Dec 2025 02:12:40 +0000 Subject: [PATCH 35/35] Restructure a lot of stuff --- .../FrooxEngineInit.cs | 18 +- InterprocessLib.Shared/Commands.cs | 25 +- InterprocessLib.Shared/Messenger.cs | 131 +++-- InterprocessLib.Shared/System.cs | 466 +++--------------- InterprocessLib.Shared/Tests.cs | 53 +- InterprocessLib.Shared/TypeManager.cs | 13 +- InterprocessLib.Standalone/StandaloneInit.cs | 41 ++ InterprocessLib.Unity/UnityInit.cs | 16 +- .../BepInExTests.cs | 22 +- .../BepisLoaderTests.cs | 50 +- Tests/InterprocessLib.RML.Tests/RML_Tests.cs | 43 +- .../Program.cs | 12 +- 12 files changed, 220 insertions(+), 670 deletions(-) create mode 100644 InterprocessLib.Standalone/StandaloneInit.cs diff --git a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs index 8e39ecb..8f5ecd6 100644 --- a/InterprocessLib.FrooxEngine/FrooxEngineInit.cs +++ b/InterprocessLib.FrooxEngine/FrooxEngineInit.cs @@ -18,18 +18,9 @@ void IMemoryPackerEntityPool.Return(T value) } } -internal static class FrooxEngineInit +internal static class Initializer { public static void Init() - { - if (Messenger.DefaultInitStarted) - throw new InvalidOperationException("Messenger default backend initialization has already been started!"); - - Messenger.DefaultInitStarted = true; - - InnerInit(); - } - private static void InnerInit() { var args = Environment.GetCommandLineArgs(); string? queueName = null; @@ -59,10 +50,11 @@ private static void InnerInit() MessagingSystem? system = null; + // If the queue name is null then the engine doesn't have a renderer, such as when it's a headless 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); + Messenger.OnWarning?.Invoke("Default shared memory queue name is null! This can happen on headless. Attempting to use fallback..."); + var task = Messenger.GetFallbackSystem("Fallback", true, MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); task.Wait(); system = task.Result; if (system is null) @@ -72,7 +64,7 @@ private static void InnerInit() } else { - system = new MessagingSystem(true, $"InterprocessLib-{queueName}", MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + system = new MessagingSystem(true, $"InterprocessLib-{queueName}", MessagingManager.DEFAULT_CAPACITY, FrooxEnginePool.Instance, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); system.Connect(); } diff --git a/InterprocessLib.Shared/Commands.cs b/InterprocessLib.Shared/Commands.cs index cb814ad..aac7e4d 100644 --- a/InterprocessLib.Shared/Commands.cs +++ b/InterprocessLib.Shared/Commands.cs @@ -161,7 +161,7 @@ public override string ToString() } } -internal class TypeCommand : IMemoryPackable +public class TypeCommand : IMemoryPackable { public Type? Type; private static Dictionary _typeCache = new(); @@ -548,18 +548,15 @@ public void Unpack(ref MemoryUnpacker unpacker) internal sealed class PingCommand : IMemoryPackable { - public DateTime SentTime; - public DateTime? ReceivedTime; + public bool Received; public void Pack(ref MemoryPacker packer) { - packer.Write(SentTime); - packer.Write(ReceivedTime); + packer.Write(Received); } public void Unpack(ref MemoryUnpacker unpacker) { - unpacker.Read(ref SentTime); - unpacker.Read(ref ReceivedTime); + unpacker.Read(ref Received); } } @@ -576,18 +573,14 @@ public static void InitNewTypes(List types) public override void Pack(ref MemoryPacker packer) { + if (Packable is null) throw new InvalidOperationException("Cannot send WrapperCommand with null Packable!"); + 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!); @@ -599,12 +592,6 @@ 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); diff --git a/InterprocessLib.Shared/Messenger.cs b/InterprocessLib.Shared/Messenger.cs index b168463..c683775 100644 --- a/InterprocessLib.Shared/Messenger.cs +++ b/InterprocessLib.Shared/Messenger.cs @@ -67,7 +67,9 @@ public class Messenger : IDisposable 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) + private DateTime _lastPingTime; + + internal static async Task GetFallbackSystem(string ownerId, bool isAuthority, long queueCapacity, IMemoryPackerEntityPool? pool = null, Action? failhandler = null, Action? warnHandler = null, Action? debugHandler = null, Action? postInitCallback = null) { OnDebug?.Invoke("GetFallbackSystem called"); @@ -82,7 +84,7 @@ public class Messenger : IDisposable 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); + var system1 = new MessagingSystem(isAuthority, $"InterprocessLib-{ownerId}{minuteInDay}", queueCapacity, pool ?? FallbackPool.Instance, failhandler, warnHandler, debugHandler, postInitCallback); system1.Connect(); if (isAuthority) { @@ -91,11 +93,12 @@ public class Messenger : IDisposable return system1; } var cancel1 = new CancellationTokenSource(); - system1.PingCallback = (ping) => + system1.RegisterCallback((ping) => { cancel1.Cancel(); - }; - system1.SendPackable(new PingCommand() { SentTime = now }); + system1.RegisterCallback(null); + }); + system1.SendPackable(new PingCommand()); try { await Task.Delay(waitTimeMs, cancel1.Token); @@ -112,13 +115,14 @@ public class Messenger : IDisposable // 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); + var system2 = new MessagingSystem(isAuthority, $"InterprocessLib-{ownerId}{minuteInDay - 1}", queueCapacity, pool ?? FallbackPool.Instance, failhandler, warnHandler, debugHandler, postInitCallback); system2.Connect(); - system2.PingCallback = (ping) => + system2.RegisterCallback((ping) => { cancel2.Cancel(); - }; - system2.SendPackable(new PingCommand() { SentTime = now }); + system2.RegisterCallback(null); + }); + system2.SendPackable(new PingCommand()); try { await Task.Delay(waitTimeMs, cancel2.Token); @@ -187,28 +191,15 @@ private void DefaultInit() if (_defaultSystem is null && !DefaultInitStarted) { - var frooxEngineInitType = Type.GetType("InterprocessLib.FrooxEngineInit"); - if (frooxEngineInitType is not null) + DefaultInitStarted = true; + var initType = Type.GetType("InterprocessLib.Initializer"); + if (initType is not null) { - frooxEngineInitType.GetMethod("Init", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)!.Invoke(null, null); + initType.GetMethod("Init", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)!.Invoke(null, null); } else { - var unityInitType = Type.GetType("InterprocessLib.UnityInit"); - if (unityInitType is not null) - { - unityInitType.GetMethod("Init", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public)!.Invoke(null, null); - } - else - { - // 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; - } + throw new EntryPointNotFoundException("Could not find InterprocessLib initialization type!"); } } } @@ -255,7 +246,7 @@ public Messenger(string ownerId, bool isAuthority, string queueName, IMemoryPack if (MessagingSystem.TryGetRegisteredSystem(queueName) is not MessagingSystem existingSystem) { - _customSystem = new MessagingSystem(isAuthority, queueName, queueCapacity, actualPool, null, OnFailure, OnWarning, OnDebug); + _customSystem = new MessagingSystem(isAuthority, queueName, queueCapacity, actualPool, OnFailure, OnWarning, OnDebug); } else { @@ -363,8 +354,6 @@ public void SendValue(string id, T value) where T : unmanaged return; } - CurrentSystem!.EnsureValueTypeInitialized(); - var command = new ValueCommand(); command.Owner = _ownerId; command.Id = id; @@ -395,8 +384,6 @@ public void SendValueHashSet(string id, HashSet? hashSet) where T : unmana return; } - CurrentSystem!.EnsureValueCollectionTypeInitialized(); - var command = new ValueCollectionCommand(); command.Owner = _ownerId; command.Id = id; @@ -415,8 +402,6 @@ public void SendValueArray(string id, T[]? array) where T : unmanaged return; } - CurrentSystem!.EnsureValueArrayTypeInitialized(); - var command = new ValueArrayCommand(); command.Owner = _ownerId; command.Id = id; @@ -459,8 +444,6 @@ public void SendStringList(string id, List? list) return; } - CurrentSystem!.EnsureStringCollectionTypeInitialized(); - var command = new StringCollectionCommand(); command.Owner = _ownerId; command.Id = id; @@ -503,7 +486,7 @@ public void SendEmptyCommand(string id) 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)); @@ -514,8 +497,6 @@ public void SendEmptyCommand(string id) return; } - CurrentSystem!.EnsureObjectTypeInitialized(); - var command = new ObjectCommand(); command.Object = obj; command.Owner = _ownerId; @@ -541,8 +522,6 @@ public void SendEmptyCommand(string id) return; } - CurrentSystem!.EnsureObjectCollectionTypeInitialized(); - var command = new ObjectCollectionCommand(); command.Owner = _ownerId; command.Id = id; @@ -561,8 +540,6 @@ public void SendEmptyCommand(string id) return; } - CurrentSystem!.EnsureObjectArrayTypeInitialized(); - var command = new ObjectArrayCommand(); command.Owner = _ownerId; command.Id = id; @@ -581,7 +558,7 @@ public void ReceiveValue(string id, Action? callback) where T : unmanaged return; } - CurrentSystem!.RegisterValueCallback(_ownerId, id, callback); + CurrentSystem!.RegisterCallback>(_ownerId, id, (cmd) => callback?.Invoke(cmd.Value)); } [Obsolete("Use ReceiveValueCollection instead.")] @@ -596,7 +573,7 @@ public void ReceiveValueHashSet(string id, Action?>? callback) whe ReceiveValueCollection, T>(id, callback); } - public void ReceiveValueCollection(string id, Action? callback) where C : ICollection?, new() where T : unmanaged + public void ReceiveValueCollection(string id, Action? callback) where C : ICollection?, new() where T : unmanaged { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -607,7 +584,7 @@ public void ReceiveValueHashSet(string id, Action?>? callback) whe return; } - CurrentSystem!.RegisterValueCollectionCallback(_ownerId, id, callback); + CurrentSystem!.RegisterCallback>(_ownerId, id, (cmd) => callback?.Invoke(cmd.Values!)); } public void ReceiveValueArray(string id, Action? callback) where T : unmanaged @@ -621,7 +598,7 @@ public void ReceiveValueArray(string id, Action? callback) where T : un return; } - CurrentSystem!.RegisterValueArrayCallback(_ownerId, id, callback); + CurrentSystem!.RegisterCallback>(_ownerId, id, (cmd) => callback?.Invoke(cmd.Values)); } public void ReceiveString(string id, Action? callback) @@ -635,7 +612,7 @@ public void ReceiveString(string id, Action? callback) return; } - CurrentSystem!.RegisterStringCallback(_ownerId, id, callback); + CurrentSystem!.RegisterCallback(_ownerId, id, (cmd) => callback?.Invoke(cmd.String)); } [Obsolete("Use ReceiveStringCollection instead.")] @@ -644,7 +621,7 @@ public void ReceiveStringList(string id, Action?>? callback) ReceiveStringCollection(id, callback); } - public void ReceiveStringCollection(string id, Action? callback) where C : ICollection?, new() + public void ReceiveStringCollection(string id, Action? callback) where C : ICollection?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -655,7 +632,7 @@ public void ReceiveStringList(string id, Action?>? callback) return; } - CurrentSystem!.RegisterStringCollectionCallback(_ownerId, id, callback); + CurrentSystem!.RegisterCallback>(_ownerId, id, (cmd) => callback?.Invoke((C)cmd.Strings!)); } public void ReceiveStringArray(string id, Action? callback) @@ -669,7 +646,7 @@ public void ReceiveStringArray(string id, Action? callback) return; } - CurrentSystem!.RegisterStringArrayCallback(_ownerId, id, callback!); + CurrentSystem!.RegisterCallback(_ownerId, id, (cmd) => callback?.Invoke(cmd.Strings)); } public void ReceiveEmptyCommand(string id, Action? callback) @@ -683,7 +660,7 @@ public void ReceiveEmptyCommand(string id, Action? callback) return; } - CurrentSystem!.RegisterEmptyCallback(_ownerId, id, callback); + CurrentSystem!.RegisterCallback(_ownerId, id, (cmd) => callback?.Invoke()); } public void ReceiveObject(string id, Action? callback) where T : class?, IMemoryPackable?, new() @@ -697,7 +674,7 @@ public void ReceiveEmptyCommand(string id, Action? callback) return; } - CurrentSystem!.RegisterObjectCallback(_ownerId, id, callback); + CurrentSystem!.RegisterCallback>(_ownerId, id, (cmd) => callback?.Invoke(cmd.Object!)); } [Obsolete("Use ReceiveObjectCollection instead.")] @@ -706,7 +683,7 @@ public void ReceiveEmptyCommand(string id, Action? callback) ReceiveObjectCollection, T>(id, callback); } - public void ReceiveObjectCollection(string id, Action? callback) where C : ICollection?, new() 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)); @@ -717,10 +694,10 @@ public void ReceiveEmptyCommand(string id, Action? callback) return; } - CurrentSystem!.RegisterObjectCollectionCallback(_ownerId, id, callback); + CurrentSystem!.RegisterCallback>(_ownerId, id, (cmd) => callback?.Invoke(cmd.Objects!)); } - public void ReceiveObjectArray(string id, Action? callback) where T : class?, IMemoryPackable?, new() + public void ReceiveObjectArray(string id, Action? callback) where T : class?, IMemoryPackable?, new() { if (id is null) throw new ArgumentNullException(nameof(id)); @@ -731,37 +708,41 @@ public void ReceiveEmptyCommand(string id, Action? callback) return; } - CurrentSystem!.RegisterObjectArrayCallback(_ownerId, id, callback); + CurrentSystem!.RegisterCallback>(_ownerId, id, (cmd) => callback?.Invoke(cmd.Objects!)); } - // A dirty hack because I'm lazy - public void SendType(string id, Type? type) + /// + /// Send a ping message which will be received and then sent back by the other process + /// Can be used to check if the other process is active, or to check the latency of the connection + /// Register a ping callback with first. + /// + public void SendPing() { - var typeCmd = new TypeCommand(); - typeCmd.Type = type; - - SendObject(id, typeCmd); - } + if (!IsInitialized) + { + RunPostInit(() => SendPing()); + return; + } - // A dirty hack because I'm lazy - public void ReceiveType(string id, Action? callback) - { - ReceiveObject(id, (typeCmd) => callback?.Invoke(typeCmd.Type)); + var pingCommand = new PingCommand(); + _lastPingTime = DateTime.UtcNow; + CurrentSystem!.SendPackable(pingCommand); } - public void CheckLatency(Action callback) + /// + /// Register a delegate to be called when this process gets a ping response + /// Calling should then result in the delegate being called shortly after if the other process is active + /// + /// The delegate to be called when the ping response gets received + public void ReceivePing(Action callback) { if (!IsInitialized) { - RunPostInit(() => CheckLatency(callback)); + RunPostInit(() => ReceivePing(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); + CurrentSystem!.RegisterCallback((ping) => callback?.Invoke(DateTime.UtcNow - _lastPingTime)); } public void Dispose() diff --git a/InterprocessLib.Shared/System.cs b/InterprocessLib.Shared/System.cs index 51479f6..fda0321 100644 --- a/InterprocessLib.Shared/System.cs +++ b/InterprocessLib.Shared/System.cs @@ -7,31 +7,12 @@ 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 readonly Dictionary IdentifiableCallbacks = new(); public OwnerData() { } } - public bool IsAuthority { get; } public string QueueName { get; } @@ -40,21 +21,9 @@ public OwnerData() 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 _handleIdentifiableCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleIdentifiableCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleIdentifiableCommand)); - private static readonly MethodInfo _handleStringCollectionCommandMethod = typeof(MessagingSystem).GetMethod(nameof(HandleStringCollectionCommand), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandleStringCollectionCommand)); - - private RenderCommandHandler? _onCommandReceived { get; } + private static readonly MethodInfo _handlePackableMethod = typeof(MessagingSystem).GetMethod(nameof(HandlePackable), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new MissingMethodException(nameof(HandlePackable)); private Action? _onWarning { get; } @@ -64,6 +33,8 @@ public OwnerData() private readonly Dictionary _ownerData = new(); + private readonly Dictionary _typedCallbacks = new(); + private Action? _postInitCallback; public bool IsConnected { get; private set; } @@ -78,8 +49,6 @@ public OwnerData() private static readonly Dictionary _backends = new(); - internal Action? PingCallback; - private readonly IMemoryPackerEntityPool _pool; private bool _messengerReadyCommandReceived = false; @@ -139,8 +108,7 @@ internal void RunPostInit(Action act) public void RegisterOwner(string ownerName) { - var ownerData = new OwnerData(); - _ownerData.Add(ownerName, ownerData); + _ownerData.Add(ownerName, new()); } public bool HasOwner(string ownerName) @@ -148,57 +116,17 @@ 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 + public void RegisterCallback(string owner, string id, Action? callback) where T : IdentifiableCommand { - _ownerData[owner].ValueArrayCallbacks[id] = callback; + _ownerData[owner].IdentifiableCallbacks[id] = callback; } - public void RegisterStringCallback(string owner, string id, Action? callback) + public void RegisterCallback(Action? callback) where T : class, IMemoryPackable, new() { - _ownerData[owner].StringCallbacks[id] = callback; + _typedCallbacks[typeof(T)] = 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) + public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, IMemoryPackerEntityPool pool, 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)); @@ -210,7 +138,6 @@ public MessagingSystem(bool isAuthority, string queueName, long queueCapacity, I _onDebug = debugHandler; _onWarning = warnHandler; _onFailure = failhandler; - _onCommandReceived = commandHandler; _postInitCallback = postInitCallback; @@ -248,315 +175,118 @@ public void Dispose() 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() + private void HandleIdentifiableCommand(T cmd) where T : IdentifiableCommand { - if (_ownerData[command.Owner!].ObjectCallbacks.TryGetValue(command.Id!, out var callback)) + if (_ownerData[cmd.Owner!].IdentifiableCallbacks.TryGetValue(cmd.Id!, out var callback)) { if (callback != null) { - ((Action)callback).Invoke((T?)command.UntypedObject); + ((Action)callback).Invoke(cmd); } } else { - _onWarning?.Invoke($"ObjectCommand<{command.ObjectType.Name}> with Id \"{command.Id}\" is not registered to receive a callback!"); + _onWarning?.Invoke($"IdentifiableCommand of type {cmd.GetType().Name} with Id \"{cmd.Id}\" and Owner \"{cmd.Owner}\" is not registered to receive a callback!"); } } - private void HandleObjectArrayCommand(ObjectArrayCommand command) where T : class?, IMemoryPackable?, new() + private void HandlePackable(T obj) where T : class, IMemoryPackable, new() { - if (_ownerData[command.Owner!].ObjectArrayCallbacks.TryGetValue(command.Id!, out var callback)) + if (_typedCallbacks.TryGetValue(typeof(T), out var data)) { - if (callback != null) - { - ((Action)callback).Invoke(command.Objects); - } + var callback = (Action?)_typedCallbacks[typeof(T)]; + callback?.Invoke(obj); } 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; + _onWarning?.Invoke($"Packable of type \"{typeof(T)}\" is not registered to receive a callback!"); } } 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; - } + IMemoryPackable packable = wrapperCommand.Packable!; + _onDebug?.Invoke($"{QueueName}: Received {packable}"); - 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) + if (packable is PingCommand ping) { - 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); + if (!ping.Received) + { + ping.Received = true; + SendPackable(ping); + } + else + { + HandlePackable(ping); + } } - else if (packable is StringCommand stringCommand) + else if (packable is MessengerReadyCommand) { - HandleStringCommand(stringCommand); + if (_messengerReadyCommandReceived) + { + _onWarning?.Invoke("Received another MessengerReadyCommand! Registered types will be reset!"); + OutgoingTypeManager = new(_pool, OnOutgoingTypeRegistered); + IncomingTypeManager = new(_pool, null); + SendPackable(new MessengerReadyCommand()); + } + else + { + _messengerReadyCommandReceived = true; + } } - else if (packable is StringArrayCommand stringArrayCommand) + else if (!_messengerReadyCommandReceived) { - HandleStringArrayCommand(stringArrayCommand); + throw new InvalidDataException("MessengerReadyCommand needs to be first!"); } - else if (packable is CollectionCommand collectionCommand) + else if (packable is TypeRegistrationCommand typeRegCommand) { - var collectionType = collectionCommand.CollectionType; - var innerDataType = collectionCommand.StoredType; - if (innerDataType == typeof(string)) + if (typeRegCommand.Type is not null) { - var typedMethod = _handleStringCollectionCommandMethod.MakeGenericMethod(collectionType); - typedMethod.Invoke(this, [packable]); + IncomingTypeManager.InvokeRegisterType(typeRegCommand.Type); } - else if (innerDataType.IsValueType) + else { - if (collectionType.IsArray) - { - var typedMethod = _handleValueArrayCommandMethod.MakeGenericMethod(innerDataType); - typedMethod.Invoke(this, [packable]); - } - else - { - var typedMethod = _handleValueCollectionCommandMethod.MakeGenericMethod(collectionType, innerDataType); - typedMethod.Invoke(this, [packable]); - } + throw new InvalidDataException("Other process tried to register a type that could not be found in this process!"); + } + } + else 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)) + { + _handleIdentifiableCommandMethod.MakeGenericMethod(identifiableCommand.GetType()).Invoke(this, [identifiableCommand]); } else { - if (collectionType.IsArray) - { - var typedMethod = _handleObjectArrayCommandMethod.MakeGenericMethod(innerDataType); - typedMethod.Invoke(this, [packable]); - } - else - { - var typedMethod = _handleObjectCollectionCommandMethod.MakeGenericMethod(collectionType, innerDataType); - typedMethod.Invoke(this, [packable]); - } + _onWarning?.Invoke($"Owner \"{identifiableCommand.Owner}\" is not registered!"); } } else { - throw new InvalidDataException($"Received unrecognized IdentifiableCommand of type {identifiableCommand.GetType().Name}: {identifiableCommand.Owner}:{identifiableCommand.Id}"); + _handlePackableMethod.MakeGenericMethod(packable.GetType()).Invoke(this, [packable]); } } 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"}"); + _onWarning?.Invoke($"{QueueName}: Received an unexpected RendererCommand! {command}"); } } - public void SendPackable(IMemoryPackable packable) + public void SendPackable(T packable) where T : class, IMemoryPackable, new() { if (packable is null) throw new ArgumentNullException(nameof(packable)); if (!IsConnected) throw new InvalidOperationException("Not connected!"); - _onDebug?.Invoke($"Sending packable: {packable}"); + _onDebug?.Invoke($"Sending: {packable}"); + + if (!OutgoingTypeManager.IsTypeRegistered()) + { + OutgoingTypeManager.RegisterType(); + } var wrapper = new WrapperCommand(); wrapper.QueueName = QueueName; @@ -565,62 +295,6 @@ public void SendPackable(IMemoryPackable 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(); diff --git a/InterprocessLib.Shared/Tests.cs b/InterprocessLib.Shared/Tests.cs index caf1db0..f368cc5 100644 --- a/InterprocessLib.Shared/Tests.cs +++ b/InterprocessLib.Shared/Tests.cs @@ -1,7 +1,4 @@ -//#define TEST_COMPILATION - using Renderite.Shared; -using System.Reflection; namespace InterprocessLib.Tests; @@ -41,11 +38,11 @@ public static void RunTests(Messenger messenger, Action logCallback) static void TestTypeCommand() { - _messenger!.ReceiveType("TestTypeCommand", (type) => + _messenger!.ReceiveObject("TestTypeCommand", (cmd) => { - _logCallback!($"TestTypeCommand: {type?.FullName ?? "NULL"}"); + _logCallback!($"TestTypeCommand: {cmd?.Type?.FullName ?? "NULL"}"); }); - _messenger!.SendType("TestTypeCommand", typeof(Dictionary, float>)); + _messenger!.SendObject("TestTypeCommand", new() { Type = typeof(Dictionary, float>) }); } static void TestValueArray() @@ -65,7 +62,7 @@ static void TestObjectArray() { _messenger!.ReceiveObjectArray("TestObjectArray", (arr) => { - _logCallback!($"TestObjectArray: {string.Join(",", arr!)}"); + _logCallback!($"TestObjectArray: {string.Join(",", arr)}"); }); var arr = new TestCommand?[3]; arr[0] = new TestCommand(); @@ -319,38 +316,6 @@ static void TestVanillaObject() var obj = new RendererInitData(); _messenger.SendObject("TestVanillaObject", obj); } - -#if TEST_COMPILATION - //Won't compile - static void TestInvalidType() - { - _messenger!.ReceiveObject("InvalidType", (recvInvalidType) => - { - _logCallback!($"InvalidType: {recvInvalidType?.Exception}"); - }); - - var invalid = new InvalidType(); - - invalid.Exception = new Exception(); - - _messenger!.SendObject("InvalidType", invalid); - } - - // Won't compile - static void TestInvalidStruct() - { - _messenger!.ReceiveValue("StructWithObject", (recvStructWithObject) => - { - _logCallback!($"StructWithObject: {recvStructWithObject.Assembly}"); - }); - - var invalid = new StructWithObject(); - - invalid.Assembly = Assembly.GetExecutingAssembly(); - - _messenger!.SendValue("StructWithObject", invalid); - } -#endif } public class TestCommand : RendererCommand @@ -424,14 +389,4 @@ public struct TestStruct public struct TestNestedStruct { public TestStruct Nested; -} - -public class InvalidType -{ - public Exception? Exception; -} - -public struct StructWithObject -{ - public Assembly Assembly; } \ No newline at end of file diff --git a/InterprocessLib.Shared/TypeManager.cs b/InterprocessLib.Shared/TypeManager.cs index f9e0a52..e8b6c28 100644 --- a/InterprocessLib.Shared/TypeManager.cs +++ b/InterprocessLib.Shared/TypeManager.cs @@ -7,7 +7,7 @@ internal class TypeManager { private bool _initializedCoreTypes = false; - private static readonly MethodInfo _registerDirectCommandTypeMethod = typeof(TypeManager).GetMethod(nameof(RegisterDirectCommandType), BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new MissingMethodException(nameof(RegisterDirectCommandType)); + private static readonly MethodInfo _registerTypeMethod = typeof(TypeManager).GetMethod(nameof(RegisterType), BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new MissingMethodException(nameof(RegisterType)); private readonly List _newTypes = new(); @@ -24,6 +24,7 @@ internal class TypeManager 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)); + // These are types that will be assumed to be already registered in the other process private static readonly List _coreTypes = [ typeof(MessengerReadyCommand), @@ -77,21 +78,21 @@ internal int GetTypeIndex(Type type) return _typeToIndex[type]; } - internal bool IsDirectCommandTypeInitialized() where T : class?, IMemoryPackable?, new() + internal bool IsTypeRegistered() where T : class, IMemoryPackable, new() { return _typeToIndex.ContainsKey(typeof(T)); } - internal void InitDirectCommandType(Type type) + internal void InvokeRegisterType(Type type) { - _registerDirectCommandTypeMethod!.MakeGenericMethod(type).Invoke(this, null); + _registerTypeMethod!.MakeGenericMethod(type).Invoke(this, null); } - internal void RegisterDirectCommandType() where T : class, IMemoryPackable, new() + internal void RegisterType() where T : class, IMemoryPackable, new() { var type = typeof(T); - Messenger.OnDebug?.Invoke($"Registering direct command type: {type.Name}"); + Messenger.OnDebug?.Invoke($"Registering type: {type.Name}"); PushNewTypes([type]); } diff --git a/InterprocessLib.Standalone/StandaloneInit.cs b/InterprocessLib.Standalone/StandaloneInit.cs new file mode 100644 index 0000000..bb50790 --- /dev/null +++ b/InterprocessLib.Standalone/StandaloneInit.cs @@ -0,0 +1,41 @@ +using Renderite.Shared; + +namespace InterprocessLib; + +internal static class Initializer +{ + public static void Init() + { + Messenger.OnWarning = (msg) => + { + Console.WriteLine($"[InterprocessLib] [WARN] {msg}"); + }; + Messenger.OnFailure = (ex) => + { + Console.WriteLine($"[InterprocessLib] [ERROR] Error in InterprocessLib Messaging Backend!\n{ex}"); + }; +#if DEBUG + Messenger.OnDebug = (msg) => + { + Console.WriteLine($"[InterprocessLib] [DEBUG] {msg}"); + }; +#endif + + MessagingSystem? system = null; + + var task = Messenger.GetFallbackSystem("Fallback", false, MessagingManager.DEFAULT_CAPACITY, FallbackPool.Instance, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + task.Wait(); + system = task.Result; + if (system is null) + { + throw new EntryPointNotFoundException("Unable to get fallback messaging system!"); + } + + lock (Messenger.LockObj) + { + Messenger.PreInit(system); + Messenger.SetDefaultSystem(system); + system.Initialize(); + } + } +} \ No newline at end of file diff --git a/InterprocessLib.Unity/UnityInit.cs b/InterprocessLib.Unity/UnityInit.cs index 1b4cbcd..adc26f0 100644 --- a/InterprocessLib.Unity/UnityInit.cs +++ b/InterprocessLib.Unity/UnityInit.cs @@ -7,18 +7,9 @@ namespace InterprocessLib; -internal static class UnityInit +internal static class Initializer { public static void Init() - { - if (Messenger.DefaultInitStarted) - throw new InvalidOperationException("Messenger default host initialization has already been started!"); - - Messenger.DefaultInitStarted = true; - - InnerInit(); - } - private static void InnerInit() { Messenger.OnWarning = (msg) => { @@ -53,7 +44,7 @@ private static void InnerInit() 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); + var fallbackTask = Messenger.GetFallbackSystem("Resonite", false, MessagingManager.DEFAULT_CAPACITY, PackerMemoryPool.Instance, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); fallbackTask.Wait(); system = fallbackTask.Result; if (system is null) @@ -61,8 +52,7 @@ private static void InnerInit() } else { - - system = new MessagingSystem(false, $"InterprocessLib-{engineSharedMemoryPrefix}", MessagingManager.DEFAULT_CAPACITY, PackerMemoryPool.Instance, null, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); + system = new MessagingSystem(false, $"InterprocessLib-{engineSharedMemoryPrefix}", MessagingManager.DEFAULT_CAPACITY, PackerMemoryPool.Instance, Messenger.OnFailure, Messenger.OnWarning, Messenger.OnDebug); system.Connect(); } diff --git a/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs b/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs index c2afaa0..88c68de 100644 --- a/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs +++ b/Tests/InterprocessLib.BepInEx.Tests/BepInExTests.cs @@ -1,6 +1,4 @@ -//#define TEST_OBSOLETE_CONSTRUCTOR - -using BepInEx; +using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; @@ -11,11 +9,6 @@ public class UnityPlugin : BaseUnityPlugin { public static ManualLogSource? Log; private static Messenger? _messenger; - private static Messenger? _unknownMessenger; - -#if TEST_OBSOLETE_CONSTRUCTOR - private static Messenger? _testObsoleteConstructor; -#endif public static ConfigEntry? SyncTest; @@ -23,20 +16,11 @@ void Awake() { Log = base.Logger; _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, Log!.LogInfo); - Tests.RunTests(_unknownMessenger, Log!.LogInfo); - -#if TEST_OBSOLETE_CONSTRUCTOR - Tests.RunTests(_testObsoleteConstructor, Log!.LogInfo); -#endif }); _messenger.ReceiveEmptyCommand("CheckSync", () => { @@ -47,9 +31,5 @@ void Awake() SyncTest.Value = 0; }); 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.BepisLoader.Tests/BepisLoaderTests.cs b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs index e17813f..fb07192 100644 --- a/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs +++ b/Tests/InterprocessLib.BepisLoader.Tests/BepisLoaderTests.cs @@ -1,5 +1,4 @@ //#define TEST_SPAWN_PROCESS -//#define TEST_OBSOLETE_CONSTRUCTOR using BepInEx; using BepInEx.Configuration; @@ -18,18 +17,13 @@ public class Plugin : BasePlugin public static new ManualLogSource? Log; public static ConfigEntry? RunTestsToggle; public static Messenger? _messenger; - public static Messenger? _unknownMessenger; - -#if TEST_OBSOLETE_CONSTRUCTOR - public static Messenger? _testObsoleteConstructor; -#endif - public static ConfigEntry? SyncTest; + public static ConfigEntry? MyValue; public static ConfigEntry? CheckSyncToggle; public static ConfigEntry? SyncTestOutput; public static ConfigEntry? ResetToggle; - public static ConfigEntry? SendLatencyMilliseconds; - public static ConfigEntry? RecvLatencyMilliseconds; + public static ConfigEntry? CheckLatencyToggle; + public static ConfigEntry? LatencyMilliseconds; #if TEST_SPAWN_PROCESS public static Messenger? _customMessenger; @@ -95,18 +89,8 @@ public override void Load() }; _messenger = new Messenger("InterprocessLib.Tests"); - _unknownMessenger = new Messenger("InterprocessLib.Tests.UnknownMessengerFrooxEngine"); - -#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(); @@ -118,36 +102,26 @@ public override void Load() LastProcessHeartbeat = Config.Bind("General", "LastProcessHeartbeat", DateTime.MinValue); #endif - SyncTest = Config.Bind("General", "SyncTest", 34); - _messenger.SyncConfigEntry(SyncTest); + MyValue = Config.Bind("General", "SyncTest", 34); + _messenger.SyncConfigEntry(MyValue); RunTestsToggle = Config.Bind("General", "RunTests", false); 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); + LatencyMilliseconds = Config.Bind("General", "LatencyMilliseconds", -1.0); + CheckLatencyToggle = Config.Bind("General", "CheckLatencyToggle", false); - _messenger.CheckLatency((send, recv) => + _messenger.ReceivePing((latency) => { - SendLatencyMilliseconds.Value = send.TotalMilliseconds; - RecvLatencyMilliseconds.Value = recv.TotalMilliseconds; + LatencyMilliseconds.Value = latency.TotalMilliseconds; }); + _messenger.SendPing(); RunTestsToggle!.SettingChanged += (sender, args) => { _messenger!.SendEmptyCommand("RunTests"); 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) @@ -164,6 +138,10 @@ public override void Load() { _messenger.SendEmptyCommand("Reset"); }; + CheckLatencyToggle!.SettingChanged += (sender, args) => + { + _messenger.SendPing(); + }; _messenger.ReceiveValue("SyncTestOutput", (val) => { SyncTestOutput!.Value = val; diff --git a/Tests/InterprocessLib.RML.Tests/RML_Tests.cs b/Tests/InterprocessLib.RML.Tests/RML_Tests.cs index dab4848..741ac80 100644 --- a/Tests/InterprocessLib.RML.Tests/RML_Tests.cs +++ b/Tests/InterprocessLib.RML.Tests/RML_Tests.cs @@ -1,6 +1,4 @@ -//#define TEST_OBSOLETE_CONSTRUCTOR - -using ResoniteModLoader; +using ResoniteModLoader; namespace InterprocessLib.Tests; @@ -25,38 +23,23 @@ public class RML_Tests : ResoniteMod [AutoRegisterConfigKey] private static ModConfigurationKey ResetToggle = new ModConfigurationKey("ResetToggle", "ResetToggle:", () => false); [AutoRegisterConfigKey] - private static ModConfigurationKey SendLatencyMilliseconds = new ModConfigurationKey("SendLatencyMilliseconds", "SendLatencyMilliseconds:", () => -1.0); + private static ModConfigurationKey CheckLatencyToggle = new ModConfigurationKey("CheckLatencyToggle", "CheckLatencyToggle:", () => false); [AutoRegisterConfigKey] - private static ModConfigurationKey RecvLatencyMilliseconds = new ModConfigurationKey("RecvLatencyMilliseconds", "RecvLatencyMilliseconds:", () => -1.0); + private static ModConfigurationKey LatencyMilliseconds = new ModConfigurationKey("LatencyMilliseconds", "LatencyMilliseconds:", () => -1.0); public static Messenger? _messenger; - public static Messenger? _unknownMessenger; - -#if TEST_OBSOLETE_CONSTRUCTOR - public static Messenger? _testObsoleteConstructor; -#endif public override void OnEngineInit() { _messenger = new Messenger("InterprocessLib.Tests"); - _unknownMessenger = new Messenger("InterprocessLib.Tests.UnknownMessengerFrooxEngine"); - -#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) => + _messenger.ReceivePing((latency) => { - SendLatencyMilliseconds.Value = send.TotalMilliseconds; - RecvLatencyMilliseconds.Value = recv.TotalMilliseconds; + LatencyMilliseconds.Value = latency.TotalMilliseconds; }); + _messenger.SendPing(); _messenger.SyncConfigEntry(SyncTest); @@ -64,16 +47,6 @@ public override void OnEngineInit() { _messenger!.SendEmptyCommand("RunTests"); 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) => { @@ -83,6 +56,10 @@ public override void OnEngineInit() { _messenger.SendEmptyCommand("Reset"); }; + CheckLatencyToggle.OnChanged += (object? newValue) => + { + _messenger.SendPing(); + }; _messenger.ReceiveValue("SyncTestOutput", (val) => { SyncTestOutput!.Value = val; diff --git a/Tests/InterprocessLib.Standalone.Tests/Program.cs b/Tests/InterprocessLib.Standalone.Tests/Program.cs index d829586..3b77777 100644 --- a/Tests/InterprocessLib.Standalone.Tests/Program.cs +++ b/Tests/InterprocessLib.Standalone.Tests/Program.cs @@ -1,17 +1,11 @@ -using System.Diagnostics; -using System.Runtime.CompilerServices; -using InterprocessLib; +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) { @@ -52,7 +46,7 @@ static void Main(string[] args) Tests.RunTests(messenger, Console.WriteLine); - messenger.ReceiveEmptyCommand("HeartbeatResponse", () => + messenger.ReceivePing((latency) => { _cancel.CancelAfter(5000); }); @@ -63,7 +57,7 @@ static void Main(string[] args) { while (!_cancel.IsCancellationRequested) { - messenger.SendEmptyCommand("Heartbeat"); + messenger.SendPing(); await Task.Delay(2500); } }).Wait();