diff --git a/src/Artemis.Core/Events/Plugins/DeviceProviderEventArgs.cs b/src/Artemis.Core/Events/Plugins/DeviceProviderEventArgs.cs
deleted file mode 100644
index 878946530..000000000
--- a/src/Artemis.Core/Events/Plugins/DeviceProviderEventArgs.cs
+++ /dev/null
@@ -1,27 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Artemis.Core.DeviceProviders;
-
-namespace Artemis.Core;
-
-///
-/// Provides data about device provider related events
-///
-public class DeviceProviderEventArgs : EventArgs
-{
- internal DeviceProviderEventArgs(DeviceProvider deviceProvider, List devices)
- {
- DeviceProvider = deviceProvider;
- Devices = devices;
- }
-
- ///
- /// Gets the device provider the event is related to.
- ///
- public DeviceProvider DeviceProvider { get; }
-
- ///
- /// Gets a list of the affected devices.
- ///
- public List Devices { get; set; }
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs b/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs
index b7dc91525..ac2c9ed24 100644
--- a/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs
+++ b/src/Artemis.Core/Models/ProfileConfiguration/Hotkey.cs
@@ -1,5 +1,4 @@
-using System;
-using Artemis.Core.Services;
+using Artemis.Core.Services;
using Artemis.Storage.Entities.Profile;
namespace Artemis.Core;
@@ -17,14 +16,6 @@ public class Hotkey : CorePropertyChanged, IStorageModel
Entity = new ProfileConfigurationHotkeyEntity();
}
- ///
- public Hotkey(KeyboardKey? key, KeyboardModifierKey? modifiers)
- {
- Key = key;
- Modifiers = modifiers;
- Entity = new ProfileConfigurationHotkeyEntity();
- }
-
///
/// Creates a new instance of based on the provided entity
///
@@ -55,7 +46,7 @@ public class Hotkey : CorePropertyChanged, IStorageModel
/// if the event args match the hotkey; otherwise
public bool MatchesEventArgs(ArtemisKeyboardKeyEventArgs eventArgs)
{
- return eventArgs.Key == Key && (eventArgs.Modifiers == Modifiers || (eventArgs.Modifiers == KeyboardModifierKey.None && Modifiers == null));
+ return eventArgs.Key == Key && eventArgs.Modifiers == Modifiers;
}
#region Implementation of IStorageModel
diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
index ef29c01f7..bf1e6211b 100644
--- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
+++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
@@ -15,17 +15,11 @@ namespace Artemis.Core;
///
public class ArtemisDevice : CorePropertyChanged
{
- private readonly List _originalLeds;
- private readonly Size _originalSize;
private SKPath? _path;
private SKRect _rectangle;
internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider)
{
- _originalLeds = new List(rgbDevice.Select(l => new OriginalLed(l)));
- Rectangle ledRectangle = new(rgbDevice.Select(x => x.Boundary));
- _originalSize = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y);
-
Identifier = rgbDevice.GetDeviceIdentifier();
DeviceEntity = new DeviceEntity();
RgbDevice = rgbDevice;
@@ -54,10 +48,6 @@ public class ArtemisDevice : CorePropertyChanged
internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, DeviceEntity deviceEntity)
{
- _originalLeds = new List(rgbDevice.Select(l => new OriginalLed(l)));
- Rectangle ledRectangle = new(rgbDevice.Select(x => x.Boundary));
- _originalSize = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y);
-
Identifier = rgbDevice.GetDeviceIdentifier();
DeviceEntity = deviceEntity;
RgbDevice = rgbDevice;
@@ -360,40 +350,6 @@ public class ArtemisDevice : CorePropertyChanged
return artemisLed;
}
- ///
- /// Returns the most preferred device layout for this device.
- ///
- /// The most preferred device layout for this device.
- public ArtemisLayout? GetBestDeviceLayout()
- {
- ArtemisLayout? layout;
-
- // Configured layout path takes precedence over all other options
- if (CustomLayoutPath != null)
- {
- layout = new ArtemisLayout(CustomLayoutPath, LayoutSource.Configured);
- if (layout.IsValid)
- return layout;
- }
-
- // Look for a layout provided by the user
- layout = DeviceProvider.LoadUserLayout(this);
- if (layout.IsValid)
- return layout;
-
- if (DisableDefaultLayout)
- return null;
-
- // Look for a layout provided by the plugin
- layout = DeviceProvider.LoadLayout(this);
- if (layout.IsValid)
- return layout;
-
- // Finally fall back to a default layout
- layout = ArtemisLayout.GetDefaultLayout(this);
- return layout;
- }
-
///
/// Occurs when the underlying RGB.NET device was updated
///
@@ -459,18 +415,8 @@ public class ArtemisDevice : CorePropertyChanged
/// A boolean indicating whether to remove excess LEDs present in the device but missing
/// in the layout
///
- internal void ApplyLayout(ArtemisLayout? layout, bool createMissingLeds, bool removeExcessiveLeds)
+ internal void ApplyLayout(ArtemisLayout layout, bool createMissingLeds, bool removeExcessiveLeds)
{
- if (layout == null)
- {
- ClearLayout();
- UpdateLeds();
-
- CalculateRenderProperties();
- OnDeviceUpdated();
- return;
- }
-
if (createMissingLeds && !DeviceProvider.CreateMissingLedsSupported)
throw new ArtemisCoreException($"Cannot apply layout with {nameof(createMissingLeds)} " +
"set to true because the device provider does not support it");
@@ -478,33 +424,18 @@ public class ArtemisDevice : CorePropertyChanged
throw new ArtemisCoreException($"Cannot apply layout with {nameof(removeExcessiveLeds)} " +
"set to true because the device provider does not support it");
- ClearLayout();
if (layout.IsValid)
layout.ApplyTo(RgbDevice, createMissingLeds, removeExcessiveLeds);
+
+
UpdateLeds();
-
+
Layout = layout;
Layout.ApplyDevice(this);
-
CalculateRenderProperties();
OnDeviceUpdated();
}
- private void ClearLayout()
- {
- if (Layout == null)
- return;
-
- RgbDevice.DeviceInfo.LayoutMetadata = null;
- RgbDevice.Size = _originalSize;
- Layout = null;
-
- while (RgbDevice.Any())
- RgbDevice.RemoveLed(RgbDevice.First().Id);
- foreach (OriginalLed originalLed in _originalLeds)
- RgbDevice.AddLed(originalLed.Id, originalLed.Location, originalLed.Size, originalLed.CustomData);
- }
-
internal void ApplyToEntity()
{
// Other properties are computed
diff --git a/src/Artemis.Core/Models/Surface/ArtemisLed.cs b/src/Artemis.Core/Models/Surface/ArtemisLed.cs
index f3d0500e1..48d1be479 100644
--- a/src/Artemis.Core/Models/Surface/ArtemisLed.cs
+++ b/src/Artemis.Core/Models/Surface/ArtemisLed.cs
@@ -59,13 +59,13 @@ public class ArtemisLed : CorePropertyChanged
internal void CalculateRectangles()
{
- Rectangle = RenderScale.CreateScaleCompatibleRect(
+ Rectangle = Utilities.CreateScaleCompatibleRect(
RgbLed.Boundary.Location.X,
RgbLed.Boundary.Location.Y,
RgbLed.Boundary.Size.Width,
RgbLed.Boundary.Size.Height
);
- AbsoluteRectangle = RenderScale.CreateScaleCompatibleRect(
+ AbsoluteRectangle = Utilities.CreateScaleCompatibleRect(
RgbLed.AbsoluteBoundary.Location.X,
RgbLed.AbsoluteBoundary.Location.Y,
RgbLed.AbsoluteBoundary.Size.Width,
diff --git a/src/Artemis.Core/Models/Surface/OriginalLed.cs b/src/Artemis.Core/Models/Surface/OriginalLed.cs
deleted file mode 100644
index 87e3f4835..000000000
--- a/src/Artemis.Core/Models/Surface/OriginalLed.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using RGB.NET.Core;
-
-namespace Artemis.Core;
-
-internal class OriginalLed
-{
- public OriginalLed(Led source)
- {
- Id = source.Id;
- Location = source.Location;
- Size = source.Size;
- CustomData = source.CustomData;
- }
-
- public LedId Id { get; set; }
- public Point Location { get; set; }
- public Size Size { get; set; }
- public object? CustomData { get; set; }
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/Core/IRenderer.cs b/src/Artemis.Core/Services/Core/IRenderer.cs
deleted file mode 100644
index 8aac001db..000000000
--- a/src/Artemis.Core/Services/Core/IRenderer.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using SkiaSharp;
-
-namespace Artemis.Core.Services.Core;
-
-///
-/// Represents a renderer that renders to a canvas.
-///
-public interface IRenderer
-{
- ///
- /// Renders to the provided canvas, the delta is the time in seconds since the last time was
- /// called.
- ///
- /// The canvas to render to.
- /// The time in seconds since the last time was called.
- void Render(SKCanvas canvas, double delta);
-
- ///
- /// Called after the rendering has taken place.
- ///
- /// The texture that the render resulted in.
- void PostRender(SKTexture texture);
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/Core/SurfaceManager.cs b/src/Artemis.Core/Services/Core/SurfaceManager.cs
deleted file mode 100644
index fb3845e3c..000000000
--- a/src/Artemis.Core/Services/Core/SurfaceManager.cs
+++ /dev/null
@@ -1,222 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Artemis.Core.SkiaSharp;
-using RGB.NET.Core;
-using SkiaSharp;
-
-namespace Artemis.Core.Services.Core;
-
-///
-/// An engine drivers an update loop for a set of devices using a graphics context
-///
-internal sealed class SurfaceManager : IDisposable
-{
- private readonly IRenderer _renderer;
- private readonly TimerUpdateTrigger _updateTrigger;
- private readonly object _renderLock = new();
- private readonly List _devices = new();
- private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute};
-
- private ListLedGroup? _surfaceLedGroup;
- private SKTexture? _texture;
-
- public SurfaceManager(IRenderer renderer, IManagedGraphicsContext? graphicsContext, int targetFrameRate, float renderScale)
- {
- _renderer = renderer;
- _updateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / targetFrameRate};
-
- GraphicsContext = graphicsContext;
- TargetFrameRate = targetFrameRate;
- RenderScale = renderScale;
- Surface = new RGBSurface();
- Surface.Updating += SurfaceOnUpdating;
- Surface.RegisterUpdateTrigger(_updateTrigger);
-
- SetPaused(true);
- }
-
- private void SurfaceOnUpdating(UpdatingEventArgs args)
- {
- lock (_renderLock)
- {
- SKTexture? texture = _texture;
- if (texture == null || texture.IsInvalid)
- texture = CreateTexture();
-
- // Prepare a canvas
- SKCanvas canvas = texture.Surface.Canvas;
- canvas.Save();
-
- // Apply scaling if necessary
- if (Math.Abs(texture.RenderScale - 1) > 0.001)
- canvas.Scale(texture.RenderScale);
-
- // Fresh start!
- canvas.Clear(new SKColor(0, 0, 0));
-
- try
- {
- _renderer.Render(canvas, args.DeltaTime);
- }
- finally
- {
- canvas.RestoreToCount(-1);
- canvas.Flush();
- texture.CopyPixelData();
- }
-
- try
- {
- _renderer.PostRender(texture);
- }
- catch
- {
- // ignored
- }
- }
- }
-
- public IManagedGraphicsContext? GraphicsContext { get; private set; }
- public int TargetFrameRate { get; private set; }
- public float RenderScale { get; private set; }
- public RGBSurface Surface { get; }
-
- public bool IsPaused { get; private set; }
-
- public void AddDevices(IEnumerable devices)
- {
- lock (_renderLock)
- {
- foreach (ArtemisDevice artemisDevice in devices)
- {
- if (_devices.Contains(artemisDevice))
- continue;
- _devices.Add(artemisDevice);
- Surface.Attach(artemisDevice.RgbDevice);
- artemisDevice.DeviceUpdated += ArtemisDeviceOnDeviceUpdated;
- }
-
- Update();
- }
- }
-
- public void RemoveDevices(IEnumerable devices)
- {
- lock (_renderLock)
- {
- foreach (ArtemisDevice artemisDevice in devices)
- {
- artemisDevice.DeviceUpdated -= ArtemisDeviceOnDeviceUpdated;
- Surface.Detach(artemisDevice.RgbDevice);
- _devices.Remove(artemisDevice);
- }
-
- Update();
- }
- }
-
- public bool SetPaused(bool paused)
- {
- if (IsPaused == paused)
- return false;
-
- if (paused)
- _updateTrigger.Stop();
- else
- _updateTrigger.Start();
-
- IsPaused = paused;
- return true;
- }
-
- private void Update()
- {
- lock (_renderLock)
- {
- UpdateLedGroup();
- CreateTexture();
- }
- }
-
- private void UpdateLedGroup()
- {
- List leds = _devices.SelectMany(d => d.Leds).Select(l => l.RgbLed).ToList();
-
- if (_surfaceLedGroup == null)
- {
- _surfaceLedGroup = new ListLedGroup(Surface, leds) {Brush = _textureBrush};
- LedsChanged?.Invoke(this, EventArgs.Empty);
- return;
- }
-
- // Clean up the old background
- _surfaceLedGroup.Detach();
-
- // Apply the application wide brush and decorator
- _surfaceLedGroup = new ListLedGroup(Surface, leds) {Brush = _textureBrush};
- LedsChanged?.Invoke(this, EventArgs.Empty);
- }
-
- private SKTexture CreateTexture()
- {
- float evenWidth = Surface.Boundary.Size.Width;
- if (evenWidth % 2 != 0)
- evenWidth++;
- float evenHeight = Surface.Boundary.Size.Height;
- if (evenHeight % 2 != 0)
- evenHeight++;
-
- 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, _devices);
- _textureBrush.Texture = _texture;
-
- return _texture;
- }
-
- private void ArtemisDeviceOnDeviceUpdated(object? sender, EventArgs e)
- {
- Update();
- }
-
- public event EventHandler? LedsChanged;
-
- ///
- public void Dispose()
- {
- SetPaused(true);
-
- Surface.UnregisterUpdateTrigger(_updateTrigger);
- _updateTrigger.Dispose();
- _texture?.Dispose();
- Surface.Dispose();
- }
-
- public void UpdateTargetFrameRate(int targetFrameRate)
- {
- TargetFrameRate = targetFrameRate;
- _updateTrigger.UpdateFrequency = 1.0 / TargetFrameRate;
- }
-
- public void UpdateRenderScale(float renderScale)
- {
- lock (_renderLock)
- {
- RenderScale = renderScale;
- _texture?.Invalidate();
- }
- }
-
- public void UpdateGraphicsContext(IManagedGraphicsContext? graphicsContext)
- {
- lock (_renderLock)
- {
- GraphicsContext = graphicsContext;
- _texture?.Dispose();
- _texture = null;
- }
- }
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs
index 89a951149..137f339b8 100644
--- a/src/Artemis.Core/Services/CoreService.cs
+++ b/src/Artemis.Core/Services/CoreService.cs
@@ -20,11 +20,19 @@ namespace Artemis.Core.Services;
///
internal class CoreService : ICoreService
{
+ private readonly Stopwatch _frameStopWatch;
private readonly ILogger _logger;
private readonly PluginSetting _loggingLevel;
+ private readonly IModuleService _moduleService;
private readonly IPluginManagementService _pluginManagementService;
- private readonly IRenderService _renderService;
+ private readonly IProfileService _profileService;
+ private readonly IRgbService _rgbService;
+ private readonly IScriptingService _scriptingService;
+ private readonly List _updateExceptions = new();
+ private int _frames;
+ private DateTime _lastExceptionLog;
+ private DateTime _lastFrameRateSample;
// ReSharper disable UnusedParameter.Local
public CoreService(IContainer container,
@@ -32,49 +40,36 @@ internal class CoreService : ICoreService
StorageMigrationService _1, // injected to ensure migration runs early
ISettingsService settingsService,
IPluginManagementService pluginManagementService,
+ IRgbService rgbService,
IProfileService profileService,
IModuleService moduleService,
- IScriptingService scriptingService,
- IRenderService renderService)
+ IScriptingService scriptingService)
{
Constants.CorePlugin.Container = container;
_logger = logger;
_pluginManagementService = pluginManagementService;
- _renderService = renderService;
+ _rgbService = rgbService;
+ _profileService = profileService;
+ _moduleService = moduleService;
+ _scriptingService = scriptingService;
_loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug);
+ _frameStopWatch = new Stopwatch();
+
+ _rgbService.Surface.Updating += SurfaceOnUpdating;
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
}
-
- public bool IsElevated { get; set; }
- public bool IsInitialized { get; set; }
+ // ReSharper restore UnusedParameter.Local
- public void Initialize()
+ protected virtual void OnFrameRendering(FrameRenderingEventArgs e)
{
- if (IsInitialized)
- throw new ArtemisCoreException("Cannot initialize the core as it is already initialized.");
+ FrameRendering?.Invoke(this, e);
+ }
- _logger.Information("Initializing Artemis Core version {CurrentVersion}", Constants.CurrentVersion);
- _logger.Information("Startup arguments: {StartupArguments}", Constants.StartupArguments);
- _logger.Information("Elevated permissions: {IsElevated}", IsElevated);
- _logger.Information("Stopwatch high resolution: {IsHighResolution}", Stopwatch.IsHighResolution);
-
- ApplyLoggingLevel();
-
- ProcessMonitor.Start();
-
- // Don't remove even if it looks useless
- // Just this line should prevent a certain someone from removing HidSharp as an unused dependency as well
- Version? hidSharpVersion = Assembly.GetAssembly(typeof(HidDevice))!.GetName().Version;
- _logger.Debug("Forcing plugins to use HidSharp {HidSharpVersion}", hidSharpVersion);
-
- // Initialize the services
- _pluginManagementService.CopyBuiltInPlugins();
- _pluginManagementService.LoadPlugins(IsElevated);
- _renderService.Initialize();
-
- OnInitialized();
+ protected virtual void OnFrameRendered(FrameRenderedEventArgs e)
+ {
+ FrameRendered?.Invoke(this, e);
}
private void ApplyLoggingLevel()
@@ -103,11 +98,131 @@ internal class CoreService : ICoreService
}
}
- public event EventHandler? Initialized;
+ private void SurfaceOnUpdating(UpdatingEventArgs args)
+ {
+ if (_rgbService.IsRenderPaused)
+ return;
+
+ if (_rgbService.FlushLeds)
+ {
+ _rgbService.FlushLeds = false;
+ _rgbService.Surface.Update(true);
+ return;
+ }
+
+ try
+ {
+ _frameStopWatch.Restart();
+
+ foreach (GlobalScript script in _scriptingService.GlobalScripts)
+ script.OnCoreUpdating(args.DeltaTime);
+
+ _moduleService.UpdateActiveModules(args.DeltaTime);
+ SKTexture texture = _rgbService.OpenRender();
+ SKCanvas canvas = texture.Surface.Canvas;
+ canvas.Save();
+ if (Math.Abs(texture.RenderScale - 1) > 0.001)
+ canvas.Scale(texture.RenderScale);
+ canvas.Clear(new SKColor(0, 0, 0));
+
+ if (!ProfileRenderingDisabled)
+ {
+ _profileService.UpdateProfiles(args.DeltaTime);
+ _profileService.RenderProfiles(canvas);
+ }
+
+ OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface));
+ canvas.RestoreToCount(-1);
+ canvas.Flush();
+
+ OnFrameRendered(new FrameRenderedEventArgs(texture, _rgbService.Surface));
+
+ foreach (GlobalScript script in _scriptingService.GlobalScripts)
+ script.OnCoreUpdated(args.DeltaTime);
+ }
+ catch (Exception e)
+ {
+ _updateExceptions.Add(e);
+ }
+ finally
+ {
+ _rgbService.CloseRender();
+ _frameStopWatch.Stop();
+ _frames++;
+
+ if ((DateTime.Now - _lastFrameRateSample).TotalSeconds >= 1)
+ {
+ FrameRate = _frames;
+ _frames = 0;
+ _lastFrameRateSample = DateTime.Now;
+ }
+
+ FrameTime = _frameStopWatch.Elapsed;
+
+ LogUpdateExceptions();
+ }
+ }
+
+ private void LogUpdateExceptions()
+ {
+ // Only log update exceptions every 10 seconds to avoid spamming the logs
+ if (DateTime.Now - _lastExceptionLog < TimeSpan.FromSeconds(10))
+ return;
+ _lastExceptionLog = DateTime.Now;
+
+ if (!_updateExceptions.Any())
+ return;
+
+ // Group by stack trace, that should gather up duplicate exceptions
+ foreach (IGrouping exceptions in _updateExceptions.GroupBy(e => e.StackTrace))
+ _logger.Warning(exceptions.First(), "Exception was thrown {count} times during update in the last 10 seconds", exceptions.Count());
+
+ // When logging is finished start with a fresh slate
+ _updateExceptions.Clear();
+ }
private void OnInitialized()
{
IsInitialized = true;
Initialized?.Invoke(this, EventArgs.Empty);
}
+
+ public int FrameRate { get; private set; }
+ public TimeSpan FrameTime { get; private set; }
+ public bool ProfileRenderingDisabled { get; set; }
+ public bool IsElevated { get; set; }
+
+ public bool IsInitialized { get; set; }
+
+ public void Initialize()
+ {
+ if (IsInitialized)
+ throw new ArtemisCoreException("Cannot initialize the core as it is already initialized.");
+
+ _logger.Information("Initializing Artemis Core version {CurrentVersion}", Constants.CurrentVersion);
+ _logger.Information("Startup arguments: {StartupArguments}", Constants.StartupArguments);
+ _logger.Information("Elevated permissions: {IsElevated}", IsElevated);
+ _logger.Information("Stopwatch high resolution: {IsHighResolution}", Stopwatch.IsHighResolution);
+
+ ApplyLoggingLevel();
+
+ ProcessMonitor.Start();
+
+ // Don't remove even if it looks useless
+ // Just this line should prevent a certain someone from removing HidSharp as an unused dependency as well
+ Version? hidSharpVersion = Assembly.GetAssembly(typeof(HidDevice))!.GetName().Version;
+ _logger.Debug("Forcing plugins to use HidSharp {HidSharpVersion}", hidSharpVersion);
+
+ // Initialize the services
+ _pluginManagementService.CopyBuiltInPlugins();
+ _pluginManagementService.LoadPlugins(IsElevated);
+
+ _rgbService.ApplyPreferredGraphicsContext(Constants.StartupArguments.Contains("--force-software-render"));
+ _rgbService.SetRenderPaused(false);
+ OnInitialized();
+ }
+
+ public event EventHandler? Initialized;
+ public event EventHandler? FrameRendering;
+ public event EventHandler? FrameRendered;
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs
index 2cddc24f4..f0b170289 100644
--- a/src/Artemis.Core/Services/DeviceService.cs
+++ b/src/Artemis.Core/Services/DeviceService.cs
@@ -1,252 +1,22 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
+using System.Linq;
using System.Threading.Tasks;
-using Artemis.Core.DeviceProviders;
-using Artemis.Core.Services.Models;
-using Artemis.Storage.Entities.Surface;
-using Artemis.Storage.Repositories.Interfaces;
using RGB.NET.Core;
-using Serilog;
namespace Artemis.Core.Services;
internal class DeviceService : IDeviceService
{
- private readonly ILogger _logger;
- private readonly IPluginManagementService _pluginManagementService;
- private readonly IDeviceRepository _deviceRepository;
- private readonly Lazy _renderService;
- private readonly List _enabledDevices = new();
- private readonly List _devices = new();
+ private readonly IRgbService _rgbService;
- public DeviceService(ILogger logger, IPluginManagementService pluginManagementService, IDeviceRepository deviceRepository, Lazy renderService)
+ public DeviceService(IRgbService rgbService)
{
- _logger = logger;
- _pluginManagementService = pluginManagementService;
- _deviceRepository = deviceRepository;
- _renderService = renderService;
-
- EnabledDevices = new ReadOnlyCollection(_enabledDevices);
- Devices = new ReadOnlyCollection(_devices);
-
- RenderScale.RenderScaleMultiplierChanged += RenderScaleOnRenderScaleMultiplierChanged;
+ _rgbService = rgbService;
}
- public IReadOnlyCollection EnabledDevices { get; }
- public IReadOnlyCollection Devices { get; }
-
- ///
- public void IdentifyDevice(ArtemisDevice device)
- {
- BlinkDevice(device, 0);
- }
-
- ///
- public void AddDeviceProvider(DeviceProvider deviceProvider)
- {
- _logger.Verbose("[AddDeviceProvider] Adding {DeviceProvider}", deviceProvider.GetType().Name);
- IRGBDeviceProvider rgbDeviceProvider = deviceProvider.RgbDeviceProvider;
-
- try
- {
- // Can't see why this would happen, RgbService used to do this though
- List toRemove = _devices.Where(a => rgbDeviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList();
- _logger.Verbose("[AddDeviceProvider] Removing {Count} old device(s)", toRemove.Count);
- foreach (ArtemisDevice device in toRemove)
- {
- _devices.Remove(device);
- OnDeviceRemoved(new DeviceEventArgs(device));
- }
-
- List 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);
- }
-
- _logger.Verbose("[AddDeviceProvider] Initializing device provider");
- rgbDeviceProvider.Exception += DeviceProviderOnException;
- rgbDeviceProvider.Initialize();
- _logger.Verbose("[AddDeviceProvider] Attaching devices of device provider");
- rgbDeviceProvider.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 (!rgbDeviceProvider.Devices.Any())
- {
- _logger.Warning("Device provider {deviceProvider} has no devices", deviceProvider.GetType().Name);
- return;
- }
-
- List addedDevices = new();
- foreach (IRGBDevice rgbDevice in rgbDeviceProvider.Devices)
- {
- ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice);
- addedDevices.Add(artemisDevice);
- _devices.Add(artemisDevice);
- if (artemisDevice.IsEnabled)
- _enabledDevices.Add(artemisDevice);
-
- _logger.Debug("Device provider {deviceProvider} added {deviceName}", deviceProvider.GetType().Name, rgbDevice.DeviceInfo.DeviceName);
- }
-
- _devices.Sort((a, b) => a.ZIndex - b.ZIndex);
- _enabledDevices.Sort((a, b) => a.ZIndex - b.ZIndex);
-
- OnDeviceProviderAdded(new DeviceProviderEventArgs(deviceProvider, addedDevices));
- foreach (ArtemisDevice artemisDevice in addedDevices)
- OnDeviceAdded(new DeviceEventArgs(artemisDevice));
-
- UpdateLeds();
- }
- catch (Exception e)
- {
- _logger.Error(e, "Exception during device loading for device provider {deviceProvider}", deviceProvider.GetType().Name);
- throw;
- }
- }
-
- ///
- public void RemoveDeviceProvider(DeviceProvider deviceProvider)
- {
- _logger.Verbose("[RemoveDeviceProvider] Pausing rendering to remove {DeviceProvider}", deviceProvider.GetType().Name);
- IRGBDeviceProvider rgbDeviceProvider = deviceProvider.RgbDeviceProvider;
- List toRemove = _devices.Where(a => rgbDeviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList();
-
- try
- {
- _logger.Verbose("[RemoveDeviceProvider] Removing {Count} old device(s)", toRemove.Count);
- foreach (ArtemisDevice device in toRemove)
- {
- _devices.Remove(device);
- _enabledDevices.Remove(device);
- }
-
- _devices.Sort((a, b) => a.ZIndex - b.ZIndex);
-
- OnDeviceProviderRemoved(new DeviceProviderEventArgs(deviceProvider, toRemove));
- foreach (ArtemisDevice artemisDevice in toRemove)
- OnDeviceRemoved(new DeviceEventArgs(artemisDevice));
-
- UpdateLeds();
- }
- catch (Exception e)
- {
- _logger.Error(e, "Exception during device removal for device provider {deviceProvider}", deviceProvider.GetType().Name);
- throw;
- }
- }
-
- ///
- public void AutoArrangeDevices()
- {
- SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement();
- surfaceArrangement.Arrange(_devices);
- foreach (ArtemisDevice artemisDevice in _devices)
- artemisDevice.ApplyDefaultCategories();
-
- SaveDevices();
- }
-
- ///
- public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout)
- {
- if (layout == null || layout.Source == LayoutSource.Default)
- device.ApplyLayout(layout, false, false);
- else
- device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported);
-
- UpdateLeds();
- }
-
- ///
- public void EnableDevice(ArtemisDevice device)
- {
- if (device.IsEnabled)
- return;
-
- _enabledDevices.Add(device);
- device.IsEnabled = true;
- device.ApplyToEntity();
- _deviceRepository.Save(device.DeviceEntity);
-
- OnDeviceEnabled(new DeviceEventArgs(device));
- UpdateLeds();
- }
-
- ///
- public void DisableDevice(ArtemisDevice device)
- {
- if (!device.IsEnabled)
- return;
-
- _enabledDevices.Remove(device);
- device.IsEnabled = false;
- device.ApplyToEntity();
- _deviceRepository.Save(device.DeviceEntity);
-
- OnDeviceDisabled(new DeviceEventArgs(device));
- UpdateLeds();
- }
-
- ///
- public void SaveDevice(ArtemisDevice artemisDevice)
- {
- artemisDevice.ApplyToEntity();
- artemisDevice.ApplyToRgbDevice();
-
- _deviceRepository.Save(artemisDevice.DeviceEntity);
- UpdateLeds();
- }
-
- ///
- public void SaveDevices()
- {
- foreach (ArtemisDevice artemisDevice in _devices)
- {
- artemisDevice.ApplyToEntity();
- artemisDevice.ApplyToRgbDevice();
- }
-
- _deviceRepository.Save(_devices.Select(d => d.DeviceEntity));
- UpdateLeds();
- }
-
- private ArtemisDevice GetArtemisDevice(IRGBDevice rgbDevice)
- {
- string deviceIdentifier = rgbDevice.GetDeviceIdentifier();
- DeviceEntity? deviceEntity = _deviceRepository.Get(deviceIdentifier);
- DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice);
-
- ArtemisDevice device;
- if (deviceEntity != null)
- device = new ArtemisDevice(rgbDevice, deviceProvider, deviceEntity);
- // Fall back on creating a new device
- else
- {
- _logger.Information("No device config found for {DeviceInfo}, device hash: {DeviceHashCode}. Adding a new entry", rgbDevice.DeviceInfo, deviceIdentifier);
- device = new ArtemisDevice(rgbDevice, deviceProvider);
- }
-
- device.ApplyToRgbDevice();
- ApplyDeviceLayout(device, device.GetBestDeviceLayout());
- return device;
- }
-
private void BlinkDevice(ArtemisDevice device, int blinkCount)
{
- RGBSurface surface = _renderService.Value.Surface;
-
// Create a LED group way at the top
- ListLedGroup ledGroup = new(surface, device.Leds.Select(l => l.RgbLed))
+ ListLedGroup ledGroup = new(_rgbService.Surface, device.Leds.Select(l => l.RgbLed))
{
Brush = new SolidColorBrush(new Color(255, 255, 255)),
ZIndex = 999
@@ -266,81 +36,9 @@ internal class DeviceService : IDeviceService
}
});
}
-
- private void CalculateRenderProperties()
+
+ public void IdentifyDevice(ArtemisDevice device)
{
- foreach (ArtemisDevice artemisDevice in Devices)
- artemisDevice.CalculateRenderProperties();
- UpdateLeds();
+ BlinkDevice(device, 0);
}
-
- private void UpdateLeds()
- {
- OnLedsChanged();
- }
-
- private void RenderScaleOnRenderScaleMultiplierChanged(object? sender, EventArgs e)
- {
- CalculateRenderProperties();
- }
-
- #region Events
-
- ///
- public event EventHandler? DeviceAdded;
-
- ///
- public event EventHandler? DeviceRemoved;
-
- ///
- public event EventHandler? DeviceEnabled;
-
- ///
- public event EventHandler? DeviceDisabled;
-
- ///
- public event EventHandler? DeviceProviderAdded;
-
- ///
- public event EventHandler? DeviceProviderRemoved;
-
- ///
- public event EventHandler? LedsChanged;
-
- protected virtual void OnDeviceAdded(DeviceEventArgs e)
- {
- DeviceAdded?.Invoke(this, e);
- }
-
- protected virtual void OnDeviceRemoved(DeviceEventArgs e)
- {
- DeviceRemoved?.Invoke(this, e);
- }
-
- protected virtual void OnDeviceEnabled(DeviceEventArgs e)
- {
- DeviceEnabled?.Invoke(this, e);
- }
-
- protected virtual void OnDeviceDisabled(DeviceEventArgs e)
- {
- DeviceDisabled?.Invoke(this, e);
- }
-
- protected virtual void OnDeviceProviderAdded(DeviceProviderEventArgs e)
- {
- DeviceProviderAdded?.Invoke(this, e);
- }
-
- protected virtual void OnDeviceProviderRemoved(DeviceProviderEventArgs e)
- {
- DeviceProviderRemoved?.Invoke(this, e);
- }
-
- protected virtual void OnLedsChanged()
- {
- LedsChanged?.Invoke(this, EventArgs.Empty);
- }
-
- #endregion
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/Input/Events/ArtemisKeyboardKeyEventArgs.cs b/src/Artemis.Core/Services/Input/Events/ArtemisKeyboardKeyEventArgs.cs
index 4738bab71..e6193c515 100644
--- a/src/Artemis.Core/Services/Input/Events/ArtemisKeyboardKeyEventArgs.cs
+++ b/src/Artemis.Core/Services/Input/Events/ArtemisKeyboardKeyEventArgs.cs
@@ -34,13 +34,4 @@ public class ArtemisKeyboardKeyEventArgs : EventArgs
/// Gets the modifiers that are pressed
///
public KeyboardModifierKey Modifiers { get; }
-
- ///
- /// Creates a hotkey matching the event.
- ///
- /// The resulting hotkey.
- public Hotkey ToHotkey()
- {
- return new Hotkey {Key = Key, Modifiers = Modifiers};
- }
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/Input/InputService.cs b/src/Artemis.Core/Services/Input/InputService.cs
index 179c6641d..a7502c6ef 100644
--- a/src/Artemis.Core/Services/Input/InputService.cs
+++ b/src/Artemis.Core/Services/Input/InputService.cs
@@ -9,19 +9,19 @@ namespace Artemis.Core.Services;
internal class InputService : IInputService
{
private readonly ILogger _logger;
- private readonly IDeviceService _deviceService;
+ private readonly IRgbService _rgbService;
private ArtemisDevice? _firstKeyboard;
private ArtemisDevice? _firstMouse;
private int _keyboardCount;
private int _mouseCount;
- public InputService(ILogger logger, IDeviceService deviceService)
+ public InputService(ILogger logger, IRgbService rgbService)
{
_logger = logger;
- _deviceService = deviceService;
+ _rgbService = rgbService;
- _deviceService.DeviceAdded += DeviceServiceOnDevicesModified;
- _deviceService.DeviceRemoved += DeviceServiceOnDevicesModified;
+ _rgbService.DeviceAdded += RgbServiceOnDevicesModified;
+ _rgbService.DeviceRemoved += RgbServiceOnDevicesModified;
BustIdentifierCache();
}
@@ -157,7 +157,7 @@ internal class InputService : IInputService
_logger.Debug("Stop identifying device {device}", _identifyingDevice);
_identifyingDevice = null;
- _deviceService.SaveDevices();
+ _rgbService.SaveDevices();
BustIdentifierCache();
}
@@ -209,7 +209,7 @@ internal class InputService : IInputService
public void BustIdentifierCache()
{
_deviceCache.Clear();
- _devices = _deviceService.EnabledDevices.Where(d => d.InputIdentifiers.Any()).ToList();
+ _devices = _rgbService.EnabledDevices.Where(d => d.InputIdentifiers.Any()).ToList();
}
private void AddDeviceToCache(ArtemisDevice match, InputProvider provider, object identifier)
@@ -241,12 +241,12 @@ internal class InputService : IInputService
OnDeviceIdentified();
}
- private void DeviceServiceOnDevicesModified(object? sender, DeviceEventArgs args)
+ private void RgbServiceOnDevicesModified(object? sender, DeviceEventArgs args)
{
- _firstKeyboard = _deviceService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard);
- _firstMouse = _deviceService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Mouse);
- _keyboardCount = _deviceService.Devices.Count(d => d.DeviceType == RGBDeviceType.Keyboard);
- _mouseCount = _deviceService.Devices.Count(d => d.DeviceType == RGBDeviceType.Mouse);
+ _firstKeyboard = _rgbService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard);
+ _firstMouse = _rgbService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Mouse);
+ _keyboardCount = _rgbService.Devices.Count(d => d.DeviceType == RGBDeviceType.Keyboard);
+ _mouseCount = _rgbService.Devices.Count(d => d.DeviceType == RGBDeviceType.Mouse);
BustIdentifierCache();
}
diff --git a/src/Artemis.Core/Services/Interfaces/ICoreService.cs b/src/Artemis.Core/Services/Interfaces/ICoreService.cs
index 8d0d1f60b..5830815ba 100644
--- a/src/Artemis.Core/Services/Interfaces/ICoreService.cs
+++ b/src/Artemis.Core/Services/Interfaces/ICoreService.cs
@@ -12,6 +12,21 @@ public interface ICoreService : IArtemisService
///
bool IsInitialized { get; }
+ ///
+ /// The time the last frame took to render
+ ///
+ TimeSpan FrameTime { get; }
+
+ ///
+ /// The amount of frames rendered each second
+ ///
+ public int FrameRate { get; }
+
+ ///
+ /// Gets or sets whether profiles are rendered each frame by calling their Render method
+ ///
+ bool ProfileRenderingDisabled { get; set; }
+
///
/// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions)
///
@@ -26,4 +41,14 @@ public interface ICoreService : IArtemisService
/// Occurs the core has finished initializing
///
event EventHandler Initialized;
+
+ ///
+ /// Occurs whenever a frame is rendering, after modules have rendered
+ ///
+ event EventHandler FrameRendering;
+
+ ///
+ /// Occurs whenever a frame is finished rendering and the render pipeline is closed
+ ///
+ event EventHandler FrameRendered;
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/Interfaces/IDeviceService.cs b/src/Artemis.Core/Services/Interfaces/IDeviceService.cs
index b66f14022..c3b8c7d59 100644
--- a/src/Artemis.Core/Services/Interfaces/IDeviceService.cs
+++ b/src/Artemis.Core/Services/Interfaces/IDeviceService.cs
@@ -1,109 +1,13 @@
-using System;
-using System.Collections.Generic;
-using Artemis.Core.DeviceProviders;
-
-namespace Artemis.Core.Services;
+namespace Artemis.Core.Services;
///
/// A service that allows you manage an
///
public interface IDeviceService : IArtemisService
{
- ///
- /// Gets a read-only collection containing all enabled devices
- ///
- IReadOnlyCollection EnabledDevices { get; }
-
- ///
- /// Gets a read-only collection containing all registered devices
- ///
- IReadOnlyCollection Devices { get; }
-
///
/// Identifies the device by making it blink white 5 times
///
///
void IdentifyDevice(ArtemisDevice device);
-
- ///
- /// Adds the given device provider and its devices.
- ///
- ///
- void AddDeviceProvider(DeviceProvider deviceProvider);
-
- ///
- /// Removes the given device provider and its devices.
- ///
- ///
- void RemoveDeviceProvider(DeviceProvider deviceProvider);
-
- ///
- /// Applies auto-arranging logic to the surface
- ///
- void AutoArrangeDevices();
-
- ///
- /// Apples the provided to the provided
- ///
- ///
- ///
- void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout);
-
- ///
- /// Enables the provided device
- ///
- /// The device to enable
- void EnableDevice(ArtemisDevice device);
-
- ///
- /// Disables the provided device
- ///
- /// The device to disable
- void DisableDevice(ArtemisDevice device);
-
- ///
- /// Saves the configuration of the provided device to persistent storage
- ///
- ///
- void SaveDevice(ArtemisDevice artemisDevice);
-
- ///
- /// Saves the configuration of all current devices to persistent storage
- ///
- void SaveDevices();
-
- ///
- /// Occurs when a single device was added.
- ///
- event EventHandler DeviceAdded;
-
- ///
- /// Occurs when a single device was removed.
- ///
- event EventHandler DeviceRemoved;
-
- ///
- /// Occurs when a single device was disabled
- ///
- event EventHandler DeviceEnabled;
-
- ///
- /// Occurs when a single device was disabled.
- ///
- event EventHandler DeviceDisabled;
-
- ///
- /// Occurs when a device provider was added.
- ///
- event EventHandler DeviceProviderAdded;
-
- ///
- /// Occurs when a device provider was removed.
- ///
- event EventHandler DeviceProviderRemoved;
-
- ///
- /// Occurs when the surface has had modifications to its LED collection
- ///
- event EventHandler LedsChanged;
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/Interfaces/IRenderService.cs b/src/Artemis.Core/Services/Interfaces/IRenderService.cs
deleted file mode 100644
index 153f4c827..000000000
--- a/src/Artemis.Core/Services/Interfaces/IRenderService.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Artemis.Core.Services.Core;
-using Artemis.Core.SkiaSharp;
-using RGB.NET.Core;
-
-namespace Artemis.Core.Services;
-
-///
-/// Represents a service that manages the render loop and renderers.
-///
-public interface IRenderService : IArtemisService
-{
- ///
- /// Gets the graphics context to be used for rendering
- ///
- IManagedGraphicsContext? GraphicsContext { get; }
-
- ///
- /// Gets the RGB surface to which is being rendered.
- ///
- RGBSurface Surface { get; }
-
- ///
- /// Gets a list of registered renderers.
- ///
- List Renderers { get; }
-
- ///
- /// Gets or sets a boolean indicating whether rendering is paused.
- ///
- bool IsPaused { get; set; }
-
- ///
- /// The time the last frame took to render
- ///
- TimeSpan FrameTime { get; }
-
- ///
- /// The amount of frames rendered each second
- ///
- public int FrameRate { get; }
-
- ///
- /// Initializes the render service and starts rendering.
- ///
- void Initialize();
-
- ///
- /// Occurs whenever a frame is rendering, after modules have rendered
- ///
- event EventHandler FrameRendering;
-
- ///
- /// Occurs whenever a frame is finished rendering and the render pipeline is closed
- ///
- event EventHandler FrameRendered;
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs
new file mode 100644
index 000000000..df258f5bf
--- /dev/null
+++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs
@@ -0,0 +1,168 @@
+using System;
+using System.Collections.Generic;
+using Artemis.Core.SkiaSharp;
+using RGB.NET.Core;
+
+namespace Artemis.Core.Services;
+
+///
+/// A service that allows you to manage the and its contents
+///
+public interface IRgbService : IArtemisService, IDisposable
+{
+ ///
+ /// Gets a read-only collection containing all enabled devices
+ ///
+ IReadOnlyCollection EnabledDevices { get; }
+
+ ///
+ /// Gets a read-only collection containing all registered devices
+ ///
+ IReadOnlyCollection Devices { get; }
+
+ ///
+ /// Gets a dictionary containing all s on the surface with their corresponding RGB.NET
+ /// as key
+ ///
+ IReadOnlyDictionary LedMap { get; }
+
+ ///
+ /// Gets or sets the RGB surface rendering is performed on
+ ///
+ RGBSurface Surface { get; set; }
+
+ ///
+ /// Gets or sets whether rendering should be paused
+ ///
+ bool IsRenderPaused { get; }
+
+ ///
+ /// Gets a boolean indicating whether the render pipeline is open
+ ///
+ bool RenderOpen { get; }
+
+ ///
+ /// Gets or sets a boolean indicating whether to flush the RGB.NET LEDs during next update
+ ///
+ bool FlushLeds { get; set; }
+
+ ///
+ /// Opens the render pipeline
+ ///
+ SKTexture OpenRender();
+
+ ///
+ /// Closes the render pipeline
+ ///
+ void CloseRender();
+
+ ///
+ /// Applies the current value of the Core.PreferredGraphicsContext setting to the graphics context.
+ ///
+ /// A boolean to indicate whether or not to force the graphics context to software mode.
+ void ApplyPreferredGraphicsContext(bool forceSoftware);
+
+ ///
+ /// Updates the graphics context to the provided .
+ /// Note: The old graphics context will be used until the next frame starts rendering and is disposed afterwards.
+ ///
+ ///
+ /// The new managed graphics context. If , software rendering
+ /// is used.
+ ///
+ void UpdateGraphicsContext(IManagedGraphicsContext? managedGraphicsContext);
+
+ ///
+ /// Adds the given device provider to the
+ ///
+ ///
+ void AddDeviceProvider(IRGBDeviceProvider deviceProvider);
+
+ ///
+ /// Removes the given device provider from the
+ ///
+ ///
+ void RemoveDeviceProvider(IRGBDeviceProvider deviceProvider);
+
+ ///
+ /// Applies auto-arranging logic to the surface
+ ///
+ void AutoArrangeDevices();
+
+ ///
+ /// Applies the best available layout for the given
+ ///
+ /// The device to apply the best available layout to
+ /// The layout that was applied to the device
+ ArtemisLayout? ApplyBestDeviceLayout(ArtemisDevice device);
+
+ ///
+ /// Apples the provided to the provided
+ ///
+ ///
+ ///
+ void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout);
+
+ ///
+ /// Attempts to retrieve the that corresponds the provided RGB.NET
+ ///
+ ///
+ ///
+ /// The RGB.NET to find the corresponding
+ /// for
+ ///
+ /// If found, the corresponding ; otherwise .
+ ArtemisDevice? GetDevice(IRGBDevice rgbDevice);
+
+ ///
+ /// Attempts to retrieve the that corresponds the provided RGB.NET
+ ///
+ /// The RGB.NET to find the corresponding for
+ /// If found, the corresponding ; otherwise .
+ ArtemisLed? GetLed(Led led);
+
+ ///
+ /// Saves the configuration of the provided device to persistent storage
+ ///
+ ///
+ void SaveDevice(ArtemisDevice artemisDevice);
+
+ ///
+ /// Saves the configuration of all current devices to persistent storage
+ ///
+ void SaveDevices();
+
+ ///
+ /// Enables the provided device
+ ///
+ /// The device to enable
+ void EnableDevice(ArtemisDevice device);
+
+ ///
+ /// Disables the provided device
+ ///
+ /// The device to disable
+ void DisableDevice(ArtemisDevice device);
+
+ ///
+ /// Pauses or resumes rendering, method won't return until the current frame finished rendering
+ ///
+ ///
+ /// if the pause state was changed; otherwise .
+ bool SetRenderPaused(bool paused);
+
+ ///
+ /// Occurs when a single device was added
+ ///
+ event EventHandler DeviceAdded;
+
+ ///
+ /// Occurs when a single device was removed
+ ///
+ event EventHandler DeviceRemoved;
+
+ ///
+ /// Occurs when the surface has had modifications to its LED collection
+ ///
+ event EventHandler LedsChanged;
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/RenderService.cs b/src/Artemis.Core/Services/RenderService.cs
deleted file mode 100644
index d9653279a..000000000
--- a/src/Artemis.Core/Services/RenderService.cs
+++ /dev/null
@@ -1,251 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using Artemis.Core.Providers;
-using Artemis.Core.Services.Core;
-using Artemis.Core.SkiaSharp;
-using DryIoc;
-using RGB.NET.Core;
-using Serilog;
-using SkiaSharp;
-
-namespace Artemis.Core.Services;
-
-internal class RenderService : IRenderService, IRenderer, IDisposable
-{
- private readonly Stopwatch _frameStopWatch;
- private readonly List _updateExceptions = new();
-
- private readonly ILogger _logger;
- private readonly IDeviceService _deviceService;
- private readonly LazyEnumerable _graphicsContextProviders;
- private readonly PluginSetting _targetFrameRateSetting;
- private readonly PluginSetting _renderScaleSetting;
- private readonly PluginSetting _preferredGraphicsContext;
-
- private SurfaceManager _surfaceManager;
- private int _frames;
- private DateTime _lastExceptionLog;
- private DateTime _lastFrameRateSample;
- private bool _initialized;
-
- public RenderService(ILogger logger, ISettingsService settingsService, IDeviceService deviceService, LazyEnumerable graphicsContextProviders)
- {
- _frameStopWatch = new Stopwatch();
- _logger = logger;
- _deviceService = deviceService;
- _graphicsContextProviders = graphicsContextProviders;
-
- _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 30);
- _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.25);
- _preferredGraphicsContext = settingsService.GetSetting("Core.PreferredGraphicsContext", "Software");
- _targetFrameRateSetting.SettingChanged += OnRenderSettingsChanged;
- _renderScaleSetting.SettingChanged += RenderScaleSettingOnSettingChanged;
- _preferredGraphicsContext.SettingChanged += PreferredGraphicsContextOnSettingChanged;
-
- Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
- _surfaceManager = new SurfaceManager(this, GraphicsContext, _targetFrameRateSetting.Value, (float) _renderScaleSetting.Value);
- }
-
- ///
- public IManagedGraphicsContext? GraphicsContext { get; private set; }
-
- ///
- public RGBSurface Surface => _surfaceManager.Surface;
-
- ///
- public List Renderers { get; } = new();
-
- ///
- public bool IsPaused
- {
- get => _surfaceManager.IsPaused;
- set => _surfaceManager.SetPaused(value);
- }
-
- ///
- public int FrameRate { get; private set; }
-
- ///
- public TimeSpan FrameTime { get; private set; }
-
- ///
- public void Render(SKCanvas canvas, double delta)
- {
- _frameStopWatch.Restart();
- try
- {
- OnFrameRendering(new FrameRenderingEventArgs(canvas, delta, _surfaceManager.Surface));
- foreach (IRenderer renderer in Renderers)
- renderer.Render(canvas, delta);
- }
- catch (Exception e)
- {
- _updateExceptions.Add(e);
- }
- }
-
- ///
- public void PostRender(SKTexture texture)
- {
- try
- {
- foreach (IRenderer renderer in Renderers)
- renderer.PostRender(texture);
- OnFrameRendered(new FrameRenderedEventArgs(texture, _surfaceManager.Surface));
- }
- catch (Exception e)
- {
- _updateExceptions.Add(e);
- }
- finally
- {
- _frameStopWatch.Stop();
- _frames++;
-
- if ((DateTime.Now - _lastFrameRateSample).TotalSeconds >= 1)
- {
- FrameRate = _frames;
- _frames = 0;
- _lastFrameRateSample = DateTime.Now;
- }
-
- FrameTime = _frameStopWatch.Elapsed;
-
- LogUpdateExceptions();
- }
- }
-
- private void SetGraphicsContext()
- {
- if (Constants.StartupArguments.Contains("--force-software-render"))
- {
- _logger.Warning("Startup argument '--force-software-render' is applied, forcing software rendering");
- GraphicsContext = null;
- return;
- }
-
- if (_preferredGraphicsContext.Value == "Software")
- {
- GraphicsContext = null;
- return;
- }
-
- List providers = _graphicsContextProviders.ToList();
- if (!providers.Any())
- {
- _logger.Warning("No graphics context provider found, defaulting to software rendering");
- GraphicsContext = null;
- }
- else
- {
- IManagedGraphicsContext? context = providers.FirstOrDefault(p => p.GraphicsContextName == _preferredGraphicsContext.Value)?.GetGraphicsContext();
- if (context == null)
- _logger.Warning("No graphics context named '{Context}' found, defaulting to software rendering", _preferredGraphicsContext.Value);
-
- GraphicsContext = context;
- }
- }
-
- private void LogUpdateExceptions()
- {
- // Only log update exceptions every 10 seconds to avoid spamming the logs
- if (DateTime.Now - _lastExceptionLog < TimeSpan.FromSeconds(10))
- return;
- _lastExceptionLog = DateTime.Now;
-
- if (!_updateExceptions.Any())
- return;
-
- // Group by stack trace, that should gather up duplicate exceptions
- foreach (IGrouping exceptions in _updateExceptions.GroupBy(e => e.StackTrace))
- _logger.Warning(exceptions.First(), "Exception was thrown {count} times during update in the last 10 seconds", exceptions.Count());
-
- // When logging is finished start with a fresh slate
- _updateExceptions.Clear();
- }
-
- private void DeviceServiceOnDeviceProviderAdded(object? sender, DeviceProviderEventArgs e)
- {
- _surfaceManager.AddDevices(e.Devices.Where(d => d.IsEnabled));
- }
-
- private void DeviceServiceOnDeviceProviderRemoved(object? sender, DeviceProviderEventArgs e)
- {
- _surfaceManager.RemoveDevices(e.Devices);
- }
-
- private void DeviceServiceOnDeviceEnabled(object? sender, DeviceEventArgs e)
- {
- _surfaceManager.AddDevices(new List {e.Device});
- }
-
- private void DeviceServiceOnDeviceDisabled(object? sender, DeviceEventArgs e)
- {
- _surfaceManager.RemoveDevices(new List {e.Device});
- }
-
- private void OnRenderSettingsChanged(object? sender, EventArgs e)
- {
- _surfaceManager.UpdateTargetFrameRate(_targetFrameRateSetting.Value);
- }
-
- private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e)
- {
- RenderScale.SetRenderScaleMultiplier((int) (1 / _renderScaleSetting.Value));
- _surfaceManager.UpdateRenderScale((float) _renderScaleSetting.Value);
- }
-
- private void PreferredGraphicsContextOnSettingChanged(object? sender, EventArgs e)
- {
- SetGraphicsContext();
- _surfaceManager.UpdateGraphicsContext(GraphicsContext);
- }
-
- private void UtilitiesOnShutdownRequested(object? sender, EventArgs e)
- {
- IsPaused = true;
- }
-
- ///
- public void Dispose()
- {
- IsPaused = true;
- _surfaceManager.Dispose();
- }
-
- ///
- public event EventHandler? FrameRendering;
-
- ///
- public event EventHandler? FrameRendered;
-
- ///
- public void Initialize()
- {
- if (_initialized)
- return;
-
- SetGraphicsContext();
- _surfaceManager.AddDevices(_deviceService.EnabledDevices);
-
- _deviceService.DeviceProviderAdded += DeviceServiceOnDeviceProviderAdded;
- _deviceService.DeviceProviderRemoved += DeviceServiceOnDeviceProviderRemoved;
- _deviceService.DeviceEnabled += DeviceServiceOnDeviceEnabled;
- _deviceService.DeviceDisabled += DeviceServiceOnDeviceDisabled;
-
- IsPaused = false;
- _initialized = true;
- }
-
- protected virtual void OnFrameRendering(FrameRenderingEventArgs e)
- {
- FrameRendering?.Invoke(this, e);
- }
-
- protected virtual void OnFrameRendered(FrameRenderedEventArgs e)
- {
- FrameRendered?.Invoke(this, e);
- }
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs
new file mode 100644
index 000000000..753becc66
--- /dev/null
+++ b/src/Artemis.Core/Services/RgbService.cs
@@ -0,0 +1,613 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Threading;
+using Artemis.Core.DeviceProviders;
+using Artemis.Core.Providers;
+using Artemis.Core.Services.Models;
+using Artemis.Core.SkiaSharp;
+using Artemis.Storage.Entities.Surface;
+using Artemis.Storage.Repositories.Interfaces;
+using DryIoc;
+using RGB.NET.Core;
+using Serilog;
+
+namespace Artemis.Core.Services;
+
+///
+/// Provides wrapped access the RGB.NET
+///
+internal class RgbService : IRgbService
+{
+ private readonly ILogger _logger;
+ private readonly IPluginManagementService _pluginManagementService;
+ private readonly IDeviceRepository _deviceRepository;
+ private readonly LazyEnumerable _graphicsContextProviders;
+
+ private readonly PluginSetting _preferredGraphicsContext;
+ private readonly PluginSetting _renderScaleSetting;
+ private readonly PluginSetting _targetFrameRateSetting;
+
+ private readonly List _devices;
+ private readonly List _enabledDevices;
+ private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute};
+ private Dictionary _ledMap;
+ private ListLedGroup? _surfaceLedGroup;
+ private SKTexture? _texture;
+
+ public RgbService(ILogger logger,
+ ISettingsService settingsService,
+ IPluginManagementService pluginManagementService,
+ IDeviceRepository deviceRepository,
+ LazyEnumerable graphicsContextProviders)
+ {
+ _logger = logger;
+ _pluginManagementService = pluginManagementService;
+ _deviceRepository = deviceRepository;
+ _graphicsContextProviders = graphicsContextProviders;
+
+ _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 30);
+ _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.25);
+ _preferredGraphicsContext = settingsService.GetSetting("Core.PreferredGraphicsContext", "Software");
+
+ Surface = new RGBSurface();
+ Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value);
+
+ // Let's throw these for now
+ Surface.Exception += SurfaceOnException;
+ Surface.SurfaceLayoutChanged += SurfaceOnLayoutChanged;
+ _targetFrameRateSetting.SettingChanged += TargetFrameRateSettingOnSettingChanged;
+ _renderScaleSetting.SettingChanged += RenderScaleSettingOnSettingChanged;
+ _preferredGraphicsContext.SettingChanged += PreferredGraphicsContextOnSettingChanged;
+ _enabledDevices = new List();
+ _devices = new List();
+ _ledMap = new Dictionary();
+
+ EnabledDevices = new ReadOnlyCollection(_enabledDevices);
+ Devices = new ReadOnlyCollection(_devices);
+ LedMap = new ReadOnlyDictionary(_ledMap);
+
+ UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value};
+ SetRenderPaused(true);
+ Surface.RegisterUpdateTrigger(UpdateTrigger);
+
+ Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
+ }
+
+
+ public TimerUpdateTrigger UpdateTrigger { get; }
+
+ protected virtual void OnDeviceRemoved(DeviceEventArgs e)
+ {
+ DeviceRemoved?.Invoke(this, e);
+ }
+
+ protected virtual void OnLedsChanged()
+ {
+ LedsChanged?.Invoke(this, EventArgs.Empty);
+ _texture?.Invalidate();
+ }
+
+ private void UtilitiesOnShutdownRequested(object? sender, EventArgs e)
+ {
+ SetRenderPaused(true);
+ }
+
+ private void SurfaceOnLayoutChanged(SurfaceLayoutChangedEventArgs args)
+ {
+ UpdateLedGroup();
+ }
+
+ private void UpdateLedGroup()
+ {
+ bool changedRenderPaused = SetRenderPaused(true);
+ try
+ {
+ _ledMap = new Dictionary(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed));
+ LedMap = new ReadOnlyDictionary(_ledMap);
+
+ if (_surfaceLedGroup == null)
+ {
+ _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush};
+ OnLedsChanged();
+ return;
+ }
+
+ lock (_surfaceLedGroup)
+ {
+ // Clean up the old background
+ _surfaceLedGroup.Detach();
+
+ // Apply the application wide brush and decorator
+ _surfaceLedGroup = new ListLedGroup(Surface, LedMap.Select(l => l.Key)) {Brush = _textureBrush};
+ OnLedsChanged();
+ }
+ }
+ finally
+ {
+ if (changedRenderPaused)
+ SetRenderPaused(false);
+ }
+ }
+
+ private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e)
+ {
+ UpdateTrigger.UpdateFrequency = 1.0 / _targetFrameRateSetting.Value;
+ }
+
+ private void SurfaceOnException(ExceptionEventArgs args)
+ {
+ _logger.Warning(args.Exception, "Surface caught exception");
+ throw args.Exception;
+ }
+
+ private void OnDeviceAdded(DeviceEventArgs e)
+ {
+ DeviceAdded?.Invoke(this, e);
+ }
+
+ private void RenderScaleSettingOnSettingChanged(object? sender, EventArgs e)
+ {
+ Utilities.RenderScaleMultiplier = (int) (1 / _renderScaleSetting.Value);
+
+ _texture?.Invalidate();
+ foreach (ArtemisDevice artemisDevice in Devices)
+ artemisDevice.CalculateRenderProperties();
+ OnLedsChanged();
+ }
+
+ private void PreferredGraphicsContextOnSettingChanged(object? sender, EventArgs e)
+ {
+ ApplyPreferredGraphicsContext(false);
+ }
+
+ public IReadOnlyCollection EnabledDevices { get; }
+ public IReadOnlyCollection Devices { get; }
+ public IReadOnlyDictionary LedMap { get; private set; }
+
+ public RGBSurface Surface { get; set; }
+ public bool IsRenderPaused { get; set; }
+ public bool RenderOpen { get; private set; }
+
+ ///
+ public bool FlushLeds { get; set; }
+
+ public void AddDeviceProvider(IRGBDeviceProvider deviceProvider)
+ {
+ _logger.Verbose("[AddDeviceProvider] Pausing rendering to add {DeviceProvider}", deviceProvider.GetType().Name);
+ bool changedRenderPaused = SetRenderPaused(true);
+
+ try
+ {
+ List toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList();
+ _logger.Verbose("[AddDeviceProvider] Removing {Count} old device(s)", toRemove.Count);
+ Surface.Detach(toRemove.Select(d => d.RgbDevice));
+ foreach (ArtemisDevice device in toRemove)
+ RemoveDevice(device);
+
+ List 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);
+ }
+
+ _logger.Verbose("[AddDeviceProvider] Initializing device provider");
+ deviceProvider.Exception += DeviceProviderOnException;
+ deviceProvider.Initialize();
+ _logger.Verbose("[AddDeviceProvider] Attaching devices of device provider");
+ 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);
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e, "Exception during device loading for device provider {deviceProvider}", deviceProvider.GetType().Name);
+ throw;
+ }
+ finally
+ {
+ _logger.Verbose("[AddDeviceProvider] Updating the LED group");
+ UpdateLedGroup();
+
+ _logger.Verbose("[AddDeviceProvider] Resuming rendering after adding {DeviceProvider}", deviceProvider.GetType().Name);
+ if (changedRenderPaused)
+ SetRenderPaused(false);
+ }
+ }
+
+ public void RemoveDeviceProvider(IRGBDeviceProvider deviceProvider)
+ {
+ _logger.Verbose("[RemoveDeviceProvider] Pausing rendering to remove {DeviceProvider}", deviceProvider.GetType().Name);
+ bool changedRenderPaused = SetRenderPaused(true);
+
+ try
+ {
+ List toRemove = _devices.Where(a => deviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList();
+ _logger.Verbose("[RemoveDeviceProvider] Removing {Count} old device(s)", toRemove.Count);
+ 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
+ {
+ _logger.Verbose("[RemoveDeviceProvider] Updating the LED group");
+ UpdateLedGroup();
+
+ _logger.Verbose("[RemoveDeviceProvider] Resuming rendering after adding {DeviceProvider}", deviceProvider.GetType().Name);
+ if (changedRenderPaused)
+ SetRenderPaused(false);
+ }
+ }
+
+ public void Dispose()
+ {
+ Surface.UnregisterUpdateTrigger(UpdateTrigger);
+
+ UpdateTrigger.Dispose();
+ 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? DeviceAdded;
+ public event EventHandler? DeviceRemoved;
+ public event EventHandler? LedsChanged;
+
+ #region Rendering
+
+ private IManagedGraphicsContext? _newGraphicsContext;
+
+
+ public SKTexture OpenRender()
+ {
+ if (RenderOpen)
+ throw new ArtemisCoreException("Render pipeline is already open");
+
+ if (_texture == null || _texture.IsInvalid)
+ CreateTexture();
+
+ RenderOpen = true;
+ return _texture!;
+ }
+
+ public void CloseRender()
+ {
+ if (!RenderOpen)
+ throw new ArtemisCoreException("Render pipeline is already closed");
+
+ RenderOpen = false;
+ _texture?.CopyPixelData();
+ }
+
+ public void CreateTexture()
+ {
+ if (RenderOpen)
+ throw new ArtemisCoreException("Cannot update the texture while rendering");
+
+ lock (_devices)
+ {
+ 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, Devices);
+ _textureBrush.Texture = _texture;
+
+
+ if (!ReferenceEquals(_newGraphicsContext, Constants.ManagedGraphicsContext = _newGraphicsContext))
+ {
+ Constants.ManagedGraphicsContext?.Dispose();
+ Constants.ManagedGraphicsContext = _newGraphicsContext;
+ _newGraphicsContext = null;
+ }
+ }
+ }
+
+ public void ApplyPreferredGraphicsContext(bool forceSoftware)
+ {
+ if (forceSoftware)
+ {
+ _logger.Warning("Startup argument '--force-software-render' is applied, forcing software rendering");
+ UpdateGraphicsContext(null);
+ return;
+ }
+
+ if (_preferredGraphicsContext.Value == "Software")
+ {
+ UpdateGraphicsContext(null);
+ return;
+ }
+
+
+ List providers = _graphicsContextProviders.ToList();
+ if (!providers.Any())
+ {
+ _logger.Warning("No graphics context provider found, defaulting to software rendering");
+ UpdateGraphicsContext(null);
+ return;
+ }
+
+ IManagedGraphicsContext? context = providers.FirstOrDefault(p => p.GraphicsContextName == _preferredGraphicsContext.Value)?.GetGraphicsContext();
+ if (context == null)
+ {
+ _logger.Warning("No graphics context named '{Context}' found, defaulting to software rendering", _preferredGraphicsContext.Value);
+ UpdateGraphicsContext(null);
+ return;
+ }
+
+ UpdateGraphicsContext(context);
+ }
+
+ public void UpdateGraphicsContext(IManagedGraphicsContext? managedGraphicsContext)
+ {
+ if (ReferenceEquals(managedGraphicsContext, Constants.ManagedGraphicsContext))
+ return;
+
+ _newGraphicsContext = managedGraphicsContext;
+ _texture?.Invalidate();
+ }
+
+ #endregion
+
+ #region EnabledDevices
+
+ public void AutoArrangeDevices()
+ {
+ bool changedRenderPaused = SetRenderPaused(true);
+
+ 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)
+ {
+ ArtemisLayout? layout;
+
+ // Configured layout path takes precedence over all other options
+ if (device.CustomLayoutPath != null)
+ {
+ layout = new ArtemisLayout(device.CustomLayoutPath, LayoutSource.Configured);
+ if (layout.IsValid)
+ {
+ ApplyDeviceLayout(device, layout);
+ return layout;
+ }
+ }
+
+ // Look for a layout provided by the user
+ layout = device.DeviceProvider.LoadUserLayout(device);
+ if (layout.IsValid)
+ {
+ ApplyDeviceLayout(device, layout);
+ return layout;
+ }
+
+ if (device.DisableDefaultLayout)
+ {
+ layout = null;
+ ApplyDeviceLayout(device, layout);
+ return null;
+ }
+
+ // Look for a layout provided by the plugin
+ layout = device.DeviceProvider.LoadLayout(device);
+ if (layout.IsValid)
+ {
+ ApplyDeviceLayout(device, layout);
+ return layout;
+ }
+
+ // Finally fall back to a default layout
+ layout = ArtemisLayout.GetDefaultLayout(device);
+ ApplyDeviceLayout(device, layout);
+ return layout;
+ }
+
+ public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout)
+ {
+ if (layout == null)
+ {
+ if (device.Layout != null)
+ ReloadDevice(device);
+ return;
+ }
+
+ if (layout.Source == LayoutSource.Default)
+ device.ApplyLayout(layout, false, false);
+ else
+ device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported);
+
+ UpdateLedGroup();
+ }
+
+ private void ReloadDevice(ArtemisDevice device)
+ {
+ // Any pending changes are otherwise lost including DisableDefaultLayout
+ device.ApplyToEntity();
+ _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);
+ _pluginManagementService.EnablePluginFeature(deviceProvider, false);
+ }
+
+ public ArtemisDevice? GetDevice(IRGBDevice rgbDevice)
+ {
+ return _devices.FirstOrDefault(d => d.RgbDevice == rgbDevice);
+ }
+
+ public ArtemisLed? GetLed(Led led)
+ {
+ LedMap.TryGetValue(led, out ArtemisLed? artemisLed);
+ return artemisLed;
+ }
+
+ public void EnableDevice(ArtemisDevice device)
+ {
+ if (device.IsEnabled)
+ return;
+
+ _enabledDevices.Add(device);
+ device.IsEnabled = true;
+ device.ApplyToEntity();
+ _deviceRepository.Save(device.DeviceEntity);
+
+ OnDeviceAdded(new DeviceEventArgs(device));
+ }
+
+ public void DisableDevice(ArtemisDevice device)
+ {
+ if (!device.IsEnabled)
+ return;
+
+ _enabledDevices.Remove(device);
+ device.IsEnabled = false;
+ device.ApplyToEntity();
+ _deviceRepository.Save(device.DeviceEntity);
+
+ OnDeviceRemoved(new DeviceEventArgs(device));
+ }
+
+ private void AddDevice(ArtemisDevice device)
+ {
+ if (_devices.Any(d => d.RgbDevice == device.RgbDevice))
+ throw new ArtemisCoreException("Attempted to add a duplicate device to the RGB Service");
+
+ device.ApplyToRgbDevice();
+ _devices.Add(device);
+ if (device.IsEnabled)
+ _enabledDevices.Add(device);
+
+ // Will call UpdateBitmapBrush()
+ ApplyBestDeviceLayout(device);
+ OnDeviceAdded(new DeviceEventArgs(device));
+ }
+
+ private void RemoveDevice(ArtemisDevice device)
+ {
+ _devices.Remove(device);
+ if (device.IsEnabled)
+ _enabledDevices.Remove(device);
+
+ OnDeviceRemoved(new DeviceEventArgs(device));
+ }
+
+ #endregion
+
+ #region Storage
+
+ private ArtemisDevice GetArtemisDevice(IRGBDevice rgbDevice)
+ {
+ string deviceIdentifier = rgbDevice.GetDeviceIdentifier();
+ DeviceEntity? deviceEntity = _deviceRepository.Get(deviceIdentifier);
+ DeviceProvider deviceProvider = _pluginManagementService.GetDeviceProviderByDevice(rgbDevice);
+
+ if (deviceEntity != null)
+ return new ArtemisDevice(rgbDevice, deviceProvider, deviceEntity);
+
+ // Fall back on creating a new device
+ _logger.Information(
+ "No device config found for {deviceInfo}, device hash: {deviceHashCode}. Adding a new entry.",
+ rgbDevice.DeviceInfo,
+ deviceIdentifier
+ );
+ return new ArtemisDevice(rgbDevice, deviceProvider);
+ }
+
+ public void SaveDevice(ArtemisDevice artemisDevice)
+ {
+ artemisDevice.ApplyToEntity();
+ artemisDevice.ApplyToRgbDevice();
+
+ _deviceRepository.Save(artemisDevice.DeviceEntity);
+ OnLedsChanged();
+ }
+
+ public void SaveDevices()
+ {
+ foreach (ArtemisDevice artemisDevice in _devices)
+ {
+ artemisDevice.ApplyToEntity();
+ artemisDevice.ApplyToRgbDevice();
+ }
+
+ _deviceRepository.Save(_devices.Select(d => d.DeviceEntity));
+ OnLedsChanged();
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
index 4e8d357fa..59fae7a9d 100644
--- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
+++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
@@ -41,11 +41,6 @@ public interface IProfileService : IArtemisService
/// Gets or sets a value indicating whether the currently focused profile should receive updates.
///
bool UpdateFocusProfile { get; set; }
-
- ///
- /// Gets or sets whether profiles are rendered each frame by calling their Render method
- ///
- bool ProfileRenderingDisabled { get; set; }
///
/// Creates a copy of the provided profile configuration.
diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs
index 485dfd267..7321fc6b2 100644
--- a/src/Artemis.Core/Services/Storage/ProfileService.cs
+++ b/src/Artemis.Core/Services/Storage/ProfileService.cs
@@ -5,9 +5,9 @@ using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using Artemis.Core.Modules;
-using Artemis.Core.Services.Core;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Repositories.Interfaces;
using Newtonsoft.Json;
@@ -16,37 +16,36 @@ using SkiaSharp;
namespace Artemis.Core.Services;
-internal class ProfileService : IProfileService, IRenderer
+internal class ProfileService : IProfileService
{
private readonly ILogger _logger;
+ private readonly IRgbService _rgbService;
private readonly IProfileCategoryRepository _profileCategoryRepository;
private readonly IPluginManagementService _pluginManagementService;
- private readonly IDeviceService _deviceService;
+
private readonly List _pendingKeyboardEvents = new();
private readonly List _profileCategories;
private readonly IProfileRepository _profileRepository;
private readonly List _renderExceptions = new();
private readonly List _updateExceptions = new();
-
private DateTime _lastRenderExceptionLog;
private DateTime _lastUpdateExceptionLog;
public ProfileService(ILogger logger,
+ IRgbService rgbService,
IProfileCategoryRepository profileCategoryRepository,
IPluginManagementService pluginManagementService,
IInputService inputService,
- IDeviceService deviceService,
- IRenderService renderService,
IProfileRepository profileRepository)
{
_logger = logger;
+ _rgbService = rgbService;
_profileCategoryRepository = profileCategoryRepository;
_pluginManagementService = pluginManagementService;
- _deviceService = deviceService;
_profileRepository = profileRepository;
_profileCategories = new List(_profileCategoryRepository.GetAll().Select(c => new ProfileCategory(c)).OrderBy(c => c.Order));
- _deviceService.LedsChanged += DeviceServiceOnLedsChanged;
+ _rgbService.LedsChanged += RgbServiceOnLedsChanged;
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
@@ -55,15 +54,11 @@ internal class ProfileService : IProfileService, IRenderer
if (!_profileCategories.Any())
CreateDefaultProfileCategories();
UpdateModules();
-
- renderService.Renderers.Add(this);
}
public ProfileConfiguration? FocusProfile { get; set; }
public ProfileElement? FocusProfileElement { get; set; }
public bool UpdateFocusProfile { get; set; }
-
- public bool ProfileRenderingDisabled { get; set; }
///
public void UpdateProfiles(double deltaTime)
@@ -187,21 +182,6 @@ internal class ProfileService : IProfileService, IRenderer
}
}
}
-
- ///
- public void Render(SKCanvas canvas, double delta)
- {
- if (ProfileRenderingDisabled)
- return;
-
- UpdateProfiles(delta);
- RenderProfiles(canvas);
- }
-
- ///
- public void PostRender(SKTexture texture)
- {
- }
///
public void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration)
@@ -255,7 +235,7 @@ internal class ProfileService : IProfileService, IRenderer
throw new ArtemisCoreException($"Cannot find profile named: {profileConfiguration.Name} ID: {profileConfiguration.Entity.ProfileId}");
Profile profile = new(profileConfiguration, profileEntity);
- profile.PopulateLeds(_deviceService.EnabledDevices);
+ profile.PopulateLeds(_rgbService.EnabledDevices);
if (profile.IsFreshImport)
{
@@ -543,7 +523,7 @@ internal class ProfileService : IProfileService, IRenderer
///
public void AdaptProfile(Profile profile)
{
- List devices = _deviceService.EnabledDevices.ToList();
+ List devices = _rgbService.EnabledDevices.ToList();
foreach (Layer layer in profile.GetAllLayers())
layer.Adapter.Adapt(devices);
@@ -572,7 +552,7 @@ internal class ProfileService : IProfileService, IRenderer
foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations)
{
if (profileConfiguration.Profile == null) continue;
- profileConfiguration.Profile.PopulateLeds(_deviceService.EnabledDevices);
+ profileConfiguration.Profile.PopulateLeds(_rgbService.EnabledDevices);
if (!profileConfiguration.Profile.IsFreshImport) continue;
_logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileConfiguration.Profile);
@@ -593,7 +573,7 @@ internal class ProfileService : IProfileService, IRenderer
}
}
- private void DeviceServiceOnLedsChanged(object? sender, EventArgs e)
+ private void RgbServiceOnLedsChanged(object? sender, EventArgs e)
{
ActiveProfilesPopulateLeds();
}
diff --git a/src/Artemis.Core/Utilities/RenderScale.cs b/src/Artemis.Core/Utilities/RenderScale.cs
deleted file mode 100644
index 43f27d54d..000000000
--- a/src/Artemis.Core/Utilities/RenderScale.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using SkiaSharp;
-
-namespace Artemis.Core;
-
-internal static class RenderScale
-{
- internal static int RenderScaleMultiplier { get; private set; } = 2;
-
- internal static event EventHandler? RenderScaleMultiplierChanged;
-
- internal static void SetRenderScaleMultiplier(int renderScaleMultiplier)
- {
- RenderScaleMultiplier = renderScaleMultiplier;
- RenderScaleMultiplierChanged?.Invoke(null, EventArgs.Empty);
- }
-
- internal static SKRectI CreateScaleCompatibleRect(float x, float y, float width, float height)
- {
- int roundX = (int) MathF.Floor(x);
- int roundY = (int) MathF.Floor(y);
- int roundWidth = (int) MathF.Ceiling(width);
- int roundHeight = (int) MathF.Ceiling(height);
-
- if (RenderScaleMultiplier == 1)
- return SKRectI.Create(roundX, roundY, roundWidth, roundHeight);
-
- return SKRectI.Create(
- roundX - roundX % RenderScaleMultiplier,
- roundY - roundY % RenderScaleMultiplier,
- roundWidth - roundWidth % RenderScaleMultiplier,
- roundHeight - roundHeight % RenderScaleMultiplier
- );
- }
-}
\ No newline at end of file
diff --git a/src/Artemis.Core/Utilities/Utilities.cs b/src/Artemis.Core/Utilities/Utilities.cs
index ed0ded1d7..db6e373a0 100644
--- a/src/Artemis.Core/Utilities/Utilities.cs
+++ b/src/Artemis.Core/Utilities/Utilities.cs
@@ -156,4 +156,28 @@ public static class Utilities
{
UpdateRequested?.Invoke(null, e);
}
+
+ #region Scaling
+
+ internal static int RenderScaleMultiplier { get; set; } = 2;
+
+ internal static SKRectI CreateScaleCompatibleRect(float x, float y, float width, float height)
+ {
+ int roundX = (int) MathF.Floor(x);
+ int roundY = (int) MathF.Floor(y);
+ int roundWidth = (int) MathF.Ceiling(width);
+ int roundHeight = (int) MathF.Ceiling(height);
+
+ if (RenderScaleMultiplier == 1)
+ return SKRectI.Create(roundX, roundY, roundWidth, roundHeight);
+
+ return SKRectI.Create(
+ roundX - roundX % RenderScaleMultiplier,
+ roundY - roundY % RenderScaleMultiplier,
+ roundWidth - roundWidth % RenderScaleMultiplier,
+ roundHeight - roundHeight % RenderScaleMultiplier
+ );
+ }
+
+ #endregion
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
index 84e4be2c2..6908fadd3 100644
--- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
+++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs
@@ -27,8 +27,8 @@ namespace Artemis.UI.Shared;
///
public class DeviceVisualizer : Control
{
- internal static readonly Dictionary BitmapCache = new();
- private readonly IRenderService _renderService;
+ internal static readonly Dictionary BitmapCache = new();
+ private readonly ICoreService _coreService;
private readonly List _deviceVisualizerLeds;
private Rect _deviceBounds;
@@ -40,7 +40,7 @@ public class DeviceVisualizer : Control
///
public DeviceVisualizer()
{
- _renderService = UI.Locator.Resolve();
+ _coreService = UI.Locator.Resolve();
_deviceVisualizerLeds = new List();
PointerReleased += OnPointerReleased;
@@ -195,6 +195,16 @@ public class DeviceVisualizer : Control
SetupForDevice();
}
+ private void DevicePropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ Dispatcher.UIThread.Invoke(async () =>
+ {
+ if (Device != null)
+ BitmapCache.Remove(Device);
+ await SetupForDevice();
+ }, DispatcherPriority.Background);
+ }
+
private void DeviceUpdated(object? sender, EventArgs e)
{
Dispatcher.UIThread.Invoke(SetupForDevice, DispatcherPriority.Background);
@@ -241,6 +251,7 @@ public class DeviceVisualizer : Control
{
if (Device != null)
{
+ Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
Device.DeviceUpdated -= DeviceUpdated;
}
@@ -250,7 +261,7 @@ public class DeviceVisualizer : Control
///
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
- _renderService.FrameRendered += OnFrameRendered;
+ _coreService.FrameRendered += OnFrameRendered;
base.OnAttachedToLogicalTree(e);
}
@@ -258,7 +269,7 @@ public class DeviceVisualizer : Control
///
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
- _renderService.FrameRendered -= OnFrameRendered;
+ _coreService.FrameRendered -= OnFrameRendered;
base.OnDetachedFromLogicalTree(e);
}
@@ -272,6 +283,7 @@ public class DeviceVisualizer : Control
if (_oldDevice != null)
{
+ _oldDevice.RgbDevice.PropertyChanged -= DevicePropertyChanged;
_oldDevice.DeviceUpdated -= DeviceUpdated;
}
@@ -282,6 +294,7 @@ public class DeviceVisualizer : Control
_deviceBounds = MeasureDevice();
_loading = true;
+ Device.RgbDevice.PropertyChanged += DevicePropertyChanged;
Device.DeviceUpdated += DeviceUpdated;
// Create all the LEDs
@@ -308,15 +321,12 @@ public class DeviceVisualizer : Control
private RenderTargetBitmap? GetDeviceImage(ArtemisDevice device)
{
- string? path = device.Layout?.Image?.LocalPath;
- if (path == null)
- return null;
-
- if (BitmapCache.TryGetValue(path, out RenderTargetBitmap? existingBitmap))
+ if (BitmapCache.TryGetValue(device, out RenderTargetBitmap? existingBitmap))
return existingBitmap;
- if (!File.Exists(path))
+
+ if (device.Layout?.Image == null || !File.Exists(device.Layout.Image.LocalPath))
{
- BitmapCache[path] = null;
+ BitmapCache[device] = null;
return null;
}
@@ -325,7 +335,7 @@ public class DeviceVisualizer : Control
RenderTargetBitmap renderTargetBitmap = new(new PixelSize((int) device.RgbDevice.ActualSize.Width * 2, (int) device.RgbDevice.ActualSize.Height * 2));
using DrawingContext context = renderTargetBitmap.CreateDrawingContext();
- using Bitmap bitmap = new(path);
+ using Bitmap bitmap = new(device.Layout.Image.LocalPath);
using Bitmap scaledBitmap = bitmap.CreateScaledBitmap(renderTargetBitmap.PixelSize);
context.DrawImage(scaledBitmap, new Rect(scaledBitmap.Size));
@@ -335,7 +345,7 @@ public class DeviceVisualizer : Control
deviceVisualizerLed.DrawBitmap(context, 2 * device.Scale);
}
- BitmapCache[path] = renderTargetBitmap;
+ BitmapCache[device] = renderTargetBitmap;
return renderTargetBitmap;
}
diff --git a/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs b/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs
index 0e18f6c68..d85066451 100644
--- a/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs
+++ b/src/Artemis.UI.Shared/Services/MainWindow/IMainWindowService.cs
@@ -11,12 +11,7 @@ public interface IMainWindowService : IArtemisSharedUIService
/// Gets a boolean indicating whether the main window is currently open
///
bool IsMainWindowOpen { get; }
-
- ///
- /// Gets a boolean indicating whether the main window is currently focused
- ///
- bool IsMainWindowFocused { get; }
-
+
///
/// Sets up the main window provider that controls the state of the main window
///
diff --git a/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs b/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs
index 0a95a0087..a95126c66 100644
--- a/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs
+++ b/src/Artemis.UI.Shared/Services/MainWindow/MainWindowService.cs
@@ -10,9 +10,6 @@ internal class MainWindowService : IMainWindowService
///
public bool IsMainWindowOpen { get; private set; }
- ///
- public bool IsMainWindowFocused { get; private set; }
-
protected virtual void OnMainWindowOpened()
{
MainWindowOpened?.Invoke(this, EventArgs.Empty);
@@ -27,13 +24,11 @@ internal class MainWindowService : IMainWindowService
protected virtual void OnMainWindowFocused()
{
MainWindowFocused?.Invoke(this, EventArgs.Empty);
- IsMainWindowFocused = true;
}
protected virtual void OnMainWindowUnfocused()
{
MainWindowUnfocused?.Invoke(this, EventArgs.Empty);
- IsMainWindowFocused = false;
}
private void SyncWithManager()
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs
index 7b1bf30d8..e34febdfe 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs
@@ -1,6 +1,4 @@
using System;
-using Artemis.Core;
-using Avalonia.Input;
using Material.Icons;
namespace Artemis.UI.Shared.Services.ProfileEditor;
@@ -45,11 +43,6 @@ public interface IToolViewModel : IDisposable
/// Gets the tooltip which this tool should show in the toolbar.
///
public string ToolTip { get; }
-
- ///
- /// Gets the keyboard hotkey that activates the tool.
- ///
- Hotkey? Hotkey { get; }
}
///
@@ -105,8 +98,5 @@ public abstract class ToolViewModel : ActivatableViewModelBase, IToolViewModel
///
public abstract string ToolTip { get; }
- ///
- public abstract Hotkey? Hotkey { get; }
-
#endregion
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
index 6a69bdd3b..f0c4cf085 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
@@ -21,7 +21,6 @@ internal class ProfileEditorService : IProfileEditorService
private readonly ILayerBrushService _layerBrushService;
private readonly BehaviorSubject _layerPropertySubject = new(null);
private readonly ILogger _logger;
- private readonly IDeviceService _deviceService;
private readonly IModuleService _moduleService;
private readonly BehaviorSubject _pixelsPerSecondSubject = new(120);
private readonly BehaviorSubject _playingSubject = new(false);
@@ -29,6 +28,7 @@ internal class ProfileEditorService : IProfileEditorService
private readonly Dictionary _profileEditorHistories = new();
private readonly BehaviorSubject _profileElementSubject = new(null);
private readonly IProfileService _profileService;
+ private readonly IRgbService _rgbService;
private readonly SourceList _selectedKeyframes;
private readonly BehaviorSubject _suspendedEditingSubject = new(false);
private readonly BehaviorSubject _timeSubject = new(TimeSpan.Zero);
@@ -36,17 +36,17 @@ internal class ProfileEditorService : IProfileEditorService
private ProfileEditorCommandScope? _profileEditorHistoryScope;
public ProfileEditorService(ILogger logger,
- IDeviceService deviceService,
IProfileService profileService,
IModuleService moduleService,
+ IRgbService rgbService,
ILayerBrushService layerBrushService,
IMainWindowService mainWindowService,
IWindowService windowService)
{
_logger = logger;
- _deviceService = deviceService;
_profileService = profileService;
_moduleService = moduleService;
+ _rgbService = rgbService;
_layerBrushService = layerBrushService;
_windowService = windowService;
@@ -369,7 +369,7 @@ internal class ProfileEditorService : IProfileEditorService
Layer layer = new(targetLayer.Parent, targetLayer.GetNewLayerName());
_layerBrushService.ApplyDefaultBrush(layer);
- layer.AddLeds(_deviceService.EnabledDevices.SelectMany(d => d.Leds));
+ layer.AddLeds(_rgbService.EnabledDevices.SelectMany(d => d.Leds));
ExecuteCommand(new AddProfileElement(layer, targetLayer.Parent, targetLayer.Order));
return layer;
@@ -379,7 +379,7 @@ internal class ProfileEditorService : IProfileEditorService
Layer layer = new(target, target.GetNewLayerName());
_layerBrushService.ApplyDefaultBrush(layer);
- layer.AddLeds(_deviceService.EnabledDevices.SelectMany(d => d.Leds));
+ layer.AddLeds(_rgbService.EnabledDevices.SelectMany(d => d.Leds));
ExecuteCommand(new AddProfileElement(layer, target, 0));
return layer;
diff --git a/src/Artemis.UI.Shared/Utilities.cs b/src/Artemis.UI.Shared/Utilities.cs
index 48e0ae2b3..3b1c58495 100644
--- a/src/Artemis.UI.Shared/Utilities.cs
+++ b/src/Artemis.UI.Shared/Utilities.cs
@@ -22,11 +22,10 @@ public static class UI
static UI()
{
- CurrentKeyBindingsEnabled = InputElement.GotFocusEvent.Raised.Select(e => e.Item2.Source is not TextBox).StartWith(true);
- CurrentKeyBindingsEnabled.Subscribe(b => KeyBindingsEnabled = b);
+ KeyBindingsEnabled = InputElement.GotFocusEvent.Raised.Select(e => e.Item2.Source is not TextBox).StartWith(true);
MicaEnabled = MicaEnabledSubject.AsObservable();
}
-
+
///
/// Gets the current IoC locator.
///
@@ -37,15 +36,10 @@ public static class UI
///
public static IClipboard Clipboard { get; set; } = null!;
- ///
- /// Gets an observable boolean indicating whether hotkeys are to be disabled.
- ///
- public static IObservable CurrentKeyBindingsEnabled { get; }
-
///
/// Gets a boolean indicating whether hotkeys are to be disabled.
///
- public static bool KeyBindingsEnabled { get; private set; }
+ public static IObservable KeyBindingsEnabled { get; }
///
/// Gets a boolean indicating whether the Mica effect should be enabled.
diff --git a/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs b/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs
index d61cc8c25..c21700423 100644
--- a/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs
+++ b/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs
@@ -14,7 +14,7 @@ namespace Artemis.UI.Screens.Debugger.Performance;
public class PerformanceDebugViewModel : ActivatableViewModelBase
{
- private readonly IRenderService _renderService;
+ private readonly ICoreService _coreService;
private readonly IPluginManagementService _pluginManagementService;
private readonly DispatcherTimer _updateTimer;
private double _currentFps;
@@ -22,9 +22,9 @@ public class PerformanceDebugViewModel : ActivatableViewModelBase
private int _renderHeight;
private int _renderWidth;
- public PerformanceDebugViewModel(IRenderService renderService, IPluginManagementService pluginManagementService)
+ public PerformanceDebugViewModel(ICoreService coreService, IPluginManagementService pluginManagementService)
{
- _renderService = renderService;
+ _coreService = coreService;
_pluginManagementService = pluginManagementService;
_updateTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(500), DispatcherPriority.Background, (_, _) => Update());
@@ -87,12 +87,12 @@ public class PerformanceDebugViewModel : ActivatableViewModelBase
private void HandleActivation()
{
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";
- _renderService.FrameRendered += RenderServiceOnFrameRendered;
+ _coreService.FrameRendered += CoreServiceOnFrameRendered;
}
private void HandleDeactivation()
{
- _renderService.FrameRendered -= RenderServiceOnFrameRendered;
+ _coreService.FrameRendered -= CoreServiceOnFrameRendered;
}
private void PopulateItems()
@@ -116,9 +116,9 @@ public class PerformanceDebugViewModel : ActivatableViewModelBase
viewModel.Update();
}
- private void RenderServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
+ private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
{
- CurrentFps = _renderService.FrameRate;
+ CurrentFps = _coreService.FrameRate;
SKImageInfo bitmapInfo = e.Texture.ImageInfo;
RenderHeight = bitmapInfo.Height;
diff --git a/src/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs b/src/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs
index b10317022..ae18a564c 100644
--- a/src/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs
+++ b/src/Artemis.UI/Screens/Debugger/Tabs/Render/RenderDebugViewModel.cs
@@ -11,7 +11,7 @@ namespace Artemis.UI.Screens.Debugger.Render;
public class RenderDebugViewModel : ActivatableViewModelBase
{
- private readonly IRenderService _renderService;
+ private readonly ICoreService _coreService;
private double _currentFps;
private Bitmap? _currentFrame;
@@ -20,9 +20,9 @@ public class RenderDebugViewModel : ActivatableViewModelBase
private int _renderHeight;
private int _renderWidth;
- public RenderDebugViewModel(IRenderService renderService)
+ public RenderDebugViewModel(ICoreService coreService)
{
- _renderService = renderService;
+ _coreService = coreService;
DisplayName = "Rendering";
@@ -66,17 +66,17 @@ public class RenderDebugViewModel : ActivatableViewModelBase
private void HandleActivation()
{
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";
- _renderService.FrameRendered += RenderServiceOnFrameRendered;
+ _coreService.FrameRendered += CoreServiceOnFrameRendered;
}
private void HandleDeactivation()
{
- _renderService.FrameRendered -= RenderServiceOnFrameRendered;
+ _coreService.FrameRendered -= CoreServiceOnFrameRendered;
}
- private void RenderServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
+ private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
{
- CurrentFps = _renderService.FrameRate;
+ CurrentFps = _coreService.FrameRate;
using SKImage skImage = e.Texture.Surface.Snapshot();
SKImageInfo bitmapInfo = e.Texture.ImageInfo;
diff --git a/src/Artemis.UI/Screens/Device/DeviceDetectInputViewModel.cs b/src/Artemis.UI/Screens/Device/DeviceDetectInputViewModel.cs
index 9e70ce509..b915e14c6 100644
--- a/src/Artemis.UI/Screens/Device/DeviceDetectInputViewModel.cs
+++ b/src/Artemis.UI/Screens/Device/DeviceDetectInputViewModel.cs
@@ -17,16 +17,20 @@ namespace Artemis.UI.Screens.Device;
public class DeviceDetectInputViewModel : ContentDialogViewModelBase
{
private readonly IInputService _inputService;
+ private readonly ListLedGroup _ledGroup;
private readonly INotificationService _notificationService;
+ private readonly IRgbService _rgbService;
- public DeviceDetectInputViewModel(ArtemisDevice device, IInputService inputService, INotificationService notificationService, IRenderService renderService)
+ public DeviceDetectInputViewModel(ArtemisDevice device, IInputService inputService, INotificationService notificationService, IRgbService rgbService)
{
_inputService = inputService;
_notificationService = notificationService;
+ _rgbService = rgbService;
+
Device = device;
// Create a LED group way at the top
- ListLedGroup ledGroup = new(renderService.Surface, Device.Leds.Select(l => l.RgbLed))
+ _ledGroup = new ListLedGroup(_rgbService.Surface, Device.Leds.Select(l => l.RgbLed))
{
Brush = new SolidColorBrush(new Color(255, 255, 0)),
ZIndex = 999
@@ -42,7 +46,7 @@ public class DeviceDetectInputViewModel : ContentDialogViewModelBase
Disposable.Create(() =>
{
_inputService.StopIdentify();
- ledGroup.Detach();
+ _ledGroup.Detach();
}).DisposeWith(disposables);
});
}
diff --git a/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs b/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs
index a87966726..2c9cf056a 100644
--- a/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs
+++ b/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs
@@ -17,7 +17,7 @@ public class DevicePropertiesViewModel : DialogViewModelBase