1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-31 17:53:32 +00:00

Plugins - Gave each plugin its own child Ninject kernel

Plugins - Added support for validation in VMs
This commit is contained in:
Robert 2020-09-21 21:05:00 +02:00
parent 693ea29600
commit 2547b6a8bf
18 changed files with 132 additions and 36 deletions

View File

@ -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()
{
}
}
}

View File

@ -16,6 +16,16 @@ namespace Artemis.Core.LayerBrushes
LayerBrush = layerBrush; LayerBrush = layerBrush;
} }
/// <summary>
/// Creates a new instance of the <see cref="BrushConfigurationViewModel" /> class with a validator
/// </summary>
/// <param name="layerBrush"></param>
/// <param name="validator"></param>
protected BrushConfigurationViewModel(BaseLayerBrush layerBrush, IModelValidator validator) : base(validator)
{
LayerBrush = layerBrush;
}
/// <summary> /// <summary>
/// Gets the layer brush this view model is associated with /// Gets the layer brush this view model is associated with
/// </summary> /// </summary>

View File

@ -62,7 +62,7 @@ namespace Artemis.Core.LayerBrushes
if (layer.LayerBrush != null) if (layer.LayerBrush != null)
throw new ArtemisCoreException("Layer already has an instantiated layer brush"); 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.Layer = layer;
brush.Descriptor = this; brush.Descriptor = this;
brush.Initialize(); brush.Initialize();

View File

@ -16,6 +16,16 @@ namespace Artemis.Core.LayerEffects
LayerEffect = layerEffect; LayerEffect = layerEffect;
} }
/// <summary>
/// Creates a new instance of the <see cref="EffectConfigurationViewModel" /> class with a validator
/// </summary>
/// <param name="layerEffect"></param>
/// <param name="validator"></param>
protected EffectConfigurationViewModel(BaseLayerEffect layerEffect, IModelValidator validator) : base(validator)
{
LayerEffect = layerEffect;
}
/// <summary> /// <summary>
/// Gets the layer effect this view model is associated with /// Gets the layer effect this view model is associated with
/// </summary> /// </summary>

View File

@ -46,7 +46,7 @@ namespace Artemis.Core.LayerEffects
/// The plugin that provided this <see cref="LayerEffectDescriptor" /> /// The plugin that provided this <see cref="LayerEffectDescriptor" />
/// </summary> /// </summary>
public LayerEffectProvider LayerEffectProvider { get; } public LayerEffectProvider LayerEffectProvider { get; }
/// <summary> /// <summary>
/// Gets the GUID this descriptor is acting as a placeholder for. If null, this descriptor is not a placeholder /// Gets the GUID this descriptor is acting as a placeholder for. If null, this descriptor is not a placeholder
/// </summary> /// </summary>
@ -67,7 +67,7 @@ namespace Artemis.Core.LayerEffects
return; return;
} }
var effect = (BaseLayerEffect)CoreService.Kernel.Get(LayerEffectType); var effect = (BaseLayerEffect) LayerEffectProvider.PluginInfo.Kernel.Get(LayerEffectType);
effect.ProfileElement = renderElement; effect.ProfileElement = renderElement;
effect.EntityId = entity.Id; effect.EntityId = entity.Id;
effect.Order = entity.Order; effect.Order = entity.Order;

View File

@ -16,6 +16,16 @@ namespace Artemis.Core
Plugin = plugin; Plugin = plugin;
} }
/// <summary>
/// Creates a new instance of the <see cref="PluginConfigurationViewModel" /> class with a validator
/// </summary>
/// <param name="plugin"></param>
/// <param name="validator"></param>
protected PluginConfigurationViewModel(Plugin plugin, IModelValidator validator) : base(validator)
{
Plugin = plugin;
}
/// <summary> /// <summary>
/// Gets the plugin this configuration view model is associated with /// Gets the plugin this configuration view model is associated with
/// </summary> /// </summary>

View File

