Compare commits

...

1 Commits

Author SHA1 Message Date
dylan
9d085b08dd wip from a while ago 2024-05-11 17:40:33 -07:00
4 changed files with 108 additions and 2 deletions

View File

@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from "react";
import { DbRelease } from "../server/dbal/dbal"; import { DbRelease } from "../server/dbal/dbal";
import { css } from "@emotion/css"; import { css } from "@emotion/css";
import { useWebsocket } from "./hooks/useWebsocket"; import { useWebsocket } from "./hooks/useWebsocket";
import { PicoPortal } from "./components/PicoPortal";
type Info = { type Info = {
release: DbRelease | null; release: DbRelease | null;
@ -16,9 +17,15 @@ export const GamePage = () => {
const room = searchParams.get('room'); const room = searchParams.get('room');
const picoRef = useRef<Pico8ConsoleImperatives>(null); const picoRef = useRef<Pico8ConsoleImperatives>(null);
const socket = useWebsocket({ const socket = useWebsocket({
url: `/api/ws/room?room=${room}`, url: `/api/ws/room?room=${room}&`,
// url: "wss://echo.websocket.org", // url: "wss://echo.websocket.org",
onMessage({message}) { onMessage({message}) {
if (picoRef.current) {
const handle = picoRef.current.getPicoConsoleHandle();
if (handle) {
handle.buttons;
}
}
// const msg = message as any; // const msg = message as any;
// if (msg.type === "gpio") { // if (msg.type === "gpio") {
// if (picoRef.current) { // if (picoRef.current) {
@ -120,6 +127,7 @@ export const GamePage = () => {
</select> </select>
</div> </div>
</div> </div>
<PicoPortal />
{/* <div> {/* <div>
<p>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.</p> <p>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.</p>
</div> */} </div> */}

View File

@ -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<Pico8ConsoleImperatives>(null);
return <div>
<Pico8Console
ref={(ref) => {
if (!ref) {
return;
}
const handle = ref.getPicoConsoleHandle();
if (!handle) {
return;
}
handle.buttons.subscribe((buttons) => {
console.log(buttons);
});
}}
carts={[cart]}
/>
</div>
}

View File

@ -1,6 +1,7 @@
import { assertNever } from "@firebox/tsutil"; import { assertNever } from "@firebox/tsutil";
import { pngToRom } from "./pngToRom"; import { pngToRom } from "./pngToRom";
import { RenderCart, renderCart as rawRenderCart } from "./rawRenderCart"; import { RenderCart, renderCart as rawRenderCart } from "./rawRenderCart";
import { Watched, watch } from "../util/watch";
export type PicoCart = { export type PicoCart = {
name: string; name: string;
@ -20,13 +21,16 @@ type PlayerButtons = {
menu: boolean; menu: boolean;
} }
type RawHandle = ReturnType<RenderCart>;
export type PicoPlayerHandle = { export type PicoPlayerHandle = {
raw: ReturnType<RenderCart>; raw: RawHandle;
rawModule: unknown; rawModule: unknown;
// external things // external things
readonly canvas: HTMLCanvasElement; readonly canvas: HTMLCanvasElement;
// i/o // i/o
buttons: Watched<NonNullable<RawHandle["pico8_buttons"]>>;
setButtons: (buttons: PlayerButtons[]) => void; setButtons: (buttons: PlayerButtons[]) => void;
setMouse: (mouse: { setMouse: (mouse: {
x: number; x: number;
@ -177,6 +181,7 @@ export const makePicoConsole = async (props: {
setMouse({x, y, leftClick, rightClick}) { setMouse({x, y, leftClick, rightClick}) {
handle.pico8_mouse = [x, y, bitfield(leftClick, rightClick)]; handle.pico8_mouse = [x, y, bitfield(leftClick, rightClick)];
}, },
buttons: watch(handle.pico8_buttons!),
setButtons(buttons) { setButtons(buttons) {
// TODO: pad this properly here instead of casting // 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; handle.pico8_buttons = buttons.map(({left, right, up, down, o, x, menu}) => bitfield(left, right, up, down, o, x, menu)) as any;

67
src/client/util/watch.ts Normal file
View File

@ -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<T> = {
value: T;
subscribe: (f: (newVal: T, oldVal: T) => void) => void;
unsubscribe: (f: (newVal: T, oldVal: T) => void) => void;
locked: boolean;
}
export const watch = <T extends Record<any,any> | any[]>(target: T): Watched<T> => {
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;
}
}
}