feat(focas): coalesce adjacent PMC reads into one pmc_rdpmcrng (#266)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -56,6 +56,51 @@ internal class FakeFocasClient : IFocasClient
|
||||
return Task.FromResult((value, status));
|
||||
}
|
||||
|
||||
/// <summary>PMC byte memory the range read serves, keyed by (upper-case letter, byte address); absent = 0.</summary>
|
||||
public Dictionary<(string Letter, int Address), byte> PmcMemory { get; } = new();
|
||||
/// <summary>Gets the log of range reads issued (letter, slot type, start, end).</summary>
|
||||
public List<(string Letter, FocasDataType SlotType, int Start, int End)> PmcRangeReads { get; } = new();
|
||||
/// <summary>Ranges (letter, start, end) the fake CNC rejects with <see cref="PmcRangeRejectStatus"/>.</summary>
|
||||
public List<Func<string, int, int, bool>> PmcRangeRejectors { get; } = new();
|
||||
/// <summary>The status a rejected range returns.</summary>
|
||||
public uint PmcRangeRejectStatus { get; set; } = FocasStatusMapper.BadOutOfRange;
|
||||
/// <summary>When true every range read throws (transport failure).</summary>
|
||||
public bool ThrowOnPmcRange { get; set; }
|
||||
|
||||
/// <summary>Reads a PMC range as raw big-endian slots from <see cref="PmcMemory"/>.</summary>
|
||||
/// <param name="letter">PMC letter.</param>
|
||||
/// <param name="slotType">Slot type.</param>
|
||||
/// <param name="start">First byte.</param>
|
||||
/// <param name="end">Last byte, inclusive.</param>
|
||||
/// <param name="ct">The cancellation token.</param>
|
||||
public virtual Task<(IReadOnlyList<long>? slots, uint status)> ReadPmcSlotsAsync(
|
||||
string letter, FocasDataType slotType, int start, int end, CancellationToken ct)
|
||||
{
|
||||
PmcRangeReads.Add((letter, slotType, start, end));
|
||||
if (ThrowOnPmcRange) throw new InvalidOperationException("transport down");
|
||||
if (PmcRangeRejectors.Any(r => r(letter, start, end)))
|
||||
return Task.FromResult<(IReadOnlyList<long>?, uint)>((null, PmcRangeRejectStatus));
|
||||
|
||||
var width = slotType switch
|
||||
{
|
||||
FocasDataType.Int16 => 2,
|
||||
FocasDataType.Int32 or FocasDataType.Float32 => 4,
|
||||
FocasDataType.Float64 => 8,
|
||||
_ => 1,
|
||||
};
|
||||
var slots = new List<long>();
|
||||
for (var o = start; o + width - 1 <= end; o += width)
|
||||
{
|
||||
long v = 0;
|
||||
for (var k = 0; k < width; k++)
|
||||
v = (v << 8) | (PmcMemory.TryGetValue((letter, o + k), out var b) ? b : (byte)0);
|
||||
// Sign-extend like the wire parser (Int16/Int32/Int64 big-endian); width 1 stays 0..255.
|
||||
v = width switch { 2 => (short)v, 4 => (int)v, _ => v };
|
||||
slots.Add(v);
|
||||
}
|
||||
return Task.FromResult<(IReadOnlyList<long>?, uint)>((slots, FocasStatusMapper.Good));
|
||||
}
|
||||
|
||||
/// <summary>Writes a value to a FOCAS address asynchronously.</summary>
|
||||
/// <param name="address">The FOCAS address to write to.</param>
|
||||
/// <param name="type">The data type of the value.</param>
|
||||
|
||||
Reference in New Issue
Block a user