diff --git a/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx b/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx index c3e2a81..1f4ab89 100644 --- a/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx +++ b/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx @@ -32,9 +32,7 @@ export function CCComponentEditorNodePinPropertyEditor() { "NodePinPropertyEditor can only be used for node pins with user specified bit width", ); const componentPinAttributes = nullthrows( - IntrinsicComponentDefinition.intrinsicComponentPinAttributesByComponentPinId.get( - target.componentPinId, - ), + IntrinsicComponentDefinition.getPinAttributesByPinId(target.componentPinId), "NodePinPropertyEditor can only be used for intrinsic component pins", ); diff --git a/src/store/componentPin.ts b/src/store/componentPin.ts index e5ad966..e534566 100644 --- a/src/store/componentPin.ts +++ b/src/store/componentPin.ts @@ -19,6 +19,7 @@ import { xor, } from "./intrinsics/definitions"; import type { CCNodePinId } from "./nodePin"; +// import { IntrinsicComponentDefinition } from "./intrinsics/base"; export type CCComponentPin = { readonly id: CCComponentPinId; @@ -36,16 +37,25 @@ export const ccPinTypes: CCComponentPinType[] = ["input", "output"]; /** null for intrinsic components */ export type CCPinImplementation = CCNodePinId | null; +/** + * The resolved bit width status of a node pin instance. + * - `isFixed: false` — the bit width has not yet been determined. + * - `isFixed: true` — the bit width is known and available as `bitWidth`. + */ export type CCNodePinBitWidthStatus = | { isFixed: false } | { isFixed: true; bitWidth: number }; +/** + * The bit width status of a component pin definition. + * - `isFixed: false, fixMode: "automatic"` — the bit width is not yet determined and will be inferred automatically from connections. + * - `isFixed: false, fixMode: "manual"` — the bit width is not yet determined and must be specified manually by the user. + * - `isFixed: true` — the bit width is known and available as `bitWidth`. + */ export type CCComponentPinBitWidthStatus = | { isFixed: false; fixMode: "automatic" | "manual" } | { isFixed: true; bitWidth: number }; -export type CCNodePinFixedBitWidth = number; - export type CCComponentPinStoreEvents = { didRegister(pin: CCComponentPin): void; willUnregister(pin: CCComponentPin): void; @@ -217,6 +227,21 @@ export class CCComponentPinStore extends EventEmitter ): CCComponentPinBitWidthStatus { const pin = this.#pins.get(pinId); invariant(pin); + + // const intrinsicPinAttributes = + // IntrinsicComponentDefinition.getPinAttributesByPinId(pin.id); + // if (intrinsicPinAttributes) { + // if (intrinsicPinAttributes.bitWidthPolicy.type === "inferred") + // return { isFixed: false, fixMode: "automatic" }; + // if (intrinsicPinAttributes.bitWidthPolicy.type === "configurable") + // return { isFixed: false, fixMode: "manual" }; + // if (intrinsicPinAttributes.bitWidthPolicy.type === "fixed") { + // const definition = nullthrows(IntrinsicComponentDefinition.getByComponentId(pin.componentId)); + // } + // throw new Error(`Unknown bit width policy: ${intrinsicPinAttributes.bitWidthPolicy}`); + // } + + // TODO: Remove hardcoded intrinsic component pin IDs and replace with a more flexible system, such as metadata on the component definitions. switch (pin.id) { case nullthrows(and.inputPin.A.id): case nullthrows(and.inputPin.B.id): diff --git a/src/store/intrinsics/base.ts b/src/store/intrinsics/base.ts index 470ba35..348f45f 100644 --- a/src/store/intrinsics/base.ts +++ b/src/store/intrinsics/base.ts @@ -22,22 +22,37 @@ export type CCComponentPinInstanceShapes = { export type ComponentEvaluationContext = { previousFrame: SimulationFrame | null; currentFrame: SimulationFrame; + defaultBitWidth: number; }; export type Context = { componentId: CCComponentId; }; -type IntrinsicComponentPinAttributes = { +type IntrinsicComponentPinBitWidthPolicy< + Spec extends CCIntrinsicComponentSpec, +> = + | { type: "inferred" } + | { + type: "fixed"; + calculateBitWidth: ( + config: Spec["config"], + manualBitWidths: Partial>, + ) => number; + } + | { type: "configurable"; isSplittable: boolean }; + +type IntrinsicComponentPinAttributes = { name: string; + bitWidthPolicy: IntrinsicComponentPinBitWidthPolicy; isBitWidthConfigurable?: boolean; isSplittable?: boolean; }; type Props = { type: CCIntrinsicComponentType; name: string; - in: Record; - out: Record; + in: Record>; + out: Record>; initialConfig: Spec["config"]; evaluate: ( context: ComponentEvaluationContext, @@ -48,11 +63,6 @@ type Props = { export class IntrinsicComponentDefinition< Spec extends CCIntrinsicComponentSpec = CCIntrinsicComponentSpec, > { - static intrinsicComponentPinAttributesByComponentPinId: Map< - CCComponentPinId, - IntrinsicComponentPinAttributes - > = new Map(); - readonly id: CCComponentId; readonly type: CCIntrinsicComponentType; readonly name: string; @@ -88,7 +98,9 @@ export class IntrinsicComponentDefinition< intrinsicType: props.type, name: this.name, }; + IntrinsicComponentDefinition._byId.set(this.id, this); this.evaluate = props.evaluate; + this.inputPin = mapValues(props.in, (attributes) => { const pin: CCComponentPin = { id: this._generateId() as CCComponentPinId, @@ -98,9 +110,9 @@ export class IntrinsicComponentDefinition< order: this._lastLocalIndex++, name: attributes.name, }; - IntrinsicComponentDefinition.intrinsicComponentPinAttributesByComponentPinId.set( + IntrinsicComponentDefinition._pinAttributesByPinId.set( pin.id, - attributes, + attributes as IntrinsicComponentPinAttributes, ); this.allPins.push(pin); return pin; @@ -114,26 +126,29 @@ export class IntrinsicComponentDefinition< order: this._lastLocalIndex++, name: attributes.name, }; - IntrinsicComponentDefinition.intrinsicComponentPinAttributesByComponentPinId.set( + IntrinsicComponentDefinition._pinAttributesByPinId.set( pin.id, - attributes, + attributes as IntrinsicComponentPinAttributes, ); this.allPins.push(pin); return pin; }); this.initialConfig = props.initialConfig; - // this.outputPin = { - // id: this._generateId() as CCComponentPinId, - // componentId: this.id, - // type: "output", - // implementation: null, - // order: this._lastLocalIndex++, - // name: props.out.name, - // }; - // IntrinsicComponentDefinition.intrinsicComponentPinAttributesByComponentPinId.set( - // this.outputPin.id, - // props.out - // ); - // this.allPins.push(this.outputPin); + } + + private static _byId: Map = + new Map(); + static getByComponentId(componentId: CCComponentId) { + return IntrinsicComponentDefinition._byId.get(componentId) ?? null; + } + + private static _pinAttributesByPinId: Map< + CCComponentPinId, + IntrinsicComponentPinAttributes + > = new Map(); + static getPinAttributesByPinId(pinId: CCComponentPinId) { + return ( + IntrinsicComponentDefinition._pinAttributesByPinId.get(pinId) ?? null + ); } } diff --git a/src/store/intrinsics/definitions.ts b/src/store/intrinsics/definitions.ts index 227ecda..82d5b8e 100644 --- a/src/store/intrinsics/definitions.ts +++ b/src/store/intrinsics/definitions.ts @@ -24,8 +24,8 @@ function createUnaryOperator( { type, name, - in: { In: { name: "In" } }, - out: { Out: { name: "Out" } }, + in: { In: { name: "In", bitWidthPolicy: { type: "inferred" } } }, + out: { Out: { name: "Out", bitWidthPolicy: { type: "inferred" } } }, initialConfig: null, evaluate: (context, nodeId, shape) => { const inputShape = shape.inputShape.In; @@ -57,8 +57,11 @@ function createBinaryOperator( { type, name, - in: { A: { name: "A" }, B: { name: "B" } }, - out: { Out: { name: "Out" } }, + in: { + A: { name: "A", bitWidthPolicy: { type: "inferred" } }, + B: { name: "B", bitWidthPolicy: { type: "inferred" } }, + }, + out: { Out: { name: "Out", bitWidthPolicy: { type: "inferred" } } }, initialConfig: null, evaluate: (context, nodeId, shape) => { const inputShapeA = shape.inputShape.A; @@ -73,7 +76,15 @@ function createBinaryOperator( } invariant( inputValueA.length === inputValueB.length, - "Input lengths must match", + "Input lengths must match (name: " + + name + + ", nodeId: " + + nodeId + + ", inputValueA: " + + inputValueA + + ", inputValueB: " + + inputValueB + + ")", ); const outputValue = Array.from({ length: inputValueA.length }, (_, i) => evaluate(nullthrows(inputValueA[i]), nullthrows(inputValueB[i])), @@ -98,7 +109,7 @@ function createNullaryOperator( type, name, in: {}, - out: { Out: { name: "Out" } }, + out: { Out: { name: "Out", bitWidthPolicy: { type: "inferred" } } }, initialConfig: null, evaluate: (context, nodeId, shape) => { const outputShape = shape.outputShape.Out; @@ -149,7 +160,7 @@ export const input = type: ccIntrinsicComponentTypes.INPUT, name: "Input", in: {}, - out: { Out: { name: "Out" } }, + out: { Out: { name: "In", bitWidthPolicy: { type: "inferred" } } }, initialConfig: null, evaluate: (_context, _nodeId, _shape) => { return true; @@ -160,7 +171,7 @@ export const output = new IntrinsicComponentDefinition({ type: ccIntrinsicComponentTypes.OUTPUT, name: "Output", - in: { In: { name: "In" } }, + in: { In: { name: "Out", bitWidthPolicy: { type: "inferred" } } }, out: {}, initialConfig: null, evaluate: (_context, _nodeId, _shape) => { @@ -173,9 +184,24 @@ export const aggregate = type: ccIntrinsicComponentTypes.AGGREGATE, name: "Aggregate", in: { - In: { name: "In", isBitWidthConfigurable: true, isSplittable: true }, + In: { + name: "In", + bitWidthPolicy: { type: "configurable", isSplittable: true }, + isBitWidthConfigurable: true, + isSplittable: true, + }, + }, + out: { + Out: { + name: "Out", + bitWidthPolicy: { + type: "fixed", + calculateBitWidth: (_, manualBitWidths) => + manualBitWidths?.In?.reduce((sum, bitWidth) => sum + bitWidth, 0) ?? + 0, + }, + }, }, - out: { Out: { name: "Out" } }, initialConfig: null, evaluate: (context, nodeId, shape) => { const inputShape = shape.inputShape.In; @@ -200,10 +226,25 @@ export const decompose = type: ccIntrinsicComponentTypes.DECOMPOSE, name: "Decompose", in: { - In: { name: "In" }, + In: { + name: "In", + bitWidthPolicy: { + type: "fixed", + calculateBitWidth: (_, manualBitWidths) => + manualBitWidths?.Out?.reduce( + (sum, bitWidth) => sum + bitWidth, + 0, + ) ?? 0, + }, + }, }, out: { - Out: { name: "Out", isBitWidthConfigurable: true, isSplittable: true }, + Out: { + name: "Out", + bitWidthPolicy: { type: "configurable", isSplittable: true }, + isBitWidthConfigurable: true, + isSplittable: true, + }, }, initialConfig: null, evaluate: (context, nodeId, shape) => { @@ -232,9 +273,18 @@ export const broadcast = type: ccIntrinsicComponentTypes.BROADCAST, name: "Broadcast", in: { - In: { name: "In" }, + In: { + name: "In", + bitWidthPolicy: { type: "fixed", calculateBitWidth: () => 1 }, + }, + }, + out: { + Out: { + name: "Out", + bitWidthPolicy: { type: "configurable", isSplittable: false }, + isBitWidthConfigurable: true, + }, }, - out: { Out: { name: "Out", isBitWidthConfigurable: true } }, initialConfig: null, evaluate: (context, nodeId, shape) => { const inputShape = shape.inputShape.In; @@ -261,10 +311,8 @@ export const flipflop = new IntrinsicComponentDefinition({ type: ccIntrinsicComponentTypes.FLIPFLOP, name: "FlipFlop", - in: { - In: { name: "In" }, - }, - out: { Out: { name: "Out" } }, + in: { In: { name: "In", bitWidthPolicy: { type: "inferred" } } }, + out: { Out: { name: "Out", bitWidthPolicy: { type: "inferred" } } }, initialConfig: null, evaluate: (context, nodeId, shape) => { const inputShape = shape.inputShape.In; @@ -272,6 +320,7 @@ export const flipflop = const outputShape = shape.outputShape.Out; invariant(outputShape[0] && !outputShape[1]); const nodePinIdToValue = context.currentFrame.nodes.get(nodeId)?.pins; + console.log("FlipFlop evaluate", { nodeId, inputShape, outputShape }); const previousValue = context.previousFrame?.nodes .get(nodeId) @@ -289,7 +338,16 @@ export const display = new IntrinsicComponentDefinition({ type: ccIntrinsicComponentTypes.DISPLAY, name: "Display", - in: { Pixels: { name: "Pixels" } }, + in: { + Pixels: { + name: "Pixels", + bitWidthPolicy: { + type: "fixed", + calculateBitWidth: (config) => + config.resolution.x * config.resolution.y, + }, + }, + }, out: {}, initialConfig: { resolution: { x: 20, y: 15 } }, evaluate: () => true, diff --git a/src/store/node.ts b/src/store/node.ts index 9e2232e..8135dd4 100644 --- a/src/store/node.ts +++ b/src/store/node.ts @@ -57,7 +57,20 @@ export class CCNodeStore extends EventEmitter { } } - mount() {} + mount() { + this.#store.components.on("willUnregister", (component) => { + const ids = [...this.#nodes.values()] + .filter( + (node) => + node.parentComponentId === component.id || + node.componentId === component.id, + ) + .map((node) => node.id); + if (ids.length > 0) { + this.unregister(ids); + } + }); + } /** * Register a node diff --git a/src/store/nodePin.ts b/src/store/nodePin.ts index 9514753..7a1d91a 100644 --- a/src/store/nodePin.ts +++ b/src/store/nodePin.ts @@ -377,10 +377,9 @@ export class CCNodePinStore extends EventEmitter { partialPin: Omit & Partial>, ): CCNodePin { - const attributes = - IntrinsicComponentDefinition.intrinsicComponentPinAttributesByComponentPinId.get( - partialPin.componentPinId, - ); + const attributes = IntrinsicComponentDefinition.getPinAttributesByPinId( + partialPin.componentPinId, + ); return { ...partialPin, id: crypto.randomUUID() as CCNodePinId, diff --git a/src/store/simulation.ts b/src/store/simulation.ts index b670de2..11761e6 100644 --- a/src/store/simulation.ts +++ b/src/store/simulation.ts @@ -45,6 +45,7 @@ function createShape( store: CCStore, nodeId: CCNodeId, pin: Record, + context: ComponentEvaluationContext, ): Record { const node = nullthrows(store.nodes.get(nodeId)); const { componentId } = node; @@ -63,7 +64,9 @@ function createShape( const bitWidthStatus = store.nodePins.getNodePinBitWidthStatus( nodePin.id, ); - const bitWidth = !bitWidthStatus.isFixed ? 1 : bitWidthStatus.bitWidth; + const bitWidth = !bitWidthStatus.isFixed + ? context.defaultBitWidth + : bitWidthStatus.bitWidth; shape[key].push({ nodePinId: nodePin.id, bitWidth }); } } @@ -73,6 +76,7 @@ function createShape( function createIntrinsicComponentShape( store: CCStore, nodeId: CCNodeId, + context: ComponentEvaluationContext, ): CCIntrinsicComponentShape { const node = nullthrows(store.nodes.get(nodeId)); const { componentId } = node; @@ -80,8 +84,8 @@ function createIntrinsicComponentShape( invariant(componentDefinition); const inputPin = componentDefinition.inputPin; const outputPin = componentDefinition.outputPin; - const inputShape = createShape(store, nodeId, inputPin); - const outputShape = createShape(store, nodeId, outputPin); + const inputShape = createShape(store, nodeId, inputPin, context); + const outputShape = createShape(store, nodeId, outputPin, context); return { inputShape, outputShape }; } @@ -95,7 +99,7 @@ function simulateIntrinsic( const { componentId } = node; const componentDefinition = definitionByComponentId.get(componentId); invariant(componentDefinition); - const shape = createIntrinsicComponentShape(store, nodeId); + const shape = createIntrinsicComponentShape(store, nodeId, context); return componentDefinition.evaluate(context, nodeId, shape); } @@ -132,6 +136,27 @@ function simulateNode( } } + let childDefaultBitWidth = context.defaultBitWidth; + for (const nodePin of nodePins) { + const componentPin = nullthrows( + store.componentPins.get(nodePin.componentPinId), + ); + const componentPinBitWidthStatus = + store.componentPins.getComponentPinBitWidthStatus(componentPin.id); + if ( + !componentPinBitWidthStatus.isFixed && + componentPinBitWidthStatus.fixMode === "automatic" + ) { + const nodePinBitWidthStatus = store.nodePins.getNodePinBitWidthStatus( + nodePin.id, + ); + if (nodePinBitWidthStatus.isFixed) { + childDefaultBitWidth = nodePinBitWidthStatus.bitWidth; + break; + } + } + } + const innerSimulationFrame = simulateComponent( store, component.id, @@ -139,6 +164,7 @@ function simulateNode( context.previousFrame ? nullthrows(context.previousFrame.nodes.get(nodeId)).child : null, + childDefaultBitWidth, ); // Set output values for component to parent @@ -169,6 +195,7 @@ export default function simulateComponent( componentId: CCComponentId, inputValues: Map, previousFrame: SimulationFrame | null, + defaultBitWidth: number = 1, ): SimulationFrame { const currentSimulationFrame = { componentId: componentId, @@ -243,6 +270,7 @@ export default function simulateComponent( const childContext = { previousFrame: previousFrame, currentFrame: currentSimulationFrame, + defaultBitWidth, }; while (unevaluatedNodes.size > 0) {