From a7afb0e7c3670c30b01785c4636ada164d2e9fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=B8llegaard?= Date: Sun, 3 Jan 2021 12:26:15 +0100 Subject: [PATCH 1/9] Extract LuaService from LuaPlugin, Re-did Lua integration --- Backend/Engine.cs | 9 +- Backend/Plugins/LuaPlugin.cs | 216 +++--------------- Backend/Plugins/ReceiverPlugin.cs | 5 +- Backend/Plugins/TransmitterPlugin.cs | 5 +- Backend/Services/EventSerdeService.cs | 2 +- Backend/Services/IEventSerdeService.cs | 12 + Backend/Services/ILuaContext.cs | 13 ++ Backend/Services/ITxrxService.cs | 13 ++ Backend/Services/LuaService.cs | 32 +++ .../LuaServiceLib/AudioMethodCollection.cs | 50 ++++ .../LuaServiceLib/CoreMethodCollection.cs | 130 +++++++++++ Backend/Services/LuaServiceLib/LuaContext.cs | 69 ++++++ .../Services/LuaServiceLib/LuaException.cs | 12 + .../LuaServiceLib/StateMethodCollection.cs | 54 +++++ .../LuaServiceLib/TwitchMethodCollection.cs | 43 ++++ .../LuaServiceLib/UIMethodCollection.cs | 52 +++++ Backend/Services/TxrxService.cs | 9 +- Program.cs | 2 + Slipstream.csproj | 11 + 19 files changed, 544 insertions(+), 195 deletions(-) create mode 100644 Backend/Services/IEventSerdeService.cs create mode 100644 Backend/Services/ILuaContext.cs create mode 100644 Backend/Services/ITxrxService.cs create mode 100644 Backend/Services/LuaService.cs create mode 100644 Backend/Services/LuaServiceLib/AudioMethodCollection.cs create mode 100644 Backend/Services/LuaServiceLib/CoreMethodCollection.cs create mode 100644 Backend/Services/LuaServiceLib/LuaContext.cs create mode 100644 Backend/Services/LuaServiceLib/LuaException.cs create mode 100644 Backend/Services/LuaServiceLib/StateMethodCollection.cs create mode 100644 Backend/Services/LuaServiceLib/TwitchMethodCollection.cs create mode 100644 Backend/Services/LuaServiceLib/UIMethodCollection.cs diff --git a/Backend/Engine.cs b/Backend/Engine.cs index c11b2922..5572f43f 100644 --- a/Backend/Engine.cs +++ b/Backend/Engine.cs @@ -19,14 +19,17 @@ class Engine : Worker, IEngine, IDisposable private readonly IEventBusSubscription Subscription; private readonly IStateService StateService; private readonly IApplicationConfiguration ApplicationConfiguration; + private readonly ITxrxService TxrxService; private readonly Shared.EventHandler EventHandler = new Shared.EventHandler(); - public Engine(IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, IApplicationConfiguration applicationConfiguration) : base("engine") + public Engine(IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, ITxrxService txrxService, IApplicationConfiguration applicationConfiguration) : base("engine") { EventFactory = eventFactory; EventBus = eventBus; StateService = stateService; ApplicationConfiguration = applicationConfiguration; + TxrxService = txrxService; + PluginManager = new PluginManager(this, eventFactory, eventBus); Subscription = EventBus.RegisterListener(); @@ -148,7 +151,7 @@ private void OnCommandPluginRegister(Shared.Events.Internal.InternalCommandPlugi } else { - PluginManager.RegisterPlugin(new TransmitterPlugin(ev.Id, EventFactory, EventBus, settings)); + PluginManager.RegisterPlugin(new TransmitterPlugin(ev.Id, EventFactory, EventBus, TxrxService, settings)); } } break; @@ -160,7 +163,7 @@ private void OnCommandPluginRegister(Shared.Events.Internal.InternalCommandPlugi } else { - PluginManager.RegisterPlugin(new ReceiverPlugin(ev.Id, EventFactory, EventBus, settings)); + PluginManager.RegisterPlugin(new ReceiverPlugin(ev.Id, EventFactory, EventBus, TxrxService, settings)); } } break; diff --git a/Backend/Plugins/LuaPlugin.cs b/Backend/Plugins/LuaPlugin.cs index bde17e74..a47141f9 100644 --- a/Backend/Plugins/LuaPlugin.cs +++ b/Backend/Plugins/LuaPlugin.cs @@ -1,10 +1,7 @@ -using NLua; -using Slipstream.Backend.Services; +using Slipstream.Backend.Services; +using Slipstream.Backend.Services.LuaServiceLib; using Slipstream.Shared; -using Slipstream.Shared.Events; using Slipstream.Shared.Events.Setting; -using System; -using System.Collections.Generic; using System.IO; using EventHandler = Slipstream.Shared.EventHandler; @@ -12,25 +9,30 @@ namespace Slipstream.Backend.Plugins { - class LuaPlugin : BasePlugin + + public class LuaPlugin : BasePlugin { private readonly IEventFactory EventFactory; private readonly IEventBus EventBus; - private readonly IStateService StateService; - + private readonly LuaService LuaService; + private ILuaContext? LuaContext; + private string Prefix = ""; private string? FilePath; - private LuaFunction? HandleFunc; - private LuaApi? Api; - private Lua? Lua; public LuaPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, LuaSettings settings) : base(id, "LuaPlugin", "LuaPlugin", "Lua") { EventFactory = eventFactory; EventBus = eventBus; - StateService = stateService; + + LuaService = new LuaService(eventFactory, eventBus, stateService); EventHandler.OnSettingLuaSettings += (s, e) => OnLuaSettings(e.Event); + // Avoid that WriteToConsole is evaluated by Lua, that in turn will + // add more WriteToConsole events, making a endless loop + EventHandler.OnUICommandWriteToConsole += (s, e) => { }; + EventHandler.OnDefault += (s, e) => LuaContext?.HandleEvent(e.Event); + OnLuaSettings(settings); } @@ -40,6 +42,7 @@ private void OnLuaSettings(LuaSettings @event) return; FilePath = @event.FilePath; + Prefix = Path.GetFileName(FilePath); StartLua(); } @@ -49,189 +52,33 @@ private void StartLua() if (!Enabled || FilePath == null) return; + DisplayName = "Lua: " + Path.GetFileName(FilePath); + try { - Api = new LuaApi(EventFactory, EventBus, StateService, Path.GetFileName(FilePath)); - Lua = new Lua(); - - DisplayName = "Lua: " + Path.GetFileName(FilePath); - - Lua.RegisterFunction("print", Api, typeof(LuaApi).GetMethod("Print", new[] { typeof(string) })); - Lua.RegisterFunction("say", Api, typeof(LuaApi).GetMethod("Say", new[] { typeof(string), typeof(float) })); - Lua.RegisterFunction("play", Api, typeof(LuaApi).GetMethod("Play", new[] { typeof(string), typeof(float) })); - Lua.RegisterFunction("debounce", Api, typeof(LuaApi).GetMethod("Debounce", new[] { typeof(string), typeof(LuaFunction), typeof(float) })); - Lua.RegisterFunction("wait", Api, typeof(LuaApi).GetMethod("Wait", new[] { typeof(string), typeof(LuaFunction), typeof(float) })); - Lua.RegisterFunction("send_twitch_message", Api, typeof(LuaApi).GetMethod("SendTwitchMessage", new[] { typeof(string) })); - Lua.RegisterFunction("set_state", Api, typeof(LuaApi).GetMethod("SetState", new[] { typeof(string), typeof(string) })); - Lua.RegisterFunction("set_temp_state", Api, typeof(LuaApi).GetMethod("SetTempState", new[] { typeof(string), typeof(string), typeof(int) })); - Lua.RegisterFunction("get_state", Api, typeof(LuaApi).GetMethod("GetState", new[] { typeof(string) })); - Lua.RegisterFunction("event_to_json", Api, typeof(LuaApi).GetMethod("EventToJson", new[] { typeof(IEvent) })); - Lua.RegisterFunction("create_button", Api, typeof(LuaApi).GetMethod("CreateButton", new[] { typeof(string) })); - Lua.RegisterFunction("delete_button", Api, typeof(LuaApi).GetMethod("DeleteButton", new[] { typeof(string) })); - - var ScriptPath = Path.GetDirectoryName(FilePath).Replace("\\", "\\\\"); - Lua.DoString($"package.path = \"{ScriptPath}\\\\?.lua;\" .. package.path;"); - - var f = Lua.LoadFile(FilePath); - - f.Call(); - - HandleFunc = Lua["handle"] as LuaFunction; - - // Avoid that WriteToConsole is evaluated by Lua, that in turn will - // add more WriteToConsole events, making a endless loop - EventHandler.OnUICommandWriteToConsole += (s, e) => { }; - EventHandler.OnDefault += (s, e) => HandleFunc?.Call(e.Event); + LuaContext = LuaService.Parse(FilePath, Prefix); } - catch (NLua.Exceptions.LuaScriptException e) + catch (LuaException e) { - Api?.Print($"ERROR: {e.Message}"); - EventBus.PublishEvent(EventFactory.CreateInternalCommandPluginUnregister(Id)); + HandleLuaException(e); } } - public override void OnDisable() + private void HandleLuaException(LuaException e) { - Api = null; - Lua = null; + EventBus.PublishEvent(EventFactory.CreateUICommandWriteToConsole($"{Prefix}: ERROR: { e.Message}")); + EventBus.PublishEvent(EventFactory.CreateInternalCommandPluginUnregister(Id)); } - public override void OnEnable() + public override void OnDisable() { - StartLua(); + LuaContext?.Dispose(); + LuaContext = null; } - class LuaApi + public override void OnEnable() { - private readonly IEventFactory EventFactory; - private readonly IEventBus EventBus; - private readonly string Prefix; - private readonly IDictionary DebouncedFunctions = new Dictionary(); - private readonly IDictionary WaitingFunctions = new Dictionary(); - private readonly IStateService StateService; - private readonly EventSerdeService EventSerdeService; - - private class DelayedExecution - { - public LuaFunction Function; - public DateTime TriggersAt; - - public DelayedExecution(LuaFunction luaFunction, DateTime triggersAt) - { - Function = luaFunction; - TriggersAt = triggersAt; - } - } - - public LuaApi(IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, string prefix) - { - EventFactory = eventFactory; - EventBus = eventBus; - Prefix = prefix; - StateService = stateService; - EventSerdeService = new EventSerdeService(); - } - - public void Print(string s) - { - EventBus.PublishEvent(EventFactory.CreateUICommandWriteToConsole($"{Prefix}: {s}")); - } - - public void Say(string message, float volume) - { - EventBus.PublishEvent(EventFactory.CreateAudioCommandSay(message, volume)); - } - - public void Play(string filename, float volume) - { - EventBus.PublishEvent(EventFactory.CreateAudioCommandPlay(filename, volume)); - } - - public void SendTwitchMessage(string message) - { - EventBus.PublishEvent(EventFactory.CreateTwitchCommandSendMessage(message)); - } - - public void SetState(string key, string value) - { - StateService.SetState(key, value); - } - - public void SetTempState(string key, string value, int lifetimSeconds) - { - StateService.SetState(key, value, lifetimSeconds); - } - - public string GetState(string key) - { - return StateService.GetState(key); - } - - public void Debounce(string name, LuaFunction func, float debounceLength) - { - if (func != null) - { - DebouncedFunctions[name] = new DelayedExecution(func, DateTime.Now.AddSeconds(debounceLength)); - } - else - { - Print("Can't debounce without a function"); - } - } - - public void Wait(string name, LuaFunction func, float duration) - { - if (func != null) - { - if (!WaitingFunctions.ContainsKey(name)) - WaitingFunctions[name] = new DelayedExecution(func, DateTime.Now.AddSeconds(duration)); - } - else - { - Print("Can't wait without a function"); - } - } - - public string EventToJson(IEvent @event) - { - return EventSerdeService.Serialize(@event); - } - - public void CreateButton(string text) - { - EventBus.PublishEvent(EventFactory.CreateUICommandCreateButton(text)); - } - - public void DeleteButton(string text) - { - EventBus.PublishEvent(EventFactory.CreateUICommandDeleteButton(text)); - } - - private void HandleDelayedExecution(IDictionary functions) - { - string? deleteKey = null; - - foreach (var d in functions) - { - if (d.Value.TriggersAt < DateTime.Now) - { - d.Value.Function.Call(); - deleteKey = d.Key; - break; - } - } - - if (deleteKey != null) - { - functions.Remove(deleteKey); - } - } - - public void Loop() - { - HandleDelayedExecution(DebouncedFunctions); - HandleDelayedExecution(WaitingFunctions); - } + StartLua(); } public override void Loop() @@ -241,12 +88,11 @@ public override void Loop() try { - Api?.Loop(); + LuaContext?.Loop(); } - catch (NLua.Exceptions.LuaScriptException e) + catch (LuaException e) { - Api?.Print($"ERROR: {e.Message}"); - EventBus.PublishEvent(EventFactory.CreateInternalCommandPluginUnregister(Id)); + HandleLuaException(e); } } } diff --git a/Backend/Plugins/ReceiverPlugin.cs b/Backend/Plugins/ReceiverPlugin.cs index d8942bdb..2eebb820 100644 --- a/Backend/Plugins/ReceiverPlugin.cs +++ b/Backend/Plugins/ReceiverPlugin.cs @@ -19,15 +19,16 @@ class ReceiverPlugin : BasePlugin private string Ip = ""; private Int32 Port = 42424; private TcpListener? Listener; - private readonly TxrxService TxrxService = new TxrxService(); + private readonly ITxrxService TxrxService; private Socket? Client; private const int READ_BUFFER_SIZE = 1024 * 16; readonly byte[] ReadBuffer = new byte[READ_BUFFER_SIZE]; - public ReceiverPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, TxrxSettings settings) : base(id, "ReceiverPlugin", "ReceiverPlugin", "ReceiverPlugin") + public ReceiverPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, ITxrxService txrxService, TxrxSettings settings) : base(id, "ReceiverPlugin", "ReceiverPlugin", "ReceiverPlugin") { EventFactory = eventFactory; EventBus = eventBus; + TxrxService = txrxService; OnSetting(settings); diff --git a/Backend/Plugins/TransmitterPlugin.cs b/Backend/Plugins/TransmitterPlugin.cs index ae34ee11..fd01adea 100644 --- a/Backend/Plugins/TransmitterPlugin.cs +++ b/Backend/Plugins/TransmitterPlugin.cs @@ -19,12 +19,13 @@ class TransmitterPlugin : BasePlugin private string Ip = ""; private Int32 Port = 42424; private TcpClient? Client = null; - private readonly TxrxService TxrxService = new TxrxService(); + private readonly ITxrxService TxrxService; - public TransmitterPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, TxrxSettings settings) : base(id, "TransmitterPlugin", "TransmitterPlugin", "TransmitterPlugin") + public TransmitterPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, ITxrxService txrxService, TxrxSettings settings) : base(id, "TransmitterPlugin", "TransmitterPlugin", "TransmitterPlugin") { EventBus = eventBus; EventFactory = eventFactory; + TxrxService = txrxService; OnSetting(settings); diff --git a/Backend/Services/EventSerdeService.cs b/Backend/Services/EventSerdeService.cs index 1954246f..32673256 100644 --- a/Backend/Services/EventSerdeService.cs +++ b/Backend/Services/EventSerdeService.cs @@ -9,7 +9,7 @@ namespace Slipstream.Backend.Services { - public class EventSerdeService + public class EventSerdeService : IEventSerdeService { private static readonly Dictionary EventsMap = new Dictionary(); diff --git a/Backend/Services/IEventSerdeService.cs b/Backend/Services/IEventSerdeService.cs new file mode 100644 index 00000000..502a0fda --- /dev/null +++ b/Backend/Services/IEventSerdeService.cs @@ -0,0 +1,12 @@ +using Slipstream.Shared; + +#nullable enable + +namespace Slipstream.Backend.Services +{ + public interface IEventSerdeService + { + IEvent? Deserialize(string json); + string Serialize(IEvent @event); + } +} diff --git a/Backend/Services/ILuaContext.cs b/Backend/Services/ILuaContext.cs new file mode 100644 index 00000000..361b3add --- /dev/null +++ b/Backend/Services/ILuaContext.cs @@ -0,0 +1,13 @@ +using Slipstream.Shared; +using System; + +#nullable enable + +namespace Slipstream.Backend.Services +{ + public interface ILuaContext : IDisposable + { + void Loop(); + void HandleEvent(IEvent @event); + } +} diff --git a/Backend/Services/ITxrxService.cs b/Backend/Services/ITxrxService.cs new file mode 100644 index 00000000..28d1e99c --- /dev/null +++ b/Backend/Services/ITxrxService.cs @@ -0,0 +1,13 @@ +using Slipstream.Shared; +using System; + +#nullable enable + +namespace Slipstream.Backend.Services +{ + public interface ITxrxService + { + string Serialize(IEvent e); + void Parse(string data, Action processLine); + } +} diff --git a/Backend/Services/LuaService.cs b/Backend/Services/LuaService.cs new file mode 100644 index 00000000..be57218f --- /dev/null +++ b/Backend/Services/LuaService.cs @@ -0,0 +1,32 @@ +using Slipstream.Backend.Services.LuaServiceLib; +using Slipstream.Shared; + +#nullable enable + +namespace Slipstream.Backend.Services +{ + public interface ILuaSevice + { + ILuaContext Parse(string filename, string logPrefix); + } + + public class LuaService : ILuaSevice + { + private readonly IEventFactory EventFactory; + private readonly IEventBus EventBus; + private readonly IStateService StateService; + + public LuaService(IEventFactory eventFactory, IEventBus eventBus, IStateService stateService) + { + EventFactory = eventFactory; + EventBus = eventBus; + StateService = stateService; + } + + public ILuaContext Parse(string filename, string logPrefix) + { + var ctx = new LuaContext(EventFactory, EventBus, StateService, filename, logPrefix); + return ctx; + } + } +} diff --git a/Backend/Services/LuaServiceLib/AudioMethodCollection.cs b/Backend/Services/LuaServiceLib/AudioMethodCollection.cs new file mode 100644 index 00000000..49839bfd --- /dev/null +++ b/Backend/Services/LuaServiceLib/AudioMethodCollection.cs @@ -0,0 +1,50 @@ +using NLua; +using Slipstream.Shared; + +#nullable enable + +namespace Slipstream.Backend.Services.LuaServiceLib +{ + public partial class LuaContext + { + public class AudioMethodCollection + { + private readonly IEventBus EventBus; + private readonly IEventFactory EventFactory; + + public static AudioMethodCollection Register(IEventBus eventBus, IEventFactory eventFactory, Lua lua) + { + var m = new AudioMethodCollection(eventBus, eventFactory); + m.Register(lua); + return m; + } + + public AudioMethodCollection(IEventBus eventBus, IEventFactory eventFactory) + { + EventBus = eventBus; + EventFactory = eventFactory; + } + + public void Register(Lua lua) + { + lua["audio"] = this; + lua.DoString(@" +function say(a,b); audio:say(a,b); end +function play(a,b); audio:play(a,b); end +"); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void say(string message, float volume) + { + EventBus.PublishEvent(EventFactory.CreateAudioCommandSay(message, volume)); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void play(string filename, float volume) + { + EventBus.PublishEvent(EventFactory.CreateAudioCommandPlay(filename, volume)); + } + } + } +} diff --git a/Backend/Services/LuaServiceLib/CoreMethodCollection.cs b/Backend/Services/LuaServiceLib/CoreMethodCollection.cs new file mode 100644 index 00000000..a0bf2011 --- /dev/null +++ b/Backend/Services/LuaServiceLib/CoreMethodCollection.cs @@ -0,0 +1,130 @@ +using NLua; +using Slipstream.Shared; +using System; +using System.Collections.Generic; + +#nullable enable + +namespace Slipstream.Backend.Services.LuaServiceLib +{ + public partial class LuaContext + { + public class CoreMethodCollection + { + private class DelayedExecution + { + public LuaFunction Function; + public DateTime TriggersAt; + + public DelayedExecution(LuaFunction luaFunction, DateTime triggersAt) + { + Function = luaFunction; + TriggersAt = triggersAt; + } + } + + private readonly string Prefix; + private readonly IEventFactory EventFactory; + private readonly IEventBus EventBus; + private readonly IEventSerdeService EventSerdeService; + + private readonly IDictionary DebounceDelayedFunctions = new Dictionary(); + private readonly IDictionary WaitDelayedFunctions = new Dictionary(); + + public static CoreMethodCollection Register(IEventFactory eventFactory, IEventBus eventBus, string logPrefix, IEventSerdeService eventSerdeService, Lua lua) + { + var m = new CoreMethodCollection(eventFactory, eventBus, logPrefix, eventSerdeService); + + m.Register(lua); + + return m; + } + + public CoreMethodCollection(IEventFactory eventFactory, IEventBus eventBus, string logPrefix, IEventSerdeService eventSerdeService) + { + Prefix = logPrefix; + EventFactory = eventFactory; + EventBus = eventBus; + EventSerdeService = eventSerdeService; + } + + public void Register(Lua lua) + { + lua["core"] = this; + + // Make old Lua script work as before + lua.DoString(@" +function print(s); core:print(s); end +function debounce(a, b, c); core:debounce(a, b, c); end +function wait(a, b, c); core:wait(a, b, c); end +function event_to_json(a); return core:event_to_json(a); end +"); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void print(string s) + { + EventBus.PublishEvent(EventFactory.CreateUICommandWriteToConsole($"{Prefix}: {s}")); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void debounce(string name, LuaFunction func, float debounceLength) + { + if (func != null) + { + DebounceDelayedFunctions[name] = new DelayedExecution(func, DateTime.Now.AddSeconds(debounceLength)); + } + else + { + print("Can't debounce without a function"); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void wait(string name, LuaFunction func, float duration) + { + if (func != null) + { + if (!WaitDelayedFunctions.ContainsKey(name)) + WaitDelayedFunctions[name] = new DelayedExecution(func, DateTime.Now.AddSeconds(duration)); + } + else + { + print("Can't wait without a function"); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public string event_to_json(IEvent @event) + { + return EventSerdeService.Serialize(@event); + } + + public void Loop() + { + HandleDelayedExecution(WaitDelayedFunctions); + HandleDelayedExecution(DebounceDelayedFunctions); + } + + private void HandleDelayedExecution(IDictionary functions) + { + string? deleteKey = null; + + foreach (var d in functions) + { + if (d.Value.TriggersAt < DateTime.Now) + { + d.Value.Function.Call(); + deleteKey = d.Key; + break; + } + } + + if (deleteKey != null) + { + functions.Remove(deleteKey); + } + } + } + } +} diff --git a/Backend/Services/LuaServiceLib/LuaContext.cs b/Backend/Services/LuaServiceLib/LuaContext.cs new file mode 100644 index 00000000..68bc725c --- /dev/null +++ b/Backend/Services/LuaServiceLib/LuaContext.cs @@ -0,0 +1,69 @@ +using NLua; +using Slipstream.Shared; +using System.Collections.Generic; +using System.IO; + +#nullable enable + +namespace Slipstream.Backend.Services.LuaServiceLib +{ + public partial class LuaContext : ILuaContext + { + private readonly CoreMethodCollection CoreMethodCollection_; + private readonly LuaFunction? HandleFunc; + private Lua? Lua; + + public LuaContext(IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, string filePath, string logPrefix) + { + try + { + Lua = new Lua(); + + CoreMethodCollection_ = CoreMethodCollection.Register(eventFactory, eventBus, logPrefix, new EventSerdeService(), Lua); + AudioMethodCollection.Register(eventBus, eventFactory, Lua); + TwitchMethodCollection.Register(eventBus, eventFactory, Lua); + StateMethodCollection.Register(stateService, Lua); + UIMethodCollection.Register(eventBus, eventFactory, Lua); + + // Fix paths, so we can require() files relative to where the script is located + var ScriptPath = Path.GetDirectoryName(filePath).Replace("\\", "\\\\"); + Lua.DoString($"package.path = \"{ScriptPath}\\\\?.lua;\" .. package.path;"); + + // Load the LUA + var f = Lua.LoadFile(filePath); + + // Evalulate it + f.Call(); + + // Find "handle()" function which will be triggered on received events + HandleFunc = Lua["handle"] as LuaFunction; + } + catch (NLua.Exceptions.LuaScriptException e) + { + throw new LuaException($"Error initializing Lua: {e}", e); + } + } + + public void Loop() + { + try + { + CoreMethodCollection_.Loop(); + } + catch (NLua.Exceptions.LuaScriptException e) + { + throw new LuaException($"Lua runtime error: {e}", e); + } + } + + public void Dispose() + { + Lua = null; + } + + public void HandleEvent(IEvent @event) + { + HandleFunc?.Call(@event); + } + } +} diff --git a/Backend/Services/LuaServiceLib/LuaException.cs b/Backend/Services/LuaServiceLib/LuaException.cs new file mode 100644 index 00000000..b8350eef --- /dev/null +++ b/Backend/Services/LuaServiceLib/LuaException.cs @@ -0,0 +1,12 @@ +using NLua.Exceptions; +using System; + +namespace Slipstream.Backend.Services.LuaServiceLib +{ + public class LuaException : Exception + { + public LuaException(string error, LuaScriptException e) : base(error, e) + { + } + } +} diff --git a/Backend/Services/LuaServiceLib/StateMethodCollection.cs b/Backend/Services/LuaServiceLib/StateMethodCollection.cs new file mode 100644 index 00000000..297887b6 --- /dev/null +++ b/Backend/Services/LuaServiceLib/StateMethodCollection.cs @@ -0,0 +1,54 @@ +using NLua; + +#nullable enable + +namespace Slipstream.Backend.Services.LuaServiceLib +{ + public partial class LuaContext + { + public class StateMethodCollection + { + private readonly IStateService StateService; + + public static StateMethodCollection Register(IStateService stateService, Lua lua) + { + var m = new StateMethodCollection(stateService); + m.Register(lua); + return m; + } + + public StateMethodCollection(IStateService stateService) + { + StateService = stateService; + } + + public void Register(Lua lua) + { + lua["state"] = this; + lua.DoString(@" +function get_state(a); return state:get(a); end +function set_state(a,b); return state:set(a, b); end +function set_temp_state(a,b,c); return state:set_temp(a,b,c); end +"); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public string get(string key) + { + return StateService.GetState(key); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void set(string key, string value) + { + StateService.SetState(key, value); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void set_temp(string key, string value, int lifetimeInSeconds) + { + StateService.SetState(key, value, lifetimeInSeconds); + } + } + } +} diff --git a/Backend/Services/LuaServiceLib/TwitchMethodCollection.cs b/Backend/Services/LuaServiceLib/TwitchMethodCollection.cs new file mode 100644 index 00000000..d7bdd3ff --- /dev/null +++ b/Backend/Services/LuaServiceLib/TwitchMethodCollection.cs @@ -0,0 +1,43 @@ +using NLua; +using Slipstream.Shared; + +#nullable enable + +namespace Slipstream.Backend.Services.LuaServiceLib +{ + public partial class LuaContext + { + public class TwitchMethodCollection + { + private readonly IEventBus EventBus; + private readonly IEventFactory EventFactory; + + public static TwitchMethodCollection Register(IEventBus eventBus, IEventFactory eventFactory, Lua lua) + { + var m = new TwitchMethodCollection(eventBus, eventFactory); + m.Register(lua); + return m; + } + + public TwitchMethodCollection(IEventBus eventBus, IEventFactory eventFactory) + { + EventBus = eventBus; + EventFactory = eventFactory; + } + + public void Register(Lua lua) + { + lua["twitch"] = this; + lua.DoString(@" +function send_twitch_message(a); twitch:send_channel_message(a); end +"); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void send_channel_message(string message) + { + EventBus.PublishEvent(EventFactory.CreateTwitchCommandSendMessage(message)); + } + } + } +} diff --git a/Backend/Services/LuaServiceLib/UIMethodCollection.cs b/Backend/Services/LuaServiceLib/UIMethodCollection.cs new file mode 100644 index 00000000..6526d9ca --- /dev/null +++ b/Backend/Services/LuaServiceLib/UIMethodCollection.cs @@ -0,0 +1,52 @@ +using NLua; +using Slipstream.Shared; + +#nullable enable + +namespace Slipstream.Backend.Services.LuaServiceLib +{ + public partial class LuaContext + { + public class UIMethodCollection + { + private readonly IEventBus EventBus; + private readonly IEventFactory EventFactory; + + public static UIMethodCollection Register(IEventBus eventBus, IEventFactory eventFactory, Lua lua) + { + var m = new UIMethodCollection(eventBus, eventFactory); + + m.Register(lua); + + return m; + } + + public UIMethodCollection(IEventBus eventBus, IEventFactory eventFactory) + { + EventBus = eventBus; + EventFactory = eventFactory; + } + + public void Register(Lua lua) + { + lua["ui"] = this; + lua.DoString(@" +function create_button(a); ui:create_button(a); end +function delete_button(a); ui:delete_button(a); end +"); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void create_button(string text) + { + EventBus.PublishEvent(EventFactory.CreateUICommandCreateButton(text)); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void delete_button(string text) + { + EventBus.PublishEvent(EventFactory.CreateUICommandDeleteButton(text)); + } + } + } +} diff --git a/Backend/Services/TxrxService.cs b/Backend/Services/TxrxService.cs index c4966902..441902e9 100644 --- a/Backend/Services/TxrxService.cs +++ b/Backend/Services/TxrxService.cs @@ -6,11 +6,16 @@ namespace Slipstream.Backend.Services { - public class TxrxService + public class TxrxService : ITxrxService { - private readonly EventSerdeService Serde = new EventSerdeService(); + private readonly IEventSerdeService Serde; private string UnterminatedJson = ""; + public TxrxService(IEventSerdeService eventSerdeService) + { + Serde = eventSerdeService; + } + public string Serialize(IEvent e) { string json = Serde.Serialize(e); diff --git a/Program.cs b/Program.cs index 3fde5398..992fa9f7 100644 --- a/Program.cs +++ b/Program.cs @@ -45,6 +45,8 @@ private static void ConfigureServices(ServiceCollection services) services.AddScoped(x => x.GetService()); services.AddScoped(); services.AddScoped(x => new Backend.Services.StateService(x.GetService(), x.GetService(), Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + $@"\Slipstream\state.txt")); + services.AddScoped(); + services.AddScoped(); services.AddTransient(); } } diff --git a/Slipstream.csproj b/Slipstream.csproj index d0b55242..b82a3638 100644 --- a/Slipstream.csproj +++ b/Slipstream.csproj @@ -138,8 +138,19 @@ + + + + + + + + + + + From 37139a2b77311a794d67c1ef6091a372adb79fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=B8llegaard?= Date: Sun, 3 Jan 2021 14:26:18 +0100 Subject: [PATCH 2/9] Move Plugin Creation to PluginFactory --- Backend/Engine.cs | 99 +++--------------------------------- Backend/PluginFactory.cs | 105 +++++++++++++++++++++++++++++++++++++++ Program.cs | 1 + Slipstream.csproj | 1 + 4 files changed, 114 insertions(+), 92 deletions(-) create mode 100644 Backend/PluginFactory.cs diff --git a/Backend/Engine.cs b/Backend/Engine.cs index 5572f43f..6c9f26c5 100644 --- a/Backend/Engine.cs +++ b/Backend/Engine.cs @@ -1,9 +1,7 @@ -using Slipstream.Backend.Plugins; -using Slipstream.Backend.Services; +using Slipstream.Backend.Services; using Slipstream.Shared; using Slipstream.Shared.Events; using Slipstream.Shared.Events.Internal; -using Slipstream.Shared.Events.Setting; using System; using static Slipstream.Shared.IEventFactory; @@ -11,24 +9,22 @@ namespace Slipstream.Backend { - class Engine : Worker, IEngine, IDisposable + partial class Engine : Worker, IEngine, IDisposable { private readonly IEventFactory EventFactory; private readonly IEventBus EventBus; private readonly PluginManager PluginManager; private readonly IEventBusSubscription Subscription; - private readonly IStateService StateService; private readonly IApplicationConfiguration ApplicationConfiguration; - private readonly ITxrxService TxrxService; + private readonly PluginFactory PluginFactory; private readonly Shared.EventHandler EventHandler = new Shared.EventHandler(); - public Engine(IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, ITxrxService txrxService, IApplicationConfiguration applicationConfiguration) : base("engine") + public Engine(IEventFactory eventFactory, IEventBus eventBus, IApplicationConfiguration applicationConfiguration, PluginFactory pluginFactory) : base("engine") { EventFactory = eventFactory; EventBus = eventBus; - StateService = stateService; ApplicationConfiguration = applicationConfiguration; - TxrxService = txrxService; + PluginFactory = pluginFactory; PluginManager = new PluginManager(this, eventFactory, eventBus); @@ -87,89 +83,8 @@ public void UnregisterSubscription(IEventBusSubscription subscription) private void OnCommandPluginRegister(Shared.Events.Internal.InternalCommandPluginRegister ev) { - switch (ev.PluginName) - { - case "FileMonitorPlugin": - { - if (!(ev.Settings is FileMonitorSettings settings)) - { - throw new Exception("Unexpected settings for FileMonitorPlugin"); - } - else - { - PluginManager.RegisterPlugin(new FileMonitorPlugin(ev.Id, EventFactory, EventBus, settings)); - } - } - break; - case "FileTriggerPlugin": - PluginManager.RegisterPlugin(new FileTriggerPlugin(ev.Id, EventFactory, EventBus)); - break; - case "LuaPlugin": - { - if (!(ev.Settings is LuaSettings settings)) - { - throw new Exception("Unexpected settings for LuaPlugin"); - } - else - { - PluginManager.RegisterPlugin(new LuaPlugin(ev.Id, EventFactory, EventBus, StateService, settings)); - } - } - break; - case "AudioPlugin": - { - if (!(ev.Settings is AudioSettings settings)) - { - throw new Exception("Unexpected settings for AudioPlugin"); - } - else - { - PluginManager.RegisterPlugin(new AudioPlugin(ev.Id, EventFactory, EventBus, settings)); - } - } - break; - case "IRacingPlugin": - PluginManager.RegisterPlugin(new IRacingPlugin(ev.Id, EventFactory, EventBus)); - break; - case "TwitchPlugin": - { - if (!(ev.Settings is TwitchSettings settings)) - { - throw new Exception("Unexpected settings for TwitchPlugin"); - } - else - { - PluginManager.RegisterPlugin(new TwitchPlugin(ev.Id, EventFactory, EventBus, settings)); - } - } - break; - case "TransmitterPlugin": - { - if (!(ev.Settings is TxrxSettings settings)) - { - throw new Exception("Unexpected settings for TransmitterPlugin"); - } - else - { - PluginManager.RegisterPlugin(new TransmitterPlugin(ev.Id, EventFactory, EventBus, TxrxService, settings)); - } - } - break; - case "ReceiverPlugin": - { - if (!(ev.Settings is TxrxSettings settings)) - { - throw new Exception("Unexpected settings for ReceiverPlugin"); - } - else - { - PluginManager.RegisterPlugin(new ReceiverPlugin(ev.Id, EventFactory, EventBus, TxrxService, settings)); - } - } - break; - default: - throw new Exception($"Unknown plugin '{ev.PluginName}'"); - } + + PluginManager.RegisterPlugin(PluginFactory.CreatePlugin(ev.Id, ev.PluginName, ev.Settings)); } protected override void Main() diff --git a/Backend/PluginFactory.cs b/Backend/PluginFactory.cs new file mode 100644 index 00000000..bec3f118 --- /dev/null +++ b/Backend/PluginFactory.cs @@ -0,0 +1,105 @@ +using Slipstream.Backend.Plugins; +using Slipstream.Backend.Services; +using Slipstream.Shared; +using Slipstream.Shared.Events.Setting; +using System; + +#nullable enable + +namespace Slipstream.Backend +{ + public class PluginFactory + { + private readonly IEventFactory EventFactory; + private readonly IEventBus EventBus; + private readonly IStateService StateService; + private readonly ITxrxService TxrxService; + + public PluginFactory(IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, ITxrxService txrxService) + { + EventFactory = eventFactory; + EventBus = eventBus; + StateService = stateService; + TxrxService = txrxService; + } + + public IPlugin CreatePlugin(string id, string name, IEvent? settings) + { + switch (name) + { + case "FileMonitorPlugin": + { + if (!(settings is FileMonitorSettings typedSettings)) + { + throw new Exception("Unexpected settings for FileMonitorPlugin"); + } + else + { + return new FileMonitorPlugin(id, EventFactory, EventBus, typedSettings); + } + } + case "FileTriggerPlugin": + return new FileTriggerPlugin(id, EventFactory, EventBus); + case "LuaPlugin": + { + if (!(settings is LuaSettings typedSettings)) + { + throw new Exception("Unexpected settings for LuaPlugin"); + } + else + { + return new LuaPlugin(id, EventFactory, EventBus, StateService, typedSettings); + } + } + case "AudioPlugin": + { + if (!(settings is AudioSettings typedSettings)) + { + throw new Exception("Unexpected settings for AudioPlugin"); + } + else + { + return new AudioPlugin(id, EventFactory, EventBus, typedSettings); + } + } + case "IRacingPlugin": + return new IRacingPlugin(id, EventFactory, EventBus); + case "TwitchPlugin": + { + if (!(settings is TwitchSettings typedSettings)) + { + throw new Exception("Unexpected settings for TwitchPlugin"); + } + else + { + return new TwitchPlugin(id, EventFactory, EventBus, typedSettings); + } + } + case "TransmitterPlugin": + { + if (!(settings is TxrxSettings typedSettings)) + { + throw new Exception("Unexpected settings for TransmitterPlugin"); + } + else + { + return new TransmitterPlugin(id, EventFactory, EventBus, TxrxService, typedSettings); + } + } + case "ReceiverPlugin": + { + if (!(settings is TxrxSettings typedSettings)) + { + throw new Exception("Unexpected settings for ReceiverPlugin"); + } + else + { + return new ReceiverPlugin(id, EventFactory, EventBus, TxrxService, typedSettings); + } + } + default: + throw new Exception($"Unknown plugin '{name}'"); + } + } + } +} diff --git a/Program.cs b/Program.cs index 992fa9f7..96fb77e4 100644 --- a/Program.cs +++ b/Program.cs @@ -44,6 +44,7 @@ private static void ConfigureServices(ServiceCollection services) services.AddScoped(); services.AddScoped(x => x.GetService()); services.AddScoped(); + services.AddScoped(); services.AddScoped(x => new Backend.Services.StateService(x.GetService(), x.GetService(), Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + $@"\Slipstream\state.txt")); services.AddScoped(); services.AddScoped(); diff --git a/Slipstream.csproj b/Slipstream.csproj index b82a3638..265acf1d 100644 --- a/Slipstream.csproj +++ b/Slipstream.csproj @@ -132,6 +132,7 @@ + From 3e103bae530deea2b61c7a242eadd70981e63998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=B8llegaard?= Date: Sun, 3 Jan 2021 18:23:39 +0100 Subject: [PATCH 3/9] Simplify settings. Now they are injected. No more Settings messages. Also means that we cannot reconfigure a plugin without recreating it. --- Backend/Engine.cs | 30 ++---- Backend/IEngine.cs | 1 - Backend/PluginFactory.cs | 93 ++++--------------- Backend/PluginManager.cs | 27 ++++-- Backend/Plugins/AudioPlugin.cs | 14 +-- Backend/Plugins/FileMonitorPlugin.cs | 14 +-- Backend/Plugins/FileTriggerPlugin.cs | 30 +++--- Backend/Plugins/LuaPlugin.cs | 19 +--- Backend/Plugins/ReceiverPlugin.cs | 42 ++++----- Backend/Plugins/TransmitterPlugin.cs | 50 +++++----- Backend/Plugins/TwitchPlugin.cs | 40 +++----- Frontend/ApplicationConfiguration.cs | 52 +++-------- Frontend/MainWindow.cs | 28 +----- Program.cs | 5 +- Shared/EventFactory.cs | 52 +---------- Shared/EventHandler.cs | 54 ----------- .../Internal/InternalCommandPluginRegister.cs | 1 - Shared/Events/Setting/AudioSettings.cs | 11 --- Shared/Events/Setting/FileMonitorSettings.cs | 11 --- Shared/Events/Setting/LuaSettings.cs | 12 --- Shared/Events/Setting/TwitchSettings.cs | 12 --- Shared/Events/Setting/TxrxSettings.cs | 11 --- Shared/IApplicationConfiguration.cs | 34 ++++++- Shared/IEventFactory.cs | 8 -- Slipstream.csproj | 5 - 25 files changed, 173 insertions(+), 483 deletions(-) delete mode 100644 Shared/Events/Setting/AudioSettings.cs delete mode 100644 Shared/Events/Setting/FileMonitorSettings.cs delete mode 100644 Shared/Events/Setting/LuaSettings.cs delete mode 100644 Shared/Events/Setting/TwitchSettings.cs delete mode 100644 Shared/Events/Setting/TxrxSettings.cs diff --git a/Backend/Engine.cs b/Backend/Engine.cs index 6c9f26c5..a4b67ee2 100644 --- a/Backend/Engine.cs +++ b/Backend/Engine.cs @@ -1,6 +1,4 @@ -using Slipstream.Backend.Services; -using Slipstream.Shared; -using Slipstream.Shared.Events; +using Slipstream.Shared; using Slipstream.Shared.Events.Internal; using System; using static Slipstream.Shared.IEventFactory; @@ -13,20 +11,17 @@ partial class Engine : Worker, IEngine, IDisposable { private readonly IEventFactory EventFactory; private readonly IEventBus EventBus; - private readonly PluginManager PluginManager; + private readonly IPluginManager PluginManager; private readonly IEventBusSubscription Subscription; - private readonly IApplicationConfiguration ApplicationConfiguration; private readonly PluginFactory PluginFactory; private readonly Shared.EventHandler EventHandler = new Shared.EventHandler(); - public Engine(IEventFactory eventFactory, IEventBus eventBus, IApplicationConfiguration applicationConfiguration, PluginFactory pluginFactory) : base("engine") + public Engine(IEventFactory eventFactory, IEventBus eventBus, PluginFactory pluginFactory, IPluginManager pluginManager) : base("engine") { EventFactory = eventFactory; EventBus = eventBus; - ApplicationConfiguration = applicationConfiguration; PluginFactory = pluginFactory; - - PluginManager = new PluginManager(this, eventFactory, eventBus); + PluginManager = pluginManager; Subscription = EventBus.RegisterListener(); @@ -37,13 +32,13 @@ public Engine(IEventFactory eventFactory, IEventBus eventBus, IApplicationConfig EventHandler.OnInternalCommandPluginStates += (s, e) => OnCommandPluginStates(e.Event); // Plugins.. - RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("FileMonitorPlugin", "FileMonitorPlugin", ApplicationConfiguration.GetFileMonitorSettingsEvent())); + RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("FileMonitorPlugin", "FileMonitorPlugin")); RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("FileTriggerPlugin", "FileTriggerPlugin")); - RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("AudioPlugin", "AudioPlugin", ApplicationConfiguration.GetAudioSettingsEvent())); + RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("AudioPlugin", "AudioPlugin")); RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("IRacingPlugin", "IRacingPlugin")); - RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("TwitchPlugin", "TwitchPlugin", ApplicationConfiguration.GetTwitchSettingsEvent())); - RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("TransmitterPlugin", "TransmitterPlugin", ApplicationConfiguration.GetTxrxSettingsEvent()), false); - RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("ReceiverPlugin", "ReceiverPlugin", ApplicationConfiguration.GetTxrxSettingsEvent()), false); + RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("TwitchPlugin", "TwitchPlugin")); + RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("TransmitterPlugin", "TransmitterPlugin"), false); + RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("ReceiverPlugin", "ReceiverPlugin"), false); // Tell Plugins that we're live - this will make eventbus distribute events EventBus.Enabled = true; @@ -66,11 +61,6 @@ private void RegisterPlugin(InternalCommandPluginRegister e, bool enable = true) } } - public IEventBusSubscription RegisterListener() - { - return EventBus.RegisterListener(); - } - private void OnCommandPluginUnregister(InternalCommandPluginUnregister ev) { PluginManager.UnregisterPlugin(ev.Id); @@ -84,7 +74,7 @@ public void UnregisterSubscription(IEventBusSubscription subscription) private void OnCommandPluginRegister(Shared.Events.Internal.InternalCommandPluginRegister ev) { - PluginManager.RegisterPlugin(PluginFactory.CreatePlugin(ev.Id, ev.PluginName, ev.Settings)); + PluginManager.RegisterPlugin(PluginFactory.CreatePlugin(ev.Id, ev.PluginName)); } protected override void Main() diff --git a/Backend/IEngine.cs b/Backend/IEngine.cs index c707406d..0f300d21 100644 --- a/Backend/IEngine.cs +++ b/Backend/IEngine.cs @@ -4,7 +4,6 @@ namespace Slipstream.Backend { public interface IEngine { - IEventBusSubscription RegisterListener(); void UnregisterSubscription(IEventBusSubscription subscription); void Start(); void Dispose(); diff --git a/Backend/PluginFactory.cs b/Backend/PluginFactory.cs index bec3f118..0d2147b3 100644 --- a/Backend/PluginFactory.cs +++ b/Backend/PluginFactory.cs @@ -1,7 +1,6 @@ using Slipstream.Backend.Plugins; using Slipstream.Backend.Services; using Slipstream.Shared; -using Slipstream.Shared.Events.Setting; using System; #nullable enable @@ -14,92 +13,32 @@ public class PluginFactory private readonly IEventBus EventBus; private readonly IStateService StateService; private readonly ITxrxService TxrxService; + private readonly IApplicationConfiguration ApplicationConfiguration; + private readonly IPluginManager PluginManager; - public PluginFactory(IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, ITxrxService txrxService) + public PluginFactory(IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, ITxrxService txrxService, IApplicationConfiguration applicationConfiguration, IPluginManager pluginManager) { EventFactory = eventFactory; EventBus = eventBus; StateService = stateService; TxrxService = txrxService; + ApplicationConfiguration = applicationConfiguration; + PluginManager = pluginManager; } - public IPlugin CreatePlugin(string id, string name, IEvent? settings) + public IPlugin CreatePlugin(string id, string name) { - switch (name) + return name switch { - case "FileMonitorPlugin": - { - if (!(settings is FileMonitorSettings typedSettings)) - { - throw new Exception("Unexpected settings for FileMonitorPlugin"); - } - else - { - return new FileMonitorPlugin(id, EventFactory, EventBus, typedSettings); - } - } - case "FileTriggerPlugin": - return new FileTriggerPlugin(id, EventFactory, EventBus); - case "LuaPlugin": - { - if (!(settings is LuaSettings typedSettings)) - { - throw new Exception("Unexpected settings for LuaPlugin"); - } - else - { - return new LuaPlugin(id, EventFactory, EventBus, StateService, typedSettings); - } - } - case "AudioPlugin": - { - if (!(settings is AudioSettings typedSettings)) - { - throw new Exception("Unexpected settings for AudioPlugin"); - } - else - { - return new AudioPlugin(id, EventFactory, EventBus, typedSettings); - } - } - case "IRacingPlugin": - return new IRacingPlugin(id, EventFactory, EventBus); - case "TwitchPlugin": - { - if (!(settings is TwitchSettings typedSettings)) - { - throw new Exception("Unexpected settings for TwitchPlugin"); - } - else - { - return new TwitchPlugin(id, EventFactory, EventBus, typedSettings); - } - } - case "TransmitterPlugin": - { - if (!(settings is TxrxSettings typedSettings)) - { - throw new Exception("Unexpected settings for TransmitterPlugin"); - } - else - { - return new TransmitterPlugin(id, EventFactory, EventBus, TxrxService, typedSettings); - } - } - case "ReceiverPlugin": - { - if (!(settings is TxrxSettings typedSettings)) - { - throw new Exception("Unexpected settings for ReceiverPlugin"); - } - else - { - return new ReceiverPlugin(id, EventFactory, EventBus, TxrxService, typedSettings); - } - } - default: - throw new Exception($"Unknown plugin '{name}'"); - } + "FileMonitorPlugin" => new FileMonitorPlugin(id, EventFactory, EventBus, ApplicationConfiguration), + "FileTriggerPlugin" => new FileTriggerPlugin(id, EventFactory, EventBus, StateService, PluginManager), + "AudioPlugin" => new AudioPlugin(id, EventFactory, EventBus, ApplicationConfiguration), + "IRacingPlugin" => new IRacingPlugin(id, EventFactory, EventBus), + "TwitchPlugin" => new TwitchPlugin(id, EventFactory, EventBus, ApplicationConfiguration), + "TransmitterPlugin" => new TransmitterPlugin(id, EventFactory, EventBus, TxrxService, ApplicationConfiguration), + "ReceiverPlugin" => new ReceiverPlugin(id, EventFactory, EventBus, TxrxService, ApplicationConfiguration), + _ => throw new Exception($"Unknown plugin '{name}'"), + }; } } } diff --git a/Backend/PluginManager.cs b/Backend/PluginManager.cs index 1fa38bdb..72b42c74 100644 --- a/Backend/PluginManager.cs +++ b/Backend/PluginManager.cs @@ -7,17 +7,28 @@ namespace Slipstream.Backend { - class PluginManager + public interface IPluginManager : IDisposable + { + public void UnregisterPlugins(); + public void UnregisterPlugin(IPlugin p); + public void UnregisterPlugin(string id); + public void RegisterPlugin(IPlugin plugin); + public void EnablePlugin(IPlugin p); + public void DisablePlugins(); + public void DisablePlugin(IPlugin p); + public void FindPluginAndExecute(string pluginId, Action a); + public void ForAllPluginsExecute(Action a); + } + + public class PluginManager : IPluginManager { - private readonly IEngine Engine; private readonly IEventFactory EventFactory; private readonly IEventBus EventBus; private readonly IDictionary Plugins = new Dictionary(); private readonly IDictionary PluginWorkers = new Dictionary(); - public PluginManager(IEngine engine, IEventFactory eventFactory, IEventBus eventBus) + public PluginManager(IEventFactory eventFactory, IEventBus eventBus) { - Engine = engine; EventFactory = eventFactory; EventBus = eventBus; } @@ -34,7 +45,7 @@ public void UnregisterPlugins() } } - private void UnregisterPlugin(IPlugin p) + public void UnregisterPlugin(IPlugin p) { PluginWorkers[p.WorkerName].RemovePlugin(p); EmitPluginStateChanged(p, PluginStatusEnum.Unregistered); @@ -57,7 +68,7 @@ public void RegisterPlugin(IPlugin plugin) } else { - worker = new PluginWorker(plugin.WorkerName, Engine.RegisterListener(), EventFactory, EventBus); + worker = new PluginWorker(plugin.WorkerName, EventBus.RegisterListener(), EventFactory, EventBus); worker.Start(); PluginWorkers.Add(worker.Name, worker); } @@ -105,7 +116,7 @@ public void FindPluginAndExecute(string pluginId, Action a) } } - internal void ForAllPluginsExecute(Action a) + public void ForAllPluginsExecute(Action a) { lock (Plugins) { @@ -128,7 +139,7 @@ public void UnregisterPlugin(string id) } } - internal void Dispose() + public void Dispose() { DisablePlugins(); UnregisterPlugins(); diff --git a/Backend/Plugins/AudioPlugin.cs b/Backend/Plugins/AudioPlugin.cs index b2fbe013..b2825933 100644 --- a/Backend/Plugins/AudioPlugin.cs +++ b/Backend/Plugins/AudioPlugin.cs @@ -1,7 +1,5 @@ using NAudio.Wave; using Slipstream.Shared; -using Slipstream.Shared.Events.Setting; -using Slipstream.Shared.Events.UI; using System; using System.Speech.Synthesis; using System.Threading; @@ -15,27 +13,21 @@ class AudioPlugin : BasePlugin { private readonly IEventFactory EventFactory; private readonly IEventBus EventBus; - private string? Path; + private readonly string Path; private readonly SpeechSynthesizer Synthesizer; - public AudioPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, AudioSettings settings) : base(id, "AudioPlugin", "AudioPlugin", "Audio") + public AudioPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, IAudioConfiguration audioConfiguration) : base(id, "AudioPlugin", "AudioPlugin", "Audio") { EventFactory = eventFactory; EventBus = eventBus; - EventHandler_OnSettingAudioSettings(settings); + Path = audioConfiguration.AudioPath; Synthesizer = new SpeechSynthesizer(); Synthesizer.SetOutputToDefaultAudioDevice(); EventHandler.OnAudioCommandSay += EventHandler_OnUtilitySay; EventHandler.OnAudioCommandPlay += EventHandler_OnAudioCommandPlay; - EventHandler.OnSettingAudioSettings += (s, e) => EventHandler_OnSettingAudioSettings(e.Event); - } - - private void EventHandler_OnSettingAudioSettings(AudioSettings e) - { - Path = e.Path; } private void EventHandler_OnAudioCommandPlay(Shared.EventHandler source, Shared.EventHandler.EventHandlerArgs e) diff --git a/Backend/Plugins/FileMonitorPlugin.cs b/Backend/Plugins/FileMonitorPlugin.cs index c4e8f0d2..3acd6436 100644 --- a/Backend/Plugins/FileMonitorPlugin.cs +++ b/Backend/Plugins/FileMonitorPlugin.cs @@ -1,6 +1,5 @@ using Slipstream.Shared; using Slipstream.Shared.Events.FileMonitor; -using Slipstream.Shared.Events.Setting; using System.Collections.Generic; using System.IO; @@ -15,14 +14,12 @@ class FileMonitorPlugin : BasePlugin private readonly IList fileSystemWatchers = new List(); private readonly bool InitialScan = false; - public FileMonitorPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, FileMonitorSettings settings) : base(id, "FileMonitorPlugin", "FileMonitorPlugin", "Core") + public FileMonitorPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, IFileMonitorConfiguration fileMonitorConfiguration) : base(id, "FileMonitorPlugin", "FileMonitorPlugin", "Core") { EventFactory = eventFactory; EventBus = eventBus; - OnFileMonitorSettings(settings); - - EventHandler.OnSettingFileMonitorSettings += (s, e) => OnFileMonitorSettings(e.Event); + StartMonitoring(fileMonitorConfiguration.FileMonitorPaths); } public override void OnDisable() @@ -56,17 +53,14 @@ private void RescanExistingFiles() EventBus.PublishEvent(new FileMonitorScanCompleted()); } - private void OnFileMonitorSettings(FileMonitorSettings ev) + private void StartMonitoring(string[] paths) { // Delete all watchers, and then recreate them foreach (var watcher in fileSystemWatchers) watcher.Dispose(); fileSystemWatchers.Clear(); - if (ev.Paths == null) - return; - - foreach (var path in ev.Paths) + foreach (var path in paths) { var watcher = new FileSystemWatcher(path); watcher.Created += WatcherOnCreated; diff --git a/Backend/Plugins/FileTriggerPlugin.cs b/Backend/Plugins/FileTriggerPlugin.cs index 5bb946d2..d77dd86b 100644 --- a/Backend/Plugins/FileTriggerPlugin.cs +++ b/Backend/Plugins/FileTriggerPlugin.cs @@ -1,4 +1,5 @@ -using Slipstream.Shared; +using Slipstream.Backend.Services; +using Slipstream.Shared; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -12,7 +13,9 @@ class FileTriggerPlugin : BasePlugin { private readonly IEventFactory EventFactory; private readonly IEventBus EventBus; - private readonly IDictionary Scripts = new Dictionary(); + private readonly IStateService StateService; + private readonly IPluginManager PluginManager; + private readonly IDictionary Scripts = new Dictionary(); // At bootup we will receive zero or more FileCreated events ending with a ScanCompleted. // If we count the (relevant) files found that launches LuaPlugin, we can keep an eye on PluginState @@ -20,10 +23,12 @@ class FileTriggerPlugin : BasePlugin private bool BootUp = true; private readonly List WaitingForLuaScripts = new List(); - public FileTriggerPlugin(string id, IEventFactory eventFactory, IEventBus eventBus) : base(id, "FileTriggerPlugin", "FileTriggerPlugin", "Core") + public FileTriggerPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, IPluginManager pluginManager) : base(id, "FileTriggerPlugin", "FileTriggerPlugin", "Core") { EventFactory = eventFactory; EventBus = eventBus; + StateService = stateService; + PluginManager = pluginManager; EventHandler.OnFileMonitorFileCreated += EventHandler_OnFileMonitorFileCreated; EventHandler.OnFileMonitorFileDeleted += EventHandler_OnFileMonitorFileDeleted; @@ -44,7 +49,6 @@ private void EventHandler_OnInternalPluginState(EventHandler source, EventHandle // We're done EventHandler.OnInternalPluginState -= EventHandler_OnInternalPluginState; - EventBus.PublishEvent(new Shared.Events.Internal.InternalInitialized()); EventBus.PublishEvent(EventFactory.CreateInternalInitialized()); } } @@ -57,6 +61,11 @@ private void EventHandler_OnFileMonitorScanCompleted(EventHandler source, EventH BootUp = false; } + class LuaConfiguration : ILuaConfiguration + { + public string FilePath { get; set; } = ""; + } + private void NewFile(string filePath) { if (Scripts.ContainsKey(filePath)) @@ -64,7 +73,6 @@ private void NewFile(string filePath) return; } - string pluginName = "LuaPlugin"; string pluginId = Path.GetFileName(filePath); if (BootUp) @@ -72,17 +80,17 @@ private void NewFile(string filePath) WaitingForLuaScripts.Add(pluginId); } - EventBus.PublishEvent(EventFactory.CreateInternalCommandPluginRegister(pluginId, pluginName, EventFactory.CreateLuaSettings(pluginId, filePath))); - EventBus.PublishEvent(EventFactory.CreateInternalCommandPluginEnable(pluginId)); + // Use PluginManager director + var plugin = new LuaPlugin(pluginId, EventFactory, EventBus, StateService, new LuaConfiguration { FilePath = filePath }); + PluginManager.RegisterPlugin(plugin); + PluginManager.EnablePlugin(plugin); - Scripts.Add(filePath, pluginId); + Scripts.Add(filePath, plugin); } private void DeletedFile(string filePath) { - var ev = EventFactory.CreateInternalCommandPluginUnregister(Scripts[filePath]); - - EventBus.PublishEvent(ev); + PluginManager.UnregisterPlugin(Scripts[filePath]); Scripts.Remove(filePath); } diff --git a/Backend/Plugins/LuaPlugin.cs b/Backend/Plugins/LuaPlugin.cs index a47141f9..3b1f39f7 100644 --- a/Backend/Plugins/LuaPlugin.cs +++ b/Backend/Plugins/LuaPlugin.cs @@ -1,7 +1,6 @@ using Slipstream.Backend.Services; using Slipstream.Backend.Services.LuaServiceLib; using Slipstream.Shared; -using Slipstream.Shared.Events.Setting; using System.IO; using EventHandler = Slipstream.Shared.EventHandler; @@ -16,35 +15,27 @@ public class LuaPlugin : BasePlugin private readonly IEventBus EventBus; private readonly LuaService LuaService; private ILuaContext? LuaContext; - private string Prefix = ""; - private string? FilePath; + private readonly string Prefix = ""; + private readonly string FilePath; - public LuaPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, LuaSettings settings) : base(id, "LuaPlugin", "LuaPlugin", "Lua") + public LuaPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, ILuaConfiguration configuration) : base(id, "LuaPlugin", "LuaPlugin", "Lua") { EventFactory = eventFactory; EventBus = eventBus; LuaService = new LuaService(eventFactory, eventBus, stateService); - EventHandler.OnSettingLuaSettings += (s, e) => OnLuaSettings(e.Event); - // Avoid that WriteToConsole is evaluated by Lua, that in turn will // add more WriteToConsole events, making a endless loop EventHandler.OnUICommandWriteToConsole += (s, e) => { }; EventHandler.OnDefault += (s, e) => LuaContext?.HandleEvent(e.Event); - OnLuaSettings(settings); - } - - private void OnLuaSettings(LuaSettings @event) - { - if (Id != @event.PluginId) - return; - FilePath = @event.FilePath; + FilePath = configuration.FilePath; Prefix = Path.GetFileName(FilePath); StartLua(); + } private void StartLua() diff --git a/Backend/Plugins/ReceiverPlugin.cs b/Backend/Plugins/ReceiverPlugin.cs index 2eebb820..9d0c111c 100644 --- a/Backend/Plugins/ReceiverPlugin.cs +++ b/Backend/Plugins/ReceiverPlugin.cs @@ -1,12 +1,9 @@ using Slipstream.Backend.Services; using Slipstream.Shared; -using Slipstream.Shared.Events.Setting; -using Slipstream.Shared.Events.UI; using System; using System.Diagnostics; using System.Net; using System.Net.Sockets; -using EventHandler = Slipstream.Shared.EventHandler; #nullable enable @@ -16,23 +13,34 @@ class ReceiverPlugin : BasePlugin { private readonly IEventBus EventBus; private readonly IEventFactory EventFactory; - private string Ip = ""; - private Int32 Port = 42424; + private readonly string Ip = ""; + private readonly Int32 Port = 42424; private TcpListener? Listener; private readonly ITxrxService TxrxService; private Socket? Client; private const int READ_BUFFER_SIZE = 1024 * 16; readonly byte[] ReadBuffer = new byte[READ_BUFFER_SIZE]; - public ReceiverPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, ITxrxService txrxService, TxrxSettings settings) : base(id, "ReceiverPlugin", "ReceiverPlugin", "ReceiverPlugin") + public ReceiverPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, ITxrxService txrxService, ITxrxConfiguration txrxConfiguration) : base(id, "ReceiverPlugin", "ReceiverPlugin", "ReceiverPlugin") { EventFactory = eventFactory; EventBus = eventBus; TxrxService = txrxService; - OnSetting(settings); + var input = txrxConfiguration.TxrxIpPort.Split(':'); - EventHandler.OnSettingTxrxSettings += (s, e) => OnSetting(e.Event); + if (input.Length == 2) + { + Ip = input[0]; + if (!Int32.TryParse(input[1], out Port)) + { + EventBus.PublishEvent(EventFactory.CreateUICommandWriteToConsole($"ReceiverPlugin: Invalid port in TxrxHost provided: '{txrxConfiguration.TxrxIpPort}'")); + } + } + else + { + EventBus.PublishEvent(EventFactory.CreateUICommandWriteToConsole($"ReceiverPlugin: Invalid TxrxHost provided: '{txrxConfiguration.TxrxIpPort}'")); + } } public override void OnEnable() @@ -47,24 +55,6 @@ public override void OnDisable() Client = null; } - private void OnSetting(TxrxSettings e) - { - var input = e.TxrxIpPort.Split(':'); - - if (input.Length == 2) - { - Ip = input[0]; - if (!Int32.TryParse(input[1], out Port)) - { - EventBus.PublishEvent(EventFactory.CreateUICommandWriteToConsole($"ReceiverPlugin: Invalid port in TxrxHost provided: '{e.TxrxIpPort}'")); - } - } - else - { - EventBus.PublishEvent(EventFactory.CreateUICommandWriteToConsole($"ReceiverPlugin: Invalid TxrxHost provided: '{e.TxrxIpPort}'")); - } - } - private void SetupListener() { IPAddress localAddr = IPAddress.Parse(Ip); diff --git a/Backend/Plugins/TransmitterPlugin.cs b/Backend/Plugins/TransmitterPlugin.cs index fd01adea..cc258e4d 100644 --- a/Backend/Plugins/TransmitterPlugin.cs +++ b/Backend/Plugins/TransmitterPlugin.cs @@ -1,7 +1,5 @@ using Slipstream.Backend.Services; using Slipstream.Shared; -using Slipstream.Shared.Events.Setting; -using Slipstream.Shared.Events.UI; using System; using System.Diagnostics; using System.Net.Sockets; @@ -16,20 +14,36 @@ class TransmitterPlugin : BasePlugin { private readonly IEventBus EventBus; private readonly IEventFactory EventFactory; - private string Ip = ""; - private Int32 Port = 42424; + private readonly string Ip = ""; + private readonly Int32 Port = 42424; private TcpClient? Client = null; private readonly ITxrxService TxrxService; - public TransmitterPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, ITxrxService txrxService, TxrxSettings settings) : base(id, "TransmitterPlugin", "TransmitterPlugin", "TransmitterPlugin") + public TransmitterPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, ITxrxService txrxService, ITxrxConfiguration txrxConfiguration) : base(id, "TransmitterPlugin", "TransmitterPlugin", "TransmitterPlugin") { EventBus = eventBus; EventFactory = eventFactory; TxrxService = txrxService; - OnSetting(settings); + var input = txrxConfiguration.TxrxIpPort.Split(':'); + + if (input.Length == 2) + { + Ip = input[0]; + if (!Int32.TryParse(input[1], out Port)) + { + EventBus.PublishEvent(EventFactory.CreateUICommandWriteToConsole($"TransmitterPlugin: Invalid port in TxrxIpPort provided: '{txrxConfiguration.TxrxIpPort}'")); + } + else + { + Reset(); + } + } + else + { + EventBus.PublishEvent(EventFactory.CreateUICommandWriteToConsole($"TransmitterPlugin: Invalid TxrxIpPort provided: '{txrxConfiguration.TxrxIpPort}'")); + } - EventHandler.OnSettingTxrxSettings += (s, e) => OnSetting(e.Event); EventHandler.OnDefault += (s, e) => OnEvent(e.Event); } @@ -60,28 +74,6 @@ private void OnEvent(IEvent @event) } } - private void OnSetting(TxrxSettings e) - { - var input = e.TxrxIpPort.Split(':'); - - if (input.Length == 2) - { - Ip = input[0]; - if (!Int32.TryParse(input[1], out Port)) - { - EventBus.PublishEvent(EventFactory.CreateUICommandWriteToConsole($"TransmitterPlugin: Invalid port in TxrxIpPort provided: '{e.TxrxIpPort}'")); - } - else - { - Reset(); - } - } - else - { - EventBus.PublishEvent(EventFactory.CreateUICommandWriteToConsole($"TransmitterPlugin: Invalid TxrxIpPort provided: '{e.TxrxIpPort}'")); - } - } - private void Reset() { Client?.Close(); diff --git a/Backend/Plugins/TwitchPlugin.cs b/Backend/Plugins/TwitchPlugin.cs index df0d1ead..cb815cd8 100644 --- a/Backend/Plugins/TwitchPlugin.cs +++ b/Backend/Plugins/TwitchPlugin.cs @@ -1,7 +1,4 @@ using Slipstream.Shared; -using Slipstream.Shared.Events.Setting; -using Slipstream.Shared.Events.Twitch; -using Slipstream.Shared.Events.UI; using System; using TwitchLib.Client; using TwitchLib.Client.Events; @@ -21,19 +18,18 @@ class TwitchPlugin : BasePlugin private readonly IEventFactory EventFactory; private readonly IEventBus EventBus; - private string? TwitchUsername; - private string? TwitchChannel; - private string? TwitchToken; - private bool TwitchLog; + private readonly string TwitchUsername; + private readonly string TwitchChannel; + private readonly string TwitchToken; + private readonly bool TwitchLog; private bool RequestReconnect; private bool AnnouncedConnected = false; - public TwitchPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, TwitchSettings settings) : base(id, "TwitchPlugin", "TwitchPlugin", "TwitchPlugin") + public TwitchPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, ITwitchConfiguration twitchConfiguration) : base(id, "TwitchPlugin", "TwitchPlugin", "TwitchPlugin") { EventFactory = eventFactory; EventBus = eventBus; - EventHandler.OnSettingTwitchSettings += (s, e) => OnTwitchSettings(e.Event); EventHandler.OnTwitchCommandSendMessage += (s, e) => { if (Client != null && Client.JoinedChannels.Count > 0 && Enabled) @@ -42,26 +38,12 @@ public TwitchPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, T } }; - OnTwitchSettings(settings); - } - - private void OnTwitchSettings(TwitchSettings settings) - { - { - if (TwitchUsername != settings.TwitchUsername || TwitchToken != settings.TwitchToken || TwitchChannel != settings.TwitchChannel || TwitchLog != settings.TwitchLog) - { - TwitchUsername = settings.TwitchUsername; - TwitchChannel = settings.TwitchChannel; - TwitchToken = settings.TwitchToken; - TwitchLog = settings.TwitchLog; - - if (String.IsNullOrEmpty(TwitchChannel)) - TwitchChannel = TwitchUsername; - - Disconnect(); - Connect(); - } - }; + TwitchUsername = twitchConfiguration.TwitchUsername; + TwitchChannel = twitchConfiguration.TwitchChannel; + TwitchToken = twitchConfiguration.TwitchToken; + TwitchLog = twitchConfiguration.TwitchLog; + if (String.IsNullOrEmpty(TwitchChannel)) + TwitchChannel = TwitchUsername; } public override void OnDisable() diff --git a/Frontend/ApplicationConfiguration.cs b/Frontend/ApplicationConfiguration.cs index a2fc5cf9..1e31cc69 100644 --- a/Frontend/ApplicationConfiguration.cs +++ b/Frontend/ApplicationConfiguration.cs @@ -1,56 +1,26 @@ using Slipstream.Shared; -using Slipstream.Shared.Events.Setting; namespace Slipstream.Frontend { public class ApplicationConfiguration : IApplicationConfiguration { - private readonly IEventFactory EventFactory; private readonly Properties.Settings Settings; - public ApplicationConfiguration(IEventFactory eventFactory) + public ApplicationConfiguration() { - EventFactory = eventFactory; Settings = Properties.Settings.Default; - System.IO.Directory.CreateDirectory(GetAudioPath()); - System.IO.Directory.CreateDirectory(GetScriptsPath()); + System.IO.Directory.CreateDirectory(AudioPath); + System.IO.Directory.CreateDirectory(ScriptPath); } - public string GetAudioPath() - { - return @"Audio\"; - } - - public AudioSettings GetAudioSettingsEvent() - { - return EventFactory.CreateAudioSettings(GetAudioPath()); - } - - public FileMonitorSettings GetFileMonitorSettingsEvent() - { - return EventFactory.CreateFileMonitorSettings(new string[] { GetScriptsPath() }); - } - - public string GetScriptsPath() - { - return @"Scripts\"; - } - - public TwitchSettings GetTwitchSettingsEvent() - { - return EventFactory.CreateTwitchSettings - ( - twitchUsername: Settings.TwitchUsername, - twitchChannel: Settings.TwitchChannel, - twitchToken: Settings.TwitchToken, - twitchLog: Settings.TwitchLog - ); - } - - public TxrxSettings GetTxrxSettingsEvent() - { - return EventFactory.CreateTxrxSettings(Settings.TxrxIpPort); - } + public string TxrxIpPort => Settings.TxrxIpPort; + public string AudioPath => @"Audio\"; + public string ScriptPath => @"Scripts\"; + public string[] FileMonitorPaths => new string[] { ScriptPath }; + public string TwitchUsername => Settings.TwitchUsername; + public string TwitchChannel => Settings.TwitchChannel; + public string TwitchToken => Settings.TwitchToken; + public bool TwitchLog => Settings.TwitchLog; } } diff --git a/Frontend/MainWindow.cs b/Frontend/MainWindow.cs index c85df155..d561c1ff 100644 --- a/Frontend/MainWindow.cs +++ b/Frontend/MainWindow.cs @@ -113,8 +113,7 @@ private void EventHandler_OnUICommandCreateButton(UICommandCreateButton @event) b.Click += (s, e) => { - var b = s as Button; - if(b != null) + if (s is Button b) EventBus.PublishEvent(EventFactory.CreateUIButtonTriggered(b.Text)); }; @@ -173,25 +172,12 @@ private void EventHandler_OnInternalPluginState(Shared.Events.Internal.InternalP { switch (e.Id) { - case "FileMonitorPlugin": - EventBus.PublishEvent(ApplicationConfiguration.GetFileMonitorSettingsEvent()); - break; - - case "AudioPlugin": - EventBus.PublishEvent(ApplicationConfiguration.GetAudioSettingsEvent()); - break; - - case "TwitchPlugin": - var settings = Properties.Settings.Default; - EventBus.PublishEvent(ApplicationConfiguration.GetTwitchSettingsEvent()); - break; - case "TransmitterPlugin": - ExecuteSecure(() => Text += $" <<< transmitting to {ApplicationConfiguration.GetTxrxSettingsEvent().TxrxIpPort} >>>"); + ExecuteSecure(() => Text += $" <<< transmitting to {ApplicationConfiguration.TxrxIpPort} >>>"); break; case "ReceiverPlugin": - ExecuteSecure(() => Text += $" <<< receiving from {ApplicationConfiguration.GetTxrxSettingsEvent().TxrxIpPort} >>>"); + ExecuteSecure(() => Text += $" <<< receiving from {ApplicationConfiguration.TxrxIpPort} >>>"); break; } @@ -245,21 +231,17 @@ private void ExitToolStripMenuItem_Click(object sender, EventArgs e) private void OpenScriptsDirectoryToolStripMenuItem_Click(object sender, EventArgs e) { - Process.Start(ApplicationConfiguration.GetScriptsPath()); + Process.Start(ApplicationConfiguration.ScriptPath); } private void OpenAudioDirectoryToolStripMenuItem_Click(object sender, EventArgs e) { - Process.Start(ApplicationConfiguration.GetAudioPath()); + Process.Start(ApplicationConfiguration.AudioPath); } private void SettingsToolStripMenuItem_Click(object sender, EventArgs e) { _ = new SettingsForm().ShowDialog(this); - - // Just spam settings to everyone that wants it - EventBus.PublishEvent(ApplicationConfiguration.GetTwitchSettingsEvent()); - EventBus.PublishEvent(ApplicationConfiguration.GetTxrxSettingsEvent()); } } } diff --git a/Program.cs b/Program.cs index 96fb77e4..81fb2e75 100644 --- a/Program.cs +++ b/Program.cs @@ -39,7 +39,7 @@ private static void ConfigureServices(ServiceCollection services) { services.AddScoped(); services.AddScoped(); - services.AddScoped(x => x.GetService()); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(x => x.GetService()); @@ -48,7 +48,8 @@ private static void ConfigureServices(ServiceCollection services) services.AddScoped(x => new Backend.Services.StateService(x.GetService(), x.GetService(), Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + $@"\Slipstream\state.txt")); services.AddScoped(); services.AddScoped(); - services.AddTransient(); + services.AddScoped(); + services.AddSingleton(); } } } diff --git a/Shared/EventFactory.cs b/Shared/EventFactory.cs index d0e59067..c23d0a91 100644 --- a/Shared/EventFactory.cs +++ b/Shared/EventFactory.cs @@ -2,7 +2,6 @@ using Slipstream.Shared.Events.FileMonitor; using Slipstream.Shared.Events.Internal; using Slipstream.Shared.Events.IRacing; -using Slipstream.Shared.Events.Setting; using Slipstream.Shared.Events.Twitch; using Slipstream.Shared.Events.UI; using System; @@ -56,12 +55,7 @@ public InternalCommandPluginEnable CreateInternalCommandPluginEnable(string plug public InternalCommandPluginRegister CreateInternalCommandPluginRegister(string pluginId, string pluginName) { - return CreateInternalCommandPluginRegister(pluginId, pluginName, null); - } - - public InternalCommandPluginRegister CreateInternalCommandPluginRegister(string pluginId, string pluginName, IEvent? settings) - { - return new InternalCommandPluginRegister { Id = pluginId, PluginName = pluginName, Settings = settings }; + return new InternalCommandPluginRegister { Id = pluginId, PluginName = pluginName }; } public InternalCommandPluginStates CreateInternalCommandPluginStates() @@ -371,50 +365,6 @@ public IRacingWeatherInfo CreateIRacingWeatherInfo( }; } - public AudioSettings CreateAudioSettings(string path) - { - return new AudioSettings - { - Path = path - }; - } - - public FileMonitorSettings CreateFileMonitorSettings(string[] paths) - { - return new FileMonitorSettings - { - Paths = paths - }; - } - - public LuaSettings CreateLuaSettings(string pluginId, string filePath) - { - return new LuaSettings - { - PluginId = pluginId, - FilePath = filePath, - }; - } - - public TwitchSettings CreateTwitchSettings(string twitchUsername, string twitchChannel, string twitchToken, bool twitchLog) - { - return new TwitchSettings - { - TwitchUsername = twitchUsername, - TwitchChannel = twitchChannel, - TwitchToken = twitchToken, - TwitchLog = twitchLog - }; - } - - public TxrxSettings CreateTxrxSettings(string txrxIpPort) - { - return new TxrxSettings - { - TxrxIpPort = txrxIpPort - }; - } - public TwitchCommandSendMessage CreateTwitchCommandSendMessage(string message) { return new TwitchCommandSendMessage diff --git a/Shared/EventHandler.cs b/Shared/EventHandler.cs index f1ab18c2..800d44ef 100644 --- a/Shared/EventHandler.cs +++ b/Shared/EventHandler.cs @@ -84,23 +84,6 @@ public EventHandlerArgs(T e) public event OnAudioCommandPlayHandler? OnAudioCommandPlay; #endregion - #region Events: Setting - public delegate void OnSettingFileMonitorSettingsHandler(EventHandler source, EventHandlerArgs e); - public event OnSettingFileMonitorSettingsHandler? OnSettingFileMonitorSettings; - - public delegate void OnSettingAudioSettingsHandler(EventHandler source, EventHandlerArgs e); - public event OnSettingAudioSettingsHandler? OnSettingAudioSettings; - - public delegate void OnSettingTwitchSettingsHandler(EventHandler source, EventHandlerArgs e); - public event OnSettingTwitchSettingsHandler? OnSettingTwitchSettings; - - public delegate void OnSettingLuaSettingsHandler(EventHandler source, EventHandlerArgs e); - public event OnSettingLuaSettingsHandler? OnSettingLuaSettings; - - public delegate void OnSettingTxrxSettingsHandler(EventHandler source, EventHandlerArgs e); - public event OnSettingTxrxSettingsHandler? OnSettingTxrxSettings; - #endregion - #region Events: IRacing public delegate void OnIRacingConnectedHandler(EventHandler source, EventHandlerArgs e); public event OnIRacingConnectedHandler? OnIRacingConnected; @@ -287,43 +270,6 @@ public void HandleEvent(IEvent? ev) OnUIButtonTriggered.Invoke(this, new EventHandlerArgs(tev)); break; - // Setting - - case Shared.Events.Setting.FileMonitorSettings tev: - if (OnSettingFileMonitorSettings == null) - OnDefault?.Invoke(this, new EventHandlerArgs(tev)); - else - OnSettingFileMonitorSettings.Invoke(this, new EventHandlerArgs(tev)); - break; - - case Shared.Events.Setting.AudioSettings tev: - if (OnSettingAudioSettings == null) - OnDefault?.Invoke(this, new EventHandlerArgs(tev)); - else - OnSettingAudioSettings.Invoke(this, new EventHandlerArgs(tev)); - break; - - case Shared.Events.Setting.TwitchSettings tev: - if (OnSettingTwitchSettings == null) - OnDefault?.Invoke(this, new EventHandlerArgs(tev)); - else - OnSettingTwitchSettings.Invoke(this, new EventHandlerArgs(tev)); - break; - - case Shared.Events.Setting.LuaSettings tev: - if (OnSettingLuaSettings == null) - OnDefault?.Invoke(this, new EventHandlerArgs(tev)); - else - OnSettingLuaSettings.Invoke(this, new EventHandlerArgs(tev)); - break; - - case Shared.Events.Setting.TxrxSettings tev: - if (OnSettingTxrxSettings == null) - OnDefault?.Invoke(this, new EventHandlerArgs(tev)); - else - OnSettingTxrxSettings.Invoke(this, new EventHandlerArgs(tev)); - break; - // IRacing case Shared.Events.IRacing.IRacingConnected tev: diff --git a/Shared/Events/Internal/InternalCommandPluginRegister.cs b/Shared/Events/Internal/InternalCommandPluginRegister.cs index 0b39ba26..400b8518 100644 --- a/Shared/Events/Internal/InternalCommandPluginRegister.cs +++ b/Shared/Events/Internal/InternalCommandPluginRegister.cs @@ -8,6 +8,5 @@ public class InternalCommandPluginRegister : IEvent public bool ExcludeFromTxrx => true; public string Id { get; set; } = "INVALID-PLUGIN-ID"; public string PluginName { get; set; } = "INVALID-PLUGIN-NAME"; - public IEvent? Settings; } } diff --git a/Shared/Events/Setting/AudioSettings.cs b/Shared/Events/Setting/AudioSettings.cs deleted file mode 100644 index 49056f0a..00000000 --- a/Shared/Events/Setting/AudioSettings.cs +++ /dev/null @@ -1,11 +0,0 @@ -#nullable enable - -namespace Slipstream.Shared.Events.Setting -{ - public class AudioSettings : IEvent - { - public string EventType => "AudioSettings"; - public bool ExcludeFromTxrx => true; - public string? Path { get; set; } - } -} diff --git a/Shared/Events/Setting/FileMonitorSettings.cs b/Shared/Events/Setting/FileMonitorSettings.cs deleted file mode 100644 index ae805e25..00000000 --- a/Shared/Events/Setting/FileMonitorSettings.cs +++ /dev/null @@ -1,11 +0,0 @@ -#nullable enable - -namespace Slipstream.Shared.Events.Setting -{ - public class FileMonitorSettings : IEvent - { - public string EventType => "FileMonitorSettings"; - public bool ExcludeFromTxrx => true; - public string[]? Paths { get; set; } - } -} diff --git a/Shared/Events/Setting/LuaSettings.cs b/Shared/Events/Setting/LuaSettings.cs deleted file mode 100644 index 038a8811..00000000 --- a/Shared/Events/Setting/LuaSettings.cs +++ /dev/null @@ -1,12 +0,0 @@ -#nullable enable - -namespace Slipstream.Shared.Events.Setting -{ - public class LuaSettings : IEvent - { - public string EventType => "LuaSettings"; - public bool ExcludeFromTxrx => true; - public string PluginId { get; set; } = "INVALID-PLUGIN-ID"; - public string FilePath { get; set; } = string.Empty; - } -} diff --git a/Shared/Events/Setting/TwitchSettings.cs b/Shared/Events/Setting/TwitchSettings.cs deleted file mode 100644 index f0f3cd6e..00000000 --- a/Shared/Events/Setting/TwitchSettings.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Slipstream.Shared.Events.Setting -{ - public class TwitchSettings : IEvent - { - public string EventType => "TwitchSettings"; - public bool ExcludeFromTxrx => true; - public string TwitchUsername { get; set; } = string.Empty; - public string TwitchChannel { get; set; } = string.Empty; - public string TwitchToken { get; set; } = string.Empty; - public bool TwitchLog { get; set; } - } -} diff --git a/Shared/Events/Setting/TxrxSettings.cs b/Shared/Events/Setting/TxrxSettings.cs deleted file mode 100644 index 16ef4dd2..00000000 --- a/Shared/Events/Setting/TxrxSettings.cs +++ /dev/null @@ -1,11 +0,0 @@ -#nullable enable - -namespace Slipstream.Shared.Events.Setting -{ - public class TxrxSettings : IEvent - { - public string EventType => "TxrxSettings"; - public bool ExcludeFromTxrx => true; - public string TxrxIpPort { get; set; } = ""; - } -} diff --git a/Shared/IApplicationConfiguration.cs b/Shared/IApplicationConfiguration.cs index 7f6a6903..d0d5e65c 100644 --- a/Shared/IApplicationConfiguration.cs +++ b/Shared/IApplicationConfiguration.cs @@ -1,10 +1,34 @@ namespace Slipstream.Shared { - interface IApplicationConfiguration + public interface IApplicationConfiguration : ITxrxConfiguration, IAudioConfiguration, IFileMonitorConfiguration, ITwitchConfiguration { - public Events.Setting.TwitchSettings GetTwitchSettingsEvent(); - public Events.Setting.FileMonitorSettings GetFileMonitorSettingsEvent(); - public Events.Setting.AudioSettings GetAudioSettingsEvent(); - public Events.Setting.TxrxSettings GetTxrxSettingsEvent(); + } + + public interface ITxrxConfiguration + { + string TxrxIpPort { get; } + } + + public interface IAudioConfiguration + { + string AudioPath { get; } + } + + public interface IFileMonitorConfiguration + { + string[] FileMonitorPaths { get; } + } + + public interface ITwitchConfiguration + { + string TwitchUsername { get; } + string TwitchChannel { get; } + string TwitchToken { get; } + bool TwitchLog { get; } + } + + public interface ILuaConfiguration + { + string FilePath { get; } } } diff --git a/Shared/IEventFactory.cs b/Shared/IEventFactory.cs index 28322b4f..f08f48a0 100644 --- a/Shared/IEventFactory.cs +++ b/Shared/IEventFactory.cs @@ -2,7 +2,6 @@ using Slipstream.Shared.Events.FileMonitor; using Slipstream.Shared.Events.Internal; using Slipstream.Shared.Events.IRacing; -using Slipstream.Shared.Events.Setting; using Slipstream.Shared.Events.Twitch; using Slipstream.Shared.Events.UI; @@ -39,7 +38,6 @@ public enum IRacingSessionStateEnum InternalCommandPluginEnable CreateInternalCommandPluginEnable(string pluginId); InternalCommandPluginRegister CreateInternalCommandPluginRegister(string pluginId, string pluginName); - InternalCommandPluginRegister CreateInternalCommandPluginRegister(string pluginId, string pluginName, IEvent? settings); InternalCommandPluginStates CreateInternalCommandPluginStates(); InternalCommandPluginUnregister CreateInternalCommandPluginUnregister(string pluginId); InternalInitialized CreateInternalInitialized(); @@ -147,12 +145,6 @@ IRacingWeatherInfo CreateIRacingWeatherInfo( string fogLevel ); - AudioSettings CreateAudioSettings(string path); - FileMonitorSettings CreateFileMonitorSettings(string[] paths); - LuaSettings CreateLuaSettings(string pluginId, string filePath); - TwitchSettings CreateTwitchSettings(string twitchUsername, string twitchChannel, string twitchToken, bool twitchLog); - TxrxSettings CreateTxrxSettings(string txrxIpPort); - TwitchCommandSendMessage CreateTwitchCommandSendMessage(string message); TwitchConnected CreateTwitchConnected(); TwitchDisconnected CreateTwitchDisconnected(); diff --git a/Slipstream.csproj b/Slipstream.csproj index 265acf1d..565f016e 100644 --- a/Slipstream.csproj +++ b/Slipstream.csproj @@ -165,7 +165,6 @@ - @@ -191,16 +190,12 @@ - - - - From fddc4fe63f1b56dc3b208e4445e9c1b0814c6a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=B8llegaard?= Date: Mon, 4 Jan 2021 06:14:05 +0100 Subject: [PATCH 4/9] Restart plugins if needed, when configuration changes --- Backend/Engine.cs | 11 +- Backend/IPlugin.cs | 2 +- Backend/IPluginFactory.cs | 9 ++ Backend/IPluginManager.cs | 19 +++ Backend/PluginFactory.cs | 44 ----- Backend/PluginManager.cs | 151 ++++++++++++------ Backend/Plugins/AudioPlugin.cs | 2 +- Backend/Plugins/BasePlugin.cs | 4 +- Backend/Plugins/FileMonitorPlugin.cs | 2 +- Backend/Plugins/FileTriggerPlugin.cs | 16 +- Backend/Plugins/IRacingPlugin.cs | 2 +- Backend/Plugins/ReceiverPlugin.cs | 7 +- Backend/Plugins/TransmitterPlugin.cs | 2 +- Backend/Plugins/TwitchPlugin.cs | 2 +- Frontend/MainWindow.cs | 9 +- Frontend/SettingsForm.cs | 2 + Program.cs | 2 +- Shared/EventFactory.cs | 6 +- Shared/EventHandler.cs | 12 ++ .../Events/Internal/InternalReconfigured.cs | 10 ++ Shared/IEventFactory.cs | 1 + Slipstream.csproj | 4 +- 22 files changed, 200 insertions(+), 119 deletions(-) create mode 100644 Backend/IPluginFactory.cs create mode 100644 Backend/IPluginManager.cs delete mode 100644 Backend/PluginFactory.cs create mode 100644 Shared/Events/Internal/InternalReconfigured.cs diff --git a/Backend/Engine.cs b/Backend/Engine.cs index a4b67ee2..d8e78b64 100644 --- a/Backend/Engine.cs +++ b/Backend/Engine.cs @@ -13,10 +13,10 @@ partial class Engine : Worker, IEngine, IDisposable private readonly IEventBus EventBus; private readonly IPluginManager PluginManager; private readonly IEventBusSubscription Subscription; - private readonly PluginFactory PluginFactory; + private readonly IPluginFactory PluginFactory; private readonly Shared.EventHandler EventHandler = new Shared.EventHandler(); - public Engine(IEventFactory eventFactory, IEventBus eventBus, PluginFactory pluginFactory, IPluginManager pluginManager) : base("engine") + public Engine(IEventFactory eventFactory, IEventBus eventBus, IPluginFactory pluginFactory, IPluginManager pluginManager) : base("engine") { EventFactory = eventFactory; EventBus = eventBus; @@ -30,6 +30,7 @@ public Engine(IEventFactory eventFactory, IEventBus eventBus, PluginFactory plug EventHandler.OnInternalCommandPluginEnable += (s, e) => PluginManager.FindPluginAndExecute(e.Event.Id, (plugin) => PluginManager.EnablePlugin(plugin)); EventHandler.OnInternalCommandPluginDisable += (s, e) => PluginManager.FindPluginAndExecute(e.Event.Id, (plugin) => PluginManager.DisablePlugin(plugin)); EventHandler.OnInternalCommandPluginStates += (s, e) => OnCommandPluginStates(e.Event); + EventHandler.OnInternalReconfigured += (s, e) => OnInternalReconfigured(); // Plugins.. RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("FileMonitorPlugin", "FileMonitorPlugin")); @@ -44,6 +45,11 @@ public Engine(IEventFactory eventFactory, IEventBus eventBus, PluginFactory plug EventBus.Enabled = true; } + private void OnInternalReconfigured() + { + PluginManager.RestartReconfigurablePlugins(); + } + private void OnCommandPluginStates(InternalCommandPluginStates _) { PluginManager.ForAllPluginsExecute( @@ -73,7 +79,6 @@ public void UnregisterSubscription(IEventBusSubscription subscription) private void OnCommandPluginRegister(Shared.Events.Internal.InternalCommandPluginRegister ev) { - PluginManager.RegisterPlugin(PluginFactory.CreatePlugin(ev.Id, ev.PluginName)); } diff --git a/Backend/IPlugin.cs b/Backend/IPlugin.cs index 49672c2c..fda41370 100644 --- a/Backend/IPlugin.cs +++ b/Backend/IPlugin.cs @@ -26,11 +26,11 @@ public EventHandlerArgs(T e) public bool PendingOnEnable { get; set; } public bool PendingOnDisable { get; set; } public Shared.EventHandler EventHandler { get; } + public bool Reconfigurable { get; } void Enable(); void Disable(); void OnEnable(); void OnDisable(); - void Loop(); } } diff --git a/Backend/IPluginFactory.cs b/Backend/IPluginFactory.cs new file mode 100644 index 00000000..d82dfa0f --- /dev/null +++ b/Backend/IPluginFactory.cs @@ -0,0 +1,9 @@ +#nullable enable + +namespace Slipstream.Backend +{ + public interface IPluginFactory + { + IPlugin CreatePlugin(string id, string name); + } +} diff --git a/Backend/IPluginManager.cs b/Backend/IPluginManager.cs new file mode 100644 index 00000000..871fc902 --- /dev/null +++ b/Backend/IPluginManager.cs @@ -0,0 +1,19 @@ +#nullable enable + +using System; + +namespace Slipstream.Backend +{ + public interface IPluginManager : IDisposable + { + public void UnregisterPlugin(IPlugin p); + public void UnregisterPlugin(string id); + public void RegisterPlugin(IPlugin plugin); + public void EnablePlugin(IPlugin p); + public void DisablePlugins(); + public void DisablePlugin(IPlugin p); + public void FindPluginAndExecute(string pluginId, Action a); + public void ForAllPluginsExecute(Action a); + void RestartReconfigurablePlugins(); + } +} \ No newline at end of file diff --git a/Backend/PluginFactory.cs b/Backend/PluginFactory.cs deleted file mode 100644 index 0d2147b3..00000000 --- a/Backend/PluginFactory.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Slipstream.Backend.Plugins; -using Slipstream.Backend.Services; -using Slipstream.Shared; -using System; - -#nullable enable - -namespace Slipstream.Backend -{ - public class PluginFactory - { - private readonly IEventFactory EventFactory; - private readonly IEventBus EventBus; - private readonly IStateService StateService; - private readonly ITxrxService TxrxService; - private readonly IApplicationConfiguration ApplicationConfiguration; - private readonly IPluginManager PluginManager; - - public PluginFactory(IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, ITxrxService txrxService, IApplicationConfiguration applicationConfiguration, IPluginManager pluginManager) - { - EventFactory = eventFactory; - EventBus = eventBus; - StateService = stateService; - TxrxService = txrxService; - ApplicationConfiguration = applicationConfiguration; - PluginManager = pluginManager; - } - - public IPlugin CreatePlugin(string id, string name) - { - return name switch - { - "FileMonitorPlugin" => new FileMonitorPlugin(id, EventFactory, EventBus, ApplicationConfiguration), - "FileTriggerPlugin" => new FileTriggerPlugin(id, EventFactory, EventBus, StateService, PluginManager), - "AudioPlugin" => new AudioPlugin(id, EventFactory, EventBus, ApplicationConfiguration), - "IRacingPlugin" => new IRacingPlugin(id, EventFactory, EventBus), - "TwitchPlugin" => new TwitchPlugin(id, EventFactory, EventBus, ApplicationConfiguration), - "TransmitterPlugin" => new TransmitterPlugin(id, EventFactory, EventBus, TxrxService, ApplicationConfiguration), - "ReceiverPlugin" => new ReceiverPlugin(id, EventFactory, EventBus, TxrxService, ApplicationConfiguration), - _ => throw new Exception($"Unknown plugin '{name}'"), - }; - } - } -} diff --git a/Backend/PluginManager.cs b/Backend/PluginManager.cs index 72b42c74..21acb4da 100644 --- a/Backend/PluginManager.cs +++ b/Backend/PluginManager.cs @@ -1,5 +1,7 @@ #nullable enable +using Slipstream.Backend.Plugins; +using Slipstream.Backend.Services; using Slipstream.Shared; using System; using System.Collections.Generic; @@ -7,50 +9,49 @@ namespace Slipstream.Backend { - public interface IPluginManager : IDisposable - { - public void UnregisterPlugins(); - public void UnregisterPlugin(IPlugin p); - public void UnregisterPlugin(string id); - public void RegisterPlugin(IPlugin plugin); - public void EnablePlugin(IPlugin p); - public void DisablePlugins(); - public void DisablePlugin(IPlugin p); - public void FindPluginAndExecute(string pluginId, Action a); - public void ForAllPluginsExecute(Action a); - } - - public class PluginManager : IPluginManager + public class PluginManager : IPluginManager, IPluginFactory { private readonly IEventFactory EventFactory; private readonly IEventBus EventBus; private readonly IDictionary Plugins = new Dictionary(); private readonly IDictionary PluginWorkers = new Dictionary(); + private readonly IApplicationConfiguration ApplicationConfiguration; + private readonly IStateService StateService; + private readonly ITxrxService TxrxService; - public PluginManager(IEventFactory eventFactory, IEventBus eventBus) + public PluginManager(IEventFactory eventFactory, IEventBus eventBus, IApplicationConfiguration applicationConfiguration, IStateService stateService, ITxrxService txrxService) { EventFactory = eventFactory; EventBus = eventBus; + ApplicationConfiguration = applicationConfiguration; + StateService = stateService; + TxrxService = txrxService; } - public void UnregisterPlugins() + private void UnregisterPluginsWithoutLock() { - lock (Plugins) + foreach (var p in Plugins) { - foreach (var p in Plugins) - { - UnregisterPlugin(p.Value); - } - Plugins.Clear(); + UnregisterPlugin(p.Value); } + Plugins.Clear(); } public void UnregisterPlugin(IPlugin p) + { + lock(Plugins) + { + UnregisterPluginWithoutLock(p); + } + } + + private void UnregisterPluginWithoutLock(IPlugin p) { PluginWorkers[p.WorkerName].RemovePlugin(p); EmitPluginStateChanged(p, PluginStatusEnum.Unregistered); } + private void EmitPluginStateChanged(IPlugin plugin, PluginStatusEnum pluginStatus) { EmitEvent(EventFactory.CreateInternalPluginState(plugin.Id, plugin.Name, plugin.DisplayName, pluginStatus)); @@ -60,25 +61,30 @@ public void RegisterPlugin(IPlugin plugin) { lock (Plugins) { - PluginWorker? worker; + RegisterPluginWithoutLock(plugin); + } + } - if (PluginWorkers.ContainsKey(plugin.WorkerName)) - { - worker = PluginWorkers[plugin.WorkerName]; - } - else - { - worker = new PluginWorker(plugin.WorkerName, EventBus.RegisterListener(), EventFactory, EventBus); - worker.Start(); - PluginWorkers.Add(worker.Name, worker); - } + private void RegisterPluginWithoutLock(IPlugin plugin) + { + PluginWorker? worker; - worker.AddPlugin(plugin); + if (PluginWorkers.ContainsKey(plugin.WorkerName)) + { + worker = PluginWorkers[plugin.WorkerName]; + } + else + { + worker = new PluginWorker(plugin.WorkerName, EventBus.RegisterListener(), EventFactory, EventBus); + worker.Start(); + PluginWorkers.Add(worker.Name, worker); + } - Plugins.Add(plugin.Id, plugin); + worker.AddPlugin(plugin); - EmitPluginStateChanged(plugin, PluginStatusEnum.Registered); - } + Plugins.Add(plugin.Id, plugin); + + EmitPluginStateChanged(plugin, PluginStatusEnum.Registered); } public void EnablePlugin(IPlugin p) @@ -131,23 +137,78 @@ public void UnregisterPlugin(string id) { lock (Plugins) { - if (Plugins.ContainsKey(id)) + UnregisterPluginWithoutLock(id); + } + } + + private void UnregisterPluginWithoutLock(string id) + { + if (Plugins.ContainsKey(id)) + { + UnregisterPlugin(Plugins[id]); + Plugins.Remove(id); + } + } + + public void RestartReconfigurablePlugins() + { + lock(Plugins) + { + var restartList = new List(); + + foreach(var oldPlugin in Plugins) + { + if(oldPlugin.Value.Reconfigurable) + { + restartList.Add(oldPlugin.Value); + } + } + + foreach(var plugin in restartList) { - UnregisterPlugin(Plugins[id]); - Plugins.Remove(id); + UnregisterPluginWithoutLock(plugin); + Plugins.Remove(plugin.Id); + + IPlugin newPlugin = CreatePlugin(plugin.Id, plugin.Name); + + RegisterPluginWithoutLock(newPlugin); + + if (plugin.Enabled) + EnablePlugin(newPlugin); } } + + EventBus.PublishEvent(EventFactory.CreateInternalInitialized()); + } + + public IPlugin CreatePlugin(string id, string name) + { + return name switch + { + "FileMonitorPlugin" => new FileMonitorPlugin(id, EventFactory, EventBus, ApplicationConfiguration), + "FileTriggerPlugin" => new FileTriggerPlugin(id, EventFactory, EventBus, StateService, this), + "AudioPlugin" => new AudioPlugin(id, EventFactory, EventBus, ApplicationConfiguration), + "IRacingPlugin" => new IRacingPlugin(id, EventFactory, EventBus), + "TwitchPlugin" => new TwitchPlugin(id, EventFactory, EventBus, ApplicationConfiguration), + "TransmitterPlugin" => new TransmitterPlugin(id, EventFactory, EventBus, TxrxService, ApplicationConfiguration), + "ReceiverPlugin" => new ReceiverPlugin(id, EventFactory, EventBus, TxrxService, ApplicationConfiguration), + _ => throw new Exception($"Unknown plugin '{name}'"), + }; } public void Dispose() { - DisablePlugins(); - UnregisterPlugins(); - Plugins.Clear(); - foreach (var worker in PluginWorkers) + lock(Plugins) { - worker.Value.Dispose(); + DisablePlugins(); + UnregisterPluginsWithoutLock(); + Plugins.Clear(); + foreach (var worker in PluginWorkers) + { + worker.Value.Dispose(); + } } } + } } \ No newline at end of file diff --git a/Backend/Plugins/AudioPlugin.cs b/Backend/Plugins/AudioPlugin.cs index b2825933..f738025e 100644 --- a/Backend/Plugins/AudioPlugin.cs +++ b/Backend/Plugins/AudioPlugin.cs @@ -16,7 +16,7 @@ class AudioPlugin : BasePlugin private readonly string Path; private readonly SpeechSynthesizer Synthesizer; - public AudioPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, IAudioConfiguration audioConfiguration) : base(id, "AudioPlugin", "AudioPlugin", "Audio") + public AudioPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, IAudioConfiguration audioConfiguration) : base(id, "AudioPlugin", "AudioPlugin", "Audio", true) { EventFactory = eventFactory; EventBus = eventBus; diff --git a/Backend/Plugins/BasePlugin.cs b/Backend/Plugins/BasePlugin.cs index e2ef447d..54bafc53 100644 --- a/Backend/Plugins/BasePlugin.cs +++ b/Backend/Plugins/BasePlugin.cs @@ -36,6 +36,7 @@ public bool Enabled public bool PendingOnDisable { get { return pendingOnDisable; } set { pendingOnDisable = value; } } public event IPlugin.OnStateChangedHandler? OnStateChanged; + public bool Reconfigurable { get; protected set; } public string WorkerName { @@ -45,12 +46,13 @@ public string WorkerName public Shared.EventHandler EventHandler { get; } = new Shared.EventHandler(); - public BasePlugin(string id, string name, string displayName, string workerName) + public BasePlugin(string id, string name, string displayName, string workerName, bool reconfigurable = false) { Id = id; Name = name; DisplayName = displayName; WorkerName = workerName; + Reconfigurable = reconfigurable; } public void Disable() diff --git a/Backend/Plugins/FileMonitorPlugin.cs b/Backend/Plugins/FileMonitorPlugin.cs index 3acd6436..8105f915 100644 --- a/Backend/Plugins/FileMonitorPlugin.cs +++ b/Backend/Plugins/FileMonitorPlugin.cs @@ -14,7 +14,7 @@ class FileMonitorPlugin : BasePlugin private readonly IList fileSystemWatchers = new List(); private readonly bool InitialScan = false; - public FileMonitorPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, IFileMonitorConfiguration fileMonitorConfiguration) : base(id, "FileMonitorPlugin", "FileMonitorPlugin", "Core") + public FileMonitorPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, IFileMonitorConfiguration fileMonitorConfiguration) : base(id, "FileMonitorPlugin", "FileMonitorPlugin", "Core", true) { EventFactory = eventFactory; EventBus = eventBus; diff --git a/Backend/Plugins/FileTriggerPlugin.cs b/Backend/Plugins/FileTriggerPlugin.cs index d77dd86b..8c96584d 100644 --- a/Backend/Plugins/FileTriggerPlugin.cs +++ b/Backend/Plugins/FileTriggerPlugin.cs @@ -1,7 +1,7 @@ using Slipstream.Backend.Services; using Slipstream.Shared; using System.Collections.Generic; -using System.Diagnostics; +//using System.Diagnostics; using System.IO; using EventHandler = Slipstream.Shared.EventHandler; @@ -56,8 +56,6 @@ private void EventHandler_OnInternalPluginState(EventHandler source, EventHandle private void EventHandler_OnFileMonitorScanCompleted(EventHandler source, EventHandler.EventHandlerArgs e) { - Debug.Assert(BootUp); - BootUp = false; } @@ -97,11 +95,9 @@ private void DeletedFile(string filePath) private void EventHandler_OnFileMonitorFileRenamed(EventHandler source, EventHandler.EventHandlerArgs e) { - if (e.Event.FilePath == null || e.Event.OldFilePath == null) + if (e.Event.FilePath == null || e.Event.OldFilePath == null || BootUp) return; - Debug.Assert(!BootUp); - if (IsApplicable(e.Event.OldFilePath)) DeletedFile(e.Event.OldFilePath); if (IsApplicable(e.Event.FilePath)) @@ -110,22 +106,18 @@ private void EventHandler_OnFileMonitorFileRenamed(EventHandler source, EventHan private void EventHandler_OnFileMonitorFileChanged(EventHandler source, EventHandler.EventHandlerArgs e) { - if (e.Event.FilePath == null || !IsApplicable(e.Event.FilePath)) + if (e.Event.FilePath == null || !IsApplicable(e.Event.FilePath) || BootUp) return; - Debug.Assert(!BootUp); - DeletedFile(e.Event.FilePath); NewFile(e.Event.FilePath); } private void EventHandler_OnFileMonitorFileDeleted(EventHandler source, EventHandler.EventHandlerArgs e) { - if (e.Event.FilePath == null || !IsApplicable(e.Event.FilePath)) + if (e.Event.FilePath == null || !IsApplicable(e.Event.FilePath) || BootUp) return; - Debug.Assert(!BootUp); - DeletedFile(e.Event.FilePath); } diff --git a/Backend/Plugins/IRacingPlugin.cs b/Backend/Plugins/IRacingPlugin.cs index 2a28d900..cd8078d3 100644 --- a/Backend/Plugins/IRacingPlugin.cs +++ b/Backend/Plugins/IRacingPlugin.cs @@ -17,6 +17,7 @@ class IRacingPlugin : BasePlugin private readonly iRacingConnection Connection = new iRacingConnection(); private readonly IEventFactory EventFactory; private readonly IEventBus EventBus; + private bool InitializedSeen; private class CarState @@ -78,7 +79,6 @@ public IRacingPlugin(string id, IEventFactory eventFactory, IEventBus eventBus) EventHandler.OnInternalInitialized += (s, e) => InitializedSeen = true; } - public override void OnEnable() { Reset(); diff --git a/Backend/Plugins/ReceiverPlugin.cs b/Backend/Plugins/ReceiverPlugin.cs index 9d0c111c..652e15d3 100644 --- a/Backend/Plugins/ReceiverPlugin.cs +++ b/Backend/Plugins/ReceiverPlugin.cs @@ -19,9 +19,9 @@ class ReceiverPlugin : BasePlugin private readonly ITxrxService TxrxService; private Socket? Client; private const int READ_BUFFER_SIZE = 1024 * 16; - readonly byte[] ReadBuffer = new byte[READ_BUFFER_SIZE]; + private readonly byte[] ReadBuffer = new byte[READ_BUFFER_SIZE]; - public ReceiverPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, ITxrxService txrxService, ITxrxConfiguration txrxConfiguration) : base(id, "ReceiverPlugin", "ReceiverPlugin", "ReceiverPlugin") + public ReceiverPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, ITxrxService txrxService, ITxrxConfiguration txrxConfiguration) : base(id, "ReceiverPlugin", "ReceiverPlugin", "ReceiverPlugin", true) { EventFactory = eventFactory; EventBus = eventBus; @@ -69,6 +69,9 @@ private void AcceptClient() Debug.Assert(Client == null); Debug.Assert(Listener != null); + if (!Listener!.Pending()) + return; + Client = Listener!.AcceptSocket(); EventBus.PublishEvent(EventFactory.CreateUICommandWriteToConsole($"ReceiverPlugin got a connection from {Client.RemoteEndPoint}")); diff --git a/Backend/Plugins/TransmitterPlugin.cs b/Backend/Plugins/TransmitterPlugin.cs index cc258e4d..53fa08fd 100644 --- a/Backend/Plugins/TransmitterPlugin.cs +++ b/Backend/Plugins/TransmitterPlugin.cs @@ -19,7 +19,7 @@ class TransmitterPlugin : BasePlugin private TcpClient? Client = null; private readonly ITxrxService TxrxService; - public TransmitterPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, ITxrxService txrxService, ITxrxConfiguration txrxConfiguration) : base(id, "TransmitterPlugin", "TransmitterPlugin", "TransmitterPlugin") + public TransmitterPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, ITxrxService txrxService, ITxrxConfiguration txrxConfiguration) : base(id, "TransmitterPlugin", "TransmitterPlugin", "TransmitterPlugin", true) { EventBus = eventBus; EventFactory = eventFactory; diff --git a/Backend/Plugins/TwitchPlugin.cs b/Backend/Plugins/TwitchPlugin.cs index cb815cd8..5e517de8 100644 --- a/Backend/Plugins/TwitchPlugin.cs +++ b/Backend/Plugins/TwitchPlugin.cs @@ -25,7 +25,7 @@ class TwitchPlugin : BasePlugin private bool RequestReconnect; private bool AnnouncedConnected = false; - public TwitchPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, ITwitchConfiguration twitchConfiguration) : base(id, "TwitchPlugin", "TwitchPlugin", "TwitchPlugin") + public TwitchPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, ITwitchConfiguration twitchConfiguration) : base(id, "TwitchPlugin", "TwitchPlugin", "TwitchPlugin", true) { EventFactory = eventFactory; EventBus = eventBus; diff --git a/Frontend/MainWindow.cs b/Frontend/MainWindow.cs index d561c1ff..e52b422b 100644 --- a/Frontend/MainWindow.cs +++ b/Frontend/MainWindow.cs @@ -173,11 +173,11 @@ private void EventHandler_OnInternalPluginState(Shared.Events.Internal.InternalP switch (e.Id) { case "TransmitterPlugin": - ExecuteSecure(() => Text += $" <<< transmitting to {ApplicationConfiguration.TxrxIpPort} >>>"); + ExecuteSecure(() => Text = $"{CleanTitle} <<< transmitting to {ApplicationConfiguration.TxrxIpPort} >>>"); break; case "ReceiverPlugin": - ExecuteSecure(() => Text += $" <<< receiving from {ApplicationConfiguration.TxrxIpPort} >>>"); + ExecuteSecure(() => Text = $"{CleanTitle} <<< receiving from {ApplicationConfiguration.TxrxIpPort} >>>"); break; } @@ -241,7 +241,10 @@ private void OpenAudioDirectoryToolStripMenuItem_Click(object sender, EventArgs private void SettingsToolStripMenuItem_Click(object sender, EventArgs e) { - _ = new SettingsForm().ShowDialog(this); + if(new SettingsForm().ShowDialog(this) == DialogResult.OK) + { + EventBus.PublishEvent(EventFactory.CreateInternalReconfigured()); + } } } } diff --git a/Frontend/SettingsForm.cs b/Frontend/SettingsForm.cs index a4991bb3..62637472 100644 --- a/Frontend/SettingsForm.cs +++ b/Frontend/SettingsForm.cs @@ -36,6 +36,8 @@ private void ApplyButton_Click(object sender, EventArgs e) settings.Save(); + DialogResult = DialogResult.OK; + Close(); } diff --git a/Program.cs b/Program.cs index 81fb2e75..bde0d0b1 100644 --- a/Program.cs +++ b/Program.cs @@ -44,11 +44,11 @@ private static void ConfigureServices(ServiceCollection services) services.AddScoped(); services.AddScoped(x => x.GetService()); services.AddScoped(); - services.AddScoped(); services.AddScoped(x => new Backend.Services.StateService(x.GetService(), x.GetService(), Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + $@"\Slipstream\state.txt")); services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddSingleton(); } } diff --git a/Shared/EventFactory.cs b/Shared/EventFactory.cs index c23d0a91..5a7e9080 100644 --- a/Shared/EventFactory.cs +++ b/Shared/EventFactory.cs @@ -78,6 +78,11 @@ public InternalPluginState CreateInternalPluginState(string pluginId, string plu return new InternalPluginState { Id = pluginId, PluginName = pluginName, DisplayName = displayName, PluginStatus = pluginStatus.ToString() }; } + public InternalReconfigured CreateInternalReconfigured() + { + return new InternalReconfigured(); + } + public IRacingCarCompletedLap CreateIRacingCarCompletedLap(double sessionTime, long carIdx, double time, int lapsCompleted, float? fuelDiff, bool localUser) { return new IRacingCarCompletedLap @@ -88,7 +93,6 @@ public IRacingCarCompletedLap CreateIRacingCarCompletedLap(double sessionTime, l LapsCompleted = lapsCompleted, FuelDiff = fuelDiff, LocalUser = localUser, - }; } diff --git a/Shared/EventHandler.cs b/Shared/EventHandler.cs index 800d44ef..019f9958 100644 --- a/Shared/EventHandler.cs +++ b/Shared/EventHandler.cs @@ -43,6 +43,10 @@ public EventHandlerArgs(T e) public delegate void OnInternalInitializedHandler(EventHandler source, EventHandlerArgs e); public event OnInternalInitializedHandler? OnInternalInitialized; + + public delegate void OnInternalReconfiguredHandler(EventHandler source, EventHandlerArgs e); + public event OnInternalReconfiguredHandler? OnInternalReconfigured; + #endregion #region FileMonitor @@ -191,8 +195,16 @@ public void HandleEvent(IEvent? ev) else OnInternalInitialized.Invoke(this, new EventHandlerArgs(tev)); break; + + case Shared.Events.Internal.InternalReconfigured tev: + if (OnInternalReconfigured == null) + OnDefault?.Invoke(this, new EventHandlerArgs(tev)); + else + OnInternalReconfigured.Invoke(this, new EventHandlerArgs(tev)); + break; // File Monitor + case Shared.Events.FileMonitor.FileMonitorFileCreated tev: if (OnFileMonitorFileCreated == null) OnDefault?.Invoke(this, new EventHandlerArgs(tev)); diff --git a/Shared/Events/Internal/InternalReconfigured.cs b/Shared/Events/Internal/InternalReconfigured.cs new file mode 100644 index 00000000..10c1a409 --- /dev/null +++ b/Shared/Events/Internal/InternalReconfigured.cs @@ -0,0 +1,10 @@ +#nullable enable + +namespace Slipstream.Shared.Events.Internal +{ + public class InternalReconfigured : IEvent + { + public string EventType => "InternalReconfigured"; + public bool ExcludeFromTxrx => true; + } +} diff --git a/Shared/IEventFactory.cs b/Shared/IEventFactory.cs index f08f48a0..e70c1fe7 100644 --- a/Shared/IEventFactory.cs +++ b/Shared/IEventFactory.cs @@ -42,6 +42,7 @@ public enum IRacingSessionStateEnum InternalCommandPluginUnregister CreateInternalCommandPluginUnregister(string pluginId); InternalInitialized CreateInternalInitialized(); InternalPluginState CreateInternalPluginState(string pluginId, string pluginName, string displayName, PluginStatusEnum pluginStatus); + InternalReconfigured CreateInternalReconfigured(); IRacingCarCompletedLap CreateIRacingCarCompletedLap(double sessionTime, long carIdx, double time, int lapsCompleted, float? fuelDiff, bool localUser); IRacingCarInfo CreateIRacingCarInfo( diff --git a/Slipstream.csproj b/Slipstream.csproj index 565f016e..f779f5f3 100644 --- a/Slipstream.csproj +++ b/Slipstream.csproj @@ -132,7 +132,8 @@ - + + @@ -163,6 +164,7 @@ + From a70d502d8e8e82ce7a0e06f4fceb3151c0f440c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=B8llegaard?= Date: Mon, 4 Jan 2021 21:19:55 +0100 Subject: [PATCH 5/9] Lua: Adds functions to control plugins --- Backend/PluginManager.cs | 8 ++- .../LuaServiceLib/InternalMethodCollection.cs | 66 +++++++++++++++++++ Backend/Services/LuaServiceLib/LuaContext.cs | 1 + CHANGELOG.md | 1 + Slipstream.csproj | 1 + 5 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 Backend/Services/LuaServiceLib/InternalMethodCollection.cs diff --git a/Backend/PluginManager.cs b/Backend/PluginManager.cs index 21acb4da..d34dfdaa 100644 --- a/Backend/PluginManager.cs +++ b/Backend/PluginManager.cs @@ -51,7 +51,6 @@ private void UnregisterPluginWithoutLock(IPlugin p) EmitPluginStateChanged(p, PluginStatusEnum.Unregistered); } - private void EmitPluginStateChanged(IPlugin plugin, PluginStatusEnum pluginStatus) { EmitEvent(EventFactory.CreateInternalPluginState(plugin.Id, plugin.Name, plugin.DisplayName, pluginStatus)); @@ -67,6 +66,11 @@ public void RegisterPlugin(IPlugin plugin) private void RegisterPluginWithoutLock(IPlugin plugin) { + if(Plugins.ContainsKey(plugin.Id)) + { + Plugins.Remove(plugin.Id); + } + PluginWorker? worker; if (PluginWorkers.ContainsKey(plugin.WorkerName)) @@ -118,7 +122,7 @@ public void FindPluginAndExecute(string pluginId, Action a) } else { - throw new Exception($"Can't find plugin '{pluginId}'"); + EventBus.PublishEvent(EventFactory.CreateUICommandWriteToConsole($"Can't find plugin '{pluginId}'")); } } diff --git a/Backend/Services/LuaServiceLib/InternalMethodCollection.cs b/Backend/Services/LuaServiceLib/InternalMethodCollection.cs new file mode 100644 index 00000000..fe320b73 --- /dev/null +++ b/Backend/Services/LuaServiceLib/InternalMethodCollection.cs @@ -0,0 +1,66 @@ +using NLua; +using Slipstream.Shared; + +#nullable enable + +namespace Slipstream.Backend.Services.LuaServiceLib +{ + public partial class LuaContext + { + public class InternalMethodCollection + { + private readonly IEventBus EventBus; + private readonly IEventFactory EventFactory; + + public static InternalMethodCollection Register(IEventBus eventBus, IEventFactory eventFactory, Lua lua) + { + var m = new InternalMethodCollection(eventBus, eventFactory); + + m.Register(lua); + + return m; + } + + public InternalMethodCollection(IEventBus eventBus, IEventFactory eventFactory) + { + EventBus = eventBus; + EventFactory = eventFactory; + } + + public void Register(Lua lua) + { + lua["internal"] = this; + lua.DoString(@" +function register_plugin(id,name); internal:register_plugin(id,name); end +function unregister_plugin(id); internal:unregister_plugin(id); end +function enable_plugin(id); internal:enable_plugin(id); end +function disable_plugin(id); internal:disable_plugin(id); end +"); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void register_plugin(string id, string name) + { + EventBus.PublishEvent(EventFactory.CreateInternalCommandPluginRegister(id, name)); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void unregister_plugin(string id) + { + EventBus.PublishEvent(EventFactory.CreateInternalCommandPluginUnregister(id)); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void enable_plugin(string id) + { + EventBus.PublishEvent(EventFactory.CreateInternalCommandPluginEnable(id)); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "This is expose in Lua, so we want to keep that naming style")] + public void disable_plugin(string id) + { + EventBus.PublishEvent(EventFactory.CreateInternalCommandPluginDisable(id)); + } + } + } +} diff --git a/Backend/Services/LuaServiceLib/LuaContext.cs b/Backend/Services/LuaServiceLib/LuaContext.cs index 68bc725c..446c75e3 100644 --- a/Backend/Services/LuaServiceLib/LuaContext.cs +++ b/Backend/Services/LuaServiceLib/LuaContext.cs @@ -24,6 +24,7 @@ public LuaContext(IEventFactory eventFactory, IEventBus eventBus, IStateService TwitchMethodCollection.Register(eventBus, eventFactory, Lua); StateMethodCollection.Register(stateService, Lua); UIMethodCollection.Register(eventBus, eventFactory, Lua); + InternalMethodCollection.Register(eventBus, eventFactory, Lua); // Fix paths, so we can require() files relative to where the script is located var ScriptPath = Path.GetDirectoryName(filePath).Replace("\\", "\\\\"); diff --git a/CHANGELOG.md b/CHANGELOG.md index d8c10e9a..a6060ba9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - CommandTwitchSendMessage to TwitchCommandSendMessage - CommandWriteToConsole to UICommandWriteToConsole - Buttons: Make simple buttons in UI using Lua (create_button("text")) and remove them again (delete_button("text")) + - Lua: Adds functions to control plugins: register_plugin, unregister_plugin, enable_plugin, disable_plugin ## [0.2.0](https://github.com/dennis/slipstream/releases/tag/v0.2.0) (2020-12-30) [Full Changelog](https://github.com/dennis/slipstream/compare/v0.1.0...v0.2.0) diff --git a/Slipstream.csproj b/Slipstream.csproj index f779f5f3..a85b139e 100644 --- a/Slipstream.csproj +++ b/Slipstream.csproj @@ -152,6 +152,7 @@ + From 68afb5f8cb626dacc92fe29bd3027f9457d96f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=B8llegaard?= Date: Mon, 4 Jan 2021 21:37:46 +0100 Subject: [PATCH 6/9] Loads init-.lua upon startup If its missing create it with default content. You can modify this file to suit your needs. --- Backend/Engine.cs | 69 ++++++++++++++++++++++++++++++++++------------- Program.cs | 1 + 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/Backend/Engine.cs b/Backend/Engine.cs index d8e78b64..7d6430d8 100644 --- a/Backend/Engine.cs +++ b/Backend/Engine.cs @@ -1,6 +1,8 @@ -using Slipstream.Shared; +using Slipstream.Backend.Services; +using Slipstream.Shared; using Slipstream.Shared.Events.Internal; using System; +using System.IO; using static Slipstream.Shared.IEventFactory; #nullable enable @@ -16,7 +18,7 @@ partial class Engine : Worker, IEngine, IDisposable private readonly IPluginFactory PluginFactory; private readonly Shared.EventHandler EventHandler = new Shared.EventHandler(); - public Engine(IEventFactory eventFactory, IEventBus eventBus, IPluginFactory pluginFactory, IPluginManager pluginManager) : base("engine") + public Engine(IEventFactory eventFactory, IEventBus eventBus, IPluginFactory pluginFactory, IPluginManager pluginManager, ILuaSevice luaService, IApplicationVersionService applicationVersionService) : base("engine") { EventFactory = eventFactory; EventBus = eventBus; @@ -33,13 +35,53 @@ public Engine(IEventFactory eventFactory, IEventBus eventBus, IPluginFactory plu EventHandler.OnInternalReconfigured += (s, e) => OnInternalReconfigured(); // Plugins.. - RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("FileMonitorPlugin", "FileMonitorPlugin")); - RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("FileTriggerPlugin", "FileTriggerPlugin")); - RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("AudioPlugin", "AudioPlugin")); - RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("IRacingPlugin", "IRacingPlugin")); - RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("TwitchPlugin", "TwitchPlugin")); - RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("TransmitterPlugin", "TransmitterPlugin"), false); - RegisterPlugin(EventFactory.CreateInternalCommandPluginRegister("ReceiverPlugin", "ReceiverPlugin"), false); + { + var initFilename = $"init-{applicationVersionService.Version}.lua"; + + if (!File.Exists(initFilename)) + { + File.WriteAllText(initFilename, @" +-- This file is auto generated upon startup, if it doesnt exist. So if you +-- ever break it, just rename/delete it, and a new working one is created. +-- There is no auto-reloading of this file - it is only evaluated at startup +print ""Initializing"" + +-- Listens for samples to play or text to speek. Disabling this will mute all +-- sounds +register_plugin(""AudioPlugin"", ""AudioPlugin"") +enable_plugin(""AudioPlugin"") + +-- Delivers IRacing events as they happen +register_plugin(""IRacingPlugin"", ""IRacingPlugin"") +enable_plugin(""IRacingPlugin"") + +-- Connects to Twitch (via the values provided in Settings) and provide +-- a way to sende and receive twitch messages +register_plugin(""TwitchPlugin"", ""TwitchPlugin"") +enable_plugin(""TwitchPlugin"") + +-- Only one of these may be active at a time. ReceiverPlugin listens +-- for TCP connections, while Transmitter will send the events it sees +-- to the destination. Both are configured as Txrx in Settings. +register_plugin(""TransmitterPlugin"", ""TransmitterPlugin"") +register_plugin(""ReceiverPlugin"", ""ReceiverPlugin"") + +-- FileMonitorPlugin monitors the script directory and sends out events +-- every time a file is created, renamed, modified or deleted +register_plugin(""FileMonitorPlugin"", ""FileMonitorPlugin"") +enable_plugin(""FileMonitorPlugin"") + +-- FileTriggerPlugin listens for FileMonitorPlugin events and acts on them. +-- Currently it will only act on files ending with .lua, which it launches +-- a plugin for. If the file is modified, it will take down the plugin and +-- launch a new one with the same file. If files are moved out of the directory +-- it is consider as if it were deleted. Deleted files are taken down. +register_plugin(""FileTriggerPlugin"", ""FileTriggerPlugin"") +enable_plugin(""FileTriggerPlugin"")"); + } + + luaService.Parse(filename: initFilename, logPrefix: "INIT"); + } // Tell Plugins that we're live - this will make eventbus distribute events EventBus.Enabled = true; @@ -58,15 +100,6 @@ private void OnCommandPluginStates(InternalCommandPluginStates _) )); } - private void RegisterPlugin(InternalCommandPluginRegister e, bool enable = true) - { - OnCommandPluginRegister(e); - if (enable) - { - EventBus.PublishEvent(EventFactory.CreateInternalCommandPluginEnable(e.Id)); - } - } - private void OnCommandPluginUnregister(InternalCommandPluginUnregister ev) { PluginManager.UnregisterPlugin(ev.Id); diff --git a/Program.cs b/Program.cs index bde0d0b1..e7c1f008 100644 --- a/Program.cs +++ b/Program.cs @@ -49,6 +49,7 @@ private static void ConfigureServices(ServiceCollection services) services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddSingleton(); } } From 49830714dfab8226e4e370e7f20ab5434483f14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=B8llegaard?= Date: Mon, 4 Jan 2021 22:07:34 +0100 Subject: [PATCH 7/9] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6060ba9..784bec90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - CommandWriteToConsole to UICommandWriteToConsole - Buttons: Make simple buttons in UI using Lua (create_button("text")) and remove them again (delete_button("text")) - Lua: Adds functions to control plugins: register_plugin, unregister_plugin, enable_plugin, disable_plugin + - init-.lua is read upon startup. Here you can define what plugins to register and enable. If file is not found, one is created ## [0.2.0](https://github.com/dennis/slipstream/releases/tag/v0.2.0) (2020-12-30) [Full Changelog](https://github.com/dennis/slipstream/compare/v0.1.0...v0.2.0) From c33d284fe4097733f60975a6d204969c799e54d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=B8llegaard?= Date: Mon, 4 Jan 2021 23:52:23 +0100 Subject: [PATCH 8/9] Tidying up the PR --- Backend/IPluginFactory.cs | 1 + Backend/IPluginManager.cs | 2 +- Backend/PluginManager.cs | 13 ++++++++++++- Backend/Plugins/BasePlugin.cs | 2 +- Backend/Plugins/FileTriggerPlugin.cs | 17 +++++++++-------- Backend/Services/ILuaSevice.cs | 9 +++++++++ Backend/Services/LuaService.cs | 5 ----- Slipstream.csproj | 1 + 8 files changed, 34 insertions(+), 16 deletions(-) create mode 100644 Backend/Services/ILuaSevice.cs diff --git a/Backend/IPluginFactory.cs b/Backend/IPluginFactory.cs index d82dfa0f..a929b843 100644 --- a/Backend/IPluginFactory.cs +++ b/Backend/IPluginFactory.cs @@ -5,5 +5,6 @@ namespace Slipstream.Backend public interface IPluginFactory { IPlugin CreatePlugin(string id, string name); + IPlugin CreatePlugin(string pluginId, string name, T configuration); } } diff --git a/Backend/IPluginManager.cs b/Backend/IPluginManager.cs index 871fc902..5ca700d3 100644 --- a/Backend/IPluginManager.cs +++ b/Backend/IPluginManager.cs @@ -14,6 +14,6 @@ public interface IPluginManager : IDisposable public void DisablePlugin(IPlugin p); public void FindPluginAndExecute(string pluginId, Action a); public void ForAllPluginsExecute(Action a); - void RestartReconfigurablePlugins(); + public void RestartReconfigurablePlugins(); } } \ No newline at end of file diff --git a/Backend/PluginManager.cs b/Backend/PluginManager.cs index d34dfdaa..46ec819a 100644 --- a/Backend/PluginManager.cs +++ b/Backend/PluginManager.cs @@ -190,7 +190,7 @@ public IPlugin CreatePlugin(string id, string name) return name switch { "FileMonitorPlugin" => new FileMonitorPlugin(id, EventFactory, EventBus, ApplicationConfiguration), - "FileTriggerPlugin" => new FileTriggerPlugin(id, EventFactory, EventBus, StateService, this), + "FileTriggerPlugin" => new FileTriggerPlugin(id, EventFactory, EventBus, this, this), "AudioPlugin" => new AudioPlugin(id, EventFactory, EventBus, ApplicationConfiguration), "IRacingPlugin" => new IRacingPlugin(id, EventFactory, EventBus), "TwitchPlugin" => new TwitchPlugin(id, EventFactory, EventBus, ApplicationConfiguration), @@ -200,6 +200,17 @@ public IPlugin CreatePlugin(string id, string name) }; } + public IPlugin CreatePlugin(string pluginId, string name, T configuration) + { + return name switch + { +#pragma warning disable CS8604 // Possible null reference argument. + "LuaPlugin" when configuration is ILuaConfiguration => new LuaPlugin(pluginId, EventFactory, EventBus, StateService, configuration as ILuaConfiguration), +#pragma warning restore CS8604 // Possible null reference argument. + _ => throw new Exception($"Unknown configurable plugin '{name}'"), + }; + } + public void Dispose() { lock(Plugins) diff --git a/Backend/Plugins/BasePlugin.cs b/Backend/Plugins/BasePlugin.cs index 54bafc53..d33496c6 100644 --- a/Backend/Plugins/BasePlugin.cs +++ b/Backend/Plugins/BasePlugin.cs @@ -36,7 +36,7 @@ public bool Enabled public bool PendingOnDisable { get { return pendingOnDisable; } set { pendingOnDisable = value; } } public event IPlugin.OnStateChangedHandler? OnStateChanged; - public bool Reconfigurable { get; protected set; } + public bool Reconfigurable { get; private set; } public string WorkerName { diff --git a/Backend/Plugins/FileTriggerPlugin.cs b/Backend/Plugins/FileTriggerPlugin.cs index 8c96584d..adb88e3a 100644 --- a/Backend/Plugins/FileTriggerPlugin.cs +++ b/Backend/Plugins/FileTriggerPlugin.cs @@ -1,7 +1,5 @@ -using Slipstream.Backend.Services; -using Slipstream.Shared; +using Slipstream.Shared; using System.Collections.Generic; -//using System.Diagnostics; using System.IO; using EventHandler = Slipstream.Shared.EventHandler; @@ -13,8 +11,8 @@ class FileTriggerPlugin : BasePlugin { private readonly IEventFactory EventFactory; private readonly IEventBus EventBus; - private readonly IStateService StateService; private readonly IPluginManager PluginManager; + private readonly IPluginFactory PluginFactory; private readonly IDictionary Scripts = new Dictionary(); // At bootup we will receive zero or more FileCreated events ending with a ScanCompleted. @@ -23,12 +21,12 @@ class FileTriggerPlugin : BasePlugin private bool BootUp = true; private readonly List WaitingForLuaScripts = new List(); - public FileTriggerPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, IStateService stateService, IPluginManager pluginManager) : base(id, "FileTriggerPlugin", "FileTriggerPlugin", "Core") + public FileTriggerPlugin(string id, IEventFactory eventFactory, IEventBus eventBus, IPluginManager pluginManager, IPluginFactory pluginFactory) : base(id, "FileTriggerPlugin", "FileTriggerPlugin", "Core") { EventFactory = eventFactory; EventBus = eventBus; - StateService = stateService; PluginManager = pluginManager; + PluginFactory = pluginFactory; EventHandler.OnFileMonitorFileCreated += EventHandler_OnFileMonitorFileCreated; EventHandler.OnFileMonitorFileDeleted += EventHandler_OnFileMonitorFileDeleted; @@ -78,8 +76,9 @@ private void NewFile(string filePath) WaitingForLuaScripts.Add(pluginId); } - // Use PluginManager director - var plugin = new LuaPlugin(pluginId, EventFactory, EventBus, StateService, new LuaConfiguration { FilePath = filePath }); + + var plugin = PluginFactory.CreatePlugin(pluginId, "LuaPlugin", (ILuaConfiguration)new LuaConfiguration { FilePath = filePath }); + PluginManager.RegisterPlugin(plugin); PluginManager.EnablePlugin(plugin); @@ -95,6 +94,8 @@ private void DeletedFile(string filePath) private void EventHandler_OnFileMonitorFileRenamed(EventHandler source, EventHandler.EventHandlerArgs e) { + // If we're been unregistered (while app is running) and re-registered,we need to ignore these events + // until we're ready (received the FileMonitorScanCompleted). We need to revisit this later. if (e.Event.FilePath == null || e.Event.OldFilePath == null || BootUp) return; diff --git a/Backend/Services/ILuaSevice.cs b/Backend/Services/ILuaSevice.cs new file mode 100644 index 00000000..003f8a76 --- /dev/null +++ b/Backend/Services/ILuaSevice.cs @@ -0,0 +1,9 @@ +#nullable enable + +namespace Slipstream.Backend.Services +{ + public interface ILuaSevice + { + ILuaContext Parse(string filename, string logPrefix); + } +} diff --git a/Backend/Services/LuaService.cs b/Backend/Services/LuaService.cs index be57218f..e28a4ff8 100644 --- a/Backend/Services/LuaService.cs +++ b/Backend/Services/LuaService.cs @@ -5,11 +5,6 @@ namespace Slipstream.Backend.Services { - public interface ILuaSevice - { - ILuaContext Parse(string filename, string logPrefix); - } - public class LuaService : ILuaSevice { private readonly IEventFactory EventFactory; diff --git a/Slipstream.csproj b/Slipstream.csproj index a85b139e..99415d02 100644 --- a/Slipstream.csproj +++ b/Slipstream.csproj @@ -141,6 +141,7 @@ + From 32bb8992297f5b7451340117847c2651ff2eb065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennis=20M=C3=B8llegaard?= Date: Tue, 5 Jan 2021 00:09:40 +0100 Subject: [PATCH 9/9] Bump version 0.3.0 --- CHANGELOG.md | 5 ++++- Properties/AssemblyInfo.cs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 784bec90..fa2d02fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ # Changelog ## Next version -[Full Changelog](https://github.com/dennis/slipstream/compare/v0.2.0...main) +[Full Changelog](https://github.com/dennis/slipstream/compare/v0.3.0...main) + +## [0.3.0](https://github.com/dennis/slipstream/releases/tag/v0.3.0) (2020-01-05) +[Full Changelog](https://github.com/dennis/slipstream/compare/v0.2.0...v0.3.0) **Improvements** - Lua: Add event_to_json() function, that will convert a Slipstream event to a json string. Change debug.lua to use that, making it trivial. diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index d3916557..8166d2bb 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -33,4 +33,4 @@ // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] -[assembly: AssemblyInformationalVersionAttribute("0.2.0")] +[assembly: AssemblyInformationalVersionAttribute("0.3.0")]