@ -4,6 +4,8 @@ using System.Reflection;
using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Entities.Plugins;
using McMaster.NETCore.Plugins; using McMaster.NETCore.Plugins;
using Newtonsoft.Json; using Newtonsoft.Json;
using Ninject;
using Ninject.Extensions.ChildKernel;
using Stylet; using Stylet;
namespace Artemis.Core namespace Artemis.Core
@ -135,13 +137,18 @@ namespace Artemis.Core
/// <summary> /// <summary>
/// The assembly the plugin code lives in /// The assembly the plugin code lives in
/// </summary> /// </summary>
internal Assembly Assembly { get; set; } public Assembly Assembly { get; internal set; }
/// <summary>
/// The Ninject kernel of the plugin
/// </summary>
public IKernel Kernel { get; internal set; }
/// <summary> /// <summary>
/// The entity representing the plugin /// The entity representing the plugin
/// </summary> /// </summary>
internal PluginEntity PluginEntity { get; set; } internal PluginEntity PluginEntity { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {

View File

@ -41,6 +41,8 @@ namespace Artemis.Core.Services
IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService, IModuleService moduleService) IRgbService rgbService, ISurfaceService surfaceService, IProfileService profileService, IModuleService moduleService)
{ {
Kernel = kernel; Kernel = kernel;
Constants.CorePluginInfo.Kernel = kernel;
_logger = logger; _logger = logger;
_pluginService = pluginService; _pluginService = pluginService;
_rgbService = rgbService; _rgbService = rgbService;

View File

@ -4,6 +4,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Artemis.Core.DeviceProviders; using Artemis.Core.DeviceProviders;
using Artemis.Core.Ninject;
using Artemis.Storage.Entities.Plugins; using Artemis.Storage.Entities.Plugins;
using Artemis.Storage.Repositories.Interfaces; using Artemis.Storage.Repositories.Interfaces;
using McMaster.NETCore.Plugins; using McMaster.NETCore.Plugins;
@ -25,7 +26,6 @@ namespace Artemis.Core.Services
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IPluginRepository _pluginRepository; private readonly IPluginRepository _pluginRepository;
private readonly List<PluginInfo> _plugins; private readonly List<PluginInfo> _plugins;
private IKernel _childKernel;
public PluginService(IKernel kernel, ILogger logger, IPluginRepository pluginRepository) public PluginService(IKernel kernel, ILogger logger, IPluginRepository pluginRepository)
{ {
@ -110,9 +110,6 @@ namespace Artemis.Core.Services
// Unload all currently loaded plugins first // Unload all currently loaded plugins first
UnloadPlugins(); 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 // Load the plugin assemblies into the plugin context
var pluginDirectory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins")); var pluginDirectory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins"));
foreach (var subDirectory in pluginDirectory.EnumerateDirectories()) foreach (var subDirectory in pluginDirectory.EnumerateDirectories())
@ -160,13 +157,6 @@ namespace Artemis.Core.Services
while (_plugins.Count > 0) while (_plugins.Count > 0)
UnloadPlugin(_plugins[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(); _plugins.Clear();
} }
} }
@ -232,7 +222,9 @@ namespace Artemis.Core.Services
{ {
new Parameter("PluginInfo", pluginInfo, false) 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; pluginInfo.Instance.PluginInfo = pluginInfo;
} }
catch (Exception e) catch (Exception e)
@ -262,10 +254,10 @@ namespace Artemis.Core.Services
OnPluginDisabled(new PluginEventArgs(pluginInfo)); OnPluginDisabled(new PluginEventArgs(pluginInfo));
} }
_childKernel.Unbind(pluginInfo.Instance.GetType());
pluginInfo.Instance.Dispose(); pluginInfo.Instance.Dispose();
pluginInfo.PluginLoader.Dispose(); pluginInfo.PluginLoader.Dispose();
pluginInfo.Kernel.Dispose();
_plugins.Remove(pluginInfo); _plugins.Remove(pluginInfo);
OnPluginUnloaded(new PluginEventArgs(pluginInfo)); OnPluginUnloaded(new PluginEventArgs(pluginInfo));

View File

