1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-01-02 10:43:31 +00:00

Profiles - Tweaked render pipeline, improving render performance by ~40%

This commit is contained in:
SpoinkyNL 2020-11-15 17:38:06 +01:00
parent 7735edea1e
commit 991c5fd955
11 changed files with 231 additions and 207 deletions

View File

@ -29,7 +29,6 @@ namespace Artemis.Core
Profile = Parent.Profile; Profile = Parent.Profile;
Name = name; Name = name;
Enabled = true; Enabled = true;
Renderer = new Renderer();
_layerEffects = new List<BaseLayerEffect>(); _layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>(); _expandedPropertyGroups = new List<string>();
@ -47,7 +46,6 @@ namespace Artemis.Core
Name = folderEntity.Name; Name = folderEntity.Name;
Enabled = folderEntity.Enabled; Enabled = folderEntity.Enabled;
Order = folderEntity.Order; Order = folderEntity.Order;
Renderer = new Renderer();
_layerEffects = new List<BaseLayerEffect>(); _layerEffects = new List<BaseLayerEffect>();
_expandedPropertyGroups = new List<string>(); _expandedPropertyGroups = new List<string>();
@ -69,8 +67,6 @@ namespace Artemis.Core
internal override RenderElementEntity RenderElementEntity => FolderEntity; internal override RenderElementEntity RenderElementEntity => FolderEntity;
internal Renderer Renderer { get; }
/// <inheritdoc /> /// <inheritdoc />
public override List<ILayerProperty> GetAllLayerProperties() public override List<ILayerProperty> GetAllLayerProperties()
{ {
@ -133,7 +129,7 @@ namespace Artemis.Core
/// <returns>The newly created copy</returns> /// <returns>The newly created copy</returns>
public Folder CreateCopy() public Folder CreateCopy()
{ {
JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }; JsonSerializerSettings settings = new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.All};
FolderEntity entityCopy = JsonConvert.DeserializeObject<FolderEntity>(JsonConvert.SerializeObject(FolderEntity, settings), settings)!; FolderEntity entityCopy = JsonConvert.DeserializeObject<FolderEntity>(JsonConvert.SerializeObject(FolderEntity, settings), settings)!;
entityCopy.Id = Guid.NewGuid(); entityCopy.Id = Guid.NewGuid();
entityCopy.Name += " - Copy"; entityCopy.Name += " - Copy";
@ -233,7 +229,6 @@ namespace Artemis.Core
lock (Timeline) lock (Timeline)
{ {
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled))
{ {
baseLayerEffect.BaseProperties?.Update(Timeline); baseLayerEffect.BaseProperties?.Update(Timeline);
@ -244,18 +239,18 @@ namespace Artemis.Core
{ {
canvas.Save(); canvas.Save();
Renderer.Open(Path, Parent as Folder); Renderer.Open(Path, Parent as Folder);
if (Renderer.Canvas == null || Renderer.Path == null || Renderer.Paint == null) if (Renderer.Canvas == null || Renderer.Path == null || Renderer.Paint == null)
throw new ArtemisCoreException("Failed to open folder render context"); throw new ArtemisCoreException("Failed to open folder render context");
SKRect rendererBounds = Renderer.Path.Bounds;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) 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 required, apply the opacity override of the module to the root folder
if (IsRootFolder && Profile.Module.OpacityOverride < 1) if (IsRootFolder && Profile.Module.OpacityOverride < 1)
{ {
double multiplier = Easings.SineEaseInOut(Profile.Module.OpacityOverride); 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 // No point rendering if the alpha was set to zero by one of the effects
@ -267,7 +262,7 @@ namespace Artemis.Core
Children[index].Render(Renderer.Canvas); Children[index].Render(Renderer.Canvas);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => e.Enabled)) 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); canvas.DrawBitmap(Renderer.Bitmap, Renderer.TargetLocation, Renderer.Paint);
} }

View File

@ -39,7 +39,6 @@ namespace Artemis.Core
Enabled = true; Enabled = true;
General = new LayerGeneralProperties(); General = new LayerGeneralProperties();
Transform = new LayerTransformProperties(); Transform = new LayerTransformProperties();
Renderer = new Renderer();
_layerEffects = new List<BaseLayerEffect>(); _layerEffects = new List<BaseLayerEffect>();
_leds = new List<ArtemisLed>(); _leds = new List<ArtemisLed>();
@ -58,7 +57,6 @@ namespace Artemis.Core
Parent = parent; Parent = parent;
General = new LayerGeneralProperties(); General = new LayerGeneralProperties();
Transform = new LayerTransformProperties(); Transform = new LayerTransformProperties();
Renderer = new Renderer();
_layerEffects = new List<BaseLayerEffect>(); _layerEffects = new List<BaseLayerEffect>();
_leds = new List<ArtemisLed>(); _leds = new List<ArtemisLed>();
@ -114,8 +112,6 @@ namespace Artemis.Core
internal override RenderElementEntity RenderElementEntity => LayerEntity; internal override RenderElementEntity RenderElementEntity => LayerEntity;
internal Renderer Renderer { get; }
/// <summary> /// <summary>
/// Creates a deep copy of the layer /// Creates a deep copy of the layer
/// </summary> /// </summary>
@ -349,11 +345,11 @@ namespace Artemis.Core
Renderer.Paint.BlendMode = General.BlendMode.CurrentValue; Renderer.Paint.BlendMode = General.BlendMode.CurrentValue;
Renderer.Paint.Color = new SKColor(0, 0, 0, (byte) (Transform.Opacity.CurrentValue * 2.55f)); 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(); 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) 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 // If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off
Renderer.Canvas.ClipPath(renderPath); Renderer.Canvas.ClipPath(renderPath);
DelegateRendering(renderPath); DelegateRendering(renderPath.Bounds);
} }
else if (General.TransformMode.CurrentValue == LayerTransformMode.Clip) 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 // If a brush is a bad boy and tries to color outside the lines, ensure that its clipped off
Renderer.Canvas.ClipPath(renderPath); Renderer.Canvas.ClipPath(renderPath);
DelegateRendering(Renderer.Path); DelegateRendering(Renderer.Path.Bounds);
} }
canvas.DrawBitmap(Renderer.Bitmap, Renderer.TargetLocation, Renderer.Paint); 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)) 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)) 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() internal void CalculateRenderProperties()

