1
0
mirror of https://github.com/DarthAffe/RGB.NET.git synced 2025-12-12 17:48:31 +00:00

(MAJOR) Optimized surface-updating to reduce the amount of allocations

This commit is contained in:
Darth Affe 2023-02-11 22:36:59 +01:00
parent 3ff357212b
commit 8431a8cb5e
8 changed files with 129 additions and 24 deletions

View File

@ -26,7 +26,7 @@ public class UpdatingEventArgs : EventArgs
/// <summary>
/// Gets the custom-data provided by the trigger for this update.
/// </summary>
public CustomUpdateData CustomData { get; }
public ICustomUpdateData CustomData { get; }
#endregion
@ -39,7 +39,7 @@ public class UpdatingEventArgs : EventArgs
/// <param name="deltaTime">The elapsed time (in seconds) since the last update.</param>
/// <param name="trigger">The trigger causing this update.</param>
/// <param name="customData">The custom-data provided by the trigger for this update.</param>
public UpdatingEventArgs(double deltaTime, IUpdateTrigger? trigger, CustomUpdateData customData)
public UpdatingEventArgs(double deltaTime, IUpdateTrigger? trigger, ICustomUpdateData customData)
{
this.DeltaTime = deltaTime;
this.Trigger = trigger;

View File

@ -1,5 +1,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace RGB.NET.Core;
@ -51,6 +52,9 @@ public abstract class AbstractLedGroup : AbstractDecoratable<ILedGroupDecorator>
/// <inheritdoc />
public virtual void OnDetach() { }
/// <inheritdoc />
public virtual IList<Led> ToList() => GetLeds().ToList();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

View File

@ -39,4 +39,10 @@ public interface ILedGroup : IDecoratable<ILedGroupDecorator>, IEnumerable<Led>
/// Called when the <see cref="ILedGroup"/> is detached from the <see cref="RGBSurface"/>.
/// </summary>
void OnDetach();
/// <summary>
/// Returns a list containing all <see cref="Led"/> in this group.
/// </summary>
/// <returns>A list containing all <see cref="Led"/> in this group.</returns>
IList<Led> ToList();
}

View File

@ -122,7 +122,14 @@ public class ListLedGroup : AbstractLedGroup
/// Gets a list containing the <see cref="T:RGB.NET.Core.Led" /> from this group.
/// </summary>
/// <returns>The list containing the <see cref="T:RGB.NET.Core.Led" />.</returns>
protected override IEnumerable<Led> GetLeds()
protected override IEnumerable<Led> GetLeds() => ToList();
/// <inheritdoc />
/// <summary>
/// Gets a list containing the <see cref="T:RGB.NET.Core.Led" /> from this group.
/// </summary>
/// <returns>The list containing the <see cref="T:RGB.NET.Core.Led" />.</returns>
public override IList<Led> ToList()
{
lock (GroupLeds)
return new List<Led>(GroupLeds);

View File

@ -57,7 +57,8 @@ public readonly struct Rectangle : IEquatable<Rectangle>
/// Initializes a new instance of the <see cref="Rectangle"/> class using the <see cref="Location"/>(0,0) and the specified <see cref="Core.Size"/>.
/// </summary>
/// <param name="size">The size of of this <see cref="T:RGB.NET.Core.Rectangle" />.</param>
public Rectangle(Size size) : this(new Point(), size)
public Rectangle(Size size)
: this(new Point(), size)
{ }
/// <summary>
@ -120,15 +121,13 @@ public readonly struct Rectangle : IEquatable<Rectangle>
public Rectangle(params Point[] points)
: this(points.AsEnumerable())
{ }
/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="T:RGB.NET.Core.Rectangle" /> class using the specified list of <see cref="T:RGB.NET.Core.Point" />.
/// The <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" /> is calculated to contain all points provided as parameters.
/// </summary>
/// <param name="points">The list of <see cref="T:RGB.NET.Core.Point" /> used to calculate the <see cref="P:RGB.NET.Core.Rectangle.Location" /> and <see cref="P:RGB.NET.Core.Rectangle.Size" />.</param>
public Rectangle(IEnumerable<Point> points)
: this()
{
bool hasPoint = false;
float posX = float.MaxValue;
@ -145,13 +144,37 @@ public readonly struct Rectangle : IEquatable<Rectangle>
posY2 = Math.Max(posY2, point.Y);
}
(Point location, Size size) = hasPoint ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2)) : InitializeFromPoints(new Point(0, 0), new Point(0, 0));
(Point location, Size size) = hasPoint ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2))
: InitializeFromPoints(new Point(0, 0), new Point(0, 0));
Location = location;
Size = size;
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f));
}
internal Rectangle(IList<Led> leds)
{
float posX = float.MaxValue;
float posY = float.MaxValue;
float posX2 = float.MinValue;
float posY2 = float.MinValue;
// ReSharper disable once ForCanBeConvertedToForeach
for (int i = 0; i < leds.Count; i++)
{
Rectangle rectangle = leds[i].AbsoluteBoundary;
posX = Math.Min(posX, rectangle.Location.X);
posY = Math.Min(posY, rectangle.Location.Y);
posX2 = Math.Max(posX2, rectangle.Location.X + rectangle.Size.Width);
posY2 = Math.Max(posY2, rectangle.Location.Y + rectangle.Size.Height);
}
(Point location, Size size) = leds.Count > 0 ? InitializeFromPoints(new Point(posX, posY), new Point(posX2, posY2)) : InitializeFromPoints(new Point(0, 0), new Point(0, 0));
Location = location;
Size = size;
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f));
}
#endregion
#region Methods

