wip from a while ago
This commit is contained in:
		| @@ -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<Pico8ConsoleImperatives>(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 = () => { | ||||
| 					</select> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 			<PicoPortal /> | ||||
| 			{/* <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> | ||||
| 			</div> */} | ||||
|   | ||||
							
								
								
									
										26
									
								
								src/client/components/PicoPortal.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/client/components/PicoPortal.tsx
									
									
									
									
									
										Normal 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> | ||||
| } | ||||
| @@ -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<RenderCart>; | ||||
|  | ||||
| export type PicoPlayerHandle = { | ||||
| 	raw: ReturnType<RenderCart>; | ||||
| 	raw: RawHandle; | ||||
| 	rawModule: unknown; | ||||
| 	// external things | ||||
| 	readonly canvas: HTMLCanvasElement; | ||||
|  | ||||
| 	// i/o | ||||
| 	buttons: Watched<NonNullable<RawHandle["pico8_buttons"]>>; | ||||
| 	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; | ||||
|   | ||||
							
								
								
									
										67
									
								
								src/client/util/watch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/client/util/watch.ts
									
									
									
									
									
										Normal 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; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 dylan
					dylan