@ -58,7 +58,7 @@ namespace Artemis.UI.Shared.Services
return mainDataModel; return mainDataModel;
} }
var dataModel = _dataModelService.GetPluginDataModel(plugin); var dataModel = _dataModelService.GetPluginDataModel(plugin);
if (dataModel == null) if (dataModel == null)
return null; return null;
@ -71,7 +71,7 @@ namespace Artemis.UI.Shared.Services
viewModel.UpdateRequested += (sender, args) => viewModel.Update(this); viewModel.UpdateRequested += (sender, args) => viewModel.Update(this);
return viewModel; return viewModel;
} }
public DataModelVisualizationRegistration RegisterDataModelInput<T>(PluginInfo pluginInfo, IReadOnlyCollection<Type> compatibleConversionTypes = null) where T : DataModelInputViewModel public DataModelVisualizationRegistration RegisterDataModelInput<T>(PluginInfo pluginInfo, IReadOnlyCollection<Type> compatibleConversionTypes = null) where T : DataModelInputViewModel
{ {
if (compatibleConversionTypes == null) if (compatibleConversionTypes == null)
@ -165,7 +165,7 @@ namespace Artemis.UI.Shared.Services
{ {
var match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType); var match = _registeredDataModelDisplays.FirstOrDefault(d => d.SupportedType == propertyType);
if (match != null) if (match != null)
return (DataModelDisplayViewModel) _kernel.Get(match.ViewModelType); return (DataModelDisplayViewModel) match.PluginInfo.Kernel.Get(match.ViewModelType);
return !fallBackToDefault ? null : _kernel.Get<DefaultDataModelDisplayViewModel>(); return !fallBackToDefault ? null : _kernel.Get<DefaultDataModelDisplayViewModel>();
} }
} }
@ -222,7 +222,7 @@ namespace Artemis.UI.Shared.Services
new ConstructorArgument("description", description), new ConstructorArgument("description", description),
new ConstructorArgument("initialValue", initialValue) 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; viewModel.CompatibleConversionTypes = registration.CompatibleConversionTypes;
return viewModel; return viewModel;
} }

View File

@ -11,6 +11,7 @@ using Stylet;
namespace Artemis.UI.Shared.Services namespace Artemis.UI.Shared.Services
{ {
// TODO: Become plugin-aware and use plugin kernel if injected into a plugin
internal class DialogService : IDialogService internal class DialogService : IDialogService
{ {
private readonly IKernel _kernel; private readonly IKernel _kernel;

View File

@ -273,7 +273,8 @@ namespace Artemis.UI.Shared.Services
return null; return null;
var parameter = new ConstructorArgument("layerProperty", layerProperty); var parameter = new ConstructorArgument("layerProperty", layerProperty);
return (PropertyInputViewModel<T>) Kernel.Get(viewModelType, parameter); var kernel = registration != null ? registration.PluginInfo.Kernel : Kernel;
return (PropertyInputViewModel<T>) kernel.Get(viewModelType, parameter);
} }
public ProfileModule GetCurrentModule() public ProfileModule GetCurrentModule()

View File

@ -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<IValidator>()
.BindAllInterfaces();
});
}
}
}

View File

@ -14,7 +14,6 @@ using Stylet;
namespace Artemis.UI.Ninject namespace Artemis.UI.Ninject
{ {
// ReSharper disable once InconsistentNaming
public class UIModule : NinjectModule public class UIModule : NinjectModule
{ {
public override void Load() public override void Load()

View File

@ -10,16 +10,14 @@ namespace Artemis.UI.Screens.Modules
{ {
public class ModuleRootViewModel : Conductor<Screen>.Collection.OneActive public class ModuleRootViewModel : Conductor<Screen>.Collection.OneActive
{ {
private readonly IKernel _kernel;
private readonly IModuleVmFactory _moduleVmFactory; private readonly IModuleVmFactory _moduleVmFactory;
public ModuleRootViewModel(Module module, IModuleVmFactory moduleVmFactory, IKernel kernel) public ModuleRootViewModel(Module module, IModuleVmFactory moduleVmFactory)
{ {
DisplayName = module?.DisplayName; DisplayName = module?.DisplayName;
Module = module; Module = module;
_moduleVmFactory = moduleVmFactory; _moduleVmFactory = moduleVmFactory;
_kernel = kernel;
} }
public Module Module { get; } public Module Module { get; }
@ -47,7 +45,7 @@ namespace Artemis.UI.Screens.Modules
var module = new ConstructorArgument("module", Module); var module = new ConstructorArgument("module", Module);
var displayName = new ConstructorArgument("displayName", DisplayName); 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); Items.Add(viewModel);
} }
} }

View File

@ -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 // 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 brushParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerBrush).IsAssignableFrom(p.ParameterType));
var argument = new ConstructorArgument(brushParameter.Name, layerBrush); 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)); _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 effectParameter = constructors.First().GetParameters().First(p => typeof(BaseLayerEffect).IsAssignableFrom(p.ParameterType));
var argument = new ConstructorArgument(effectParameter.Name, layerEffect); 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)); _windowManager.ShowDialog(new LayerEffectSettingsWindowViewModel(viewModel));
} }
catch (Exception e) catch (Exception e)

