diff --git a/src/components/Panels/PropertiesPanel/ModuleProperty/ProjectProperty/ProjectProperty.vue b/src/components/Panels/PropertiesPanel/ModuleProperty/ProjectProperty/ProjectProperty.vue index 8c2dd05d1..eae88c7c5 100644 --- a/src/components/Panels/PropertiesPanel/ModuleProperty/ProjectProperty/ProjectProperty.vue +++ b/src/components/Panels/PropertiesPanel/ModuleProperty/ProjectProperty/ProjectProperty.vue @@ -259,4 +259,5 @@ type SimulatorStateType = { } - + + diff --git a/src/components/Panels/Shared/InputGroups.vue b/src/components/Panels/Shared/InputGroups.vue index 42cbf6db9..0f00e6133 100644 --- a/src/components/Panels/Shared/InputGroups.vue +++ b/src/components/Panels/Shared/InputGroups.vue @@ -57,7 +57,6 @@ function increaseValue() { step = isNaN(step) ? 1 : step if (value + step <= props.valueMax) value = value + step else return - props.propertyValue = value ele.value = value // manually triggering on change event const e = new Event('change') @@ -72,7 +71,6 @@ function decreaseValue() { step = isNaN(step) ? 1 : step if (value - step >= props.valueMin) value = value - step else return - props.propertyValue = value ele.value = value // manually triggering on change event const e = new Event('change') diff --git a/src/simulator/src/data/load.js b/src/simulator/src/data/load.js index 1bc51e65a..d3426affe 100644 --- a/src/simulator/src/data/load.js +++ b/src/simulator/src/data/load.js @@ -84,25 +84,11 @@ function loadModule(data, scope) { } /** - * This function shouldn't ideally exist. But temporary fix - * for some issues while loading nodes. + * @deprecated This function has been removed as the root cause has been fixed. + * Nodes are now loaded correctly and orphaned nodes are cleaned up properly. + * The removeBugNodes() workaround is no longer needed. * @category data */ -function removeBugNodes(scope = globalScope) { - let x = scope.allNodes.length - for (let i = 0; i < x; i++) { - if ( - scope.allNodes[i].type !== 2 && - scope.allNodes[i].parent.objectType === 'CircuitElement' - ) { - scope.allNodes[i].delete() - } - if (scope.allNodes.length !== x) { - i = 0 - x = scope.allNodes.length - } - } -} /** * Function to load a full circuit @@ -114,7 +100,7 @@ export function loadScope(scope, data) { const ML = moduleList.slice() // Module List copy scope.restrictedCircuitElementsUsed = data.restrictedCircuitElementsUsed - // Load all nodes + // Load all nodes first (required for replace() to work with correct indices) data.allNodes.map((x) => loadNode(x, scope)) // Make all connections @@ -137,11 +123,20 @@ export function loadScope(scope, data) { } } } + + // Clean up orphaned input/output nodes that weren't properly replaced + // Delete inline during reverse iteration to avoid index staling (delete() reassigns scope.allNodes) + for (let i = scope.allNodes.length - 1; i >= 0; i--) { + const node = scope.allNodes[i] + if (node.type !== 2 && node.parent === scope.root) { + node.delete() + } + } + // Update wires according scope.wires.map((x) => { x.updateData(scope) }) - removeBugNodes(scope) // To be deprecated // If Verilog Circuit Metadata exists, then restore if (data.verilogMetadata) { diff --git a/src/simulator/src/ux.js b/src/simulator/src/ux.js index 821620639..504c31b5c 100644 --- a/src/simulator/src/ux.js +++ b/src/simulator/src/ux.js @@ -46,6 +46,11 @@ var ctxPos = { } let isFullViewActive = false let prevMobileState = null + +// Debounce timers for clock properties to prevent excessive updates +let clockTimeDebounceTimer = null +let clockEnableDebounceTimer = null +const DEBOUNCE_DELAY = 300 // milliseconds // FUNCTION TO SHOW AND HIDE CONTEXT MENU function hideContextMenu() { var el = document.getElementById('contextMenu') @@ -184,6 +189,33 @@ function checkValidBitWidth() { } export function objectPropertyAttributeUpdate() { + const propertyName = this.name + + // Special handling for clock time - debounce and skip heavy updates + // Clock time changes don't require canvas resets, only interval updates + if (propertyName === 'changeClockTime') { + if (clockTimeDebounceTimer) { + clearTimeout(clockTimeDebounceTimer) + } + + clockTimeDebounceTimer = setTimeout(() => { + checkValidBitWidth() + let { value } = this + if (this.type === 'number') { + value = parseFloat(value) + } + // Only update clock time, skip canvas updates to prevent crashes + if (simulationArea.lastSelected && simulationArea.lastSelected[propertyName]) { + simulationArea.lastSelected[propertyName](value) + } else { + circuitProperty[propertyName](value) + } + clockTimeDebounceTimer = null + }, DEBOUNCE_DELAY) + return + } + + // Default behavior for other properties checkValidBitWidth() scheduleUpdate() updateCanvasSet(true) @@ -200,7 +232,31 @@ export function objectPropertyAttributeUpdate() { } export function objectPropertyAttributeCheckedUpdate() { - if (this.name === 'toggleLabelInLayoutMode') return // Hack to prevent toggleLabelInLayoutMode from toggling twice + const propertyName = this.name + + // Hack to prevent toggleLabelInLayoutMode from toggling twice + if (propertyName === 'toggleLabelInLayoutMode') return + + // Special handling for clock enable - debounce and skip heavy updates + // Clock enable changes don't require canvas resets, only state updates + if (propertyName === 'changeClockEnable') { + if (clockEnableDebounceTimer) { + clearTimeout(clockEnableDebounceTimer) + } + + clockEnableDebounceTimer = setTimeout(() => { + // Only update clock enable, skip canvas updates to prevent crashes + if (simulationArea.lastSelected && simulationArea.lastSelected[propertyName]) { + simulationArea.lastSelected[propertyName](this.checked) + } else { + circuitProperty[propertyName](this.checked) + } + clockEnableDebounceTimer = null + }, DEBOUNCE_DELAY) + return + } + + // Default behavior for other properties scheduleUpdate() updateCanvasSet(true) wireToBeCheckedSet(1) diff --git a/v1/src/components/Panels/PropertiesPanel/ModuleProperty/ProjectProperty/ProjectProperty.vue b/v1/src/components/Panels/PropertiesPanel/ModuleProperty/ProjectProperty/ProjectProperty.vue index 8c2dd05d1..eae88c7c5 100644 --- a/v1/src/components/Panels/PropertiesPanel/ModuleProperty/ProjectProperty/ProjectProperty.vue +++ b/v1/src/components/Panels/PropertiesPanel/ModuleProperty/ProjectProperty/ProjectProperty.vue @@ -259,4 +259,5 @@ type SimulatorStateType = { } - + + diff --git a/v1/src/components/Panels/Shared/InputGroups.vue b/v1/src/components/Panels/Shared/InputGroups.vue index 42cbf6db9..0f00e6133 100644 --- a/v1/src/components/Panels/Shared/InputGroups.vue +++ b/v1/src/components/Panels/Shared/InputGroups.vue @@ -57,7 +57,6 @@ function increaseValue() { step = isNaN(step) ? 1 : step if (value + step <= props.valueMax) value = value + step else return - props.propertyValue = value ele.value = value // manually triggering on change event const e = new Event('change') @@ -72,7 +71,6 @@ function decreaseValue() { step = isNaN(step) ? 1 : step if (value - step >= props.valueMin) value = value - step else return - props.propertyValue = value ele.value = value // manually triggering on change event const e = new Event('change') diff --git a/v1/src/simulator/src/data/load.js b/v1/src/simulator/src/data/load.js index 1bc51e65a..dfec5d99e 100644 --- a/v1/src/simulator/src/data/load.js +++ b/v1/src/simulator/src/data/load.js @@ -84,25 +84,10 @@ function loadModule(data, scope) { } /** - * This function shouldn't ideally exist. But temporary fix - * for some issues while loading nodes. + * @deprecated This function has been removed as the root cause has been fixed. + * Nodes are now loaded correctly and orphaned nodes are cleaned up properly. * @category data */ -function removeBugNodes(scope = globalScope) { - let x = scope.allNodes.length - for (let i = 0; i < x; i++) { - if ( - scope.allNodes[i].type !== 2 && - scope.allNodes[i].parent.objectType === 'CircuitElement' - ) { - scope.allNodes[i].delete() - } - if (scope.allNodes.length !== x) { - i = 0 - x = scope.allNodes.length - } - } -} /** * Function to load a full circuit @@ -137,11 +122,19 @@ export function loadScope(scope, data) { } } } + // Clean up orphaned input/output nodes that weren't properly replaced + // Delete inline during reverse iteration to avoid index staling (delete() reassigns scope.allNodes) + for (let i = scope.allNodes.length - 1; i >= 0; i--) { + const node = scope.allNodes[i] + if (node.type !== 2 && node.parent === scope.root) { + node.delete() + } + } + // Update wires according scope.wires.map((x) => { x.updateData(scope) }) - removeBugNodes(scope) // To be deprecated // If Verilog Circuit Metadata exists, then restore if (data.verilogMetadata) { diff --git a/v1/src/simulator/src/ux.js b/v1/src/simulator/src/ux.js index 0d7a204bb..1930de8ab 100644 --- a/v1/src/simulator/src/ux.js +++ b/v1/src/simulator/src/ux.js @@ -45,7 +45,12 @@ var ctxPos = { visible: false, } let isFullViewActive = false -let prevMobileState = null +let prevMobileState = null + +// Debounce timers for clock properties to prevent excessive updates +let clockTimeDebounceTimer = null +let clockEnableDebounceTimer = null +const DEBOUNCE_DELAY = 300 // milliseconds // FUNCTION TO SHOW AND HIDE CONTEXT MENU function hideContextMenu() { var el = document.getElementById('contextMenu') @@ -184,6 +189,33 @@ function checkValidBitWidth() { } export function objectPropertyAttributeUpdate() { + const propertyName = this.name + + // Special handling for clock time - debounce and skip heavy updates + // Clock time changes don't require canvas resets, only interval updates + if (propertyName === 'changeClockTime') { + if (clockTimeDebounceTimer) { + clearTimeout(clockTimeDebounceTimer) + } + + clockTimeDebounceTimer = setTimeout(() => { + checkValidBitWidth() + let { value } = this + if (this.type === 'number') { + value = parseFloat(value) + } + // Only update clock time, skip canvas updates to prevent crashes + if (simulationArea.lastSelected && simulationArea.lastSelected[propertyName]) { + simulationArea.lastSelected[propertyName](value) + } else { + circuitProperty[propertyName](value) + } + clockTimeDebounceTimer = null + }, DEBOUNCE_DELAY) + return + } + + // Default behavior for other properties checkValidBitWidth() scheduleUpdate() updateCanvasSet(true) @@ -200,7 +232,31 @@ export function objectPropertyAttributeUpdate() { } export function objectPropertyAttributeCheckedUpdate() { - if (this.name === 'toggleLabelInLayoutMode') return // Hack to prevent toggleLabelInLayoutMode from toggling twice + const propertyName = this.name + + // Hack to prevent toggleLabelInLayoutMode from toggling twice + if (propertyName === 'toggleLabelInLayoutMode') return + + // Special handling for clock enable - debounce and skip heavy updates + // Clock enable changes don't require canvas resets, only state updates + if (propertyName === 'changeClockEnable') { + if (clockEnableDebounceTimer) { + clearTimeout(clockEnableDebounceTimer) + } + + clockEnableDebounceTimer = setTimeout(() => { + // Only update clock enable, skip canvas updates to prevent crashes + if (simulationArea.lastSelected && simulationArea.lastSelected[propertyName]) { + simulationArea.lastSelected[propertyName](this.checked) + } else { + circuitProperty[propertyName](this.checked) + } + clockEnableDebounceTimer = null + }, DEBOUNCE_DELAY) + return + } + + // Default behavior for other properties scheduleUpdate() updateCanvasSet(true) wireToBeCheckedSet(1)