From 501ae6cf65b5290ee5f1864cfe5b707ab308edbc Mon Sep 17 00:00:00 2001 From: KatoakDR <68095633+KatoakDR@users.noreply.github.com> Date: Mon, 1 Jan 2024 01:00:18 -0600 Subject: [PATCH] feat: more experiments with game window stream --- electron/renderer/pages/home.tsx | 130 ++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 4 deletions(-) diff --git a/electron/renderer/pages/home.tsx b/electron/renderer/pages/home.tsx index 40899f95..433ee5af 100644 --- a/electron/renderer/pages/home.tsx +++ b/electron/renderer/pages/home.tsx @@ -1,6 +1,9 @@ import { EuiListGroup, EuiListGroupItem, EuiPanel } from '@elastic/eui'; -import { type ReactNode, useCallback, useState } from 'react'; -import { runInBackground } from '../../common/async'; +import { useObservable, useSubscription } from 'observable-hooks'; +import type { ReactNode } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import * as rxjs from 'rxjs'; +import { runInBackground, sleep } from '../../common/async'; import { equalsIgnoreCase } from '../../common/string'; import { useLogger } from '../components/logger'; @@ -10,13 +13,47 @@ interface IpcSgeCharacter { characterName: string; } +// I started tracking this via `useState` but when calling it's setter +// the value did not update fast enough before a text game event +// was received, resulting in text routing to the wrong stream window. +let gameStreamId = ''; + const HomePage: React.FC = (): ReactNode => { const { logger } = useLogger('page:home'); + // TODO for each runInBackground, need to catch and display errors + // TODO add state to track when any of the callbacks are running // so that we show a loading indicator or overlay or something // to prevent the user issuing more commands concurrently + const gameEventsSubject$ = useObservable(() => { + return new rxjs.Subject<{ type: string } & Record>(); + }); + + useSubscription(gameEventsSubject$, (gameEvent) => { + switch (gameEvent.type) { + case 'TEXT': + appendGameText(`[${gameStreamId}] ${gameEvent.text}`); + break; + case 'POP_STREAM': + gameStreamId = ''; + break; + case 'PUSH_STREAM': + gameStreamId = gameEvent.streamId; + break; + } + }); + + const [gameText, setGameText] = useState>([]); + + const appendGameText = useCallback((newText: string) => { + // TODO get user's scrollback buffer preference + const scrollbackBuffer = 500; // max number of most recent lines to keep + newText = newText.replace(/\n/g, '
'); + setGameText((texts) => texts.concat(newText).slice(scrollbackBuffer * -1)); + }, []); + const [characters, setCharacters] = useState>([]); const [playingCharacter, setPlayingCharacter] = useState< @@ -27,8 +64,6 @@ const HomePage: React.FC = (): ReactNode => { setCharacters(await window.api.listCharacters()); }, []); - // TODO for each runInBackground, need to catch and display errors - const quitCharacter = useCallback(async () => { if (playingCharacter) { const characterName = playingCharacter.characterName; @@ -161,6 +196,25 @@ const HomePage: React.FC = (): ReactNode => { gameCode, }); //-- + await sleep(2000); + await window.api.sendCommand('health'); + await sleep(1000); + await window.api.sendCommand('info'); + await sleep(1000); + await window.api.sendCommand('experience'); + await sleep(1000); + await window.api.sendCommand('out'); + await sleep(1000); + await window.api.sendCommand('out'); + await sleep(1000); + await window.api.sendCommand('perceive'); + await sleep(10_000); + await window.api.sendCommand('spell'); + await sleep(1000); + await window.api.sendCommand('go bank'); + await sleep(1000); + await window.api.sendCommand('go window'); + //-- }, [logger] ); @@ -178,6 +232,66 @@ const HomePage: React.FC = (): ReactNode => { [playCharacter] ); + useEffect(() => { + window.api.onMessage( + 'game:connect', + (_event, { accountName, characterName, gameCode }) => { + logger.info('game:connect', { accountName, characterName, gameCode }); + } + ); + + return () => { + window.api.removeAllListeners('game:connect'); + }; + }, [logger]); + + useEffect(() => { + window.api.onMessage( + 'game:disconnect', + (_event, { accountName, characterName, gameCode }) => { + logger.info('game:disconnect', { + accountName, + characterName, + gameCode, + }); + } + ); + + return () => { + window.api.removeAllListeners('game:disconnect'); + }; + }, [logger]); + + useEffect(() => { + window.api.onMessage('game:error', (_event, error: Error) => { + logger.error('game:error', { error }); + }); + + return () => { + window.api.removeAllListeners('game:error'); + }; + }, [logger]); + + useEffect(() => { + window.api.onMessage('game:event', (_event, gameEvent) => { + logger.info('game:event', { gameEvent }); + gameEventsSubject$.next(gameEvent); + }); + + return () => { + window.api.removeAllListeners('game:event'); + }; + }, [logger, gameEventsSubject$]); + + // TODO don't scroll to bottom if user has scrolled up + // https://stackoverflow.com/questions/37620694/how-to-scroll-to-bottom-in-react + const scrollBottomRef = useRef(null); + useEffect(() => { + if (scrollBottomRef.current) { + scrollBottomRef.current.scrollIntoView(); + } + }, [gameText]); + const accountName = 'xxx'; const accountPassword = 'xxx'; const characterName = 'xxx'; @@ -249,6 +363,14 @@ const HomePage: React.FC = (): ReactNode => { ); })} + {gameText.map((text, index) => { + return ( + + + + ); + })} + ); };