Core - Split up RGBService into RenderService and DeviceService

Core - Removed rendering from CoreService
Core - Store and restore original device LEDs when applying/clearing layouts
This commit is contained in:
Robert committed 2023-10-05 20:38:07 +02:00
1 parent 919199a8d0
commit 2a0a0f3cfd
43 files changed
+1363 -1189

No files matched your search

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using Artemis.Core.DeviceProviders;
namespace Artemis.Core;
/// <summary>
/// Provides data about device provider related events
/// </summary>
public class DeviceProviderEventArgs : EventArgs
{
internal DeviceProviderEventArgs(DeviceProvider deviceProvider, List<ArtemisDevice> devices)
{
DeviceProvider = deviceProvider;
Devices = devices;
}
/// <summary>
/// Gets the device provider the event is related to.
/// </summary>
public DeviceProvider DeviceProvider { get; }
/// <summary>
/// Gets a list of the affected devices.
/// </summary>
public List<ArtemisDevice> Devices { get; set; }
}
@@ -15,11 +15,17 @@ namespace Artemis.Core;
/// </summary>
public class ArtemisDevice : CorePropertyChanged
{
private readonly List<OriginalLed> _originalLeds;
private readonly Size _originalSize;
private SKPath? _path;
private SKRect _rectangle;
internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider)
{
_originalLeds = new List<OriginalLed>(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<OriginalLed>(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;
}
/// <summary>
/// Returns the most preferred device layout for this device.
/// </summary>
/// <returns>The most preferred device layout for this device.</returns>
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;
}
/// <summary>
/// Occurs when the underlying RGB.NET device was updated
/// </summary>
@@ -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
/// </param>
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
@@ -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,
@@ -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; }
}
@@ -0,0 +1,23 @@
using SkiaSharp;
namespace Artemis.Core.Services.Core;
/// <summary>
/// Represents a renderer that renders to a canvas.
/// </summary>
public interface IRenderer
{
/// <summary>
/// Renders to the provided canvas, the delta is the time in seconds since the last time <see cref="Render" /> was
/// called.
/// </summary>
/// <param name="canvas">The canvas to render to.</param>
/// <param name="delta">The time in seconds since the last time <see cref="Render" /> was called.</param>
void Render(SKCanvas canvas, double delta);
/// <summary>
/// Called after the rendering has taken place.
/// </summary>
/// <param name="texture">The texture that the render resulted in.</param>
void PostRender(SKTexture texture);
}
@@ -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;
/// <summary>
/// An engine drivers an update loop for a set of devices using a graphics context
/// </summary>
internal sealed class SurfaceManager : IDisposable
{
private readonly IRenderer _renderer;
private readonly TimerUpdateTrigger _updateTrigger;
private readonly object _renderLock = new();
private readonly List<ArtemisDevice> _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<ArtemisDevice> 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<ArtemisDevice> 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<Led> 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;
/// <inheritdoc />
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;
}
}
}
+31 -146
View File
@@ -20,19 +20,11 @@ namespace Artemis.Core.Services;
/// </summary>
internal class CoreService : ICoreService
{
private readonly Stopwatch _frameStopWatch;
private readonly ILogger _logger;
private readonly PluginSetting<LogEventLevel> _loggingLevel;
private readonly IModuleService _moduleService;
private readonly IPluginManagementService _pluginManagementService;
private readonly IProfileService _profileService;
private readonly IRgbService _rgbService;
private readonly IScriptingService _scriptingService;
private readonly List<Exception> _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<string?, Exception> 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<FrameRenderingEventArgs>? FrameRendering;
public event EventHandler<FrameRenderedEventArgs>? FrameRendered;
}
+310 -8
View File
@@ -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<IRenderService> _renderService;
private readonly List<ArtemisDevice> _enabledDevices = new();
private readonly List<ArtemisDevice> _devices = new();
public DeviceService(IRgbService rgbService)
public DeviceService(ILogger logger, IPluginManagementService pluginManagementService, IDeviceRepository deviceRepository, Lazy<IRenderService> renderService)
{
_rgbService = rgbService;
_logger = logger;
_pluginManagementService = pluginManagementService;
_deviceRepository = deviceRepository;
_renderService = renderService;
EnabledDevices = new ReadOnlyCollection<ArtemisDevice>(_enabledDevices);
Devices = new ReadOnlyCollection<ArtemisDevice>(_devices);
RenderScale.RenderScaleMultiplierChanged += RenderScaleOnRenderScaleMultiplierChanged;
}
public IReadOnlyCollection<ArtemisDevice> EnabledDevices { get; }
public IReadOnlyCollection<ArtemisDevice> Devices { get; }
/// <inheritdoc />
public void IdentifyDevice(ArtemisDevice device)
{
BlinkDevice(device, 0);
}
/// <inheritdoc />
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<ArtemisDevice> 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<Exception> providerExceptions = new();
void DeviceProviderOnException(object? sender, ExceptionEventArgs e)
{
if (e.IsCritical)
providerExceptions.Add(e.Exception);
else
_logger.Warning(e.Exception, "Device provider {deviceProvider} threw non-critical exception", deviceProvider.GetType().Name);
}
_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<ArtemisDevice> 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;
}
}
/// <inheritdoc />
public void RemoveDeviceProvider(DeviceProvider deviceProvider)
{
_logger.Verbose("[RemoveDeviceProvider] Pausing rendering to remove {DeviceProvider}", deviceProvider.GetType().Name);
IRGBDeviceProvider rgbDeviceProvider = deviceProvider.RgbDeviceProvider;
List<ArtemisDevice> 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;
}
}
/// <inheritdoc />
public void AutoArrangeDevices()
{
SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement();
surfaceArrangement.Arrange(_devices);
foreach (ArtemisDevice artemisDevice in _devices)
artemisDevice.ApplyDefaultCategories();
SaveDevices();
}
/// <inheritdoc />
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();
}
/// <inheritdoc />
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();
}
/// <inheritdoc />
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();
}
/// <inheritdoc />
public void SaveDevice(ArtemisDevice artemisDevice)
{
artemisDevice.ApplyToEntity();
artemisDevice.ApplyToRgbDevice();
_deviceRepository.Save(artemisDevice.DeviceEntity);
UpdateLeds();
}
/// <inheritdoc />
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
/// <inheritdoc />
public event EventHandler<DeviceEventArgs>? DeviceAdded;
/// <inheritdoc />
public event EventHandler<DeviceEventArgs>? DeviceRemoved;
/// <inheritdoc />
public event EventHandler<DeviceEventArgs>? DeviceEnabled;
/// <inheritdoc />
public event EventHandler<DeviceEventArgs>? DeviceDisabled;
/// <inheritdoc />
public event EventHandler<DeviceProviderEventArgs>? DeviceProviderAdded;
/// <inheritdoc />
public event EventHandler<DeviceProviderEventArgs>? DeviceProviderRemoved;
/// <inheritdoc />
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
}
+12 -12
View File
@@ -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();
}
@@ -12,21 +12,6 @@ public interface ICoreService : IArtemisService
/// </summary>
bool IsInitialized { get; }
/// <summary>
/// The time the last frame took to render
/// </summary>
TimeSpan FrameTime { get; }
/// <summary>
/// The amount of frames rendered each second
/// </summary>
public int FrameRate { get; }
/// <summary>
/// Gets or sets whether profiles are rendered each frame by calling their Render method
/// </summary>
bool ProfileRenderingDisabled { get; set; }
/// <summary>
/// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions)
/// </summary>
@@ -41,14 +26,4 @@ public interface ICoreService : IArtemisService
/// Occurs the core has finished initializing
/// </summary>
event EventHandler Initialized;
/// <summary>
/// Occurs whenever a frame is rendering, after modules have rendered
/// </summary>
event EventHandler<FrameRenderingEventArgs> FrameRendering;
/// <summary>
/// Occurs whenever a frame is finished rendering and the render pipeline is closed
/// </summary>
event EventHandler<FrameRenderedEventArgs> FrameRendered;
}
@@ -1,13 +1,109 @@
namespace Artemis.Core.Services;
using System;
using System.Collections.Generic;
using Artemis.Core.DeviceProviders;
namespace Artemis.Core.Services;
/// <summary>
/// A service that allows you manage an <see cref="ArtemisDevice" />
/// </summary>
public interface IDeviceService : IArtemisService
{
/// <summary>
/// Gets a read-only collection containing all enabled devices
/// </summary>
IReadOnlyCollection<ArtemisDevice> EnabledDevices { get; }
/// <summary>
/// Gets a read-only collection containing all registered devices
/// </summary>
IReadOnlyCollection<ArtemisDevice> Devices { get; }
/// <summary>
/// Identifies the device by making it blink white 5 times
/// </summary>
/// <param name="device"></param>
void IdentifyDevice(ArtemisDevice device);
/// <summary>
/// Adds the given device provider and its devices.
/// </summary>
/// <param name="deviceProvider"></param>
void AddDeviceProvider(DeviceProvider deviceProvider);
/// <summary>
/// Removes the given device provider and its devices.
/// </summary>
/// <param name="deviceProvider"></param>
void RemoveDeviceProvider(DeviceProvider deviceProvider);
/// <summary>
/// Applies auto-arranging logic to the surface
/// </summary>
void AutoArrangeDevices();
/// <summary>
/// Apples the provided <see cref="ArtemisLayout" /> to the provided <see cref="ArtemisDevice" />
/// </summary>
/// <param name="device"></param>
/// <param name="layout"></param>
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout);
/// <summary>
/// Enables the provided device
/// </summary>
/// <param name="device">The device to enable</param>
void EnableDevice(ArtemisDevice device);
/// <summary>
/// Disables the provided device
/// </summary>
/// <param name="device">The device to disable</param>
void DisableDevice(ArtemisDevice device);
/// <summary>
/// Saves the configuration of the provided device to persistent storage
/// </summary>
/// <param name="artemisDevice"></param>
void SaveDevice(ArtemisDevice artemisDevice);
/// <summary>
/// Saves the configuration of all current devices to persistent storage
/// </summary>
void SaveDevices();
/// <summary>
/// Occurs when a single device was added.
/// </summary>
event EventHandler<DeviceEventArgs> DeviceAdded;
/// <summary>
/// Occurs when a single device was removed.
/// </summary>
event EventHandler<DeviceEventArgs> DeviceRemoved;
/// <summary>
/// Occurs when a single device was disabled
/// </summary>
event EventHandler<DeviceEventArgs> DeviceEnabled;
/// <summary>
/// Occurs when a single device was disabled.
/// </summary>
event EventHandler<DeviceEventArgs> DeviceDisabled;
/// <summary>
/// Occurs when a device provider was added.
/// </summary>
event EventHandler<DeviceProviderEventArgs> DeviceProviderAdded;
/// <summary>
/// Occurs when a device provider was removed.
/// </summary>
event EventHandler<DeviceProviderEventArgs> DeviceProviderRemoved;
/// <summary>
/// Occurs when the surface has had modifications to its LED collection
/// </summary>
event EventHandler LedsChanged;
}
@@ -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;
/// <summary>
/// Represents a service that manages the render loop and renderers.
/// </summary>
public interface IRenderService : IArtemisService
{
/// <summary>
/// Gets the graphics context to be used for rendering
/// </summary>
IManagedGraphicsContext? GraphicsContext { get; }
/// <summary>
/// Gets the RGB surface to which is being rendered.
/// </summary>
RGBSurface Surface { get; }
/// <summary>
/// Gets a list of registered renderers.
/// </summary>
List<IRenderer> Renderers { get; }
/// <summary>
/// Gets or sets a boolean indicating whether rendering is paused.
/// </summary>
bool IsPaused { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether to flush the RGB.NET LEDs during next update
/// </summary>
bool FlushLeds { get; set; }
/// <summary>
/// The time the last frame took to render
/// </summary>
TimeSpan FrameTime { get; }
/// <summary>
/// The amount of frames rendered each second
/// </summary>
public int FrameRate { get; }
/// <summary>
/// Initializes the render service and starts rendering.
/// </summary>
void Initialize();
/// <summary>
/// Occurs whenever a frame is rendering, after modules have rendered
/// </summary>
event EventHandler<FrameRenderingEventArgs> FrameRendering;
/// <summary>
/// Occurs whenever a frame is finished rendering and the render pipeline is closed
/// </summary>
event EventHandler<FrameRenderedEventArgs> FrameRendered;
}
@@ -1,168 +0,0 @@
using System;
using System.Collections.Generic;
using Artemis.Core.SkiaSharp;
using RGB.NET.Core;
namespace Artemis.Core.Services;
/// <summary>
/// A service that allows you to manage the <see cref="RGBSurface" /> and its contents
/// </summary>
public interface IRgbService : IArtemisService, IDisposable
{
/// <summary>
/// Gets a read-only collection containing all enabled devices
/// </summary>
IReadOnlyCollection<ArtemisDevice> EnabledDevices { get; }
/// <summary>
/// Gets a read-only collection containing all registered devices
/// </summary>
IReadOnlyCollection<ArtemisDevice> Devices { get; }
/// <summary>
/// Gets a dictionary containing all <see cref="ArtemisLed" />s on the surface with their corresponding RGB.NET
/// <see cref="Led" /> as key
/// </summary>
IReadOnlyDictionary<Led, ArtemisLed> LedMap { get; }
/// <summary>
/// Gets or sets the RGB surface rendering is performed on
/// </summary>
RGBSurface Surface { get; set; }
/// <summary>
/// Gets or sets whether rendering should be paused
/// </summary>
bool IsRenderPaused { get; }
/// <summary>
/// Gets a boolean indicating whether the render pipeline is open
/// </summary>
bool RenderOpen { get; }
/// <summary>
/// Gets or sets a boolean indicating whether to flush the RGB.NET LEDs during next update
/// </summary>
bool FlushLeds { get; set; }
/// <summary>
/// Opens the render pipeline
/// </summary>
SKTexture OpenRender();
/// <summary>
/// Closes the render pipeline
/// </summary>
void CloseRender();
/// <summary>
/// Applies the current value of the <c>Core.PreferredGraphicsContext</c> setting to the graphics context.
/// </summary>
/// <param name="forceSoftware">A boolean to indicate whether or not to force the graphics context to software mode.</param>
void ApplyPreferredGraphicsContext(bool forceSoftware);
/// <summary>
/// Updates the graphics context to the provided <paramref name="managedGraphicsContext"></paramref>.
/// <para>Note: The old graphics context will be used until the next frame starts rendering and is disposed afterwards.</para>
/// </summary>
/// <param name="managedGraphicsContext">
/// The new managed graphics context. If <see langword="null" />, software rendering
/// is used.
/// </param>
void UpdateGraphicsContext(IManagedGraphicsContext? managedGraphicsContext);
/// <summary>
/// Adds the given device provider to the <see cref="Surface" />
/// </summary>
/// <param name="deviceProvider"></param>
void AddDeviceProvider(IRGBDeviceProvider deviceProvider);
/// <summary>
/// Removes the given device provider from the <see cref="Surface" />
/// </summary>
/// <param name="deviceProvider"></param>
void RemoveDeviceProvider(IRGBDeviceProvider deviceProvider);
/// <summary>
/// Applies auto-arranging logic to the surface
/// </summary>
void AutoArrangeDevices();
/// <summary>
/// Applies the best available layout for the given <see cref="ArtemisDevice" />
/// </summary>
/// <param name="device">The device to apply the best available layout to</param>
/// <returns>The layout that was applied to the device</returns>
ArtemisLayout? ApplyBestDeviceLayout(ArtemisDevice device);
/// <summary>
/// Apples the provided <see cref="ArtemisLayout" /> to the provided <see cref="ArtemisDevice" />
/// </summary>
/// <param name="device"></param>
/// <param name="layout"></param>
void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout);
/// <summary>
/// Attempts to retrieve the <see cref="ArtemisDevice" /> that corresponds the provided RGB.NET
/// <see cref="IRGBDevice" />
/// </summary>
/// <param name="rgbDevice">
/// The RGB.NET <see cref="IRGBDevice" /> to find the corresponding <see cref="ArtemisDevice" />
/// for
/// </param>
/// <returns>If found, the corresponding <see cref="ArtemisDevice" />; otherwise <see langword="null" />.</returns>
ArtemisDevice? GetDevice(IRGBDevice rgbDevice);
/// <summary>
/// Attempts to retrieve the <see cref="ArtemisLed" /> that corresponds the provided RGB.NET <see cref="Led" />
/// </summary>
/// <param name="led">The RGB.NET <see cref="Led" /> to find the corresponding <see cref="ArtemisLed" /> for </param>
/// <returns>If found, the corresponding <see cref="ArtemisLed" />; otherwise <see langword="null" />.</returns>
ArtemisLed? GetLed(Led led);
/// <summary>
/// Saves the configuration of the provided device to persistent storage
/// </summary>
/// <param name="artemisDevice"></param>
void SaveDevice(ArtemisDevice artemisDevice);
/// <summary>
/// Saves the configuration of all current devices to persistent storage
/// </summary>
void SaveDevices();
/// <summary>
/// Enables the provided device
/// </summary>
/// <param name="device">The device to enable</param>
void EnableDevice(ArtemisDevice device);
/// <summary>
/// Disables the provided device
/// </summary>
/// <param name="device">The device to disable</param>
void DisableDevice(ArtemisDevice device);
/// <summary>
/// Pauses or resumes rendering, method won't return until the current frame finished rendering
/// </summary>
/// <param name="paused"></param>
/// <returns><see langword="true" /> if the pause state was changed; otherwise <see langword="false" />.</returns>
bool SetRenderPaused(bool paused);
/// <summary>
/// Occurs when a single device was added
/// </summary>
event EventHandler<DeviceEventArgs> DeviceAdded;
/// <summary>
/// Occurs when a single device was removed
/// </summary>
event EventHandler<DeviceEventArgs> DeviceRemoved;
/// <summary>
/// Occurs when the surface has had modifications to its LED collection
/// </summary>
event EventHandler LedsChanged;
}
+262
View File
@@ -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<Exception> _updateExceptions = new();
private readonly ILogger _logger;
private readonly IDeviceService _deviceService;
private readonly LazyEnumerable<IGraphicsContextProvider> _graphicsContextProviders;
private readonly PluginSetting<int> _targetFrameRateSetting;
private readonly PluginSetting<double> _renderScaleSetting;
private readonly PluginSetting<string> _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<IGraphicsContextProvider> 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);
}
/// <inheritdoc />
public IManagedGraphicsContext? GraphicsContext { get; private set; }
/// <inheritdoc />
public RGBSurface Surface => _surfaceManager.Surface;
/// <inheritdoc />
public List<IRenderer> Renderers { get; } = new();
/// <inheritdoc />
public bool IsPaused
{
get => _surfaceManager.IsPaused;
set => _surfaceManager.SetPaused(value);
}
/// <inheritdoc />
public int FrameRate { get; private set; }
/// <inheritdoc />
public TimeSpan FrameTime { get; private set; }
/// <inheritdoc />
public bool FlushLeds { get; set; }
/// <inheritdoc />
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);
}
}
/// <inheritdoc />
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<IGraphicsContextProvider> 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<string?, Exception> 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<ArtemisDevice> {e.Device});
}
private void DeviceServiceOnDeviceDisabled(object? sender, DeviceEventArgs e)
{
_surfaceManager.RemoveDevices(new List<ArtemisDevice> {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;
}
/// <inheritdoc />
public void Dispose()
{
IsPaused = true;
_surfaceManager.Dispose();
}
/// <inheritdoc />
public event EventHandler<FrameRenderingEventArgs>? FrameRendering;
/// <inheritdoc />
public event EventHandler<FrameRenderedEventArgs>? FrameRendered;
/// <inheritdoc />
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);
}
}
-613
View File
@@ -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;
/// <summary>
/// Provides wrapped access the RGB.NET
/// </summary>
internal class RgbService : IRgbService
{
private readonly ILogger _logger;
private readonly IPluginManagementService _pluginManagementService;
private readonly IDeviceRepository _deviceRepository;
private readonly LazyEnumerable<IGraphicsContextProvider> _graphicsContextProviders;
private readonly PluginSetting<string> _preferredGraphicsContext;
private readonly PluginSetting<double> _renderScaleSetting;
private readonly PluginSetting<int> _targetFrameRateSetting;
private readonly List<ArtemisDevice> _devices;
private readonly List<ArtemisDevice> _enabledDevices;
private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute};
private Dictionary<Led, ArtemisLed> _ledMap;
private ListLedGroup? _surfaceLedGroup;
private SKTexture? _texture;
public RgbService(ILogger logger,
ISettingsService settingsService,
IPluginManagementService pluginManagementService,
IDeviceRepository deviceRepository,
LazyEnumerable<IGraphicsContextProvider> 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<ArtemisDevice>();
_devices = new List<ArtemisDevice>();
_ledMap = new Dictionary<Led, ArtemisLed>();
EnabledDevices = new ReadOnlyCollection<ArtemisDevice>(_enabledDevices);
Devices = new ReadOnlyCollection<ArtemisDevice>(_devices);
LedMap = new ReadOnlyDictionary<Led, ArtemisLed>(_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<Led, ArtemisLed>(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed));
LedMap = new ReadOnlyDictionary<Led, ArtemisLed>(_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<ArtemisDevice> EnabledDevices { get; }
public IReadOnlyCollection<ArtemisDevice> Devices { get; }
public IReadOnlyDictionary<Led, ArtemisLed> LedMap { get; private set; }
public RGBSurface Surface { get; set; }
public bool IsRenderPaused { get; set; }
public bool RenderOpen { get; private set; }
/// <inheritdoc />
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<ArtemisDevice> 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<Exception> providerExceptions = new();
void DeviceProviderOnException(object? sender, ExceptionEventArgs e)
{
if (e.IsCritical)
providerExceptions.Add(e.Exception);
else
_logger.Warning(e.Exception, "Device provider {deviceProvider} threw non-critical exception", deviceProvider.GetType().Name);
}
_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<ArtemisDevice> 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<DeviceEventArgs>? DeviceAdded;
public event EventHandler<DeviceEventArgs>? 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<IGraphicsContextProvider> 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
}
@@ -41,6 +41,11 @@ public interface IProfileService : IArtemisService
/// Gets or sets a value indicating whether the currently focused profile should receive updates.
/// </summary>
bool UpdateFocusProfile { get; set; }
/// <summary>
/// Gets or sets whether profiles are rendered each frame by calling their Render method
/// </summary>
bool ProfileRenderingDisabled { get; set; }
/// <summary>
/// Creates a copy of the provided profile configuration.
@@ -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<ArtemisKeyboardKeyEventArgs> _pendingKeyboardEvents = new();
private readonly List<ProfileCategory> _profileCategories;
private readonly IProfileRepository _profileRepository;
private readonly List<Exception> _renderExceptions = new();
private readonly List<Exception> _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<ProfileCategory>(_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; }
/// <inheritdoc />
public void UpdateProfiles(double deltaTime)
@@ -182,6 +187,21 @@ internal class ProfileService : IProfileService
}
}
}
/// <inheritdoc />
public void Render(SKCanvas canvas, double delta)
{
if (ProfileRenderingDisabled)
return;
UpdateProfiles(delta);
RenderProfiles(canvas);
}
/// <inheritdoc />
public void PostRender(SKTexture texture)
{
}
/// <inheritdoc />
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
/// <inheritdoc />
public void AdaptProfile(Profile profile)
{
List<ArtemisDevice> devices = _rgbService.EnabledDevices.ToList();
List<ArtemisDevice> 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();
}
+35
View File
@@ -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
);
}
}
-24
View File
@@ -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
}