feat: port labels on component gates + persistent internal state
Show input/output labels next to ports on custom component chips, and persist internal gate state between evaluations so latches and flip-flops retain their values correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -53,6 +53,8 @@ export function saveComponentFromCircuit(name) {
|
|||||||
/**
|
/**
|
||||||
* Evaluate a component instance.
|
* Evaluate a component instance.
|
||||||
* Simulates the internal circuit and returns an array of output values.
|
* Simulates the internal circuit and returns an array of output values.
|
||||||
|
* IMPORTANT: Uses persistent internal state so latches/flip-flops retain
|
||||||
|
* their values between evaluations (just like the main circuit).
|
||||||
*/
|
*/
|
||||||
export function evaluateComponent(gate, inputs) {
|
export function evaluateComponent(gate, inputs) {
|
||||||
if (!gate.component) {
|
if (!gate.component) {
|
||||||
@@ -62,8 +64,11 @@ export function evaluateComponent(gate, inputs) {
|
|||||||
|
|
||||||
const comp = gate.component;
|
const comp = gate.component;
|
||||||
|
|
||||||
// Deep clone internal circuit for simulation
|
// Persist internal gate state on the gate instance so latches hold their value
|
||||||
const internalGates = JSON.parse(JSON.stringify(comp.gates));
|
if (!gate._internalGates) {
|
||||||
|
gate._internalGates = JSON.parse(JSON.stringify(comp.gates));
|
||||||
|
}
|
||||||
|
const internalGates = gate._internalGates;
|
||||||
const internalConns = comp.connections; // read-only, no need to clone
|
const internalConns = comp.connections; // read-only, no need to clone
|
||||||
|
|
||||||
// Map external inputs to internal INPUT gates using stored inputIds
|
// Map external inputs to internal INPUT gates using stored inputIds
|
||||||
@@ -133,7 +138,8 @@ export function evaluateComponent(gate, inputs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[component] eval "${comp.name}" inputs=[${inputs}] → outputs=[${outputs}]`);
|
console.log(`[component] eval "${comp.name}" inputs=[${inputs}] → outputs=[${outputs}]`,
|
||||||
|
`(internal state preserved: ${gate._internalGates ? 'yes' : 'no'})`);
|
||||||
return outputs;
|
return outputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,23 @@ function drawComponentGate(gate) {
|
|||||||
ctx.fillStyle = '#444';
|
ctx.fillStyle = '#444';
|
||||||
ctx.fillText(getGateLabel(gate), gate.x + w / 2, gate.y + h - 6);
|
ctx.fillText(getGateLabel(gate), gate.x + w / 2, gate.y + h - 6);
|
||||||
|
|
||||||
|
// Get port labels from the component definition
|
||||||
|
const comp = gate.component;
|
||||||
|
const inputLabels = [];
|
||||||
|
const outputLabels = [];
|
||||||
|
if (comp) {
|
||||||
|
const inputIds = comp.inputIds || [];
|
||||||
|
const outputIds = comp.outputIds || [];
|
||||||
|
for (const id of inputIds) {
|
||||||
|
const g = comp.gates.find(g => g.id === id);
|
||||||
|
inputLabels.push(g?.label || '');
|
||||||
|
}
|
||||||
|
for (const id of outputIds) {
|
||||||
|
const g = comp.gates.find(g => g.id === id);
|
||||||
|
outputLabels.push(g?.label || '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Input ports
|
// Input ports
|
||||||
getInputPorts(gate).forEach(p => {
|
getInputPorts(gate).forEach(p => {
|
||||||
const isPortHovered = state.hoveredPort &&
|
const isPortHovered = state.hoveredPort &&
|
||||||
@@ -178,6 +195,16 @@ function drawComponentGate(gate) {
|
|||||||
ctx.strokeStyle = isPortHovered ? '#fff' : '#555';
|
ctx.strokeStyle = isPortHovered ? '#fff' : '#555';
|
||||||
ctx.lineWidth = 1.5;
|
ctx.lineWidth = 1.5;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Port label (inside the gate, to the right of the port)
|
||||||
|
const label = inputLabels[p.index];
|
||||||
|
if (label) {
|
||||||
|
ctx.font = '9px "Segoe UI", system-ui, sans-serif';
|
||||||
|
ctx.fillStyle = '#aaa';
|
||||||
|
ctx.textAlign = 'left';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillText(label, p.x + PORT_R + 4, p.y);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Output ports
|
// Output ports
|
||||||
@@ -195,6 +222,16 @@ function drawComponentGate(gate) {
|
|||||||
ctx.strokeStyle = isPortHovered ? '#fff' : '#555';
|
ctx.strokeStyle = isPortHovered ? '#fff' : '#555';
|
||||||
ctx.lineWidth = 1.5;
|
ctx.lineWidth = 1.5;
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
|
// Port label (inside the gate, to the left of the port)
|
||||||
|
const label = outputLabels[p.index];
|
||||||
|
if (label) {
|
||||||
|
ctx.font = '9px "Segoe UI", system-ui, sans-serif';
|
||||||
|
ctx.fillStyle = '#aaa';
|
||||||
|
ctx.textAlign = 'right';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillText(label, p.x - PORT_R - 4, p.y);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user