components work

This commit is contained in:
sfja 2026-06-11 03:55:35 +02:00
parent cbc416da46
commit e8c8609217
6 changed files with 146 additions and 46 deletions

View File

@ -29,10 +29,10 @@ export class Board {
private wireCachedState = new Map<Wire, ir.State>();
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<string, ComponentKind>,
): 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<Component, boolean>) {
console.log("Lowering to IR");
const comp = this.toIr();
const comps = new Map<string, ir.Component>();
const comp = this.toIr("<main>", 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<string, ir.Component>): 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<Wire, ir.State>();
@ -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<string, ComponentKind>();
private savedBoards = new Map<string, ser.Board>();
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 {

View File

@ -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) {

View File

@ -10,7 +10,6 @@ export class Project {
private events: EventBus,
private boardEditors: BoardEditor[],
public componentRepo: ComponentRepo,
private savedBoards: Map<string, ser.Board>,
) {
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;

View File

@ -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<State>();
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<Stmt, number> {
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}`;
}
}
}

View File

@ -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 = {

View File

@ -16,6 +16,7 @@ export class Sim {
const regs = new Array<boolean>(comp.stmts.length).fill(false);
const stateDependents = new Map<ir.State, number>();
const callOutput = new Map<ir.Stmt, boolean[]>();
const operation = <Ops extends ir.Stmt[]>(
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<boolean>(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);