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

Merge pull request #298 from DarthAffe/Core/Optimizations

(MAJOR) Optimized surface-updating to reduce the amount of allocations
This commit is contained in:
DarthAffe 2023-02-11 23:03:49 +01:00 committed by GitHub
commit df73551497
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 129 additions and 24 deletions

View File

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

View File

@ -1,5 +1,6 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace RGB.NET.Core; namespace RGB.NET.Core;
@ -51,6 +52,9 @@ public abstract class AbstractLedGroup : AbstractDecoratable<ILedGroupDecorator>
/// <inheritdoc /> /// <inheritdoc />
public virtual void OnDetach() { } public virtual void OnDetach() { }
/// <inheritdoc />
public virtual IList<Led> ToList() => GetLeds().ToList();
/// <inheritdoc /> /// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 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"/>. /// Called when the <see cref="ILedGroup"/> is detached from the <see cref="RGBSurface"/>.
/// </summary> /// </summary>
void OnDetach(); 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. /// Gets a list containing the <see cref="T:RGB.NET.Core.Led" /> from this group.
/// </summary> /// </summary>
/// <returns>The list containing the <see cref="T:RGB.NET.Core.Led" />.</returns> /// <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) lock (GroupLeds)
return new List<Led>(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"/>. /// 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> /// </summary>
/// <param name="size">The size of of this <see cref="T:RGB.NET.Core.Rectangle" />.</param> /// <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> /// <summary>
@ -121,14 +122,12 @@ public readonly struct Rectangle : IEquatable<Rectangle>
: this(points.AsEnumerable()) : this(points.AsEnumerable())
{ } { }
/// <inheritdoc />
/// <summary> /// <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" />. /// 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. /// 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> /// </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> /// <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) public Rectangle(IEnumerable<Point> points)
: this()
{ {
bool hasPoint = false; bool hasPoint = false;
float posX = float.MaxValue; float posX = float.MaxValue;
@ -145,13 +144,37 @@ public readonly struct Rectangle : IEquatable<Rectangle>
posY2 = Math.Max(posY2, point.Y); 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; Location = location;
Size = size; Size = size;
Center = new Point(Location.X + (Size.Width / 2.0f), Location.Y + (Size.Height / 2.0f)); 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 #endregion
#region Methods #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. /// 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> /// </summary>
/// <param name="flushLeds">Specifies whether all <see cref="Led"/>, (including clean ones) should be updated.</param> /// <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 try
{ {
@ -149,19 +150,25 @@ public sealed class RGBSurface : AbstractBindable, IDisposable
{ {
OnUpdating(updateTrigger, customData); 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) if (render)
lock (_ledGroups) lock (_ledGroups)
{ {
// Render brushes // Render brushes
foreach (ILedGroup ledGroup in _ledGroups) for (int i = 0; i < _ledGroups.Count; i++)
try { Render(ledGroup); } {
try { Render(_ledGroups[i]); }
catch (Exception ex) { OnException(ex); } catch (Exception ex) { OnException(ex); }
}
} }
if (updateDevices) if (updateDevices)
foreach (IRGBDevice device in _devices) for (int i = 0; i < _devices.Count; i++)
try { device.Update(flushLeds); } {
try { _devices[i].Update(flushLeds); }
catch (Exception ex) { OnException(ex); } catch (Exception ex) { OnException(ex); }
}
// ReSharper restore ForCanBeConvertedToForeach
OnUpdated(); 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> /// <exception cref="ArgumentException">Thrown if the <see cref="IBrush.CalculationMode"/> of the Brush is not valid.</exception>
private void Render(ILedGroup ledGroup) private void Render(ILedGroup ledGroup)
{ {
IList<Led> leds = ledGroup.ToList();
IBrush? brush = ledGroup.Brush; IBrush? brush = ledGroup.Brush;
if ((brush == null) || !brush.IsEnabled) return; if ((brush == null) || !brush.IsEnabled) return;
IList<Led> leds = ledGroup.ToList();
IEnumerable<(RenderTarget renderTarget, Color color)> render; IEnumerable<(RenderTarget renderTarget, Color color)> render;
switch (brush.CalculationMode) switch (brush.CalculationMode)
{ {
case RenderMode.Relative: case RenderMode.Relative:
Rectangle brushRectangle = new(leds.Select(led => led.AbsoluteBoundary)); Rectangle brushRectangle = new(leds);
Point offset = new(-brushRectangle.Location.X, -brushRectangle.Location.Y); Point offset = new(-brushRectangle.Location.X, -brushRectangle.Location.Y);
brushRectangle = brushRectangle.SetLocation(new Point(0, 0)); brushRectangle = brushRectangle.SetLocation(new Point(0, 0));
render = brush.Render(brushRectangle, leds.Select(led => new RenderTarget(led, led.AbsoluteBoundary.Translate(offset)))); 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> /// <summary>
/// Handles the needed event-calls before updating. /// Handles the needed event-calls before updating.
/// </summary> /// </summary>
private void OnUpdating(IUpdateTrigger? trigger, CustomUpdateData customData) private void OnUpdating(IUpdateTrigger? trigger, ICustomUpdateData customData)
{ {
try try
{ {

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
namespace RGB.NET.Core; namespace RGB.NET.Core;
@ -34,11 +35,24 @@ public static class CustomUpdateDataIndex
/// <summary> /// <summary>
/// Represents a set of custom data, each indexed by a string-key. /// Represents a set of custom data, each indexed by a string-key.
/// </summary> /// </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 #region Properties & Fields
private Dictionary<string, object?> _data = new(); private readonly Dictionary<string, object?> _data = new();
#endregion #endregion
@ -51,8 +65,8 @@ public class CustomUpdateData
/// <returns>The value represented by the specified key.</returns> /// <returns>The value represented by the specified key.</returns>
public object? this[string key] public object? this[string key]
{ {
get => _data.TryGetValue(key.ToUpperInvariant(), out object? data) ? data : default; get => _data.TryGetValue(key, out object? data) ? data : default;
set => _data[key.ToUpperInvariant()] = value; set => _data[key] = value;
} }
#endregion #endregion
@ -77,3 +91,39 @@ public class CustomUpdateData
#endregion #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" />. /// Gets a list containing all <see cref="T:RGB.NET.Core.Led" /> of this <see cref="T:RGB.NET.Presets.Groups.RectangleLedGroup" />.
/// </summary> /// </summary>
/// <returns>The list containing all <see cref="T:RGB.NET.Core.Led" /> of this <see cref="T:RGB.NET.Presets.Groups.RectangleLedGroup" />.</returns> /// <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; private void InvalidateCache() => _ledCache = null;