From 9d085b08dd9ba17cb7adb8da838807ff09252c42 Mon Sep 17 00:00:00 2001 From: dylan <> Date: Sat, 11 May 2024 17:40:33 -0700 Subject: [PATCH] wip from a while ago --- src/client/GamePage.tsx | 10 +++- src/client/components/PicoPortal.tsx | 26 +++++++++++ src/client/pico8-client/renderCart.ts | 7 ++- src/client/util/watch.ts | 67 +++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 src/client/components/PicoPortal.tsx create mode 100644 src/client/util/watch.ts diff --git a/src/client/GamePage.tsx b/src/client/GamePage.tsx index 4a3beed..936cec4 100644 --- a/src/client/GamePage.tsx +++ b/src/client/GamePage.tsx @@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from "react"; import { DbRelease } from "../server/dbal/dbal"; import { css } from "@emotion/css"; import { useWebsocket } from "./hooks/useWebsocket"; +import { PicoPortal } from "./components/PicoPortal"; type Info = { release: DbRelease | null; @@ -16,9 +17,15 @@ export const GamePage = () => { const room = searchParams.get('room'); const picoRef = useRef(null); const socket = useWebsocket({ - url: `/api/ws/room?room=${room}`, + url: `/api/ws/room?room=${room}&`, // url: "wss://echo.websocket.org", onMessage({message}) { + if (picoRef.current) { + const handle = picoRef.current.getPicoConsoleHandle(); + if (handle) { + handle.buttons; + } + } // const msg = message as any; // if (msg.type === "gpio") { // if (picoRef.current) { @@ -120,6 +127,7 @@ export const GamePage = () => { + {/*

This is a paragraph about this game. It is a cool game. And a cool website to play it on. It automagically connects from GitHub.

*/} diff --git a/src/client/components/PicoPortal.tsx b/src/client/components/PicoPortal.tsx new file mode 100644 index 0000000..3be8b18 --- /dev/null +++ b/src/client/components/PicoPortal.tsx @@ -0,0 +1,26 @@ +import { useRef } from "react"; +import { Pico8Console, Pico8ConsoleImperatives } from "../pico8-client/Pico8Console" + +export const PicoPortal = () => { + const emptyCartData: number[] = new Array(32786).fill(0); + const cart = {name: "empty", rom: emptyCartData}; + // const picoRef = useRef(null); + + return
+ { + if (!ref) { + return; + } + const handle = ref.getPicoConsoleHandle(); + if (!handle) { + return; + } + handle.buttons.subscribe((buttons) => { + console.log(buttons); + }); + }} + carts={[cart]} + /> +
+} \ No newline at end of file diff --git a/src/client/pico8-client/renderCart.ts b/src/client/pico8-client/renderCart.ts index 98702d2..ceee8f5 100644 --- a/src/client/pico8-client/renderCart.ts +++ b/src/client/pico8-client/renderCart.ts @@ -1,6 +1,7 @@ import { assertNever } from "@firebox/tsutil"; import { pngToRom } from "./pngToRom"; import { RenderCart, renderCart as rawRenderCart } from "./rawRenderCart"; +import { Watched, watch } from "../util/watch"; export type PicoCart = { name: string; @@ -20,13 +21,16 @@ type PlayerButtons = { menu: boolean; } +type RawHandle = ReturnType; + export type PicoPlayerHandle = { - raw: ReturnType; + raw: RawHandle; rawModule: unknown; // external things readonly canvas: HTMLCanvasElement; // i/o + buttons: Watched>; setButtons: (buttons: PlayerButtons[]) => void; setMouse: (mouse: { x: number; @@ -177,6 +181,7 @@ export const makePicoConsole = async (props: { setMouse({x, y, leftClick, rightClick}) { handle.pico8_mouse = [x, y, bitfield(leftClick, rightClick)]; }, + buttons: watch(handle.pico8_buttons!), setButtons(buttons) { // TODO: pad this properly here instead of casting handle.pico8_buttons = buttons.map(({left, right, up, down, o, x, menu}) => bitfield(left, right, up, down, o, x, menu)) as any; diff --git a/src/client/util/watch.ts b/src/client/util/watch.ts new file mode 100644 index 0000000..d2b32aa --- /dev/null +++ b/src/client/util/watch.ts @@ -0,0 +1,67 @@ +const deepEqual = (a: any, b: any) => { + if (a === b) return true; + + if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false; + + let keysA = Object.keys(a), keysB = Object.keys(b); + + if (keysA.length !== keysB.length) return false; + + for (let key of keysA) { + if (!keysB.includes(key)) return false; + + if (typeof a[key] === 'function' || typeof b[key] === 'function') { + if (a[key].toString() !== b[key].toString()) return false; + } else { + if (!deepEqual(a[key], b[key])) return false; + } + } + + return true; +} + +export type Watched = { + value: T; + subscribe: (f: (newVal: T, oldVal: T) => void) => void; + unsubscribe: (f: (newVal: T, oldVal: T) => void) => void; + locked: boolean; +} + +export const watch = | any[]>(target: T): Watched => { + let listeners: Array<(newVal: T, oldVal: T) => void> = []; + let locked = false; + const proxy = new Proxy(target, { + get(t: any, prop) { + return t[prop as any]; + }, + set(t: any, prop, newValue) { + if (locked) { + return false; + } + const prev = structuredClone(t); + t[prop as any] = newValue; + if (deepEqual(prev, t)) { + listeners.forEach(listener => listener(t, prev)); + } + return true; + } + }); + return { + get value() { + return target; + }, + subscribe(f) { + listeners.push(f) + }, + unsubscribe(f) { + listeners = listeners.filter(l => l !== f); + }, + get locked() { + return locked; + }, + set locked(newVal: boolean) { + locked = newVal; + } + } +} +