Phase 8A (1/2): de-Span the serial layer, swap JSON to Newtonsoft

Prepares RioJoy.Core for the net40 (Windows XP) target, which has no
System.Memory, ValueTask, or System.Text.Json:
- IRioTransport and the whole protocol/framing layer now use byte[] +
  Task (RioPacket.Payload, PacketParser/Builder, RioChecksum, replies,
  AnalogReport, RioHidReport). At 9600 baud Span bought nothing; the
  SerialPortTransport bridge copies disappear entirely.
- ConfigStore/OverlayTemplateStore switch to Newtonsoft 13 with the
  same conventions (indented, PascalCase, string enums, null-skipping);
  verified against the real STJ-written config.json and regions.json
  (load + round-trip). System.Memory and System.Text.Json packages
  dropped.

275 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-11 20:36:19 -05:00
co-authored by Claude Fable 5
parent 02abfa8a14
commit 3b2af7b79a
21 changed files with 104 additions and 93 deletions
+7 -2
View File
@@ -15,8 +15,13 @@ public interface IRioTransport : IDisposable
/// Read available bytes into <paramref name="buffer"/>. Returns the number of
/// bytes read; a return of 0 indicates the transport has closed.
/// </summary>
ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken);
/// <remarks>
/// Plain <c>byte[]</c>/<see cref="Task"/> rather than Memory/ValueTask: the
/// link runs at 9600 baud, and the net40 (Windows XP) flavor has neither
/// System.Memory nor ValueTask (see docs/PLAN.md §Phase 8).
/// </remarks>
Task<int> ReadAsync(byte[] buffer, CancellationToken cancellationToken);
/// <summary>Write all of <paramref name="data"/> to the transport.</summary>
ValueTask WriteAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken);
Task WriteAsync(byte[] data, CancellationToken cancellationToken);
}
+4 -4
View File
@@ -79,7 +79,7 @@ public sealed class RioSerialLink
}
/// <summary>Send a pre-built packet (see <see cref="PacketBuilder"/>) to the RIO.</summary>
public async Task SendAsync(ReadOnlyMemory<byte> packet, CancellationToken cancellationToken = default)
public async Task SendAsync(byte[] packet, CancellationToken cancellationToken = default)
{
await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try
@@ -155,7 +155,7 @@ public sealed class RioSerialLink
switch (packet.Command)
{
case RioCommand.AnalogReply:
if (AnalogReport.TryParse(packet.Payload.Span, out AnalogReport report))
if (AnalogReport.TryParse(packet.Payload, out AnalogReport report))
{
_sinceAnalog.Restart();
AnalogReceived?.Invoke(report);
@@ -163,11 +163,11 @@ public sealed class RioSerialLink
break;
case RioCommand.VersionReply:
VersionReceived?.Invoke(VersionInfo.Parse(packet.Payload.Span));
VersionReceived?.Invoke(VersionInfo.Parse(packet.Payload));
break;
case RioCommand.CheckReply:
CheckReceived?.Invoke(CheckStatus.Parse(packet.Payload.Span));
CheckReceived?.Invoke(CheckStatus.Parse(packet.Payload));
break;
}
}
+4 -14
View File
@@ -47,21 +47,11 @@ public sealed class SerialPortTransport : IRioTransport
public string Description => $"{_port.PortName} @ {BaudRate} 8N1";
// net48's Stream has no Memory-based ReadAsync/WriteAsync overloads, so bridge
// through a pooled array and copy into/out of the caller's Memory<byte>.
public async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken)
{
byte[] tmp = new byte[buffer.Length];
int read = await _stream.ReadAsync(tmp, 0, tmp.Length, cancellationToken).ConfigureAwait(false);
new ReadOnlySpan<byte>(tmp, 0, read).CopyTo(buffer.Span);
return read;
}
public Task<int> ReadAsync(byte[] buffer, CancellationToken cancellationToken) =>
_stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
public async ValueTask WriteAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken)
{
byte[] tmp = data.ToArray();
await _stream.WriteAsync(tmp, 0, tmp.Length, cancellationToken).ConfigureAwait(false);
}
public Task WriteAsync(byte[] data, CancellationToken cancellationToken) =>
_stream.WriteAsync(data, 0, data.Length, cancellationToken);
public void Dispose()
{