View File

@ -23,7 +23,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
public class PluginSettingsViewModel : PropertyChangedBase public class PluginSettingsViewModel : PropertyChangedBase
{ {
private readonly IDialogService _dialogService; private readonly IDialogService _dialogService;
private readonly IKernel _kernel;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IPluginService _pluginService; private readonly IPluginService _pluginService;
private readonly ISnackbarMessageQueue _snackbarMessageQueue; private readonly ISnackbarMessageQueue _snackbarMessageQueue;
@ -33,7 +32,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
private PluginInfo _pluginInfo; private PluginInfo _pluginInfo;
public PluginSettingsViewModel(Plugin plugin, public PluginSettingsViewModel(Plugin plugin,
IKernel kernel,
ILogger logger, ILogger logger,
IWindowManager windowManager, IWindowManager windowManager,
IDialogService dialogService, IDialogService dialogService,
@ -43,7 +41,6 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins
Plugin = plugin; Plugin = plugin;
PluginInfo = plugin.PluginInfo; PluginInfo = plugin.PluginInfo;
_kernel = kernel;
_logger = logger; _logger = logger;
_windowManager = windowManager; _windowManager = windowManager;
_dialogService = dialogService; _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 pluginParameter = constructors.First().GetParameters().First(p => typeof(Plugin).IsAssignableFrom(p.ParameterType));
var plugin = new ConstructorArgument(pluginParameter.Name, Plugin); 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)); _windowManager.ShowDialog(new PluginSettingsWindowViewModel(viewModel, Icon));
} }
catch (Exception e) catch (Exception e)

View File

@ -1,6 +1,8 @@
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.DefaultTypes.DataModel.Display; using Artemis.UI.DefaultTypes.DataModel.Display;
using Artemis.UI.DefaultTypes.DataModel.Input; using Artemis.UI.DefaultTypes.DataModel.Input;
using Artemis.UI.Ninject;
using Artemis.UI.PropertyInput; using Artemis.UI.PropertyInput;
using Artemis.UI.Services.Interfaces; using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
@ -11,14 +13,19 @@ namespace Artemis.UI.Services
{ {
private readonly IDataModelUIService _dataModelUIService; private readonly IDataModelUIService _dataModelUIService;
private readonly IProfileEditorService _profileEditorService; private readonly IProfileEditorService _profileEditorService;
private readonly IPluginService _pluginService;
private bool _registeredBuiltInDataModelDisplays; private bool _registeredBuiltInDataModelDisplays;
private bool _registeredBuiltInDataModelInputs; private bool _registeredBuiltInDataModelInputs;
private bool _registeredBuiltInPropertyEditors; private bool _registeredBuiltInPropertyEditors;
public RegistrationService(IDataModelUIService dataModelUIService, IProfileEditorService profileEditorService) public RegistrationService(IDataModelUIService dataModelUIService, IProfileEditorService profileEditorService, IPluginService pluginService)
{ {
_dataModelUIService = dataModelUIService; _dataModelUIService = dataModelUIService;
_profileEditorService = profileEditorService; _profileEditorService = profileEditorService;
_pluginService = pluginService;
LoadPluginModules();
pluginService.PluginLoaded += PluginServiceOnPluginLoaded;
} }
public void RegisterBuiltInDataModelDisplays() public void RegisterBuiltInDataModelDisplays()
@ -63,6 +70,17 @@ namespace Artemis.UI.Services
_registeredBuiltInPropertyEditors = true; _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 public interface IRegistrationService : IArtemisUIService