View File

@ -16,6 +16,7 @@ namespace Artemis.Core
protected RenderProfileElement() protected RenderProfileElement()
{ {
Timeline = new Timeline(); Timeline = new Timeline();
Renderer = new Renderer();
LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded; LayerEffectStore.LayerEffectAdded += LayerEffectStoreOnLayerEffectAdded;
LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved; LayerEffectStore.LayerEffectRemoved += LayerEffectStoreOnLayerEffectRemoved;
@ -102,9 +103,23 @@ namespace Artemis.Core
#region Properties #region Properties
private ProfileElement _parent;
private SKPath _path; private SKPath _path;
internal abstract RenderElementEntity RenderElementEntity { get; } internal abstract RenderElementEntity RenderElementEntity { get; }
/// <summary>
/// Gets the parent of this element
/// </summary>
public new ProfileElement Parent
{
get => _parent;
internal set
{
SetAndNotify(ref _parent, value);
Renderer.Invalidate();
}
}
/// <summary> /// <summary>
/// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is /// Gets the path containing all the LEDs this entity is applied to, any rendering outside the entity Path is
/// clipped. /// clipped.
@ -118,6 +133,7 @@ namespace Artemis.Core
// I can't really be sure about the performance impact of calling Bounds often but // 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 // SkiaSharp calls SkiaApi.sk_path_get_bounds (Handle, &rect); which sounds expensive
Bounds = value?.Bounds ?? SKRect.Empty; Bounds = value?.Bounds ?? SKRect.Empty;
Renderer.Invalidate();
} }
} }
@ -130,6 +146,8 @@ namespace Artemis.Core
private set => SetAndNotify(ref _bounds, value); private set => SetAndNotify(ref _bounds, value);
} }
internal Renderer Renderer { get; }
#region Property group expansion #region Property group expansion
protected List<string> _expandedPropertyGroups; protected List<string> _expandedPropertyGroups;

