Merge branch 'development' into VisualScripting

This commit is contained in:
Robert committed 2021-08-23 20:42:36 +02:00
commit 3874078f16
35 files changed
+790 -506

No files matched your search

+3 -3
View File
@@ -48,10 +48,10 @@
<PackageReference Include="Ninject.Extensions.ChildKernel" Version="3.3.0" />
<PackageReference Include="Ninject.Extensions.Conventions" Version="3.3.0" />
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.Debug" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="SkiaSharp" Version="2.80.2" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SkiaSharp" Version="2.80.3" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="5.0.0" />
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
+1 -1
View File
@@ -27,7 +27,7 @@ namespace Artemis.Core
/// <summary>
/// The full path to the Artemis data folder
/// </summary>
public static readonly string DataFolder = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\Artemis\\";
public static readonly string DataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Artemis");
/// <summary>
/// The plugin info used by core components of Artemis
@@ -169,29 +169,29 @@ namespace Artemis.Core
internal static ArtemisLayout? GetDefaultLayout(ArtemisDevice device)
{
string layoutFolder = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts\\Artemis");
string layoutFolder = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts", "Artemis");
if (device.DeviceType == RGBDeviceType.Keyboard)
{
// XL layout is defined by its programmable macro keys
if (device.Leds.Any(l => l.RgbLed.Id >= LedId.Keyboard_Programmable1 && l.RgbLed.Id <= LedId.Keyboard_Programmable32))
{
if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
return new ArtemisLayout(layoutFolder + "\\Keyboard\\Artemis XL keyboard-ANSI.xml", LayoutSource.Default);
return new ArtemisLayout(layoutFolder + "\\Keyboard\\Artemis XL keyboard-ISO.xml", LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder, "Keyboard", "Artemis XL keyboard-ANSI.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder, "Keyboard", "Artemis XL keyboard-ISO.xml"), LayoutSource.Default);
}
// L layout is defined by its numpad
if (device.Leds.Any(l => l.RgbLed.Id >= LedId.Keyboard_NumLock && l.RgbLed.Id <= LedId.Keyboard_NumPeriodAndDelete))
{
if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
return new ArtemisLayout(layoutFolder + "\\Keyboard\\Artemis L keyboard-ANSI.xml", LayoutSource.Default);
return new ArtemisLayout(layoutFolder + "\\Keyboard\\Artemis L keyboard-ISO.xml", LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard","Artemis L keyboard-ANSI.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard","Artemis L keyboard-ISO.xml"), LayoutSource.Default);
}
// No numpad will result in TKL
if (device.PhysicalLayout == KeyboardLayoutType.ANSI)
return new ArtemisLayout(layoutFolder + "\\Keyboard\\Artemis TKL keyboard-ANSI.xml", LayoutSource.Default);
return new ArtemisLayout(layoutFolder + "\\Keyboard\\Artemis TKL keyboard-ISO.xml", LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard","Artemis TKL keyboard-ANSI.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder + "Keyboard","Artemis TKL keyboard-ISO.xml"), LayoutSource.Default);
}
// if (device.DeviceType == RGBDeviceType.Mouse)
@@ -199,21 +199,21 @@ namespace Artemis.Core
// if (device.Leds.Count == 1)
// {
// if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo))
// return new ArtemisLayout(layoutFolder + "\\Mouse\\1 LED mouse logo.xml", LayoutSource.Default);
// return new ArtemisLayout(layoutFolder + "\\Mouse\\1 LED mouse.xml", LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "1 LED mouse logo.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "1 LED mouse.xml"), LayoutSource.Default);
// }
// if (device.Leds.Any(l => l.RgbLed.Id == LedId.Logo))
// return new ArtemisLayout(layoutFolder + "\\Mouse\\4 LED mouse logo.xml", LayoutSource.Default);
// return new ArtemisLayout(layoutFolder + "\\Mouse\\4 LED mouse.xml", LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "4 LED mouse logo.xml"), LayoutSource.Default);
// return new ArtemisLayout(Path.Combine(layoutFolder + "Mouse", "4 LED mouse.xml"), LayoutSource.Default);
// }
if (device.DeviceType == RGBDeviceType.Headset)
{
if (device.Leds.Count == 1)
return new ArtemisLayout(layoutFolder + "\\Headset\\Artemis 1 LED headset.xml", LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 1 LED headset.xml"), LayoutSource.Default);
if (device.Leds.Count == 2)
return new ArtemisLayout(layoutFolder + "\\Headset\\Artemis 2 LED headset.xml", LayoutSource.Default);
return new ArtemisLayout(layoutFolder + "\\Headset\\Artemis 4 LED headset.xml", LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 2 LED headset.xml"), LayoutSource.Default);
return new ArtemisLayout(Path.Combine(layoutFolder + "Headset", "Artemis 4 LED headset.xml"), LayoutSource.Default);
}
return null;
@@ -61,7 +61,6 @@ namespace Artemis.Core
if (LayoutCustomLedData.LogicalLayouts == null || !LayoutCustomLedData.LogicalLayouts.Any())
return;
Uri layoutDirectory = new(Path.GetDirectoryName(DeviceLayout.FilePath)! + "\\", UriKind.Absolute);
// Prefer a matching layout or else a default layout (that has no name)
LayoutCustomLedDataLogicalLayout logicalLayout = LayoutCustomLedData.LogicalLayouts
.OrderBy(l => l.Name == artemisDevice.LogicalLayout)
@@ -69,7 +68,7 @@ namespace Artemis.Core
.First();
LogicalName = logicalLayout.Name;
Image = new Uri(layoutDirectory, logicalLayout.Image);
Image = new Uri(Path.Combine(Path.GetDirectoryName(DeviceLayout.FilePath)!, logicalLayout.Image!), UriKind.Absolute);
}
}
}
+2 -1
View File
@@ -1,4 +1,5 @@
using System;
using System.IO;
using Ninject.Activation;
using Serilog;
using Serilog.Core;
@@ -12,7 +13,7 @@ namespace Artemis.Core.Ninject
private static readonly ILogger Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.File(Constants.DataFolder + "logs/Artemis log-.log",
.WriteTo.File(Path.Combine(Constants.DataFolder, "logs", "Artemis log-.log"),
rollingInterval: RollingInterval.Day,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}")
.WriteTo.Console()
+15 -27
View File
@@ -11,8 +11,8 @@ namespace Artemis.Core
public abstract class PluginFeature : CorePropertyChanged, IDisposable
{
private bool _isEnabled;
private Exception? _loadException;
/// <summary>
/// Gets the plugin feature info related to this feature
/// </summary>
@@ -37,14 +37,7 @@ namespace Artemis.Core
internal set => SetAndNotify(ref _isEnabled, value);
}
/// <summary>
/// Gets the exception thrown while loading
/// </summary>
public Exception? LoadException
{
get => _loadException;
internal set => SetAndNotify(ref _loadException, value);
}
internal int AutoEnableAttempts { get; set; }
/// <summary>
/// Gets the identifier of this plugin feature
@@ -82,7 +75,8 @@ namespace Artemis.Core
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing) InternalDisable();
if (disposing)
SetEnabled(false);
}
/// <summary>
@@ -111,16 +105,6 @@ namespace Artemis.Core
Profiler.StopMeasurement("Update");
}
internal void StartRenderMeasure()
{
Profiler.StartMeasurement("Render");
}
internal void StopRenderMeasure()
{
Profiler.StopMeasurement("Render");
}
internal void SetEnabled(bool enable, bool isAutoEnable = false)
{
if (enable == IsEnabled)
@@ -146,13 +130,16 @@ namespace Artemis.Core
try
{
if (isAutoEnable)
AutoEnableAttempts++;
if (isAutoEnable && GetLockFileCreated())
{
// Don't wrap existing lock exceptions, simply rethrow them
if (LoadException is ArtemisPluginLockException)
throw LoadException;
if (Info.LoadException is ArtemisPluginLockException)
throw Info.LoadException;
throw new ArtemisPluginLockException(LoadException);
throw new ArtemisPluginLockException(Info.LoadException);
}
CreateLockFile();
@@ -165,21 +152,22 @@ namespace Artemis.Core
if (!enableTask.Wait(TimeSpan.FromSeconds(15)))
throw new ArtemisPluginException(Plugin, "Plugin load timeout");
LoadException = null;
Info.LoadException = null;
AutoEnableAttempts = 0;
OnEnabled();
}
// If enable failed, put it back in a disabled state
catch (Exception e)
{
IsEnabled = false;
LoadException = e;
Info.LoadException = e;
throw;
}
finally
{
// Clean up the lock file unless the failure was due to the lock file
// After all, we failed but not miserably :)
if (!(LoadException is ArtemisPluginLockException))
if (Info.LoadException is not ArtemisPluginLockException)
DeleteLockFile();
}
}
@@ -17,6 +17,7 @@ namespace Artemis.Core
[JsonObject(MemberSerialization.OptIn)]
public class PluginFeatureInfo : CorePropertyChanged, IPrerequisitesSubject
{
private Exception? _loadException;
private string? _description;
private string? _icon;
private PluginFeature? _instance;
@@ -80,6 +81,15 @@ namespace Artemis.Core
/// </summary>
public Type FeatureType { get; }
/// <summary>
/// Gets the exception thrown while loading
/// </summary>
public Exception? LoadException
{
get => _loadException;
internal set => SetAndNotify(ref _loadException, value);
}
/// <summary>
/// The name of the plugin
/// </summary>
@@ -8,21 +8,6 @@ namespace Artemis.Core.Services
/// <inheritdoc />
internal class ColorQuantizerService : IColorQuantizerService
{
private static float GetComparisonValue(float sat, float targetSaturation, float luma, float targetLuma)
{
static float InvertDiff(float value, float target)
{
return 1 - Math.Abs(value - target);
}
const float totalWeight = weightSaturation + weightLuma;
float totalValue = InvertDiff(sat, targetSaturation) * weightSaturation +
InvertDiff(luma, targetLuma) * weightLuma;
return totalValue / totalWeight;
}
/// <inheritdoc />
public SKColor[] Quantize(IEnumerable<SKColor> colors, int amount)
{
@@ -48,29 +33,12 @@ namespace Artemis.Core.Services
/// <inheritdoc />
public SKColor FindColorVariation(IEnumerable<SKColor> colors, ColorType type, bool ignoreLimits = false)
{
(float targetLuma, float minLuma, float maxLuma, float targetSaturation, float minSaturation, float maxSaturation) = type switch
{
ColorType.Vibrant => (targetNormalLuma, minNormalLuma, maxNormalLuma, targetVibrantSaturation, minVibrantSaturation, 1f),
ColorType.LightVibrant => (targetLightLuma, minLightLuma, 1f, targetVibrantSaturation, minVibrantSaturation, 1f),
ColorType.DarkVibrant => (targetDarkLuma, 0f, maxDarkLuma, targetVibrantSaturation, minVibrantSaturation, 1f),
ColorType.Muted => (targetNormalLuma, minNormalLuma, maxNormalLuma, targetMutesSaturation, 0, maxMutesSaturation),
ColorType.LightMuted => (targetLightLuma, minLightLuma, 1f, targetMutesSaturation, 0, maxMutesSaturation),
ColorType.DarkMuted => (targetDarkLuma, 0, maxDarkLuma, targetMutesSaturation, 0, maxMutesSaturation),
_ => (0.5f, 0f, 1f, 0.5f, 0f, 1f)
};
float bestColorScore = float.MinValue;
float bestColorScore = 0;
SKColor bestColor = SKColor.Empty;
foreach (SKColor clr in colors)
{
clr.ToHsl(out float _, out float sat, out float luma);
sat /= 100f;
luma /= 100f;
if (!ignoreLimits && (sat <= minSaturation || sat >= maxSaturation || luma <= minLuma || luma >= maxLuma))
continue;
float score = GetComparisonValue(sat, targetSaturation, luma, targetLuma);
float score = GetScore(clr, type, ignoreLimits);
if (score > bestColorScore)
{
bestColorScore = score;
@@ -81,6 +49,82 @@ namespace Artemis.Core.Services
return bestColor;
}
/// <inheritdoc />
public ColorSwatch FindAllColorVariations(IEnumerable<SKColor> colors, bool ignoreLimits = false)
{
SKColor bestVibrantColor = SKColor.Empty;
SKColor bestLightVibrantColor = SKColor.Empty;
SKColor bestDarkVibrantColor = SKColor.Empty;
SKColor bestMutedColor = SKColor.Empty;
SKColor bestLightMutedColor = SKColor.Empty;
SKColor bestDarkMutedColor = SKColor.Empty;
float bestVibrantScore = 0;
float bestLightVibrantScore = 0;
float bestDarkVibrantScore = 0;
float bestMutedScore = 0;
float bestLightMutedScore = 0;
float bestDarkMutedScore = 0;
//ugly but at least we only loop through the enumerable once ¯\_(ツ)_/¯
foreach (var color in colors)
{
static void SetIfBetterScore(ref float bestScore, ref SKColor bestColor, SKColor newColor, ColorType type, bool ignoreLimits)
{
float newScore = GetScore(newColor, type, ignoreLimits);
if (newScore > bestScore)
{
bestScore = newScore;
bestColor = newColor;
}
}
SetIfBetterScore(ref bestVibrantScore, ref bestVibrantColor, color, ColorType.Vibrant, ignoreLimits);
SetIfBetterScore(ref bestLightVibrantScore, ref bestLightVibrantColor, color, ColorType.LightVibrant, ignoreLimits);
SetIfBetterScore(ref bestDarkVibrantScore, ref bestDarkVibrantColor, color, ColorType.DarkVibrant, ignoreLimits);
SetIfBetterScore(ref bestMutedScore, ref bestMutedColor, color, ColorType.Muted, ignoreLimits);
SetIfBetterScore(ref bestLightMutedScore, ref bestLightMutedColor, color, ColorType.LightMuted, ignoreLimits);
SetIfBetterScore(ref bestDarkMutedScore, ref bestDarkMutedColor, color, ColorType.DarkMuted, ignoreLimits);
}
return new()
{
Vibrant = bestVibrantColor,
LightVibrant = bestLightVibrantColor,
DarkVibrant = bestDarkVibrantColor,
Muted = bestMutedColor,
LightMuted = bestLightMutedColor,
DarkMuted = bestDarkMutedColor,
};
}
private static float GetScore(SKColor color, ColorType type, bool ignoreLimits = false)
{
static float InvertDiff(float value, float target)
{
return 1 - Math.Abs(value - target);
}
color.ToHsl(out float _, out float saturation, out float luma);
saturation /= 100f;
luma /= 100f;
if (!ignoreLimits &&
(saturation <= GetMinSaturation(type) || saturation >= GetMaxSaturation(type)
|| luma <= GetMinLuma(type) || luma >= GetMaxLuma(type)))
{
//if either saturation or luma falls outside the min-max, return the
//lowest score possible unless we're ignoring these limits.
return float.MinValue;
}
float totalValue = (InvertDiff(saturation, GetTargetSaturation(type)) * weightSaturation) +
(InvertDiff(luma, GetTargetLuma(type)) * weightLuma);
const float totalWeight = weightSaturation + weightLuma;
return totalValue / totalWeight;
}
#region Constants
private const float targetDarkLuma = 0.26f;
@@ -97,6 +141,72 @@ namespace Artemis.Core.Services
private const float weightSaturation = 3f;
private const float weightLuma = 5f;
private static float GetTargetLuma(ColorType colorType) => colorType switch
{
ColorType.Vibrant => targetNormalLuma,
ColorType.LightVibrant => targetLightLuma,
ColorType.DarkVibrant => targetDarkLuma,
ColorType.Muted => targetNormalLuma,
ColorType.LightMuted => targetLightLuma,
ColorType.DarkMuted => targetDarkLuma,
_ => throw new ArgumentException(nameof(colorType))
};
private static float GetMinLuma(ColorType colorType) => colorType switch
{
ColorType.Vibrant => minNormalLuma,
ColorType.LightVibrant => minLightLuma,
ColorType.DarkVibrant => 0f,
ColorType.Muted => minNormalLuma,
ColorType.LightMuted => minLightLuma,
ColorType.DarkMuted => 0,
_ => throw new ArgumentException(nameof(colorType))
};
private static float GetMaxLuma(ColorType colorType) => colorType switch
{
ColorType.Vibrant => maxNormalLuma,
ColorType.LightVibrant => 1f,
ColorType.DarkVibrant => maxDarkLuma,
ColorType.Muted => maxNormalLuma,
ColorType.LightMuted => 1f,
ColorType.DarkMuted => maxDarkLuma,
_ => throw new ArgumentException(nameof(colorType))
};
private static float GetTargetSaturation(ColorType colorType) => colorType switch
{
ColorType.Vibrant => targetVibrantSaturation,
ColorType.LightVibrant => targetVibrantSaturation,
ColorType.DarkVibrant => targetVibrantSaturation,
ColorType.Muted => targetMutesSaturation,
ColorType.LightMuted => targetMutesSaturation,
ColorType.DarkMuted => targetMutesSaturation,
_ => throw new ArgumentException(nameof(colorType))
};
private static float GetMinSaturation(ColorType colorType) => colorType switch
{
ColorType.Vibrant => minVibrantSaturation,
ColorType.LightVibrant => minVibrantSaturation,
ColorType.DarkVibrant => minVibrantSaturation,
ColorType.Muted => 0,
ColorType.LightMuted => 0,
ColorType.DarkMuted => 0,
_ => throw new ArgumentException(nameof(colorType))
};
private static float GetMaxSaturation(ColorType colorType) => colorType switch
{
ColorType.Vibrant => 1f,
ColorType.LightVibrant => 1f,
ColorType.DarkVibrant => 1f,
ColorType.Muted => maxMutesSaturation,
ColorType.LightMuted => maxMutesSaturation,
ColorType.DarkMuted => maxMutesSaturation,
_ => throw new ArgumentException(nameof(colorType))
};
#endregion
}
}
@@ -0,0 +1,40 @@
using SkiaSharp;
namespace Artemis.Core.Services
{
/// <summary>
/// Swatch containing the known useful color variations.
/// </summary>
public struct ColorSwatch
{
/// <summary>
/// The <see cref="ColorType.Vibrant"/> component.
/// </summary>
public SKColor Vibrant { get; init; }
/// <summary>
/// The <see cref="ColorType.LightVibrant"/> component.
/// </summary>
public SKColor LightVibrant { get; init; }
/// <summary>
/// The <see cref="ColorType.DarkVibrant"/> component.
/// </summary>
public SKColor DarkVibrant { get; init; }
/// <summary>
/// The <see cref="ColorType.Muted"/> component.
/// </summary>
public SKColor Muted { get; init; }
/// <summary>
/// The <see cref="ColorType.LightMuted"/> component.
/// </summary>
public SKColor LightMuted { get; init; }
/// <summary>
/// The <see cref="ColorType.DarkMuted"/> component.
/// </summary>
public SKColor DarkMuted { get; init; }
}
}
@@ -26,5 +26,13 @@ namespace Artemis.Core.Services
/// <param name="ignoreLimits">Ignore hard limits on whether a color is considered for each category. Result may be <see cref="SKColor.Empty"/> if this is false</param>
/// <returns>The color found</returns>
public SKColor FindColorVariation(IEnumerable<SKColor> colors, ColorType type, bool ignoreLimits = false);
/// <summary>
/// Finds all the color variations available and returns a struct containing them all.
/// </summary>
/// <param name="colors">The colors to find the variations in</param>
/// <param name="ignoreLimits">Ignore hard limits on whether a color is considered for each category. Some colors may be <see cref="SKColor.Empty"/> if this is false</param>
/// <returns>A swatch containing all color variations</returns>
public ColorSwatch FindAllColorVariations(IEnumerable<SKColor> colors, bool ignoreLimits = false);
}
}
+9 -3
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core.Ninject;
using Artemis.Core.ScriptingProviders;
@@ -13,7 +14,6 @@ using RGB.NET.Core;
using Serilog;
using Serilog.Events;
using SkiaSharp;
using Module = Artemis.Core.Modules.Module;
namespace Artemis.Core.Services
{
@@ -59,7 +59,6 @@ namespace Artemis.Core.Services
_frameStopWatch = new Stopwatch();
StartupArguments = new List<string>();
_rgbService.IsRenderPaused = true;
_rgbService.Surface.Updating += SurfaceOnUpdating;
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
}
@@ -107,6 +106,13 @@ namespace Artemis.Core.Services
if (_rgbService.IsRenderPaused)
return;
if (_rgbService.FlushLeds)
{
_rgbService.FlushLeds = false;
_rgbService.Surface.Update(true);
return;
}
try
{
_frameStopWatch.Restart();
@@ -214,7 +220,7 @@ namespace Artemis.Core.Services
_pluginManagementService.CopyBuiltInPlugins();
_pluginManagementService.LoadPlugins(StartupArguments, IsElevated);
_rgbService.IsRenderPaused = false;
_rgbService.SetRenderPaused(false);
OnInitialized();
}
@@ -34,12 +34,17 @@ namespace Artemis.Core.Services
/// <summary>
/// Gets or sets whether rendering should be paused
/// </summary>
bool IsRenderPaused { get; set; }
bool IsRenderPaused { get; }
/// <summary>
/// Gets a boolean indicating whether the render pipeline is open
/// </summary>
bool RenderOpen { get; }
/// <summary>
/// Gets or sets a boolean indicating whether to flush the RGB.NET LEDs during next update
/// </summary>
bool FlushLeds { get; set; }
/// <summary>
/// Opens the render pipeline
@@ -133,6 +138,13 @@ namespace Artemis.Core.Services
/// <param name="device">The device to disable</param>
void DisableDevice(ArtemisDevice device);
/// <summary>
/// Pauses or resumes rendering, method won't return until the current frame finished rendering
/// </summary>
/// <param name="paused"></param>
/// <returns><see langword="true"/> if the pause state was changed; otherwise <see langword="false"/>.</returns>
bool SetRenderPaused(bool paused);
/// <summary>
/// Occurs when a single device was added
/// </summary>
@@ -5,6 +5,7 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Artemis.Core.DeviceProviders;
using Artemis.Core.Ninject;
using Artemis.Storage.Entities.General;
@@ -90,11 +91,6 @@ namespace Artemis.Core.Services
using StreamReader reader = new(metaDataFileEntry.Open());
PluginInfo builtInPluginInfo = CoreJson.DeserializeObject<PluginInfo>(reader.ReadToEnd())!;
string preferred = builtInPluginInfo.PreferredPluginDirectory;
string oldPreferred = Path.GetFileNameWithoutExtension(zipFile.Name);
// Rename folders to the new format
// TODO: Get rid of this eventually, it's nice to keep around but it's extra IO that's best avoided
if (pluginDirectory.EnumerateDirectories().FirstOrDefault(d => d.Name == oldPreferred) != null)
Directory.Move(Path.Combine(pluginDirectory.FullName, oldPreferred), Path.Combine(pluginDirectory.FullName, preferred));
// Find the matching plugin in the plugin folder
DirectoryInfo? match = pluginDirectory.EnumerateDirectories().FirstOrDefault(d => d.Name == preferred);
@@ -431,6 +427,7 @@ namespace Artemis.Core.Services
catch (Exception e)
{
_logger.Warning(new ArtemisPluginException(plugin, "Failed to instantiate feature", e), "Failed to instantiate feature", plugin);
featureInfo.LoadException = e;
}
}
@@ -614,10 +611,38 @@ namespace Artemis.Core.Services
}
catch (Exception e)
{
_logger.Warning(
new ArtemisPluginException(pluginFeature.Plugin, $"Exception during SetEnabled(true) on {pluginFeature}", e),
"Failed to enable plugin"
);
if (isAutoEnable)
{
// Schedule a retry based on the amount of attempts
if (pluginFeature.AutoEnableAttempts < 4)
{
TimeSpan retryDelay = TimeSpan.FromSeconds(pluginFeature.AutoEnableAttempts * 10);
_logger.Warning(
e,
"Plugin feature '{feature} - {plugin}' failed to enable during attempt ({attempt}/3), scheduling a retry in {retryDelay}.",
pluginFeature,
pluginFeature.Plugin,
pluginFeature.AutoEnableAttempts,
retryDelay
);
Task.Run(async () =>
{
await Task.Delay(retryDelay);
if (!pluginFeature.IsEnabled)
EnablePluginFeature(pluginFeature, saveState, true);
});
}
else
{
_logger.Warning(e, "Plugin feature '{feature} - {plugin}' failed to enable after 3 attempts, giving up.", pluginFeature, pluginFeature.Plugin);
}
}
else
{
_logger.Warning(e, "Plugin feature '{feature} - {plugin}' failed to enable.", pluginFeature, pluginFeature.Plugin);
throw;
}
}
finally
{
+146 -113
View File
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading;
using Artemis.Core.DeviceProviders;
@@ -28,7 +27,6 @@ namespace Artemis.Core.Services
private readonly PluginSetting<int> _targetFrameRateSetting;
private readonly TextureBrush _textureBrush = new(ITexture.Empty) {CalculationMode = RenderMode.Absolute};
private Dictionary<Led, ArtemisLed> _ledMap;
private bool _modifyingProviders;
private ListLedGroup? _surfaceLedGroup;
private SKTexture? _texture;
@@ -52,8 +50,9 @@ namespace Artemis.Core.Services
_ledMap = new Dictionary<Led, ArtemisLed>();
UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value};
SetRenderPaused(true);
Surface.RegisterUpdateTrigger(UpdateTrigger);
Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
}
@@ -82,16 +81,14 @@ namespace Artemis.Core.Services
private void UpdateLedGroup()
{
lock (_devices)
bool changedRenderPaused = SetRenderPaused(true);
try
{
if (_modifyingProviders)
return;
_ledMap = new Dictionary<Led, ArtemisLed>(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed));
if (_surfaceLedGroup == null)
{
_surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush};
_surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) { Brush = _textureBrush };
OnLedsChanged();
return;
}
@@ -102,10 +99,16 @@ namespace Artemis.Core.Services
_surfaceLedGroup.Detach();
// Apply the application wide brush and decorator
_surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush};
_surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) { Brush = _textureBrush };
OnLedsChanged();
}
}
finally
{
if (changedRenderPaused)
SetRenderPaused(false);
}
}
private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e)
@@ -137,91 +140,90 @@ namespace Artemis.Core.Services
public bool IsRenderPaused { get; set; }
public bool RenderOpen { get; private set; }
/// <inheritdoc />
public bool FlushLeds { get; set; }
public void AddDeviceProvider(IRGBDeviceProvider deviceProvider)
{
lock (_devices)
bool changedRenderPaused = SetRenderPaused(true);
try
{
try
List<ArtemisDevice> toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList();
Surface.Detach(toRemove.Select(d => d.RgbDevice));
foreach (ArtemisDevice device in toRemove)
RemoveDevice(device);
List<Exception> providerExceptions = new();
void DeviceProviderOnException(object? sender, ExceptionEventArgs e)
{
_modifyingProviders = true;
List<ArtemisDevice> toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList();
Surface.Detach(toRemove.Select(d => d.RgbDevice));
foreach (ArtemisDevice device in toRemove)
RemoveDevice(device);
List<Exception> providerExceptions = new();
void DeviceProviderOnException(object? sender, ExceptionEventArgs e)
{
if (e.IsCritical)
providerExceptions.Add(e.Exception);
else
_logger.Warning(e.Exception, "Device provider {deviceProvider} threw non-critical exception", deviceProvider.GetType().Name);
}
deviceProvider.Exception += DeviceProviderOnException;
deviceProvider.Initialize();
Surface.Attach(deviceProvider.Devices);
deviceProvider.Exception -= DeviceProviderOnException;
if (providerExceptions.Count == 1)
throw new ArtemisPluginException("RGB.NET threw exception: " + providerExceptions.First().Message, providerExceptions.First());
if (providerExceptions.Count > 1)
throw new ArtemisPluginException("RGB.NET threw multiple exceptions", new AggregateException(providerExceptions));
if (!deviceProvider.Devices.Any())
{
_logger.Warning("Device provider {deviceProvider} has no devices", deviceProvider.GetType().Name);
return;
}
foreach (IRGBDevice rgbDevice in deviceProvider.Devices)
{
ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice);
AddDevice(artemisDevice);
_logger.Debug("Device provider {deviceProvider} added {deviceName}", deviceProvider.GetType().Name, rgbDevice.DeviceInfo.DeviceName);
}
_devices.Sort((a, b) => a.ZIndex - b.ZIndex);
if (e.IsCritical)
providerExceptions.Add(e.Exception);
else
_logger.Warning(e.Exception, "Device provider {deviceProvider} threw non-critical exception", deviceProvider.GetType().Name);
}
catch (Exception e)
deviceProvider.Exception += DeviceProviderOnException;
deviceProvider.Initialize();
Surface.Attach(deviceProvider.Devices);
deviceProvider.Exception -= DeviceProviderOnException;
if (providerExceptions.Count == 1)
throw new ArtemisPluginException("RGB.NET threw exception: " + providerExceptions.First().Message, providerExceptions.First());
if (providerExceptions.Count > 1)
throw new ArtemisPluginException("RGB.NET threw multiple exceptions", new AggregateException(providerExceptions));
if (!deviceProvider.Devices.Any())
{
_logger.Error(e, "Exception during device loading for device provider {deviceProvider}", deviceProvider.GetType().Name);
throw;
_logger.Warning("Device provider {deviceProvider} has no devices", deviceProvider.GetType().Name);
return;
}
finally
foreach (IRGBDevice rgbDevice in deviceProvider.Devices)
{
_modifyingProviders = false;
UpdateLedGroup();
ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice);
AddDevice(artemisDevice);
_logger.Debug("Device provider {deviceProvider} added {deviceName}", deviceProvider.GetType().Name, rgbDevice.DeviceInfo.DeviceName);
}
_devices.Sort((a, b) => a.ZIndex - b.ZIndex);
}
catch (Exception e)
{
_logger.Error(e, "Exception during device loading for device provider {deviceProvider}", deviceProvider.GetType().Name);
throw;
}
finally
{
UpdateLedGroup();
if (changedRenderPaused)
SetRenderPaused(false);
}
}
public void RemoveDeviceProvider(IRGBDeviceProvider deviceProvider)
{
lock (_devices)
bool changedRenderPaused = SetRenderPaused(true);
try
{
try
{
_modifyingProviders = true;
List<ArtemisDevice> toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList();
Surface.Detach(toRemove.Select(d => d.RgbDevice));
foreach (ArtemisDevice device in toRemove)
RemoveDevice(device);
List<ArtemisDevice> toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList();
Surface.Detach(toRemove.Select(d => d.RgbDevice));
foreach (ArtemisDevice device in toRemove)
RemoveDevice(device);
_devices.Sort((a, b) => a.ZIndex - b.ZIndex);
}
catch (Exception e)
{
_logger.Error(e, "Exception during device removal for device provider {deviceProvider}", deviceProvider.GetType().Name);
throw;
}
finally
{
_modifyingProviders = false;
UpdateLedGroup();
}
_devices.Sort((a, b) => a.ZIndex - b.ZIndex);
}
catch (Exception e)
{
_logger.Error(e, "Exception during device removal for device provider {deviceProvider}", deviceProvider.GetType().Name);
throw;
}
finally
{
UpdateLedGroup();
if (changedRenderPaused)
SetRenderPaused(false);
}
}
@@ -233,6 +235,24 @@ namespace Artemis.Core.Services
Surface.Dispose();
}
public bool SetRenderPaused(bool paused)
{
if (IsRenderPaused == paused)
return false;
if (paused)
{
UpdateTrigger.Stop();
}
else
{
UpdateTrigger.Start();
}
IsRenderPaused = paused;
return true;
}
public event EventHandler<DeviceEventArgs>? DeviceAdded;
public event EventHandler<DeviceEventArgs>? DeviceRemoved;
public event EventHandler? LedsChanged;
@@ -267,36 +287,39 @@ namespace Artemis.Core.Services
if (RenderOpen)
throw new ArtemisCoreException("Cannot update the texture while rendering");
IManagedGraphicsContext? graphicsContext = Constants.ManagedGraphicsContext = _newGraphicsContext;
if (!ReferenceEquals(graphicsContext, _newGraphicsContext))
graphicsContext = _newGraphicsContext;
if (graphicsContext != null)
_logger.Debug("Creating SKTexture with graphics context {graphicsContext}", graphicsContext.GetType().Name);
else
_logger.Debug("Creating SKTexture with software-based graphics context");
float evenWidth = Surface.Boundary.Size.Width;
if (evenWidth % 2 != 0)
evenWidth++;
float evenHeight = Surface.Boundary.Size.Height;
if (evenHeight % 2 != 0)
evenHeight++;
float renderScale = (float) _renderScaleSetting.Value;
int width = Math.Max(1, MathF.Min(evenWidth * renderScale, 4096).RoundToInt());
int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt());
_texture?.Dispose();
_texture = new SKTexture(graphicsContext, width, height, renderScale);
_textureBrush.Texture = _texture;
if (!ReferenceEquals(_newGraphicsContext, Constants.ManagedGraphicsContext = _newGraphicsContext))
lock (_devices)
{
Constants.ManagedGraphicsContext?.Dispose();
Constants.ManagedGraphicsContext = _newGraphicsContext;
_newGraphicsContext = null;
IManagedGraphicsContext? graphicsContext = Constants.ManagedGraphicsContext = _newGraphicsContext;
if (!ReferenceEquals(graphicsContext, _newGraphicsContext))
graphicsContext = _newGraphicsContext;
if (graphicsContext != null)
_logger.Debug("Creating SKTexture with graphics context {graphicsContext}", graphicsContext.GetType().Name);
else
_logger.Debug("Creating SKTexture with software-based graphics context");
float evenWidth = Surface.Boundary.Size.Width;
if (evenWidth % 2 != 0)
evenWidth++;
float evenHeight = Surface.Boundary.Size.Height;
if (evenHeight % 2 != 0)
evenHeight++;
float renderScale = (float) _renderScaleSetting.Value;
int width = Math.Max(1, MathF.Min(evenWidth * renderScale, 4096).RoundToInt());
int height = Math.Max(1, MathF.Min(evenHeight * renderScale, 4096).RoundToInt());
_texture?.Dispose();
_texture = new SKTexture(graphicsContext, width, height, renderScale);
_textureBrush.Texture = _texture;
if (!ReferenceEquals(_newGraphicsContext, Constants.ManagedGraphicsContext = _newGraphicsContext))
{
Constants.ManagedGraphicsContext?.Dispose();
Constants.ManagedGraphicsContext = _newGraphicsContext;
_newGraphicsContext = null;
}
}
}
@@ -315,12 +338,22 @@ namespace Artemis.Core.Services
public void AutoArrangeDevices()
{
SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement();
surfaceArrangement.Arrange(_devices);
foreach (ArtemisDevice artemisDevice in _devices)
artemisDevice.ApplyDefaultCategories();
bool changedRenderPaused = SetRenderPaused(true);
SaveDevices();
try
{
SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement();
surfaceArrangement.Arrange(_devices);
foreach (ArtemisDevice artemisDevice in _devices)
artemisDevice.ApplyDefaultCategories();
SaveDevices();
}
finally
{
if (changedRenderPaused)
SetRenderPaused(false);
}
}
public ArtemisLayout? ApplyBestDeviceLayout(ArtemisDevice device)
@@ -391,7 +424,7 @@ namespace Artemis.Core.Services
_deviceRepository.Save(device.DeviceEntity);
DeviceProvider deviceProvider = device.DeviceProvider;
// Feels bad but need to in order to get the initial LEDs back
_pluginManagementService.DisablePluginFeature(deviceProvider, false);
Thread.Sleep(500);
+2 -2
View File
@@ -18,8 +18,8 @@ namespace Artemis.Core
public static void PrepareFirstLaunch()
{
CreateAccessibleDirectory(Constants.DataFolder);
CreateAccessibleDirectory(Constants.DataFolder + "plugins");
CreateAccessibleDirectory(Constants.DataFolder + "user layouts");
CreateAccessibleDirectory(Path.Combine(Constants.DataFolder ,"plugins"));
CreateAccessibleDirectory(Path.Combine(Constants.DataFolder ,"user layouts"));
}
/// <summary>
+11 -17
View File
@@ -81,14 +81,11 @@
},
"Serilog.Sinks.Console": {
"type": "Direct",
"requested": "[3.1.1, )",
"resolved": "3.1.1",
"contentHash": "56mI5AqvyF/i/c2451nvV71kq370XOCE4Uu5qiaJ295sOhMb9q3BWwG7mWLOVSnmpWiq0SBT3SXfgRXGNP6vzA==",
"requested": "[4.0.0, )",
"resolved": "4.0.0",
"contentHash": "yJQit9sTJ4xGLKgCujqDJsaGqBNJwGB/H898z+xYlMG06twy4//6LLnSrsmpduZxcHIG4im7cv+JmXLzXz2EkQ==",
"dependencies": {
"Serilog": "2.5.0",
"System.Console": "4.3.0",
"System.Runtime.InteropServices": "4.3.0",
"System.Runtime.InteropServices.RuntimeInformation": "4.3.0"
"Serilog": "2.10.0"
}
},
"Serilog.Sinks.Debug": {
@@ -102,21 +99,18 @@
},
"Serilog.Sinks.File": {
"type": "Direct",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "U0b34w+ZikbqWEZ3ui7BdzxY/19zwrdhLtI3o6tfmLdD3oXxg7n2TZJjwCCTlKPgRuYic9CBWfrZevbb70mTaw==",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "uwV5hdhWPwUH1szhO8PJpFiahqXmzPzJT/sOijH/kFgUx+cyoDTMM8MHD0adw9+Iem6itoibbUXHYslzXsLEAg==",
"dependencies": {
"Serilog": "2.5.0",
"System.IO.FileSystem": "4.0.1",
"System.Text.Encoding.Extensions": "4.0.11",
"System.Threading.Timer": "4.0.1"
"Serilog": "2.10.0"
}
},
"SkiaSharp": {
"type": "Direct",
"requested": "[2.80.2, )",
"resolved": "2.80.2",
"contentHash": "D25rzdCwh+3L+XyXqpNa+H/yiLJbE3/R3K/XexwHyQjGdzZvSufFW3oqf3En7hhqSIsxsJ8f5NEZ0J5W5wlGBg==",
"requested": "[2.80.3, )",
"resolved": "2.80.3",
"contentHash": "qX6tGNP3+MXNYe2pKm0PCRiJ/cx+LTeLaggwZifB7sUMXhECfKKKHJq45VqZKt37xQegnCCdf1jHXwmHeJQs5Q==",
"dependencies": {
"System.Memory": "4.5.3"
}