Skip to content

Commit 22a1a92

Browse files
PlayStation: move per-device HID state onto the device classes
Addresses DarthAffe's review feedback on PR #454 — the per-device lifecycle state (HidStream, optional HidRawWriter, DevicePath, and the "known disconnected" hint) was previously held in four collections on the provider, all keyed by IRGBDevice. Moves that state onto the DualShock4 / DualSense device classes themselves and lets each device clean up its own I/O via Dispose. - New IPlayStationRGBDevice interface exposes DevicePath / IsKnownDisconnected / MarkKnownDisconnected so the provider's hot-plug iteration can walk Devices.OfType<IPlayStationRGBDevice>() instead of consulting a Dictionary<IRGBDevice, ...>. - DualShock4RGBDevice and DualSenseRGBDevice each take their HidStream, HidRawWriter? and DevicePath in the constructor, override Dispose to send a graceful off-frame (when not known-disconnected) and release the I/O. SuspendWrites / Shutdown are no longer needed on the device class — MarkKnownDisconnected covers the former, Dispose covers the latter. - Provider drops _openStreams, _devicePaths, _rawWriters, _confirmedDisconnected dictionaries plus the _stateLock and _disposing flag they protected. RemoveDevice becomes a base.RemoveDevice + device.Dispose passthrough. Reconcile and SuspendDeadDevices iterate Devices.OfType<IPlayStationRGBDevice>() and read .DevicePath off each. Dispose marks all owned devices as known-disconnected before tearing them down so their off-frame attempt is skipped at app shutdown. No behaviour change: same hot-plug timings, same off-frame policy, same alive-paths snapshot mechanism. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6a3462b commit 22a1a92

4 files changed

Lines changed: 160 additions & 130 deletions

File tree

RGB.NET.Devices.PlayStation/DualSense/DualSenseRGBDevice.cs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,40 @@
1+
using HidSharp;
12
using RGB.NET.Core;
23

34
namespace RGB.NET.Devices.PlayStation;
45

5-
/// <inheritdoc />
6+
/// <inheritdoc cref="AbstractRGBDevice{TDeviceInfo}" />
67
/// <summary>
7-
/// Represents a Sony DualSense controller (PS5 / DualSense Edge).
8+
/// Represents a Sony DualSense controller (PS5 / DualSense Edge). Owns its
9+
/// HID I/O directly — the open <see cref="HidStream"/>, the optional Win32
10+
/// raw-write fallback, and the device path used for identity comparison
11+
/// during hot-plug — and tears them down on <see cref="Dispose"/>.
812
/// </summary>
9-
public sealed class DualSenseRGBDevice : AbstractRGBDevice<PlayStationDeviceInfo>
13+
public sealed class DualSenseRGBDevice : AbstractRGBDevice<PlayStationDeviceInfo>, IPlayStationRGBDevice
1014
{
1115
#region Properties & Fields
1216

1317
private readonly DualSenseUpdateQueue _updateQueue;
18+
private readonly HidStream _stream;
19+
private readonly HidRawWriter? _rawWriter;
20+
21+
/// <inheritdoc />
22+
public string DevicePath { get; }
23+
24+
/// <inheritdoc />
25+
public bool IsKnownDisconnected { get; private set; }
1426

1527
#endregion
1628

1729
#region Constructors
1830

19-
internal DualSenseRGBDevice(PlayStationDeviceInfo deviceInfo, DualSenseUpdateQueue updateQueue)
31+
internal DualSenseRGBDevice(PlayStationDeviceInfo deviceInfo, DualSenseUpdateQueue updateQueue, HidStream stream, HidRawWriter? rawWriter, string devicePath)
2032
: base(deviceInfo, updateQueue)
2133
{
2234
_updateQueue = updateQueue;
35+
_stream = stream;
36+
_rawWriter = rawWriter;
37+
DevicePath = devicePath ?? string.Empty;
2338
InitializeLayout();
2439
}
2540

@@ -58,8 +73,22 @@ private void InitializeLayout()
5873
}
5974
}
6075

61-
internal void SuspendWrites() => _updateQueue.SuspendWrites();
62-
internal void Shutdown(bool sendOffFrame = true) => _updateQueue.Shutdown(sendOffFrame);
76+
/// <inheritdoc />
77+
public void MarkKnownDisconnected()
78+
{
79+
IsKnownDisconnected = true;
80+
try { _updateQueue.SuspendWrites(); } catch { /* best effort */ }
81+
}
82+
83+
/// <inheritdoc />
84+
public override void Dispose()
85+
{
86+
try { _updateQueue.Shutdown(sendOffFrame: !IsKnownDisconnected); } catch { /* best effort */ }
87+
try { _rawWriter?.Dispose(); } catch { /* best effort */ }
88+
try { _stream.Dispose(); } catch { /* best effort */ }
89+
90+
base.Dispose();
91+
}
6392

