mirror of
https://github.com/DarthAffe/RGB.NET.git
synced 2025-12-12 17:48: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:
commit
df73551497
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user