diff --git a/src/Artemis.Core/Ninject/PluginModule.cs b/src/Artemis.Core/Ninject/PluginModule.cs new file mode 100644 index 000000000..bd1b81ddd --- /dev/null +++ b/src/Artemis.Core/Ninject/PluginModule.cs @@ -0,0 +1,19 @@ +using System; +using Ninject.Modules; + +namespace Artemis.Core.Ninject +{ + internal class PluginModule : NinjectModule + { + public PluginModule(PluginInfo pluginInfo) + { + PluginInfo = pluginInfo ?? throw new ArgumentNullException(nameof(pluginInfo)); + } + + public PluginInfo PluginInfo { get; } + + public override void Load() + { + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/LayerBrushes/BrushConfigurationViewModel.cs b/src/Artemis.Core/Plugins/LayerBrushes/BrushConfigurationViewModel.cs index 16915f0a2..34a928f94 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/BrushConfigurationViewModel.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/BrushConfigurationViewModel.cs @@ -16,6 +16,16 @@ namespace Artemis.Core.LayerBrushes LayerBrush = layerBrush; } + /// + /// Creates a new instance of the class with a validator + /// + /// + /// + protected BrushConfigurationViewModel(BaseLayerBrush layerBrush, IModelValidator validator) : base(validator) + { + LayerBrush = layerBrush; + } + /// /// Gets the layer brush this view model is associated with /// diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs index abea15d1c..8524c5de1 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrushDescriptor.cs @@ -62,7 +62,7 @@ namespace Artemis.Core.LayerBrushes if (layer.LayerBrush != null) throw new ArtemisCoreException("Layer already has an instantiated layer brush"); - var brush = (BaseLayerBrush) CoreService.Kernel.Get(LayerBrushType); + var brush = (BaseLayerBrush) LayerBrushProvider.PluginInfo.Kernel.Get(LayerBrushType); brush.Layer = layer; brush.Descriptor = this; brush.Initialize(); diff --git a/src/Artemis.Core/Plugins/LayerEffects/EffectConfigurationViewModel.cs b/src/Artemis.Core/Plugins/LayerEffects/EffectConfigurationViewModel.cs index 472f947ea..effc967a3 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/EffectConfigurationViewModel.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/EffectConfigurationViewModel.cs @@ -16,6 +16,16 @@ namespace Artemis.Core.LayerEffects LayerEffect = layerEffect; } + /// + /// Creates a new instance of the class with a validator + /// + /// + /// + protected EffectConfigurationViewModel(BaseLayerEffect layerEffect, IModelValidator validator) : base(validator) + { + LayerEffect = layerEffect; + } + /// /// Gets the layer effect this view model is associated with /// diff --git a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs index 50ffaa310..d913de153 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/LayerEffectDescriptor.cs @@ -46,7 +46,7 @@ namespace Artemis.Core.LayerEffects /// The plugin that provided this /// public LayerEffectProvider LayerEffectProvider { get; } - + /// /// Gets the GUID this descriptor is acting as a placeholder for. If null, this descriptor is not a placeholder /// @@ -67,7 +67,7 @@ namespace Artemis.Core.LayerEffects return; } - var effect = (BaseLayerEffect)CoreService.Kernel.Get(LayerEffectType); + var effect = (BaseLayerEffect) LayerEffectProvider.PluginInfo.Kernel.Get(LayerEffectType); effect.ProfileElement = renderElement; effect.EntityId = entity.Id; effect.Order = entity.Order; diff --git a/src/Artemis.Core/Plugins/PluginConfigurationViewModel.cs b/src/Artemis.Core/Plugins/PluginConfigurationViewModel.cs index 731dc1b3f..ea4169732 100644 --- a/src/Artemis.Core/Plugins/PluginConfigurationViewModel.cs +++ b/src/Artemis.Core/Plugins/PluginConfigurationViewModel.cs @@ -16,6 +16,16 @@ namespace Artemis.Core Plugin = plugin; } + /// + /// Creates a new instance of the class with a validator + /// + /// + /// + protected PluginConfigurationViewModel(Plugin plugin, IModelValidator validator) : base(validator) + { + Plugin = plugin; + } + /// /// Gets the plugin this configuration view model is associated with /// diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs index 28646a2da..414b33d44 100644 --- a/src/Artemis.Core/Plugins/PluginInfo.cs +++ b/src/Artemis.Core/Plugins/PluginInfo.cs @@ -4,6 +4,8 @@ using System.Reflection; using Artemis.Storage.Entities.Plugins; using McMaster.NETCore.Plugins; using Newtonsoft.Json; +using Ninject; +using Ninject.Extensions.ChildKernel; using Stylet; namespace Artemis.Core @@ -135,13 +137,18 @@ namespace Artemis.Core /// /// The assembly the plugin code lives in /// - internal Assembly Assembly { get; set; } + public Assembly Assembly { get; internal set; } + + /// + /// The Ninject kernel of the plugin + /// + public IKernel Kernel { get; internal set; } /// /// The entity representing the plugin /// internal PluginEntity PluginEntity { get; set; } - + /// public override string ToString() { diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index e981a567e..a7a010118 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -41,6 +41,8 @@ namespace Artemis.Core.Services IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService, IModuleService moduleService) { Kernel = kernel; + Constants.CorePluginInfo.Kernel = kernel; + _logger = logger; _pluginService = pluginService; _rgbService = rgbService; diff --git a/src/Artemis.Core/Services/PluginService.cs b/src/Artemis.Core/Services/PluginService.cs index 0fd4e3574..4eae4f677 100644 --- a/src/Artemis.Core/Services/PluginService.cs +++ b/src/Artemis.Core/Services/PluginService.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Reflection; using Artemis.Core.DeviceProviders; +using Artemis.Core.Ninject; using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Repositories.Interfaces; using McMaster.NETCore.Plugins; @@ -25,7 +26,6 @@ namespace Artemis.Core.Services private readonly ILogger _logger; private readonly IPluginRepository _pluginRepository; private readonly List _plugins; - private IKernel _childKernel; public PluginService(IKernel kernel, ILogger logger, IPluginRepository pluginRepository) { @@ -110,9 +110,6 @@ namespace Artemis.Core.Services // Unload all currently loaded plugins first UnloadPlugins(); - // Create a child kernel and app domain that will only contain the plugins - _childKernel = new ChildKernel(_kernel); - // Load the plugin assemblies into the plugin context var pluginDirectory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins")); foreach (var subDirectory in pluginDirectory.EnumerateDirectories()) @@ -160,13 +157,6 @@ namespace Artemis.Core.Services while (_plugins.Count > 0) UnloadPlugin(_plugins[0]); - // Dispose the child kernel and therefore any leftover plugins instantiated with it - if (_childKernel != null) - { - _childKernel.Dispose(); - _childKernel = null; - } - _plugins.Clear(); } } @@ -232,7 +222,9 @@ namespace Artemis.Core.Services { new Parameter("PluginInfo", pluginInfo, false) }; - pluginInfo.Instance = (Plugin) _childKernel.Get(pluginType, constraint: null, parameters: parameters); + pluginInfo.Kernel = new ChildKernel(_kernel); + pluginInfo.Kernel.Load(new PluginModule(pluginInfo)); + pluginInfo.Instance = (Plugin) pluginInfo.Kernel.Get(pluginType, constraint: null, parameters: parameters); pluginInfo.Instance.PluginInfo = pluginInfo; } catch (Exception e) @@ -262,10 +254,10 @@ namespace Artemis.Core.Services OnPluginDisabled(new PluginEventArgs(pluginInfo)); } - _childKernel.Unbind(pluginInfo.Instance.GetType()); - pluginInfo.Instance.Dispose(); pluginInfo.PluginLoader.Dispose(); + pluginInfo.Kernel.Dispose(); + _plugins.Remove(pluginInfo); OnPluginUnloaded(new PluginEventArgs(pluginInfo)); diff --git a/src/Artemis.UI.Shared/Services/DataModelUIService.cs b/src/Artemis.UI.Shared/Services/DataModelUIService.cs index e56b5f697..d3f35326d 100644 --- a/src/Artemis.UI.Shared/Services/DataModelUIService.cs +++ b/src/Artemis.UI.Shared/Services/DataModelUIService.cs @@ -58,7 +58,7 @@ namespace Artemis.UI.Shared.Services return mainDataModel; } - + var dataModel = _dataModelService.GetPluginDataModel(plugin); if (dataModel == null) return null; @@ -71,7 +71,7 @@ namespace Artemis.UI.Shared.Services viewModel.UpdateRequested += (sender, args) => viewModel.Update(this); return viewModel; } - + public DataModelVisualizationRegistration RegisterDataModelInput(PluginInfo pluginInfo, IReadOnlyCollection compatibleConversionTypes = null) where T : DataModelInputViewModel { if (compatibleConversionTypes == null) @@ -165,7 +165,7 @@ namespace Artemis.UI.Shared.Services { var match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType); if (match != null) - return (DataModelDisplayViewModel) _kernel.Get(match.ViewModelType); + return (DataModelDisplayViewModel) match.PluginInfo.Kernel.Get(match.ViewModelType); return !fallBackToDefault ? null : _kernel.Get(); } } @@ -222,7 +222,7 @@ namespace Artemis.UI.Shared.Services new ConstructorArgument("description", description), new ConstructorArgument("initialValue", initialValue) }; - var viewModel = (DataModelInputViewModel) _kernel.Get(registration.ViewModelType, parameters); + var viewModel = (DataModelInputViewModel) registration.PluginInfo.Kernel.Get(registration.ViewModelType, parameters); viewModel.CompatibleConversionTypes = registration.CompatibleConversionTypes; return viewModel; } diff --git a/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs b/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs index cd160e0cf..84b8e259d 100644 --- a/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs +++ b/src/Artemis.UI.Shared/Services/Dialog/DialogService.cs @@ -11,6 +11,7 @@ using Stylet; namespace Artemis.UI.Shared.Services { + // TODO: Become plugin-aware and use plugin kernel if injected into a plugin internal class DialogService : IDialogService { private readonly IKernel _kernel; diff --git a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs index 674e98223..afdc19069 100644 --- a/src/Artemis.UI.Shared/Services/ProfileEditorService.cs +++ b/src/Artemis.UI.Shared/Services/ProfileEditorService.cs @@ -273,7 +273,8 @@ namespace Artemis.UI.Shared.Services return null; var parameter = new ConstructorArgument("layerProperty", layerProperty); - return (PropertyInputViewModel) Kernel.Get(viewModelType, parameter); + var kernel = registration != null ? registration.PluginInfo.Kernel : Kernel; + return (PropertyInputViewModel) kernel.Get(viewModelType, parameter); } public ProfileModule GetCurrentModule() diff --git a/src/Artemis.UI/Ninject/PluginUIModule.cs b/src/Artemis.UI/Ninject/PluginUIModule.cs new file mode 100644 index 000000000..5cccc2033 --- /dev/null +++ b/src/Artemis.UI/Ninject/PluginUIModule.cs @@ -0,0 +1,32 @@ +using System; +using Artemis.Core; +using Artemis.UI.Stylet; +using FluentValidation; +using Ninject.Extensions.Conventions; +using Ninject.Modules; +using Stylet; + +namespace Artemis.UI.Ninject +{ + public class PluginUIModule : NinjectModule + { + public PluginUIModule(PluginInfo pluginInfo) + { + PluginInfo = pluginInfo ?? throw new ArgumentNullException(nameof(pluginInfo)); + } + + public PluginInfo PluginInfo { get; } + + public override void Load() + { + Bind(typeof(IModelValidator<>)).To(typeof(FluentValidationAdapter<>)); + Kernel.Bind(x => + { + x.From(PluginInfo.Assembly) + .SelectAllClasses() + .InheritedFrom() + .BindAllInterfaces(); + }); + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Ninject/UiModule.cs b/src/Artemis.UI/Ninject/UiModule.cs index 3d807d1d9..9bfa0a102 100644 --- a/src/Artemis.UI/Ninject/UiModule.cs +++ b/src/Artemis.UI/Ninject/UiModule.cs @@ -14,7 +14,6 @@ using Stylet; namespace Artemis.UI.Ninject { - // ReSharper disable once InconsistentNaming public class UIModule : NinjectModule { public override void Load() diff --git a/src/Artemis.UI/Screens/Modules/ModuleRootViewModel.cs b/src/Artemis.UI/Screens/Modules/ModuleRootViewModel.cs index 402c3412c..d8086b15f 100644 --- a/src/Artemis.UI/Screens/Modules/ModuleRootViewModel.cs +++ b/src/Artemis.UI/Screens/Modules/ModuleRootViewModel.cs @@ -10,16 +10,14 @@ namespace Artemis.UI.Screens.Modules { public class ModuleRootViewModel : Conductor.Collection.OneActive { - private readonly IKernel _kernel; private readonly IModuleVmFactory _moduleVmFactory; - public ModuleRootViewModel(Module module, IModuleVmFactory moduleVmFactory, IKernel kernel) + public ModuleRootViewModel(Module module, IModuleVmFactory moduleVmFactory) { DisplayName = module?.DisplayName; Module = module; _moduleVmFactory = moduleVmFactory; - _kernel = kernel; } public Module Module { get; } @@ -47,7 +45,7 @@ namespace Artemis.UI.Screens.Modules var module = new ConstructorArgument("module", Module); var displayName = new ConstructorArgument("displayName", DisplayName); - var viewModel = (ModuleViewModel) _kernel.Get(moduleTab.Type, module, displayName); + var viewModel = (ModuleViewModel) Module.PluginInfo.Kernel.Get(moduleTab.Type, module, displayName); Items.Add(viewModel); } } diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs index 07eb31138..67dbc1956 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/Tree/TreeGroupViewModel.cs @@ -60,7 +60,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree // Find the BaseLayerBrush parameter, it is required by the base constructor so its there for sure var brushParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerBrush).IsAssignableFrom(p.ParameterType)); var argument = new ConstructorArgument(brushParameter.Name, layerBrush); - var viewModel = (BrushConfigurationViewModel) _kernel.Get(configurationViewModel.Type, argument); + var viewModel = (BrushConfigurationViewModel) layerBrush.PluginInfo.Kernel.Get(configurationViewModel.Type, argument); _windowManager.ShowDialog(new LayerBrushSettingsWindowViewModel(viewModel)); } @@ -86,7 +86,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Tree var effectParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerEffect).IsAssignableFrom(p.ParameterType)); var argument = new ConstructorArgument(effectParameter.Name, layerEffect); - var viewModel = (EffectConfigurationViewModel) _kernel.Get(configurationViewModel.Type, argument); + var viewModel = (EffectConfigurationViewModel) layerEffect.PluginInfo.Kernel.Get(configurationViewModel.Type, argument); _windowManager.ShowDialog(new LayerEffectSettingsWindowViewModel(viewModel)); } catch (Exception e) diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs index f35cf5a68..0796a1297 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsViewModel.cs @@ -23,7 +23,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins public class PluginSettingsViewModel : PropertyChangedBase { private readonly IDialogService _dialogService; - private readonly IKernel _kernel; private readonly ILogger _logger; private readonly IPluginService _pluginService; private readonly ISnackbarMessageQueue _snackbarMessageQueue; @@ -33,7 +32,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins private PluginInfo _pluginInfo; public PluginSettingsViewModel(Plugin plugin, - IKernel kernel, ILogger logger, IWindowManager windowManager, IDialogService dialogService, @@ -43,7 +41,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins Plugin = plugin; PluginInfo = plugin.PluginInfo; - _kernel = kernel; _logger = logger; _windowManager = windowManager; _dialogService = dialogService; @@ -96,7 +93,7 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins var pluginParameter = constructors.First().GetParameters().First(p => typeof(Plugin).IsAssignableFrom(p.ParameterType)); var plugin = new ConstructorArgument(pluginParameter.Name, Plugin); - var viewModel = (PluginConfigurationViewModel) _kernel.Get(configurationViewModel.Type, plugin); + var viewModel = (PluginConfigurationViewModel) PluginInfo.Kernel.Get(configurationViewModel.Type, plugin); _windowManager.ShowDialog(new PluginSettingsWindowViewModel(viewModel, Icon)); } catch (Exception e) diff --git a/src/Artemis.UI/Services/RegistrationService.cs b/src/Artemis.UI/Services/RegistrationService.cs index 738fae7ea..5e1e44c22 100644 --- a/src/Artemis.UI/Services/RegistrationService.cs +++ b/src/Artemis.UI/Services/RegistrationService.cs @@ -1,6 +1,8 @@ using Artemis.Core; +using Artemis.Core.Services; using Artemis.UI.DefaultTypes.DataModel.Display; using Artemis.UI.DefaultTypes.DataModel.Input; +using Artemis.UI.Ninject; using Artemis.UI.PropertyInput; using Artemis.UI.Services.Interfaces; using Artemis.UI.Shared.Services; @@ -11,14 +13,19 @@ namespace Artemis.UI.Services { private readonly IDataModelUIService _dataModelUIService; private readonly IProfileEditorService _profileEditorService; + private readonly IPluginService _pluginService; private bool _registeredBuiltInDataModelDisplays; private bool _registeredBuiltInDataModelInputs; private bool _registeredBuiltInPropertyEditors; - public RegistrationService(IDataModelUIService dataModelUIService, IProfileEditorService profileEditorService) + public RegistrationService(IDataModelUIService dataModelUIService, IProfileEditorService profileEditorService, IPluginService pluginService) { _dataModelUIService = dataModelUIService; _profileEditorService = profileEditorService; + _pluginService = pluginService; + + LoadPluginModules(); + pluginService.PluginLoaded += PluginServiceOnPluginLoaded; } public void RegisterBuiltInDataModelDisplays() @@ -63,6 +70,17 @@ namespace Artemis.UI.Services _registeredBuiltInPropertyEditors = true; } + + private void PluginServiceOnPluginLoaded(object? sender, PluginEventArgs e) + { + e.PluginInfo.Kernel.Load(new[] { new PluginUIModule(e.PluginInfo) }); + } + + private void LoadPluginModules() + { + foreach (var pluginInfo in _pluginService.GetAllPluginInfo()) + pluginInfo.Kernel.Load(new[] { new PluginUIModule(pluginInfo) }); + } } public interface IRegistrationService : IArtemisUIService