1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 13:28:33 +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)
{
_originalLeds = new List<OriginalLed>(rgbDevice.Select(l => new OriginalLed(l)));
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);
Identifier = rgbDevice.GetDeviceIdentifier();
@ -54,10 +54,10 @@ public class ArtemisDevice : CorePropertyChanged
internal ArtemisDevice(IRGBDevice rgbDevice, DeviceProvider deviceProvider, DeviceEntity deviceEntity)
{
_originalLeds = new List<OriginalLed>(rgbDevice.Select(l => new OriginalLed(l)));
Rectangle ledRectangle = new(rgbDevice.Select(x => x.Boundary));
_originalLeds = new List<OriginalLed>(rgbDevice.Select(l => new OriginalLed(l)));
_originalSize = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y);
Identifier = rgbDevice.GetDeviceIdentifier();
DeviceEntity = deviceEntity;
RgbDevice = rgbDevice;

View File

@ -16,7 +16,6 @@ internal sealed class SurfaceManager : IDisposable
private readonly TimerUpdateTrigger _updateTrigger;
private readonly List<ArtemisDevice> _devices = new();
private readonly SKTextureBrush _textureBrush = new(null) {CalculationMode = RenderMode.Absolute};
private readonly object _renderLock = new();
private ListLedGroup? _surfaceLedGroup;
private SKTexture? _texture;
@ -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;
@ -45,34 +44,46 @@ internal sealed class SurfaceManager : IDisposable
public void AddDevices(IEnumerable<ArtemisDevice> devices)
{
lock (_renderLock)
List<IRGBDevice> 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;
}
_texture?.Invalidate();
}
if (!newDevices.Any())
return;
Surface.Attach(newDevices);
_texture?.Invalidate();
}
public void RemoveDevices(IEnumerable<ArtemisDevice> devices)
{
lock (_renderLock)
List<IRGBDevice> 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);
}
_texture?.Invalidate();
}
if (!removedDevices.Any())
return;
Surface.Detach(removedDevices);
_texture?.Invalidate();
}
public bool SetPaused(bool paused)
@ -97,20 +108,14 @@ internal sealed class SurfaceManager : IDisposable
public void UpdateRenderScale(float renderScale)
{
lock (_renderLock)
{
RenderScale = renderScale;
_texture?.Invalidate();
}
RenderScale = renderScale;
_texture?.Invalidate();
}
public void UpdateGraphicsContext(IManagedGraphicsContext? graphicsContext)
{
lock (_renderLock)
{
GraphicsContext = graphicsContext;
_texture?.Invalidate();
}
GraphicsContext = graphicsContext;
_texture?.Invalidate();
}
/// <inheritdoc />
@ -118,29 +123,10 @@ internal sealed class SurfaceManager : IDisposable
{
SetPaused(true);
Surface.UnregisterUpdateTrigger(_updateTrigger);
lock (_renderLock)
{
_updateTrigger.Dispose();
_texture?.Dispose();
Surface.Dispose();
}
}
private void UpdateLedGroup()
{
List<Led> leds = _devices.SelectMany(d => d.Leds).Select(l => l.RgbLed).ToList();
if (_surfaceLedGroup == null)
{
_surfaceLedGroup = new ListLedGroup(Surface, leds) {Brush = _textureBrush};
return;
}
// Clean up the old background
_surfaceLedGroup.Detach();
// Apply the application wide brush and decorator
_surfaceLedGroup = new ListLedGroup(Surface, leds) {Brush = _textureBrush};
_updateTrigger.Dispose();
_texture?.Dispose();
Surface.Dispose();
}
private SKTexture CreateTexture()
@ -155,53 +141,54 @@ 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};
}
UpdateLedGroup();
return _texture;
}
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;
if (texture == null || texture.IsInvalid)
texture = CreateTexture();
_renderer.Render(canvas, args.DeltaTime);
}
finally
{
canvas.RestoreToCount(-1);
canvas.Flush();
texture.CopyPixelData();
}
// 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
}
try
{
_renderer.PostRender(texture);
}
catch
{
// ignored
}
}

View File

@ -153,9 +153,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);

View File

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