From 2ccdd4ca14d4343294fbea730a2ad6df6bd24564 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 21 Nov 2023 16:18:32 -1000 Subject: [PATCH] refactor(1kce): to use remote grainMaps and distribute powers --- packages/cli/demo/1kce/game.js | 15 ++--- packages/cli/demo/1kce/ui/app.js | 102 +++++------------------------ packages/cli/demo/1kce/ui/endo.js | 10 +-- packages/cli/demo/1kce/ui/game.js | 93 ++++++++++++++------------ packages/cli/demo/1kce/ui/index.js | 4 +- packages/cli/demo/1kce/weblet.js | 46 ++++++++++++- 6 files changed, 126 insertions(+), 144 deletions(-) diff --git a/packages/cli/demo/1kce/game.js b/packages/cli/demo/1kce/game.js index 0207c9cbd3..34356c2226 100644 --- a/packages/cli/demo/1kce/game.js +++ b/packages/cli/demo/1kce/game.js @@ -1,7 +1,7 @@ import { E, Far } from '@endo/far'; import { makeIteratorRef } from '@endo/daemon/reader-ref.js'; import { makeSyncArrayGrain, makeSyncGrain, makeSyncGrainArrayMap, makeSyncGrainMap, makeDerivedSyncGrain, composeGrainsAsync, composeGrains } from '@endo/grain'; -import { makeRemoteGrain } from '@endo/grain/captp.js'; +import { makeRemoteGrain, makeRemoteGrainMap } from '@endo/grain/captp.js'; const playerRemoteToLocal = new Map() class Player { @@ -13,9 +13,6 @@ class Player { async getName () { return localPlayer.name }, - async followHand (canceled) { - return makeIteratorRef(localPlayer.hand.follow(canceled)) - }, async getHandGrain () { return makeRemoteGrain(localPlayer.hand) }, @@ -254,11 +251,13 @@ export function makeGame () { // game state, aggregated for remote subscribers const state = makeSyncGrainMap({ log: logGrain, - currentPlayer: currentPlayerIndex, + currentPlayerIndex, + currentPlayer: currentRemotePlayer, currentTurnPhase: currentTurnPhaseName, locations, scores: scoresGrain, deck: deckGrain, + players: remotePlayers, }) const followState = (canceled) => { return state.follow(canceled) @@ -305,7 +304,7 @@ export const make = (powers) => { return makeIteratorRef(game.followState(canceled)) }, async getStateGrain () { - return makeRemoteGrain(game.state) + return makeRemoteGrainMap(game.state) }, async followCurrentPlayer (canceled) { @@ -319,10 +318,6 @@ export const make = (powers) => { return makeIteratorRef(game.getCardsAtLocation(location).follow(canceled)) }, - async followCardsAtPlayerLocation (remotePlayer, canceled) { - const { name } = playerRemoteToLocal.get(remotePlayer) - return makeIteratorRef(game.getCardsAtLocation(name).follow(canceled)) - }, async getCardsAtPlayerLocationGrain (remotePlayer) { const { name } = playerRemoteToLocal.get(remotePlayer) return makeRemoteGrain(game.getCardsAtLocation(name)) diff --git a/packages/cli/demo/1kce/ui/app.js b/packages/cli/demo/1kce/ui/app.js index f94c6f0809..b586729961 100644 --- a/packages/cli/demo/1kce/ui/app.js +++ b/packages/cli/demo/1kce/ui/app.js @@ -1,110 +1,42 @@ import React from 'react'; import { E } from '@endo/far'; -import { makeRefIterator } from '@endo/daemon/ref-reader.js'; -import { makeReadonlyArrayGrainFromRemote } from '@endo/grain/captp.js'; +import { makeReadonlyGrainMapFromRemote } from '@endo/grain/captp.js'; import { h } from './util.js'; import { DeckManagerComponent, PlayGameComponent } from './game.js'; -// no way of resolving relative paths from the weblet -const projectRootPath = './demo/1kce'; -const makeThing = async (powers, importFullPath, resultName) => { - const workerName = 'MAIN'; - const powersName = 'NONE'; - const deck = await E(powers).importUnsafeAndEndow( - workerName, - importFullPath, - powersName, - resultName, - ); - return deck -} - -const makeNewDeck = async (powers) => { - const importFullPath = `${projectRootPath}/deck.js`; - const resultName = 'deck'; - return await makeThing(powers, importFullPath, resultName) -} - -const makeGame = async (powers) => { - const importFullPath = `${projectRootPath}/game.js`; - const resultName = 'game'; - return await makeThing(powers, importFullPath, resultName) -} - -export const App = ({ powers }) => { +export const App = ({ inventory }) => { const [deck, setDeck] = React.useState(undefined); - const [game, setGame] = React.useState(undefined); + const [{ game, stateGrain }, setGame] = React.useState({}); - const actions = { + const deckMgmt = { // deck mgmt async fetchDeck () { - // workaround for https://github.com/endojs/endo/issues/1843 - if (await E(powers).has('deck')) { - const deck = await E(powers).lookup('deck') + // has-check is workaround for https://github.com/endojs/endo/issues/1843 + if (await inventory.has('deck')) { + const deck = await inventory.lookup('deck') setDeck(deck) } }, async makeNewDeck () { - const deck = await makeNewDeck(powers) + const deck = await inventory.makeNewDeck() setDeck(deck) }, async addCardToDeck (card) { await E(deck).add(card); }, async addCardToDeckByName (cardName) { - const card = await E(powers).lookup(cardName) + const card = await inventory.lookup(cardName) await E(deck).add(card); }, - async reverseLookupCard (card) { - return await E(powers).reverseLookup(card) - }, - async getCardDetails (card) { - return await E(card).getDetails() - }, - async getCardRenderer (card) { - let renderer - try { - const code = await E(card).getRendererCode() - const compartment = new Compartment({ Math, console }) - const makeRenderer = compartment.evaluate(`(${code})`) - renderer = makeRenderer() - } catch (err) { - console.error(err) - // ignore missing or failed renderer - renderer = () => {} - } - return renderer - }, - - followCardsAtPlayerLocation (player, canceled) { - return makeRefIterator(E(game).followCardsAtPlayerLocation(player, canceled)) - }, - getCardsAtPlayerLocationGrain (player) { - const remoteGrain = E(game).getCardsAtPlayerLocationGrain(player) - return makeReadonlyArrayGrainFromRemote(remoteGrain) - }, - followPlayerHand (player, canceled) { - return makeRefIterator(E(player).followHand(canceled)) - }, - getPlayerHandGrain (player) { - const remoteGrain = E(player).getHandGrain() - return makeReadonlyArrayGrainFromRemote(remoteGrain) - }, - - // inventory - subscribeToNames () { - return makeRefIterator(E(powers).followNames()) - }, - async removeName (name) { - await E(powers).remove(name) - }, + } - // game + const gameMgmt = { async start () { // make game - const game = await makeGame(powers) - setGame(game) + const game = await inventory.makeGame() + const stateGrain = makeReadonlyGrainMapFromRemote(E(game).getStateGrain()) + setGame({ game, stateGrain }) await E(game).start(deck) }, async playCardFromHand (player, card, destinationPlayer) { @@ -114,7 +46,7 @@ export const App = ({ powers }) => { // on first render React.useEffect(() => { - actions.fetchDeck() + deckMgmt.fetchDeck() }, []); return ( @@ -130,8 +62,8 @@ export const App = ({ powers }) => { fontSize: '42px', }, }, ['🃏1kce🃏']), - !game && h(DeckManagerComponent, { key: 'deck-manager', actions, deck }), - deck && h(PlayGameComponent, { key: 'play-game-component', actions, game }), + !game && h(DeckManagerComponent, { key: 'deck-manager', deck, deckMgmt, inventory }), + deck && h(PlayGameComponent, { key: 'play-game-component', game, stateGrain, gameMgmt }), ]) ) }; \ No newline at end of file diff --git a/packages/cli/demo/1kce/ui/endo.js b/packages/cli/demo/1kce/ui/endo.js index 518d4c8fd4..caeebf594a 100644 --- a/packages/cli/demo/1kce/ui/endo.js +++ b/packages/cli/demo/1kce/ui/endo.js @@ -46,14 +46,14 @@ const useBrokenSubscriptionForArray = (getSubFn, deps) => { return state; } -export const ObjectsListObjectComponent = ({ actions, name }) => { +export const ObjectsListObjectComponent = ({ addAction, name }) => { return ( h('div', {}, [ h('span', { key: 'name'}, [name]), h('button', { key: 'add', onClick: async () => { - await actions.addCardToDeckByName(name) + await addAction(name) }, style: { margin: '6px', @@ -63,9 +63,9 @@ export const ObjectsListObjectComponent = ({ actions, name }) => { ) } -export const ObjectsListComponent = ({ actions }) => { +export const ObjectsListComponent = ({ inventory, addAction }) => { const names = useBrokenSubscriptionForArray( - () => actions.subscribeToNames(), + () => inventory.subscribeToNames(), [], ) @@ -79,7 +79,7 @@ export const ObjectsListComponent = ({ actions }) => { key: name, }, [ h('span', null, [ - h(ObjectsListObjectComponent, { actions, name }), + h(ObjectsListObjectComponent, { addAction, name }), ]), ]) }) diff --git a/packages/cli/demo/1kce/ui/game.js b/packages/cli/demo/1kce/ui/game.js index 96eed5ed45..db2f5ef745 100644 --- a/packages/cli/demo/1kce/ui/game.js +++ b/packages/cli/demo/1kce/ui/game.js @@ -1,18 +1,32 @@ import React from 'react'; import { E } from '@endo/far'; -import { makeReadonlyArrayGrainFromRemote, makeReadonlyGrainFromRemote } from '@endo/grain/captp.js'; -import { h, useRaf, useMouse, useAsync, keyForItems, useGrainGetter } from './util.js'; +import { makeReadonlyArrayGrainFromRemote } from '@endo/grain/captp.js'; +import { h, useRaf, useMouse, useAsync, keyForItems, useGrainGetter, useGrain } from './util.js'; import { ObjectsListComponent } from './endo.js'; +const getCardRenderer = async (card) => { + let renderer + try { + const code = await E(card).getRendererCode() + const compartment = new Compartment({ Math, console }) + const makeRenderer = compartment.evaluate(`(${code})`) + renderer = makeRenderer() + } catch (err) { + console.error(err) + // ignore missing or failed renderer + renderer = () => {} + } + return renderer +} -export const CardComponent = ({ actions, card }) => { +export const CardComponent = ({ card }) => { const { value: cardDetails } = useAsync(async () => { - return await actions.getCardDetails(card) + return await E(card).getDetails() }, [card]); const mouseData = useMouse() const canvasRef = React.useRef(null); const { value: render } = useAsync(async () => { - return await actions.getCardRenderer(card) + return await getCardRenderer(card) }, [card]); useRaf((timeElapsed) => { if (!render) return @@ -118,13 +132,13 @@ export const CardComponent = ({ actions, card }) => { ) }; -export const CardsDisplayComponent = ({ actions, cards, cardControlComponent }) => { +export const CardsDisplayComponent = ({ cards, cardControlComponent }) => { const cardsList = cards.map(card => { return ( h('div', { key: keyForItems(card), }, [ - h(CardComponent, { actions, card }), + h(CardComponent, { card }), cardControlComponent && h(cardControlComponent, { card }), ]) ) @@ -139,7 +153,7 @@ export const CardsDisplayComponent = ({ actions, cards, cardControlComponent }) ) } -export const DeckCardsComponent = ({ actions, deck }) => { +export const DeckCardsComponent = ({ deck }) => { const cards = useGrainGetter( () => makeReadonlyArrayGrainFromRemote( E(deck).getCardsGrain(), @@ -152,33 +166,38 @@ export const DeckCardsComponent = ({ actions, deck }) => { h('h3', { key: 'title' }, ['Cards in deck']), !deck && 'No deck found.', cards.length === 0 && 'No cards in deck.', - cards.length > 0 && h(CardsDisplayComponent, { key: 'cards', actions, cards }), + cards.length > 0 && h(CardsDisplayComponent, { key: 'cards', cards }), ]) ) }; -export const DeckManagerComponent = ({ actions, deck }) => { +export const DeckManagerComponent = ({ deck, deckMgmt, inventory }) => { + const addAction = (name) => { + return deckMgmt.addCardToDeckByName(name) + } return ( h('div', {}, [ h('h2', { key: 'title' }, ['Deck Manager']), h('button', { key: 'new-deck', onClick: async () => { - await actions.makeNewDeck() + await deckMgmt.makeNewDeck() }, }, ['New Deck']), - deck && h(ObjectsListComponent, { key: 'inventory', actions }), - deck && h(DeckCardsComponent, { key: 'deck', actions, deck }), + deck && h(ObjectsListComponent, { key: 'inventory', inventory, addAction }), + deck && h(DeckCardsComponent, { key: 'deck', deck }), ]) ) }; -export const GameCurrentPlayerComponent = ({ actions, player, players }) => { +export const GameCurrentPlayerComponent = ({ gameMgmt, player, players }) => { const { value: name } = useAsync(async () => { return await E(player).getName() }, [player]); const hand = useGrainGetter( - () => actions.getPlayerHandGrain(player), + () => makeReadonlyArrayGrainFromRemote( + E(player).getHandGrain(), + ), [player], ) @@ -192,7 +211,7 @@ export const GameCurrentPlayerComponent = ({ actions, player, players }) => { margin: '2px', }, onClick: async () => { - await actions.playCardFromHand(sourcePlayer, card, destPlayer) + await gameMgmt.playCardFromHand(sourcePlayer, card, destPlayer) }, }, [playLabel]) } @@ -224,47 +243,39 @@ export const GameCurrentPlayerComponent = ({ actions, player, players }) => { return ( h('div', {}, [ h('h3', null, [`Current Player: ${name}`]), - h(CardsDisplayComponent, { actions, cards: hand, cardControlComponent }), + h(CardsDisplayComponent, { cards: hand, cardControlComponent }), ]) ) } -export const GamePlayerAreaComponent = ({ actions, player, isCurrentPlayer }) => { +export const GamePlayerAreaComponent = ({ game, player, isCurrentPlayer }) => { const { value: name } = useAsync(async () => { return await E(player).getName() }, [player]); const playerAreaCards = useGrainGetter( - () => actions.getCardsAtPlayerLocationGrain(player), + () => makeReadonlyArrayGrainFromRemote( + E(game).getCardsAtPlayerLocationGrain(player), + ), [player], ) return ( h('div', {}, [ h('h4', null, [`${name} ${isCurrentPlayer ? '(current)' : ''}`]), - h(CardsDisplayComponent, { actions, cards: playerAreaCards }), + h(CardsDisplayComponent, { cards: playerAreaCards }), ]) ) } -export const ActiveGameComponent = ({ actions, game }) => { +export const ActiveGameComponent = ({ game, gameMgmt, stateGrain }) => { + const gameState = useGrain(stateGrain) const players = useGrainGetter( - () => makeReadonlyArrayGrainFromRemote( - E(game).getPlayersGrain(), - ), - [game], - ) - const gameState = useGrainGetter( - () => makeReadonlyGrainFromRemote( - E(game).getStateGrain(), - {}, - ), - [game], + () => stateGrain.getGrain('players'), + [stateGrain], ) const currentPlayer = useGrainGetter( - () => makeReadonlyGrainFromRemote( - E(game).getCurrentPlayerGrain(), - ), - [game], + () => stateGrain.getGrain('currentPlayer'), + [stateGrain], ) return ( @@ -277,18 +288,18 @@ export const ActiveGameComponent = ({ actions, game }) => { key: keyForItems(player), }, [ h(GamePlayerAreaComponent, { - actions, + game, player, isCurrentPlayer: player === currentPlayer, }), ]) })), - currentPlayer && h(GameCurrentPlayerComponent, { actions, player: currentPlayer, players }), + currentPlayer && h(GameCurrentPlayerComponent, { gameMgmt, player: currentPlayer, players }), ]) ) } -export const PlayGameComponent = ({ actions, game }) => { +export const PlayGameComponent = ({ game, stateGrain, gameMgmt }) => { return ( h('div', {}, [ h('h2', { @@ -297,10 +308,10 @@ export const PlayGameComponent = ({ actions, game }) => { !game && h('button', { key: 'start', onClick: async () => { - actions.start() + gameMgmt.start() }, }, ['Start']), - game && h(ActiveGameComponent, { key: 'game', actions, game }), + game && h(ActiveGameComponent, { key: 'game', game, gameMgmt, stateGrain }), ]) ) } diff --git a/packages/cli/demo/1kce/ui/index.js b/packages/cli/demo/1kce/ui/index.js index 93e5416828..ede937853f 100644 --- a/packages/cli/demo/1kce/ui/index.js +++ b/packages/cli/demo/1kce/ui/index.js @@ -4,7 +4,7 @@ import { createRoot } from 'react-dom/client'; import { h } from './util.js'; import { App } from './app.js'; -export const make = async powers => { +export const make = async ({ inventory }) => { document.body.innerHTML = ''; const style = document.createElement('style'); @@ -26,5 +26,5 @@ export const make = async powers => { document.body.appendChild(container); const root = createRoot(container); - root.render(h(App, { powers })); + root.render(h(App, { inventory })); }; diff --git a/packages/cli/demo/1kce/weblet.js b/packages/cli/demo/1kce/weblet.js index 5cb74c1292..120f00d28f 100644 --- a/packages/cli/demo/1kce/weblet.js +++ b/packages/cli/demo/1kce/weblet.js @@ -1 +1,45 @@ -export { make } from './ui/index.js'; \ No newline at end of file +import { E } from '@endo/far'; +import { makeRefIterator } from '@endo/daemon/ref-reader'; +import { make as makeApp } from './ui/index.js'; + +// no way of resolving relative paths from the weblet +const projectRootPath = './demo/1kce'; + +const makeThing = async (powers, importFullPath, resultName) => { + const workerName = 'MAIN'; + const powersName = 'NONE'; + const deck = await E(powers).importUnsafeAndEndow( + workerName, + importFullPath, + powersName, + resultName, + ); + return deck +} + +export const make = (powers) => { + // form inventory from powers + const inventory = { + subscribeToNames () { + return makeRefIterator(E(powers).followNames()) + }, + async has (name) { + return E(powers).has(name) + }, + async lookup (name) { + return await E(powers).lookup(name) + }, + async makeNewDeck () { + const importFullPath = `${projectRootPath}/deck.js`; + const resultName = 'deck'; + return await makeThing(powers, importFullPath, resultName) + }, + async makeGame () { + const importFullPath = `${projectRootPath}/game.js`; + const resultName = 'game'; + return await makeThing(powers, importFullPath, resultName) + }, + } + + return makeApp({ inventory }) +} \ No newline at end of file