From 2f570545abce0fa82ea7c832f9c5614d03507f43 Mon Sep 17 00:00:00 2001 From: meelstorm <8780022+TesAnti@users.noreply.github.com> Date: Tue, 19 Aug 2025 14:27:36 +0200 Subject: [PATCH] plugin selection --- Plugins/TestPlugin/TestPlugin.cs | 4 +- .../Attributes/ModulePriorityAttribute.cs | 12 +++++ VisionBuilder.UI.Common/Extensions.cs | 35 ++++++++++--- .../Plugins/PluginLoader.cs | 44 ++++++++++++---- .../Plugins/PluginSettings.cs | 50 +++++++++++++++++++ .../VisionBuilderConsole.cs | 2 + .../Modules/IOCommanderModule.cs | 1 + .../Settings/IOCommanderSettings.cs | 1 + VisionBuilder.UI.Windows.Test/Program.cs | 17 +++++-- 9 files changed, 144 insertions(+), 22 deletions(-) create mode 100644 VisionBuilder.UI.Common/Attributes/ModulePriorityAttribute.cs create mode 100644 VisionBuilder.UI.Common/Plugins/PluginSettings.cs diff --git a/Plugins/TestPlugin/TestPlugin.cs b/Plugins/TestPlugin/TestPlugin.cs index 7e34d88..b2fe8be 100644 --- a/Plugins/TestPlugin/TestPlugin.cs +++ b/Plugins/TestPlugin/TestPlugin.cs @@ -10,7 +10,7 @@ namespace TestPlugin { public void InitializeModule() { - Console.WriteLine("Global plugin initialized"); + Console.WriteLine("Global HELLO from test plugin"); } } @@ -18,7 +18,7 @@ namespace TestPlugin { public void InitializeModule() { - Console.WriteLine("Camera plugin initialized"); + Console.WriteLine("Camera HELLO from test plugin"); } } public class TestPlugin: IPlugin diff --git a/VisionBuilder.UI.Common/Attributes/ModulePriorityAttribute.cs b/VisionBuilder.UI.Common/Attributes/ModulePriorityAttribute.cs new file mode 100644 index 0000000..006717d --- /dev/null +++ b/VisionBuilder.UI.Common/Attributes/ModulePriorityAttribute.cs @@ -0,0 +1,12 @@ +namespace VisionBuilder.UI.Common.Attributes; + +[AttributeUsage(AttributeTargets.Class)] +public class ModulePriorityAttribute: Attribute +{ + public int Priority { get; } + + public ModulePriorityAttribute(int priority) + { + Priority = priority; + } +} \ No newline at end of file diff --git a/VisionBuilder.UI.Common/Extensions.cs b/VisionBuilder.UI.Common/Extensions.cs index d422f65..8280350 100644 --- a/VisionBuilder.UI.Common/Extensions.cs +++ b/VisionBuilder.UI.Common/Extensions.cs @@ -2,6 +2,8 @@ using Ninject; using Ninject.Extensions.ChildKernel; using Serilog; +using System.ComponentModel; +using VisionBuilder.UI.Common.Attributes; using VisionBuilder.UI.Common.Plugins; namespace VisionBuilder.UI.Common; @@ -24,12 +26,23 @@ public static class Extensions self.Bind().To().InSingletonScope(); } - + + private static int GetModulePriority(IVisionBuilderModule module) + { + var priorityAttribute = module.GetType().GetCustomAttributes(typeof(ModulePriorityAttribute), true) + .FirstOrDefault() as ModulePriorityAttribute; + return priorityAttribute?.Priority ?? 0; + } + + private static List _initializedModules = new List(); public static void InitializeModules(this IKernel self) { - var serviceTypes = self.GetAll(typeof(IVisionBuilderModule)).Select(x => (IVisionBuilderModule)x).ToList(); + var serviceTypes = self.GetAll(typeof(IVisionBuilderModule)).Select(x => (IVisionBuilderModule)x) + .Where(x => !_initializedModules.Contains(x.GetType())) + .OrderByDescending(GetModulePriority) + .ToList(); foreach (var service in serviceTypes) { try @@ -45,12 +58,22 @@ public static class Extensions Log.Error($"Error initializing module {service.GetType().Name}: {e}", e); } + _initializedModules.Add(service.GetType()); } } - public static IKernel LoadGlobalPlugins(this IKernel self) + public static IKernel UsePlugins(this IKernel self) { - var plugins = PluginLoader.Instance.Plugins; + TypeDescriptor.AddAttributes(typeof(List), new TypeConverterAttribute(typeof(PluginFlagConverter))); + self.Bind().ToConstant(new PluginSettings()); + self.RegisterModule(); + return self; + } + + + public static IKernel InitializeGlobalPlugins(this IKernel self) + { + var plugins = self.Get().Plugins; foreach (var plugin in plugins) { try @@ -66,9 +89,9 @@ public static class Extensions return self; } - public static IKernel LoadCameraPlugins(this IChildKernel self, string cameraName) + public static IKernel InitializeCameraPlugins(this IChildKernel self, string cameraName) { - var plugins = PluginLoader.Instance.Plugins; + var plugins = self.Get().Plugins; foreach (var plugin in plugins) { try diff --git a/VisionBuilder.UI.Common/Plugins/PluginLoader.cs b/VisionBuilder.UI.Common/Plugins/PluginLoader.cs index 856e696..fe85f2a 100644 --- a/VisionBuilder.UI.Common/Plugins/PluginLoader.cs +++ b/VisionBuilder.UI.Common/Plugins/PluginLoader.cs @@ -2,32 +2,56 @@ using Inspectron.Settings; using Ninject; using Serilog; +using VisionBuilder.UI.Common.Attributes; namespace VisionBuilder.UI.Common.Plugins; -public class PluginLoader +[ModulePriority(1000)] +public class PluginLoader:IVisionBuilderModule { - private static PluginLoader _instance; - public static PluginLoader Instance => _instance ??= new PluginLoader(); + private readonly PluginSettings _settings; + const string PLUGINS_DIRECTORY = "Plugins"; - private PluginLoader() + public PluginLoader(PluginSettings settings) { - Initialize(); + _settings = settings; } public List Plugins { get; } = new List(); - - public void Initialize() + private bool _isInitialized; + public void InitializeModule() { + if (_isInitialized) + { + return; + } + + _isInitialized = true; Log.Information("Loading plugins..."); var pluginsPath = Path.Combine("..", "Data", PLUGINS_DIRECTORY); Directory.CreateDirectory(pluginsPath); var pluginFolders = Directory.GetDirectories(pluginsPath); - + foreach (string folder in pluginFolders) { + var pluginName = Path.GetFileNameWithoutExtension(folder); + + if (_settings.Plugins.All(p => p.Name != pluginName)) + { + Log.Warning("Discovered new plugin:{PluginName}. Adding to settings and skipping.", pluginName); + _settings.Plugins.Add(new PluginSettings.PluginFlag { Name = pluginName, Enabled = false }); + continue; + } + + if (_settings.Plugins.Where(p => p.Name == pluginName).All(x=>!x.Enabled)) + { + Log.Information("Skipping plugin {PluginName} as it is not enabled in settings.", pluginName); + continue; + } + + var assemblyPath = Path.Combine(folder, Path.GetFileNameWithoutExtension(folder) + ".dll"); if (!File.Exists(assemblyPath)) @@ -58,10 +82,12 @@ public class PluginLoader if (type.IsAssignableTo(typeof(IPlugin)) && !type.IsAbstract) { Plugins.Add((IPlugin)Activator.CreateInstance(type)); - Log.Information("Discovered plugin: {PluginName}", type.Name); + Log.Information("Loaded plugin: {PluginName}", type.Name); } } } + + } \ No newline at end of file diff --git a/VisionBuilder.UI.Common/Plugins/PluginSettings.cs b/VisionBuilder.UI.Common/Plugins/PluginSettings.cs new file mode 100644 index 0000000..33f1eed --- /dev/null +++ b/VisionBuilder.UI.Common/Plugins/PluginSettings.cs @@ -0,0 +1,50 @@ +using System.ComponentModel; +using System.Globalization; +using Inspectron.Settings; +using VisionBuilder.UI.Common.Utils; + +namespace VisionBuilder.UI.Common.Plugins; + +public class PluginSettings:ISettings +{ + public class PluginFlag + { + public bool Enabled { get; set; } + public string Name { get; set; } + + public override string ToString() + { + return $"{(Enabled ? "" : "(Disabled)")}{Name}"; + } + } + + public List Plugins { get; set; } = new List(); + public void RegisterSettings(InspectronSettings settings) + { + settings.RegisterSimple(this, () => this.Plugins,"Plugins",nameof(Plugins)); + } +} + +public class PluginFlagConverter : GeneralListTypeConverter +{ + protected override PluginSettings.PluginFlag ConvertFromString(ITypeDescriptorContext context, CultureInfo culture, string stringValue) + { + if (string.IsNullOrEmpty(stringValue)) + return null; + var parts = stringValue.Split(','); + if (parts.Length == 2) + { + return new PluginSettings.PluginFlag + { + Name = parts[0].Trim(), + Enabled = bool.Parse(parts[1].Trim()) + }; + } + return null; + } + + protected override string ConvertToString(ITypeDescriptorContext context, CultureInfo culture, PluginSettings.PluginFlag errorShortName) + { + return $"{errorShortName.Name}, {errorShortName.Enabled}"; + } +} \ No newline at end of file diff --git a/VisionBuilder.UI.Console/VisionBuilderConsole.cs b/VisionBuilder.UI.Console/VisionBuilderConsole.cs index cda569f..f67c9a6 100644 --- a/VisionBuilder.UI.Console/VisionBuilderConsole.cs +++ b/VisionBuilder.UI.Console/VisionBuilderConsole.cs @@ -1,9 +1,11 @@ using Ninject; using Serilog; using VisionBuilder.UI.Common; +using VisionBuilder.UI.Common.Attributes; namespace VisionBuilder.UI.Console { + [ModulePriority(100000)] public class VisionBuilderConsole: IVisionBuilderModule { private readonly VisionBuilderConsoleSettings _settings; diff --git a/VisionBuilder.UI.IOCommander/Modules/IOCommanderModule.cs b/VisionBuilder.UI.IOCommander/Modules/IOCommanderModule.cs index d6a04ff..2c53d29 100644 --- a/VisionBuilder.UI.IOCommander/Modules/IOCommanderModule.cs +++ b/VisionBuilder.UI.IOCommander/Modules/IOCommanderModule.cs @@ -26,6 +26,7 @@ public class IOCommanderModule:IVisionBuilderModule public void InitializeModule() { + if(!_settings.Enabled)return; try { _ioCommander = _factory.Create(); diff --git a/VisionBuilder.UI.IOCommander/Settings/IOCommanderSettings.cs b/VisionBuilder.UI.IOCommander/Settings/IOCommanderSettings.cs index 7424f59..84636be 100644 --- a/VisionBuilder.UI.IOCommander/Settings/IOCommanderSettings.cs +++ b/VisionBuilder.UI.IOCommander/Settings/IOCommanderSettings.cs @@ -10,6 +10,7 @@ public class IOCommanderSettings: ISettings public int PulseLength { get; set; } = 100; public bool ShowDebugView { get; set; } = false; public string Port { get; set; }="COM3"; + public bool Enabled { get; set; } public List GlobalEvents { get; set; } = [ diff --git a/VisionBuilder.UI.Windows.Test/Program.cs b/VisionBuilder.UI.Windows.Test/Program.cs index 90a7cfc..eb69786 100644 --- a/VisionBuilder.UI.Windows.Test/Program.cs +++ b/VisionBuilder.UI.Windows.Test/Program.cs @@ -47,7 +47,8 @@ namespace VisionBuilder.UI.Windows.Test .UseIOCommanderWindowsDebug() .UseIOCommanderVirtualInput() .UseWindowsServices() - .LoadGlobalPlugins() + .UsePlugins() + ; mainKernel.Get().StartLoading("Camera initialization"); @@ -64,12 +65,9 @@ namespace VisionBuilder.UI.Windows.Test .UseRingbuffer(CAMERA1) .UseHawkeyeRecipes(CAMERA1) .UseCameraIOCommander(CAMERA1) - .LoadCameraPlugins(CAMERA1) ; - - - + // LOAD SETTINGS // mainKernel.RegisterSettings(); kernelCamera1.RegisterSettings(); @@ -79,7 +77,16 @@ namespace VisionBuilder.UI.Windows.Test // Must be done after settings are loaded kernelCamera1.UseCamera(); + // initialize modules + mainKernel.InitializeModules(); + kernelCamera1.InitializeModules(); + + settings.LoadSettings(); // load settings for plugins as they are loaded after modules + mainKernel.InitializeGlobalPlugins(); + kernelCamera1.InitializeCameraPlugins(CAMERA1); + + // initialize plugins the same way as modules(loaded modules are skipped) mainKernel.InitializeModules(); kernelCamera1.InitializeModules();