1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +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;
}
/// <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>
/// Gets the layer brush this view model is associated with
/// </summary>

View File

@ -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();

View File

@ -16,6 +16,16 @@ namespace Artemis.Core.LayerEffects
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>
/// Gets the layer effect this view model is associated with
/// </summary>

View File

@ -46,7 +46,7 @@ namespace Artemis.Core.LayerEffects
/// The plugin that provided this <see cref="LayerEffectDescriptor" />
/// </summary>
public LayerEffectProvider LayerEffectProvider { get; }
/// <summary>
/// Gets the GUID this descriptor is acting as a placeholder for. If null, this descriptor is not a placeholder
/// </summary>
@ -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;

View File

@ -16,6 +16,16 @@ namespace Artemis.Core
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>
/// Gets the plugin this configuration view model is associated with
/// </summary>

View File

@ -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
/// <summary>
/// The assembly the plugin code lives in
/// </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>
/// The entity representing the plugin
/// </summary>
internal PluginEntity PluginEntity { get; set; }
/// <inheritdoc />
public override string ToString()
{

View File

@ -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;

View File

@ -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<PluginInfo> _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));

View File

@ -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<T>(PluginInfo pluginInfo, IReadOnlyCollection<Type> 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<DefaultDataModelDisplayViewModel>();
}
}
@ -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;
}

View File

@ -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;

View File

@ -273,7 +273,8 @@ namespace Artemis.UI.Shared.Services
return null;
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()

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
{
// ReSharper disable once InconsistentNaming
public class UIModule : NinjectModule
{
public override void Load()

View File

@ -10,16 +10,14 @@ namespace Artemis.UI.Screens.Modules
{
public class ModuleRootViewModel : Conductor<Screen>.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);
}
}

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
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)

View File

@ -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)

View File

@ -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