1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Device visualizer - Never return NaN measurements

Device logical layout picker - Fixed NRE
Surface manager - Improve thread safety
This commit is contained in:
Robert 2023-10-11 20:53:21 +02:00
parent 7e72e22295
commit 38eb0ff460
4 changed files with 75 additions and 90 deletions

View File

@ -22,8 +22,8 @@ public class ArtemisDevice : CorePropertyChanged
internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider) internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider)
{ {
_originalLeds = new List<OriginalLed>(rgbDevice.Select(l => new OriginalLed(l)));
Rectangle ledRectangle = new(rgbDevice.Select(x => x.Boundary)); Rectangle ledRectangle = new(rgbDevice.Select(x => x.Boundary));
_originalLeds = new List<OriginalLed>(rgbDevice.Select(l => new OriginalLed(l)));
_originalSize = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y); _originalSize = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y);
Identifier = rgbDevice.GetDeviceIdentifier(); Identifier = rgbDevice.GetDeviceIdentifier();
@ -54,10 +54,10 @@ public class ArtemisDevice : CorePropertyChanged
internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, DeviceEntity deviceEntity) 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)); Rectangle ledRectangle = new(rgbDevice.Select(x => x.Boundary));
_originalLeds = new List<OriginalLed>(rgbDevice.Select(l => new OriginalLed(l)));
_originalSize = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y); _originalSize = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y);
Identifier = rgbDevice.GetDeviceIdentifier(); Identifier = rgbDevice.GetDeviceIdentifier();
DeviceEntity = deviceEntity; DeviceEntity = deviceEntity;
RgbDevice = rgbDevice; RgbDevice = rgbDevice;

View File

@ -16,7 +16,6 @@ internal sealed class SurfaceManager : IDisposable
private readonly TimerUpdateTrigger _updateTrigger; private readonly TimerUpdateTrigger _updateTrigger;
private readonly List<ArtemisDevice> _devices = new(); private readonly List<ArtemisDevice> _devices = new();
private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute}; private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute};
private readonly object _renderLock = new();
private ListLedGroup? _surfaceLedGroup; private ListLedGroup? _surfaceLedGroup;
private SKTexture? _texture; private SKTexture? _texture;
@ -24,7 +23,7 @@ internal sealed class SurfaceManager : IDisposable
public SurfaceManager(IRenderer renderer, IManagedGraphicsContext? graphicsContext, int targetFrameRate, float renderScale) public SurfaceManager(IRenderer renderer, IManagedGraphicsContext? graphicsContext, int targetFrameRate, float renderScale)
{ {
_renderer = renderer; _renderer = renderer;
_updateTrigger = new TimerUpdateTrigger {UpdateFrequency = 1.0 / targetFrameRate}; _updateTrigger = new TimerUpdateTrigger(false) {UpdateFrequency = 1.0 / targetFrameRate};
GraphicsContext = graphicsContext; GraphicsContext = graphicsContext;
TargetFrameRate = targetFrameRate; TargetFrameRate = targetFrameRate;
@ -45,34 +44,46 @@ internal sealed class SurfaceManager : IDisposable
public void AddDevices(IEnumerable<ArtemisDevice> devices) public void AddDevices(IEnumerable<ArtemisDevice> devices)
{ {
lock (_renderLock) List<IRGBDevice> newDevices = new();
lock (_devices)
{ {
foreach (ArtemisDevice artemisDevice in devices) foreach (ArtemisDevice artemisDevice in devices)
{ {
if (_devices.Contains(artemisDevice)) if (_devices.Contains(artemisDevice))
continue; continue;
_devices.Add(artemisDevice); _devices.Add(artemisDevice);
Surface.Attach(artemisDevice.RgbDevice); newDevices.Add(artemisDevice.RgbDevice);
artemisDevice.DeviceUpdated += ArtemisDeviceOnDeviceUpdated; artemisDevice.DeviceUpdated += ArtemisDeviceOnDeviceUpdated;
} }
_texture?.Invalidate();
} }
if (!newDevices.Any())
return;
Surface.Attach(newDevices);
_texture?.Invalidate();
} }
public void RemoveDevices(IEnumerable<ArtemisDevice> devices) public void RemoveDevices(IEnumerable<ArtemisDevice> devices)
{ {
lock (_renderLock) List<IRGBDevice> removedDevices = new();
lock (_devices)
{ {
foreach (ArtemisDevice artemisDevice in devices) foreach (ArtemisDevice artemisDevice in devices)
{ {
if (!_devices.Remove(artemisDevice))
continue;
artemisDevice.DeviceUpdated -= ArtemisDeviceOnDeviceUpdated; artemisDevice.DeviceUpdated -= ArtemisDeviceOnDeviceUpdated;
Surface.Detach(artemisDevice.RgbDevice); removedDevices.Add(artemisDevice.RgbDevice);
_devices.Remove(artemisDevice); _devices.Remove(artemisDevice);
} }
_texture?.Invalidate();
} }
if (!removedDevices.Any())
return;
Surface.Detach(removedDevices);
_texture?.Invalidate();
} }
public bool SetPaused(bool paused) public bool SetPaused(bool paused)
@ -97,20 +108,14 @@ internal sealed class SurfaceManager : IDisposable
public void UpdateRenderScale(float renderScale) public void UpdateRenderScale(float renderScale)
{ {
lock (_renderLock) RenderScale = renderScale;
{ _texture?.Invalidate();
RenderScale = renderScale;
_texture?.Invalidate();
}
} }
public void UpdateGraphicsContext(IManagedGraphicsContext? graphicsContext) public void UpdateGraphicsContext(IManagedGraphicsContext? graphicsContext)
{ {
lock (_renderLock) GraphicsContext = graphicsContext;
{ _texture?.Invalidate();
GraphicsContext = graphicsContext;
_texture?.Invalidate();
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -118,29 +123,10 @@ internal sealed class SurfaceManager : IDisposable
{ {
SetPaused(true); SetPaused(true);
Surface.UnregisterUpdateTrigger(_updateTrigger); Surface.UnregisterUpdateTrigger(_updateTrigger);
lock (_renderLock)
{
_updateTrigger.Dispose();
_texture?.Dispose();
Surface.Dispose();
}
}
private void UpdateLedGroup() _updateTrigger.Dispose();
{ _texture?.Dispose();
List<Led> leds = _devices.SelectMany(d => d.Leds).Select(l => l.RgbLed).ToList(); Surface.Dispose();
if (_surfaceLedGroup == null)
{
_surfaceLedGroup = new ListLedGroup(Surface, leds) {Brush = _textureBrush};
return;
}
// Clean up the old background
_surfaceLedGroup.Detach();
// Apply the application wide brush and decorator
_surfaceLedGroup = new ListLedGroup(Surface, leds) {Brush = _textureBrush};
} }
private SKTexture CreateTexture() private SKTexture CreateTexture()
@ -155,53 +141,54 @@ internal sealed class SurfaceManager : IDisposable
int width = Math.Max(1, MathF.Min(evenWidth * RenderScale, 4096).RoundToInt()); int width = Math.Max(1, MathF.Min(evenWidth * RenderScale, 4096).RoundToInt());
int height = Math.Max(1, MathF.Min(evenHeight * RenderScale, 4096).RoundToInt()); int height = Math.Max(1, MathF.Min(evenHeight * RenderScale, 4096).RoundToInt());
_texture?.Dispose(); lock (_devices)
_texture = new SKTexture(GraphicsContext, width, height, RenderScale, _devices); {
_textureBrush.Texture = _texture; _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};
}
UpdateLedGroup();
return _texture; return _texture;
} }
private void SurfaceOnUpdating(UpdatingEventArgs args) 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
{ {
SKTexture? texture = _texture; _renderer.Render(canvas, args.DeltaTime);
if (texture == null || texture.IsInvalid) }
texture = CreateTexture(); finally
{
canvas.RestoreToCount(-1);
canvas.Flush();
texture.CopyPixelData();
}
// Prepare a canvas try
SKCanvas canvas = texture.Surface.Canvas; {
canvas.Save(); _renderer.PostRender(texture);
}
// Apply scaling if necessary catch
if (Math.Abs(texture.RenderScale - 1) > 0.001) {
canvas.Scale(texture.RenderScale); // ignored
// 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
}
} }
} }

