diff --git a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs index 851fabb62..53dcaa0bd 100644 --- a/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs +++ b/src/Artemis.Core/Models/Profile/Conditions/DataModelConditionGroup.cs @@ -94,19 +94,14 @@ namespace Artemis.Core private bool EvaluateWithOperator(IEnumerable targets) { - switch (BooleanOperator) + return BooleanOperator switch { - case BooleanOperator.And: - return targets.All(c => c.Evaluate()); - case BooleanOperator.Or: - return targets.Any(c => c.Evaluate()); - case BooleanOperator.AndNot: - return targets.All(c => !c.Evaluate()); - case BooleanOperator.OrNot: - return targets.Any(c => !c.Evaluate()); - default: - throw new ArgumentOutOfRangeException(); - } + BooleanOperator.And => targets.All(c => c.Evaluate()), + BooleanOperator.Or => targets.Any(c => c.Evaluate()), + BooleanOperator.AndNot => targets.All(c => !c.Evaluate()), + BooleanOperator.OrNot => targets.Any(c => !c.Evaluate()), + _ => throw new ArgumentOutOfRangeException() + }; } #region IDisposable diff --git a/src/Artemis.Core/RGB.NET/SKTexture.cs b/src/Artemis.Core/RGB.NET/SKTexture.cs index 59f8d0558..87e2c8cfb 100644 --- a/src/Artemis.Core/RGB.NET/SKTexture.cs +++ b/src/Artemis.Core/RGB.NET/SKTexture.cs @@ -10,12 +10,15 @@ namespace Artemis.Core /// public sealed class SKTexture : PixelTexture, IDisposable { + private bool _disposed; + #region Constructors - internal SKTexture(SKBitmap bitmap) - : base(bitmap.Width, bitmap.Height, 4, new AverageByteSampler()) + internal SKTexture(int width, int height, float renderScale) + : base(width, height, 4, new AverageByteSampler()) { - Bitmap = bitmap; + Bitmap = new SKBitmap(new SKImageInfo(width, height, SKColorType.Rgb888x)); + RenderScale = renderScale; } #endregion @@ -40,11 +43,31 @@ namespace Artemis.Core /// /// Gets the color data in RGB format /// - protected override ReadOnlySpan Data => Bitmap.GetPixelSpan(); + protected override ReadOnlySpan Data => _disposed ? new ReadOnlySpan() : Bitmap.GetPixelSpan(); + /// + /// Gets the render scale of the texture + /// + public float RenderScale { get; } + + /// + /// Gets a boolean indicating whether has been called on this texture, indicating it should + /// be replaced + /// + public bool IsInvalid { get; private set; } + + /// + /// Invalidates the texture + /// + public void Invalidate() + { + IsInvalid = true; + } + /// public void Dispose() { + _disposed = true; Bitmap.Dispose(); } diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 19b9b6210..239aaa567 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -29,14 +29,11 @@ namespace Artemis.Core.Services private readonly PluginSetting _loggingLevel; private readonly IPluginManagementService _pluginManagementService; private readonly IProfileService _profileService; - private readonly PluginSetting _renderScale; private readonly IRgbService _rgbService; private readonly List _updateExceptions = new(); private List _dataModelExpansions = new(); private DateTime _lastExceptionLog; private List _modules = new(); - private SKBitmap? _bitmap; - private readonly object _bitmapLock = new(); // ReSharper disable UnusedParameter.Local public CoreService(IKernel kernel, @@ -57,17 +54,13 @@ namespace Artemis.Core.Services _rgbService = rgbService; _profileService = profileService; _loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug); - _renderScale = settingsService.GetSetting("Core.RenderScale", 0.5); _frameStopWatch = new Stopwatch(); StartupArguments = new List(); UpdatePluginCache(); _rgbService.Surface.Updating += SurfaceOnUpdating; - _rgbService.Surface.Updated += SurfaceOnUpdated; - _rgbService.Surface.SurfaceLayoutChanged += SurfaceOnSurfaceLayoutChanged; _loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel(); - _renderScale.SettingChanged += RenderScaleSettingChanged; _pluginManagementService.PluginFeatureEnabled += (sender, args) => UpdatePluginCache(); _pluginManagementService.PluginFeatureDisabled += (sender, args) => UpdatePluginCache(); @@ -75,6 +68,133 @@ namespace Artemis.Core.Services // ReSharper restore UnusedParameter.Local + protected virtual void OnFrameRendering(FrameRenderingEventArgs e) + { + FrameRendering?.Invoke(this, e); + } + + protected virtual void OnFrameRendered(FrameRenderedEventArgs e) + { + FrameRendered?.Invoke(this, e); + } + + private void UpdatePluginCache() + { + _modules = _pluginManagementService.GetFeaturesOfType().Where(p => p.IsEnabled).ToList(); + _dataModelExpansions = _pluginManagementService.GetFeaturesOfType().Where(p => p.IsEnabled).ToList(); + } + + private void ApplyLoggingLevel() + { + string? argument = StartupArguments.FirstOrDefault(a => a.StartsWith("--logging")); + if (argument != null) + { + // Parse the provided log level + string[] parts = argument.Split('='); + if (parts.Length == 2 && Enum.TryParse(typeof(LogEventLevel), parts[1], true, out object? logLevelArgument)) + { + _logger.Information("Setting logging level to {loggingLevel} from startup argument", (LogEventLevel) logLevelArgument!); + LoggerProvider.LoggingLevelSwitch.MinimumLevel = (LogEventLevel) logLevelArgument; + } + else + { + _logger.Warning("Failed to set log level from startup argument {argument}", argument); + _logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value); + LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value; + } + } + else + { + _logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value); + LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value; + } + } + + private void SurfaceOnUpdating(UpdatingEventArgs args) + { + if (_rgbService.IsRenderPaused) + return; + + try + { + _frameStopWatch.Restart(); + lock (_dataModelExpansions) + { + // Update all active modules, check Enabled status because it may go false before before the _dataModelExpansions list is updated + foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.IsEnabled)) + dataModelExpansion.InternalUpdate(args.DeltaTime); + } + + List modules; + lock (_modules) + { + modules = _modules.Where(m => m.IsActivated || m.InternalExpandsMainDataModel) + .OrderBy(m => m.PriorityCategory) + .ThenByDescending(m => m.Priority) + .ToList(); + } + + // Update all active modules + foreach (Module module in modules) + module.InternalUpdate(args.DeltaTime); + + // Render all active modules + SKTexture texture =_rgbService.OpenRender(); + + using (SKCanvas canvas = new(texture.Bitmap)) + { + canvas.Scale(texture.RenderScale); + canvas.Clear(new SKColor(0, 0, 0)); + // While non-activated modules may be updated above if they expand the main data model, they may never render + if (!ModuleRenderingDisabled) + { + foreach (Module module in modules.Where(m => m.IsActivated)) + module.InternalRender(args.DeltaTime, canvas, texture.Bitmap.Info); + } + + OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface)); + } + + OnFrameRendered(new FrameRenderedEventArgs(texture, _rgbService.Surface)); + } + catch (Exception e) + { + _updateExceptions.Add(e); + } + finally + { + _frameStopWatch.Stop(); + FrameTime = _frameStopWatch.Elapsed; + _rgbService.CloseRender(); + + LogUpdateExceptions(); + } + } + + private void LogUpdateExceptions() + { + // Only log update exceptions every 10 seconds to avoid spamming the logs + if (DateTime.Now - _lastExceptionLog < TimeSpan.FromSeconds(10)) + return; + _lastExceptionLog = DateTime.Now; + + if (!_updateExceptions.Any()) + return; + + // Group by stack trace, that should gather up duplicate exceptions + foreach (IGrouping exceptions in _updateExceptions.GroupBy(e => e.StackTrace)) + _logger.Warning(exceptions.First(), "Exception was thrown {count} times during update in the last 10 seconds", exceptions.Count()); + + // When logging is finished start with a fresh slate + _updateExceptions.Clear(); + } + + private void OnInitialized() + { + IsInitialized = true; + Initialized?.Invoke(this, EventArgs.Empty); + } + public TimeSpan FrameTime { get; private set; } public bool ModuleRenderingDisabled { get; set; } public List StartupArguments { get; set; } @@ -123,16 +243,6 @@ namespace Artemis.Core.Services OnInitialized(); } - protected virtual void OnFrameRendering(FrameRenderingEventArgs e) - { - FrameRendering?.Invoke(this, e); - } - - protected virtual void OnFrameRendered(FrameRenderedEventArgs e) - { - FrameRendered?.Invoke(this, e); - } - public void PlayIntroAnimation() { IntroAnimation intro = new(_logger, _profileService, _rgbService.EnabledDevices); @@ -156,155 +266,8 @@ namespace Artemis.Core.Services }); } - private void UpdatePluginCache() - { - _modules = _pluginManagementService.GetFeaturesOfType().Where(p => p.IsEnabled).ToList(); - _dataModelExpansions = _pluginManagementService.GetFeaturesOfType().Where(p => p.IsEnabled).ToList(); - } - - private void ApplyLoggingLevel() - { - string? argument = StartupArguments.FirstOrDefault(a => a.StartsWith("--logging")); - if (argument != null) - { - // Parse the provided log level - string[] parts = argument.Split('='); - if (parts.Length == 2 && Enum.TryParse(typeof(LogEventLevel), parts[1], true, out object? logLevelArgument)) - { - _logger.Information("Setting logging level to {loggingLevel} from startup argument", (LogEventLevel)logLevelArgument!); - LoggerProvider.LoggingLevelSwitch.MinimumLevel = (LogEventLevel)logLevelArgument; - } - else - { - _logger.Warning("Failed to set log level from startup argument {argument}", argument); - _logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value); - LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value; - } - } - else - { - _logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value); - LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value; - } - } - - private void SurfaceOnUpdating(UpdatingEventArgs args) - { - if (_rgbService.IsRenderPaused) - return; - - try - { - _frameStopWatch.Restart(); - lock (_dataModelExpansions) - { - // Update all active modules, check Enabled status because it may go false before before the _dataModelExpansions list is updated - foreach (BaseDataModelExpansion dataModelExpansion in _dataModelExpansions.Where(e => e.IsEnabled)) - dataModelExpansion.InternalUpdate(args.DeltaTime); - } - - List modules; - lock (_modules) - { - modules = _modules.Where(m => m.IsActivated || m.InternalExpandsMainDataModel) - .OrderBy(m => m.PriorityCategory) - .ThenByDescending(m => m.Priority) - .ToList(); - } - - // Update all active modules - foreach (Module module in modules) - module.InternalUpdate(args.DeltaTime); - - lock (_bitmapLock) - { - if (_bitmap == null) - { - _bitmap = CreateBitmap(); - _rgbService.UpdateTexture(_bitmap); - } - - // Render all active modules - using SKCanvas canvas = new(_bitmap); - canvas.Scale((float)_renderScale.Value); - canvas.Clear(new SKColor(0, 0, 0)); - if (!ModuleRenderingDisabled) - // While non-activated modules may be updated above if they expand the main data model, they may never render - foreach (Module module in modules.Where(m => m.IsActivated)) - module.InternalRender(args.DeltaTime, canvas, _bitmap.Info); - - OnFrameRendering(new FrameRenderingEventArgs(canvas, args.DeltaTime, _rgbService.Surface)); - } - } - catch (Exception e) - { - _updateExceptions.Add(e); - } - finally - { - _frameStopWatch.Stop(); - FrameTime = _frameStopWatch.Elapsed; - - LogUpdateExceptions(); - } - } - - private SKBitmap CreateBitmap() - { - float width = MathF.Min(_rgbService.Surface.Boundary.Size.Width * (float)_renderScale.Value, 4096); - float height = MathF.Min(_rgbService.Surface.Boundary.Size.Height * (float)_renderScale.Value, 4096); - return new SKBitmap(new SKImageInfo(width.RoundToInt(), height.RoundToInt(), SKColorType.Rgb888x)); - } - - private void InvalidateBitmap() - { - lock (_bitmapLock) - { - _bitmap = null; - } - } - - private void SurfaceOnSurfaceLayoutChanged(SurfaceLayoutChangedEventArgs args) => InvalidateBitmap(); - private void RenderScaleSettingChanged(object? sender, EventArgs e) => InvalidateBitmap(); - - 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 SurfaceOnUpdated(UpdatedEventArgs args) - { - if (_rgbService.IsRenderPaused) - return; - - OnFrameRendered(new FrameRenderedEventArgs(_rgbService.Texture!, _rgbService.Surface)); - } - - #region Events - public event EventHandler? Initialized; public event EventHandler? FrameRendering; public event EventHandler? FrameRendered; - - private void OnInitialized() - { - IsInitialized = true; - Initialized?.Invoke(this, EventArgs.Empty); - } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/Interfaces/ICoreService.cs b/src/Artemis.Core/Services/Interfaces/ICoreService.cs index f3d934d11..4e1077c0f 100644 --- a/src/Artemis.Core/Services/Interfaces/ICoreService.cs +++ b/src/Artemis.Core/Services/Interfaces/ICoreService.cs @@ -54,7 +54,7 @@ namespace Artemis.Core.Services event EventHandler FrameRendering; /// - /// Occurs whenever a frame is finished rendering and processed by RGB.NET + /// Occurs whenever a frame is finished rendering and the render pipeline is closed /// event EventHandler FrameRendered; } diff --git a/src/Artemis.Core/Services/Interfaces/IRgbService.cs b/src/Artemis.Core/Services/Interfaces/IRgbService.cs index 565fb31a3..099b4bb3f 100644 --- a/src/Artemis.Core/Services/Interfaces/IRgbService.cs +++ b/src/Artemis.Core/Services/Interfaces/IRgbService.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using RGB.NET.Core; -using SkiaSharp; namespace Artemis.Core.Services { @@ -31,31 +30,25 @@ namespace Artemis.Core.Services /// RGBSurface Surface { get; set; } - /// - /// Gets the texture brush used to convert the rendered frame to LED-colors - /// - TextureBrush TextureBrush { get; } - - /// - /// Gets the texture used to convert the rendered frame to LED-colors - /// - SKTexture? Texture { get; } - - /// - /// Gets the update trigger that drives the render loop - /// - TimerUpdateTrigger UpdateTrigger { get; } - /// /// Gets or sets whether rendering should be paused /// bool IsRenderPaused { get; set; } /// - /// Recreates the Texture to use the given + /// Gets a boolean indicating whether the render pipeline is open /// - /// - void UpdateTexture(SKBitmap bitmap); + bool RenderOpen { get; } + + /// + /// Opens the render pipeline + /// + SKTexture OpenRender(); + + /// + /// Closes the render pipeline + /// + void CloseRender(); /// /// Adds the given device provider to the diff --git a/src/Artemis.Core/Services/RgbService.cs b/src/Artemis.Core/Services/RgbService.cs index 6d322b8d0..28937a7c3 100644 --- a/src/Artemis.Core/Services/RgbService.cs +++ b/src/Artemis.Core/Services/RgbService.cs @@ -17,16 +17,18 @@ namespace Artemis.Core.Services /// internal class RgbService : IRgbService { + private readonly IDeviceRepository _deviceRepository; private readonly List _devices; private readonly List _enabledDevices; - private Dictionary _ledMap; - private readonly ILogger _logger; private readonly IPluginManagementService _pluginManagementService; - private readonly IDeviceRepository _deviceRepository; + private readonly PluginSetting _renderScaleSetting; private readonly PluginSetting _targetFrameRateSetting; - private ListLedGroup? _surfaceLedGroup; + private readonly TextureBrush _textureBrush = new(ITexture.Empty) {CalculationMode = RenderMode.Absolute}; + private Dictionary _ledMap; private bool _modifyingProviders; + private ListLedGroup? _surfaceLedGroup; + private SKTexture? _texture; public RgbService(ILogger logger, ISettingsService settingsService, IPluginManagementService pluginManagementService, IDeviceRepository deviceRepository) { @@ -34,6 +36,7 @@ namespace Artemis.Core.Services _pluginManagementService = pluginManagementService; _deviceRepository = deviceRepository; _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 25); + _renderScaleSetting = settingsService.GetSetting("Core.RenderScale", 0.5); Surface = new RGBSurface(); @@ -45,10 +48,72 @@ namespace Artemis.Core.Services _devices = new List(); _ledMap = new Dictionary(); - UpdateTrigger = new TimerUpdateTrigger { UpdateFrequency = 1.0 / _targetFrameRateSetting.Value }; + UpdateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / _targetFrameRateSetting.Value}; Surface.RegisterUpdateTrigger(UpdateTrigger); } + 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 SurfaceOnLayoutChanged(SurfaceLayoutChangedEventArgs args) + { + UpdateLedGroup(); + } + + private void UpdateLedGroup() + { + lock (_devices) + { + if (_modifyingProviders) + return; + + _ledMap = new Dictionary(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed)); + + 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(); + } + } + } + + private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e) + { + UpdateTrigger.UpdateFrequency = 1.0 / _targetFrameRateSetting.Value; + } + + private void SurfaceOnException(ExceptionEventArgs args) + { + _logger.Warning("Surface threw e"); + throw args.Exception; + } + + private void OnDeviceAdded(DeviceEventArgs e) + { + DeviceAdded?.Invoke(this, e); + } + public IReadOnlyCollection EnabledDevices => _enabledDevices.AsReadOnly(); public IReadOnlyCollection Devices => _devices.AsReadOnly(); public IReadOnlyDictionary LedMap => new ReadOnlyDictionary(_ledMap); @@ -56,11 +121,8 @@ namespace Artemis.Core.Services /// public RGBSurface Surface { get; set; } - public TimerUpdateTrigger UpdateTrigger { get; } - public TextureBrush TextureBrush { get; private set; } = new(ITexture.Empty) { CalculationMode = RenderMode.Absolute }; - public SKTexture? Texture { get; private set; } - public bool IsRenderPaused { get; set; } + public bool RenderOpen { get; private set; } public void AddDeviceProvider(IRGBDeviceProvider deviceProvider) { @@ -88,7 +150,7 @@ namespace Artemis.Core.Services { ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice); AddDevice(artemisDevice); - _logger.Debug("Device provider {deviceProvider} added {deviceName}", deviceProvider.GetType().Name, rgbDevice.DeviceInfo?.DeviceName); + _logger.Debug("Device provider {deviceProvider} added {deviceName}", deviceProvider.GetType().Name, rgbDevice.DeviceInfo.DeviceName); } _devices.Sort((a, b) => a.ZIndex - b.ZIndex); @@ -134,46 +196,6 @@ namespace Artemis.Core.Services } } - public void UpdateTexture(SKBitmap bitmap) - { - SKTexture? oldTexture = Texture; - Texture = new SKTexture(bitmap); - TextureBrush.Texture = Texture; - - oldTexture?.Dispose(); - } - - private void SurfaceOnLayoutChanged(SurfaceLayoutChangedEventArgs args) => UpdateLedGroup(); - - private void UpdateLedGroup() - { - lock (_devices) - { - if (_modifyingProviders) - return; - - _ledMap = new Dictionary(_devices.SelectMany(d => d.Leds).ToDictionary(l => l.RgbLed)); - - 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(); - } - } - } - - #region IDisposable public void Dispose() { @@ -183,6 +205,48 @@ namespace Artemis.Core.Services Surface.Dispose(); } + public event EventHandler? DeviceAdded; + public event EventHandler? DeviceRemoved; + public event EventHandler? LedsChanged; + + #region Rendering + + 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; + } + + public void CreateTexture() + { + if (RenderOpen) + throw new ArtemisCoreException("Cannot update the texture while rendering"); + + SKTexture? oldTexture = _texture; + + float renderScale = (float) _renderScaleSetting.Value; + int width = Math.Max(1, MathF.Min(Surface.Boundary.Size.Width * renderScale, 4096).RoundToInt()); + int height = Math.Max(1, MathF.Min(Surface.Boundary.Size.Height * renderScale, 4096).RoundToInt()); + _texture = new SKTexture(width, height, renderScale); + _textureBrush.Texture = _texture; + + oldTexture?.Dispose(); + } + #endregion #region EnabledDevices @@ -197,7 +261,7 @@ namespace Artemis.Core.Services public ArtemisLayout ApplyBestDeviceLayout(ArtemisDevice device) { ArtemisLayout layout; - + // Configured layout path takes precedence over all other options if (device.CustomLayoutPath != null) { @@ -233,7 +297,7 @@ namespace Artemis.Core.Services private ArtemisLayout LoadDefaultLayout(ArtemisDevice device) { - return new ArtemisLayout("NYI", LayoutSource.Default); + return new("NYI", LayoutSource.Default); } public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout layout, bool createMissingLeds, bool removeExessiveLeds) @@ -347,43 +411,5 @@ namespace Artemis.Core.Services } #endregion - - #region Event handlers - - private void TargetFrameRateSettingOnSettingChanged(object? sender, EventArgs e) - { - UpdateTrigger.UpdateFrequency = 1.0 / _targetFrameRateSetting.Value; - } - - private void SurfaceOnException(ExceptionEventArgs args) - { - _logger.Warning("Surface threw e"); - throw args.Exception; - } - - #endregion - - #region Events - - public event EventHandler? DeviceAdded; - public event EventHandler? DeviceRemoved; - public event EventHandler? LedsChanged; - - private void OnDeviceAdded(DeviceEventArgs e) - { - DeviceAdded?.Invoke(this, e); - } - - protected virtual void OnDeviceRemoved(DeviceEventArgs e) - { - DeviceRemoved?.Invoke(this, e); - } - - protected virtual void OnLedsChanged() - { - LedsChanged?.Invoke(this, EventArgs.Empty); - } - - #endregion } } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Controls/ColorPicker.xaml.cs b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml.cs index 9a5a4fb02..84656d58a 100644 --- a/src/Artemis.UI.Shared/Controls/ColorPicker.xaml.cs +++ b/src/Artemis.UI.Shared/Controls/ColorPicker.xaml.cs @@ -245,7 +245,8 @@ namespace Artemis.UI.Shared private void PopupOpened() { - RecentColorsContainer.ItemsSource = new ObservableCollection(_colorPickerService.RecentColors); + if (_colorPickerService != null) + RecentColorsContainer.ItemsSource = new ObservableCollection(_colorPickerService.RecentColors); } private void SelectRecentColor(object sender, MouseButtonEventArgs e) diff --git a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs index f7ead0cbb..12292fc6d 100644 --- a/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs +++ b/src/Artemis.UI.Shared/DataModelVisualization/Input/DataModelDynamicViewModel.cs @@ -26,7 +26,7 @@ namespace Artemis.UI.Shared.Input private DataModelPath? _dataModelPath; private DataModelPropertiesViewModel? _dataModelViewModel; private bool _displaySwitchButton; - private Type[] _filterTypes = new Type[0]; + private Type[] _filterTypes = Array.Empty(); private bool _isDataModelViewModelOpen; private bool _isEnabled = true; private string _placeholder = "Select a property"; @@ -143,7 +143,8 @@ namespace Artemis.UI.Shared.Input if (value) { UpdateDataModelVisualization(); - OpenSelectedValue(DataModelViewModel); + if (DataModelViewModel != null) + OpenSelectedValue(DataModelViewModel); } } } @@ -242,7 +243,7 @@ namespace Artemis.UI.Shared.Input private void ExecuteSelectPropertyCommand(object? context) { - if (!(context is DataModelVisualizationViewModel selected)) + if (context is not DataModelVisualizationViewModel selected) return; ChangeDataModelPath(selected.DataModelPath); diff --git a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs index e3b54179a..b45c7728b 100644 --- a/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs +++ b/src/Artemis.UI/Screens/ProfileEditor/LayerProperties/DataBindings/ConditionalDataBinding/ConditionalDataBindingModeViewModel.cs @@ -57,7 +57,7 @@ namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.DataBindings.Conditio base.OnClose(); } - private void CoreServiceOnFrameRendered(object? sender, FrameRenderedEventArgs e) + private void CoreServiceOnFrameRendered(object sender, FrameRenderedEventArgs e) { foreach (DataBindingConditionViewModel dataBindingConditionViewModel in Items) dataBindingConditionViewModel.Evaluate(); diff --git a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs index e07bf9d37..b66263387 100644 --- a/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Debug/Tabs/RenderDebugViewModel.cs @@ -64,43 +64,29 @@ namespace Artemis.UI.Screens.Settings.Debug.Tabs private void CoreServiceOnFrameRendered(object sender, FrameRenderedEventArgs e) { - Execute.PostToUIThread(() => + Execute.OnUIThreadSync(() => { - // TODO: Remove, frames shouldn't even be rendered if this is the case - if (e.Texture.Bitmap.Pixels.Length == 0) - return; - SKImageInfo bitmapInfo = e.Texture.Bitmap.Info; RenderHeight = bitmapInfo.Height; RenderWidth = bitmapInfo.Width; - if (!(CurrentFrame is WriteableBitmap writeableBitmap) || - writeableBitmap.Width != bitmapInfo.Width || - writeableBitmap.Height != bitmapInfo.Height) + // ReSharper disable twice CompareOfFloatsByEqualityOperator + if (CurrentFrame is not WriteableBitmap writable || writable.Width != bitmapInfo.Width || writable.Height != bitmapInfo.Height) { CurrentFrame = e.Texture.Bitmap.ToWriteableBitmap(); return; } - - try + + using SKImage skImage = SKImage.FromPixels(e.Texture.Bitmap.PeekPixels()); + SKImageInfo info = new(skImage.Width, skImage.Height); + writable.Lock(); + using (SKPixmap pixmap = new(info, writable.BackBuffer, writable.BackBufferStride)) { - using (SKImage skiaImage = SKImage.FromPixels(e.Texture.Bitmap.PeekPixels())) - { - SKImageInfo info = new(skiaImage.Width, skiaImage.Height); - writeableBitmap.Lock(); - using (SKPixmap pixmap = new(info, writeableBitmap.BackBuffer, writeableBitmap.BackBufferStride)) - { - skiaImage.ReadPixels(pixmap, 0, 0); - } + skImage.ReadPixels(pixmap, 0, 0); + } - writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, writeableBitmap.PixelWidth, writeableBitmap.PixelHeight)); - writeableBitmap.Unlock(); - } - } - catch (AccessViolationException) - { - // oops - } + writable.AddDirtyRect(new Int32Rect(0, 0, writable.PixelWidth, writable.PixelHeight)); + writable.Unlock(); }); } diff --git a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs index c1472c721..760f3fb96 100644 --- a/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Device/DeviceDialogViewModel.cs @@ -192,7 +192,7 @@ namespace Artemis.UI.Screens.Settings.Device #region Event handlers - private void DeviceOnDeviceUpdated(object? sender, EventArgs e) + private void DeviceOnDeviceUpdated(object sender, EventArgs e) { NotifyOfPropertyChange(nameof(CanExportLayout)); }