View File

@ -5,12 +5,12 @@ namespace Artemis.Core
{ {
internal class Renderer : IDisposable internal class Renderer : IDisposable
{ {
private bool _valid;
private bool _disposed; private bool _disposed;
public SKBitmap? Bitmap { get; private set; } public SKBitmap? Bitmap { get; private set; }
public SKCanvas? Canvas { get; private set; } public SKCanvas? Canvas { get; private set; }
public SKPaint? Paint { get; private set; } public SKPaint? Paint { get; private set; }
public SKPath? Path { get; private set; } public SKPath? Path { get; private set; }
public SKPath? ClipPath { get; private set; }
public SKPoint TargetLocation { get; private set; } public SKPoint TargetLocation { get; private set; }
public bool IsOpen { get; private set; } public bool IsOpen { get; private set; }
@ -26,27 +26,30 @@ namespace Artemis.Core
if (IsOpen) if (IsOpen)
throw new ArtemisCoreException("Cannot open render context because it is already open"); throw new ArtemisCoreException("Cannot open render context because it is already open");
int width = (int) path.Bounds.Width; if (!_valid)
int height = (int) path.Bounds.Height; {
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); 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(); Paint = new SKPaint();
Path.Transform(SKMatrix.MakeTranslation(Path.Bounds.Left * -1, Path.Bounds.Top * -1));
Canvas.Clear(); Canvas.Clear();
Canvas.Save();
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));
IsOpen = true; IsOpen = true;
} }
@ -56,25 +59,34 @@ namespace Artemis.Core
if (_disposed) if (_disposed)
throw new ObjectDisposedException("Renderer"); throw new ObjectDisposedException("Renderer");
Canvas?.Dispose(); Canvas.Restore();
Paint?.Dispose(); Paint?.Dispose();
Path?.Dispose();
ClipPath?.Dispose();
Canvas = null;
Paint = null; Paint = null;
Path = null;
ClipPath = null;
IsOpen = false; IsOpen = false;
} }
public void Invalidate()
{
if (_disposed)
throw new ObjectDisposedException("Renderer");
_valid = false;
}
public void Dispose() public void Dispose()
{ {
if (IsOpen) if (IsOpen)
Close(); Close();
Canvas?.Dispose();
Paint?.Dispose();
Path?.Dispose();
Bitmap?.Dispose(); Bitmap?.Dispose();
Canvas = null;
Paint = null;
Path = null;
Bitmap = null; Bitmap = null;
_disposed = true; _disposed = true;

View File

@ -95,7 +95,7 @@ namespace Artemis.Core.LayerBrushes
// but LayerBrush<T> and RgbNetLayerBrush<T> outside the core // but LayerBrush<T> and RgbNetLayerBrush<T> outside the core
internal abstract void Initialize(); 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) internal virtual void Dispose(bool disposing)
{ {

View File

@ -22,13 +22,13 @@ namespace Artemis.Core.LayerBrushes
/// <para>Called during rendering or layer preview, in the order configured on the layer</para> /// <para>Called during rendering or layer preview, in the order configured on the layer</para>
/// </summary> /// </summary>
/// <param name="canvas">The layer canvas</param> /// <param name="canvas">The layer canvas</param>
/// <param name="path">The path to be filled, represents the shape</param> /// <param name="bounds">The area to be filled, covers the shape</param>
/// <param name="paint">The paint to be used to fill the shape</param> /// <param name="paint">The paint to be used to fill the shape</param>
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() internal override void Initialize()

View File

@ -28,7 +28,7 @@ namespace Artemis.Core.LayerBrushes
/// <returns>The color the LED will receive</returns> /// <returns>The color the LED will receive</returns>
public abstract SKColor GetColor(ArtemisLed led, SKPoint renderPoint); 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 // 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) if (Layer.General.TransformMode.CurrentValue == LayerTransformMode.Normal && SupportsTransformation)

View File

@ -68,7 +68,7 @@ namespace Artemis.Core.LayerBrushes
} }
// Not used in this effect type // 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"); throw new NotImplementedException("RGB.NET layer effectes do not implement InternalRender");
} }

