diff --git a/package.json b/package.json index fa7f040..3b5822b 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "prod-start": "echo \"building frontend\" && npm run prod-build-client && echo \"${DATABASE_URL}\" && echo \"running migrations\" && npm run prod-migrate && echo \"starting server\" && npm run withenv ./src/server/index.ts", "withenv": "tsx ./scripts/run-with-env.ts", "test": "echo \"Error: no test specified\" && exit 1", - "build-p8client": "bun build src/client/pico8-client/veryRawRenderCart.js --outdir src/client/pico8-client/build" + "build-p8client": "bun build src/client/pico8-client/veryRawRenderCart.js --outdir src/client/pico8-client/build", + "add-pico": "npm run withenv ./scripts/do-release.ts" }, "repository": { "type": "git", diff --git a/scripts/do-release.ts b/scripts/do-release.ts new file mode 100644 index 0000000..2991b46 --- /dev/null +++ b/scripts/do-release.ts @@ -0,0 +1,27 @@ +import { insertRelease } from "../src/server/dbal/dbal"; +import { ManifestType } from "../src/server/types"; +import { getCarts } from "../src/server/util/carts"; +import fs from "fs/promises"; +import path from "path"; + +const doRelease = async (dir: string) => { + const manifest = JSON.parse(await fs.readFile(path.join(dir, "picobook.json"), "utf8")); + + if (!ManifestType.Check(manifest)) { + return false; + } + + const carts = await getCarts(dir, manifest.carts); + + await insertRelease({ + manifest, + carts, + }); +} + +if (process.argv[2]) { + // console.log(process.argv[3]); + await doRelease(process.argv[3]); +} else { + console.log("must pass in a path to a repo"); +} \ No newline at end of file diff --git a/src/client/GamePage.tsx b/src/client/GamePage.tsx index 43d3225..f543db7 100644 --- a/src/client/GamePage.tsx +++ b/src/client/GamePage.tsx @@ -1,6 +1,6 @@ import { Link, useParams, useSearchParams } from "react-router-dom" -import { Pico8Console } from "./pico8-client/Pico8Console"; -import { useEffect, useState } from "react"; +import { Pico8Console, Pico8ConsoleImperatives } from "./pico8-client/Pico8Console"; +import { useEffect, useRef, useState } from "react"; import { DbRelease } from "../server/dbal/dbal"; import { css } from "@emotion/css"; import { useWebsocket } from "./hooks/useWebsocket"; @@ -14,10 +14,24 @@ export const GamePage = () => { const {author, slug} = useParams(); const [searchParams, setSearchParams] = useSearchParams(); const room = searchParams.get('room'); + const picoRef = useRef(null); const socket = useWebsocket({ url: `/api/ws/room?room=${room}`, // url: "wss://echo.websocket.org", onMessage({message}) { + const msg = message as any; + if (msg.type === "gpio") { + if (picoRef.current) { + const handle = picoRef.current.getPicoConsoleHandle(); + if (handle) { + console.log("updating pico gpio"); + (handle.gpio as any).dontSend = true; + handle.gpio.length = 0; + handle.gpio.push(...msg.gpio); + (handle.gpio as any).dontSend = false; + } + } + } console.log('message', message); } }) @@ -81,7 +95,13 @@ export const GamePage = () => { border: 2px solid limegreen; } `}> - + { + console.log("sending gpio"); + socket.sendMessage({ + type: "gpio", + gpio, + }); + }} />
{
-
- -
{/*

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/pico8-client/Pico8Console.tsx b/src/client/pico8-client/Pico8Console.tsx index 04309e0..fcdbbce 100644 --- a/src/client/pico8-client/Pico8Console.tsx +++ b/src/client/pico8-client/Pico8Console.tsx @@ -2,12 +2,19 @@ import { css } from "@emotion/css"; import { PicoCart, PicoPlayerHandle, makePicoConsole } from "./renderCart"; import { ForwardedRef, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"; -type Pico8ConsoleImperatives = { +export type Pico8ConsoleImperatives = { getPicoConsoleHandle(): PicoPlayerHandle | null; } -export const Pico8Console = forwardRef((props: { carts: PicoCart[] }, forwardedRef: ForwardedRef) => { - const {carts} = props; +export type Pico8ConsoleProps = { + carts: PicoCart[], + onGpioChange?(gpio: number[]): void, +} + +const noop = () => {}; + +export const Pico8Console = forwardRef((props: Pico8ConsoleProps, forwardedRef: ForwardedRef) => { + const {carts, onGpioChange = noop} = props; const [playing, setPlaying] = useState(false); const ref = useRef(null); const [handle, setHandle] = useState(null); @@ -26,6 +33,7 @@ export const Pico8Console = forwardRef((props: { carts: PicoCart[] }, forwardedR picoConsole.canvas.focus(); } setHandle(picoConsole); + picoConsole.gpio.subscribe(onGpioChange); picoConsole.canvas.addEventListener('keydown',(event) => { if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)) { event.preventDefault(); diff --git a/src/client/pico8-client/renderCart.ts b/src/client/pico8-client/renderCart.ts index 36cfe78..f9f8df5 100644 --- a/src/client/pico8-client/renderCart.ts +++ b/src/client/pico8-client/renderCart.ts @@ -35,7 +35,7 @@ export type PicoPlayerHandle = { rightClick: boolean; }) => void; setGamepadCount: (count: number) => void; - readonly gpio: number[]; // read + write (should be 256-tuple) + gpio: number[] & {subscribe: (f: (gpio: number[]) => void) => void}; // read + write (should be 256-tuple) // state readonly state: { @@ -97,7 +97,8 @@ export const makePicoConsole = async (props: { handle.pico8_state = {}; handle.pico8_buttons = [0,0,0,0,0,0,0,0]; handle.pico8_mouse = [0,0,0]; - handle.pico8_gpio = [ + let gpioChanged = (gpio: number[]) => {}; + const gpioInner = [ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -115,6 +116,27 @@ export const makePicoConsole = async (props: { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ]; + handle.pico8_gpio = new Proxy(gpioInner, { + get(target, prop) { + return target[prop as any]; + }, + set(target, prop, newValue) { + const t = target as any; + if (t.setting) { + return false; + } + const prev = [...target]; + target[prop as any] = newValue; + const next = [...target]; + if (!t.dontSend && prev.some((p, i) => p !== next[i])) { + gpioChanged(target); + } + return true; + } + }); + (handle as any).pico8_gpio.subscribe = (f: (gpio: number[]) => void) => { + gpioChanged = f; + } handle.pico8_gamepads = {count: 0}; return { raw: handle, @@ -130,7 +152,7 @@ export const makePicoConsole = async (props: { shutdownRequested: !!handle.pico8_state.shutdown_requested!, soundVolume: handle.pico8_state.sound_volume!, }, - gpio: handle.pico8_gpio, + gpio: handle.pico8_gpio as PicoPlayerHandle["gpio"], setMouse({x, y, leftClick, rightClick}) { handle.pico8_mouse = [x, y, bitfield(leftClick, rightClick)]; }, diff --git a/src/server/api/room.ts b/src/server/api/room.ts index 416955d..2e94a3d 100644 --- a/src/server/api/room.ts +++ b/src/server/api/room.ts @@ -1,6 +1,7 @@ import { Type } from "@sinclair/typebox"; import { FirRouteOptions, FirWebsocketHandler, FirWebsocketInput } from "../util/routewrap.js"; import { WebSocket } from "@fastify/websocket"; +import { FastifyRequest } from "fastify"; const method = "GET"; const url = "/api/ws/room"; @@ -14,9 +15,13 @@ type Room = { let rooms: Room[] = []; +const getRoomName = (req: FastifyRequest) => { + return (req.query as any).room; +} + const websocket = { onOpen({socket, req}) { - const {room: roomName} = req.query as any; + const roomName = getRoomName(req); let room = rooms.find(r => r.name === roomName); if (!room) { console.log("creating room", roomName); @@ -33,7 +38,7 @@ const websocket = { console.log('rooms', rooms); }, onClose({socket, req}) { - const {room: roomName} = req.query as any; + const roomName = getRoomName(req); const room = rooms.find(r => r.name === roomName); if (room) { room.sockets = room.sockets.filter(sock => sock !== socket); @@ -43,8 +48,8 @@ const websocket = { } console.log('rooms', rooms); }, - onMessage({socket, payload}) { - const {room: roomName} = payload as any; + onMessage({socket, payload, req}) { + const roomName = getRoomName(req); let room = rooms.find(r => r.name === roomName); if (!room) { console.log("creating room", roomName); @@ -58,9 +63,11 @@ const websocket = { console.log("adding socket to room", roomName); room.sockets.push(socket); } - console.log("replying to everyone in room", roomName); + console.log("replying to everyone else in room", roomName); room.sockets.forEach(sock => { - sock.send(JSON.stringify(payload)); + if (sock !== socket) { + sock.send(JSON.stringify(payload)); + } }); console.log('rooms', rooms); },