From 991c5fd955b426b8b3702290080f462e59a7c974 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 15 Nov 2020 17:38:06 +0100 Subject: [PATCH] Profiles - Tweaked render pipeline, improving render performance by ~40% --- src/Artemis.Core/Models/Profile/Folder.cs | 19 +- src/Artemis.Core/Models/Profile/Layer.cs | 28 +- .../Models/Profile/RenderProfileElement.cs | 18 ++ src/Artemis.Core/Models/Profile/Renderer.cs | 60 ++-- .../LayerBrushes/Internal/BaseLayerBrush.cs | 2 +- .../Plugins/LayerBrushes/LayerBrush.cs | 8 +- .../Plugins/LayerBrushes/PerLedLayerBrush.cs | 2 +- .../Plugins/LayerBrushes/RgbNetLayerBrush.cs | 2 +- .../LayerEffects/Internal/BaseLayerEffect.cs | 4 +- .../Placeholder/PlaceholderLayerEffect.cs | 4 +- .../Services/PluginManagementService.cs | 291 +++++++++--------- 11 files changed, 231 insertions(+), 207 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index 54635f8c1..94a3909f8 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -29,7 +29,6 @@ namespace Artemis.Core Profile = Parent.Profile; Name = name; Enabled = true; - Renderer = new Renderer(); _layerEffects = new List(); _expandedPropertyGroups = new List(); @@ -47,7 +46,6 @@ namespace Artemis.Core Name = folderEntity.Name; Enabled = folderEntity.Enabled; Order = folderEntity.Order; - Renderer = new Renderer(); _layerEffects = new List(); _expandedPropertyGroups = new List(); @@ -69,8 +67,6 @@ namespace Artemis.Core internal override RenderElementEntity RenderElementEntity => FolderEntity; - internal Renderer Renderer { get; } - /// public override List GetAllLayerProperties() { @@ -133,7 +129,7 @@ namespace Artemis.Core /// The newly created copy public Folder CreateCopy() { - JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }; + JsonSerializerSettings settings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All}; FolderEntity entityCopy = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(FolderEntity, settings), settings)!; entityCopy.Id = Guid.NewGuid(); entityCopy.Name += " - Copy"; @@ -233,29 +229,28 @@ namespace Artemis.Core lock (Timeline) { - foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) { baseLayerEffect.BaseProperties?.Update(Timeline); baseLayerEffect.Update(Timeline.Delta.TotalSeconds); } - + try { canvas.Save(); Renderer.Open(Path, Parent as Folder); - if (Renderer.Canvas == null || Renderer.Path == null || Renderer.Paint == null) throw new ArtemisCoreException("Failed to open folder render context"); + SKRect rendererBounds = Renderer.Path.Bounds; foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PreProcess(Renderer.Canvas, Renderer.Path, Renderer.Paint); + baseLayerEffect.PreProcess(Renderer.Canvas, rendererBounds, Renderer.Paint); // If required, apply the opacity override of the module to the root folder if (IsRootFolder && Profile.Module.OpacityOverride < 1) { double multiplier = Easings.SineEaseInOut(Profile.Module.OpacityOverride); - Renderer.Paint.Color = Renderer.Paint.Color.WithAlpha((byte)(Renderer.Paint.Color.Alpha * multiplier)); + Renderer.Paint.Color = Renderer.Paint.Color.WithAlpha((byte) (Renderer.Paint.Color.Alpha * multiplier)); } // No point rendering if the alpha was set to zero by one of the effects @@ -263,11 +258,11 @@ namespace Artemis.Core return; // Iterate the children in reverse because the first layer must be rendered last to end up on top - for (int index = Children.Count - 1; index > -1; index--) + for (int index = Children.Count - 1; index > -1; index--) Children[index].Render(Renderer.Canvas); foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PostProcess(Renderer.Canvas, Renderer.Path, Renderer.Paint); + baseLayerEffect.PostProcess(Renderer.Canvas, rendererBounds, Renderer.Paint); canvas.DrawBitmap(Renderer.Bitmap, Renderer.TargetLocation, Renderer.Paint); } diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index b2de34b23..2313aae6b 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -39,7 +39,6 @@ namespace Artemis.Core Enabled = true; General = new LayerGeneralProperties(); Transform = new LayerTransformProperties(); - Renderer = new Renderer(); _layerEffects = new List(); _leds = new List(); @@ -58,8 +57,7 @@ namespace Artemis.Core Parent = parent; General = new LayerGeneralProperties(); Transform = new LayerTransformProperties(); - Renderer = new Renderer(); - + _layerEffects = new List(); _leds = new List(); _expandedPropertyGroups = new List(); @@ -113,9 +111,7 @@ namespace Artemis.Core internal LayerEntity LayerEntity { get; set; } internal override RenderElementEntity RenderElementEntity => LayerEntity; - - internal Renderer Renderer { get; } - + /// /// Creates a deep copy of the layer /// @@ -349,11 +345,11 @@ namespace Artemis.Core Renderer.Paint.BlendMode = General.BlendMode.CurrentValue; Renderer.Paint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f)); - // Clip anything outside the LED selection bounds - canvas.ClipPath(Renderer.ClipPath); - using SKPath renderPath = new SKPath(); - renderPath.AddRect(Renderer.Path.Bounds); + if (General.ShapeType.CurrentValue == LayerShapeType.Rectangle) + renderPath.AddRect(Renderer.Path.Bounds); + else + renderPath.AddOval(Renderer.Path.Bounds); if (General.TransformMode.CurrentValue == LayerTransformMode.Normal) { @@ -373,7 +369,7 @@ namespace Artemis.Core // If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off Renderer.Canvas.ClipPath(renderPath); - DelegateRendering(renderPath); + DelegateRendering(renderPath.Bounds); } else if (General.TransformMode.CurrentValue == LayerTransformMode.Clip) { @@ -382,7 +378,7 @@ namespace Artemis.Core // If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off Renderer.Canvas.ClipPath(renderPath); - DelegateRendering(Renderer.Path); + DelegateRendering(Renderer.Path.Bounds); } canvas.DrawBitmap(Renderer.Bitmap, Renderer.TargetLocation, Renderer.Paint); @@ -394,15 +390,15 @@ namespace Artemis.Core } } - private void DelegateRendering(SKPath path) + private void DelegateRendering(SKRect bounds) { foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PreProcess(Renderer.Canvas, path, Renderer.Paint); + baseLayerEffect.PreProcess(Renderer.Canvas, bounds, Renderer.Paint); - LayerBrush.InternalRender(Renderer.Canvas, path, Renderer.Paint); + LayerBrush.InternalRender(Renderer.Canvas, bounds, Renderer.Paint); foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) - baseLayerEffect.PostProcess(Renderer.Canvas, path, Renderer.Paint); + baseLayerEffect.PostProcess(Renderer.Canvas, bounds, Renderer.Paint); } internal void CalculateRenderProperties() diff --git a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs index de9a7726a..4a4343121 100644 --- a/src/Artemis.Core/Models/Profile/RenderProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/RenderProfileElement.cs @@ -16,6 +16,7 @@ namespace Artemis.Core protected RenderProfileElement() { Timeline = new Timeline(); + Renderer = new Renderer(); LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded; LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; @@ -102,9 +103,23 @@ namespace Artemis.Core #region Properties + private ProfileElement _parent; private SKPath _path; internal abstract RenderElementEntity RenderElementEntity { get; } + /// + /// Gets the parent of this element + /// + public new ProfileElement Parent + { + get => _parent; + internal set + { + SetAndNotify(ref _parent, value); + Renderer.Invalidate(); + } + } + /// /// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is /// clipped. @@ -118,6 +133,7 @@ namespace Artemis.Core // I can't really be sure about the performance impact of calling Bounds often but // SkiaSharp calls SkiaApi.sk_path_get_bounds (Handle, &rect); which sounds expensive Bounds = value?.Bounds ?? SKRect.Empty; + Renderer.Invalidate(); } } @@ -130,6 +146,8 @@ namespace Artemis.Core private set => SetAndNotify(ref _bounds, value); } + internal Renderer Renderer { get; } + #region Property group expansion protected List _expandedPropertyGroups; diff --git a/src/Artemis.Core/Models/Profile/Renderer.cs b/src/Artemis.Core/Models/Profile/Renderer.cs index a6494c1c8..c16d69ea8 100644 --- a/src/Artemis.Core/Models/Profile/Renderer.cs +++ b/src/Artemis.Core/Models/Profile/Renderer.cs @@ -5,12 +5,12 @@ namespace Artemis.Core { internal class Renderer : IDisposable { + private bool _valid; private bool _disposed; public SKBitmap? Bitmap { get; private set; } public SKCanvas? Canvas { get; private set; } public SKPaint? Paint { get; private set; } public SKPath? Path { get; private set; } - public SKPath? ClipPath { get; private set; } public SKPoint TargetLocation { get; private set; } public bool IsOpen { get; private set; } @@ -26,27 +26,30 @@ namespace Artemis.Core if (IsOpen) throw new ArtemisCoreException("Cannot open render context because it is already open"); - int width = (int) path.Bounds.Width; - int height = (int) path.Bounds.Height; + if (!_valid) + { + SKRect pathBounds = path.Bounds; + int width = (int) pathBounds.Width; + int height = (int) pathBounds.Height; - if (Bitmap == null) - Bitmap = new SKBitmap(width, height); - else if (Bitmap.Info.Width != width || Bitmap.Info.Height != height) Bitmap = new SKBitmap(width, height); + Path = new SKPath(path); + Canvas = new SKCanvas(Bitmap); + Path.Transform(SKMatrix.MakeTranslation(pathBounds.Left * -1, pathBounds.Top * -1)); + + TargetLocation = new SKPoint(pathBounds.Location.X, pathBounds.Location.Y); + if (parent != null) + TargetLocation -= parent.Bounds.Location; + + Canvas.ClipPath(Path); + + _valid = true; + } - Path = new SKPath(path); - Canvas = new SKCanvas(Bitmap); Paint = new SKPaint(); - Path.Transform(SKMatrix.MakeTranslation(Path.Bounds.Left * -1, Path.Bounds.Top * -1)); Canvas.Clear(); - - TargetLocation = new SKPoint(path.Bounds.Location.X, path.Bounds.Location.Y); - if (parent != null) - TargetLocation -= parent.Path.Bounds.Location; - - ClipPath = new SKPath(Path); - ClipPath.Transform(SKMatrix.MakeTranslation(TargetLocation.X, TargetLocation.Y)); + Canvas.Save(); IsOpen = true; } @@ -55,26 +58,35 @@ namespace Artemis.Core { if (_disposed) throw new ObjectDisposedException("Renderer"); - - Canvas?.Dispose(); + + Canvas.Restore(); Paint?.Dispose(); - Path?.Dispose(); - ClipPath?.Dispose(); - - Canvas = null; Paint = null; - Path = null; - ClipPath = null; IsOpen = false; } + public void Invalidate() + { + if (_disposed) + throw new ObjectDisposedException("Renderer"); + + _valid = false; + } + public void Dispose() { if (IsOpen) Close(); + Canvas?.Dispose(); + Paint?.Dispose(); + Path?.Dispose(); Bitmap?.Dispose(); + + Canvas = null; + Paint = null; + Path = null; Bitmap = null; _disposed = true; diff --git a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs index fd399e1ad..e7089c4ba 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/Internal/BaseLayerBrush.cs @@ -95,7 +95,7 @@ namespace Artemis.Core.LayerBrushes // but LayerBrush and RgbNetLayerBrush outside the core internal abstract void Initialize(); - internal abstract void InternalRender(SKCanvas canvas, SKPath path, SKPaint paint); + internal abstract void InternalRender(SKCanvas canvas, SKRect path, SKPaint paint); internal virtual void Dispose(bool disposing) { diff --git a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs index 8ab2bd9cd..94b8623a4 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/LayerBrush.cs @@ -22,13 +22,13 @@ namespace Artemis.Core.LayerBrushes /// Called during rendering or layer preview, in the order configured on the layer /// /// The layer canvas - /// The path to be filled, represents the shape + /// The area to be filled, covers the shape /// The paint to be used to fill the shape - public abstract void Render(SKCanvas canvas, SKPath path, SKPaint paint); + public abstract void Render(SKCanvas canvas, SKRect bounds, SKPaint paint); - internal override void InternalRender(SKCanvas canvas, SKPath path, SKPaint paint) + internal override void InternalRender(SKCanvas canvas, SKRect bounds, SKPaint paint) { - Render(canvas, path, paint); + Render(canvas, bounds, paint); } internal override void Initialize() diff --git a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs index 368492cc3..f9ea640cb 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/PerLedLayerBrush.cs @@ -28,7 +28,7 @@ namespace Artemis.Core.LayerBrushes /// The color the LED will receive public abstract SKColor GetColor(ArtemisLed led, SKPoint renderPoint); - internal override void InternalRender(SKCanvas canvas, SKPath path, SKPaint paint) + internal override void InternalRender(SKCanvas canvas, SKRect bounds, SKPaint paint) { // We don't want rotation on this canvas because that'll displace the LEDs, translations are applied to the points of each LED instead if (Layer.General.TransformMode.CurrentValue == LayerTransformMode.Normal && SupportsTransformation) diff --git a/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs b/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs index 0856af7cf..ee90e8cfa 100644 --- a/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs +++ b/src/Artemis.Core/Plugins/LayerBrushes/RgbNetLayerBrush.cs @@ -68,7 +68,7 @@ namespace Artemis.Core.LayerBrushes } // Not used in this effect type - internal override void InternalRender(SKCanvas canvas, SKPath path, SKPaint paint) + internal override void InternalRender(SKCanvas canvas, SKRect bounds, SKPaint paint) { throw new NotImplementedException("RGB.NET layer effectes do not implement InternalRender"); } diff --git a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs index 126a1e700..bff86bdf9 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Internal/BaseLayerEffect.cs @@ -132,7 +132,7 @@ namespace Artemis.Core.LayerEffects /// The canvas used to render the frame /// The bounds this layer/folder will render in /// The paint this layer/folder will use to render - public abstract void PreProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint); + public abstract void PreProcess(SKCanvas canvas, SKRect renderBounds, SKPaint paint); /// /// Called after the layer of folder has been rendered @@ -140,7 +140,7 @@ namespace Artemis.Core.LayerEffects /// The canvas used to render the frame /// The bounds this layer/folder rendered in /// The paint this layer/folder used to render - public abstract void PostProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint); + public abstract void PostProcess(SKCanvas canvas, SKRect renderBounds, SKPaint paint); // Not only is this needed to initialize properties on the layer effects, it also prevents implementing anything // but LayerEffect outside the core diff --git a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs index 620425a62..2f92cfe91 100644 --- a/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs +++ b/src/Artemis.Core/Plugins/LayerEffects/Placeholder/PlaceholderLayerEffect.cs @@ -40,12 +40,12 @@ namespace Artemis.Core.LayerEffects.Placeholder } /// - public override void PreProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint) + public override void PreProcess(SKCanvas canvas, SKRect bounds, SKPaint paint) { } /// - public override void PostProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint) + public override void PostProcess(SKCanvas canvas, SKRect bounds, SKPaint paint) { } diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index fdf0869a1..4bcf40dcb 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -124,17 +124,26 @@ namespace Artemis.Core.Services public List GetAllPlugins() { - return new List(_plugins); + lock (_plugins) + { + return new List(_plugins); + } } public List GetFeaturesOfType() where T : PluginFeature { - return _plugins.Where(p => p.IsEnabled).SelectMany(p => p.Features.Where(i => i.IsEnabled && i is T)).Cast().ToList(); + lock (_plugins) + { + return _plugins.Where(p => p.IsEnabled).SelectMany(p => p.Features.Where(i => i.IsEnabled && i is T)).Cast().ToList(); + } } public Plugin? GetPluginByAssembly(Assembly assembly) { - return _plugins.FirstOrDefault(p => p.Assembly == assembly); + lock (_plugins) + { + return _plugins.FirstOrDefault(p => p.Assembly == assembly); + } } // TODO: move to a more appropriate service @@ -170,106 +179,105 @@ namespace Artemis.Core.Services { if (LoadingPlugins) throw new ArtemisCoreException("Cannot load plugins while a previous load hasn't been completed yet."); + + LoadingPlugins = true; - lock (_plugins) + // Unload all currently loaded plugins first + UnloadPlugins(); + + // Load the plugin assemblies into the plugin context + DirectoryInfo pluginDirectory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins")); + foreach (DirectoryInfo subDirectory in pluginDirectory.EnumerateDirectories()) { - LoadingPlugins = true; - - // Unload all currently loaded plugins first - UnloadPlugins(); - - // Load the plugin assemblies into the plugin context - DirectoryInfo pluginDirectory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins")); - foreach (DirectoryInfo subDirectory in pluginDirectory.EnumerateDirectories()) + try { - try - { - Plugin plugin = LoadPlugin(subDirectory); - if (plugin.Entity.IsEnabled) - EnablePlugin(plugin, false, ignorePluginLock); - } - catch (Exception e) - { - _logger.Warning(new ArtemisPluginException("Failed to load plugin", e), "Plugin exception"); - } + Plugin plugin = LoadPlugin(subDirectory); + if (plugin.Entity.IsEnabled) + EnablePlugin(plugin, false, ignorePluginLock); + } + catch (Exception e) + { + _logger.Warning(new ArtemisPluginException("Failed to load plugin", e), "Plugin exception"); } - - LoadingPlugins = false; } + + LoadingPlugins = false; } public void UnloadPlugins() { + // Unload all plugins + while (_plugins.Count > 0) + UnloadPlugin(_plugins[0]); + lock (_plugins) { - // Unload all plugins - while (_plugins.Count > 0) - UnloadPlugin(_plugins[0]); - _plugins.Clear(); } } public Plugin LoadPlugin(DirectoryInfo directory) { + _logger.Debug("Loading plugin from {directory}", directory.FullName); + + // Load the metadata + string metadataFile = Path.Combine(directory.FullName, "plugin.json"); + if (!File.Exists(metadataFile)) + _logger.Warning(new ArtemisPluginException("Couldn't find the plugins metadata file at " + metadataFile), "Plugin exception"); + + // PluginInfo contains the ID which we need to move on + PluginInfo pluginInfo = JsonConvert.DeserializeObject(File.ReadAllText(metadataFile)); + + if (pluginInfo.Guid == Constants.CorePluginInfo.Guid) + throw new ArtemisPluginException($"Plugin cannot use reserved GUID {pluginInfo.Guid}"); + lock (_plugins) { - _logger.Debug("Loading plugin from {directory}", directory.FullName); - - // Load the metadata - string metadataFile = Path.Combine(directory.FullName, "plugin.json"); - if (!File.Exists(metadataFile)) - _logger.Warning(new ArtemisPluginException("Couldn't find the plugins metadata file at " + metadataFile), "Plugin exception"); - - // PluginInfo contains the ID which we need to move on - PluginInfo pluginInfo = JsonConvert.DeserializeObject(File.ReadAllText(metadataFile)); - - if (pluginInfo.Guid == Constants.CorePluginInfo.Guid) - throw new ArtemisPluginException($"Plugin cannot use reserved GUID {pluginInfo.Guid}"); - // Ensure the plugin is not already loaded if (_plugins.Any(p => p.Guid == pluginInfo.Guid)) throw new ArtemisCoreException("Cannot load a plugin that is already loaded"); - - Plugin plugin = new Plugin(pluginInfo, directory); - OnPluginLoading(new PluginEventArgs(plugin)); - - // Load the entity and fall back on creating a new one - plugin.Entity = _pluginRepository.GetPluginByGuid(pluginInfo.Guid) ?? new PluginEntity {Id = plugin.Guid, IsEnabled = true}; - - // Locate the main assembly entry - string? mainFile = plugin.ResolveRelativePath(plugin.Info.Main); - if (!File.Exists(mainFile)) - throw new ArtemisPluginException(plugin, "Couldn't find the plugins main entry at " + mainFile); - - // Load the plugin, all types implementing Plugin and register them with DI - plugin.PluginLoader = PluginLoader.CreateFromAssemblyFile(mainFile!, configure => - { - configure.IsUnloadable = true; - configure.PreferSharedTypes = true; - }); - - try - { - plugin.Assembly = plugin.PluginLoader.LoadDefaultAssembly(); - } - catch (Exception e) - { - throw new ArtemisPluginException(plugin, "Failed to load the plugins assembly", e); - } - - List bootstrappers = plugin.Assembly.GetTypes().Where(t => typeof(IPluginBootstrapper).IsAssignableFrom(t)).ToList(); - if (bootstrappers.Count > 1) - _logger.Warning($"{plugin} has more than one bootstrapper, only initializing {bootstrappers.First().FullName}"); - if (bootstrappers.Any()) - plugin.Bootstrapper = (IPluginBootstrapper?) Activator.CreateInstance(bootstrappers.First()); - - _plugins.Add(plugin); - - OnPluginLoaded(new PluginEventArgs(plugin)); - - return plugin; } + + Plugin plugin = new Plugin(pluginInfo, directory); + OnPluginLoading(new PluginEventArgs(plugin)); + + // Load the entity and fall back on creating a new one + plugin.Entity = _pluginRepository.GetPluginByGuid(pluginInfo.Guid) ?? new PluginEntity {Id = plugin.Guid, IsEnabled = true}; + + // Locate the main assembly entry + string? mainFile = plugin.ResolveRelativePath(plugin.Info.Main); + if (!File.Exists(mainFile)) + throw new ArtemisPluginException(plugin, "Couldn't find the plugins main entry at " + mainFile); + + // Load the plugin, all types implementing Plugin and register them with DI + plugin.PluginLoader = PluginLoader.CreateFromAssemblyFile(mainFile!, configure => + { + configure.IsUnloadable = true; + configure.PreferSharedTypes = true; + }); + + try + { + plugin.Assembly = plugin.PluginLoader.LoadDefaultAssembly(); + } + catch (Exception e) + { + throw new ArtemisPluginException(plugin, "Failed to load the plugins assembly", e); + } + + List bootstrappers = plugin.Assembly.GetTypes().Where(t => typeof(IPluginBootstrapper).IsAssignableFrom(t)).ToList(); + if (bootstrappers.Count > 1) + _logger.Warning($"{plugin} has more than one bootstrapper, only initializing {bootstrappers.First().FullName}"); + if (bootstrappers.Any()) + plugin.Bootstrapper = (IPluginBootstrapper?) Activator.CreateInstance(bootstrappers.First()); + + lock (_plugins) + { + _plugins.Add(plugin); + } + + OnPluginLoaded(new PluginEventArgs(plugin)); + return plugin; } public void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock) @@ -342,22 +350,22 @@ namespace Artemis.Core.Services public void UnloadPlugin(Plugin plugin) { + try + { + DisablePlugin(plugin, false); + } + catch (Exception e) + { + _logger.Warning(new ArtemisPluginException(plugin, "Exception during DisablePlugin call for UnloadPlugin", e), "Failed to unload plugin"); + } + finally + { + OnPluginDisabled(new PluginEventArgs(plugin)); + } + + plugin.Dispose(); lock (_plugins) { - try - { - DisablePlugin(plugin, false); - } - catch (Exception e) - { - _logger.Warning(new ArtemisPluginException(plugin, "Exception during DisablePlugin call for UnloadPlugin", e), "Failed to unload plugin"); - } - finally - { - OnPluginDisabled(new PluginEventArgs(plugin)); - } - - plugin.Dispose(); _plugins.Remove(plugin); } } @@ -379,7 +387,7 @@ namespace Artemis.Core.Services plugin.Kernel.Dispose(); plugin.Kernel = null; - + GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); @@ -401,70 +409,65 @@ namespace Artemis.Core.Services { _logger.Debug("Enabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); - lock (_plugins) + + OnPluginFeatureEnabling(new PluginFeatureEventArgs(pluginFeature)); + try { - OnPluginFeatureEnabling(new PluginFeatureEventArgs(pluginFeature)); - try + pluginFeature.SetEnabled(true, isAutoEnable); + if (saveState) + pluginFeature.Entity.IsEnabled = true; + } + catch (Exception e) + { + _logger.Warning( + new ArtemisPluginException(pluginFeature.Plugin, $"Exception during SetEnabled(true) on {pluginFeature}", e), + "Failed to enable plugin" + ); + throw; + } + finally + { + // On an auto-enable, ensure PluginInfo.Enabled is true even if enable failed, that way a failure on auto-enable does + // not affect the user's settings + if (saveState) { - pluginFeature.SetEnabled(true, isAutoEnable); - if (saveState) + if (isAutoEnable) pluginFeature.Entity.IsEnabled = true; - } - catch (Exception e) - { - _logger.Warning( - new ArtemisPluginException(pluginFeature.Plugin, $"Exception during SetEnabled(true) on {pluginFeature}", e), - "Failed to enable plugin" - ); - throw; - } - finally - { - // On an auto-enable, ensure PluginInfo.Enabled is true even if enable failed, that way a failure on auto-enable does - // not affect the user's settings - if (saveState) - { - if (isAutoEnable) - pluginFeature.Entity.IsEnabled = true; - SavePlugin(pluginFeature.Plugin); - } + SavePlugin(pluginFeature.Plugin); + } - if (pluginFeature.IsEnabled) - { - _logger.Debug("Successfully enabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); - OnPluginFeatureEnabled(new PluginFeatureEventArgs(pluginFeature)); - } - else - { - OnPluginFeatureEnableFailed(new PluginFeatureEventArgs(pluginFeature)); - } + if (pluginFeature.IsEnabled) + { + _logger.Debug("Successfully enabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); + OnPluginFeatureEnabled(new PluginFeatureEventArgs(pluginFeature)); + } + else + { + OnPluginFeatureEnableFailed(new PluginFeatureEventArgs(pluginFeature)); } } } public void DisablePluginFeature(PluginFeature pluginFeature, bool saveState) { - lock (_plugins) + try { - try + _logger.Debug("Disabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); + pluginFeature.SetEnabled(false); + } + finally + { + if (saveState) { - _logger.Debug("Disabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); - pluginFeature.SetEnabled(false); + pluginFeature.Entity.IsEnabled = false; + SavePlugin(pluginFeature.Plugin); } - finally - { - if (saveState) - { - pluginFeature.Entity.IsEnabled = false; - SavePlugin(pluginFeature.Plugin); - } - if (!pluginFeature.IsEnabled) - { - _logger.Debug("Successfully disabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); - OnPluginFeatureDisabled(new PluginFeatureEventArgs(pluginFeature)); - } + if (!pluginFeature.IsEnabled) + { + _logger.Debug("Successfully disabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); + OnPluginFeatureDisabled(new PluginFeatureEventArgs(pluginFeature)); } } }