From 5247c1ad56d54ee1c35d4e78d372a270abb0fbf2 Mon Sep 17 00:00:00 2001 From: Lexi Date: Mon, 28 Aug 2023 08:02:39 -0400 Subject: [PATCH] GOTs message bus handling system first edition, copypasta'd out to 16 arguments --- .../API/Messaging/MessageBus.cs | 663 ++++++++++++++++++ .../API/Messaging/MessageBusBase.cs | 13 + .../API/Messaging/MessageBusManager.cs | 81 +++ SpaceWarp.Messaging/Modules/Messaging.cs | 7 +- SpaceWarp.UI/Modules/UI.cs | 2 + 5 files changed, 765 insertions(+), 1 deletion(-) create mode 100644 SpaceWarp.Messaging/API/Messaging/MessageBus.cs create mode 100644 SpaceWarp.Messaging/API/Messaging/MessageBusBase.cs create mode 100644 SpaceWarp.Messaging/API/Messaging/MessageBusManager.cs diff --git a/SpaceWarp.Messaging/API/Messaging/MessageBus.cs b/SpaceWarp.Messaging/API/Messaging/MessageBus.cs new file mode 100644 index 00000000..dc220ba3 --- /dev/null +++ b/SpaceWarp.Messaging/API/Messaging/MessageBus.cs @@ -0,0 +1,663 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using UnityEngine; + +namespace SpaceWarp.API.Messaging; + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish() + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (int i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2, arg3); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9, + T11 arg10) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9, + T11 arg10, T12 arg11) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9, + T11 arg10, T12 arg11, T13 arg12) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9, + T11 arg10, T12 arg11, T13 arg12, T14 arg13) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, + arg13); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = new(); + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9, + T11 arg10, T12 arg11, T13 arg12, T14 arg13, T15 arg14) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, + arg13, arg14); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} + +[PublicAPI] +public class MessageBus : MessageBusBase +{ + private readonly List> _handlers = + new(); + + internal override IReadOnlyList Handlers => _handlers; + internal override void RemoveHandlerAt(int index) => _handlers.RemoveAt(index); + + public void Subscribe(Action handler) + { + if (handler == null) + throw new ArgumentNullException(); + + _handlers.Add(handler); + } + + public void Unsubscribe(Action handler) + { + for (var i = _handlers.Count; i-- > 0;) + if (_handlers[i] == handler) + _handlers.RemoveAt(i); + } + + public void Publish(T1 arg0, T2 arg1, T3 arg2, T4 arg3, T5 arg4, T6 arg5, T7 arg6, T8 arg7, T9 arg8, T10 arg9, + T11 arg10, T12 arg11, T13 arg12, T14 arg13, T15 arg14, T16 arg15) + { + for (var i = _handlers.Count; i-- > 0;) + { + try + { + _handlers[i].Invoke(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, + arg13, arg14, arg15); + } + catch (Exception e) + { + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Error handling message '{Name}' : {e}"); + } + } + } +} \ No newline at end of file diff --git a/SpaceWarp.Messaging/API/Messaging/MessageBusBase.cs b/SpaceWarp.Messaging/API/Messaging/MessageBusBase.cs new file mode 100644 index 00000000..f8ae2782 --- /dev/null +++ b/SpaceWarp.Messaging/API/Messaging/MessageBusBase.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace SpaceWarp.API.Messaging; + +[PublicAPI] +public abstract class MessageBusBase +{ + public string Name { get; internal set; } + internal abstract IReadOnlyList Handlers { get; } + internal abstract void RemoveHandlerAt(int index); +} \ No newline at end of file diff --git a/SpaceWarp.Messaging/API/Messaging/MessageBusManager.cs b/SpaceWarp.Messaging/API/Messaging/MessageBusManager.cs new file mode 100644 index 00000000..8b256e92 --- /dev/null +++ b/SpaceWarp.Messaging/API/Messaging/MessageBusManager.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using UnityEngine; + +namespace SpaceWarp.API.Messaging; + +[PublicAPI] +public static class MessageBusManager +{ + private static Dictionary _messagesBusesByName = new Dictionary(); + + public static T Add(string name) where T : MessageBusBase, new() + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("Null or empty MessageBus name"); + + if (_messagesBusesByName.ContainsKey(name)) + throw new Exception($"MessageBus '{name}' exists already"); + + var messageBus = new T + { + Name = name + }; + _messagesBusesByName.Add(name, messageBus); + + Modules.Messaging.Instance.ModuleLogger.LogDebug($"MessageBus '{name}' created"); + return messageBus; + } + + public static bool Exists(string messageBusName) => _messagesBusesByName.ContainsKey(messageBusName); + + public static bool TryGet(string messageBusName, out T messageBus) where T : MessageBusBase, new() + { + if (string.IsNullOrEmpty(messageBusName)) + throw new ArgumentException("Null or empty MessageBus name"); + + if (!_messagesBusesByName.TryGetValue(messageBusName, out MessageBusBase messageBusBase)) + { + messageBus = null; + return false; + } + + if (messageBusBase is not T @base) + throw new Exception( + $"Message bus '{messageBusBase.Name}' is of type '{messageBusBase.GetType()}' but the requested type was '{typeof(T)}'"); + messageBus = @base; + return true; + + } + + // Call this (potentially a bit heavy) method on some occasions, for example when a loading screen happens + internal static void CheckForMemoryLeaks() + { + var memoryLeaks = 0; + + foreach (var messageBus in _messagesBusesByName.Values) + { + var handlers = messageBus.Handlers; + + for (var i = handlers.Count; i-- > 0;) + { + var target = handlers[i].Target; + switch (target) + { + // bypass UnityEngine.Object null equality overload + case null: + continue; + case UnityEngine.Object uObj when uObj == null: + Modules.Messaging.Instance.ModuleLogger.LogDebug($"Memory leak detected : a destroyed instance of the '{target.GetType().Assembly.GetName().Name}:{target.GetType().Name}' class is holding a '{messageBus.Name}' MessageBus handler"); + messageBus.RemoveHandlerAt(i); + memoryLeaks++; + break; + } + } + } + + if (memoryLeaks > 0) + Modules.Messaging.Instance.ModuleLogger.LogDebug($"{memoryLeaks} detected!"); + } +} \ No newline at end of file diff --git a/SpaceWarp.Messaging/Modules/Messaging.cs b/SpaceWarp.Messaging/Modules/Messaging.cs index caac092f..861ca156 100644 --- a/SpaceWarp.Messaging/Modules/Messaging.cs +++ b/SpaceWarp.Messaging/Modules/Messaging.cs @@ -1,10 +1,15 @@ -namespace SpaceWarp.Modules; +using JetBrains.Annotations; +namespace SpaceWarp.Modules; + +[PublicAPI] public class Messaging : SpaceWarpModule { public override string Name => "SpaceWarp.Messaging"; + internal static Messaging Instance; public override void LoadModule() { + Instance = this; } public override void PreInitializeModule() diff --git a/SpaceWarp.UI/Modules/UI.cs b/SpaceWarp.UI/Modules/UI.cs index 12e67180..42b02579 100644 --- a/SpaceWarp.UI/Modules/UI.cs +++ b/SpaceWarp.UI/Modules/UI.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using BepInEx.Bootstrap; +using JetBrains.Annotations; using KSP.Assets; using KSP.Game; using KSP.Game.Flow; @@ -23,6 +24,7 @@ namespace SpaceWarp.Modules; +[PublicAPI] public class UI : SpaceWarpModule { public override string Name => "SpaceWarp.UI";