From b9a67e6b0f2761fcff764cd26027ecdbac1e9463 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Apr 2024 02:12:39 +0100 Subject: [PATCH 1/6] handle many button presses and fix to use targetTouches --- website/site/index.html | 82 +++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/website/site/index.html b/website/site/index.html index d83ef6c4..b974c3a2 100644 --- a/website/site/index.html +++ b/website/site/index.html @@ -270,11 +270,11 @@ }; mobileDpad.addEventListener("touchstart", (evt) => - dpadMovement(evt.touches[0]) + dpadMovement(evt.targetTouches[0]) ); mobileDpad.addEventListener("touchmove", (evt) => - dpadMovement(evt.touches[0]) + dpadMovement(evt.targetTouches[0]) ); mobileDpad.addEventListener("touchend", (evt) => { @@ -284,57 +284,59 @@ previouslyPressedButtons = []; }); - let mobileAbAPress = undefined; - const mobileAbMovement = (touch) => { - const target = touch.target.getBoundingClientRect(); + let mobileAbAPress = new Set(); + const mobileAbMovement = (touches) => { + const currentTouches = new Set(); + for (let touch of touches) { + const target = touch.target.getBoundingClientRect(); - const touchPoint = { x: touch.clientX, y: touch.clientY }; - const targetArea = { - x: target.left, - y: target.top, - width: target.width, - height: target.height, - }; + const touchPoint = { x: touch.clientX, y: touch.clientY }; + const targetArea = { + x: target.left, + y: target.top, + width: target.width, + height: target.height, + }; - const relativePosition = { - x: touchPoint.x - targetArea.x, - y: touchPoint.y - targetArea.y, - }; + const relativePosition = { + x: touchPoint.x - targetArea.x, + y: touchPoint.y - targetArea.y, + }; - const aPress = relativePosition.x > relativePosition.y; + const aPress = relativePosition.x > relativePosition.y; + currentTouches.add(aPress ? "A" : "B"); + } - if (aPress !== mobileAbAPress) { - if (mobileAbAPress === true) { - releaseButton("A"); - } else if (mobileAbAPress === false) { - releaseButton("B"); + for (let oldTouch of mobileAbAPress) { + if (!currentTouches.has(oldTouch)) { + console.log("Release", oldTouch); + releaseButton(oldTouch); } } - if (aPress) { - pressButton("A"); - } else { - pressButton("B"); - } - mobileAbAPress = aPress; + for (let newTouch of currentTouches) { + if (!mobileAbAPress.has(newTouch)) { + console.log("Press", newTouch); + pressButton(newTouch); + } + } + mobileAbAPress = currentTouches; }; - mobileAb.addEventListener("touchstart", (evt) => - mobileAbMovement(evt.touches[0]) - ); + mobileAb.addEventListener("touchstart", (evt) => { + mobileAbMovement(evt.targetTouches); + }); - mobileAb.addEventListener("touchmove", (evt) => - mobileAbMovement(evt.touches[0]) - ); + mobileAb.addEventListener("touchmove", (evt) => { + mobileAbMovement(evt.targetTouches); + }); mobileAb.addEventListener("touchend", (evt) => { - if (mobileAbAPress === true) { - releaseButton("A"); - } else if (mobileAbAPress === false) { - releaseButton("B"); - } + mobileAbMovement(evt.targetTouches); + }); - mobileAbAPress = undefined; + mobileAb.addEventListener("touchcancel", (evt) => { + mobileAbMovement(evt.targetTouches); }); From 34610521938d5e86b18d79270302c2b91555b423 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Apr 2024 02:14:15 +0100 Subject: [PATCH 2/6] handle multiple dpad touches --- website/site/index.html | 72 +++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/website/site/index.html b/website/site/index.html index b974c3a2..82ed6be7 100644 --- a/website/site/index.html +++ b/website/site/index.html @@ -222,46 +222,53 @@ addSimpleButton(mobileStart, "Start"); addSimpleButton(mobileSelect, "Select"); - let previouslyPressedButtons = []; + let previouslyPressedButtons = new Set(); const dpadMovement = (touch) => { - const target = touch.target.getBoundingClientRect(); + const currentlyPressed = new Set(); + for (let touch of touches) { + const target = touch.target.getBoundingClientRect(); - const touchPoint = { x: touch.clientX, y: touch.clientY }; - const targetArea = { - x: target.left, - y: target.top, - width: target.width, - height: target.height, - }; + const touchPoint = { x: touch.clientX, y: touch.clientY }; + const targetArea = { + x: target.left, + y: target.top, + width: target.width, + height: target.height, + }; - const relativePosition = { - x: touchPoint.x - targetArea.x, - y: touchPoint.y - targetArea.y, - }; + const relativePosition = { + x: touchPoint.x - targetArea.x, + y: touchPoint.y - targetArea.y, + }; - const touchedBox = { - x: Math.floor(relativePosition.x / (targetArea.width / 3)), - y: Math.floor(relativePosition.y / (targetArea.height / 3)), - }; + const touchedBox = { + x: Math.floor(relativePosition.x / (targetArea.width / 3)), + y: Math.floor(relativePosition.y / (targetArea.height / 3)), + }; - const buttonBoxMapping = [ - [["Up", "Left"], ["Up"], ["Up", "Right"]], - [["Left"], [], ["Right"]], - [["Down", "Left"], ["Down"], ["Down", "Right"]], - ]; + const buttonBoxMapping = [ + [["Up", "Left"], ["Up"], ["Up", "Right"]], + [["Left"], [], ["Right"]], + [["Down", "Left"], ["Down"], ["Down", "Right"]], + ]; - const buttonsToPress = - (buttonBoxMapping[touchedBox.y] ?? [])[touchedBox.x] ?? []; + const buttonsToPress = + (buttonBoxMapping[touchedBox.y] ?? [])[touchedBox.x] ?? []; + + for (let button of buttonsToPress) { + currentlyPressed.add(button); + } + } for (let oldButton of previouslyPressedButtons) { - if (!buttonsToPress.includes(oldButton)) { + if (!buttonsToPress.has(oldButton)) { releaseButton(oldButton); } } for (let newButton of buttonsToPress) { - if (!previouslyPressedButtons.includes(newButton)) { + if (!previouslyPressedButtons.has(newButton)) { pressButton(newButton); } } @@ -270,18 +277,19 @@ }; mobileDpad.addEventListener("touchstart", (evt) => - dpadMovement(evt.targetTouches[0]) + dpadMovement(evt.targetTouches) ); mobileDpad.addEventListener("touchmove", (evt) => - dpadMovement(evt.targetTouches[0]) + dpadMovement(evt.targetTouches) ); mobileDpad.addEventListener("touchend", (evt) => { - for (let oldButton of previouslyPressedButtons) { - releaseButton(oldButton); - } - previouslyPressedButtons = []; + dpadMovement(evt.targetTouches); + }); + + mobileDpad.addEventListener("touchcancel", (evt) => { + dpadMovement(evt.targetTouches); }); let mobileAbAPress = new Set(); From 92c887720164c1923f287d3a6a1a300f725f03c7 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Apr 2024 02:17:01 +0100 Subject: [PATCH 3/6] we should be overwriting max width --- website/site/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/site/index.html b/website/site/index.html index 82ed6be7..0d64fece 100644 --- a/website/site/index.html +++ b/website/site/index.html @@ -120,7 +120,7 @@ } header, .desktopHelp { - width: 90%; + max-width: 90%; } } From f053915c6558f2756d2ec59dd5215a88e77cbd62 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Apr 2024 02:24:10 +0100 Subject: [PATCH 4/6] fix controls --- website/site/index.html | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/website/site/index.html b/website/site/index.html index 0d64fece..e6b18c50 100644 --- a/website/site/index.html +++ b/website/site/index.html @@ -62,7 +62,7 @@ max-width: 60%; margin-left: auto; margin-right: auto; - margin-block-end: 40px; + margin-block-end: 20px; } .red { @@ -110,8 +110,12 @@ .mobileControlsRow { display: flex; align-items: center; + justify-content: space-between; + width: 100%; + } + + .mobileControlsReset { justify-content: center; - gap: 40px; } @media (max-width: 800px) { @@ -169,7 +173,7 @@ -
+
@@ -224,7 +228,7 @@ let previouslyPressedButtons = new Set(); - const dpadMovement = (touch) => { + const dpadMovement = (touches) => { const currentlyPressed = new Set(); for (let touch of touches) { const target = touch.target.getBoundingClientRect(); @@ -262,18 +266,18 @@ } for (let oldButton of previouslyPressedButtons) { - if (!buttonsToPress.has(oldButton)) { + if (!currentlyPressed.has(oldButton)) { releaseButton(oldButton); } } - for (let newButton of buttonsToPress) { + for (let newButton of currentlyPressed) { if (!previouslyPressedButtons.has(newButton)) { pressButton(newButton); } } - previouslyPressedButtons = buttonsToPress; + previouslyPressedButtons = currentlyPressed; }; mobileDpad.addEventListener("touchstart", (evt) => @@ -317,14 +321,12 @@ for (let oldTouch of mobileAbAPress) { if (!currentTouches.has(oldTouch)) { - console.log("Release", oldTouch); releaseButton(oldTouch); } } for (let newTouch of currentTouches) { if (!mobileAbAPress.has(newTouch)) { - console.log("Press", newTouch); pressButton(newTouch); } } From 61a7e2fa648c5739ab2653409fc8671da54af5e9 Mon Sep 17 00:00:00 2001 From: Corwin Date: Sat, 6 Apr 2024 02:34:39 +0100 Subject: [PATCH 5/6] force full reset which can get audio working on mobile --- website/app/src/App.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/website/app/src/App.tsx b/website/app/src/App.tsx index df71c83f..42140cce 100644 --- a/website/app/src/App.tsx +++ b/website/app/src/App.tsx @@ -51,6 +51,8 @@ function App() { "agbrswebplayer", ); + const [mgbaId, setMgbaId] = useState(0); + const setVolume = (newVolume: number) => setState({ volume: newVolume, bindings }); const setBindings = (newBindings: Bindings) => @@ -79,7 +81,7 @@ function App() { } if (reset) { - mgbaRef.current?.restart(); + setMgbaId((id) => id + 1); } }; @@ -111,6 +113,7 @@ function App() { )} {isPlaying ? ( Date: Sat, 6 Apr 2024 03:26:06 +0100 Subject: [PATCH 6/6] dynamically choose frameskip or timeout --- website/app/src/mgba.tsx | 27 +++++++++++++++ website/app/src/useSmoothedFramerate.hook.ts | 35 ++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 website/app/src/useSmoothedFramerate.hook.ts diff --git a/website/app/src/mgba.tsx b/website/app/src/mgba.tsx index e2b14a49..29b17681 100644 --- a/website/app/src/mgba.tsx +++ b/website/app/src/mgba.tsx @@ -8,6 +8,7 @@ import { import mGBA from "./vendor/mgba"; import { GbaKey, KeyBindings } from "./bindings"; import { styled } from "styled-components"; +import { useSmoothedFramerate } from "./useSmoothedFramerate.hook"; type Module = any; @@ -40,6 +41,14 @@ export interface MgbaHandle { buttonRelease: (key: GbaKey) => void; } +const whichFrameSkip = (frameRate: number): number | undefined => { + if ((frameRate + 5) % 60 <= 10) { + // framerate close to multiple of 60 + // use frameskip + return Math.round(frameRate / 60); + } +}; + export const Mgba = forwardRef( ({ gameUrl, volume, controls, paused }, ref) => { const canvas = useRef(null); @@ -92,6 +101,24 @@ export const Mgba = forwardRef( }; }, [state]); + const frameRate = useSmoothedFramerate(); + const frameSkipToUse = whichFrameSkip(frameRate); + + useEffect(() => { + if (!gameLoaded) return; + + if (frameSkipToUse) { + // framerate close to multiple of 60 + // use frameskip + console.log("Using frameskip"); + mgbaModule.current.setMainLoopTiming(1, frameSkipToUse); + } else { + // frame rate not close to multiple of 60, use timeout + console.log("Using timeout"); + mgbaModule.current.setMainLoopTiming(0, 1000 / 59.727500569606); + } + }, [frameSkipToUse, gameLoaded]); + useEffect(() => { if (!gameLoaded) return; diff --git a/website/app/src/useSmoothedFramerate.hook.ts b/website/app/src/useSmoothedFramerate.hook.ts new file mode 100644 index 00000000..80666e8f --- /dev/null +++ b/website/app/src/useSmoothedFramerate.hook.ts @@ -0,0 +1,35 @@ +import { useEffect, useState } from "react"; + + +export const useSmoothedFramerate = (): number => { + + const [smoothedFrameTime, setSmoothedFrameTime] = useState(60); + + useEffect(() => { + + let previous: number | undefined = undefined; + let stopped = false; + + const raf = (time: DOMHighResTimeStamp) => { + if (previous) { + let delta = time - previous; + + setSmoothedFrameTime((time) => (time * 3 + delta) / 4); + } + previous = time; + + + if (!stopped) { + window.requestAnimationFrame(raf); + } + } + + window.requestAnimationFrame(raf); + + return () => { stopped = true; } + + }, []); + + + return Math.round(1 / (smoothedFrameTime / 1000)); +} \ No newline at end of file