Replace setInterval with requestAnimationFrame loop that tracks elapsed time via performance.now(). Clock ticks now fire based on real time rather than assuming perfect interval spacing, so user interactions (toggling inputs, dragging gates) no longer cause the clock to stutter or pause. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
76 lines
2.1 KiB
JavaScript
76 lines
2.1 KiB
JavaScript
// Clock simulation engine — uses real timestamps for consistent timing
|
|
import { state } from './state.js';
|
|
import { evaluateAll } from './gates.js';
|
|
import { forceRecordSample } from './waveform.js';
|
|
|
|
let lastTickTime = 0;
|
|
let animFrameId = null;
|
|
|
|
function simLoop(now) {
|
|
if (!state.simRunning) return;
|
|
|
|
// Fire ticks that are due based on real elapsed time
|
|
while (now - lastTickTime >= state.simSpeed) {
|
|
lastTickTime += state.simSpeed;
|
|
|
|
// Toggle all CLOCK gates
|
|
state.gates.forEach(g => {
|
|
if (g.type === 'CLOCK') {
|
|
g.value = g.value ? 0 : 1;
|
|
}
|
|
});
|
|
evaluateAll();
|
|
if (state.recording && state.waveformVisible) {
|
|
forceRecordSample();
|
|
}
|
|
}
|
|
|
|
animFrameId = requestAnimationFrame(simLoop);
|
|
}
|
|
|
|
export function startSim() {
|
|
if (state.simRunning) return;
|
|
const hasClocks = state.gates.some(g => g.type === 'CLOCK');
|
|
if (!hasClocks) return;
|
|
|
|
state.simRunning = true;
|
|
|
|
// Auto-open waveform panel
|
|
if (!state.waveformVisible) {
|
|
state.waveformVisible = true;
|
|
document.getElementById('waveform-panel').classList.add('visible');
|
|
document.getElementById('sim-btn').classList.add('active');
|
|
window.dispatchEvent(new Event('resize'));
|
|
}
|
|
|
|
lastTickTime = performance.now();
|
|
animFrameId = requestAnimationFrame(simLoop);
|
|
updateSimUI();
|
|
}
|
|
|
|
export function stopSim() {
|
|
state.simRunning = false;
|
|
if (animFrameId) cancelAnimationFrame(animFrameId);
|
|
animFrameId = null;
|
|
updateSimUI();
|
|
}
|
|
|
|
export function updateSimUI() {
|
|
const btn = document.getElementById('sim-run-btn');
|
|
if (state.simRunning) {
|
|
btn.textContent = '⏹ Stop';
|
|
btn.classList.add('active');
|
|
} else {
|
|
btn.textContent = '▶ Run';
|
|
btn.classList.remove('active');
|
|
}
|
|
document.getElementById('sim-speed-label').textContent = `${state.simSpeed}ms`;
|
|
}
|
|
|
|
export function adjustSpeed(delta) {
|
|
const wasRunning = state.simRunning;
|
|
state.simSpeed = Math.max(50, Math.min(2000, state.simSpeed + delta));
|
|
// No need to restart — the loop reads simSpeed dynamically
|
|
updateSimUI();
|
|
}
|