From e8c8609217e0b54c8b6d91565771dace3a61b752 Mon Sep 17 00:00:00 2001 From: sfja Date: Thu, 11 Jun 2026 03:55:35 +0200 Subject: [PATCH] components work --- editor/src/editor/Board.ts | 67 ++++++++++++++++++++++++++-------- editor/src/editor/Editor.ts | 5 ++- editor/src/editor/Project.ts | 31 ++++++++-------- editor/src/editor/ir.ts | 65 ++++++++++++++++++++++++++++----- editor/src/editor/serialize.ts | 2 +- editor/src/editor/sim.ts | 22 ++++++++++- 6 files changed, 146 insertions(+), 46 deletions(-) diff --git a/editor/src/editor/Board.ts b/editor/src/editor/Board.ts index d82dad0..a541fec 100644 --- a/editor/src/editor/Board.ts +++ b/editor/src/editor/Board.ts @@ -29,10 +29,10 @@ export class Board { private wireCachedState = new Map(); - constructor() {} + constructor(private repo: ComponentRepo) {} static withExample(repo: ComponentRepo): Board { - const board = new Board(); + const board = new Board(repo); board.placeComponent(repo.get("input"), v2(100, 100)); board.placeComponent(repo.get("input"), v2(100, 200)); board.placeComponent(repo.get("and"), v2(300, 150)); @@ -52,13 +52,10 @@ export class Board { return board; } - static fromSerialized( - data: ser.Board, - kindMap: Map, - ): Board { - const board = new Board(); + static fromSerialized(data: ser.Board, repo: ComponentRepo): Board { + const board = new Board(repo); board.components = data.components.map((c) => - Component.fromSerialized(c, kindMap), + Component.fromSerialized(c, repo.defs), ); board.joints = data.joints.map((j) => Joint.fromSerialized(j)); board.wires = data.wires.map((w) => @@ -318,12 +315,20 @@ export class Board { simulate(inputStates: Map) { console.log("Lowering to IR"); - const comp = this.toIr(); + const comps = new Map(); + const comp = this.toIr("
", comps); console.log("Before optimizing"); + for (const [_label, comp] of comps) { + console.log(...new ir.ComponentPrinter().stringifyToConsole(comp)); + } console.log(...new ir.ComponentPrinter().stringifyToConsole(comp)); + for (const [_label, comp] of comps) { + new ir.ComponentOptimizer(comp, []).optimizeComponent(); + } + const replacedStates: [ir.State, ir.State][] = []; - new ir.ComponentOptimizer(comp, replacedStates).optimize(); + new ir.ComponentOptimizer(comp, replacedStates).optimizeMain(); for (const [oldState, newState] of replacedStates) { this.stateWireMap @@ -333,6 +338,9 @@ export class Board { } console.log("After optimizing"); + for (const [_label, comp] of comps) { + console.log(...new ir.ComponentPrinter().stringifyToConsole(comp)); + } console.log(...new ir.ComponentPrinter().stringifyToConsole(comp)); const inputs = this.inputArray(inputStates); @@ -357,7 +365,7 @@ export class Board { } } - toIr(): ir.Component { + toIr(label: string, comps: Map): ir.Component { for (const comp of this.components) { comp.markedWiresConnected = []; } @@ -384,7 +392,7 @@ export class Board { outputIdcs.set(output, i); } - const b = new ir.ComponentBuilder(inputs.length, outputs.length, "main"); + const b = new ir.ComponentBuilder(inputs.length, outputs.length, label); this.stateWireMap.clear(); const wireStates = new Map(); @@ -429,14 +437,32 @@ export class Board { return b.makeBinary("And", inputStmt(0), inputStmt(1)); case "or": return b.makeBinary("Or", inputStmt(0), inputStmt(1)); - default: - throw new Error("not implemented"); + default: { + const savedBoard = this.repo.getSavedBoard(comp.kind.label); + if (!savedBoard) { + throw new Error(`no component '${comp.kind.label}'`); + } + + const label = comp.kind.label; + const board = Board.fromSerialized(savedBoard, this.repo); + + const ir = comps.get(label) ?? board.toIr(label, comps); + comps.set(label, ir); + + return b.makeCall( + ir, + comp.kind.inputs.map((_, i) => inputStmt(i)), + ); + } } })(); for (const [wire, connection] of comp.markedWiresConnected) { if (connection.tag === "OutputPin") { - b.makeSetState(wireStates.get(wire)!, stmt); + b.makeSetState( + wireStates.get(wire)!, + stmt.kind.tag === "Call" ? b.makeElem(stmt, connection.i) : stmt, + ); } } }, @@ -491,6 +517,7 @@ export interface BoardVisitor { export class ComponentRepo { public defs = new Map(); + private savedBoards = new Map(); static withDefaults(): ComponentRepo { const repo = new ComponentRepo(); @@ -507,12 +534,14 @@ export class ComponentRepo { repo.defs = new Map( data.defs.map((e) => [e[0], ComponentKind.fromSerialized(e[1])]), ); + repo.savedBoards = new Map(data.savedBoards); return repo; } serialize(): ser.ComponentRepo { return { defs: [...this.defs.entries()].map((e) => [e[0], e[1].serialize()]), + savedBoards: [...this.savedBoards], }; } @@ -531,6 +560,14 @@ export class ComponentRepo { } return kind; } + + addSavedBoard(ident: string, savedBoard: ser.Board) { + this.savedBoards.set(ident, savedBoard); + } + + getSavedBoard(ident: string): ser.Board | null { + return this.savedBoards.get(ident) ?? null; + } } export class Component { diff --git a/editor/src/editor/Editor.ts b/editor/src/editor/Editor.ts index a50e415..f8dd8b1 100644 --- a/editor/src/editor/Editor.ts +++ b/editor/src/editor/Editor.ts @@ -74,6 +74,8 @@ export class Editor { } case "SelectTab": { this.switchTab(ev.idx); + this.events.send({ tag: "SimulateRequest" }); + break; } case "CloseComponent": { @@ -89,7 +91,7 @@ export class Editor { break; } case "SimulateRequest": { - // this.runSimulation(); + this.runSimulation(); break; } case "SaveRequest": { @@ -146,7 +148,6 @@ export class Editor { runSimulation() { this.board.simulate(this.inputStates); - this.events.send({ tag: "RenderRequest" }); } private onSelectTool(tool: string) { diff --git a/editor/src/editor/Project.ts b/editor/src/editor/Project.ts index e825039..52729be 100644 --- a/editor/src/editor/Project.ts +++ b/editor/src/editor/Project.ts @@ -10,7 +10,6 @@ export class Project { private events: EventBus, private boardEditors: BoardEditor[], public componentRepo: ComponentRepo, - private savedBoards: Map, ) { this.current = boardEditors[this.selectedIdx]; } @@ -34,7 +33,6 @@ export class Project { }, ], repo, - new Map(), ); } @@ -46,13 +44,10 @@ export class Project { } save() { - console.log("Saving"); + // console.log("Saving"); const data = this.serialize(); - globalThis.localStorage.setItem( - "nandsim", - JSON.stringify(this.serialize()), - ); - console.log(data); + globalThis.localStorage.setItem("nandsim", JSON.stringify(data)); + // console.log(data); } private static fromSerialized(data: ser.Project, events: EventBus): Project { @@ -62,11 +57,10 @@ export class Project { data.boardEditors.map( (data): BoardEditor => ({ name: data.name, - board: Board.fromSerialized(data.board, repo.defs), + board: Board.fromSerialized(data.board, repo), }), ), repo, - new Map(data.savedBoards), ); return project; } @@ -82,7 +76,6 @@ export class Project { ), currentBoardEditorIdx: this.selectedIdx, componentRepo, - savedBoards: [...this.savedBoards], }; } @@ -103,7 +96,7 @@ export class Project { newTab(): number { this.boardEditors.push({ name: `(Unnamed ${this.boardEditors.length})`, - board: new Board(), + board: new Board(this.componentRepo), }); this.events.send({ tag: "ShowSelectedTab", idx: this.selectedIdx }); return this.boardEditors.length - 1; @@ -114,11 +107,11 @@ export class Project { this.current = this.boardEditors[this.selectedIdx]; this.events.send({ tag: "ShowSelectedTab", idx: this.selectedIdx }); this.events.send({ tag: "ShowSelectedTool", tool: this.current.name }); + this.events.send({ tag: "SaveRequest" }); } closeTab(): number { - const [removed] = this.boardEditors.splice(this.selectedIdx, 1); - this.savedBoards.set(removed.name, removed.board.serialize()); + const [_removed] = this.boardEditors.splice(this.selectedIdx, 1); this.events.send({ tag: "SaveRequest" }); if (this.boardEditors.length === 0) { @@ -138,7 +131,13 @@ export class Project { this.current.name, this.current.board.toComponentKind(this.current.name), ); + this.componentRepo.addSavedBoard( + this.current.name, + this.current.board.serialize(), + ); + this.events.send({ tag: "ShowSelectedTool", tool: this.current.name }); + this.events.send({ tag: "SaveRequest" }); } tabWithTool(name: string): number { @@ -148,12 +147,12 @@ export class Project { return foundIdx; } - const saved = this.savedBoards.get(name); + const saved = this.componentRepo.getSavedBoard(name); if (!saved) throw new Error(`cannot open '${name}'`); this.boardEditors.push({ name: name, - board: Board.fromSerialized(saved, this.componentRepo.defs), + board: Board.fromSerialized(saved, this.componentRepo), }); this.events.send({ tag: "ShowSelectedTab", idx: this.selectedIdx }); return this.boardEditors.length - 1; diff --git a/editor/src/editor/ir.ts b/editor/src/editor/ir.ts index a8e7f36..d18c3a8 100644 --- a/editor/src/editor/ir.ts +++ b/editor/src/editor/ir.ts @@ -29,8 +29,10 @@ export class Stmt { case "And": case "Or": return [k.lhs, k.rhs]; - case "Component": + case "Call": return [...k.inputs]; + case "Elem": + return [k.src]; } } @@ -53,10 +55,11 @@ export class Stmt { if (k.lhs === oldStmt) k.lhs = newStmt; if (k.rhs === oldStmt) k.rhs = newStmt; break; - case "Component": - k.inputs = k.inputs.map((stmt) => - stmt === oldStmt ? newStmt : oldStmt, - ); + case "Call": + k.inputs = k.inputs.map((stmt) => (stmt === oldStmt ? newStmt : stmt)); + break; + case "Elem": + if (k.src === oldStmt) k.src = newStmt; break; } } @@ -82,7 +85,8 @@ export type StmtKind = | { tag: "SetState"; state: State; src: Stmt } | { tag: "Not"; op: Stmt } | { tag: "And" | "Or"; lhs: Stmt; rhs: Stmt } - | { tag: "Component"; comp: Component; inputs: Stmt[]; outputs: Stmt[] }; + | { tag: "Call"; comp: Component; inputs: Stmt[] } + | { tag: "Elem"; src: Stmt; i: number }; export class State {} @@ -123,6 +127,12 @@ export class ComponentBuilder { makeBinary(tag: "And" | "Or", lhs: Stmt, rhs: Stmt): Stmt { return this.makeStmt({ tag, lhs, rhs }); } + makeCall(comp: Component, inputs: Stmt[]): Stmt { + return this.makeStmt({ tag: "Call", comp, inputs }); + } + makeElem(src: Stmt, i: number): Stmt { + return this.makeStmt({ tag: "Elem", src, i }); + } private makeStmt(kind: StmtKind): Stmt { const stmt = new Stmt(kind); @@ -186,7 +196,15 @@ export class ComponentOptimizer { private replacedStates: [State, State][], ) {} - optimize() { + optimizeMain() { + this.optimizeWithOptions(false); + } + + optimizeComponent() { + this.optimizeWithOptions(true); + } + + private optimizeWithOptions(removeUnusedStates: boolean) { const score = () => this.comp.stmts.length * 100 + this.comp.states.length; let scoreBefore: number; @@ -199,6 +217,9 @@ export class ComponentOptimizer { this.collapseStates(); this.eliminateUnusedStates(); this.eliminateRedundantSetState(); + if (removeUnusedStates) { + this.eliminateIndependentSetState(); + } } while (score() !== scoreBefore); } @@ -309,6 +330,28 @@ export class ComponentOptimizer { } } + eliminateIndependentSetState() { + const mut = new StmtsMutater(this.comp, this.replacedStates); + + const usedStates = new Set(); + for (const stmt of mut) { + const k = stmt.kind; + switch (k.tag) { + case "GetState": + usedStates.add(k.state); + break; + default: + break; + } + } + + for (const stmt of [...mut]) { + if (stmt.kind.tag === "SetState" && !usedStates.has(stmt.kind.state)) { + mut.removeStmt(stmt); + } + } + } + private indexMap(): Map { return new Map(this.comp.stmts.map((stmt, i) => [stmt, i])); } @@ -355,7 +398,7 @@ export class ComponentPrinter { "\\c(color: #d44949; font-weight: bold)$&\\c", ) .replaceAll( - /(?:Null)|(?:Input)|(?:Output)|(?:GetState)|(?:SetState)|(?:Not)|(?:And)|(?:Or)|(?:Component)/g, + /(?:Null)|(?:Input)|(?:Output)|(?:GetState)|(?:SetState)|(?:Not)|(?:And)|(?:Or)|(?:Call)|(?:Elem)/g, "\\c(color: orange)$&\\c", ); @@ -407,8 +450,10 @@ export class ComponentPrinter { case "And": case "Or": return `${stmtId(stmt)} = ${k.tag} ${stmtId(k.lhs)}, ${stmtId(k.rhs)}`; - case "Component": - return `Component <...>`; + case "Call": + return `${stmtId(stmt)} = Call ${k.comp.label} (${k.inputs.map((s) => stmtId(s)).join(", ")})`; + case "Elem": + return `${stmtId(stmt)} = Elem ${stmtId(k.src)}, ${k.i}`; } } } diff --git a/editor/src/editor/serialize.ts b/editor/src/editor/serialize.ts index 630228b..c910651 100644 --- a/editor/src/editor/serialize.ts +++ b/editor/src/editor/serialize.ts @@ -2,7 +2,6 @@ export type Project = { boardEditors: BoardEditor[]; currentBoardEditorIdx: number; componentRepo: ComponentRepo; - savedBoards: [string, Board][]; }; export type BoardEditor = { @@ -37,6 +36,7 @@ export type WireConnection = export type ComponentRepo = { defs: [string, ComponentKind][]; + savedBoards: [string, Board][]; }; export type ComponentKind = { diff --git a/editor/src/editor/sim.ts b/editor/src/editor/sim.ts index a875cec..6ea91bd 100644 --- a/editor/src/editor/sim.ts +++ b/editor/src/editor/sim.ts @@ -16,6 +16,7 @@ export class Sim { const regs = new Array(comp.stmts.length).fill(false); const stateDependents = new Map(); + const callOutput = new Map(); const operation = ( action: (...ops: boolean[]) => boolean, @@ -65,8 +66,25 @@ export class Sim { case "Or": regs[i] = operation((a, b) => a || b, k.lhs, k.rhs); break; - case "Component": - throw new Error("not implemented"); + case "Call": { + const outputs = new Array(k.comp.outputs).fill(false); + new Sim( + k.comp, + k.inputs.map((stmt) => regs[stmtIdcs.get(stmt)!]), + outputs, + this.state, + ).simulate(); + callOutput.set(stmt, outputs); + break; + } + case "Elem": { + const outputs = callOutput.get(k.src); + if (!outputs) { + throw new Error(); + } + regs[i] = outputs[k.i]; + break; + } } // console.log("Sim:", i, stmt.kind.tag, inputs, outputs, this.state);