From 8cf62ea28c05da8705c322dcba99b248701e9d19 Mon Sep 17 00:00:00 2001 From: chelproc Date: Sun, 12 Apr 2026 08:32:51 +0900 Subject: [PATCH 1/4] 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/4] 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/4] 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 From 1df4159985eed37317e9f97cceeaea08d81503d5 Mon Sep 17 00:00:00 2001 From: chelproc Date: Sat, 9 May 2026 12:55:16 +0900 Subject: [PATCH 4/4] 202560509 Co-authored-by: Takashi Nagatomi Co-authored-by: Raito Nakajima --- .../components/NodePinPropertyEditor.tsx | 23 +++- .../Editor/renderer/ComponentPin/index.tsx | 12 +- .../Node/components/Display/index.tsx | 34 ++++- .../edit/Editor/store/slices/core/index.ts | 29 ++-- .../edit/Editor/store/slices/core/types.ts | 10 +- src/pages/home/index.tsx | 19 ++- src/store/componentPin.ts | 117 ++++++---------- src/store/intrinsics/definitions.ts | 4 +- src/store/react/index.tsx | 125 +----------------- src/store/samples/default.ts | 120 +++++++++++++++++ 10 files changed, 266 insertions(+), 227 deletions(-) create mode 100644 src/store/samples/default.ts diff --git a/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx b/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx index 1f4ab89..d1deb88 100644 --- a/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx +++ b/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx @@ -4,6 +4,7 @@ import nullthrows from "nullthrows"; import { useState } from "react"; import invariant from "tiny-invariant"; import { rect } from "../../../../common/rect"; +import { CCConnectionStore } from "../../../../store/connection"; import { IntrinsicComponentDefinition } from "../../../../store/intrinsics/base"; import { CCNodePinStore } from "../../../../store/nodePin"; import { useStore } from "../../../../store/react"; @@ -126,10 +127,28 @@ export function CCComponentEditorNodePinPropertyEditor() { connection.from === nodePin.id ? connection.to : connection.from; + const fromNodePinId = + connection.from === nodePin.id + ? nodePin.id + : anotherNodePinId; + const toNodePinId = + connection.from === nodePin.id + ? anotherNodePinId + : nodePin.id; + const parentComponentId = connection.parentComponentId; + store.connections.unregister([connection.id]); if ( - !store.nodePins.isConnectable(nodePin.id, anotherNodePinId) + store.nodePins.isConnectable(nodePin.id, anotherNodePinId) ) { - store.connections.unregister([connection.id]); + // reconnect if still connectable after bit width change + store.connections.register( + CCConnectionStore.create({ + parentComponentId, + from: fromNodePinId, + to: toNodePinId, + bentPortion: 0.5, + }), + ); } } } diff --git a/src/pages/edit/Editor/renderer/ComponentPin/index.tsx b/src/pages/edit/Editor/renderer/ComponentPin/index.tsx index f65b879..1fc775a 100644 --- a/src/pages/edit/Editor/renderer/ComponentPin/index.tsx +++ b/src/pages/edit/Editor/renderer/ComponentPin/index.tsx @@ -30,7 +30,10 @@ export default function CCComponentEditorRendererComponentPin({ label: stringifySimulationValue( type === "input" ? nullthrows( - componentEditorState.getInputValue(interfaceComponentPin.id), + componentEditorState.getInputValue([ + interfaceComponentPin.id, + componentEditorState.timeStep, + ]), ) : nullthrows(componentEditorState.getNodePinValue(nodePinId)), ), @@ -38,12 +41,13 @@ export default function CCComponentEditorRendererComponentPin({ type === "input" ? () => { const nodePinValue = nullthrows( - componentEditorState.getInputValue( + componentEditorState.getInputValue([ interfaceComponentPin.id, - ), + componentEditorState.timeStep, + ]), ); componentEditorState.setInputValue( - interfaceComponentPin.id, + [interfaceComponentPin.id, componentEditorState.timeStep], wrappingIncrementSimulationValue(nodePinValue), ); } diff --git a/src/pages/edit/Editor/renderer/Node/components/Display/index.tsx b/src/pages/edit/Editor/renderer/Node/components/Display/index.tsx index 9b9892c..a80634f 100644 --- a/src/pages/edit/Editor/renderer/Node/components/Display/index.tsx +++ b/src/pages/edit/Editor/renderer/Node/components/Display/index.tsx @@ -1,12 +1,27 @@ +import nullthrows from "nullthrows"; import { theme } from "../../../../../../../common/theme"; +import { display } from "../../../../../../../store/intrinsics/definitions"; import type { CCIntrinsicComponentDisplaySpec } from "../../../../../../../store/intrinsics/types"; -import type { CCNode } from "../../../../../../../store/node"; +import { useStore } from "../../../../../../../store/react"; +import { useComponentEditorStore } from "../../../../store"; import type { CCComponentEditorRendererNodeRendererProps } from "../../types"; export function CCComponentEditorRendererNodeDisplayRenderer( props: CCComponentEditorRendererNodeRendererProps, ) { - const node = props.node as CCNode; + const { store } = useStore(); + const config = props.node.config as CCIntrinsicComponentDisplaySpec["config"]; + const inputNodePin = nullthrows( + store.nodePins + .getManyByNodeId(props.node.id) + .find((pin) => pin.componentPinId === display.inputPin.Pixels.id), + `Display node ${props.node.id} is missing input pin`, + ); + const editorState = useComponentEditorStore()(); + const inputValue = + editorState.editorMode === "play" + ? editorState.getNodePinValue(inputNodePin.id) + : undefined; return ( <> @@ -38,12 +53,12 @@ export function CCComponentEditorRendererNodeDisplayRenderer( fontSize={16} fill={theme.palette.textPrimary} > - {node.config.resolution.x}x{node.config.resolution.y} + {config.resolution.x}x{config.resolution.y} - {Array(node.config.resolution.y) + {Array(config.resolution.y) .keys() .map((y) => - Array(node.config.resolution.x) + Array(config.resolution.x) .keys() .map((x) => ( )) diff --git a/src/pages/edit/Editor/store/slices/core/index.ts b/src/pages/edit/Editor/store/slices/core/index.ts index f11b741..ada2720 100644 --- a/src/pages/edit/Editor/store/slices/core/index.ts +++ b/src/pages/edit/Editor/store/slices/core/index.ts @@ -11,7 +11,7 @@ import type { // import type { CCComponentId } from "../../../../../../store/component"; import simulateComponent from "../../../../../../store/simulation"; import type { ComponentEditorSliceCreator } from "../../types"; -import type { EditorStoreCoreSlice } from "./types"; +import type { EditorStoreCoreSlice, InputValueKey } from "./types"; export function stringifySimulationValue(value: SimulationValue): string { const binary = value.map((v) => (v ? "1" : "0")).join(""); @@ -43,11 +43,20 @@ export const createComponentEditorStoreCoreSlice: ComponentEditorSliceCreator< }, /** @private */ inputValues: new Map(), - getInputValue(componentPinId: CCComponentPinId) { - const value = get().inputValues.get(componentPinId); + getInputValue(inputValueKey: InputValueKey) { + const value = get().inputValues.get(JSON.stringify(inputValueKey)); if (!value) { + const previousTimeStepValue = get().inputValues.get( + JSON.stringify([inputValueKey[0], inputValueKey[1] - 1]), + ); + if (previousTimeStepValue) { + get().setInputValue(inputValueKey, previousTimeStepValue); + return previousTimeStepValue; + } const bitWidthStatus = - store.componentPins.getComponentPinBitWidthStatus(componentPinId); + store.componentPins.getComponentPinBitWidthStatus( + inputValueKey[0], + ); if (bitWidthStatus.isFixed) { const newValue = new Array(bitWidthStatus.bitWidth).fill(false); return newValue; @@ -60,15 +69,12 @@ export const createComponentEditorStoreCoreSlice: ComponentEditorSliceCreator< } return value; }, - setInputValue( - componentPinId: CCComponentPinId, - value: SimulationValue, - ) { + setInputValue(inputValueKey: InputValueKey, value: SimulationValue) { set((state) => { return { ...state, inputValues: new Map(state.inputValues).set( - componentPinId, + JSON.stringify(inputValueKey), value, ), }; @@ -170,7 +176,10 @@ export const createComponentEditorStoreCoreSlice: ComponentEditorSliceCreator< for (const pin of pins) { invariant(pin.implementation); if (pin.type === "input") { - inputValues.set(pin.id, editorState.getInputValue(pin.id)); + inputValues.set( + pin.id, + editorState.getInputValue([pin.id, timeStep]), + ); } } simulationCachedFrames.push( diff --git a/src/pages/edit/Editor/store/slices/core/types.ts b/src/pages/edit/Editor/store/slices/core/types.ts index 76f3748..bf8916b 100644 --- a/src/pages/edit/Editor/store/slices/core/types.ts +++ b/src/pages/edit/Editor/store/slices/core/types.ts @@ -11,7 +11,9 @@ export type EditorModePlay = "play"; export type RangeSelect = { start: Vector2; end: Vector2 } | null; -export type InputValueKey = CCComponentPinId; +export type TimeStep = number; + +export type InputValueKey = [CCComponentPinId, TimeStep]; export type NodePinPropertyEditorTarget = { nodeId: CCNodeId; @@ -29,9 +31,9 @@ export type EditorStoreCoreSlice = { setNodePinPropertyEditorTarget( target: NodePinPropertyEditorTarget | null, ): void; - inputValues: Map; - getInputValue(componentPinId: CCComponentPinId): SimulationValue; - setInputValue(componentPinId: CCComponentPinId, value: SimulationValue): void; + inputValues: Map; + getInputValue(inputValueKey: InputValueKey): SimulationValue; + setInputValue(inputValueKey: InputValueKey, value: SimulationValue): void; setEditorMode(mode: EditorMode): void; setTimeStep(timeStep: number): void; selectNode(ids: CCNodeId[], exclusive: boolean): void; diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 0ddb3ad..53a9eba 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -2,7 +2,6 @@ import { Add as AddIcon, Download as DownloadIcon, MoreVert as MoreVertIcon, - NoteAdd as NoteAddIcon, Upload as UploadIcon, } from "@mui/icons-material"; import { @@ -76,9 +75,6 @@ export default function HomePage({ onComponentSelected }: HomePageProps) { File - +
+
diff --git a/src/store/componentPin.ts b/src/store/componentPin.ts index e534566..0376f4d 100644 --- a/src/store/componentPin.ts +++ b/src/store/componentPin.ts @@ -4,22 +4,9 @@ import invariant from "tiny-invariant"; import type { Opaque } from "type-fest"; import type CCStore from "."; import type { CCComponentId } from "./component"; -import { - aggregate, - and, - broadcast, - decompose, - false_, - flipflop, - input, - not, - or, - output, - true_, - xor, -} from "./intrinsics/definitions"; +import { IntrinsicComponentDefinition } from "./intrinsics/base"; +import { aggregate, decompose, input, output } from "./intrinsics/definitions"; import type { CCNodePinId } from "./nodePin"; -// import { IntrinsicComponentDefinition } from "./intrinsics/base"; export type CCComponentPin = { readonly id: CCComponentPinId; @@ -228,71 +215,51 @@ export class CCComponentPinStore extends EventEmitter 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): - case nullthrows(and.outputPin.Out.id): - case nullthrows(or.inputPin.A.id): - case nullthrows(or.inputPin.B.id): - case nullthrows(or.outputPin.Out.id): - case nullthrows(not.inputPin.In.id): - case nullthrows(not.outputPin.Out.id): - case nullthrows(xor.inputPin.A.id): - case nullthrows(xor.inputPin.B.id): - case nullthrows(xor.outputPin.Out.id): - case nullthrows(input.outputPin.Out.id): - case nullthrows(output.inputPin.In.id): - case nullthrows(flipflop.inputPin.In.id): - case nullthrows(flipflop.outputPin.Out.id): - case nullthrows(true_.outputPin.Out.id): - case nullthrows(false_.outputPin.Out.id): { - return { isFixed: false, fixMode: "automatic" }; - } - case nullthrows(aggregate.inputPin.In.id): { + // Intrinsic components + const intrinsicPinAttributes = + IntrinsicComponentDefinition.getPinAttributesByPinId(pin.id); + if (intrinsicPinAttributes) { + // TODO: This is a temporary workaround to allow the bit width of aggregate and decompose pins to be calculated outside of the normal inference process. + // We should eventually refactor the bit width inference process to handle these cases more elegantly. + if (pin.id === aggregate.outputPin.Out.id) return { isFixed: false, fixMode: "manual" }; - } - case nullthrows(aggregate.outputPin.Out.id): { + if (pin.id === decompose.inputPin.In.id) return { isFixed: false, fixMode: "manual" }; - } - case nullthrows(decompose.outputPin.Out.id): { - return { isFixed: false, fixMode: "manual" }; - } - case nullthrows(decompose.inputPin.In.id): { - return { isFixed: false, fixMode: "manual" }; - } - case nullthrows(broadcast.inputPin.In.id): { - return { isFixed: true, bitWidth: 1 }; - } - case nullthrows(broadcast.outputPin.Out.id): { + + if (intrinsicPinAttributes.bitWidthPolicy.type === "inferred") + return { isFixed: false, fixMode: "automatic" }; + if (intrinsicPinAttributes.bitWidthPolicy.type === "configurable") return { isFixed: false, fixMode: "manual" }; - } - default: { - if (pin.implementation === null) { - throw new Error("unreachable"); - } - const bitWidthStatus = this.#store.nodePins.getNodePinBitWidthStatus( - pin.implementation, + if (intrinsicPinAttributes.bitWidthPolicy.type === "fixed") { + const definition = nullthrows( + IntrinsicComponentDefinition.getByComponentId(pin.componentId), + `Intrinsic component definition not found for component ID: ${pin.componentId}`, ); - if (bitWidthStatus.isFixed) { - return bitWidthStatus; - } else { - return { isFixed: false, fixMode: "automatic" }; - } + return { + isFixed: true, + bitWidth: intrinsicPinAttributes.bitWidthPolicy.calculateBitWidth( + definition.initialConfig, + {}, + ), + }; } + throw new Error( + `Unknown bit width policy: ${intrinsicPinAttributes.bitWidthPolicy satisfies never}`, + ); + } + + // User-defined components + invariant( + pin.implementation, + "Pin implementation must be defined for user-defined components", + ); + const bitWidthStatus = this.#store.nodePins.getNodePinBitWidthStatus( + pin.implementation, + ); + if (bitWidthStatus.isFixed) { + return bitWidthStatus; + } else { + return { isFixed: false, fixMode: "automatic" }; } } diff --git a/src/store/intrinsics/definitions.ts b/src/store/intrinsics/definitions.ts index 82d5b8e..4b9d1d5 100644 --- a/src/store/intrinsics/definitions.ts +++ b/src/store/intrinsics/definitions.ts @@ -198,7 +198,7 @@ export const aggregate = type: "fixed", calculateBitWidth: (_, manualBitWidths) => manualBitWidths?.In?.reduce((sum, bitWidth) => sum + bitWidth, 0) ?? - 0, + 1, }, }, }, @@ -234,7 +234,7 @@ export const decompose = manualBitWidths?.Out?.reduce( (sum, bitWidth) => sum + bitWidth, 0, - ) ?? 0, + ) ?? 1, }, }, }, diff --git a/src/store/react/index.tsx b/src/store/react/index.tsx index 3fdaa55..aff883b 100644 --- a/src/store/react/index.tsx +++ b/src/store/react/index.tsx @@ -1,4 +1,3 @@ -import nullthrows from "nullthrows"; import { createContext, useCallback, @@ -9,129 +8,14 @@ import { } from "react"; import invariant from "tiny-invariant"; import CCStore from ".."; -import { CCComponentStore } from "../component"; -import { CCConnectionStore } from "../connection"; -import { and, input, not, output } from "../intrinsics/definitions"; -import { CCNodeStore } from "../node"; +import { setupDefaultSample } from "../samples/default"; function useContextValue() { const [store, setStore] = useState(() => { const tempStore = new CCStore(); const isRestored = tempStore.autoSaver.tryRestore(); tempStore.mount(); - if (!isRestored) { - const rootComponent = CCComponentStore.create({ - name: "Root", - }); - tempStore.components.register(rootComponent); - - const sampleAndNode = CCNodeStore.create({ - parentComponentId: rootComponent.id, - componentId: and.component.id, - position: { x: -100, y: 0 }, - }); - tempStore.nodes.register(sampleAndNode); - - const sampleNotNode = CCNodeStore.create({ - parentComponentId: rootComponent.id, - componentId: not.component.id, - position: { x: 100, y: 0 }, - }); - tempStore.nodes.register(sampleNotNode); - - const sampleInputNode1 = CCNodeStore.create({ - parentComponentId: rootComponent.id, - componentId: input.component.id, - position: { x: -300, y: -100 }, - }); - tempStore.nodes.register(sampleInputNode1); - - const sampleInputNode2 = CCNodeStore.create({ - parentComponentId: rootComponent.id, - componentId: input.component.id, - position: { x: -300, y: 100 }, - }); - tempStore.nodes.register(sampleInputNode2); - - const sampleOutputNode = CCNodeStore.create({ - parentComponentId: rootComponent.id, - componentId: output.component.id, - position: { x: 300, y: 0 }, - }); - tempStore.nodes.register(sampleOutputNode); - - const fromPinOfSampleInputNode1 = nullthrows( - tempStore.nodePins - .getManyByNodeId(sampleInputNode1.id) - .find((nodePin) => nodePin.componentPinId === input.outputPin.Out.id), - ); - const toPinOfSampleAndNode1 = nullthrows( - tempStore.nodePins - .getManyByNodeId(sampleAndNode.id) - .find((nodePin) => nodePin.componentPinId === and.inputPin.A.id), - ); - const connection1 = CCConnectionStore.create({ - parentComponentId: rootComponent.id, - from: fromPinOfSampleInputNode1.id, - to: toPinOfSampleAndNode1.id, - bentPortion: 0.5, - }); - tempStore.connections.register(connection1); - - const fromPinOfSampleInputNode2 = nullthrows( - tempStore.nodePins - .getManyByNodeId(sampleInputNode2.id) - .find((nodePin) => nodePin.componentPinId === input.outputPin.Out.id), - ); - const toPinOfSampleAndNode2 = nullthrows( - tempStore.nodePins - .getManyByNodeId(sampleAndNode.id) - .find((nodePin) => nodePin.componentPinId === and.inputPin.B.id), - ); - const connection2 = CCConnectionStore.create({ - parentComponentId: rootComponent.id, - from: fromPinOfSampleInputNode2.id, - to: toPinOfSampleAndNode2.id, - bentPortion: 0.5, - }); - tempStore.connections.register(connection2); - - const fromPinOfSampleAndNode = nullthrows( - tempStore.nodePins - .getManyByNodeId(sampleAndNode.id) - .find((nodePin) => nodePin.componentPinId === and.outputPin.Out.id), - ); - const toPinOfSampleNotNode = nullthrows( - tempStore.nodePins - .getManyByNodeId(sampleNotNode.id) - .find((nodePin) => nodePin.componentPinId === not.inputPin.In.id), - ); - const connection3 = CCConnectionStore.create({ - parentComponentId: rootComponent.id, - from: fromPinOfSampleAndNode.id, - to: toPinOfSampleNotNode.id, - bentPortion: 0.5, - }); - tempStore.connections.register(connection3); - - const fromPinOfSampleNotNode = nullthrows( - tempStore.nodePins - .getManyByNodeId(sampleNotNode.id) - .find((nodePin) => nodePin.componentPinId === not.outputPin.Out.id), - ); - const toPinOfSampleOutputNode = nullthrows( - tempStore.nodePins - .getManyByNodeId(sampleOutputNode.id) - .find((nodePin) => nodePin.componentPinId === output.inputPin.In.id), - ); - const connection4 = CCConnectionStore.create({ - parentComponentId: rootComponent.id, - from: fromPinOfSampleNotNode.id, - to: toPinOfSampleOutputNode.id, - bentPortion: 0.5, - }); - tempStore.connections.register(connection4); - } + if (!isRestored) setupDefaultSample(tempStore); tempStore.autoSaver.watch(); return tempStore; }); @@ -146,10 +30,11 @@ function useContextValue() { }; }, [store]); - const resetStore = useCallback((json: string) => { + const resetStore = useCallback((json?: string) => { const store = new CCStore(); - store.importJson(json); + if (json) store.importJson(json); store.mount(); + if (!json) setupDefaultSample(store); store.autoSaver.watch(); setStore(store); }, []); diff --git a/src/store/samples/default.ts b/src/store/samples/default.ts new file mode 100644 index 0000000..20a7ab4 --- /dev/null +++ b/src/store/samples/default.ts @@ -0,0 +1,120 @@ +import nullthrows from "nullthrows"; +import type CCStore from ".."; +import { CCComponentStore } from "../component"; +import { CCConnectionStore } from "../connection"; +import { and, input, not, output } from "../intrinsics/definitions"; +import { CCNodeStore } from "../node"; + +export function setupDefaultSample(store: CCStore) { + const rootComponent = CCComponentStore.create({ + name: "Root", + }); + store.components.register(rootComponent); + + const sampleAndNode = CCNodeStore.create({ + parentComponentId: rootComponent.id, + componentId: and.component.id, + position: { x: -100, y: 0 }, + }); + store.nodes.register(sampleAndNode); + + const sampleNotNode = CCNodeStore.create({ + parentComponentId: rootComponent.id, + componentId: not.component.id, + position: { x: 100, y: 0 }, + }); + store.nodes.register(sampleNotNode); + + const sampleInputNode1 = CCNodeStore.create({ + parentComponentId: rootComponent.id, + componentId: input.component.id, + position: { x: -300, y: -100 }, + }); + store.nodes.register(sampleInputNode1); + + const sampleInputNode2 = CCNodeStore.create({ + parentComponentId: rootComponent.id, + componentId: input.component.id, + position: { x: -300, y: 100 }, + }); + store.nodes.register(sampleInputNode2); + + const sampleOutputNode = CCNodeStore.create({ + parentComponentId: rootComponent.id, + componentId: output.component.id, + position: { x: 300, y: 0 }, + }); + store.nodes.register(sampleOutputNode); + + const fromPinOfSampleInputNode1 = nullthrows( + store.nodePins + .getManyByNodeId(sampleInputNode1.id) + .find((nodePin) => nodePin.componentPinId === input.outputPin.Out.id), + ); + const toPinOfSampleAndNode1 = nullthrows( + store.nodePins + .getManyByNodeId(sampleAndNode.id) + .find((nodePin) => nodePin.componentPinId === and.inputPin.A.id), + ); + const connection1 = CCConnectionStore.create({ + parentComponentId: rootComponent.id, + from: fromPinOfSampleInputNode1.id, + to: toPinOfSampleAndNode1.id, + bentPortion: 0.5, + }); + store.connections.register(connection1); + + const fromPinOfSampleInputNode2 = nullthrows( + store.nodePins + .getManyByNodeId(sampleInputNode2.id) + .find((nodePin) => nodePin.componentPinId === input.outputPin.Out.id), + ); + const toPinOfSampleAndNode2 = nullthrows( + store.nodePins + .getManyByNodeId(sampleAndNode.id) + .find((nodePin) => nodePin.componentPinId === and.inputPin.B.id), + ); + const connection2 = CCConnectionStore.create({ + parentComponentId: rootComponent.id, + from: fromPinOfSampleInputNode2.id, + to: toPinOfSampleAndNode2.id, + bentPortion: 0.5, + }); + store.connections.register(connection2); + + const fromPinOfSampleAndNode = nullthrows( + store.nodePins + .getManyByNodeId(sampleAndNode.id) + .find((nodePin) => nodePin.componentPinId === and.outputPin.Out.id), + ); + const toPinOfSampleNotNode = nullthrows( + store.nodePins + .getManyByNodeId(sampleNotNode.id) + .find((nodePin) => nodePin.componentPinId === not.inputPin.In.id), + ); + const connection3 = CCConnectionStore.create({ + parentComponentId: rootComponent.id, + from: fromPinOfSampleAndNode.id, + to: toPinOfSampleNotNode.id, + bentPortion: 0.5, + }); + store.connections.register(connection3); + + const fromPinOfSampleNotNode = nullthrows( + store.nodePins + .getManyByNodeId(sampleNotNode.id) + .find((nodePin) => nodePin.componentPinId === not.outputPin.Out.id), + ); + const toPinOfSampleOutputNode = nullthrows( + store.nodePins + .getManyByNodeId(sampleOutputNode.id) + .find((nodePin) => nodePin.componentPinId === output.inputPin.In.id), + ); + const connection4 = CCConnectionStore.create({ + parentComponentId: rootComponent.id, + from: fromPinOfSampleNotNode.id, + to: toPinOfSampleOutputNode.id, + bentPortion: 0.5, + }); + store.connections.register(connection4); +}