Phase 8A (2/2): RioJoy.Core multi-targets net48 + net40 (Windows XP flavor)

- TargetFrameworks net48;net40. net48 keeps x64 + ViGEm + System.IO.Ports
  package; net40 adds Microsoft.Bcl.Async + System.ValueTuple and uses the
  in-box SerialPort.
- Compat/TaskCompat bridges Task.Run/Delay/WhenAny/WhenAll (TaskEx on
  net40) and SemaphoreSlim.WaitAsync (net40 blocks briefly - trivial at
  9600 baud).
- IReadOnlyList/IReadOnlyDictionary -> IList/IDictionary throughout
  (net40 predates the IReadOnly* interfaces and the Bcl backport cannot
  make arrays implement them).
- HashCode.Combine replaced with a manual combine (Bcl.HashCode has no
  net40 build); Marshal.SizeOf<T> -> typeof form; ViGEmJoystickSink
  gated #if !NET40. HidFeederJoystickSink stays on both flavors - it
  will drive RioGamepadXP.sys on XP via the same contract.

Both TFMs build; 275 tests green on net48.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-11 20:41:58 -05:00
co-authored by Claude Fable 5
parent 3b2af7b79a
commit 1ff0b16015
28 changed files with 121 additions and 53 deletions
+4 -4
View File
@@ -69,19 +69,19 @@ public sealed class RioSerialLink
{
// If any running loop ends (transport closed / error / cancellation),
// tear the others down too.
await Task.WhenAny(loops).ConfigureAwait(false);
await Compat.TaskCompat.WhenAny(loops).ConfigureAwait(false);
}
finally
{
linked.Cancel();
await Task.WhenAll(loops.Select(Swallow)).ConfigureAwait(false);
await Compat.TaskCompat.WhenAll(loops.Select(Swallow)).ConfigureAwait(false);
}
}
/// <summary>Send a pre-built packet (see <see cref="PacketBuilder"/>) to the RIO.</summary>
public async Task SendAsync(byte[] packet, CancellationToken cancellationToken = default)
{
await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
await Compat.TaskCompat.WaitAsync(_writeLock, cancellationToken).ConfigureAwait(false);
try
{
await _transport.WriteAsync(packet, cancellationToken).ConfigureAwait(false);
@@ -191,7 +191,7 @@ public sealed class RioSerialLink
{
while (!ct.IsCancellationRequested)
{
await Task.Delay(_options.AnalogPollInterval, ct).ConfigureAwait(false);
await Compat.TaskCompat.Delay(_options.AnalogPollInterval, ct).ConfigureAwait(false);
await RequestAnalogAsync(ct).ConfigureAwait(false);
@@ -74,7 +74,7 @@ public sealed class SerialPortTransport : IRioTransport
// still releases the handle.
}
Task close = Task.Run(() =>
Task close = Compat.TaskCompat.Run(() =>
{
try { _port.Dispose(); }
catch { /* best-effort release */ }