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 { 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> */} | ||||||
|   | |||||||
							
								
								
									
										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 { 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
									
								
							
							
						
						
									
										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