Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,11 @@ jobs:
# mock used about 2.2 cores of its 3 at 10 frames per second through the suite.
# Before the render loop lost its per-pixel allocations it needed more than 3,
# dropped frames from the first test, and later tests timed out waiting for
# every tile at one common t. Smaller screens leave tiles too few pixels for the
# seam checks' colour fit.
# every tile at one common t. A stream switch serves the aggregates and the
# direct tiles at once (18 streams, about 1.75 times the pixels) for a second or
# so; the mock then skips whole frames, the same ones for every stream, so the
# page still finds a t every tile holds. Smaller screens leave tiles too few
# pixels for the seam checks' colour fit.
FLEET_E2E_VIEW: "960x600"
FLEET_MOCK_FPS: "10"
FLEET_MOCK_THREADS: "3"
Expand Down
8 changes: 8 additions & 0 deletions fleet/web/mock/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,14 @@ export class MockRenderers {
for (const s of this.subs) if (s.tile === id) this.resize(s, s.reqH);
}
this.stats.renderBacklog = this.pool.backlog;
// Behind its clock (the last frame's draws have not all started), the renderer skips
// this frame for every stream at once. Skipping per stream would leave each tile a
// different subset of t, and a page draws all of them at one t: with 15 tiles each
// missing 4 frames in 10, no t is held by all 15, and the page never shows them in sync.
if (this.pool.backlog > 0) {
for (const s of this.subs) if (s.encoder) this.stats.framesSkipped++;
return;
}
const groups = new Map<string, Sub[]>();
for (const s of this.subs) {
if (!s.encoder) continue;
Expand Down
38 changes: 38 additions & 0 deletions fleet/web/mock/test/mock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -744,3 +744,41 @@ describe("the mock speaks the coordinator's snapshot shapes", () => {
expect([...mock].filter((p) => p !== "" && !allowed(p)).sort()).toEqual([]);
});
});

describe("mock renderer, more streams than it can draw", () => {
it("drops whole frames: every stream gets the same frame numbers, so the page can draw them at one t", async () => {
// One render thread asked for eight 360-high tiles at 30 fps: it draws a few frames a
// second. A renderer behind on its clock skips frames for every tile at once; skipping
// per stream gives each tile a different subset of t, and the page, which draws every
// tile at one common t, then has no t that all of them hold.
const scene = coord.patternScene();
const r = new MockRenderers({ port: 0, publicHost: "localhost", fps: 30, threads: 1, maxHeight: 360, epochUnixMs: scene.epoch_unix_ms, scene: () => scene });
await r.listen();
const ids = Array.from({ length: 8 }, (_, i) => `t${i}`);
ids.forEach((id, i) => r.setTile(id, { x: (i % 4) / 4, y: Math.floor(i / 4) / 2, w: 0.25, h: 0.5 }));
const got = new Map<string, number[]>(ids.map((id) => [id, []]));
const sockets = ids.map((id) => {
const ws = new WebSocket(`${r.frameUrl(id)}?h=360`);
ws.on("message", (d, bin) => {
if (!bin) return;
const p = parseFrame(new Uint8Array(d as Buffer));
if (p.ok) got.get(id)!.push(p.frame.frame);
});
return ws;
});
await new Promise((res) => setTimeout(res, 4000));
for (const ws of sockets) ws.close();
await r.close();
// Frames after the first second, once every encoder has started.
const first = Math.max(...ids.map((id) => got.get(id)![0] ?? Infinity));
const sets = ids.map((id) => new Set(got.get(id)!.filter((n) => n >= first + 30)));
const all = new Set<number>();
for (const s of sets) for (const n of s) all.add(n);
const everywhere = [...all].filter((n) => sets.every((s) => s.has(n)));
// It is behind (far fewer than 30 frames a second) ...
expect(all.size).toBeGreaterThan(0);
expect(all.size).toBeLessThan(2.5 * 30);
// ... and still every frame number it sent, it sent on every stream (the last one may be in flight).
expect(everywhere.length, `frames on all 8 streams: ${everywhere.length} of ${all.size}`).toBeGreaterThanOrEqual(all.size - 1);
}, 20_000);
});
Loading