View File

@ -132,11 +132,12 @@ public sealed class RGBSurface : AbstractBindable, IDisposable
/// Perform a full update for all devices. Updates only dirty <see cref="Led"/> by default, or all <see cref="Led"/>, if flushLeds is set to true.
/// </summary>
/// <param name="flushLeds">Specifies whether all <see cref="Led"/>, (including clean ones) should be updated.</param>
public void Update(bool flushLeds = false) => Update(null, new CustomUpdateData((CustomUpdateDataIndex.FLUSH_LEDS, flushLeds)));
//public void Update(bool flushLeds = false) => Update(null, new CustomUpdateData((CustomUpdateDataIndex.FLUSH_LEDS, flushLeds)));
public void Update(bool flushLeds = false) => Update(null, flushLeds ? DefaultCustomUpdateData.FLUSH : DefaultCustomUpdateData.NO_FLUSH);
private void Update(object? updateTrigger, CustomUpdateData customData) => Update(updateTrigger as IUpdateTrigger, customData);
private void Update(object? updateTrigger, ICustomUpdateData customData) => Update(updateTrigger as IUpdateTrigger, customData);
private void Update(IUpdateTrigger? updateTrigger, CustomUpdateData customData)
private void Update(IUpdateTrigger? updateTrigger, ICustomUpdateData customData)
{
try
{
@ -149,19 +150,25 @@ public sealed class RGBSurface : AbstractBindable, IDisposable
{
OnUpdating(updateTrigger, customData);
// ReSharper disable ForCanBeConvertedToForeach - 'for' has a performance benefit (no enumerator allocation) here and since 'Update' is considered a hot path it's optimized
if (render)
lock (_ledGroups)
{
// Render brushes
foreach (ILedGroup ledGroup in _ledGroups)
try { Render(ledGroup); }
for (int i = 0; i < _ledGroups.Count; i++)
{
try { Render(_ledGroups[i]); }
catch (Exception ex) { OnException(ex); }
}
}
if (updateDevices)
foreach (IRGBDevice device in _devices)
try { device.Update(flushLeds); }
for (int i = 0; i < _devices.Count; i++)
{
try { _devices[i].Update(flushLeds); }
catch (Exception ex) { OnException(ex); }
}
// ReSharper restore ForCanBeConvertedToForeach
OnUpdated();
}
@ -197,16 +204,17 @@ public sealed class RGBSurface : AbstractBindable, IDisposable
/// <exception cref="ArgumentException">Thrown if the <see cref="IBrush.CalculationMode"/> of the Brush is not valid.</exception>
private void Render(ILedGroup ledGroup)
{
IList<Led> leds = ledGroup.ToList();
IBrush? brush = ledGroup.Brush;
if ((brush == null) || !brush.IsEnabled) return;
IList<Led> leds = ledGroup.ToList();
IEnumerable<(RenderTarget renderTarget, Color color)> render;
switch (brush.CalculationMode)
{
case RenderMode.Relative:
Rectangle brushRectangle = new(leds.Select(led => led.AbsoluteBoundary));
Rectangle brushRectangle = new(leds);
Point offset = new(-brushRectangle.Location.X, -brushRectangle.Location.Y);
brushRectangle = brushRectangle.SetLocation(new Point(0, 0));
render = brush.Render(brushRectangle, leds.Select(led => new RenderTarget(led, led.AbsoluteBoundary.Translate(offset))));
@ -358,7 +366,7 @@ public sealed class RGBSurface : AbstractBindable, IDisposable
/// <summary>
/// Handles the needed event-calls before updating.
/// </summary>
private void OnUpdating(IUpdateTrigger? trigger, CustomUpdateData customData)
private void OnUpdating(IUpdateTrigger? trigger, ICustomUpdateData customData)
{
try
{

View File

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace RGB.NET.Core;
@ -34,11 +35,24 @@ public static class CustomUpdateDataIndex
/// <summary>
/// Represents a set of custom data, each indexed by a string-key.
/// </summary>
public class CustomUpdateData
public interface ICustomUpdateData
{
/// <summary>
/// Gets the value for a specific key.
/// </summary>
/// <param name="key">The key of the value.</param>
/// <returns>The value represented by the specified key.</returns>
object? this[string key] { get; }
}
/// <summary>
/// Represents a set of custom data, each indexed by a string-key.
/// </summary>
public class CustomUpdateData : ICustomUpdateData
{
#region Properties & Fields
private Dictionary<string, object?> _data = new();
private readonly Dictionary<string, object?> _data = new();
#endregion
@ -51,8 +65,8 @@ public class CustomUpdateData
/// <returns>The value represented by the specified key.</returns>
public object? this[string key]
{
get => _data.TryGetValue(key.ToUpperInvariant(), out object? data) ? data : default;
set => _data[key.ToUpperInvariant()] = value;
get => _data.TryGetValue(key, out object? data) ? data : default;
set => _data[key] = value;
}
#endregion
@ -77,3 +91,39 @@ public class CustomUpdateData
#endregion
}
internal class DefaultCustomUpdateData : ICustomUpdateData
{
#region Constants
public static readonly DefaultCustomUpdateData FLUSH = new(true);
public static readonly DefaultCustomUpdateData NO_FLUSH = new(false);
#endregion
#region Properties & Fields
private readonly bool _flushLeds;
public object? this[string key]
{
get
{
if (string.Equals(key, CustomUpdateDataIndex.FLUSH_LEDS, StringComparison.Ordinal))
return _flushLeds;
return null;
}
}
#endregion
#region Constructors
public DefaultCustomUpdateData(bool flushLeds)
{
this._flushLeds = flushLeds;
}
#endregion
}

View File

@ -114,7 +114,14 @@ public class RectangleLedGroup : AbstractLedGroup
/// Gets a list containing all <see cref="T:RGB.NET.Core.Led" /> of this <see cref="T:RGB.NET.Presets.Groups.RectangleLedGroup" />.
/// </summary>
/// <returns>The list containing all <see cref="T:RGB.NET.Core.Led" /> of this <see cref="T:RGB.NET.Presets.Groups.RectangleLedGroup" />.</returns>
protected override IEnumerable<Led> GetLeds() => _ledCache ??= (Surface?.Leds.Where(led => led.AbsoluteBoundary.CalculateIntersectPercentage(Rectangle) >= MinOverlayPercentage).ToList() ?? new List<Led>());
protected override IEnumerable<Led> GetLeds() => ToList();
/// <inheritdoc />
/// <summary>
/// Gets a list containing all <see cref="T:RGB.NET.Core.Led" /> of this <see cref="T:RGB.NET.Presets.Groups.RectangleLedGroup" />.
/// </summary>
/// <returns>The list containing all <see cref="T:RGB.NET.Core.Led" /> of this <see cref="T:RGB.NET.Presets.Groups.RectangleLedGroup" />.</returns>
public override IList<Led> ToList() => _ledCache ??= (Surface?.Leds.Where(led => led.AbsoluteBoundary.CalculateIntersectPercentage(Rectangle) >= MinOverlayPercentage).ToList() ?? new List<Led>());
private void InvalidateCache() => _ledCache = null;