feat(focas): coalesce adjacent PMC reads into one pmc_rdpmcrng (#266)

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 16:59:07 -04:00
parent 5b36c717de
commit 8d916f2af7
11 changed files with 790 additions and 11 deletions
@@ -31,6 +31,8 @@ public sealed class FocasDriverOptions
public FocasHandleRecycleOptions HandleRecycle { get; init; } = new();
/// <summary>Gets the fixed tree options.</summary>
public FocasFixedTreeOptions FixedTree { get; init; } = new();
/// <summary>Gets the PMC range-coalescing options (Gitea #266).</summary>
public FocasPmcCoalesceOptions PmcCoalesce { get; init; } = new();
/// <summary>
/// Timeout for the AdminUI Test Connect probe, in seconds. The AdminUI clamps to a
@@ -41,6 +43,32 @@ public sealed class FocasDriverOptions
public int ProbeTimeoutSeconds { get; init; } = 10;
}
/// <summary>
/// Merges adjacent / near PMC reads in one poll batch into fewer <c>pmc_rdpmcrng</c> calls (Gitea #266).
/// On by default: a batch with a single PMC tag per area is unaffected, and a coalesced read the CNC
/// rejects falls back to per-tag reads, so enabling it cannot make a readable tag unreadable.
/// </summary>
public sealed class FocasPmcCoalesceOptions
{
/// <summary>Gets a value indicating whether PMC coalescing is on.</summary>
public bool Enabled { get; init; } = true;
/// <summary>
/// Largest range, in bytes, one coalesced read may cover. Bounds the response size and the
/// blast radius of a rejected range. Default 128.
/// </summary>
[Range(1, 1024)]
public int MaxSpanBytes { get; init; } = 128;
/// <summary>
/// Largest run of bytes nobody asked for that may sit between two merged tags. Reading a few
/// spare bytes is cheaper than a second round-trip, but every spare byte must be a valid PMC
/// address on the CNC. Default 8; 0 merges only strictly adjacent tags.
/// </summary>
[Range(0, 1024)]
public int MaxGapBytes { get; init; } = 8;
}
/// <summary>
/// Fixed-node tree exposed by FOCAS per <c>docs/v2/driver-specs.md §7</c> —
/// <c>Identity/</c>, <c>Axes/{name}/</c>, etc. populated from