View File

@ -132,7 +132,7 @@ namespace Artemis.Core.LayerEffects
/// <param name="canvas">The canvas used to render the frame</param> /// <param name="canvas">The canvas used to render the frame</param>
/// <param name="renderBounds">The bounds this layer/folder will render in</param> /// <param name="renderBounds">The bounds this layer/folder will render in</param>
/// <param name="paint">The paint this layer/folder will use to render</param> /// <param name="paint">The paint this layer/folder will use to render</param>
public abstract void PreProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint); public abstract void PreProcess(SKCanvas canvas, SKRect renderBounds, SKPaint paint);
/// <summary> /// <summary>
/// Called after the layer of folder has been rendered /// Called after the layer of folder has been rendered
@ -140,7 +140,7 @@ namespace Artemis.Core.LayerEffects
/// <param name="canvas">The canvas used to render the frame</param> /// <param name="canvas">The canvas used to render the frame</param>
/// <param name="renderBounds">The bounds this layer/folder rendered in</param> /// <param name="renderBounds">The bounds this layer/folder rendered in</param>
/// <param name="paint">The paint this layer/folder used to render</param> /// <param name="paint">The paint this layer/folder used to render</param>
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 // Not only is this needed to initialize properties on the layer effects, it also prevents implementing anything
// but LayerEffect<T> outside the core // but LayerEffect<T> outside the core

View File

@ -40,12 +40,12 @@ namespace Artemis.Core.LayerEffects.Placeholder
} }
/// <inheritdoc /> /// <inheritdoc />
public override void PreProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint) public override void PreProcess(SKCanvas canvas, SKRect bounds, SKPaint paint)
{ {
} }
/// <inheritdoc /> /// <inheritdoc />
public override void PostProcess(SKCanvas canvas, SKPath renderBounds, SKPaint paint) public override void PostProcess(SKCanvas canvas, SKRect bounds, SKPaint paint)
{ {
} }

View File

