feat(editor): the simulation's properties in a sheet of their own, with a restart for everyone
Who started the simulation, "Pakete verwerfen" and the dropped packets move from the toolbar into a properties sheet beside the surface. New in it: restart the simulation, after asking. The room learns a restart message: stopped as by stop, every capture, dropped frame and program window emptied, started anew and named after who restarted; refused for readers and frozen projects, logged, and waiting for asked blobs like a start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -393,6 +393,9 @@ export class SimulationRoom {
|
||||
case 'stop':
|
||||
this.stop();
|
||||
return;
|
||||
case 'restart':
|
||||
this.restart(input.language, client.name);
|
||||
return;
|
||||
case 'drop':
|
||||
if (this.mode !== 'simulation') {
|
||||
client.send(message({ kind: 'error', code: 'notRunning' }));
|
||||
@@ -475,6 +478,28 @@ export class SimulationRoom {
|
||||
this.flush([...this.clients.values()], false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the simulation anew, as though nothing had run before: stopped as
|
||||
* by stop (what the devices wrote stays in the document), every card's
|
||||
* capture, the dropped frames and every program window emptied — Java's
|
||||
* outlive a stop — and started. Each watcher gets its lists and windows
|
||||
* anew, from their beginning (the dropped frames start does anew anyway; a
|
||||
* view is compared, so a changed one goes out by itself).
|
||||
*/
|
||||
restart(language?: PageLanguage, startedBy?: string): void {
|
||||
this.stop();
|
||||
this.engine.clearCapture();
|
||||
// The cables light up again with the first frame, not once a card has more than before.
|
||||
this.framesSeen.clear();
|
||||
this.terminalWindows.clear();
|
||||
this.browserWindows.clear();
|
||||
for (const client of this.clients.values()) {
|
||||
client.traceSent.clear();
|
||||
client.appSent.clear();
|
||||
}
|
||||
this.start(language, startedBy);
|
||||
}
|
||||
|
||||
/** Lets the simulation run to the wall clock's time, then sends what came of it. */
|
||||
tick(): void {
|
||||
if (this.mode !== 'simulation') return;
|
||||
|
||||
@@ -472,6 +472,14 @@ describe("a simulation room's browser and web server windows", () => {
|
||||
send({ kind: 'input', node: notebook, app: 'webBrowser', action: 'open', args: { address: gateway }, reqId: 'r' });
|
||||
run(20_000);
|
||||
expect(page().page.html).toContain('Router Administration');
|
||||
// Started anew: in the language of the restart, German without one.
|
||||
for (const [restart, title] of [[{ kind: 'restart' }, 'Administration des Vermittlungsrechners'], [{ kind: 'restart', language: 'en' }, 'Router Administration']] as const) {
|
||||
send(restart);
|
||||
run(TICK_MILLISECONDS);
|
||||
send({ kind: 'input', node: notebook, app: 'webBrowser', action: 'open', args: { address: gateway }, reqId: 'r' });
|
||||
run(20_000);
|
||||
expect(page().page.html).toContain(title);
|
||||
}
|
||||
});
|
||||
|
||||
it('names a wrong action in the windows', () => {
|
||||
@@ -595,3 +603,78 @@ describe("a simulation room's e-mail windows", () => {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('a simulation room started anew', () => {
|
||||
/** A window sent from its beginning, and a view. */
|
||||
const anew = (got: readonly HostMessage[], node: Id, app: string) => ofKind(got, 'app').filter((each) => each.node === node && each.app === app && each.from === 0);
|
||||
const viewsOf = (got: readonly HostMessage[], node: Id, app: string) => ofKind(got, 'view').filter((each) => each.node === node && each.app === app);
|
||||
|
||||
it('empties every window and list for every client, names who did it, keeps what the devices wrote, and numbers anew', () => {
|
||||
const { room, got, send, node, run } = setUp();
|
||||
const benGot: HostMessage[] = [];
|
||||
const ben = room.connect((message) => benGot.push(message), 'Ben');
|
||||
const notebook = node('Notebook 1.10');
|
||||
const server = node('Webserver 5.10').id;
|
||||
const port = notebook.ports[0]!.id;
|
||||
const topics = ['activity', `trace:${port}`, 'dropped', `app:${notebook.id}/terminal`, `app:${notebook.id}/webBrowser`, `app:${server}/webServer`];
|
||||
send({ kind: 'sub', topics });
|
||||
room.receive(ben, { v: 1, kind: 'sub', topics });
|
||||
send({ kind: 'start' });
|
||||
run(TICK_MILLISECONDS);
|
||||
const fresh = { terminal: anew(got, notebook.id, 'terminal').at(-1)!, browser: viewsOf(got, notebook.id, 'webBrowser').at(-1)! };
|
||||
const enter = (line: string) => send({ kind: 'input', node: notebook.id, app: 'terminal', action: 'enter', args: { line }, reqId: line });
|
||||
enter('touch bleibt.txt');
|
||||
enter('ping 141.99.5.10');
|
||||
send({ kind: 'input', node: notebook.id, app: 'webBrowser', action: 'open', args: { address: '141.99.5.10' }, reqId: 'open' });
|
||||
run(20_000);
|
||||
send({ kind: 'drop', on: true });
|
||||
enter('ping 141.99.5.10');
|
||||
run(5_000);
|
||||
send({ kind: 'drop', on: false });
|
||||
expect(ofKind(got, 'dropped').flatMap((each) => each.units).length).toBeGreaterThan(0);
|
||||
expect(ofKind(got, 'app').filter((each) => each.app === 'webServer').flatMap((each) => each.lines).length).toBeGreaterThan(0);
|
||||
|
||||
const before = { mia: got.length, ben: benGot.length };
|
||||
room.receive(ben, { v: 1, kind: 'restart' });
|
||||
for (const [messages, from] of [[got, before.mia], [benGot, before.ben]] as const) {
|
||||
const after = messages.slice(from);
|
||||
expect(ofKind(after, 'state').map((each) => [each.mode, each.startedBy])).toEqual([
|
||||
['design', undefined],
|
||||
['simulation', 'Ben'],
|
||||
]);
|
||||
expect(ofKind(after, 'trace').at(-1)).toMatchObject({ portId: port, reset: true, frames: [] });
|
||||
expect(ofKind(after, 'dropped').at(-1)).toMatchObject({ reset: true, units: [] });
|
||||
// The command line as at the first start, the browser on its start page, the server's log empty.
|
||||
expect(anew(after, notebook.id, 'terminal').at(-1)!.lines).toEqual(fresh.terminal.lines);
|
||||
expect(viewsOf(after, notebook.id, 'webBrowser').at(-1)!.view).toEqual(fresh.browser.view);
|
||||
expect(anew(after, server, 'webServer').at(-1)!.lines).toEqual([]);
|
||||
}
|
||||
// What the devices wrote is still there; a ping is numbered from 1, and the notebook's cable lights up with its first frame.
|
||||
const restarted = got.length;
|
||||
const cable = readProject(exampleDoc()).graph.links.find((link) => link.sourcePortId === port || link.targetPortId === port)!.id;
|
||||
enter('ls');
|
||||
enter('ping 141.99.5.10');
|
||||
for (let waited = 0; !ofKind(got.slice(restarted), 'trace').some((each) => each.frames.length > 0) && waited < 5_000; waited += TICK_MILLISECONDS) run(TICK_MILLISECONDS);
|
||||
expect(ofKind(got.slice(restarted), 'activity').at(-1)!.links).toContain(cable);
|
||||
run(20_000);
|
||||
expect(ofKind(got.slice(restarted), 'app').flatMap((each) => each.lines as { text?: string }[]).some((line) => (line.text ?? '').includes('bleibt.txt'))).toBe(true);
|
||||
const frames = ofKind(got.slice(restarted), 'trace').flatMap((each) => each.frames);
|
||||
expect(frames.length).toBeGreaterThan(8);
|
||||
expect(frames.map((frame) => frame.number)).toEqual(frames.map((_, index) => index + 1));
|
||||
});
|
||||
|
||||
it('starts from the design emptied, where a start keeps the last run', () => {
|
||||
const { got, send, node, run } = setUp();
|
||||
const notebook = node('Notebook 1.10');
|
||||
const port = notebook.ports[0]!.id;
|
||||
send({ kind: 'start' });
|
||||
send({ kind: 'input', node: notebook.id, app: 'terminal', action: 'enter', args: { line: 'ping 141.99.5.10' }, reqId: 'r1' });
|
||||
run(5_000);
|
||||
send({ kind: 'stop' });
|
||||
send({ kind: 'restart' });
|
||||
expect(ofKind(got, 'state').at(-1)!.mode).toBe('simulation');
|
||||
send({ kind: 'sub', topics: [`trace:${port}`, `app:${notebook.id}/terminal`] });
|
||||
expect(ofKind(got, 'trace').at(-1)).toMatchObject({ reset: true, frames: [] });
|
||||
expect(ofKind(got, 'app').at(-1)!.lines.some((line) => /ping/.test(JSON.stringify(line)))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user