From 8cf62ea28c05da8705c322dcba99b248701e9d19 Mon Sep 17 00:00:00 2001 From: chelproc Date: Sun, 12 Apr 2026 08:32:51 +0900 Subject: [PATCH 1/3] wip --- .../components/NodePinPropertyEditor.tsx | 4 +-- src/store/componentPin.ts | 14 ++++++-- src/store/intrinsics/base.ts | 33 ++++++++----------- src/store/intrinsics/definitions.ts | 15 +++++++-- src/store/nodePin.ts | 7 ++-- 5 files changed, 41 insertions(+), 32 deletions(-) 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..43d3667 100644 --- a/src/store/componentPin.ts +++ b/src/store/componentPin.ts @@ -36,16 +36,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 +226,7 @@ export class CCComponentPinStore extends EventEmitter ): CCComponentPinBitWidthStatus { const pin = this.#pins.get(pinId); invariant(pin); + // 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..2c09ec2 100644 --- a/src/store/intrinsics/base.ts +++ b/src/store/intrinsics/base.ts @@ -30,6 +30,7 @@ export type Context = { type IntrinsicComponentPinAttributes = { name: string; + bitWidthFixMode?: "fixed" | "configurable" | "splittable"; isBitWidthConfigurable?: boolean; isSplittable?: boolean; }; @@ -48,11 +49,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; @@ -98,7 +94,7 @@ export class IntrinsicComponentDefinition< order: this._lastLocalIndex++, name: attributes.name, }; - IntrinsicComponentDefinition.intrinsicComponentPinAttributesByComponentPinId.set( + IntrinsicComponentDefinition._pinAttributesByPinId.set( pin.id, attributes, ); @@ -114,7 +110,7 @@ export class IntrinsicComponentDefinition< order: this._lastLocalIndex++, name: attributes.name, }; - IntrinsicComponentDefinition.intrinsicComponentPinAttributesByComponentPinId.set( + IntrinsicComponentDefinition._pinAttributesByPinId.set( pin.id, attributes, ); @@ -122,18 +118,15 @@ export class IntrinsicComponentDefinition< 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 _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..d1a5287 100644 --- a/src/store/intrinsics/definitions.ts +++ b/src/store/intrinsics/definitions.ts @@ -73,7 +73,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])), @@ -149,7 +157,7 @@ export const input = type: ccIntrinsicComponentTypes.INPUT, name: "Input", in: {}, - out: { Out: { name: "Out" } }, + out: { Out: { name: "In" } }, initialConfig: null, evaluate: (_context, _nodeId, _shape) => { return true; @@ -160,7 +168,7 @@ export const output = new IntrinsicComponentDefinition({ type: ccIntrinsicComponentTypes.OUTPUT, name: "Output", - in: { In: { name: "In" } }, + in: { In: { name: "Out" } }, out: {}, initialConfig: null, evaluate: (_context, _nodeId, _shape) => { @@ -272,6 +280,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) 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, From 62907929f7b3706e2ebf38677f32bd2d825f49c7 Mon Sep 17 00:00:00 2001 From: chelproc Date: Mon, 4 May 2026 17:17:02 +0900 Subject: [PATCH 2/3] update --- src/store/componentPin.ts | 15 +++++ src/store/intrinsics/base.ts | 36 +++++++++--- src/store/intrinsics/definitions.ts | 85 +++++++++++++++++++++++------ src/store/simulation.ts | 36 ++++++++++-- 4 files changed, 143 insertions(+), 29 deletions(-) diff --git a/src/store/componentPin.ts b/src/store/componentPin.ts index 43d3667..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; @@ -226,6 +227,20 @@ 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): diff --git a/src/store/intrinsics/base.ts b/src/store/intrinsics/base.ts index 2c09ec2..348f45f 100644 --- a/src/store/intrinsics/base.ts +++ b/src/store/intrinsics/base.ts @@ -22,23 +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; - bitWidthFixMode?: "fixed" | "configurable" | "splittable"; + 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, @@ -84,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, @@ -96,7 +112,7 @@ export class IntrinsicComponentDefinition< }; IntrinsicComponentDefinition._pinAttributesByPinId.set( pin.id, - attributes, + attributes as IntrinsicComponentPinAttributes, ); this.allPins.push(pin); return pin; @@ -112,7 +128,7 @@ export class IntrinsicComponentDefinition< }; IntrinsicComponentDefinition._pinAttributesByPinId.set( pin.id, - attributes, + attributes as IntrinsicComponentPinAttributes, ); this.allPins.push(pin); return pin; @@ -120,9 +136,15 @@ export class IntrinsicComponentDefinition< this.initialConfig = props.initialConfig; } + private static _byId: Map = + new Map(); + static getByComponentId(componentId: CCComponentId) { + return IntrinsicComponentDefinition._byId.get(componentId) ?? null; + } + private static _pinAttributesByPinId: Map< CCComponentPinId, - IntrinsicComponentPinAttributes + IntrinsicComponentPinAttributes > = new Map(); static getPinAttributesByPinId(pinId: CCComponentPinId) { return ( diff --git a/src/store/intrinsics/definitions.ts b/src/store/intrinsics/definitions.ts index d1a5287..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; @@ -106,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; @@ -157,7 +160,7 @@ export const input = type: ccIntrinsicComponentTypes.INPUT, name: "Input", in: {}, - out: { Out: { name: "In" } }, + out: { Out: { name: "In", bitWidthPolicy: { type: "inferred" } } }, initialConfig: null, evaluate: (_context, _nodeId, _shape) => { return true; @@ -168,7 +171,7 @@ export const output = new IntrinsicComponentDefinition({ type: ccIntrinsicComponentTypes.OUTPUT, name: "Output", - in: { In: { name: "Out" } }, + in: { In: { name: "Out", bitWidthPolicy: { type: "inferred" } } }, out: {}, initialConfig: null, evaluate: (_context, _nodeId, _shape) => { @@ -181,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; @@ -208,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) => { @@ -240,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; @@ -269,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; @@ -298,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/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) { From c935b267589c19958b6f29fc0c6560b972ccdcb3 Mon Sep 17 00:00:00 2001 From: chelproc Date: Mon, 4 May 2026 17:50:41 +0900 Subject: [PATCH 3/3] update Co-Authored-By: Rn86222 <84275482+Rn86222@users.noreply.github.com> Co-Authored-By: taka231 <50023638+taka231@users.noreply.github.com> --- src/store/node.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) 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