using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace RGB.NET.Core; /// /// Represents the abstract base implementation for a . /// public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider { #region Properties & Fields private bool _isDisposed = false; private readonly double _defaultUpdateRateHardLimit; /// public bool IsInitialized { get; protected set; } /// public bool ThrowsExceptions { get; protected set; } /// /// The list of devices managed by this device-provider. /// protected List InternalDevices { get; } = []; /// public virtual IReadOnlyList Devices => new ReadOnlyCollection(InternalDevices); /// /// Gets the dictionary containing the registered update triggers. /// Normally should be used to access them. /// protected Dictionary UpdateTriggerMapping { get; } = []; /// public IReadOnlyList<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers => new ReadOnlyCollection<(int id, IDeviceUpdateTrigger trigger)>(UpdateTriggerMapping.Select(x => (x.Key, x.Value)).ToList()); #endregion #region Events /// public event EventHandler? Exception; /// public event EventHandler? DevicesChanged; #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// The update rate hard limit all update triggers for this device provider are initialized with. protected AbstractRGBDeviceProvider(double defaultUpdateRateHardLimit = 0) { this._defaultUpdateRateHardLimit = defaultUpdateRateHardLimit; } ~AbstractRGBDeviceProvider() => Dispose(false); #endregion #region Methods /// public bool Initialize(RGBDeviceType loadFilter = RGBDeviceType.All, bool throwExceptions = false) { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); ThrowsExceptions = throwExceptions; try { Reset(); InitializeSDK(); foreach (IRGBDevice device in GetLoadedDevices(loadFilter)) AddDevice(device); foreach (IDeviceUpdateTrigger updateTrigger in UpdateTriggerMapping.Values) updateTrigger.Start(); IsInitialized = true; } catch (DeviceProviderException) { Reset(); throw; } catch (Exception ex) { Reset(); Throw(ex, true); return false; } return true; } /// /// Loads devices and returns a filtered list of them. /// /// /// The underlying loading of the devices happens in . /// /// -flags to filter the device with. /// The filtered collection of loaded devices. protected virtual IEnumerable GetLoadedDevices(RGBDeviceType loadFilter) { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); List devices = []; foreach (IRGBDevice device in LoadDevices()) { try { if (loadFilter.HasFlag(device.DeviceInfo.DeviceType)) devices.Add(device); else device.Dispose(); } catch (Exception ex) { Throw(ex); } } return devices; } /// /// Initializes the underlying SDK. /// protected abstract void InitializeSDK(); /// /// Loads all devices this device provider is capable of loading. /// /// /// Filtering happens in . /// /// A collection of loaded devices. protected abstract IEnumerable LoadDevices(); /// /// Gets the mapped to the specified id or a new one if the id wasn't requested before. /// /// /// The creation of the update trigger happens in . /// /// The id of the update trigger. /// The update rate hard limit to be set in the update trigger. /// The update trigger mapped to the specified id. protected virtual IDeviceUpdateTrigger GetUpdateTrigger(int id = -1, double? updateRateHardLimit = null) { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); if (!UpdateTriggerMapping.TryGetValue(id, out IDeviceUpdateTrigger? updaeTrigger)) UpdateTriggerMapping[id] = (updaeTrigger = CreateUpdateTrigger(id, updateRateHardLimit ?? _defaultUpdateRateHardLimit)); return updaeTrigger; } /// /// Creates a update trigger with the specified id and the specified update rate hard limit. /// /// The id of the update trigger. /// The update rate hard limit tobe set in the update trigger. /// The newly created update trigger. protected virtual IDeviceUpdateTrigger CreateUpdateTrigger(int id, double updateRateHardLimit) => new DeviceUpdateTrigger(updateRateHardLimit); /// /// Resets the device provider and disposes all devices and update triggers. /// protected virtual void Reset() { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); foreach (IDeviceUpdateTrigger updateTrigger in UpdateTriggerMapping.Values) updateTrigger.Dispose(); foreach (IRGBDevice device in Devices) device.Dispose(); List devices = [..InternalDevices]; foreach (IRGBDevice device in devices) RemoveDevice(device); UpdateTriggerMapping.Clear(); IsInitialized = false; } /// /// Adds the provided device to the list of managed devices. /// /// The device to add. /// true if the device was added successfully; otherwise false. protected virtual bool AddDevice(IRGBDevice device) { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); if (InternalDevices.Contains(device)) return false; InternalDevices.Add(device); try { OnDevicesChanged(DevicesChangedEventArgs.CreateDevicesAddedArgs(device)); } catch { /* we don't want to throw due to bad event handlers */ } return true; } /// /// Removes the provided device from the list of managed devices. /// /// The device to remove. /// true if the device was removed successfully; otherwise false. protected virtual bool RemoveDevice(IRGBDevice device) { if (_isDisposed) throw new ObjectDisposedException(GetType().FullName); if (!InternalDevices.Remove(device)) return false; try { OnDevicesChanged(DevicesChangedEventArgs.CreateDevicesRemovedArgs(device)); } catch { /* we don't want to throw due to bad event handlers */ } return true; } /// /// Triggers the -event and throws the specified exception if is true and it is not overriden in the event. /// /// The exception to throw. /// Indicates if the exception is critical for device provider to work correctly. public virtual void Throw(Exception ex, bool isCritical = false) { ExceptionEventArgs args = new(ex, isCritical, ThrowsExceptions); try { OnException(args); } catch { /* we don't want to throw due to bad event handlers */ } if (args.Throw) throw new DeviceProviderException(ex, isCritical); } /// /// Throws the .event. /// /// The parameters passed to the event. protected virtual void OnException(ExceptionEventArgs args) => Exception?.Invoke(this, args); /// /// Throws the -event. /// /// The parameters passed to the event. protected virtual void OnDevicesChanged(DevicesChangedEventArgs args) => DevicesChanged?.Invoke(this, args); /// public void Dispose() { if (_isDisposed) return; try { Dispose(true); } catch { /* don't throw in dispose! */ } GC.SuppressFinalize(this); _isDisposed = true; } /// /// Disposes the object and frees all resources. /// /// true if explicitely called through the Dispose-Method, false if called by the destructor. protected virtual void Dispose(bool disposing) => Reset(); #endregion }