diff --git a/src/Artemis.Core/Events/Plugins/DeviceProviderEventArgs.cs b/src/Artemis.Core/Events/Plugins/DeviceProviderEventArgs.cs
new file mode 100644
index 000000000..878946530
--- /dev/null
+++ b/src/Artemis.Core/Events/Plugins/DeviceProviderEventArgs.cs
@@ -0,0 +1,27 @@
+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/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
index bf1e6211b..ef29c01f7 100644
--- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
+++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs
@@ -15,11 +15,17 @@ 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;
@@ -48,6 +54,10 @@ 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;
@@ -350,6 +360,40 @@ 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
///
@@ -415,8 +459,18 @@ 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");
@@ -424,18 +478,33 @@ 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 48d1be479..f3d0500e1 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 = Utilities.CreateScaleCompatibleRect(
+ Rectangle = RenderScale.CreateScaleCompatibleRect(
RgbLed.Boundary.Location.X,
RgbLed.Boundary.Location.Y,
RgbLed.Boundary.Size.Width,
RgbLed.Boundary.Size.Height
);
- AbsoluteRectangle = Utilities.CreateScaleCompatibleRect(
+ AbsoluteRectangle = RenderScale.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
new file mode 100644
index 000000000..87e3f4835
--- /dev/null
+++ b/src/Artemis.Core/Models/Surface/OriginalLed.cs
@@ -0,0 +1,19 @@
+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
new file mode 100644
index 000000000..8aac001db
--- /dev/null
+++ b/src/Artemis.Core/Services/Core/IRenderer.cs
@@ -0,0 +1,23 @@
+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
new file mode 100644
index 000000000..fb3845e3c
--- /dev/null
+++ b/src/Artemis.Core/Services/Core/SurfaceManager.cs
@@ -0,0 +1,222 @@
+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 137f339b8..89a951149 100644
--- a/src/Artemis.Core/Services/CoreService.cs
+++ b/src/Artemis.Core/Services/CoreService.cs
@@ -20,19 +20,11 @@ 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 IProfileService _profileService;
- private readonly IRgbService _rgbService;
- private readonly IScriptingService _scriptingService;
- private readonly List _updateExceptions = new();
+ private readonly IRenderService _renderService;
- private int _frames;
- private DateTime _lastExceptionLog;
- private DateTime _lastFrameRateSample;
// ReSharper disable UnusedParameter.Local
public CoreService(IContainer container,
@@ -40,36 +32,49 @@ internal class CoreService : ICoreService
StorageMigrationService _1, // injected to ensure migration runs early
ISettingsService settingsService,
IPluginManagementService pluginManagementService,
- IRgbService rgbService,
IProfileService profileService,
IModuleService moduleService,
- IScriptingService scriptingService)
+ IScriptingService scriptingService,
+ IRenderService renderService)
{
Constants.CorePlugin.Container = container;
_logger = logger;
_pluginManagementService = pluginManagementService;
- _rgbService = rgbService;
- _profileService = profileService;
- _moduleService = moduleService;
- _scriptingService = scriptingService;
+ _renderService = renderService;
_loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug);
- _frameStopWatch = new Stopwatch();
-
- _rgbService.Surface.Updating += SurfaceOnUpdating;
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
}
+
+ public bool IsElevated { get; set; }
- // ReSharper restore UnusedParameter.Local
+ public bool IsInitialized { get; set; }
- protected virtual void OnFrameRendering(FrameRenderingEventArgs e)
+ public void Initialize()
{
- FrameRendering?.Invoke(this, e);
- }
+ if (IsInitialized)
+ throw new ArtemisCoreException("Cannot initialize the core as it is already initialized.");
- protected virtual void OnFrameRendered(FrameRenderedEventArgs e)
- {
- FrameRendered?.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();
}
private void ApplyLoggingLevel()
@@ -98,131 +103,11 @@ internal class CoreService : ICoreService
}
}
- 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();
- }
+ public event EventHandler? Initialized;
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 f0b170289..2cddc24f4 100644
--- a/src/Artemis.Core/Services/DeviceService.cs
+++ b/src/Artemis.Core/Services/DeviceService.cs
@@ -1,22 +1,252 @@
-using System.Linq;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+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 IRgbService _rgbService;
+ 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();
- public DeviceService(IRgbService rgbService)
+ public DeviceService(ILogger logger, IPluginManagementService pluginManagementService, IDeviceRepository deviceRepository, Lazy renderService)
{
- _rgbService = rgbService;
+ _logger = logger;
+ _pluginManagementService = pluginManagementService;
+ _deviceRepository = deviceRepository;
+ _renderService = renderService;
+
+ EnabledDevices = new ReadOnlyCollection(_enabledDevices);
+ Devices = new ReadOnlyCollection(_devices);
+
+ RenderScale.RenderScaleMultiplierChanged += RenderScaleOnRenderScaleMultiplierChanged;
}
+ 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(_rgbService.Surface, device.Leds.Select(l => l.RgbLed))
+ ListLedGroup ledGroup = new(surface, device.Leds.Select(l => l.RgbLed))
{
Brush = new SolidColorBrush(new Color(255, 255, 255)),
ZIndex = 999
@@ -36,9 +266,81 @@ internal class DeviceService : IDeviceService
}
});
}
-
- public void IdentifyDevice(ArtemisDevice device)
+
+ private void CalculateRenderProperties()
{
- BlinkDevice(device, 0);
+ foreach (ArtemisDevice artemisDevice in Devices)
+ artemisDevice.CalculateRenderProperties();
+ UpdateLeds();
}
+
+ 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/InputService.cs b/src/Artemis.Core/Services/Input/InputService.cs
index a7502c6ef..179c6641d 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 IRgbService _rgbService;
+ private readonly IDeviceService _deviceService;
private ArtemisDevice? _firstKeyboard;
private ArtemisDevice? _firstMouse;
private int _keyboardCount;
private int _mouseCount;
- public InputService(ILogger logger, IRgbService rgbService)
+ public InputService(ILogger logger, IDeviceService deviceService)
{
_logger = logger;
- _rgbService = rgbService;
+ _deviceService = deviceService;
- _rgbService.DeviceAdded += RgbServiceOnDevicesModified;
- _rgbService.DeviceRemoved += RgbServiceOnDevicesModified;
+ _deviceService.DeviceAdded += DeviceServiceOnDevicesModified;
+ _deviceService.DeviceRemoved += DeviceServiceOnDevicesModified;
BustIdentifierCache();
}
@@ -157,7 +157,7 @@ internal class InputService : IInputService
_logger.Debug("Stop identifying device {device}", _identifyingDevice);
_identifyingDevice = null;
- _rgbService.SaveDevices();
+ _deviceService.SaveDevices();
BustIdentifierCache();
}
@@ -209,7 +209,7 @@ internal class InputService : IInputService
public void BustIdentifierCache()
{
_deviceCache.Clear();
- _devices = _rgbService.EnabledDevices.Where(d => d.InputIdentifiers.Any()).ToList();
+ _devices = _deviceService.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 RgbServiceOnDevicesModified(object? sender, DeviceEventArgs args)
+ private void DeviceServiceOnDevicesModified(object? sender, DeviceEventArgs args)
{
- _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);
+ _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);
BustIdentifierCache();
}
diff --git a/src/Artemis.Core/Services/Interfaces/ICoreService.cs b/src/Artemis.Core/Services/Interfaces/ICoreService.cs
index 5830815ba..8d0d1f60b 100644
--- a/src/Artemis.Core/Services/Interfaces/ICoreService.cs
+++ b/src/Artemis.Core/Services/Interfaces/ICoreService.cs
@@ -12,21 +12,6 @@ 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)
///
@@ -41,14 +26,4 @@ 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 c3b8c7d59..b66f14022 100644
--- a/src/Artemis.Core/Services/Interfaces/IDeviceService.cs
+++ b/src/Artemis.Core/Services/Interfaces/IDeviceService.cs
@@ -1,13 +1,109 @@
-namespace Artemis.Core.Services;
+using System;
+using System.Collections.Generic;
+using Artemis.Core.DeviceProviders;
+
+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
new file mode 100644
index 000000000..684cd193b
--- /dev/null
+++ b/src/Artemis.Core/Services/Interfaces/IRenderService.cs
@@ -0,0 +1,63 @@
+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; }
+
+ ///
+ /// Gets or sets a boolean indicating whether to flush the RGB.NET LEDs during next update
+ ///
+ bool FlushLeds { 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
deleted file mode 100644
index df258f5bf..000000000
--- a/src/Artemis.Core/Services/Interfaces/IRgbService.cs
+++ /dev/null
@@ -1,168 +0,0 @@
-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
new file mode 100644
index 000000000..66813052a
--- /dev/null
+++ b/src/Artemis.Core/Services/RenderService.cs
@@ -0,0 +1,262 @@
+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 bool FlushLeds { get; set; }
+
+ ///
+ public void Render(SKCanvas canvas, double delta)
+ {
+ _frameStopWatch.Restart();
+
+ if (FlushLeds)
+ {
+ FlushLeds = false;
+ Surface.Update(true);
+ return;
+ }
+
+ 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
deleted file mode 100644
index 753becc66..000000000
--- a/src/Artemis.Core/Services/RgbService.cs
+++ /dev/null
@@ -1,613 +0,0 @@
-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 59fae7a9d..4e8d357fa 100644
--- a/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
+++ b/src/Artemis.Core/Services/Storage/Interfaces/IProfileService.cs
@@ -41,6 +41,11 @@ 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 7321fc6b2..485dfd267 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,36 +16,37 @@ using SkiaSharp;
namespace Artemis.Core.Services;
-internal class ProfileService : IProfileService
+internal class ProfileService : IProfileService, IRenderer
{
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));
- _rgbService.LedsChanged += RgbServiceOnLedsChanged;
+ _deviceService.LedsChanged += DeviceServiceOnLedsChanged;
_pluginManagementService.PluginFeatureEnabled += PluginManagementServiceOnPluginFeatureToggled;
_pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureToggled;
@@ -54,11 +55,15 @@ internal class ProfileService : IProfileService
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)
@@ -182,6 +187,21 @@ internal class ProfileService : IProfileService
}
}
}
+
+ ///
+ public void Render(SKCanvas canvas, double delta)
+ {
+ if (ProfileRenderingDisabled)
+ return;
+
+ UpdateProfiles(delta);
+ RenderProfiles(canvas);
+ }
+
+ ///
+ public void PostRender(SKTexture texture)
+ {
+ }
///
public void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration)
@@ -235,7 +255,7 @@ internal class ProfileService : IProfileService
throw new ArtemisCoreException($"Cannot find profile named: {profileConfiguration.Name} ID: {profileConfiguration.Entity.ProfileId}");
Profile profile = new(profileConfiguration, profileEntity);
- profile.PopulateLeds(_rgbService.EnabledDevices);
+ profile.PopulateLeds(_deviceService.EnabledDevices);
if (profile.IsFreshImport)
{
@@ -523,7 +543,7 @@ internal class ProfileService : IProfileService
///
public void AdaptProfile(Profile profile)
{
- List devices = _rgbService.EnabledDevices.ToList();
+ List devices = _deviceService.EnabledDevices.ToList();
foreach (Layer layer in profile.GetAllLayers())
layer.Adapter.Adapt(devices);
@@ -552,7 +572,7 @@ internal class ProfileService : IProfileService
foreach (ProfileConfiguration profileConfiguration in ProfileConfigurations)
{
if (profileConfiguration.Profile == null) continue;
- profileConfiguration.Profile.PopulateLeds(_rgbService.EnabledDevices);
+ profileConfiguration.Profile.PopulateLeds(_deviceService.EnabledDevices);
if (!profileConfiguration.Profile.IsFreshImport) continue;
_logger.Debug("Profile is a fresh import, adapting to surface - {profile}", profileConfiguration.Profile);
@@ -573,7 +593,7 @@ internal class ProfileService : IProfileService
}
}
- private void RgbServiceOnLedsChanged(object? sender, EventArgs e)
+ private void DeviceServiceOnLedsChanged(object? sender, EventArgs e)
{
ActiveProfilesPopulateLeds();
}
diff --git a/src/Artemis.Core/Utilities/RenderScale.cs b/src/Artemis.Core/Utilities/RenderScale.cs
new file mode 100644
index 000000000..43f27d54d
--- /dev/null
+++ b/src/Artemis.Core/Utilities/RenderScale.cs
@@ -0,0 +1,35 @@
+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 db6e373a0..ed0ded1d7 100644
--- a/src/Artemis.Core/Utilities/Utilities.cs
+++ b/src/Artemis.Core/Utilities/Utilities.cs
@@ -156,28 +156,4 @@ 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 6908fadd3..84e4be2c2 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 ICoreService _coreService;
+ internal static readonly Dictionary BitmapCache = new();
+ private readonly IRenderService _renderService;
private readonly List _deviceVisualizerLeds;
private Rect _deviceBounds;
@@ -40,7 +40,7 @@ public class DeviceVisualizer : Control
///
public DeviceVisualizer()
{
- _coreService = UI.Locator.Resolve();
+ _renderService = UI.Locator.Resolve();
_deviceVisualizerLeds = new List();
PointerReleased += OnPointerReleased;
@@ -195,16 +195,6 @@ 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);
@@ -251,7 +241,6 @@ public class DeviceVisualizer : Control
{
if (Device != null)
{
- Device.RgbDevice.PropertyChanged -= DevicePropertyChanged;
Device.DeviceUpdated -= DeviceUpdated;
}
@@ -261,7 +250,7 @@ public class DeviceVisualizer : Control
///
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
- _coreService.FrameRendered += OnFrameRendered;
+ _renderService.FrameRendered += OnFrameRendered;
base.OnAttachedToLogicalTree(e);
}
@@ -269,7 +258,7 @@ public class DeviceVisualizer : Control
///
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
- _coreService.FrameRendered -= OnFrameRendered;
+ _renderService.FrameRendered -= OnFrameRendered;
base.OnDetachedFromLogicalTree(e);
}
@@ -283,7 +272,6 @@ public class DeviceVisualizer : Control
if (_oldDevice != null)
{
- _oldDevice.RgbDevice.PropertyChanged -= DevicePropertyChanged;
_oldDevice.DeviceUpdated -= DeviceUpdated;
}
@@ -294,7 +282,6 @@ public class DeviceVisualizer : Control
_deviceBounds = MeasureDevice();
_loading = true;
- Device.RgbDevice.PropertyChanged += DevicePropertyChanged;
Device.DeviceUpdated += DeviceUpdated;
// Create all the LEDs
@@ -321,12 +308,15 @@ public class DeviceVisualizer : Control
private RenderTargetBitmap? GetDeviceImage(ArtemisDevice device)
{
- if (BitmapCache.TryGetValue(device, out RenderTargetBitmap? existingBitmap))
+ string? path = device.Layout?.Image?.LocalPath;
+ if (path == null)
+ return null;
+
+ if (BitmapCache.TryGetValue(path, out RenderTargetBitmap? existingBitmap))
return existingBitmap;
-
- if (device.Layout?.Image == null || !File.Exists(device.Layout.Image.LocalPath))
+ if (!File.Exists(path))
{
- BitmapCache[device] = null;
+ BitmapCache[path] = null;
return null;
}
@@ -335,7 +325,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(device.Layout.Image.LocalPath);
+ using Bitmap bitmap = new(path);
using Bitmap scaledBitmap = bitmap.CreateScaledBitmap(renderTargetBitmap.PixelSize);
context.DrawImage(scaledBitmap, new Rect(scaledBitmap.Size));
@@ -345,7 +335,7 @@ public class DeviceVisualizer : Control
deviceVisualizerLed.DrawBitmap(context, 2 * device.Scale);
}
- BitmapCache[device] = renderTargetBitmap;
+ BitmapCache[path] = renderTargetBitmap;
return renderTargetBitmap;
}
diff --git a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
index f0c4cf085..6a69bdd3b 100644
--- a/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
+++ b/src/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs
@@ -21,6 +21,7 @@ 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);
@@ -28,7 +29,6 @@ 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(_rgbService.EnabledDevices.SelectMany(d => d.Leds));
+ layer.AddLeds(_deviceService.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(_rgbService.EnabledDevices.SelectMany(d => d.Leds));
+ layer.AddLeds(_deviceService.EnabledDevices.SelectMany(d => d.Leds));
ExecuteCommand(new AddProfileElement(layer, target, 0));
return layer;
diff --git a/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs b/src/Artemis.UI/Screens/Debugger/Tabs/Performance/PerformanceDebugViewModel.cs
index c21700423..d61cc8c25 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 ICoreService _coreService;
+ private readonly IRenderService _renderService;
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(ICoreService coreService, IPluginManagementService pluginManagementService)
+ public PerformanceDebugViewModel(IRenderService renderService, IPluginManagementService pluginManagementService)
{
- _coreService = coreService;
+ _renderService = renderService;
_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";
- _coreService.FrameRendered += CoreServiceOnFrameRendered;
+ _renderService.FrameRendered += RenderServiceOnFrameRendered;
}
private void HandleDeactivation()
{
- _coreService.FrameRendered -= CoreServiceOnFrameRendered;
+ _renderService.FrameRendered -= RenderServiceOnFrameRendered;
}
private void PopulateItems()
@@ -116,9 +116,9 @@ public class PerformanceDebugViewModel : ActivatableViewModelBase
viewModel.Update();
}
- private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
+ private void RenderServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
{
- CurrentFps = _coreService.FrameRate;
+ CurrentFps = _renderService.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 ae18a564c..b10317022 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 ICoreService _coreService;
+ private readonly IRenderService _renderService;
private double _currentFps;
private Bitmap? _currentFrame;
@@ -20,9 +20,9 @@ public class RenderDebugViewModel : ActivatableViewModelBase
private int _renderHeight;
private int _renderWidth;
- public RenderDebugViewModel(ICoreService coreService)
+ public RenderDebugViewModel(IRenderService renderService)
{
- _coreService = coreService;
+ _renderService = renderService;
DisplayName = "Rendering";
@@ -66,17 +66,17 @@ public class RenderDebugViewModel : ActivatableViewModelBase
private void HandleActivation()
{
Renderer = Constants.ManagedGraphicsContext != null ? Constants.ManagedGraphicsContext.GetType().Name : "Software";
- _coreService.FrameRendered += CoreServiceOnFrameRendered;
+ _renderService.FrameRendered += RenderServiceOnFrameRendered;
}
private void HandleDeactivation()
{
- _coreService.FrameRendered -= CoreServiceOnFrameRendered;
+ _renderService.FrameRendered -= RenderServiceOnFrameRendered;
}
- private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
+ private void RenderServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e)
{
- CurrentFps = _coreService.FrameRate;
+ CurrentFps = _renderService.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 b915e14c6..9e70ce509 100644
--- a/src/Artemis.UI/Screens/Device/DeviceDetectInputViewModel.cs
+++ b/src/Artemis.UI/Screens/Device/DeviceDetectInputViewModel.cs
@@ -17,20 +17,16 @@ 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, IRgbService rgbService)
+ public DeviceDetectInputViewModel(ArtemisDevice device, IInputService inputService, INotificationService notificationService, IRenderService renderService)
{
_inputService = inputService;
_notificationService = notificationService;
- _rgbService = rgbService;
-
Device = device;
// Create a LED group way at the top
- _ledGroup = new ListLedGroup(_rgbService.Surface, Device.Leds.Select(l => l.RgbLed))
+ ListLedGroup ledGroup = new(renderService.Surface, Device.Leds.Select(l => l.RgbLed))
{
Brush = new SolidColorBrush(new Color(255, 255, 0)),
ZIndex = 999
@@ -46,7 +42,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 2c9cf056a..a87966726 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