diff --git a/src/index.html.ejs b/src/index.html.ejs index a108dbe..e971be0 100644 --- a/src/index.html.ejs +++ b/src/index.html.ejs @@ -18,6 +18,7 @@ can input registers and memory as hex, unsigned decimal, or signed decimal by using the dropdowns in the editors. Use the "Examples" dropdown to select a pre-made assembly program to run. While the demo is running, you can use the side pane to view the current memory and registers. Labels and tooltips on the diagram will show the state of each wire and component. + You can use the ebreak instruction to insert a breakpoint in the simulation.

You can view the source or contribute on GitHub. diff --git a/src/ui/SimulatorUI.tsx b/src/ui/SimulatorUI.tsx index 6457d20..8fe64ad 100644 --- a/src/ui/SimulatorUI.tsx +++ b/src/ui/SimulatorUI.tsx @@ -119,23 +119,31 @@ export default function SimulatorUI() { let nextState = state if (nextState == "unstarted") { // try to start if we are unstarted const started = start() // try to start, updates state to paused if success - if (started) nextState = (mode == "play" ? 'playing' : 'paused') + if (started) { + nextState = (mode == "play" ? 'playing' : 'paused') + } } if (["paused", "playing"].includes(nextState)) { // don't do anything if we are "done" or if start failed try { // step the simulation `count` times. We step multiple at once when playing at full speed to avoid rendering each tick - const done = updateSim(sim => { + nextState = updateSim(sim => { for (let i = 0; i < count && !sim.isDone(); i++) { sim.tick() + if (sim.isBreak()) break + } + + if (sim.isDone()) { + return "done" + } else if (sim.isBreak()) { + clearPlayInterval() + return "paused" + } else if (mode == "play") { + return "playing" + } else { + return "paused" } - return sim.isDone() }) - if (done) { - nextState = "done" - } else { - nextState = (mode == "play" ? 'playing' : 'paused') - } } catch (e: any) { // this shouldn't happen. nextState = "done" error(`Error in simulation:\n${e.message}`) @@ -190,7 +198,9 @@ export default function SimulatorUI() { link.remove(); } - useInterval(() => step("play", speed == 0 ? 1000 : 1), (state == "playing") ? speed : null) + const clearPlayInterval = useInterval(() => { + step("play", speed == 0 ? 1000 : 1) + }, (state == "playing") ? speed : null) return (

diff --git a/src/ui/reactUtils.ts b/src/ui/reactUtils.ts index b267ea4..9dab7a9 100644 --- a/src/ui/reactUtils.ts +++ b/src/ui/reactUtils.ts @@ -34,9 +34,13 @@ export function getStyleProps(props: StyleProps, defaults: StyleProps = {}): Sty * React hook version of useInterval. Handles a changing callback or interval. * @param func The function to call every interval * @param delay The interval in milliseconds. Set to null to disable interval. + * + * Returns a function that can be used to cancel the timeout explicitly, which can be useful + * if you need make sure the interval doesn't trigger again before the next react re-render. */ export function useInterval(callback: () => void, delay: number|null) { const savedCallback = useRef(callback); + const intervalId = useRef() useEffect(() => { savedCallback.current = callback; @@ -45,7 +49,10 @@ export function useInterval(callback: () => void, delay: number|null) { useEffect(() => { if (delay !== null) { const id = setInterval(() => savedCallback.current(), delay); + intervalId.current = id; return () => clearInterval(id); } }, [delay]); + + return () => { clearInterval(intervalId.current) }; }