///|
/// Id-indexed bounded tail of PTY output. `pty_reader` appends every
/// chunk; a newly registered client receives the retained entries with
/// id strictly greater than its last-seen id as a delta replay.
///
/// This log is a byte-bounded suffix, not a terminal snapshot. Once
/// older chunks have been trimmed, replay from the retained head is
/// only a best-effort catch-up window: a fresh client, or any client
/// whose cursor falls before the retained head, can receive output from
/// the middle of the PTY stream and cannot rely on replay alone to
/// reconstruct full terminal state.
///
/// Ids are dense starting at 1L. `next_id.val = 0L` before the first
/// append.
const ReplayCapacityBytes : Int = 4 * 1024 * 1024
///|
/// Each entry pairs the dense `id` with `created` (ms since epoch at
/// append time) and the raw bytes. `created` is stored so replay frames
/// carry the same timestamp as the live broadcast did — a reconnecting
/// client can tell how stale catch-up output is instead of guessing.
struct ReplayEntry {
id : Int64
created : Int64
bytes : Bytes
}
///|
struct ReplayLog {
entries : Array[ReplayEntry]
next_id : Ref[Int64]
retained_bytes : Ref[Int]
capacity_bytes : Int
}
///|
fn ReplayLog::new(capacity_bytes? : Int = ReplayCapacityBytes) -> ReplayLog {
{
entries: [],
next_id: { val: 0L },
retained_bytes: { val: 0 },
capacity_bytes,
}
}
///|
/// Assign the next dense id and append. Returns the new `ReplayEntry`
/// so the caller can broadcast it without re-reading the tail. If the
/// retained byte budget is exceeded, the oldest chunks are dropped
/// until the log fits again.
fn ReplayLog::append(
self : ReplayLog,
created : Int64,
bytes : Bytes,
) -> ReplayEntry {
self.next_id.val = self.next_id.val + 1L
let id = self.next_id.val
let entry : ReplayEntry = { id, created, bytes }
self.entries.push(entry)
self.retained_bytes.val = self.retained_bytes.val + bytes.length()
while self.retained_bytes.val > self.capacity_bytes &&
self.entries.length() > 1 {
let dropped = self.entries.remove(0)
self.retained_bytes.val = self.retained_bytes.val - dropped.bytes.length()
}
entry
}