playground streaming: single text node — fixes 5 functional test regressions
Apply the REPL's per-line-div → single Text node refactor to the playground too. Same perf win on high-volume streams plus a bonus: the 5 functional tests that started failing after the per-line-div era (mandelbrot/fib-ack/sieve/self-interp on C, fib-ack on asm) all pass again. They were reading pre.textContent which silently joined sibling divs without the \n separators they expected; a single text node with embedded \n round-trips the assertion exactly. 11/11 functional tests green.
This commit is contained in:
parent
316e6db4c1
commit
049a1e1935
2 changed files with 66 additions and 82 deletions
|
|
@ -277,60 +277,52 @@ function startLiveBlock(tierName) {
|
|||
block.appendChild(pre);
|
||||
outputEl.appendChild(block);
|
||||
|
||||
// Each chunk gets split on \n: every completed line becomes its
|
||||
// own block-level <div>, the trailing fragment without a newline
|
||||
// accumulates in a pending div that's updated in place until the
|
||||
// next \n closes it. display:block + white-space:pre set INLINE
|
||||
// so a stale cached stylesheet can't suppress the fix — we kept
|
||||
// chasing what turned out to be CSS not reloading.
|
||||
const makeLine = () => {
|
||||
const d = document.createElement("div");
|
||||
d.style.display = "block";
|
||||
d.style.whiteSpace = "pre";
|
||||
return d;
|
||||
};
|
||||
let pendingLine = makeLine();
|
||||
pre.appendChild(pendingLine);
|
||||
let pendingText = "";
|
||||
// Single Text node accumulator — appendData is ~1000x faster
|
||||
// than appendChild(div) per line on high-volume streams. The
|
||||
// <pre> wrapper already preserves \n as a real line break, so
|
||||
// we don't need per-line elements. RAF coalesces chunk pile-ups
|
||||
// so a tight (display) loop can't drown the main thread.
|
||||
pre.style.whiteSpace = "pre";
|
||||
const textNode = document.createTextNode("");
|
||||
pre.appendChild(textNode);
|
||||
let buffered = "";
|
||||
let rafPending = false;
|
||||
function flushBuffered() {
|
||||
rafPending = false;
|
||||
if (!buffered) return;
|
||||
textNode.appendData(buffered);
|
||||
buffered = "";
|
||||
outputEl.scrollTop = outputEl.scrollHeight;
|
||||
}
|
||||
function scheduleFlush() {
|
||||
if (rafPending) return;
|
||||
rafPending = true;
|
||||
requestAnimationFrame(flushBuffered);
|
||||
}
|
||||
|
||||
return {
|
||||
appendText(t) {
|
||||
// Pure text chunk — never contains a newline. Append to
|
||||
// the in-progress line.
|
||||
if (!t) return;
|
||||
pendingText += t;
|
||||
pendingLine.textContent = pendingText;
|
||||
outputEl.scrollTop = outputEl.scrollHeight;
|
||||
buffered += t;
|
||||
scheduleFlush();
|
||||
},
|
||||
appendNewline() {
|
||||
// End-of-line event — close the current line (with a
|
||||
// single-space placeholder when empty so the row is
|
||||
// visible) and spawn a fresh pending line below.
|
||||
pendingLine.textContent = pendingText || " ";
|
||||
pendingText = "";
|
||||
pendingLine = makeLine();
|
||||
pre.appendChild(pendingLine);
|
||||
outputEl.scrollTop = outputEl.scrollHeight;
|
||||
buffered += "\n";
|
||||
scheduleFlush();
|
||||
},
|
||||
finalize(elapsedMs, kind, fullOutput) {
|
||||
if (kind === "cancelled") t.textContent = ` (cancelled @ ${elapsedMs.toFixed(0)} ms)`;
|
||||
else t.textContent = ` (${elapsedMs.toFixed(0)} ms)`;
|
||||
if (kind === "error") pre.className = "err";
|
||||
// Reconcile against the full output if the streamed
|
||||
// per-line divs don't add up — e.g. asm-tier fallback
|
||||
// where streaming isn't wired yet. Otherwise the joined
|
||||
// per-line text should match the eval's full output.
|
||||
// Drain any RAF-buffered chunks then reconcile against the
|
||||
// full eval output — covers the asm-tier fallback where
|
||||
// streaming isn't wired and the live text node stays empty
|
||||
// until finalize lands the canonical value.
|
||||
if (rafPending) flushBuffered();
|
||||
if (fullOutput) {
|
||||
const got = Array.from(pre.children).map((d) =>
|
||||
d.textContent === " " ? "" : d.textContent).join("\n");
|
||||
const got = textNode.nodeValue.replace(/\n$/, "");
|
||||
if (got !== fullOutput.replace(/\n$/, "")) {
|
||||
pre.textContent = "";
|
||||
const lines = fullOutput.split("\n");
|
||||
for (const ln of lines) {
|
||||
const d = makeLine();
|
||||
d.textContent = ln || " ";
|
||||
pre.appendChild(d);
|
||||
}
|
||||
textNode.nodeValue = fullOutput;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue