diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c3e07b9e26049c73f5bf89adbf83f93f334083e..52fd01b0c3c11db58b471c06a2b08838ac23064c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -* +* Logic frequency control using on click UI and keyboard ### Changed diff --git a/examples/web/index.ts b/examples/web/index.ts index c055b7b6bac5e86ac02b69ad9bad79d4671fc727..c9222e5f52e5e5bd41db6e996ebdea4e8b64c0e5 100644 --- a/examples/web/index.ts +++ b/examples/web/index.ts @@ -21,7 +21,7 @@ const LOGIC_HZ = 4194304; const VISUAL_HZ = 59.7275; const IDLE_HZ = 10; -const FREQUENCY_DELTA = 350000; +const FREQUENCY_DELTA = 400000; const SAMPLE_RATE = 2; @@ -344,11 +344,11 @@ class GameboyEmulator extends EmulatorBase implements Emulator { switch (event.key) { case "+": - this.logicFrequency += FREQUENCY_DELTA; + this.frequency += FREQUENCY_DELTA; break; case "-": - this.logicFrequency -= FREQUENCY_DELTA; + this.frequency -= FREQUENCY_DELTA; break; } }); @@ -430,6 +430,12 @@ class GameboyEmulator extends EmulatorBase implements Emulator { return this.logicFrequency; } + set frequency(value: number) { + value = Math.max(value, 0); + this.logicFrequency = value; + this.trigger("frequency", value); + } + get framerate(): number { return this.fps; } diff --git a/examples/web/react/app.tsx b/examples/web/react/app.tsx index 57d3508fda6aeddadab11aab85c9f4de8b9bc860..b3c190b0b1cab2ecc8f4d5e570d0e3a07f9ef082 100644 --- a/examples/web/react/app.tsx +++ b/examples/web/react/app.tsx @@ -28,7 +28,7 @@ import { import "./app.css"; -export type Callback<T> = (owner: T, params?: Record<string, any>) => void; +export type Callback<T> = (owner: T, params?: any) => void; /** * Abstract class that implements the basic functionality @@ -54,7 +54,7 @@ export class Observable { this.events[event] = callbacks; } - trigger(event: string, params?: Record<string, any>) { + trigger(event: string, params?: any) { const callbacks = this.events[event] ?? []; callbacks.forEach((c) => c(this, params)); } @@ -123,7 +123,7 @@ export interface Emulator extends ObservableI { * The name of the current execution engine being used * by the emulator. */ - get engine(): string; + get engine(): string | null; /** * The pixel format of the emulator's display @@ -152,6 +152,7 @@ export interface Emulator extends ObservableI { * should impact other elements of the emulator. */ get frequency(): number; + set frequency(value: number); /** * The current logic framerate of the running emulator. @@ -410,6 +411,14 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => { `${emulator.device} running in engine "${engine}" from now on!` ); }; + const onFrequencyChange = (value: number) => { + emulator.frequency = value * 1000 * 1000; + }; + const onFrequencyReady = (handler: (value: number) => void) => { + emulator.bind("frequency", (emulator: Emulator, frequency: number) => { + handler(frequency / 1000000); + }); + }; const onMinimize = () => { setFullscreen(!fullscreen); }; @@ -558,11 +567,13 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => { name={"CPU Frequency"} valueNode={ <ButtonIncrement - value={4.19} - delta={0.1} + value={emulator.frequency / 1000 / 1000} + delta={0.4} min={0} suffix={"MHz"} decimalPlaces={2} + onChange={onFrequencyChange} + onReady={onFrequencyReady} /> } /> diff --git a/examples/web/react/components/button-increment/button-increment.tsx b/examples/web/react/components/button-increment/button-increment.tsx index 62a9071ad02633bc663095c388215871d660fc24..f80609384b0773bcebe46c63187b363725261184 100644 --- a/examples/web/react/components/button-increment/button-increment.tsx +++ b/examples/web/react/components/button-increment/button-increment.tsx @@ -1,4 +1,4 @@ -import React, { FC, useState } from "react"; +import React, { FC, useEffect, useState } from "react"; import Button from "../button/button"; import "./button-increment.css"; @@ -16,6 +16,7 @@ type ButtonIncrementProps = { onClick?: () => void; onBeforeChange?: (value: number) => boolean; onChange?: (value: number) => void; + onReady?: (setValue: (value: number) => void) => void; }; export const ButtonIncrement: FC<ButtonIncrementProps> = ({ @@ -30,25 +31,31 @@ export const ButtonIncrement: FC<ButtonIncrementProps> = ({ style = ["simple", "border"], onClick, onBeforeChange, - onChange + onChange, + onReady }) => { const [valueState, setValue] = useState(value); const classes = () => ["button-increment", size, ...style].join(" "); + useEffect(() => { + onReady && onReady((value) => setValue(value)); + }, []); const _onMinusClick = () => { - const valueNew = valueState - delta; + let valueNew = valueState - delta; if (onBeforeChange) { if (!onBeforeChange(valueNew)) return; } - if (min !== undefined && valueNew < min) return; + if (min !== undefined) valueNew = Math.max(min, valueNew); + if (valueNew === valueState) return; setValue(valueNew); if (onChange) onChange(valueNew); }; const _onPlusClick = () => { - const valueNew = valueState + delta; + let valueNew = valueState + delta; if (onBeforeChange) { if (!onBeforeChange(valueNew)) return; } - if (max !== undefined && valueNew > max) return; + if (max !== undefined) valueNew = Math.min(max, valueNew); + if (valueNew === valueState) return; setValue(valueNew); if (onChange) onChange(valueNew); };