View File

@ -153,9 +153,9 @@ public class DeviceVisualizer : Control
private Rect MeasureDevice() private Rect MeasureDevice()
{ {
if (Device == null) if (Device == null || float.IsNaN(Device.RgbDevice.ActualSize.Width) || float.IsNaN(Device.RgbDevice.ActualSize.Height))
return new Rect(); return new Rect();
Rect deviceRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height); Rect deviceRect = new(0, 0, Device.RgbDevice.ActualSize.Width, Device.RgbDevice.ActualSize.Height);
Geometry geometry = new RectangleGeometry(deviceRect); Geometry geometry = new RectangleGeometry(deviceRect);
geometry.Transform = new RotateTransform(Device.Rotation); geometry.Transform = new RotateTransform(Device.Rotation);

View File

@ -10,8 +10,6 @@ namespace Artemis.UI.Screens.Device;
public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceLogicalLayoutDialogViewModel> public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceLogicalLayoutDialogViewModel>
{ {
private readonly AutoCompleteBox _autoCompleteBox;
public DeviceLogicalLayoutDialogView() public DeviceLogicalLayoutDialogView()
{ {
InitializeComponent(); InitializeComponent();
@ -23,8 +21,8 @@ public partial class DeviceLogicalLayoutDialogView : ReactiveUserControl<DeviceL
private async Task DelayedAutoFocus() private async Task DelayedAutoFocus()
{ {
await Task.Delay(200); await Task.Delay(200);
_autoCompleteBox.Focus(); RegionsAutoCompleteBox.Focus();
_autoCompleteBox.PopulateComplete(); RegionsAutoCompleteBox.PopulateComplete();
} }
private bool SearchRegions(string search, object item) private bool SearchRegions(string search, object item)