feat(editor): F4.6b the command line on a computer's desktop
A new oracle, TerminalWindowRun, types into Java's own command line window: the window writes each line after its prompt, takes a blank line, cls, reset and exit itself, keeps the lines for the arrow keys, and keeps its text, history and working directory over a stop and a start. The room now holds a command line window per computer for everyone (TerminalWindow), kept over stops; the protocol carries its lines and the one way to read them (terminalScreen); the core goes on in the working directory of the run before (continueIn). The desktop opens programs in Java's frame, and the command line works with keyboard and finger: grey on black, the prompt hidden while a command runs, history, chips for the commands, Ctrl+C. The room's failure is now tested in the browser. Fixed: the room handed the engine the wall clock with fractions of a millisecond, and a ping hung after its first timeout. The engine and the room count whole milliseconds, as Java's clock does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -107,8 +107,11 @@ describe('a simulation room', () => {
|
||||
expect(each.from).toBe(each.from === 0 ? 0 : lines.length);
|
||||
lines = [...lines.slice(0, each.from), ...each.lines];
|
||||
}
|
||||
expect(lines).toEqual(engine.terminal(notebook.id)!.log.all);
|
||||
expect(lines.length).toBeGreaterThan(4);
|
||||
// The window: its start screen, the line entered after its prompt, then what the program wrote, as the engine has it.
|
||||
const program = engine.terminal(notebook.id)!.log.all.map(({ timeMilliseconds: _time, ...entry }) => entry);
|
||||
expect(lines.slice(0, 3)).toEqual([{ kind: 'text', text: engine.terminal(notebook.id)!.startScreen }, { kind: 'prompt', prompt: '/> ' }, { kind: 'input', prompt: '/> ', line: 'ping 141.99.5.10', command: true }]);
|
||||
expect(lines.slice(3).filter((line) => (line as { kind: string }).kind !== 'prompt')).toEqual(program);
|
||||
expect(program.length).toBeGreaterThan(4);
|
||||
});
|
||||
|
||||
it('sends the cables that carry frames when they change, and only to who asked', () => {
|
||||
@@ -160,7 +163,7 @@ describe('a simulation room', () => {
|
||||
expect(reset.frames.length).toBeGreaterThan(8);
|
||||
});
|
||||
|
||||
it('empties a window when its program starts anew', () => {
|
||||
it("keeps a command line's window over a stop and a start, as Java does", () => {
|
||||
const { got, send, node, run } = setUp();
|
||||
const notebook = node('Notebook 1.10');
|
||||
send({ kind: 'sub', topics: [`app:${notebook.id}/terminal`] });
|
||||
@@ -168,11 +171,17 @@ describe('a simulation room', () => {
|
||||
send({ kind: 'start' });
|
||||
send({ kind: 'input', node: notebook.id, app: 'terminal', action: 'enter', args: { line: 'ipconfig' }, reqId: 'r1' });
|
||||
run(400);
|
||||
expect(ofKind(got, 'app').at(-1)!.lines.length).toBeGreaterThan(0);
|
||||
const before = ofKind(got, 'app').reduce((count, each) => each.from + each.lines.length, 0);
|
||||
expect(before).toBeGreaterThan(3);
|
||||
const sentBefore = ofKind(got, 'app').length;
|
||||
send({ kind: 'stop' });
|
||||
expect(ofKind(got, 'app').at(-1)).toMatchObject({ from: 0, lines: [] });
|
||||
send({ kind: 'start' });
|
||||
expect(ofKind(got, 'app').at(-1)).toMatchObject({ from: 0 });
|
||||
run(400);
|
||||
// Nothing taken away: the window goes on after its lines.
|
||||
expect(ofKind(got, 'app').slice(sentBefore).every((each) => each.from >= before)).toBe(true);
|
||||
send({ kind: 'input', node: notebook.id, app: 'terminal', action: 'enter', args: { line: 'pwd' }, reqId: 'r2' });
|
||||
run(400);
|
||||
expect(ofKind(got, 'app').at(-1)!.from).toBeGreaterThanOrEqual(before);
|
||||
});
|
||||
|
||||
it('answers an action that cannot be done with its reason', () => {
|
||||
@@ -212,12 +221,14 @@ describe('a simulation room', () => {
|
||||
expect(roundTrip(90)).toBeGreaterThan(roundTrip(100));
|
||||
});
|
||||
|
||||
it('catches up at most two seconds after the clock jumped', () => {
|
||||
const { got, send, jump } = setUp();
|
||||
it('catches up at most two seconds after the clock jumped, and goes on from there', () => {
|
||||
const { got, send, jump, run } = setUp();
|
||||
send({ kind: 'start' });
|
||||
jump(60_000);
|
||||
// The missed minute is not made up over the next ticks either.
|
||||
run(TICK_MILLISECONDS);
|
||||
send({ kind: 'stop' });
|
||||
expect(ofKind(got, 'state').at(-1)!.t).toBe(MAXIMUM_CATCH_UP_MILLISECONDS);
|
||||
expect(ofKind(got, 'state').at(-1)!.t).toBe(MAXIMUM_CATCH_UP_MILLISECONDS + TICK_MILLISECONDS);
|
||||
});
|
||||
|
||||
it('starts with the document as it is then', () => {
|
||||
@@ -329,5 +340,34 @@ describe('a simulation room', () => {
|
||||
send({ kind: 'sub', topics: [`node:${server.id}`] });
|
||||
expect(ofKind(got.slice(before), 'node')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("hands the engine whole milliseconds of a wall clock with fractions, losing none, and a ping goes through", () => {
|
||||
// performance.now() has fractions; the core counts whole milliseconds as Java does (a fraction once made the ping hang).
|
||||
const doc = exampleDoc();
|
||||
let now = 0.718;
|
||||
const room = new SimulationRoom(doc, { now: () => now, seed: () => SEED });
|
||||
const got: HostMessage[] = [];
|
||||
const client = room.connect((message) => got.push(message));
|
||||
const notebook = readProject(doc).graph.nodes.find((each) => each.displayName === 'Notebook 1.10')!;
|
||||
room.receive(client, { v: 1, kind: 'sub', topics: [`app:${notebook.id}/terminal`] });
|
||||
room.receive(client, { v: 1, kind: 'start' });
|
||||
for (let tick = 0; tick < 15; tick++) {
|
||||
now += 200.4;
|
||||
room.tick();
|
||||
}
|
||||
room.receive(client, { v: 1, kind: 'input', node: notebook.id, app: 'terminal', action: 'enter', args: { line: 'ping 141.99.5.10' }, reqId: 'r' });
|
||||
for (let tick = 0; tick < 60; tick++) {
|
||||
now += 200.4;
|
||||
room.tick();
|
||||
}
|
||||
room.receive(client, { v: 1, kind: 'speed', value: 90 });
|
||||
const t = ofKind(got, 'state').at(-1)!.t;
|
||||
expect(Number.isInteger(t)).toBe(true);
|
||||
// Less than a millisecond behind the wall clock since the start, never ahead of it.
|
||||
expect(t).toBeLessThanOrEqual(now - 0.718);
|
||||
expect(t).toBeGreaterThan(now - 0.718 - 1);
|
||||
const text = ofKind(got, 'app').flatMap((each) => each.lines as { kind: string; text?: string }[]).map((line) => line.text ?? '').join('');
|
||||
expect(text).toContain('4 Paket(e) gesendet, 4 Paket(e) empfangen');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user