playground streaming: split chunks on \n in the worker, send eol flag
After (4) rounds of newline-debugging where node tests said chunks
contained \n but fox's Firefox tab still rendered horizontal text
for the c tier (python+asm rendered vertical with the same code
path), giving up on trying to guess where the \n was being eaten
and instead getting rid of \n bytes on the wire entirely.
Worker now splits each onChunk slice on \n at the source and posts
one message per line — { kind:'chunk', tier, chunk:'tick 0', eol:true }
— so the line break is carried as a boolean flag rather than a byte.
Main thread re-attaches the '\n' before handing to liveBlock.append,
which still walks bytes for charCodeAt 10 and stacks per line.
Effectively: the loader's currentOnChunk(line + '\n') feeds the
worker which immediately splits back on the \n, both halves still
arrive on the main thread, the main thread reconstitutes them with
a fresh \n that we now know our DOM split honors (proved by the
python tier which uses the same liveBlock.append). C-tier-specific
\n loss between Module.print and postMessage drops out of the
picture.
This commit is contained in:
parent
bc13b056ac
commit
89d4877ee9
4 changed files with 80 additions and 4 deletions
|
|
@ -223,8 +223,14 @@ function runOnTierInWorker(tier, src, onLoading) {
|
|||
// First chunk spawns the in-progress block; subsequent
|
||||
// chunks append to it. User sees displays land
|
||||
// immediately, not just at the end of the run.
|
||||
// Worker splits chunks on \n at the source and sends
|
||||
// each line with an eol flag — re-attach the newline
|
||||
// here so liveBlock.append still walks bytes looking
|
||||
// for charCodeAt 10 and stacks per line. Avoids any
|
||||
// unknown-quantity newline loss between Module.print
|
||||
// and the main thread.
|
||||
if (!liveBlock) liveBlock = startLiveBlock(tier);
|
||||
liveBlock.append(e.data.chunk);
|
||||
liveBlock.append(e.data.chunk + (e.data.eol ? "\n" : ""));
|
||||
} else if (e.data.kind === "done") {
|
||||
w.removeEventListener("message", handler);
|
||||
workerState.pending[tier] = null;
|
||||
|
|
|
|||
|
|
@ -46,8 +46,40 @@ self.onmessage = async (e) => {
|
|||
// blocks the worker, but displays BEFORE/AFTER the bend
|
||||
// round-trip surface immediately instead of waiting for
|
||||
// the whole eval to finish. Long demos feel alive.
|
||||
//
|
||||
// Defensive newline normalization: the C-tier loader
|
||||
// adds the trailing \n that Emscripten's Module.print
|
||||
// strips, but we kept seeing horizontal output in fox's
|
||||
// Firefox tab as if the \n was lost somewhere on the
|
||||
// wire. To rule out anything between here and the main
|
||||
// thread, split each chunk on \n at the source and post
|
||||
// one message per line — newline preserved as a flag
|
||||
// rather than a byte. The main-thread receiver knows to
|
||||
// re-add the line break.
|
||||
(chunk) => {
|
||||
self.postMessage({ kind: "chunk", runId, tier, chunk });
|
||||
if (!chunk) return;
|
||||
let start = 0;
|
||||
for (let i = 0; i < chunk.length; i++) {
|
||||
if (chunk.charCodeAt(i) === 10) {
|
||||
self.postMessage({
|
||||
kind: "chunk",
|
||||
runId,
|
||||
tier,
|
||||
chunk: chunk.slice(start, i),
|
||||
eol: true,
|
||||
});
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
if (start < chunk.length) {
|
||||
self.postMessage({
|
||||
kind: "chunk",
|
||||
runId,
|
||||
tier,
|
||||
chunk: chunk.slice(start),
|
||||
eol: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
self.postMessage({ kind: "done", runId, output });
|
||||
|
|
|
|||
|
|
@ -223,8 +223,14 @@ function runOnTierInWorker(tier, src, onLoading) {
|
|||
// First chunk spawns the in-progress block; subsequent
|
||||
// chunks append to it. User sees displays land
|
||||
// immediately, not just at the end of the run.
|
||||
// Worker splits chunks on \n at the source and sends
|
||||
// each line with an eol flag — re-attach the newline
|
||||
// here so liveBlock.append still walks bytes looking
|
||||
// for charCodeAt 10 and stacks per line. Avoids any
|
||||
// unknown-quantity newline loss between Module.print
|
||||
// and the main thread.
|
||||
if (!liveBlock) liveBlock = startLiveBlock(tier);
|
||||
liveBlock.append(e.data.chunk);
|
||||
liveBlock.append(e.data.chunk + (e.data.eol ? "\n" : ""));
|
||||
} else if (e.data.kind === "done") {
|
||||
w.removeEventListener("message", handler);
|
||||
workerState.pending[tier] = null;
|
||||
|
|
|
|||
|
|
@ -46,8 +46,40 @@ self.onmessage = async (e) => {
|
|||
// blocks the worker, but displays BEFORE/AFTER the bend
|
||||
// round-trip surface immediately instead of waiting for
|
||||
// the whole eval to finish. Long demos feel alive.
|
||||
//
|
||||
// Defensive newline normalization: the C-tier loader
|
||||
// adds the trailing \n that Emscripten's Module.print
|
||||
// strips, but we kept seeing horizontal output in fox's
|
||||
// Firefox tab as if the \n was lost somewhere on the
|
||||
// wire. To rule out anything between here and the main
|
||||
// thread, split each chunk on \n at the source and post
|
||||
// one message per line — newline preserved as a flag
|
||||
// rather than a byte. The main-thread receiver knows to
|
||||
// re-add the line break.
|
||||
(chunk) => {
|
||||
self.postMessage({ kind: "chunk", runId, tier, chunk });
|
||||
if (!chunk) return;
|
||||
let start = 0;
|
||||
for (let i = 0; i < chunk.length; i++) {
|
||||
if (chunk.charCodeAt(i) === 10) {
|
||||
self.postMessage({
|
||||
kind: "chunk",
|
||||
runId,
|
||||
tier,
|
||||
chunk: chunk.slice(start, i),
|
||||
eol: true,
|
||||
});
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
if (start < chunk.length) {
|
||||
self.postMessage({
|
||||
kind: "chunk",
|
||||
runId,
|
||||
tier,
|
||||
chunk: chunk.slice(start),
|
||||
eol: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
self.postMessage({ kind: "done", runId, output });
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue