-
Notifications
You must be signed in to change notification settings - Fork 75
/
Copy pathAbstractRGBDeviceProvider.cs
282 lines (222 loc) · 9.65 KB
/
AbstractRGBDeviceProvider.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace RGB.NET.Core;
/// <summary>
/// Represents the abstract base implementation for a <see cref="IRGBDeviceProvider"/>.
/// </summary>
public abstract class AbstractRGBDeviceProvider : IRGBDeviceProvider
{
#region Properties & Fields
private bool _isDisposed = false;
private readonly double _defaultUpdateRateHardLimit;
/// <inheritdoc />
public bool IsInitialized { get; protected set; }
/// <inheritdoc />
public bool ThrowsExceptions { get; protected set; }
/// <summary>
/// The list of devices managed by this device-provider.
/// </summary>
protected List<IRGBDevice> InternalDevices { get; } = [];
/// <inheritdoc />
public virtual IReadOnlyList<IRGBDevice> Devices => new ReadOnlyCollection<IRGBDevice>(InternalDevices);
/// <summary>
/// Gets the dictionary containing the registered update triggers.
/// Normally <see cref="UpdateTriggers"/> should be used to access them.
/// </summary>
protected Dictionary<int, IDeviceUpdateTrigger> UpdateTriggerMapping { get; } = [];
/// <inheritdoc />
public IReadOnlyList<(int id, IDeviceUpdateTrigger trigger)> UpdateTriggers => new ReadOnlyCollection<(int id, IDeviceUpdateTrigger trigger)>(UpdateTriggerMapping.Select(x => (x.Key, x.Value)).ToList());
#endregion
#region Events
/// <inheritdoc />
public event EventHandler<ExceptionEventArgs>? Exception;
/// <inheritdoc />
public event EventHandler<DevicesChangedEventArgs>? DevicesChanged;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="AbstractRGBDeviceProvider" /> class.
/// </summary>
/// <param name="defaultUpdateRateHardLimit">The update rate hard limit all update triggers for this device provider are initialized with.</param>
protected AbstractRGBDeviceProvider(double defaultUpdateRateHardLimit = 0)
{
this._defaultUpdateRateHardLimit = defaultUpdateRateHardLimit;
}
~AbstractRGBDeviceProvider() => Dispose(false);
#endregion
#region Methods
/// <inheritdoc />
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;
}
/// <summary>
/// Loads devices and returns a filtered list of them.
/// </summary>
/// <remarks>
/// The underlying loading of the devices happens in <see cref="LoadDevices"/>.
/// </remarks>
/// <param name="loadFilter"><see cref="RGBDeviceType"/>-flags to filter the device with.</param>
/// <returns>The filtered collection of loaded devices.</returns>
protected virtual IEnumerable<IRGBDevice> GetLoadedDevices(RGBDeviceType loadFilter)
{
if (_isDisposed) throw new ObjectDisposedException(GetType().FullName);
List<IRGBDevice> 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;
}
/// <summary>
/// Initializes the underlying SDK.
/// </summary>
protected abstract void InitializeSDK();
/// <summary>
/// Loads all devices this device provider is capable of loading.
/// </summary>
/// <remarks>
/// Filtering happens in <see cref="GetLoadedDevices"/>.
/// </remarks>
/// <returns>A collection of loaded devices.</returns>
protected abstract IEnumerable<IRGBDevice> LoadDevices();
/// <summary>
/// Gets the <see cref="IDeviceUpdateTrigger"/> mapped to the specified id or a new one if the id wasn't requested before.
/// </summary>
/// <remarks>
/// The creation of the update trigger happens in <see cref="CreateUpdateTrigger"/>.
/// </remarks>
/// <param name="id">The id of the update trigger.</param>
/// <param name="updateRateHardLimit">The update rate hard limit to be set in the update trigger.</param>
/// <returns>The update trigger mapped to the specified id.</returns>
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;
}
/// <summary>
/// Creates a update trigger with the specified id and the specified update rate hard limit.
/// </summary>
/// <param name="id">The id of the update trigger.</param>
/// <param name="updateRateHardLimit">The update rate hard limit tobe set in the update trigger.</param>
/// <returns>The newly created update trigger.</returns>
protected virtual IDeviceUpdateTrigger CreateUpdateTrigger(int id, double updateRateHardLimit) => new DeviceUpdateTrigger(updateRateHardLimit);
/// <summary>
/// Resets the device provider and disposes all devices and update triggers.
/// </summary>
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<IRGBDevice> devices = [..InternalDevices];
foreach (IRGBDevice device in devices)
RemoveDevice(device);
UpdateTriggerMapping.Clear();
IsInitialized = false;
}
/// <summary>
/// Adds the provided device to the list of managed devices.
/// </summary>
/// <param name="device">The device to add.</param>
/// <returns><c>true</c> if the device was added successfully; otherwise <c>false</c>.</returns>
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;
}
/// <summary>
/// Removes the provided device from the list of managed devices.
/// </summary>
/// <param name="device">The device to remove.</param>
/// <returns><c>true</c> if the device was removed successfully; otherwise <c>false</c>.</returns>
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;
}
/// <summary>
/// Triggers the <see cref="Exception"/>-event and throws the specified exception if <see cref="ThrowsExceptions"/> is true and it is not overriden in the event.
/// </summary>
/// <param name="ex">The exception to throw.</param>
/// <param name="isCritical">Indicates if the exception is critical for device provider to work correctly.</param>
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);
}
/// <summary>
/// Throws the <see cref="Exception"/>.event.
/// </summary>
/// <param name="args">The parameters passed to the event.</param>
protected virtual void OnException(ExceptionEventArgs args) => Exception?.Invoke(this, args);
/// <summary>
/// Throws the <see cref="DevicesChanged"/>-event.
/// </summary>
/// <param name="args">The parameters passed to the event.</param>
protected virtual void OnDevicesChanged(DevicesChangedEventArgs args) => DevicesChanged?.Invoke(this, args);
/// <inheritdoc />
public void Dispose()
{
if (_isDisposed) return;
try
{
Dispose(true);
}
catch { /* don't throw in dispose! */ }
GC.SuppressFinalize(this);
_isDisposed = true;
}
/// <summary>
/// Disposes the object and frees all resources.
/// </summary>
/// <param name="disposing"><c>true</c> if explicitely called through the Dispose-Method, <c>false</c> if called by the destructor.</param>
protected virtual void Dispose(bool disposing) => Reset();
#endregion
}