diff --git a/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs b/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs index 11871df5c..7f75d77f9 100644 --- a/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs +++ b/src/Artemis.Core/Extensions/RgbDeviceExtensions.cs @@ -1,4 +1,5 @@ -using System.Text; +using System.Linq; +using System.Text; using RGB.NET.Core; using SkiaSharp; @@ -18,6 +19,18 @@ internal static class RgbDeviceExtensions builder.Append(rgbDevice.DeviceInfo.DeviceType); return builder.ToString(); } + + public static void EnsureValidDimensions(this IRGBDevice rgbDevice) + { + if (rgbDevice.Location == Point.Invalid) + rgbDevice.Location = new Point(0, 0); + + if (rgbDevice.Size == Size.Invalid) + { + Rectangle ledRectangle = new(rgbDevice.Select(x => x.Boundary)); + rgbDevice.Size = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y); + } + } } internal static class RgbRectangleExtensions diff --git a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs index ef29c01f7..914b461c5 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisDevice.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisDevice.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; using System.Linq; using Artemis.Core.DeviceProviders; -using Artemis.Core.Services; using Artemis.Storage.Entities.Surface; using RGB.NET.Core; using SkiaSharp; @@ -22,13 +23,13 @@ public class ArtemisDevice : CorePropertyChanged internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider) { + rgbDevice.EnsureValidDimensions(); _originalLeds = new List(rgbDevice.Select(l => new OriginalLed(l))); - Rectangle ledRectangle = new(rgbDevice.Select(x => x.Boundary)); - _originalSize = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y); + _originalSize = rgbDevice.Size; + RgbDevice = rgbDevice; Identifier = rgbDevice.GetDeviceIdentifier(); DeviceEntity = new DeviceEntity(); - RgbDevice = rgbDevice; DeviceProvider = deviceProvider; Rotation = 0; @@ -45,22 +46,26 @@ public class ArtemisDevice : CorePropertyChanged InputMappings = new Dictionary(); Categories = new HashSet(); - UpdateLeds(); + RgbDevice.ColorCorrections.Clear(); + RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this)); + + CreateArtemisLeds(false); ApplyKeyboardLayout(); - ApplyToEntity(); - ApplyDefaultCategories(); CalculateRenderProperties(); + Save(); + + RgbDevice.PropertyChanged += RgbDeviceOnPropertyChanged; } internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, DeviceEntity deviceEntity) { + rgbDevice.EnsureValidDimensions(); _originalLeds = new List(rgbDevice.Select(l => new OriginalLed(l))); - Rectangle ledRectangle = new(rgbDevice.Select(x => x.Boundary)); - _originalSize = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y); + _originalSize = rgbDevice.Size; + RgbDevice = rgbDevice; Identifier = rgbDevice.GetDeviceIdentifier(); DeviceEntity = deviceEntity; - RgbDevice = rgbDevice; DeviceProvider = deviceProvider; LedIds = new ReadOnlyDictionary(new Dictionary()); @@ -72,8 +77,15 @@ public class ArtemisDevice : CorePropertyChanged foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers) InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier)); - UpdateLeds(); + RgbDevice.ColorCorrections.Clear(); + RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this)); + + CreateArtemisLeds(false); + Load(); ApplyKeyboardLayout(); + CalculateRenderProperties(); + + RgbDevice.PropertyChanged += RgbDeviceOnPropertyChanged; } /// @@ -149,6 +161,8 @@ public class ArtemisDevice : CorePropertyChanged set { DeviceEntity.X = value; + if (RgbDevice.Surface != null) + ApplyLocation(DeviceEntity.X, DeviceEntity.Y); OnPropertyChanged(nameof(X)); } } @@ -162,6 +176,8 @@ public class ArtemisDevice : CorePropertyChanged set { DeviceEntity.Y = value; + if (RgbDevice.Surface != null) + ApplyLocation(DeviceEntity.X, DeviceEntity.Y); OnPropertyChanged(nameof(Y)); } } @@ -175,6 +191,8 @@ public class ArtemisDevice : CorePropertyChanged set { DeviceEntity.Rotation = value; + if (RgbDevice.Surface != null) + RgbDevice.Rotation = DeviceEntity.Rotation; OnPropertyChanged(nameof(Rotation)); } } @@ -188,6 +206,8 @@ public class ArtemisDevice : CorePropertyChanged set { DeviceEntity.Scale = value; + if (RgbDevice.Surface != null) + RgbDevice.Scale = DeviceEntity.Scale; OnPropertyChanged(nameof(Scale)); } } @@ -412,8 +432,7 @@ public class ArtemisDevice : CorePropertyChanged case RGBDeviceType.Mousepad: case RGBDeviceType.HeadsetStand: case RGBDeviceType.Keypad: - if (!Categories.Contains(DeviceCategory.Peripherals)) - Categories.Add(DeviceCategory.Peripherals); + Categories.Add(DeviceCategory.Peripherals); break; case RGBDeviceType.Mainboard: case RGBDeviceType.GraphicsCard: @@ -421,20 +440,16 @@ public class ArtemisDevice : CorePropertyChanged case RGBDeviceType.Fan: case RGBDeviceType.LedStripe: case RGBDeviceType.Cooler: - if (!Categories.Contains(DeviceCategory.Case)) - Categories.Add(DeviceCategory.Case); + Categories.Add(DeviceCategory.Case); break; case RGBDeviceType.Speaker: - if (!Categories.Contains(DeviceCategory.Desk)) - Categories.Add(DeviceCategory.Desk); + Categories.Add(DeviceCategory.Desk); break; case RGBDeviceType.Monitor: - if (!Categories.Contains(DeviceCategory.Monitor)) - Categories.Add(DeviceCategory.Monitor); + Categories.Add(DeviceCategory.Monitor); break; case RGBDeviceType.LedMatrix: - if (!Categories.Contains(DeviceCategory.Room)) - Categories.Add(DeviceCategory.Room); + Categories.Add(DeviceCategory.Room); break; } } @@ -461,40 +476,36 @@ public class ArtemisDevice : CorePropertyChanged /// internal void ApplyLayout(ArtemisLayout? layout, bool createMissingLeds, bool removeExcessiveLeds) { - if (layout == null) + if (layout != null && layout.IsValid && createMissingLeds && !DeviceProvider.CreateMissingLedsSupported) + throw new ArtemisCoreException($"Cannot apply layout with {nameof(createMissingLeds)} set to true because the device provider does not support it"); + if (layout != null && layout.IsValid && removeExcessiveLeds && !DeviceProvider.RemoveExcessiveLedsSupported) + throw new ArtemisCoreException($"Cannot apply layout with {nameof(removeExcessiveLeds)} set to true because the device provider does not support it"); + + // Always clear the current layout + ClearLayout(); + + // If a valid layout was supplied, apply the layout to the device + if (layout != null && layout.IsValid) { - ClearLayout(); - UpdateLeds(); - - CalculateRenderProperties(); - OnDeviceUpdated(); - return; + layout.ApplyToDevice(RgbDevice, createMissingLeds, removeExcessiveLeds); + Layout = layout; + } + else + { + Layout = null; } - if (createMissingLeds && !DeviceProvider.CreateMissingLedsSupported) - throw new ArtemisCoreException($"Cannot apply layout with {nameof(createMissingLeds)} " + - "set to true because the device provider does not support it"); - if (removeExcessiveLeds && !DeviceProvider.RemoveExcessiveLedsSupported) - 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); - + // Recreate Artemis LEDs + CreateArtemisLeds(true); + // Calculate render properties with the new layout CalculateRenderProperties(); - OnDeviceUpdated(); } private void ClearLayout() { if (Layout == null) return; - + RgbDevice.DeviceInfo.LayoutMetadata = null; RgbDevice.Size = _originalSize; Layout = null; @@ -505,7 +516,7 @@ public class ArtemisDevice : CorePropertyChanged RgbDevice.AddLed(originalLed.Id, originalLed.Location, originalLed.Size, originalLed.CustomData); } - internal void ApplyToEntity() + internal void Save() { // Other properties are computed DeviceEntity.Id = Identifier; @@ -513,13 +524,7 @@ public class ArtemisDevice : CorePropertyChanged DeviceEntity.InputIdentifiers.Clear(); foreach (ArtemisDeviceInputIdentifier identifier in InputIdentifiers) - { - DeviceEntity.InputIdentifiers.Add(new DeviceInputIdentifierEntity - { - InputProvider = identifier.InputProvider, - Identifier = identifier.Identifier - }); - } + DeviceEntity.InputIdentifiers.Add(new DeviceInputIdentifierEntity {InputProvider = identifier.InputProvider, Identifier = identifier.Identifier}); DeviceEntity.InputMappings.Clear(); foreach ((ArtemisLed? original, ArtemisLed? mapped) in InputMappings) @@ -530,31 +535,19 @@ public class ArtemisDevice : CorePropertyChanged DeviceEntity.Categories.Add((int) deviceCategory); } - internal void ApplyToRgbDevice() + internal void Load() { - RgbDevice.Rotation = DeviceEntity.Rotation; - RgbDevice.Scale = DeviceEntity.Scale; - - // Workaround for device rotation not applying - if (DeviceEntity.X == 0 && DeviceEntity.Y == 0) - RgbDevice.Location = new Point(1, 1); - RgbDevice.Location = new Point(DeviceEntity.X, DeviceEntity.Y); - InputIdentifiers.Clear(); foreach (DeviceInputIdentifierEntity identifierEntity in DeviceEntity.InputIdentifiers) InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(identifierEntity.InputProvider, identifierEntity.Identifier)); - if (!RgbDevice.ColorCorrections.Any()) - RgbDevice.ColorCorrections.Add(new ScaleColorCorrection(this)); - Categories.Clear(); foreach (int deviceEntityCategory in DeviceEntity.Categories) Categories.Add((DeviceCategory) deviceEntityCategory); if (!Categories.Any()) ApplyDefaultCategories(); - CalculateRenderProperties(); - OnDeviceUpdated(); + LoadInputMappings(); } internal void CalculateRenderProperties() @@ -571,13 +564,27 @@ public class ArtemisDevice : CorePropertyChanged path.AddRect(artemisLed.AbsoluteRectangle); Path = path; + + OnDeviceUpdated(); } - private void UpdateLeds() + private void CreateArtemisLeds(bool loadInputMappings) { Leds = RgbDevice.Select(l => new ArtemisLed(l, this)).ToList().AsReadOnly(); LedIds = new ReadOnlyDictionary(Leds.ToDictionary(l => l.RgbLed.Id, l => l)); + + if (loadInputMappings) + LoadInputMappings(); + } + private void UpdateArtemisLeds() + { + foreach (ArtemisLed artemisLed in Leds) + artemisLed.CalculateRectangles(); + } + + private void LoadInputMappings() + { InputMappings.Clear(); foreach (InputMappingEntity deviceEntityInputMapping in DeviceEntity.InputMappings) { @@ -604,6 +611,27 @@ public class ArtemisDevice : CorePropertyChanged else LogicalLayout = DeviceEntity.LogicalLayout; } + + private void RgbDeviceOnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName != nameof(IRGBDevice.Surface) || RgbDevice.Surface == null) + return; + + RgbDevice.Rotation = DeviceEntity.Rotation; + RgbDevice.Scale = DeviceEntity.Scale; + ApplyLocation(DeviceEntity.X, DeviceEntity.Y); + } + + private void ApplyLocation(float x, float y) + { + // Workaround for device rotation not applying + if (x == 0 && y == 0) + RgbDevice.Location = new Point(1, 1); + RgbDevice.Location = new Point(x, y); + + UpdateArtemisLeds(); + CalculateRenderProperties(); + } } /// diff --git a/src/Artemis.Core/Models/Surface/ArtemisLed.cs b/src/Artemis.Core/Models/Surface/ArtemisLed.cs index f3d0500e1..8d9cae40e 100644 --- a/src/Artemis.Core/Models/Surface/ArtemisLed.cs +++ b/src/Artemis.Core/Models/Surface/ArtemisLed.cs @@ -1,4 +1,5 @@ -using RGB.NET.Core; +using System.Linq; +using RGB.NET.Core; using SkiaSharp; namespace Artemis.Core; @@ -15,6 +16,9 @@ public class ArtemisLed : CorePropertyChanged { RgbLed = led; Device = device; + Layout = device.Layout?.Leds.FirstOrDefault(l => l.RgbLayout.Id == led.Id.ToString()); + Layout?.ApplyCustomLedData(Device); + CalculateRectangles(); } diff --git a/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs b/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs index a931dcb0a..f44e3185d 100644 --- a/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs +++ b/src/Artemis.Core/Models/Surface/Layout/ArtemisLayout.cs @@ -36,11 +36,6 @@ public class ArtemisLayout /// public LayoutSource Source { get; } - /// - /// Gets the device this layout is applied to - /// - public ArtemisDevice? Device { get; private set; } - /// /// Gets a boolean indicating whether a valid layout was loaded /// @@ -69,7 +64,7 @@ public class ArtemisLayout /// /// Applies the layout to the provided device /// - public void ApplyTo(IRGBDevice device, bool createMissingLeds = false, bool removeExcessiveLeds = false) + public void ApplyToDevice(IRGBDevice device, bool createMissingLeds = false, bool removeExcessiveLeds = false) { device.Size = new Size(MathF.Round(RgbLayout.Width), MathF.Round(RgbLayout.Height)); device.DeviceInfo.LayoutMetadata = RgbLayout.CustomData; @@ -124,13 +119,6 @@ public class ArtemisLayout } } - internal void ApplyDevice(ArtemisDevice artemisDevice) - { - Device = artemisDevice; - foreach (ArtemisLedLayout artemisLedLayout in Leds) - artemisLedLayout.ApplyDevice(Device); - } - internal static ArtemisLayout? GetDefaultLayout(ArtemisDevice device) { string layoutFolder = Path.Combine(Constants.ApplicationFolder, "DefaultLayouts", "Artemis"); diff --git a/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs b/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs index 025732df5..29872c4b6 100644 --- a/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs +++ b/src/Artemis.Core/Models/Surface/Layout/ArtemisLedLayout.cs @@ -27,11 +27,6 @@ public class ArtemisLedLayout /// public ILedLayout RgbLayout { get; } - /// - /// Gets the LED this layout is applied to - /// - public ArtemisLed? Led { get; protected set; } - /// /// Gets the name of the logical layout this LED belongs to /// @@ -46,17 +41,9 @@ public class ArtemisLedLayout /// Gets the custom layout data embedded in the RGB.NET layout /// public LayoutCustomLedData LayoutCustomLedData { get; } + - internal void ApplyDevice(ArtemisDevice device) - { - Led = device.Leds.FirstOrDefault(d => d.RgbLed.Id.ToString() == RgbLayout.Id); - if (Led != null) - Led.Layout = this; - - ApplyCustomLedData(device); - } - - private void ApplyCustomLedData(ArtemisDevice artemisDevice) + internal void ApplyCustomLedData(ArtemisDevice artemisDevice) { if (LayoutCustomLedData.LogicalLayouts == null || !LayoutCustomLedData.LogicalLayouts.Any()) return; diff --git a/src/Artemis.Core/Plugins/Modules/DataModel.cs b/src/Artemis.Core/Plugins/Modules/DataModel.cs index 880fffc4a..6d80e3b29 100644 --- a/src/Artemis.Core/Plugins/Modules/DataModel.cs +++ b/src/Artemis.Core/Plugins/Modules/DataModel.cs @@ -14,8 +14,9 @@ namespace Artemis.Core.Modules; /// public abstract class DataModel { + private const StringComparison PathsStringComparison = StringComparison.OrdinalIgnoreCase; private readonly List _activePaths = new(); - private readonly HashSet _activePathsHashSet = new(); + private readonly HashSet _activePathsHashSet = new(StringComparer.FromComparison(PathsStringComparison)); private readonly Dictionary _dynamicChildren = new(); /// @@ -332,11 +333,10 @@ public abstract class DataModel /// internal bool IsPropertyInUse(string path, bool includeChildren) { - path = path.ToUpperInvariant(); lock (_activePaths) { return includeChildren - ? _activePathsHashSet.Any(p => p.StartsWith(path, StringComparison.Ordinal)) + ? _activePathsHashSet.Any(p => p.StartsWith(path, PathsStringComparison)) : _activePathsHashSet.Contains(path); } } @@ -351,9 +351,7 @@ public abstract class DataModel _activePaths.Add(path); // Add to the hashset if this is the first path pointing - string hashPath = path.Path.ToUpperInvariant(); - if (!_activePathsHashSet.Contains(hashPath)) - _activePathsHashSet.Add(hashPath); + _activePathsHashSet.Add(path.Path); } OnActivePathAdded(new DataModelPathEventArgs(path)); @@ -368,7 +366,7 @@ public abstract class DataModel // Remove from the hashset if this was the last path pointing there if (_activePaths.All(p => p.Path != path.Path)) - _activePathsHashSet.Remove(path.Path.ToUpperInvariant()); + _activePathsHashSet.Remove(path.Path); } OnActivePathRemoved(new DataModelPathEventArgs(path)); diff --git a/src/Artemis.Core/Services/Core/SurfaceManager.cs b/src/Artemis.Core/Services/Core/SurfaceManager.cs index fb3845e3c..7fb7a044c 100644 --- a/src/Artemis.Core/Services/Core/SurfaceManager.cs +++ b/src/Artemis.Core/Services/Core/SurfaceManager.cs @@ -14,7 +14,6 @@ internal sealed class SurfaceManager : IDisposable { private readonly IRenderer _renderer; private readonly TimerUpdateTrigger _updateTrigger; - private readonly object _renderLock = new(); private readonly List _devices = new(); private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute}; @@ -24,7 +23,7 @@ internal sealed class SurfaceManager : IDisposable public SurfaceManager(IRenderer renderer, IManagedGraphicsContext? graphicsContext, int targetFrameRate, float renderScale) { _renderer = renderer; - _updateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / targetFrameRate}; + _updateTrigger = new TimerUpdateTrigger(false) {UpdateFrequency = 1.0 / targetFrameRate}; GraphicsContext = graphicsContext; TargetFrameRate = targetFrameRate; @@ -36,47 +35,6 @@ internal sealed class SurfaceManager : IDisposable 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; } @@ -86,34 +44,46 @@ internal sealed class SurfaceManager : IDisposable public void AddDevices(IEnumerable devices) { - lock (_renderLock) + List newDevices = new(); + lock (_devices) { foreach (ArtemisDevice artemisDevice in devices) { if (_devices.Contains(artemisDevice)) continue; _devices.Add(artemisDevice); - Surface.Attach(artemisDevice.RgbDevice); + newDevices.Add(artemisDevice.RgbDevice); artemisDevice.DeviceUpdated += ArtemisDeviceOnDeviceUpdated; } - - Update(); } + + if (!newDevices.Any()) + return; + + Surface.Attach(newDevices); + _texture?.Invalidate(); } public void RemoveDevices(IEnumerable devices) { - lock (_renderLock) + List removedDevices = new(); + lock (_devices) { foreach (ArtemisDevice artemisDevice in devices) { + if (!_devices.Remove(artemisDevice)) + continue; artemisDevice.DeviceUpdated -= ArtemisDeviceOnDeviceUpdated; - Surface.Detach(artemisDevice.RgbDevice); + removedDevices.Add(artemisDevice.RgbDevice); _devices.Remove(artemisDevice); } - - Update(); } + + if (!removedDevices.Any()) + return; + + Surface.Detach(removedDevices); + _texture?.Invalidate(); } public bool SetPaused(bool paused) @@ -130,32 +100,33 @@ internal sealed class SurfaceManager : IDisposable return true; } - private void Update() + public void UpdateTargetFrameRate(int targetFrameRate) { - lock (_renderLock) - { - UpdateLedGroup(); - CreateTexture(); - } + TargetFrameRate = targetFrameRate; + _updateTrigger.UpdateFrequency = 1.0 / TargetFrameRate; } - private void UpdateLedGroup() + public void UpdateRenderScale(float renderScale) { - List leds = _devices.SelectMany(d => d.Leds).Select(l => l.RgbLed).ToList(); + RenderScale = renderScale; + _texture?.Invalidate(); + } - if (_surfaceLedGroup == null) - { - _surfaceLedGroup = new ListLedGroup(Surface, leds) {Brush = _textureBrush}; - LedsChanged?.Invoke(this, EventArgs.Empty); - return; - } + public void UpdateGraphicsContext(IManagedGraphicsContext? graphicsContext) + { + GraphicsContext = graphicsContext; + _texture?.Invalidate(); + } - // Clean up the old background - _surfaceLedGroup.Detach(); + /// + public void Dispose() + { + SetPaused(true); + Surface.UnregisterUpdateTrigger(_updateTrigger); - // Apply the application wide brush and decorator - _surfaceLedGroup = new ListLedGroup(Surface, leds) {Brush = _textureBrush}; - LedsChanged?.Invoke(this, EventArgs.Empty); + _updateTrigger.Dispose(); + _texture?.Dispose(); + Surface.Dispose(); } private SKTexture CreateTexture() @@ -170,53 +141,59 @@ internal sealed class SurfaceManager : IDisposable 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; + lock (_devices) + { + _texture?.Dispose(); + _texture = new SKTexture(GraphicsContext, width, height, RenderScale, _devices); + _textureBrush.Texture = _texture; + + _surfaceLedGroup?.Detach(); + _surfaceLedGroup = new ListLedGroup(Surface, _devices.SelectMany(d => d.Leds).Select(l => l.RgbLed)) {Brush = _textureBrush}; + } return _texture; } + private void SurfaceOnUpdating(UpdatingEventArgs args) + { + 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 + } + } + private void ArtemisDeviceOnDeviceUpdated(object? sender, EventArgs e) { - Update(); - } - - public event EventHandler? LedsChanged; - - /// - public void Dispose() - { - SetPaused(true); - - Surface.UnregisterUpdateTrigger(_updateTrigger); - _updateTrigger.Dispose(); - _texture?.Dispose(); - Surface.Dispose(); - } - - public void UpdateTargetFrameRate(int targetFrameRate) - { - TargetFrameRate = targetFrameRate; - _updateTrigger.UpdateFrequency = 1.0 / TargetFrameRate; - } - - public void UpdateRenderScale(float renderScale) - { - lock (_renderLock) - { - RenderScale = renderScale; - _texture?.Invalidate(); - } - } - - public void UpdateGraphicsContext(IManagedGraphicsContext? graphicsContext) - { - lock (_renderLock) - { - GraphicsContext = graphicsContext; - _texture?.Dispose(); - _texture = null; - } + _texture?.Invalidate(); } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/CoreRenderer.cs b/src/Artemis.Core/Services/CoreRenderer.cs new file mode 100644 index 000000000..2856d646a --- /dev/null +++ b/src/Artemis.Core/Services/CoreRenderer.cs @@ -0,0 +1,42 @@ +using Artemis.Core.ScriptingProviders; +using Artemis.Core.Services.Core; +using SkiaSharp; + +namespace Artemis.Core.Services; + +internal class CoreRenderer : IRenderer +{ + private readonly IModuleService _moduleService; + private readonly IScriptingService _scriptingService; + private readonly IProfileService _profileService; + + public CoreRenderer(IModuleService moduleService, IScriptingService scriptingService, IProfileService profileService) + { + _moduleService = moduleService; + _scriptingService = scriptingService; + _profileService = profileService; + } + + /// + public void Render(SKCanvas canvas, double delta) + { + foreach (GlobalScript scriptingServiceGlobalScript in _scriptingService.GlobalScripts) + scriptingServiceGlobalScript.OnCoreUpdating(delta); + + _moduleService.UpdateActiveModules(delta); + + if (!_profileService.ProfileRenderingDisabled) + { + _profileService.UpdateProfiles(delta); + _profileService.RenderProfiles(canvas); + } + + foreach (GlobalScript scriptingServiceGlobalScript in _scriptingService.GlobalScripts) + scriptingServiceGlobalScript.OnCoreUpdated(delta); + } + + /// + public void PostRender(SKTexture texture) + { + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs index 2cddc24f4..ae43ab5ef 100644 --- a/src/Artemis.Core/Services/DeviceService.cs +++ b/src/Artemis.Core/Services/DeviceService.cs @@ -30,13 +30,13 @@ internal class DeviceService : IDeviceService EnabledDevices = new ReadOnlyCollection(_enabledDevices); Devices = new ReadOnlyCollection(_devices); - + RenderScale.RenderScaleMultiplierChanged += RenderScaleOnRenderScaleMultiplierChanged; } public IReadOnlyCollection EnabledDevices { get; } public IReadOnlyCollection Devices { get; } - + /// public void IdentifyDevice(ArtemisDevice device) { @@ -52,11 +52,12 @@ internal class DeviceService : IDeviceService try { // Can't see why this would happen, RgbService used to do this though - List toRemove = _devices.Where(a => rgbDeviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); + List toRemove = _devices.Where(a => a.DeviceProvider.Id == deviceProvider.Id).ToList(); _logger.Verbose("[AddDeviceProvider] Removing {Count} old device(s)", toRemove.Count); foreach (ArtemisDevice device in toRemove) { _devices.Remove(device); + _enabledDevices.Remove(device); OnDeviceRemoved(new DeviceEventArgs(device)); } @@ -94,7 +95,7 @@ internal class DeviceService : IDeviceService _devices.Add(artemisDevice); if (artemisDevice.IsEnabled) _enabledDevices.Add(artemisDevice); - + _logger.Debug("Device provider {deviceProvider} added {deviceName}", deviceProvider.GetType().Name, rgbDevice.DeviceInfo.DeviceName); } @@ -104,7 +105,7 @@ internal class DeviceService : IDeviceService OnDeviceProviderAdded(new DeviceProviderEventArgs(deviceProvider, addedDevices)); foreach (ArtemisDevice artemisDevice in addedDevices) OnDeviceAdded(new DeviceEventArgs(artemisDevice)); - + UpdateLeds(); } catch (Exception e) @@ -118,9 +119,8 @@ internal class DeviceService : IDeviceService public void RemoveDeviceProvider(DeviceProvider deviceProvider) { _logger.Verbose("[RemoveDeviceProvider] Pausing rendering to remove {DeviceProvider}", deviceProvider.GetType().Name); - IRGBDeviceProvider rgbDeviceProvider = deviceProvider.RgbDeviceProvider; - List toRemove = _devices.Where(a => rgbDeviceProvider.Devices.Any(d => a.RgbDevice == d)).ToList(); - + List toRemove = _devices.Where(a => a.DeviceProvider.Id == deviceProvider.Id).ToList(); + try { _logger.Verbose("[RemoveDeviceProvider] Removing {Count} old device(s)", toRemove.Count); @@ -131,11 +131,11 @@ internal class DeviceService : IDeviceService } _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) @@ -155,7 +155,7 @@ internal class DeviceService : IDeviceService SaveDevices(); } - + /// public void ApplyDeviceLayout(ArtemisDevice device, ArtemisLayout? layout) { @@ -163,10 +163,10 @@ internal class DeviceService : IDeviceService device.ApplyLayout(layout, false, false); else device.ApplyLayout(layout, device.DeviceProvider.CreateMissingLedsSupported, device.DeviceProvider.RemoveExcessiveLedsSupported); - + UpdateLeds(); } - + /// public void EnableDevice(ArtemisDevice device) { @@ -175,7 +175,7 @@ internal class DeviceService : IDeviceService _enabledDevices.Add(device); device.IsEnabled = true; - device.ApplyToEntity(); + device.Save(); _deviceRepository.Save(device.DeviceEntity); OnDeviceEnabled(new DeviceEventArgs(device)); @@ -190,7 +190,7 @@ internal class DeviceService : IDeviceService _enabledDevices.Remove(device); device.IsEnabled = false; - device.ApplyToEntity(); + device.Save(); _deviceRepository.Save(device.DeviceEntity); OnDeviceDisabled(new DeviceEventArgs(device)); @@ -200,9 +200,7 @@ internal class DeviceService : IDeviceService /// public void SaveDevice(ArtemisDevice artemisDevice) { - artemisDevice.ApplyToEntity(); - artemisDevice.ApplyToRgbDevice(); - + artemisDevice.Save(); _deviceRepository.Save(artemisDevice.DeviceEntity); UpdateLeds(); } @@ -211,15 +209,11 @@ internal class DeviceService : IDeviceService public void SaveDevices() { foreach (ArtemisDevice artemisDevice in _devices) - { - artemisDevice.ApplyToEntity(); - artemisDevice.ApplyToRgbDevice(); - } - + artemisDevice.Save(); _deviceRepository.Save(_devices.Select(d => d.DeviceEntity)); UpdateLeds(); } - + private ArtemisDevice GetArtemisDevice(IRGBDevice rgbDevice) { string deviceIdentifier = rgbDevice.GetDeviceIdentifier(); @@ -236,11 +230,10 @@ internal class DeviceService : IDeviceService 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; @@ -266,7 +259,7 @@ internal class DeviceService : IDeviceService } }); } - + private void CalculateRenderProperties() { foreach (ArtemisDevice artemisDevice in Devices) @@ -278,7 +271,7 @@ internal class DeviceService : IDeviceService { OnLedsChanged(); } - + private void RenderScaleOnRenderScaleMultiplierChanged(object? sender, EventArgs e) { CalculateRenderProperties(); @@ -303,7 +296,7 @@ internal class DeviceService : IDeviceService /// public event EventHandler? DeviceProviderRemoved; - + /// public event EventHandler? LedsChanged; @@ -336,7 +329,7 @@ internal class DeviceService : IDeviceService { DeviceProviderRemoved?.Invoke(this, e); } - + protected virtual void OnLedsChanged() { LedsChanged?.Invoke(this, EventArgs.Empty); diff --git a/src/Artemis.Core/Services/Interfaces/IRenderService.cs b/src/Artemis.Core/Services/Interfaces/IRenderService.cs index 153f4c827..e33d4fea0 100644 --- a/src/Artemis.Core/Services/Interfaces/IRenderService.cs +++ b/src/Artemis.Core/Services/Interfaces/IRenderService.cs @@ -21,11 +21,6 @@ public interface IRenderService : IArtemisService /// RGBSurface Surface { get; } - /// - /// Gets a list of registered renderers. - /// - List Renderers { get; } - /// /// Gets or sets a boolean indicating whether rendering is paused. /// diff --git a/src/Artemis.Core/Services/RenderService.cs b/src/Artemis.Core/Services/RenderService.cs index d9653279a..7dc8dd64d 100644 --- a/src/Artemis.Core/Services/RenderService.cs +++ b/src/Artemis.Core/Services/RenderService.cs @@ -19,22 +19,24 @@ internal class RenderService : IRenderService, IRenderer, IDisposable private readonly ILogger _logger; private readonly IDeviceService _deviceService; + private readonly CoreRenderer _coreRenderer; private readonly LazyEnumerable _graphicsContextProviders; private readonly PluginSetting _targetFrameRateSetting; private readonly PluginSetting _renderScaleSetting; private readonly PluginSetting _preferredGraphicsContext; + private readonly SurfaceManager _surfaceManager; - private SurfaceManager _surfaceManager; private int _frames; private DateTime _lastExceptionLog; private DateTime _lastFrameRateSample; private bool _initialized; - public RenderService(ILogger logger, ISettingsService settingsService, IDeviceService deviceService, LazyEnumerable graphicsContextProviders) + public RenderService(ILogger logger, ISettingsService settingsService, IDeviceService deviceService, CoreRenderer coreRenderer, LazyEnumerable graphicsContextProviders) { _frameStopWatch = new Stopwatch(); _logger = logger; _deviceService = deviceService; + _coreRenderer = coreRenderer; _graphicsContextProviders = graphicsContextProviders; _targetFrameRateSetting = settingsService.GetSetting("Core.TargetFrameRate", 30); @@ -54,9 +56,6 @@ internal class RenderService : IRenderService, IRenderer, IDisposable /// public RGBSurface Surface => _surfaceManager.Surface; - /// - public List Renderers { get; } = new(); - /// public bool IsPaused { @@ -77,8 +76,7 @@ internal class RenderService : IRenderService, IRenderer, IDisposable try { OnFrameRendering(new FrameRenderingEventArgs(canvas, delta, _surfaceManager.Surface)); - foreach (IRenderer renderer in Renderers) - renderer.Render(canvas, delta); + _coreRenderer.Render(canvas, delta); } catch (Exception e) { @@ -91,8 +89,7 @@ internal class RenderService : IRenderService, IRenderer, IDisposable { try { - foreach (IRenderer renderer in Renderers) - renderer.PostRender(texture); + _coreRenderer.PostRender(texture); OnFrameRendered(new FrameRenderedEventArgs(texture, _surfaceManager.Surface)); } catch (Exception e) diff --git a/src/Artemis.Core/Services/ScriptingService.cs b/src/Artemis.Core/Services/ScriptingService.cs index baeee28e9..f768c2028 100644 --- a/src/Artemis.Core/Services/ScriptingService.cs +++ b/src/Artemis.Core/Services/ScriptingService.cs @@ -12,7 +12,7 @@ internal class ScriptingService : IScriptingService private readonly IPluginManagementService _pluginManagementService; private readonly IProfileService _profileService; private readonly List _scriptingProviders; - + public ScriptingService(IPluginManagementService pluginManagementService, IProfileService profileService) { _pluginManagementService = pluginManagementService; diff --git a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs index 132c623cb..9f4e78fe1 100644 --- a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs +++ b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs @@ -27,7 +27,6 @@ internal class SurfaceArrangement { surfaceDevice.X = 0; surfaceDevice.Y = 0; - surfaceDevice.ApplyToRgbDevice(); } foreach (SurfaceArrangementType surfaceArrangementType in Types) @@ -37,18 +36,16 @@ internal class SurfaceArrangement float x = devices.Min(d => d.RgbDevice.Location.X); float y = devices.Min(d => d.RgbDevice.Location.Y); if (x < 0) + { foreach (ArtemisDevice surfaceDevice in devices) - { surfaceDevice.X += x * -1; - surfaceDevice.ApplyToRgbDevice(); - } + } if (y < 0) + { foreach (ArtemisDevice surfaceDevice in devices) - { surfaceDevice.Y += y * -1; - surfaceDevice.ApplyToRgbDevice(); - } + } } internal static SurfaceArrangement GetDefaultArrangement() diff --git a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs index 34decdd69..7f59731f9 100644 --- a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs +++ b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs @@ -80,9 +80,7 @@ internal class SurfaceArrangementConfiguration }; } - artemisDevice.ApplyToRgbDevice(); previous = artemisDevice; - SurfaceArrangement.ArrangedDevices.Add(artemisDevice); } diff --git a/src/Artemis.Core/Services/Storage/ProfileService.cs b/src/Artemis.Core/Services/Storage/ProfileService.cs index 485dfd267..f93a4f152 100644 --- a/src/Artemis.Core/Services/Storage/ProfileService.cs +++ b/src/Artemis.Core/Services/Storage/ProfileService.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Text; 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,7 +15,7 @@ using SkiaSharp; namespace Artemis.Core.Services; -internal class ProfileService : IProfileService, IRenderer +internal class ProfileService : IProfileService { private readonly ILogger _logger; private readonly IProfileCategoryRepository _profileCategoryRepository; @@ -36,7 +35,6 @@ internal class ProfileService : IProfileService, IRenderer IPluginManagementService pluginManagementService, IInputService inputService, IDeviceService deviceService, - IRenderService renderService, IProfileRepository profileRepository) { _logger = logger; @@ -55,8 +53,6 @@ internal class ProfileService : IProfileService, IRenderer if (!_profileCategories.Any()) CreateDefaultProfileCategories(); UpdateModules(); - - renderService.Renderers.Add(this); } public ProfileConfiguration? FocusProfile { get; set; } @@ -187,21 +183,6 @@ internal class ProfileService : IProfileService, IRenderer } } } - - /// - public void Render(SKCanvas canvas, double delta) - { - if (ProfileRenderingDisabled) - return; - - UpdateProfiles(delta); - RenderProfiles(canvas); - } - - /// - public void PostRender(SKTexture texture) - { - } /// public void LoadProfileConfigurationIcon(ProfileConfiguration profileConfiguration) diff --git a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs index 5cfafa6a5..b950f9f49 100644 --- a/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/Interfaces/IWebServerService.cs @@ -95,35 +95,29 @@ public interface IWebServerService : IArtemisService /// Adds a new Web API controller and restarts the web server /// /// The type of Web API controller to remove - void AddController(PluginFeature feature) where T : WebApiController; + WebApiControllerRegistration AddController(PluginFeature feature) where T : WebApiController; /// /// Removes an existing Web API controller and restarts the web server /// /// The type of Web API controller to remove - void RemoveController() where T : WebApiController; + void RemoveController(WebApiControllerRegistration registration); /// /// Adds a new EmbedIO module and restarts the web server /// - void AddModule(PluginFeature feature, Func create); + WebModuleRegistration AddModule(PluginFeature feature, Func create); /// /// Removes a EmbedIO module and restarts the web server /// - void RemoveModule(Func create); + void RemoveModule(WebModuleRegistration create); /// /// Adds a new EmbedIO module and restarts the web server /// /// The type of module to add - void AddModule(PluginFeature feature) where T : IWebModule; - - /// - /// Removes a EmbedIO module and restarts the web server - /// - /// The type of module to remove - void RemoveModule() where T : IWebModule; + WebModuleRegistration AddModule(PluginFeature feature) where T : IWebModule; /// /// Occurs when the web server has been created and is about to start. This is the ideal place to add your own modules. diff --git a/src/Artemis.Core/Services/WebServer/WebApiControllerRegistration.cs b/src/Artemis.Core/Services/WebServer/WebApiControllerRegistration.cs index 1a6375a11..4a2c7bb50 100644 --- a/src/Artemis.Core/Services/WebServer/WebApiControllerRegistration.cs +++ b/src/Artemis.Core/Services/WebServer/WebApiControllerRegistration.cs @@ -3,26 +3,55 @@ using EmbedIO.WebApi; namespace Artemis.Core.Services; -internal class WebApiControllerRegistration : WebApiControllerRegistration where T : WebApiController +/// +/// Represents a web API controller registration. +/// +/// The type of the web API controller. +public class WebApiControllerRegistration : WebApiControllerRegistration where T : WebApiController { - public WebApiControllerRegistration(PluginFeature feature) : base(feature, typeof(T)) + internal WebApiControllerRegistration(IWebServerService webServerService, PluginFeature feature) : base(webServerService, feature, typeof(T)) { Factory = () => feature.Plugin.Resolve(); } - public Func Factory { get; set; } - public override object UntypedFactory => Factory; + internal Func Factory { get; set; } + internal override object UntypedFactory => Factory; } -internal abstract class WebApiControllerRegistration +/// +/// Represents a web API controller registration. +/// +public abstract class WebApiControllerRegistration { - protected WebApiControllerRegistration(PluginFeature feature, Type controllerType) + private readonly IWebServerService _webServerService; + + /// + /// Creates a new instance of the class. + /// + protected internal WebApiControllerRegistration(IWebServerService webServerService, PluginFeature feature, Type controllerType) { + _webServerService = webServerService; Feature = feature; ControllerType = controllerType; + + Feature.Disabled += FeatureOnDisabled; } - public abstract object UntypedFactory { get; } - public Type ControllerType { get; set; } + private void FeatureOnDisabled(object? sender, EventArgs e) + { + _webServerService.RemoveController(this); + Feature.Disabled -= FeatureOnDisabled; + } + + internal abstract object UntypedFactory { get; } + + /// + /// Gets the type of the web API controller. + /// + public Type ControllerType { get; } + + /// + /// Gets the plugin feature that provided the web API controller. + /// public PluginFeature Feature { get; } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/WebModuleRegistration.cs b/src/Artemis.Core/Services/WebServer/WebModuleRegistration.cs index 270a5ec4d..dca340b35 100644 --- a/src/Artemis.Core/Services/WebServer/WebModuleRegistration.cs +++ b/src/Artemis.Core/Services/WebServer/WebModuleRegistration.cs @@ -3,25 +3,44 @@ using EmbedIO; namespace Artemis.Core.Services; -internal class WebModuleRegistration +/// +/// Represents a registration for a web module. +/// +public class WebModuleRegistration { - public WebModuleRegistration(PluginFeature feature, Type webModuleType) + private readonly IWebServerService _webServerService; + + internal WebModuleRegistration(IWebServerService webServerService, PluginFeature feature, Type webModuleType) { + _webServerService = webServerService; Feature = feature ?? throw new ArgumentNullException(nameof(feature)); WebModuleType = webModuleType ?? throw new ArgumentNullException(nameof(webModuleType)); + + Feature.Disabled += FeatureOnDisabled; } - public WebModuleRegistration(PluginFeature feature, Func create) + internal WebModuleRegistration(IWebServerService webServerService, PluginFeature feature, Func create) { + _webServerService = webServerService; Feature = feature ?? throw new ArgumentNullException(nameof(feature)); Create = create ?? throw new ArgumentNullException(nameof(create)); + + Feature.Disabled += FeatureOnDisabled; } + /// + /// The plugin feature that provided the web module. + /// public PluginFeature Feature { get; } - public Type? WebModuleType { get; } - public Func? Create { get; } - public IWebModule CreateInstance() + /// + /// The type of the web module. + /// + public Type? WebModuleType { get; } + + internal Func? Create { get; } + + internal IWebModule CreateInstance() { if (Create != null) return Create(); @@ -29,4 +48,10 @@ internal class WebModuleRegistration return (IWebModule) Feature.Plugin.Resolve(WebModuleType); throw new ArtemisCoreException("WebModuleRegistration doesn't have a create function nor a web module type :("); } + + private void FeatureOnDisabled(object? sender, EventArgs e) + { + _webServerService.RemoveModule(this); + Feature.Disabled -= FeatureOnDisabled; + } } \ No newline at end of file diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index e5030c9b4..263d95315 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -39,9 +39,9 @@ internal class WebServerService : IWebServerService, IDisposable PluginsModule = new PluginsModule("/plugins"); if (coreService.IsInitialized) - StartWebServer(); + AutoStartWebServer(); else - coreService.Initialized += (_, _) => StartWebServer(); + coreService.Initialized += (sender, args) => AutoStartWebServer(); } public event EventHandler? WebServerStopped; @@ -138,7 +138,7 @@ internal class WebServerService : IWebServerService, IDisposable // Add registered controllers to the API module foreach (WebApiControllerRegistration registration in _controllers) apiModule.RegisterController(registration.ControllerType, (Func) registration.UntypedFactory); - + // Listen for state changes. server.StateChanged += (s, e) => _logger.Verbose("WebServer new state - {state}", e.NewState); @@ -173,6 +173,18 @@ internal class WebServerService : IWebServerService, IDisposable OnWebServerStarted(); } } + + private void AutoStartWebServer() + { + try + { + StartWebServer(); + } + catch (Exception exception) + { + _logger.Warning(exception, "Failed to initially start webserver"); + } + } #endregion @@ -237,10 +249,6 @@ internal class WebServerService : IWebServerService, IDisposable return endPoint; } - private void HandleDataModelRequest(Module module, T value) where T : DataModel, new() - { - } - public void RemovePluginEndPoint(PluginEndPoint endPoint) { PluginsModule.RemovePluginEndPoint(endPoint); @@ -250,15 +258,20 @@ internal class WebServerService : IWebServerService, IDisposable #region Controller management - public void AddController(PluginFeature feature) where T : WebApiController + public WebApiControllerRegistration AddController(PluginFeature feature) where T : WebApiController { - _controllers.Add(new WebApiControllerRegistration(feature)); + if (feature == null) throw new ArgumentNullException(nameof(feature)); + + WebApiControllerRegistration registration = new(this, feature); + _controllers.Add(registration); StartWebServer(); + + return registration; } - public void RemoveController() where T : WebApiController + public void RemoveController(WebApiControllerRegistration registration) { - _controllers.RemoveAll(r => r.ControllerType == typeof(T)); + _controllers.Remove(registration); StartWebServer(); } @@ -266,33 +279,31 @@ internal class WebServerService : IWebServerService, IDisposable #region Module management - public void AddModule(PluginFeature feature, Func create) + public WebModuleRegistration AddModule(PluginFeature feature, Func create) { if (feature == null) throw new ArgumentNullException(nameof(feature)); - _modules.Add(new WebModuleRegistration(feature, create)); + WebModuleRegistration registration = new(this, feature, create); + _modules.Add(registration); StartWebServer(); + + return registration; } - public void RemoveModule(Func create) - { - _modules.RemoveAll(r => r.Create == create); - StartWebServer(); - } - - public void AddModule(PluginFeature feature) where T : IWebModule + public WebModuleRegistration AddModule(PluginFeature feature) where T : IWebModule { if (feature == null) throw new ArgumentNullException(nameof(feature)); - if (_modules.Any(r => r.WebModuleType == typeof(T))) - return; - _modules.Add(new WebModuleRegistration(feature, typeof(T))); + WebModuleRegistration registration = new(this, feature, typeof(T)); + _modules.Add(registration); StartWebServer(); + + return registration; } - public void RemoveModule() where T : IWebModule + public void RemoveModule(WebModuleRegistration registration) { - _modules.RemoveAll(r => r.WebModuleType == typeof(T)); + _modules.Remove(registration); StartWebServer(); } diff --git a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs index 84e4be2c2..46db27445 100644 --- a/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs +++ b/src/Artemis.UI.Shared/Controls/DeviceVisualizer.cs @@ -123,27 +123,35 @@ public class DeviceVisualizer : Control if (Device == null) return false; - bool difference = false; - - int newLedCount = Device.RgbDevice.Count(); - if (_previousState.Length != newLedCount) + // Device might be modified mid-check, in that case just pretend it was not dirty + try { - _previousState = new Color[newLedCount]; - difference = true; - } + bool difference = false; - // Check all LEDs for differences and copy the colors to a new state - int index = 0; - foreach (Led led in Device.RgbDevice) - { - if (_previousState[index] != led.Color) + int newLedCount = Device.RgbDevice.Count(); + if (_previousState.Length != newLedCount) + { + _previousState = new Color[newLedCount]; difference = true; + } - _previousState[index] = led.Color; - index++; + // Check all LEDs for differences and copy the colors to a new state + int index = 0; + foreach (Led led in Device.RgbDevice) + { + if (_previousState[index] != led.Color) + difference = true; + + _previousState[index] = led.Color; + index++; + } + + return difference; + } + catch (Exception) + { + return false; } - - return difference; } private void Update() @@ -153,9 +161,9 @@ public class DeviceVisualizer : Control private Rect MeasureDevice() { - if (Device == null) + if (Device == null || float.IsNaN(Device.RgbDevice.ActualSize.Width) || float.IsNaN(Device.RgbDevice.ActualSize.Height)) return new Rect(); - + Rect deviceRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height); Geometry geometry = new RectangleGeometry(deviceRect); geometry.Transform = new RotateTransform(Device.Rotation); @@ -335,7 +343,7 @@ public class DeviceVisualizer : Control deviceVisualizerLed.DrawBitmap(context, 2 * device.Scale); } - BitmapCache[path] = renderTargetBitmap; + // BitmapCache[path] = renderTargetBitmap; return renderTargetBitmap; } diff --git a/src/Artemis.UI/Controllers/RemoteController.cs b/src/Artemis.UI/Controllers/RemoteController.cs index 6ace7f441..0e6523736 100644 --- a/src/Artemis.UI/Controllers/RemoteController.cs +++ b/src/Artemis.UI/Controllers/RemoteController.cs @@ -45,9 +45,9 @@ public class RemoteController : WebApiController } [Route(HttpVerbs.Post, "/remote/restart")] - public void PostRestart() + public void PostRestart([FormField] string[] args) { - Utilities.Restart(_coreService.IsElevated, TimeSpan.FromMilliseconds(500)); + Utilities.Restart(_coreService.IsElevated, TimeSpan.FromMilliseconds(500), args); } [Route(HttpVerbs.Post, "/remote/shutdown")] diff --git a/src/Artemis.UI/Screens/Device/Tabs/DeviceLayoutTabView.axaml b/src/Artemis.UI/Screens/Device/Tabs/DeviceLayoutTabView.axaml index d253e231f..5075c6e5d 100644 --- a/src/Artemis.UI/Screens/Device/Tabs/DeviceLayoutTabView.axaml +++ b/src/Artemis.UI/Screens/Device/Tabs/DeviceLayoutTabView.axaml @@ -52,7 +52,7 @@ - + diff --git a/src/Artemis.UI/Screens/Device/Tabs/DeviceLayoutTabViewModel.cs b/src/Artemis.UI/Screens/Device/Tabs/DeviceLayoutTabViewModel.cs index 419243e9a..c6e5dd6ba 100644 --- a/src/Artemis.UI/Screens/Device/Tabs/DeviceLayoutTabViewModel.cs +++ b/src/Artemis.UI/Screens/Device/Tabs/DeviceLayoutTabViewModel.cs @@ -33,7 +33,7 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase Device = device; DisplayName = "Layout"; DefaultLayoutPath = Device.DeviceProvider.LoadLayout(Device).FilePath; - + this.WhenActivated(d => { Device.PropertyChanged += DeviceOnPropertyChanged; @@ -42,23 +42,21 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase } public ArtemisDevice Device { get; } - + public string DefaultLayoutPath { get; } public string? ImagePath => Device.Layout?.Image?.LocalPath; - + public string? CustomLayoutPath => Device.CustomLayoutPath; - + public bool HasCustomLayout => Device.CustomLayoutPath != null; - + public void ClearCustomLayout() { Device.CustomLayoutPath = null; _notificationService.CreateNotification() .WithMessage("Cleared imported layout.") .WithSeverity(NotificationSeverity.Informational); - - _deviceService.SaveDevice(Device); } public async Task BrowseCustomLayout() @@ -75,8 +73,6 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase .WithTitle("Imported layout") .WithMessage($"File loaded from {files[0]}") .WithSeverity(NotificationSeverity.Informational); - - _deviceService.SaveDevice(Device); } } @@ -154,7 +150,12 @@ public class DeviceLayoutTabViewModel : ActivatableViewModelBase { if (e.PropertyName is nameof(Device.CustomLayoutPath) or nameof(Device.DisableDefaultLayout)) { - Task.Run(() => _deviceService.ApplyDeviceLayout(Device, Device.GetBestDeviceLayout())); + Task.Run(() => + { + _deviceService.ApplyDeviceLayout(Device, Device.GetBestDeviceLayout()); + _deviceService.SaveDevice(Device); + }); + this.RaisePropertyChanged(nameof(CustomLayoutPath)); this.RaisePropertyChanged(nameof(HasCustomLayout)); } diff --git a/src/Artemis.UI/Screens/Device/Tabs/DeviceLogicalLayoutDialogView.axaml.cs b/src/Artemis.UI/Screens/Device/Tabs/DeviceLogicalLayoutDialogView.axaml.cs index c90b51c67..d4dace819 100644 --- a/src/Artemis.UI/Screens/Device/Tabs/DeviceLogicalLayoutDialogView.axaml.cs +++ b/src/Artemis.UI/Screens/Device/Tabs/DeviceLogicalLayoutDialogView.axaml.cs @@ -10,8 +10,6 @@ namespace Artemis.UI.Screens.Device; public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl { - private readonly AutoCompleteBox _autoCompleteBox; - public DeviceLogicalLayoutDialogView() { InitializeComponent(); @@ -23,8 +21,8 @@ public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl vm.CanDetectInput)); + X = device.X; + Y = device.Y; + + this.WhenActivated(d => + { + Device.PropertyChanged += DeviceOnPropertyChanged; + Disposable.Create(() => Device.PropertyChanged -= DeviceOnPropertyChanged).DisposeWith(d); + }); } public ReactiveCommand DetectInput { get; } @@ -47,6 +59,18 @@ public class SurfaceDeviceViewModel : ActivatableViewModelBase set => RaiseAndSetIfChanged(ref _isSelected, value); } + public float X + { + get => _x; + set => RaiseAndSetIfChanged(ref _x, value); + } + + public float Y + { + get => _y; + set => RaiseAndSetIfChanged(ref _y, value); + } + public void StartMouseDrag(Point mouseStartPosition) { if (!IsSelected) @@ -73,16 +97,16 @@ public class SurfaceDeviceViewModel : ActivatableViewModelBase if (Fits(x, y, ignoreOverlap)) { - Device.X = x; - Device.Y = y; + X = x; + Y = y; } else if (Fits(x, Device.Y, ignoreOverlap)) { - Device.X = x; + X = x; } else if (Fits(Device.X, y, ignoreOverlap)) { - Device.Y = y; + Y = y; } } @@ -100,10 +124,9 @@ public class SurfaceDeviceViewModel : ActivatableViewModelBase IEnumerable own = Device.Leds .Select(l => SKRect.Create(l.Rectangle.Left + x, l.Rectangle.Top + y, l.Rectangle.Width, l.Rectangle.Height)); - IEnumerable others = _deviceService.EnabledDevices - .Where(d => d != Device && d.IsEnabled) - .SelectMany(d => d.Leds) - .Select(l => SKRect.Create(l.Rectangle.Left + l.Device.X, l.Rectangle.Top + l.Device.Y, l.Rectangle.Width, l.Rectangle.Height)); + IEnumerable others = SurfaceEditorViewModel.SurfaceDeviceViewModels + .Where(vm => vm != this && vm.Device.IsEnabled) + .SelectMany(vm => vm.Device.Leds.Select(l => SKRect.Create(l.Rectangle.Left + vm.X, l.Rectangle.Top + vm.Y, l.Rectangle.Width, l.Rectangle.Height))); return !own.Any(o => others.Any(l => l.IntersectsWith(o))); } @@ -122,4 +145,18 @@ public class SurfaceDeviceViewModel : ActivatableViewModelBase if (viewModel.MadeChanges) _deviceService.SaveDevice(Device); } + + public void Apply() + { + Device.X = X; + Device.Y = Y; + } + + private void DeviceOnPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Device.X)) + X = Device.X; + if (e.PropertyName == nameof(Device.Y)) + Y = Device.Y; + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.axaml b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.axaml index 4b6e39d72..05a49c0d6 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.axaml +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.axaml @@ -82,8 +82,8 @@ diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs index 980253ea2..aa23a0a90 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs @@ -135,6 +135,8 @@ public class SurfaceEditorViewModel : RoutableScreen, IMainScreenViewModel try { _saving = true; + foreach (SurfaceDeviceViewModel surfaceDeviceViewModel in SurfaceDeviceViewModels) + surfaceDeviceViewModel.Apply(); _deviceService.SaveDevices(); } catch (Exception e)