@ -124,17 +124,26 @@ namespace Artemis.Core.Services
public List<Plugin> GetAllPlugins() public List<Plugin> GetAllPlugins()
{ {
return new List<Plugin>(_plugins); lock (_plugins)
{
return new List<Plugin>(_plugins);
}
} }
public List<T> GetFeaturesOfType<T>() where T : PluginFeature public List<T> GetFeaturesOfType<T>() where T : PluginFeature
{ {
return _plugins.Where(p => p.IsEnabled).SelectMany(p => p.Features.Where(i => i.IsEnabled && i is T)).Cast<T>().ToList(); lock (_plugins)
{
return _plugins.Where(p => p.IsEnabled).SelectMany(p => p.Features.Where(i => i.IsEnabled && i is T)).Cast<T>().ToList();
}
} }
public Plugin? GetPluginByAssembly(Assembly assembly) 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 // TODO: move to a more appropriate service
@ -171,105 +180,104 @@ namespace Artemis.Core.Services
if (LoadingPlugins) if (LoadingPlugins)
throw new ArtemisCoreException("Cannot load plugins while a previous load hasn't been completed yet."); throw new ArtemisCoreException("Cannot load plugins while a previous load hasn't been completed yet.");
lock (_plugins) 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())
{ {
LoadingPlugins = true; try
// 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 Plugin plugin = LoadPlugin(subDirectory);
{ if (plugin.Entity.IsEnabled)
Plugin plugin = LoadPlugin(subDirectory); EnablePlugin(plugin, false, ignorePluginLock);
if (plugin.Entity.IsEnabled) }
EnablePlugin(plugin, false, ignorePluginLock); catch (Exception e)
} {
catch (Exception e) _logger.Warning(new ArtemisPluginException("Failed to load plugin", e), "Plugin exception");
{
_logger.Warning(new ArtemisPluginException("Failed to load plugin", e), "Plugin exception");
}
} }
LoadingPlugins = false;
} }
LoadingPlugins = false;
} }
public void UnloadPlugins() public void UnloadPlugins()
{ {
// Unload all plugins
while (_plugins.Count > 0)
UnloadPlugin(_plugins[0]);
lock (_plugins) lock (_plugins)
{ {
// Unload all plugins
while (_plugins.Count > 0)
UnloadPlugin(_plugins[0]);
_plugins.Clear(); _plugins.Clear();
} }
} }
public Plugin LoadPlugin(DirectoryInfo directory) 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<PluginInfo>(File.ReadAllText(metadataFile));
if (pluginInfo.Guid == Constants.CorePluginInfo.Guid)
throw new ArtemisPluginException($"Plugin cannot use reserved GUID {pluginInfo.Guid}");
lock (_plugins) 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<PluginInfo>(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 // Ensure the plugin is not already loaded
if (_plugins.Any(p => p.Guid == pluginInfo.Guid)) if (_plugins.Any(p => p.Guid == pluginInfo.Guid))
throw new ArtemisCoreException("Cannot load a plugin that is already loaded"); 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<Type> 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<Type> 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) public void EnablePlugin(Plugin plugin, bool saveState, bool ignorePluginLock)
@ -342,22 +350,22 @@ namespace Artemis.Core.Services
public void UnloadPlugin(Plugin plugin) 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) 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); _plugins.Remove(plugin);
} }
} }
@ -401,70 +409,65 @@ namespace Artemis.Core.Services
{ {
_logger.Debug("Enabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); _logger.Debug("Enabling plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
lock (_plugins)
OnPluginFeatureEnabling(new PluginFeatureEventArgs(pluginFeature));
try
{ {
OnPluginFeatureEnabling(new PluginFeatureEventArgs(pluginFeature)); pluginFeature.SetEnabled(true, isAutoEnable);
try 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 (isAutoEnable)
if (saveState)
pluginFeature.Entity.IsEnabled = true; 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) if (pluginFeature.IsEnabled)
{ {
_logger.Debug("Successfully enabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); _logger.Debug("Successfully enabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
OnPluginFeatureEnabled(new PluginFeatureEventArgs(pluginFeature)); OnPluginFeatureEnabled(new PluginFeatureEventArgs(pluginFeature));
} }
else else
{ {
OnPluginFeatureEnableFailed(new PluginFeatureEventArgs(pluginFeature)); OnPluginFeatureEnableFailed(new PluginFeatureEventArgs(pluginFeature));
}
} }
} }
} }
public void DisablePluginFeature(PluginFeature pluginFeature, bool saveState) 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.Entity.IsEnabled = false;
pluginFeature.SetEnabled(false); SavePlugin(pluginFeature.Plugin);
} }
finally
{
if (saveState)
{
pluginFeature.Entity.IsEnabled = false;
SavePlugin(pluginFeature.Plugin);
}
if (!pluginFeature.IsEnabled) if (!pluginFeature.IsEnabled)
{ {
_logger.Debug("Successfully disabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin); _logger.Debug("Successfully disabled plugin feature {feature} - {plugin}", pluginFeature, pluginFeature.Plugin);
OnPluginFeatureDisabled(new PluginFeatureEventArgs(pluginFeature)); OnPluginFeatureDisabled(new PluginFeatureEventArgs(pluginFeature));
}
} }
} }
} }