diff --git a/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx b/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx
index d1deb88..2dff789 100644
--- a/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx
+++ b/src/pages/edit/Editor/components/NodePinPropertyEditor.tsx
@@ -8,7 +8,7 @@ import { CCConnectionStore } from "../../../../store/connection";
import { IntrinsicComponentDefinition } from "../../../../store/intrinsics/base";
import { CCNodePinStore } from "../../../../store/nodePin";
import { useStore } from "../../../../store/react";
-import getCCComponentEditorRendererNodeGeometry from "../renderer/Node/geometry";
+import { getCCComponentEditorRendererNodeGeometry } from "../renderer/Node/geometry";
import { useComponentEditorStore } from "../store";
export function CCComponentEditorNodePinPropertyEditor() {
@@ -123,33 +123,22 @@ export function CCComponentEditorNodePinPropertyEditor() {
nodePin.id,
);
for (const connection of connections) {
- const anotherNodePinId =
- 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 from = connection.from;
+ const to = connection.to;
const parentComponentId = connection.parentComponentId;
- store.connections.unregister([connection.id]);
- if (
- store.nodePins.isConnectable(nodePin.id, anotherNodePinId)
- ) {
- // reconnect if still connectable after bit width change
- store.connections.register(
- CCConnectionStore.create({
- parentComponentId,
- from: fromNodePinId,
- to: toNodePinId,
- bentPortion: 0.5,
- }),
- );
- }
+ store.connections.unregister([connection.id]).then(() => {
+ if (store.nodePins.isConnectable(from, to)) {
+ // reconnect if still connectable after bit width change
+ store.connections.register(
+ CCConnectionStore.create({
+ parentComponentId,
+ from,
+ to,
+ bentPortion: 0.5,
+ }),
+ );
+ }
+ });
}
}
continue;
diff --git a/src/pages/edit/Editor/components/ViewModeSwitcher.tsx b/src/pages/edit/Editor/components/ViewModeSwitcher.tsx
index dabfb5d..4421f34 100644
--- a/src/pages/edit/Editor/components/ViewModeSwitcher.tsx
+++ b/src/pages/edit/Editor/components/ViewModeSwitcher.tsx
@@ -1,9 +1,11 @@
import { Edit, PlayArrow } from "@mui/icons-material";
import { Fab } from "@mui/material";
+import { useCanSimulate } from "../../../../store/react/selectors";
import { useComponentEditorStore } from "../store";
export default function CCComponentEditorViewModeSwitcher() {
const componentEditorState = useComponentEditorStore()();
+ const canSimulate = useCanSimulate(componentEditorState.componentId);
return (
{componentEditorState.editorMode === "edit" ? : }
diff --git a/src/pages/edit/Editor/renderer/ComponentPin/index.tsx b/src/pages/edit/Editor/renderer/ComponentPin/index.tsx
index 1fc775a..a0ce9a2 100644
--- a/src/pages/edit/Editor/renderer/ComponentPin/index.tsx
+++ b/src/pages/edit/Editor/renderer/ComponentPin/index.tsx
@@ -5,7 +5,7 @@ import { useStore } from "../../../../../store/react";
import { wrappingIncrementSimulationValue } from "../../../../../store/simulation";
import { useComponentEditorStore } from "../../store";
import { stringifySimulationValue } from "../../store/slices/core";
-import getCCComponentEditorRendererNodeGeometry from "./../Node/geometry";
+import { getCCComponentEditorRendererNodeGeometry } from "./../Node/geometry";
export type CCComponentEditorRendererComponentPinProps = {
nodePinId: CCNodePinId;
};
diff --git a/src/pages/edit/Editor/renderer/Connection/index.tsx b/src/pages/edit/Editor/renderer/Connection/index.tsx
index f1748c3..8808bdd 100644
--- a/src/pages/edit/Editor/renderer/Connection/index.tsx
+++ b/src/pages/edit/Editor/renderer/Connection/index.tsx
@@ -9,7 +9,7 @@ import ensureStoreItem from "../../../../../store/react/error";
import { useNode } from "../../../../../store/react/selectors";
import { useComponentEditorStore } from "../../store";
import { stringifySimulationValue } from "../../store/slices/core/index";
-import getCCComponentEditorRendererNodeGeometry from "../Node/geometry";
+import { getCCComponentEditorRendererNodeGeometry } from "../Node/geometry";
export type CCComponentEditorRendererConnectionEndpoint = {
direction: CCComponentPinType;
diff --git a/src/pages/edit/Editor/renderer/Node/components/Default/geometry.ts b/src/pages/edit/Editor/renderer/Node/components/Default/geometry.ts
index a6aedb5..4c40bed 100644
--- a/src/pages/edit/Editor/renderer/Node/components/Default/geometry.ts
+++ b/src/pages/edit/Editor/renderer/Node/components/Default/geometry.ts
@@ -1,50 +1,41 @@
-import { type Vector2, vector2 } from "../../../../../../../common/vector2";
+import type { Vector2 } from "../../../../../../../common/vector2";
import type { CCNodePinId } from "../../../../../../../store/nodePin";
import type {
- CCComponentEditorRendererNodeGeometryCalculator,
- CCComponentEditorRendererNodeGeometrySource,
+ CCComponentEditorRendererNodeLayout,
+ CCComponentEditorRendererNodeLayoutSource,
} from "../../types";
const width = 100;
const gapY = 20;
const paddingY = 15;
-export const ccComponentRendererNodeDefaultGeometryCalculator: CCComponentEditorRendererNodeGeometryCalculator =
- (source: CCComponentEditorRendererNodeGeometrySource) => {
- const size: Vector2 = {
- x: width,
- y:
- gapY *
- Math.max(
- source.inputNodePinIds.length,
- source.outputNodePinIds.length,
- ) +
- paddingY * 2,
- };
+export function ccComponentRendererNodeDefaultLayoutCalculator(
+ source: CCComponentEditorRendererNodeLayoutSource,
+): CCComponentEditorRendererNodeLayout {
+ const size: Vector2 = {
+ x: width,
+ y:
+ gapY *
+ Math.max(
+ source.inputNodePinIds.length,
+ source.outputNodePinIds.length,
+ ) +
+ paddingY * 2,
+ };
- const nodePinPositionById = new Map();
- for (const [index, nodePinId] of source.inputNodePinIds.entries()) {
- nodePinPositionById.set(nodePinId, {
- x: source.position.x - size.x / 2,
- y:
- source.position.y +
- gapY * (index - source.inputNodePinIds.length / 2 + 0.5),
- });
- }
- for (const [index, nodePinId] of source.outputNodePinIds.entries()) {
- nodePinPositionById.set(nodePinId, {
- x: source.position.x + size.x / 2,
- y:
- source.position.y +
- gapY * (index - source.outputNodePinIds.length / 2 + 0.5),
- });
- }
+ const nodePinOffsetById = new Map();
- return {
- rect: {
- position: vector2.sub(source.position, vector2.div(size, 2)),
- size,
- },
- nodePinPositionById,
- };
- };
+ const startYIn =
+ size.y / 2 - (gapY * (source.inputNodePinIds.length - 1)) / 2;
+ for (const [index, pinId] of source.inputNodePinIds.entries()) {
+ nodePinOffsetById.set(pinId, { x: 0, y: startYIn + gapY * index });
+ }
+
+ const startYOut =
+ size.y / 2 - (gapY * (source.outputNodePinIds.length - 1)) / 2;
+ for (const [index, pinId] of source.outputNodePinIds.entries()) {
+ nodePinOffsetById.set(pinId, { x: size.x, y: startYOut + gapY * index });
+ }
+
+ return { size, nodePinOffsetById };
+}
diff --git a/src/pages/edit/Editor/renderer/Node/components/Default/index.tsx b/src/pages/edit/Editor/renderer/Node/components/Default/index.tsx
index 133721b..292cbe7 100644
--- a/src/pages/edit/Editor/renderer/Node/components/Default/index.tsx
+++ b/src/pages/edit/Editor/renderer/Node/components/Default/index.tsx
@@ -8,18 +8,18 @@ export function CCComponentEditorRendererNodeDefaultRenderer(
<>
{props.component.name}
void;
+};
+
+export function CCComponentEditorRendererNodeDisplayRendererConfigSettingButton(
+ props: Props,
+) {
+ const [anchorEl, setAnchorEl] = useState(null);
+ const [newConfig, setNewConfig] = useState(props.config);
+
+ return (
+ <>
+ e.stopPropagation()}
+ onClick={(e) => {
+ e.stopPropagation();
+ setAnchorEl(e.currentTarget);
+ setNewConfig(props.config);
+ }}
+ >
+
+
+ setAnchorEl(null)}
+ slotProps={{
+ root: { onPointerDown: (e) => e.stopPropagation() },
+ paper: { sx: { width: "300px", p: 2 } },
+ }}
+ >
+
+
+ >
+ );
+}
diff --git a/src/pages/edit/Editor/renderer/Node/components/Display/geometry.ts b/src/pages/edit/Editor/renderer/Node/components/Display/geometry.ts
index 88b09be..e4eb639 100644
--- a/src/pages/edit/Editor/renderer/Node/components/Display/geometry.ts
+++ b/src/pages/edit/Editor/renderer/Node/components/Display/geometry.ts
@@ -1,33 +1,34 @@
+import nullthrows from "nullthrows";
import { type Vector2, vector2 } from "../../../../../../../common/vector2";
+import type { CCIntrinsicComponentDisplaySpec } from "../../../../../../../store/intrinsics/types";
import type { CCNodePinId } from "../../../../../../../store/nodePin";
import type {
- CCComponentEditorRendererNodeGeometryCalculator,
- CCComponentEditorRendererNodeGeometrySource,
+ CCComponentEditorRendererNodeLayout,
+ CCComponentEditorRendererNodeLayoutSource,
} from "../../types";
-const width = 320;
-const height = 200;
+export const ccComponentEditorRendererNodeDisplayLayoutConstants = {
+ padding: 8,
+ gridSize: 12,
+ gridSizeDisplayWidth: 60,
+};
-export const ccComponentRendererNodeDisplayGeometryCalculator: CCComponentEditorRendererNodeGeometryCalculator =
- (source: CCComponentEditorRendererNodeGeometrySource) => {
- const size: Vector2 = {
- x: width,
- y: height,
- };
+export function ccComponentRendererNodeDisplayLayoutCalculator(
+ source: CCComponentEditorRendererNodeLayoutSource,
+): CCComponentEditorRendererNodeLayout {
+ const { padding, gridSize, gridSizeDisplayWidth } =
+ ccComponentEditorRendererNodeDisplayLayoutConstants;
+ const config = source.config as CCIntrinsicComponentDisplaySpec["config"];
- return {
- rect: {
- position: vector2.sub(source.position, vector2.div(size, 2)),
- size,
- },
- nodePinPositionById: new Map(
- source.inputNodePinIds.map(
- (id) =>
- [
- id,
- vector2.create(source.position.x - size.x / 2, source.position.y),
- ] as const,
- ),
- ),
- };
+ const size = {
+ x: gridSizeDisplayWidth + gridSize * config.resolution.x + padding * 2,
+ y: gridSize * config.resolution.y + padding * 2,
};
+
+ return {
+ size,
+ nodePinOffsetById: new Map([
+ [nullthrows(source.inputNodePinIds[0]), vector2.create(0, size.y / 2)],
+ ]),
+ };
+}
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 a80634f..12c0cdc 100644
--- a/src/pages/edit/Editor/renderer/Node/components/Display/index.tsx
+++ b/src/pages/edit/Editor/renderer/Node/components/Display/index.tsx
@@ -5,12 +5,19 @@ import type { CCIntrinsicComponentDisplaySpec } from "../../../../../../../store
import { useStore } from "../../../../../../../store/react";
import { useComponentEditorStore } from "../../../../store";
import type { CCComponentEditorRendererNodeRendererProps } from "../../types";
+import { CCComponentEditorRendererNodeDefaultRenderer } from "../Default";
+import { CCComponentEditorRendererNodeDisplayRendererConfigSettingButton } from "./ConfigSettingButton";
+import { ccComponentEditorRendererNodeDisplayLayoutConstants } from "./geometry";
export function CCComponentEditorRendererNodeDisplayRenderer(
props: CCComponentEditorRendererNodeRendererProps,
) {
const { store } = useStore();
+
const config = props.node.config as CCIntrinsicComponentDisplaySpec["config"];
+ const updateConfig = (newConfig: CCIntrinsicComponentDisplaySpec["config"]) =>
+ store.nodes.update(props.node.id, { config: newConfig });
+
const inputNodePin = nullthrows(
store.nodePins
.getManyByNodeId(props.node.id)
@@ -23,38 +30,27 @@ export function CCComponentEditorRendererNodeDisplayRenderer(
? editorState.getNodePinValue(inputNodePin.id)
: undefined;
+ const { padding, gridSize, gridSizeDisplayWidth } =
+ ccComponentEditorRendererNodeDisplayLayoutConstants;
+
return (
<>
-
-
- Display
-
+
{config.resolution.x}x{config.resolution.y}
+
+
+
{Array(config.resolution.y)
.keys()
.map((y) =>
@@ -63,10 +59,10 @@ export function CCComponentEditorRendererNodeDisplayRenderer(
.map((x) => (
-> = {
+const specialLayoutCalculators: {
+ [key in CCIntrinsicComponentType]?: (
+ source: CCComponentEditorRendererNodeLayoutSource,
+ ) => CCComponentEditorRendererNodeLayout;
+} = {
[ccIntrinsicComponentTypes.DISPLAY]:
- ccComponentRendererNodeDisplayGeometryCalculator,
+ ccComponentRendererNodeDisplayLayoutCalculator,
};
-export default function getCCComponentEditorRendererNodeGeometry(
+export function getCCComponentEditorRendererNodeLayout(
store: CCStore,
nodeId: CCNodeId,
-) {
+): CCComponentEditorRendererNodeLayout {
const node = nullthrows(store.nodes.get(nodeId));
const component = nullthrows(store.components.get(node.componentId));
const nodePins = store.nodePins.getManyByNodeId(nodeId);
- const source: CCComponentEditorRendererNodeGeometrySource = {
- position: node.position,
+ const layoutCalculator =
+ (component.intrinsicType &&
+ specialLayoutCalculators[component.intrinsicType]) ??
+ ccComponentRendererNodeDefaultLayoutCalculator;
+
+ return layoutCalculator({
+ config: node.config,
inputNodePinIds: nodePins
.filter((np) => {
const cp = nullthrows(store.componentPins.get(np.componentPinId));
@@ -44,11 +50,32 @@ export default function getCCComponentEditorRendererNodeGeometry(
return cp.type === "output";
})
.map((np) => np.id),
+ });
+}
+
+export function ccComponentEditorRendererLayoutToGeometry(
+ layout: CCComponentEditorRendererNodeLayout,
+ nodePosition: Vector2,
+): CCComponentEditorRendererNodeGeometry {
+ const position = vector2.sub(nodePosition, vector2.div(layout.size, 2));
+ return {
+ rect: { position: position, size: layout.size },
+ nodePinPositionById: new Map(
+ layout.nodePinOffsetById
+ .entries()
+ .map(([nodePinId, offset]) => [
+ nodePinId,
+ vector2.add(position, offset),
+ ]),
+ ),
};
+}
- const calculator =
- (component.intrinsicType &&
- specialGeometryCalculators[component.intrinsicType]) ??
- ccComponentRendererNodeDefaultGeometryCalculator;
- return calculator(source);
+export function getCCComponentEditorRendererNodeGeometry(
+ store: CCStore,
+ nodeId: CCNodeId,
+): CCComponentEditorRendererNodeGeometry {
+ const node = nullthrows(store.nodes.get(nodeId));
+ const layout = getCCComponentEditorRendererNodeLayout(store, nodeId);
+ return ccComponentEditorRendererLayoutToGeometry(layout, node.position);
}
diff --git a/src/pages/edit/Editor/renderer/Node/index.tsx b/src/pages/edit/Editor/renderer/Node/index.tsx
index 58dfe46..add0190 100644
--- a/src/pages/edit/Editor/renderer/Node/index.tsx
+++ b/src/pages/edit/Editor/renderer/Node/index.tsx
@@ -12,7 +12,10 @@ import { useComponentEditorStore } from "../../store";
import CCComponentEditorRendererNodePin from "../NodePin";
import { CCComponentEditorRendererNodeDefaultRenderer } from "./components/Default";
import { CCComponentEditorRendererNodeDisplayRenderer } from "./components/Display";
-import getCCComponentEditorRendererNodeGeometry from "./geometry";
+import {
+ ccComponentEditorRendererLayoutToGeometry,
+ getCCComponentEditorRendererNodeLayout,
+} from "./geometry";
import type {
CCComponentEditorRendererNodeRendererNodeState,
CCComponentEditorRendererNodeRendererProps,
@@ -44,7 +47,11 @@ const CCComponentEditorRendererNode = ensureStoreItem(
vector2.zero,
);
- const geometry = getCCComponentEditorRendererNodeGeometry(store, nodeId);
+ const layout = getCCComponentEditorRendererNodeLayout(store, nodeId);
+ const geometry = ccComponentEditorRendererLayoutToGeometry(
+ layout,
+ node.position,
+ );
const Renderer =
(component.intrinsicType && specialRenderers[component.intrinsicType]) ||
CCComponentEditorRendererNodeDefaultRenderer;
@@ -86,8 +93,13 @@ const CCComponentEditorRendererNode = ensureStoreItem(
return (
<>
- {/** biome-ignore lint/a11y/noStaticElementInteractions: SVG */}
-
-
+
{store.nodePins.getManyByNodeId(nodeId).map((nodePin) => (
;
};
-export type CCComponentEditorRendererNodeGeometrySource = {
- position: Vector2;
+export type CCComponentEditorRendererNodeLayoutSource = {
+ config: CCNode["config"];
inputNodePinIds: CCNodePinId[];
outputNodePinIds: CCNodePinId[];
};
@@ -26,6 +28,6 @@ export type CCComponentEditorRendererNodeGeometry = {
nodePinPositionById: Map;
};
-export type CCComponentEditorRendererNodeGeometryCalculator = (
- source: CCComponentEditorRendererNodeGeometrySource,
-) => CCComponentEditorRendererNodeGeometry;
+export type CCComponentEditorRendererNodeRendererNodeState = {
+ isSelected: boolean;
+};
diff --git a/src/pages/edit/Editor/renderer/NodePin/index.tsx b/src/pages/edit/Editor/renderer/NodePin/index.tsx
index 1c25056..c1a5c98 100644
--- a/src/pages/edit/Editor/renderer/NodePin/index.tsx
+++ b/src/pages/edit/Editor/renderer/NodePin/index.tsx
@@ -14,7 +14,7 @@ import {
CCComponentEditorRendererConnectionCore,
type CCComponentEditorRendererConnectionEndpoint,
} from "./../Connection";
-import getCCComponentEditorRendererNodeGeometry from "./../Node/geometry";
+import { getCCComponentEditorRendererNodeGeometry } from "./../Node/geometry";
const NODE_PIN_POSITION_SENSITIVITY = 10;
diff --git a/src/pages/edit/Editor/store/slices/core/index.ts b/src/pages/edit/Editor/store/slices/core/index.ts
index ada2720..243d3da 100644
--- a/src/pages/edit/Editor/store/slices/core/index.ts
+++ b/src/pages/edit/Editor/store/slices/core/index.ts
@@ -11,7 +11,11 @@ import type {
// import type { CCComponentId } from "../../../../../../store/component";
import simulateComponent from "../../../../../../store/simulation";
import type { ComponentEditorSliceCreator } from "../../types";
-import type { EditorStoreCoreSlice, InputValueKey } from "./types";
+import {
+ type EditorStoreCoreSlice,
+ type InputValueKey,
+ serializeInputValueKey,
+} from "./types";
export function stringifySimulationValue(value: SimulationValue): string {
const binary = value.map((v) => (v ? "1" : "0")).join("");
@@ -44,30 +48,39 @@ export const createComponentEditorStoreCoreSlice: ComponentEditorSliceCreator<
/** @private */
inputValues: new Map(),
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(
- inputValueKey[0],
- );
- if (bitWidthStatus.isFixed) {
- const newValue = new Array(bitWidthStatus.bitWidth).fill(false);
- return newValue;
- }
- if (bitWidthStatus.fixMode === "manual") {
- throw new Error("Cannot determine bit width");
- }
- const newValue = [false];
- return newValue;
+ // If value exists for the current time step, return it
+ const value = get().inputValues.get(
+ serializeInputValueKey(inputValueKey),
+ );
+ if (value) return value;
+
+ // If not, try to find the value from the previous time step
+ const previousTimeStepValue = get().inputValues.get(
+ serializeInputValueKey({
+ ...inputValueKey,
+ timeStep: inputValueKey.timeStep - 1,
+ }),
+ );
+ if (previousTimeStepValue) {
+ get().setInputValue(inputValueKey, previousTimeStepValue);
+ return previousTimeStepValue;
}
- return value;
+
+ // If not found, initialize the value based on the bit width of the pin
+ const componentPin = nullthrows(
+ store.componentPins.get(inputValueKey.componentPinId),
+ );
+ const bitWidthStatus = store.nodePins.getNodePinBitWidthStatus(
+ nullthrows(
+ componentPin.implementation,
+ "Cannot get input value for intrinsic component pin",
+ ),
+ );
+ return bitWidthStatus.isFixed
+ ? // If the bit width is fixed, initialize the value with the specified bit width
+ new Array(bitWidthStatus.bitWidth).fill(false)
+ : // If the bit width is not fixed, initialize the single-bit value as false
+ [false];
},
setInputValue(inputValueKey: InputValueKey, value: SimulationValue) {
set((state) => {
@@ -178,7 +191,7 @@ export const createComponentEditorStoreCoreSlice: ComponentEditorSliceCreator<
if (pin.type === "input") {
inputValues.set(
pin.id,
- editorState.getInputValue([pin.id, timeStep]),
+ editorState.getInputValue({ componentPinId: pin.id, timeStep }),
);
}
}
@@ -191,11 +204,6 @@ export const createComponentEditorStoreCoreSlice: ComponentEditorSliceCreator<
}
if (isUpdated) editorStore.setState((s) => ({ ...s }));
};
- store.nodes.on("didRegister", executeSimulation);
- store.nodes.on("didUpdate", executeSimulation);
- store.nodes.on("didUnregister", executeSimulation);
- store.connections.on("didRegister", executeSimulation);
- store.connections.on("didUnregister", executeSimulation);
editorStore.subscribe(executeSimulation);
},
};
diff --git a/src/pages/edit/Editor/store/slices/core/types.ts b/src/pages/edit/Editor/store/slices/core/types.ts
index bf8916b..5f88bb7 100644
--- a/src/pages/edit/Editor/store/slices/core/types.ts
+++ b/src/pages/edit/Editor/store/slices/core/types.ts
@@ -13,7 +13,13 @@ export type RangeSelect = { start: Vector2; end: Vector2 } | null;
export type TimeStep = number;
-export type InputValueKey = [CCComponentPinId, TimeStep];
+export type InputValueKey = {
+ componentPinId: CCComponentPinId;
+ timeStep: TimeStep;
+};
+export function serializeInputValueKey(key: InputValueKey): string {
+ return `${key.componentPinId}:${key.timeStep}`;
+}
export type NodePinPropertyEditorTarget = {
nodeId: CCNodeId;
diff --git a/src/store/component.ts b/src/store/component.ts
index b2835e5..fc1bf11 100644
--- a/src/store/component.ts
+++ b/src/store/component.ts
@@ -219,3 +219,28 @@ export function validateAllComponents(store: CCStore) {
validateComponent(store, component.id);
}
}
+
+export function isEachInputPinConnected(
+ store: CCStore,
+ componentId: CCComponentId,
+) {
+ const component = nullthrows(store.components.get(componentId));
+ if (component.intrinsicType) return true;
+ const nodes = store.nodes.getManyByParentComponentId(componentId);
+ for (const node of nodes) {
+ const nodePins = store.nodePins.getManyByNodeId(node.id);
+ for (const nodePin of nodePins) {
+ const componentPin = nullthrows(
+ store.componentPins.get(nodePin.componentPinId),
+ );
+ if (componentPin.type === "input") {
+ const connectionsAssociatedWithNodePin =
+ store.connections.getConnectionsByNodePinId(nodePin.id);
+ if (connectionsAssociatedWithNodePin.length === 0) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
diff --git a/src/store/componentPin.ts b/src/store/componentPin.ts
index 0376f4d..fc7f692 100644
--- a/src/store/componentPin.ts
+++ b/src/store/componentPin.ts
@@ -208,6 +208,7 @@ export class CCComponentPinStore extends EventEmitter
* Get the bit width status of a component pin
* @param pinId id of pin
* @returns bit width status of the pin
+ * @deprecated use getNodePinBitWidthStatus instead
*/
getComponentPinBitWidthStatus(
pinId: CCComponentPinId,
diff --git a/src/store/connection.ts b/src/store/connection.ts
index 938e59a..4d0d332 100644
--- a/src/store/connection.ts
+++ b/src/store/connection.ts
@@ -61,6 +61,35 @@ export class CCConnectionStore extends EventEmitter {
this.unregister(connections.map((connection) => connection.id));
}
});
+ this.#store.connections.on("didRegister", (connection) => {
+ const component = nullthrows(
+ this.#store.components.get(connection.parentComponentId),
+ );
+ const nodes = this.#store.nodes.getManyByComponentId(component.id);
+ for (const node of nodes) {
+ const nodePins = this.#store.nodePins.getManyByNodeId(node.id);
+ for (const nodePin of nodePins) {
+ const connections = this.getConnectionsByNodePinId(nodePin.id);
+ for (const connection of connections) {
+ const parentComponentId = connection.parentComponentId;
+ const from = connection.from;
+ const to = connection.to;
+ this.unregister([connection.id]).then(() => {
+ if (this.#store.nodePins.isConnectable(from, to)) {
+ this.register(
+ CCConnectionStore.create({
+ from,
+ to,
+ parentComponentId,
+ bentPortion: 0.5,
+ }),
+ );
+ }
+ });
+ }
+ }
+ }
+ });
}
/**
diff --git a/src/store/nodePin.ts b/src/store/nodePin.ts
index 7a1d91a..cf239b1 100644
--- a/src/store/nodePin.ts
+++ b/src/store/nodePin.ts
@@ -37,6 +37,12 @@ export class CCNodePinStore extends EventEmitter {
#markedAsDeleted: Set = new Set();
+ #bitWidthCache: Map = new Map();
+
+ #clearBitWidthCache(): void {
+ this.#bitWidthCache.clear();
+ }
+
/**
* Constructor of CCNodePinStore
* @param store store
@@ -54,6 +60,10 @@ export class CCNodePinStore extends EventEmitter {
}
mount() {
+ this.#store.connections.on("didRegister", () => this.#clearBitWidthCache());
+ this.#store.connections.on("didUnregister", () =>
+ this.#clearBitWidthCache(),
+ );
this.#store.nodes.on("didRegister", (node) => {
const componentPins = this.#store.componentPins.getManyByComponentId(
node.componentId,
@@ -105,6 +115,7 @@ export class CCNodePinStore extends EventEmitter {
invariant(this.#store.componentPins.get(nodePin.componentPinId));
invariant(this.#store.nodes.get(nodePin.nodeId));
this.#nodePins.set(nodePin.id, nodePin);
+ this.#clearBitWidthCache();
this.emit("didRegister", nodePin);
}
@@ -119,6 +130,7 @@ export class CCNodePinStore extends EventEmitter {
this.emit("willUnregister", nodePin);
this.#nodePins.delete(nodePin.id);
});
+ this.#clearBitWidthCache();
this.emit("didUnregister", nodePin);
this.#markedAsDeleted.delete(id);
}
@@ -127,6 +139,7 @@ export class CCNodePinStore extends EventEmitter {
const existingNodePin = nullthrows(this.#nodePins.get(id));
const newNodePin = { ...existingNodePin, ...value };
this.#nodePins.set(id, newNodePin);
+ this.#clearBitWidthCache();
this.emit("didUpdate", newNodePin);
}
@@ -181,6 +194,8 @@ export class CCNodePinStore extends EventEmitter {
* @returns bit width status of the pin
*/
getNodePinBitWidthStatus(nodePinId: CCNodePinId): CCNodePinBitWidthStatus {
+ const cached = this.#bitWidthCache.get(nodePinId);
+ if (cached) return cached;
const traverseNodePinBitWidthStatus = (
targetNodePinId: CCNodePinId,
seen: Set,
@@ -294,7 +309,9 @@ export class CCNodePinStore extends EventEmitter {
}
return givenComponentPinBitWidthStatus;
};
- return traverseNodePinBitWidthStatus(nodePinId, new Set());
+ const result = traverseNodePinBitWidthStatus(nodePinId, new Set());
+ this.#bitWidthCache.set(nodePinId, result);
+ return result;
}
isMarkedAsDeleted(id: CCNodePinId) {
diff --git a/src/store/react/selectors.ts b/src/store/react/selectors.ts
index b6fc032..8ab7ac8 100644
--- a/src/store/react/selectors.ts
+++ b/src/store/react/selectors.ts
@@ -1,7 +1,11 @@
import memoizeOne from "memoize-one";
import nullthrows from "nullthrows";
import { useCallback, useMemo, useSyncExternalStore } from "react";
-import type { CCComponent, CCComponentId } from "../component";
+import {
+ type CCComponent,
+ type CCComponentId,
+ isEachInputPinConnected,
+} from "../component";
import type { CCComponentPin } from "../componentPin";
import type { CCNode, CCNodeId } from "../node";
import type { CCNodePin } from "../nodePin";
@@ -185,3 +189,37 @@ export function useNodePins(nodeId: CCNodeId) {
);
return useSyncExternalStore(subscribe, getSnapshot);
}
+
+export function useCanSimulate(componentId: CCComponentId) {
+ const { store } = useStore();
+ const getSnapshot = useCallback(
+ () => isEachInputPinConnected(store, componentId),
+ [store, componentId],
+ );
+ const subscribe = useCallback(
+ (onStoreChange: () => void) => {
+ store.nodes.on("didRegister", onStoreChange);
+ store.nodes.on("didUnregister", onStoreChange);
+ store.nodePins.on("didRegister", onStoreChange);
+ store.nodePins.on("didUnregister", onStoreChange);
+ store.componentPins.on("didRegister", onStoreChange);
+ store.componentPins.on("didUpdate", onStoreChange);
+ store.componentPins.on("didUnregister", onStoreChange);
+ store.connections.on("didRegister", onStoreChange);
+ store.connections.on("didUnregister", onStoreChange);
+ return () => {
+ store.nodes.off("didRegister", onStoreChange);
+ store.nodes.off("didUnregister", onStoreChange);
+ store.nodePins.off("didRegister", onStoreChange);
+ store.nodePins.off("didUnregister", onStoreChange);
+ store.componentPins.off("didRegister", onStoreChange);
+ store.componentPins.off("didUpdate", onStoreChange);
+ store.componentPins.off("didUnregister", onStoreChange);
+ store.connections.off("didRegister", onStoreChange);
+ store.connections.off("didUnregister", onStoreChange);
+ };
+ },
+ [store],
+ );
+ return useSyncExternalStore(subscribe, getSnapshot);
+}