From 5552949d7126ddca21c55f7d249a9c6ba9887b37 Mon Sep 17 00:00:00 2001 From: sfja Date: Tue, 5 May 2026 20:59:32 +0200 Subject: [PATCH] add engine --- engine/deno.json | 5 + engine/src/circuit.ts | 210 ++++++++++++++++++++++++++++++++++++++++++ engine/src/main.ts | 30 ++++++ 3 files changed, 245 insertions(+) create mode 100644 engine/deno.json create mode 100644 engine/src/circuit.ts create mode 100644 engine/src/main.ts diff --git a/engine/deno.json b/engine/deno.json new file mode 100644 index 0000000..3fecf5f --- /dev/null +++ b/engine/deno.json @@ -0,0 +1,5 @@ +{ + "fmt": { + "indentWidth": 4 + } +} diff --git a/engine/src/circuit.ts b/engine/src/circuit.ts new file mode 100644 index 0000000..dae293c --- /dev/null +++ b/engine/src/circuit.ts @@ -0,0 +1,210 @@ +export class Circuit { + constructor( + private comps: Component[], + private inputComps: Component[], + private pins: Pin[], + private pinConsumers: Map, + private pinInCount: number, + private pinOutCount: number, + ) {} + + createState(): State { + return new State(this.pins, this.pinInCount, this.pinOutCount); + } + + simulate(state: State) { + const queue: Component[] = []; + for (const comp of this.inputComps) { + queue.push(comp); + } + + while (queue.length) { + const comp = queue.shift()!; + comp.simulate(state); + + for (const pin of comp.outputs) { + queue.push(...this.pinConsumers.get(pin)!); + } + } + } +} + +export class Component { + constructor( + public kind: ComponentKind, + public inputs: (Pin | null)[], + public outputs: Pin[], + ) {} + + simulate(state: State) { + const k = this.kind; + switch (k.tag) { + case "PinIn": { + state.setPin(this.outputs[0], state.getIn(k.idx)); + break; + } + case "PinOut": { + state.setOut(k.idx, state.getPin(this.inputs[0])); + break; + } + case "Nand": { + const lhs = state.getPin(this.inputs[0]); + const rhs = state.getPin(this.inputs[1]); + state.setPin( + this.outputs[0], + !(lhs && rhs), + ); + break; + } + default: + throw new Error(`not handled (${k})`); + } + } +} + +export type ComponentKind = + | { tag: "PinIn" | "PinOut"; idx: number } + | { tag: "Nand" }; + +export class Pin { + constructor() {} +} + +export class State { + private pinsHigh = new Set(); + private pinsInHigh = new Set(); + private pinsOutHigh = new Set(); + + constructor( + private pins: Pin[], + private pinInCount: number, + private pinOutCount: number, + ) {} + + getPin(pin: Pin | null): boolean { + return pin !== null && this.pinsHigh.has(pin); + } + setPin(pin: Pin, value: boolean) { + if (value) { + this.pinsHigh.add(pin); + } else { + this.pinsHigh.delete(pin); + } + } + + getIn(idx: number): boolean { + return this.pinsInHigh.has(idx); + } + setIn(idx: number, value: boolean) { + if (value) { + this.pinsInHigh.add(idx); + } else { + this.pinsInHigh.delete(idx); + } + } + + getOut(idx: number): boolean { + return this.pinsOutHigh.has(idx); + } + setOut(idx: number, value: boolean) { + if (value) { + this.pinsOutHigh.add(idx); + } else { + this.pinsOutHigh.delete(idx); + } + } + + prettyPrint() { + console.log( + `inputs: [${ + new Array(this.pinInCount) + .fill(0) + .map((_, i) => this.pinsInHigh.has(i) ? 1 : 0) + .map((v) => v.toString()) + .join(", ") + }]`, + ); + console.log( + `outputs: [${ + new Array(this.pinOutCount) + .fill(0) + .map((_, i) => this.pinsOutHigh.has(i) ? 1 : 0) + .map((v) => v.toString()) + .join(", ") + }]`, + ); + console.log( + `state: [${ + new Array(this.pins.length) + .fill(0) + .map((_, i) => this.pinsHigh.has(this.pins[i]) ? 1 : 0) + .map((v) => v.toString()) + .join(", ") + }]`, + ); + } +} + +export class CircuitBuilder { + private comps: Component[] = []; + private inputComps: Component[] = []; + private pins: Pin[] = []; + private pinInCount = 0; + private pinOutCount = 0; + + build(): Circuit { + const pinConsumers = new Map(); + + for (const pin of this.pins) { + pinConsumers.set(pin, []); + } + for (const comp of this.comps) { + for (const pin of comp.inputs) { + if (!pin) { + continue; + } + pinConsumers.get(pin)!.push(comp); + } + } + + return new Circuit( + this.comps, + this.inputComps, + this.pins, + pinConsumers, + this.pinInCount, + this.pinOutCount, + ); + } + + addPinIn(): Component { + const idx = this.pinInCount; + this.pinInCount += 1; + const comp = this.addComponent({ tag: "PinIn", idx }, 0, 1); + this.inputComps.push(comp); + return comp; + } + + addPinOut(): Component { + const idx = this.pinOutCount; + this.pinOutCount += 1; + return this.addComponent({ tag: "PinOut", idx }, 1, 0); + } + + addNand(): Component { + return this.addComponent({ tag: "Nand" }, 2, 1); + } + + private addComponent( + kind: ComponentKind, + inputCount: number, + outputCount: number, + ) { + const inputs = new Array(inputCount).fill(null); + const outputs = new Array(outputCount).fill(0).map(() => new Pin()); + const comp = new Component(kind, inputs, outputs); + this.comps.push(comp); + this.pins.push(...outputs); + return comp; + } +} diff --git a/engine/src/main.ts b/engine/src/main.ts new file mode 100644 index 0000000..ffb7448 --- /dev/null +++ b/engine/src/main.ts @@ -0,0 +1,30 @@ +import { CircuitBuilder } from "./circuit.ts"; + +const builder = new CircuitBuilder(); + +const i0 = builder.addPinIn(); +const i1 = builder.addPinIn(); +const o0 = builder.addPinOut(); + +const and = builder.addNand(); + +and.inputs[0] = i0.outputs[0]; +and.inputs[1] = i1.outputs[0]; + +o0.inputs[0] = and.outputs[0]; + +const circuit = builder.build(); + +const state = circuit.createState(); + +state.setIn(0, true); +state.setIn(1, true); + +console.log("-- before --"); +state.prettyPrint(); + +circuit.simulate(state); + +console.log("-- after --"); +state.prettyPrint(); +