// ReSharper disable MemberCanBePrivate.Global // ReSharper disable UnusedMember.Global // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global using System; using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; namespace RGB.NET.Core; /// /// /// /// Represents a generic RGB-device. /// public abstract class AbstractRGBDevice : Placeable, IRGBDevice where TDeviceInfo : class, IRGBDeviceInfo { private RGBSurface? _surface; #region Properties & Fields RGBSurface? IRGBDevice.Surface { get => _surface; set { if (SetProperty(ref _surface, value)) { if (value == null) OnDetached(); else OnAttached(); } } } /// public TDeviceInfo DeviceInfo { get; } /// IRGBDeviceInfo IRGBDevice.DeviceInfo => DeviceInfo; /// public IList ColorCorrections { get; } = new List(); /// /// Gets or sets if the device needs to be flushed on every update. /// protected bool RequiresFlush { get; set; } = false; /// /// Gets a dictionary containing all of the . /// protected Dictionary LedMapping { get; } = []; /// /// Gets the update queue used to update this device. /// protected IUpdateQueue UpdateQueue { get; } #region Indexer /// Led? IRGBDevice.this[LedId ledId] => LedMapping.TryGetValue(ledId, out Led? led) ? led : null; /// Led? IRGBDevice.this[Point location] => LedMapping.Values.FirstOrDefault(x => x.Boundary.Contains(location)); /// IEnumerable IRGBDevice.this[Rectangle referenceRect, double minOverlayPercentage] => LedMapping.Values.Where(x => referenceRect.CalculateIntersectPercentage(x.Boundary) >= minOverlayPercentage); #endregion #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// The device info of this device. /// The queue used to update this device. protected AbstractRGBDevice(TDeviceInfo deviceInfo, IUpdateQueue updateQueue) { this.DeviceInfo = deviceInfo; this.UpdateQueue = updateQueue; UpdateQueue.AddReferencingObject(this); } #endregion #region Methods /// public virtual void Update(bool flushLeds = false) { // Device-specific updates DeviceUpdate(); // Send LEDs to SDK UpdateLeds(GetLedsToUpdate(flushLeds)); } /// /// Gets an enumerable of LEDs that are changed and requires an update. /// /// Forces all LEDs to be treated as dirty. /// The collection LEDs to update. protected virtual IEnumerable GetLedsToUpdate(bool flushLeds) => ((RequiresFlush || flushLeds || UpdateQueue.RequiresFlush) ? LedMapping.Values : LedMapping.Values.Where(x => x.IsDirty)).Where(led => led.RequestedColor?.A > 0); /// /// Gets an enumerable of a custom data and color tuple for the specified leds. /// /// /// Applies all . /// if no ist specified the is used. /// /// The enumerable of leds to convert. /// The enumerable of custom data and color tuples for the specified leds. [MethodImpl(MethodImplOptions.AggressiveInlining)] protected (object key, Color color) GetUpdateData(Led led) { Color color = led.Color; object key = led.CustomData ?? led.Id; // ReSharper disable once ForCanBeConvertedToForeach - This causes an allocation that's not really needed here for (int i = 0; i < ColorCorrections.Count; i++) ColorCorrections[i].ApplyTo(ref color); return (key, color); } /// /// Sends all the updated to the device. /// protected virtual void UpdateLeds(IEnumerable ledsToUpdate) { (object key, Color color)[] buffer = ArrayPool<(object, Color)>.Shared.Rent(LedMapping.Count); int counter = 0; foreach (Led led in ledsToUpdate) { led.Update(); buffer[counter] = GetUpdateData(led); ++counter; } UpdateQueue.SetData(new ReadOnlySpan<(object, Color)>(buffer)[..counter]); ArrayPool<(object, Color)>.Shared.Return(buffer); } /// public virtual void Dispose() { try { UpdateQueue.RemoveReferencingObject(this); if (!UpdateQueue.HasActiveReferences()) UpdateQueue.Dispose(); } catch { /* :( */ } try { LedMapping.Clear(); } catch { /* this really shouldn't happen */ } IdGenerator.ResetCounter(GetType().Assembly); GC.SuppressFinalize(this); } /// /// Performs device specific updates. /// protected virtual void DeviceUpdate() { } /// public virtual Led? AddLed(LedId ledId, Point location, Size size, object? customData = null) { if ((ledId == LedId.Invalid) || LedMapping.ContainsKey(ledId)) return null; Led led = new(this, ledId, location, size, customData ?? GetLedCustomData(ledId)); LedMapping.Add(ledId, led); return led; } /// public virtual Led? RemoveLed(LedId ledId) { if (ledId == LedId.Invalid) return null; if (!LedMapping.TryGetValue(ledId, out Led? led)) return null; LedMapping.Remove(ledId); return led; } /// /// Gets the custom data associated with the specified LED. /// /// The id of the led. /// The custom data for the specified LED. protected virtual object? GetLedCustomData(LedId ledId) => null; /// /// Called when the device is attached to a surface. /// /// /// When overriden base should be called to validate boundries. /// protected virtual void OnAttached() { if (Location == Point.Invalid) Location = new Point(0, 0); if (Size == Size.Invalid) { Rectangle ledRectangle = new(this.Select(x => x.Boundary)); Size = ledRectangle.Size + new Size(ledRectangle.Location.X, ledRectangle.Location.Y); } } /// /// Called when the device is detached from a surface. /// protected virtual void OnDetached() { } #region Enumerator /// /// /// Returns an enumerator that iterates over all of the . /// /// An enumerator for all of the . public IEnumerator GetEnumerator() => LedMapping.Values.GetEnumerator(); /// /// /// Returns an enumerator that iterates over all of the . /// /// An enumerator for all of the . IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion #endregion }