feat: F4.6f2 the text editor on the document's Y.Text, files both ways while the simulation runs
Oracle TextEditorWindowRun uses Java's text editor window and its file dialog as a person does. The open file's text is its Y.Text in the document (plan 7.3): typed is in the file at once, for everyone and for the running engine. Core openText/saveTextAs without Java's mistakes (lost saves after save-as onto a file or a removed file, files named 'ordner/d.txt' or ''); doc files.ts (changeFiles, fileText, nodeFiles, readFilesInto); the room takes a person's files into the engine at once and writes the engine's files back at the tick they changed; the editor's window, Java's dialog, the text area following others' changes with the caret in place, its own undo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,13 @@
|
||||
// as `origin: 'sim'`: at most once a second while it runs, after each action
|
||||
// in a window and when it stops (writeRunState). So the next start, and every
|
||||
// client, sees it — as Java's project saved after a run holds it.
|
||||
//
|
||||
// Files go both ways while it runs (plan §7.3: the text editor writes the
|
||||
// document's Y.Text): what a person writes into the document goes into the
|
||||
// engine's files at once (readFilesInto), and what the engine changed in its
|
||||
// files goes into the document at the tick it changed, not a second later —
|
||||
// so the document holds every change of the engine before a person's next
|
||||
// one comes in, and a text two write at once keeps both.
|
||||
|
||||
import * as Y from 'yjs';
|
||||
import {
|
||||
@@ -50,7 +57,7 @@ import {
|
||||
type PageLanguage,
|
||||
type TopologySnapshot,
|
||||
} from '@mdg-filius/core';
|
||||
import { docSnapshot, emailServerAfterRun, fromY, installedSoftware, ROOTS, SIM_ORIGIN, writeRunState, type NodeRunState } from '@mdg-filius/doc';
|
||||
import { docSnapshot, emailServerAfterRun, fromY, installedSoftware, readFilesInto, ROOTS, SIM_ORIGIN, writeRunState, type NodeRunState } from '@mdg-filius/doc';
|
||||
import {
|
||||
parseClientMessage,
|
||||
SIM_PROTOCOL_VERSION,
|
||||
@@ -136,6 +143,35 @@ export class SimulationRoom {
|
||||
doc.on('update', () => {
|
||||
if (this.mode === 'simulation') this.guarded(() => this.syncSoftware());
|
||||
});
|
||||
doc.getMap<Y.Map<unknown>>(ROOTS.nodes).observeDeep((events, transaction) => {
|
||||
// The room's own writes hold what the engine has.
|
||||
if (transaction.origin === SIM_ORIGIN) return;
|
||||
const nodes = new Set<Id>();
|
||||
for (const event of events) {
|
||||
const [node, key] = event.path as string[];
|
||||
// A device added or removed comes in at the next start.
|
||||
if (node !== undefined && (key === 'files' || (key === undefined && event.keys.has('files')))) nodes.add(node);
|
||||
}
|
||||
if (nodes.size > 0) this.guarded(() => this.syncFiles(nodes));
|
||||
});
|
||||
}
|
||||
|
||||
/** The document's files of the nodes into the running engine's (a person wrote them). */
|
||||
private syncFiles(nodes: ReadonlySet<Id>): void {
|
||||
const fileSystems = this.engine.state.topologySnapshot.fileSystemsByNodeId;
|
||||
for (const id of nodes) {
|
||||
const files = fileSystems.get(id);
|
||||
// A node whose programs use no files has none in the engine; it reads the document's at the next start.
|
||||
if (files !== undefined && readFilesInto(this.doc, id, files)) this.writtenFiles.set(id, { files, revision: files.revision });
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether a node's files changed since they were last written back; marked written if so. */
|
||||
private takeChangedFiles(id: Id, files: { readonly revision: number } | undefined): boolean {
|
||||
const written = this.writtenFiles.get(id);
|
||||
const changed = files !== undefined && (written?.files !== files || written.revision !== files.revision);
|
||||
if (files !== undefined && changed) this.writtenFiles.set(id, { files, revision: files.revision });
|
||||
return changed;
|
||||
}
|
||||
|
||||
/** The computers' programs in the document. */
|
||||
@@ -206,10 +242,7 @@ export class SimulationRoom {
|
||||
settings.firewall = snapshot.firewallConfigurationsByNodeId.get(id);
|
||||
if (snapshot.dhcpClientConfigurationsByNodeId.get(id)?.isEnabled === true) settings.device = snapshot.deviceConfigurations.get(id);
|
||||
const files = snapshot.fileSystemsByNodeId.get(id);
|
||||
const written = this.writtenFiles.get(id);
|
||||
const changed = files !== undefined && (written?.files !== files || written.revision !== files.revision);
|
||||
if (files !== undefined && changed) this.writtenFiles.set(id, { files, revision: files.revision });
|
||||
states.set(id, { ...(changed ? { files } : {}), active, settings });
|
||||
states.set(id, { ...(this.takeChangedFiles(id, files) && files !== undefined ? { files } : {}), active, settings });
|
||||
}
|
||||
return states;
|
||||
}
|
||||
@@ -221,6 +254,13 @@ export class SimulationRoom {
|
||||
writeRunState(this.doc, this.runState(), SIM_ORIGIN);
|
||||
}
|
||||
|
||||
/** Writes the files the engine changed since they were last written (origin 'sim'); nothing when none changed. */
|
||||
private writeFilesBack(): void {
|
||||
const states = new Map<Id, NodeRunState>();
|
||||
for (const [id, files] of this.engine.state.topologySnapshot.fileSystemsByNodeId) if (this.takeChangedFiles(id, files)) states.set(id, { files });
|
||||
if (states.size > 0) writeRunState(this.doc, states, SIM_ORIGIN);
|
||||
}
|
||||
|
||||
/** The document as the engine takes it, in the language of the start. */
|
||||
private snapshot(): TopologySnapshot {
|
||||
const snapshot = docSnapshot(this.doc);
|
||||
@@ -364,6 +404,7 @@ export class SimulationRoom {
|
||||
if (!this.guarded(() => this.engine.handle({ type: 'advance', toMilliseconds: this.engine.state.currentTimeMilliseconds + elapsed }))) return;
|
||||
this.flush([...this.clients.values()], false);
|
||||
if (now - this.lastWriteBack >= (this.options.writeBackMilliseconds ?? WRITE_BACK_MILLISECONDS)) this.guarded(() => this.writeBack());
|
||||
else this.guarded(() => this.writeFilesBack());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user