feat(editor): F4.6a simulation mode and a computer's desktop
Java's mode buttons start and stop the room, now in a worker of the editor's page (LocalSimHost), with Java's speed slider. While the simulation runs the design is locked as in Java: no palette, no sheet, nothing moves, no undo. Cables that carry a frame light up in Java's green, and a tap on a computer opens its desktop: Java's title, the software installation first, then the installed programs in the order of Java's list for the language, a taskbar with the network icon and the network as the simulation runs it. The room sends that over a new topic, node:<id>. Desktops stay over a return to the design and show again at the next start. The programs' windows follow from F4.6b. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
// keep rising (plan §6.2).
|
||||
|
||||
import * as Y from 'yjs';
|
||||
import { DEFAULT_SPEED_PERCENT, NetworkRuntimeEngine, captureRows, type CapturedFrame, type CaptureRow, type Id, type LogLine } from '@mdg-filius/core';
|
||||
import { DEFAULT_SPEED_PERCENT, NetworkRuntimeEngine, captureRows, interfaceKey, stableGeneratedMacAddress, type CapturedFrame, type CaptureRow, type Id, type LogLine } from '@mdg-filius/core';
|
||||
import { docSnapshot } from '@mdg-filius/doc';
|
||||
import {
|
||||
parseClientMessage,
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
type ClientMessage,
|
||||
type ErrorCode,
|
||||
type HostMessage,
|
||||
type NodeSettings,
|
||||
type SimMode,
|
||||
type Topic,
|
||||
type TraceFrame,
|
||||
@@ -47,6 +48,8 @@ interface Client {
|
||||
readonly traceSent: Map<Id, number>;
|
||||
/** Per program window, how many of its program's lines it has; none while the program does not run (a stop empties it). */
|
||||
readonly appSent: Map<string, number>;
|
||||
/** Per computer, the settings last sent (as text). */
|
||||
readonly nodeSent: Map<Id, string>;
|
||||
}
|
||||
|
||||
/** A program with a window: its lines. */
|
||||
@@ -79,7 +82,7 @@ export class SimulationRoom {
|
||||
/** A new client; it gets the room's state at once. */
|
||||
connect(send: (message: HostMessage) => void): number {
|
||||
const id = this.nextClient++;
|
||||
this.clients.set(id, { send, topics: new Set(), traceSent: new Map(), appSent: new Map() });
|
||||
this.clients.set(id, { send, topics: new Set(), traceSent: new Map(), appSent: new Map(), nodeSent: new Map() });
|
||||
send(this.state());
|
||||
return id;
|
||||
}
|
||||
@@ -117,6 +120,7 @@ export class SimulationRoom {
|
||||
client.topics.add(topic);
|
||||
if (topic.startsWith('trace:')) client.traceSent.delete(topic.slice('trace:'.length));
|
||||
if (topic.startsWith('app:')) client.appSent.delete(topic);
|
||||
if (topic.startsWith('node:')) client.nodeSent.delete(topic.slice('node:'.length));
|
||||
}
|
||||
this.flush([client], true);
|
||||
return;
|
||||
@@ -260,6 +264,14 @@ export class SimulationRoom {
|
||||
for (const topic of client.topics) {
|
||||
if (topic === 'activity') {
|
||||
if (subscribing) client.send(message({ kind: 'activity', links: this.lastActivity }));
|
||||
} else if (topic.startsWith('node:')) {
|
||||
const node = topic.slice('node:'.length);
|
||||
const settings = this.nodeSettings(node);
|
||||
if (settings === undefined) continue;
|
||||
const text = JSON.stringify(settings);
|
||||
if (client.nodeSent.get(node) === text) continue;
|
||||
client.nodeSent.set(node, text);
|
||||
client.send(message({ kind: 'node', node, settings }));
|
||||
} else if (topic.startsWith('trace:')) {
|
||||
const portId = topic.slice('trace:'.length);
|
||||
const reset = !client.traceSent.has(portId);
|
||||
@@ -288,6 +300,26 @@ export class SimulationRoom {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A device's first card as the engine runs it (a computer's settings, which
|
||||
* DHCP may change); undefined for a device the simulation never had.
|
||||
*/
|
||||
private nodeSettings(id: Id): NodeSettings | undefined {
|
||||
const snapshot = this.engine.state.topologySnapshot;
|
||||
const node = snapshot.nodes.find((each) => each.id === id);
|
||||
if (node === undefined) return undefined;
|
||||
const port = node.ports[0];
|
||||
const device = snapshot.deviceConfigurations.get(id);
|
||||
const card = port === undefined ? undefined : snapshot.interfaceConfigurations.get(interfaceKey(id, port.id));
|
||||
return {
|
||||
ipAddress: device?.ipAddress ?? card?.ipAddress ?? '',
|
||||
subnetMask: device?.subnetMask ?? card?.subnetMask ?? '',
|
||||
defaultGateway: device?.defaultGateway ?? '',
|
||||
dnsServer: device?.dnsServer ?? '',
|
||||
macAddress: port === undefined ? '' : (port.macAddress ?? stableGeneratedMacAddress(port.id)),
|
||||
};
|
||||
}
|
||||
|
||||
/** The cables whose cards got frames since the last tick; sent when that changes. */
|
||||
private updateActivity(): void {
|
||||
const links = new Set<string>();
|
||||
|
||||
Reference in New Issue
Block a user