6493
#endregion
6594
}

RGB.NET.Devices.PlayStation/DualShock4/DualShock4RGBDevice.cs

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,40 @@
1+
using HidSharp;
12
using RGB.NET.Core;
23

34
namespace RGB.NET.Devices.PlayStation;
45

5-
/// <inheritdoc />
6+
/// <inheritdoc cref="AbstractRGBDevice{TDeviceInfo}" />
67
/// <summary>
7-
/// Represents a Sony DualShock 4 controller.
8+
/// Represents a Sony DualShock 4 controller. Owns its HID I/O directly —
9+
/// the open <see cref="HidStream"/>, the optional Win32 raw-write fallback,
10+
/// and the device path used for identity comparison during hot-plug — and
11+
/// tears them down on <see cref="Dispose"/>.
812
/// </summary>
9-
public sealed class DualShock4RGBDevice : AbstractRGBDevice<PlayStationDeviceInfo>
13+
public sealed class DualShock4RGBDevice : AbstractRGBDevice<PlayStationDeviceInfo>, IPlayStationRGBDevice
1014
{
1115
#region Properties & Fields
1216

1317
private readonly DualShock4UpdateQueue _updateQueue;
18+
private readonly HidStream _stream;
19+
private readonly HidRawWriter? _rawWriter;
20+
21+
/// <inheritdoc />
22+
public string DevicePath { get; }
23+
24+
/// <inheritdoc />
25+
public bool IsKnownDisconnected { get; private set; }
1426

1527
#endregion
1628

1729
#region Constructors
1830

19-
internal DualShock4RGBDevice(PlayStationDeviceInfo deviceInfo, DualShock4UpdateQueue updateQueue)
31+
internal DualShock4RGBDevice(PlayStationDeviceInfo deviceInfo, DualShock4UpdateQueue updateQueue, HidStream stream, HidRawWriter? rawWriter, string devicePath)
2032
: base(deviceInfo, updateQueue)
2133
{
2234
_updateQueue = updateQueue;
35+
_stream = stream;
36+
_rawWriter = rawWriter;
37+
DevicePath = devicePath ?? string.Empty;
2338
InitializeLayout();
2439
}
2540

@@ -38,8 +53,26 @@ private void InitializeLayout()
3853
lightbar.Shape = Shape.Rectangle;
3954
}
4055

41-
internal void SuspendWrites() => _updateQueue.SuspendWrites();
42-
internal void Shutdown(bool sendOffFrame = true) => _updateQueue.Shutdown(sendOffFrame);
56+
/// <inheritdoc />
57+
public void MarkKnownDisconnected()
58+
{
59+
IsKnownDisconnected = true;
60+
try { _updateQueue.SuspendWrites(); } catch { /* best effort */ }
61+
}
62+
63+
/// <inheritdoc />
64+
public override void Dispose()
65+
{
66+
// Off-frame is best-effort: send a final all-zero lightbar so the
67+
// controller doesn't sit on our last colour after we tear down.
68+
// Skipped when we've already been marked disconnected — the handle
69+
// is invalid and the write would just throw.
70+
try { _updateQueue.Shutdown(sendOffFrame: !IsKnownDisconnected); } catch { /* best effort */ }
71+
try { _rawWriter?.Dispose(); } catch { /* best effort */ }
72+
try { _stream.Dispose(); } catch { /* best effort */ }
73+
74+
base.Dispose();
75+
}
4376

4477
#endregion
4578
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using RGB.NET.Core;
2+
3+
namespace RGB.NET.Devices.PlayStation;
4+
5+
/// <summary>
6+
/// Common contract for PlayStation controller RGB devices. Each device owns
7+
/// its own HID I/O (HidStream + optional HidRawWriter) and DevicePath, and
8+
/// is responsible for tearing those down on <see cref="System.IDisposable.Dispose"/>.
9+
/// </summary>
10+
internal interface IPlayStationRGBDevice : IRGBDevice
11+
{
12+
/// <summary>The Windows / Linux HID device path the controller was opened on.</summary>
13+
string DevicePath { get; }
14+
15+
/// <summary>
16+
/// Set by the provider's hot-plug pass when the controller has disappeared
17+
/// from HID enumeration. Causes Dispose to skip the polite off-frame write
18+
/// (which would just throw against the invalidated handle anyway).
19+
/// </summary>
20+
bool IsKnownDisconnected { get; }
21+
22+
/// <summary>
23+
/// Records that the device is no longer reachable on its HID path AND
24+
/// suspends any further writes from the update queue. Called from the
25+
/// hot-plug callback before the debounced Reconcile fires, so the next
26+
/// 30Hz tick is a no-op rather than an exception.
27+
/// </summary>
28+
void MarkKnownDisconnected();
29+
}

0 commit comments

Comments
 (0)