diff --git a/packages/cli/demo/1kce.js b/packages/cli/demo/1kce.js index be8f53d5c8..f285f4b34a 100644 --- a/packages/cli/demo/1kce.js +++ b/packages/cli/demo/1kce.js @@ -4,7 +4,19 @@ import { E } from '@endo/far'; import { makeRefIterator } from '@endo/daemon/ref-reader.js'; import { createRoot } from 'react-dom/client'; import React from 'react'; -import { makeSyncGrainFromFollow } from './grain.js'; +import { makeSyncGrainFromFollow, makeReadonlyArrayGrainFromRemote } from './grain.js'; + +const randomString = () => Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); +const keyMap = new WeakMap() +const keyForItem = (item) => { + if (!keyMap.has(item)) { + keyMap.set(item, randomString()) + } + return keyMap.get(item) +} +const keyForItems = (...items) => { + return items.map((item) => typeof key === 'string' ? item : keyForItem(item)).join('-') +} const h = React.createElement; const useAsync = (asyncFn, deps) => { @@ -144,6 +156,24 @@ const useArrayGrainFollow = (getSubFn, deps) => { return useGrainFollow(getSubFn, deps, []) } +const useGrain = (grain) => { + const [grainValue, setGrainValue] = React.useState(grain.get()); + React.useEffect(() => { + const unsubscribe = grain.subscribe(value => { + setGrainValue(value) + }) + return () => { + unsubscribe(); + } + }) + return grainValue; +} + +const useGrainGetter = (grainGetter, deps) => { + const grain = useMemo(grainGetter, deps) + return useGrain(grain) +} + const useRaf = ( callback, isActive, @@ -228,10 +258,10 @@ export function useMouse() { } React.useEffect(() => { - document.addEventListener("mousemove", updateMousePosition); + document.addEventListener('mousemove', updateMousePosition); return () => { - document.removeEventListener("mousemove", updateMousePosition); + document.removeEventListener('mousemove', updateMousePosition); }; }, []); @@ -265,12 +295,6 @@ const makeGame = async (powers) => { } const DeckCardsCardComponent = ({ actions, card }) => { - const { value: nickname } = useAsync(async () => { - if (card === undefined) { - return ''; - } - return await actions.reverseLookupCard(card) - }, [card]); const { value: cardDetails } = useAsync(async () => { return await actions.getCardDetails(card) }, [card]); @@ -283,7 +307,7 @@ const DeckCardsCardComponent = ({ actions, card }) => { if (!render) return const canvas = canvasRef.current; if (!canvas) return - const ctx = canvas.getContext("2d"); + const ctx = canvas.getContext('2d'); const rect = canvas.getBoundingClientRect(); canvas.width = rect.width; canvas.height = rect.height; @@ -310,17 +334,19 @@ const DeckCardsCardComponent = ({ actions, card }) => { flexGrow: 0, overflow: 'hidden', position: 'relative', - } + }, }, [ h('canvas', { + key: 'card-art', ref: canvasRef, style: { position: 'absolute', width: '100%', height: '100%', - } + }, }), h('div', { + key: 'card-body', style: { position: 'absolute', overflow: 'hidden', @@ -329,14 +355,16 @@ const DeckCardsCardComponent = ({ actions, card }) => { }, }, [ h('div', { + key: 'card-body-inner', style: { display: 'flex', flexDirection: 'column', justifyContent: 'space-between', height: '100%', - } + }, }, [ h('span', { + key: 'title', title: cardName, style: { margin: '8px 12px', @@ -354,9 +382,10 @@ const DeckCardsCardComponent = ({ actions, card }) => { textOverflow: 'ellipsis', whiteSpace: 'nowrap', cursor: 'default', - } + }, }, [cardName]), h('pre', { + key: 'description', style: { margin: '8px 12px', padding: '6px 4px', @@ -370,9 +399,9 @@ const DeckCardsCardComponent = ({ actions, card }) => { color: 'aliceblue', cursor: 'default', whiteSpace: 'pre-wrap', - } + }, }, [cardDescription]), - ]) + ]), ]), ]) ) @@ -381,7 +410,9 @@ const DeckCardsCardComponent = ({ actions, card }) => { const CardsDisplayComponent = ({ actions, cards, cardControlComponent }) => { const cardsList = cards.map(card => { return ( - h('div', null, [ + h('div', { + key: keyForItems(card), + }, [ h(DeckCardsCardComponent, { actions, card }), cardControlComponent && h(cardControlComponent, { card }), ]) @@ -392,7 +423,7 @@ const CardsDisplayComponent = ({ actions, cards, cardControlComponent }) => { style: { display: 'flex', flexWrap: 'wrap', - } + }, }, cards.length > 0 ? cardsList : '(no cards)') ) } @@ -400,15 +431,15 @@ const CardsDisplayComponent = ({ actions, cards, cardControlComponent }) => { const DeckCardsComponent = ({ actions, deck }) => { const cards = useArrayGrainFollow( (canceled) => makeRefIterator(E(deck).follow(canceled)), - [deck] + [deck], ) return ( h('div', {}, [ - h('h3', null, ['Cards in deck']), + h('h3', { key: 'title' }, ['Cards in deck']), !deck && 'No deck found.', cards.length === 0 && 'No cards in deck.', - cards.length > 0 && h(CardsDisplayComponent, { actions, cards }), + cards.length > 0 && h(CardsDisplayComponent, { key: 'cards', actions, cards }), ]) ) }; @@ -416,14 +447,15 @@ const DeckCardsComponent = ({ actions, deck }) => { const DeckManagerComponent = ({ actions, deck }) => { return ( h('div', {}, [ - h('h2', null, ['Deck Manager']), - deck && h(ObjectsListComponent, { actions }), + h('h2', { key: 'title' }, ['Deck Manager']), h('button', { + key: 'new-deck', onClick: async () => { await actions.makeNewDeck() - } + }, }, ['New Deck']), - deck && h(DeckCardsComponent, { actions, deck }), + deck && h(ObjectsListComponent, { key: 'inventory', actions }), + deck && h(DeckCardsComponent, { key: 'deck', actions, deck }), ]) ) }; @@ -434,7 +466,7 @@ const GameCurrentPlayerComponent = ({ actions, player, players }) => { }, [player]); const hand = useArrayGrainFollow( (canceled) => actions.followPlayerHand(player, canceled), - [player] + [player], ) // specify a component to render under the cards @@ -442,12 +474,13 @@ const GameCurrentPlayerComponent = ({ actions, player, players }) => { const makePlayCardButton = ({ sourcePlayer, destPlayer }) => { const playLabel = sourcePlayer === destPlayer ? `Play on self` : `Play on ${destPlayer}` return h('button', { + key: keyForItems(sourcePlayer, destPlayer), style: { margin: '2px', }, onClick: async () => { await actions.playCardFromHand(sourcePlayer, card, destPlayer) - } + }, }, [playLabel]) } const playControls = [ @@ -459,7 +492,7 @@ const GameCurrentPlayerComponent = ({ actions, player, players }) => { return null } return makePlayCardButton({ sourcePlayer: player, destPlayer: otherPlayer }) - }) + }), ] return ( h('div', { @@ -468,7 +501,7 @@ const GameCurrentPlayerComponent = ({ actions, player, players }) => { justifyContent: 'center', alignItems: 'center', flexDirection: 'column', - } + }, }, [ ...playControls, ]) @@ -489,8 +522,12 @@ const GamePlayerAreaComponent = ({ actions, player, isCurrentPlayer }) => { }, [player]); const playerAreaCards = useArrayGrainFollow( (canceled) => actions.followCardsAtPlayerLocation(player, canceled), - [player] + [player], ) + // const playerAreaCards = useGrainGetter( + // () => actions.getCardsAtPlayerLocationGrain(player), + // [player] + // ) return ( h('div', {}, [ @@ -503,29 +540,31 @@ const GamePlayerAreaComponent = ({ actions, player, isCurrentPlayer }) => { const ActiveGameComponent = ({ actions, game }) => { const players = useArrayGrainFollow( (canceled) => makeRefIterator(E(game).followPlayers(canceled)), - [game] + [game], ) const gameState = useGrainFollow( (canceled) => makeRefIterator(E(game).followState(canceled)), - [game] + [game], ) const currentPlayer = useGrainFollow( (canceled) => makeRefIterator(E(game).followCurrentPlayer(canceled)), - [game] + [game], ) return ( h('div', {}, [ - h('h3', null, ['Game']), - h('pre', null, JSON.stringify(gameState, null, 2)), - h('h3', null, ['Players']), - h('div', null, players && players.map(player => { - return h('div', null, [ + h('h3', { key: 'title'}, ['Game']), + h('pre', { key: 'gamestate' }, JSON.stringify(gameState, null, 2)), + h('h3', { key: 'subtitle' }, ['Players']), + h('div', { key: 'players' }, players && players.map(player => { + return h('div', { + key: keyForItems(player), + }, [ h(GamePlayerAreaComponent, { actions, player, - isCurrentPlayer: player === currentPlayer - }) + isCurrentPlayer: player === currentPlayer, + }), ]) })), currentPlayer && h(GameCurrentPlayerComponent, { actions, player: currentPlayer, players }), @@ -536,13 +575,16 @@ const ActiveGameComponent = ({ actions, game }) => { const PlayGameComponent = ({ actions, game }) => { return ( h('div', {}, [ - h('h2', null, ['Play Game']), + h('h2', { + key: 'title', + }, ['Play Game']), !game && h('button', { + key: 'start', onClick: async () => { actions.start() - } + }, }, ['Start']), - game && h(ActiveGameComponent, { actions, game }), + game && h(ActiveGameComponent, { key: 'game', actions, game }), ]) ) } @@ -550,14 +592,15 @@ const PlayGameComponent = ({ actions, game }) => { const ObjectsListObjectComponent = ({ actions, name }) => { return ( h('div', {}, [ - h('span', null, [name]), + h('span', { key: 'name'}, [name]), h('button', { + key: 'add', onClick: async () => { await actions.addCardToDeckByName(name) }, style: { margin: '6px', - } + }, }, ['Add to Deck']), ]) ) @@ -566,7 +609,7 @@ const ObjectsListObjectComponent = ({ actions, name }) => { const ObjectsListComponent = ({ actions }) => { const names = useBrokenSubscriptionForArray( () => actions.subscribeToNames(), - [] + [], ) const uniqueNames = [...new Set(names)] @@ -575,9 +618,11 @@ const ObjectsListComponent = ({ actions }) => { objectList = 'No objects found.' } else { objectList = uniqueNames.map(name => { - return h('li', null, [ + return h('li', { + key: name, + }, [ h('span', null, [ - h(ObjectsListObjectComponent, { actions, name }) + h(ObjectsListObjectComponent, { actions, name }), ]), ]) }) @@ -585,8 +630,8 @@ const ObjectsListComponent = ({ actions }) => { return ( h('div', {}, [ - h('h3', null, ['Inventory']), - h('ul', null, objectList), + h('h3', { key: 'title' }, ['Inventory']), + h('ul', { key: 'list' }, objectList), ]) ) @@ -596,7 +641,7 @@ const ObjectsListComponent = ({ actions }) => { const GrainComponent = ({ grain }) => { const grainValue = useGrainFollow( (canceled) => makeRefIterator(grain.follow(canceled)), - [game] + [game], ) return ( @@ -628,9 +673,7 @@ const App = ({ powers }) => { await E(deck).add(card); }, async addCardToDeckByName (cardName) { - const card = await E(powers).lookup( - cardName, - ) + const card = await E(powers).lookup(cardName) await E(deck).add(card); }, async reverseLookupCard (card) { @@ -659,9 +702,17 @@ const App = ({ powers }) => { 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 () { @@ -691,6 +742,7 @@ const App = ({ powers }) => { return ( h('div', {}, [ h('h1', { + key: 'title', style: { display: 'inline', border: '2px solid black', @@ -698,11 +750,10 @@ const App = ({ powers }) => { padding: '4px', background: 'white', fontSize: '42px', - } + }, }, ['🃏1kce🃏']), - !game && h(DeckManagerComponent, { actions, deck }), - // h(FollowMessagesComponent, { powers }), - deck && h(PlayGameComponent, { actions, game }), + !game && h(DeckManagerComponent, { key: 'deck-manager', actions, deck }), + deck && h(PlayGameComponent, { key: 'play-game-component', actions, game }), ]) ) }; diff --git a/packages/cli/demo/cards/deja-vu.js b/packages/cli/demo/cards/deja-vu.js index 53ba778604..0cfa5b0d5d 100644 --- a/packages/cli/demo/cards/deja-vu.js +++ b/packages/cli/demo/cards/deja-vu.js @@ -1,4 +1,4 @@ -import { Far } from "@endo/far" +import { Far } from '@endo/far' export const make = (powers) => { return Far('deja vu', { @@ -21,7 +21,7 @@ export const make = (powers) => { function makeRenderer() { let isInitialized = false; - let elements = []; + const elements = []; const speed = 0.1; function initialize(rect) { @@ -31,7 +31,7 @@ function makeRenderer() { x: Math.random() * rect.width, y: Math.random() * rect.height, size: Math.random() * 20 + 5, - color: `hsla(${Math.random() * 360}, 100%, 50%, 0.7)` + color: `hsla(${Math.random() * 360}, 100%, 50%, 0.7)`, }); } } diff --git a/packages/cli/demo/cards/firmament.js b/packages/cli/demo/cards/firmament.js index e6ddc32bd8..099d5892dd 100644 --- a/packages/cli/demo/cards/firmament.js +++ b/packages/cli/demo/cards/firmament.js @@ -1,4 +1,4 @@ -import { Far } from "@endo/far" +import { Far } from '@endo/far' export const make = (powers) => { return Far('the firmament', { @@ -42,7 +42,7 @@ function makeRenderer () { function draw(context, rect, mousePos) { context.clearRect(0, 0, 600, 400); context.beginPath(); - context.fillStyle = "#EFEFEF"; + context.fillStyle = '#EFEFEF'; context.rect(0, 0, 600, 400); context.closePath(); context.fill(); @@ -54,7 +54,7 @@ function makeRenderer () { const px = p.x; const py = p.y; context.beginPath(); - context.fillStyle = "#000000"; + context.fillStyle = '#000000'; context.arc(px, py, radius, 0, Math.PI * 2, true); context.closePath(); context.fill(); diff --git a/packages/cli/demo/cards/fruitful-harvest.js b/packages/cli/demo/cards/fruitful-harvest.js index 7d27902f3d..1808d99152 100644 --- a/packages/cli/demo/cards/fruitful-harvest.js +++ b/packages/cli/demo/cards/fruitful-harvest.js @@ -1,4 +1,4 @@ -import { Far } from "@endo/far" +import { Far } from '@endo/far' export const make = (powers) => { return Far('fruitful harvest', { @@ -27,7 +27,7 @@ function makeRenderer() { fruits = Array.from({ length: 5 }, () => ({ x: Math.random() * rect.width, y: Math.random() * rect.height, - size: 10 + Math.random() * 10 // Size between 10 and 20 + size: 10 + Math.random() * 10, // Size between 10 and 20 })); } @@ -60,7 +60,7 @@ function makeRenderer() { // Change something based on mousePos.x and mousePos.y // Example: Increase the size of the nearest fruit const nearestFruit = fruits.reduce((nearest, fruit) => { - let distance = Math.sqrt((fruit.x - mousePos.x) ** 2 + (fruit.y - mousePos.y) ** 2); + const distance = Math.sqrt((fruit.x - mousePos.x) ** 2 + (fruit.y - mousePos.y) ** 2); return distance < nearest.distance ? { fruit, distance } : nearest; }, { fruit: null, distance: Infinity }); diff --git a/packages/cli/demo/cards/library-of-alexandria.js b/packages/cli/demo/cards/library-of-alexandria.js index ad53406aef..a6c7f49296 100644 --- a/packages/cli/demo/cards/library-of-alexandria.js +++ b/packages/cli/demo/cards/library-of-alexandria.js @@ -1,4 +1,4 @@ -import { E, Far } from "@endo/far" +import { E, Far } from '@endo/far' export const make = (powers) => { return Far('library of alexandria', { @@ -11,7 +11,7 @@ export const make = (powers) => { score += name.length * 10 } return score - } + }, })) }, getDetails () { diff --git a/packages/cli/demo/cards/lost-and-afraid.js b/packages/cli/demo/cards/lost-and-afraid.js index 9bf22f02f0..85a0ceee3a 100644 --- a/packages/cli/demo/cards/lost-and-afraid.js +++ b/packages/cli/demo/cards/lost-and-afraid.js @@ -1,4 +1,4 @@ -import { Far } from "@endo/far" +import { Far } from '@endo/far' export const make = (powers) => { return Far('lost and afraid', { @@ -28,19 +28,19 @@ function makeRenderer () { // c.height = box.height; // } - var light = { + const light = { x: 160, - y: 200 + y: 200, } - var colors = ["#f5c156", "#e6616b", "#5cd3ad"]; + const colors = ['#f5c156', '#e6616b', '#5cd3ad']; function drawLight (ctx) { ctx.beginPath(); ctx.arc(light.x, light.y, 10000, 0, 2 * Math.PI); - var gradient = ctx.createRadialGradient(light.x, light.y, 0, light.x, light.y, 1000); - gradient.addColorStop(0, "#3b4654"); - gradient.addColorStop(0.4, "#2c343f"); + const gradient = ctx.createRadialGradient(light.x, light.y, 0, light.x, light.y, 1000); + gradient.addColorStop(0, '#3b4654'); + gradient.addColorStop(0.4, '#2c343f'); ctx.fillStyle = gradient; ctx.fill(); } @@ -54,40 +54,40 @@ function makeRenderer () { this.color = colors[Math.floor((Math.random() * colors.length))]; this.getDots = function() { - var full = (Math.PI * 2) / 4; + const full = (Math.PI * 2) / 4; - var p1 = { + const p1 = { x: this.x + this.half_size * Math.sin(this.r), - y: this.y + this.half_size * Math.cos(this.r) + y: this.y + this.half_size * Math.cos(this.r), }; - var p2 = { + const p2 = { x: this.x + this.half_size * Math.sin(this.r + full), - y: this.y + this.half_size * Math.cos(this.r + full) + y: this.y + this.half_size * Math.cos(this.r + full), }; - var p3 = { + const p3 = { x: this.x + this.half_size * Math.sin(this.r + full * 2), - y: this.y + this.half_size * Math.cos(this.r + full * 2) + y: this.y + this.half_size * Math.cos(this.r + full * 2), }; - var p4 = { + const p4 = { x: this.x + this.half_size * Math.sin(this.r + full * 3), - y: this.y + this.half_size * Math.cos(this.r + full * 3) + y: this.y + this.half_size * Math.cos(this.r + full * 3), }; return { - p1: p1, - p2: p2, - p3: p3, - p4: p4 + p1, + p2, + p3, + p4, }; } this.rotate = function() { - var speed = (60 - this.half_size) / 200; + const speed = (60 - this.half_size) / 200; this.r += speed * 0.002; this.x += speed; this.y += speed; } this.draw = function(ctx) { - var dots = this.getDots(); + const dots = this.getDots(); ctx.beginPath(); ctx.moveTo(dots.p1.x, dots.p1.y); ctx.lineTo(dots.p2.x, dots.p2.y); @@ -104,37 +104,37 @@ function makeRenderer () { } } this.drawShadow = function(ctx) { - var dots = this.getDots(); - var angles = []; - var points = []; + const dots = this.getDots(); + const angles = []; + const points = []; for (const dot in dots) { - var angle = Math.atan2(light.y - dots[dot].y, light.x - dots[dot].x); - var endX = dots[dot].x + this.shadow_length * Math.sin(-angle - Math.PI / 2); - var endY = dots[dot].y + this.shadow_length * Math.cos(-angle - Math.PI / 2); + const angle = Math.atan2(light.y - dots[dot].y, light.x - dots[dot].x); + const endX = dots[dot].x + this.shadow_length * Math.sin(-angle - Math.PI / 2); + const endY = dots[dot].y + this.shadow_length * Math.cos(-angle - Math.PI / 2); angles.push(angle); points.push({ - endX: endX, - endY: endY, + endX, + endY, startX: dots[dot].x, - startY: dots[dot].y + startY: dots[dot].y, }); }; - for (var i = points.length - 1; i >= 0; i--) { - var n = i == 3 ? 0 : i + 1; + for (let i = points.length - 1; i >= 0; i--) { + const n = i == 3 ? 0 : i + 1; ctx.beginPath(); ctx.moveTo(points[i].startX, points[i].startY); ctx.lineTo(points[n].startX, points[n].startY); ctx.lineTo(points[n].endX, points[n].endY); ctx.lineTo(points[i].endX, points[i].endY); - ctx.fillStyle = "#2c343f"; + ctx.fillStyle = '#2c343f'; ctx.fill(); }; } } - var boxes = []; + const boxes = []; function draw(ctx, c, mousePos) { makeBoxes(c) @@ -168,11 +168,11 @@ function makeRenderer () { } function collisionDetection(b){ - for (var i = boxes.length - 1; i >= 0; i--) { + for (let i = boxes.length - 1; i >= 0; i--) { if(i != b){ - var dx = (boxes[b].x + boxes[b].half_size) - (boxes[i].x + boxes[i].half_size); - var dy = (boxes[b].y + boxes[b].half_size) - (boxes[i].y + boxes[i].half_size); - var d = Math.sqrt(dx * dx + dy * dy); + const dx = (boxes[b].x + boxes[b].half_size) - (boxes[i].x + boxes[i].half_size); + const dy = (boxes[b].y + boxes[b].half_size) - (boxes[i].y + boxes[i].half_size); + const d = Math.sqrt(dx * dx + dy * dy); if (d < boxes[b].half_size + boxes[i].half_size) { boxes[b].half_size = boxes[b].half_size > 1 ? boxes[b].half_size-=1 : 1; boxes[i].half_size = boxes[i].half_size > 1 ? boxes[i].half_size-=1 : 1; diff --git a/packages/cli/demo/cards/vivid-imagination.js b/packages/cli/demo/cards/vivid-imagination.js index 0ee62be2a2..25ee1ef0eb 100644 --- a/packages/cli/demo/cards/vivid-imagination.js +++ b/packages/cli/demo/cards/vivid-imagination.js @@ -1,4 +1,4 @@ -import { Far } from "@endo/far" +import { Far } from '@endo/far' export const make = (powers) => { return Far('vivid imagination', { @@ -52,14 +52,14 @@ function makeRenderer () { * */ const SimplexNoise = (function () { - "use strict"; + 'use strict'; - var F2 = 0.5 * (Math.sqrt(3.0) - 1.0), - G2 = (3.0 - Math.sqrt(3.0)) / 6.0, - F3 = 1.0 / 3.0, - G3 = 1.0 / 6.0, - F4 = (Math.sqrt(5.0) - 1.0) / 4.0, - G4 = (5.0 - Math.sqrt(5.0)) / 20.0; + const F2 = 0.5 * (Math.sqrt(3.0) - 1.0); + const G2 = (3.0 - Math.sqrt(3.0)) / 6.0; + const F3 = 1.0 / 3.0; + const G3 = 1.0 / 6.0; + const F4 = (Math.sqrt(5.0) - 1.0) / 4.0; + const G4 = (5.0 - Math.sqrt(5.0)) / 20.0; function SimplexNoise(random) { @@ -100,23 +100,23 @@ const SimplexNoise = (function () { - 1, 1, 0, 1, - 1, 1, 0, - 1, - 1, - 1, 0, 1, - 1, - 1, 0, - 1, 1, 1, 1, 0, 1, 1, - 1, 0, 1, - 1, 1, 0, 1, - 1, - 1, 0, - 1, 1, 1, 0, - 1, 1, - 1, 0, - 1, - 1, 1, 0, - 1, - 1, - 1, 0]), - noise2D: function (xin, yin) { - var permMod12 = this.permMod12, - perm = this.perm, - grad3 = this.grad3; - var n0=0, n1=0, n2=0; // Noise contributions from the three corners + noise2D (xin, yin) { + const permMod12 = this.permMod12; + const perm = this.perm; + const grad3 = this.grad3; + let n0=0; let n1=0; let n2=0; // Noise contributions from the three corners // Skew the input space to determine which simplex cell we're in - var s = (xin + yin) * F2; // Hairy factor for 2D - var i = Math.floor(xin + s); - var j = Math.floor(yin + s); - var t = (i + j) * G2; - var X0 = i - t; // Unskew the cell origin back to (x,y) space - var Y0 = j - t; - var x0 = xin - X0; // The x,y distances from the cell origin - var y0 = yin - Y0; + const s = (xin + yin) * F2; // Hairy factor for 2D + const i = Math.floor(xin + s); + const j = Math.floor(yin + s); + const t = (i + j) * G2; + const X0 = i - t; // Unskew the cell origin back to (x,y) space + const Y0 = j - t; + const x0 = xin - X0; // The x,y distances from the cell origin + const y0 = yin - Y0; // For the 2D case, the simplex shape is an equilateral triangle. // Determine which simplex we are in. - var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords + let i1; let j1; // Offsets for second (middle) corner of simplex in (i,j) coords if (x0 > y0) { i1 = 1; j1 = 0; @@ -128,29 +128,29 @@ const SimplexNoise = (function () { // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where // c = (3-sqrt(3))/6 - var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords - var y1 = y0 - j1 + G2; - var x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords - var y2 = y0 - 1.0 + 2.0 * G2; + const x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords + const y1 = y0 - j1 + G2; + const x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords + const y2 = y0 - 1.0 + 2.0 * G2; // Work out the hashed gradient indices of the three simplex corners - var ii = i & 255; - var jj = j & 255; + const ii = i & 255; + const jj = j & 255; // Calculate the contribution from the three corners - var t0 = 0.5 - x0 * x0 - y0 * y0; + let t0 = 0.5 - x0 * x0 - y0 * y0; if (t0 >= 0) { - var gi0 = permMod12[ii + perm[jj]] * 3; + const gi0 = permMod12[ii + perm[jj]] * 3; t0 *= t0; n0 = t0 * t0 * (grad3[gi0] * x0 + grad3[gi0 + 1] * y0); // (x,y) of grad3 used for 2D gradient } - var t1 = 0.5 - x1 * x1 - y1 * y1; + let t1 = 0.5 - x1 * x1 - y1 * y1; if (t1 >= 0) { - var gi1 = permMod12[ii + i1 + perm[jj + j1]] * 3; + const gi1 = permMod12[ii + i1 + perm[jj + j1]] * 3; t1 *= t1; n1 = t1 * t1 * (grad3[gi1] * x1 + grad3[gi1 + 1] * y1); } - var t2 = 0.5 - x2 * x2 - y2 * y2; + let t2 = 0.5 - x2 * x2 - y2 * y2; if (t2 >= 0) { - var gi2 = permMod12[ii + 1 + perm[jj + 1]] * 3; + const gi2 = permMod12[ii + 1 + perm[jj + 1]] * 3; t2 *= t2; n2 = t2 * t2 * (grad3[gi2] * x2 + grad3[gi2 + 1] * y2); } @@ -159,27 +159,27 @@ const SimplexNoise = (function () { return 70.0 * (n0 + n1 + n2); }, // 3D simplex noise - noise3D: function (xin, yin, zin) { - var permMod12 = this.permMod12, - perm = this.perm, - grad3 = this.grad3; - var n0, n1, n2, n3; // Noise contributions from the four corners + noise3D (xin, yin, zin) { + const permMod12 = this.permMod12; + const perm = this.perm; + const grad3 = this.grad3; + let n0; let n1; let n2; let n3; // Noise contributions from the four corners // Skew the input space to determine which simplex cell we're in - var s = (xin + yin + zin) * F3; // Very nice and simple skew factor for 3D - var i = Math.floor(xin + s); - var j = Math.floor(yin + s); - var k = Math.floor(zin + s); - var t = (i + j + k) * G3; - var X0 = i - t; // Unskew the cell origin back to (x,y,z) space - var Y0 = j - t; - var Z0 = k - t; - var x0 = xin - X0; // The x,y,z distances from the cell origin - var y0 = yin - Y0; - var z0 = zin - Z0; + const s = (xin + yin + zin) * F3; // Very nice and simple skew factor for 3D + const i = Math.floor(xin + s); + const j = Math.floor(yin + s); + const k = Math.floor(zin + s); + const t = (i + j + k) * G3; + const X0 = i - t; // Unskew the cell origin back to (x,y,z) space + const Y0 = j - t; + const Z0 = k - t; + const x0 = xin - X0; // The x,y,z distances from the cell origin + const y0 = yin - Y0; + const z0 = zin - Z0; // For the 3D case, the simplex shape is a slightly irregular tetrahedron. // Determine which simplex we are in. - var i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords - var i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + let i1; let j1; let k1; // Offsets for second corner of simplex in (i,j,k) coords + let i2; let j2; let k2; // Offsets for third corner of simplex in (i,j,k) coords if (x0 >= y0) { if (y0 >= z0) { i1 = 1; @@ -236,45 +236,45 @@ const SimplexNoise = (function () { // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where // c = 1/6. - var x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords - var y1 = y0 - j1 + G3; - var z1 = z0 - k1 + G3; - var x2 = x0 - i2 + 2.0 * G3; // Offsets for third corner in (x,y,z) coords - var y2 = y0 - j2 + 2.0 * G3; - var z2 = z0 - k2 + 2.0 * G3; - var x3 = x0 - 1.0 + 3.0 * G3; // Offsets for last corner in (x,y,z) coords - var y3 = y0 - 1.0 + 3.0 * G3; - var z3 = z0 - 1.0 + 3.0 * G3; + const x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords + const y1 = y0 - j1 + G3; + const z1 = z0 - k1 + G3; + const x2 = x0 - i2 + 2.0 * G3; // Offsets for third corner in (x,y,z) coords + const y2 = y0 - j2 + 2.0 * G3; + const z2 = z0 - k2 + 2.0 * G3; + const x3 = x0 - 1.0 + 3.0 * G3; // Offsets for last corner in (x,y,z) coords + const y3 = y0 - 1.0 + 3.0 * G3; + const z3 = z0 - 1.0 + 3.0 * G3; // Work out the hashed gradient indices of the four simplex corners - var ii = i & 255; - var jj = j & 255; - var kk = k & 255; + const ii = i & 255; + const jj = j & 255; + const kk = k & 255; // Calculate the contribution from the four corners - var t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0; + let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0; if (t0 < 0) n0 = 0.0; else { - var gi0 = permMod12[ii + perm[jj + perm[kk]]] * 3; + const gi0 = permMod12[ii + perm[jj + perm[kk]]] * 3; t0 *= t0; n0 = t0 * t0 * (grad3[gi0] * x0 + grad3[gi0 + 1] * y0 + grad3[gi0 + 2] * z0); } - var t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1; + let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1; if (t1 < 0) n1 = 0.0; else { - var gi1 = permMod12[ii + i1 + perm[jj + j1 + perm[kk + k1]]] * 3; + const gi1 = permMod12[ii + i1 + perm[jj + j1 + perm[kk + k1]]] * 3; t1 *= t1; n1 = t1 * t1 * (grad3[gi1] * x1 + grad3[gi1 + 1] * y1 + grad3[gi1 + 2] * z1); } - var t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2; + let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2; if (t2 < 0) n2 = 0.0; else { - var gi2 = permMod12[ii + i2 + perm[jj + j2 + perm[kk + k2]]] * 3; + const gi2 = permMod12[ii + i2 + perm[jj + j2 + perm[kk + k2]]] * 3; t2 *= t2; n2 = t2 * t2 * (grad3[gi2] * x2 + grad3[gi2 + 1] * y2 + grad3[gi2 + 2] * z2); } - var t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3; + let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3; if (t3 < 0) n3 = 0.0; else { - var gi3 = permMod12[ii + 1 + perm[jj + 1 + perm[kk + 1]]] * 3; + const gi3 = permMod12[ii + 1 + perm[jj + 1 + perm[kk + 1]]] * 3; t3 *= t3; n3 = t3 * t3 * (grad3[gi3] * x3 + grad3[gi3 + 1] * y3 + grad3[gi3 + 2] * z3); } @@ -283,36 +283,36 @@ const SimplexNoise = (function () { return 32.0 * (n0 + n1 + n2 + n3); }, // 4D simplex noise, better simplex rank ordering method 2012-03-09 - noise4D: function (x, y, z, w) { - var permMod12 = this.permMod12, - perm = this.perm, - grad4 = this.grad4; + noise4D (x, y, z, w) { + const permMod12 = this.permMod12; + const perm = this.perm; + const grad4 = this.grad4; - var n0, n1, n2, n3, n4; // Noise contributions from the five corners + let n0; let n1; let n2; let n3; let n4; // Noise contributions from the five corners // Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in - var s = (x + y + z + w) * F4; // Factor for 4D skewing - var i = Math.floor(x + s); - var j = Math.floor(y + s); - var k = Math.floor(z + s); - var l = Math.floor(w + s); - var t = (i + j + k + l) * G4; // Factor for 4D unskewing - var X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space - var Y0 = j - t; - var Z0 = k - t; - var W0 = l - t; - var x0 = x - X0; // The x,y,z,w distances from the cell origin - var y0 = y - Y0; - var z0 = z - Z0; - var w0 = w - W0; + const s = (x + y + z + w) * F4; // Factor for 4D skewing + const i = Math.floor(x + s); + const j = Math.floor(y + s); + const k = Math.floor(z + s); + const l = Math.floor(w + s); + const t = (i + j + k + l) * G4; // Factor for 4D unskewing + const X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space + const Y0 = j - t; + const Z0 = k - t; + const W0 = l - t; + const x0 = x - X0; // The x,y,z,w distances from the cell origin + const y0 = y - Y0; + const z0 = z - Z0; + const w0 = w - W0; // For the 4D case, the simplex is a 4D shape I won't even try to describe. // To find out which of the 24 possible simplices we're in, we need to // determine the magnitude ordering of x0, y0, z0 and w0. // Six pair-wise comparisons are performed between each possible pair // of the four coordinates, and the results are used to rank the numbers. - var rankx = 0; - var ranky = 0; - var rankz = 0; - var rankw = 0; + let rankx = 0; + let ranky = 0; + let rankz = 0; + let rankw = 0; if (x0 > y0) rankx++; else ranky++; if (x0 > z0) rankx++; @@ -325,9 +325,9 @@ const SimplexNoise = (function () { else rankw++; if (z0 > w0) rankz++; else rankw++; - var i1, j1, k1, l1; // The integer offsets for the second simplex corner - var i2, j2, k2, l2; // The integer offsets for the third simplex corner - var i3, j3, k3, l3; // The integer offsets for the fourth simplex corner + let i1; let j1; let k1; let l1; // The integer offsets for the second simplex corner + let i2; let j2; let k2; let l2; // The integer offsets for the third simplex corner + let i3; let j3; let k3; let l3; // The integer offsets for the fourth simplex corner // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. // Many values of c will never occur, since e.g. x>y>z>w makes x= 1 ? 1 : 0; l3 = rankw >= 1 ? 1 : 0; // The fifth corner has all coordinate offsets = 1, so no need to compute that. - var x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords - var y1 = y0 - j1 + G4; - var z1 = z0 - k1 + G4; - var w1 = w0 - l1 + G4; - var x2 = x0 - i2 + 2.0 * G4; // Offsets for third corner in (x,y,z,w) coords - var y2 = y0 - j2 + 2.0 * G4; - var z2 = z0 - k2 + 2.0 * G4; - var w2 = w0 - l2 + 2.0 * G4; - var x3 = x0 - i3 + 3.0 * G4; // Offsets for fourth corner in (x,y,z,w) coords - var y3 = y0 - j3 + 3.0 * G4; - var z3 = z0 - k3 + 3.0 * G4; - var w3 = w0 - l3 + 3.0 * G4; - var x4 = x0 - 1.0 + 4.0 * G4; // Offsets for last corner in (x,y,z,w) coords - var y4 = y0 - 1.0 + 4.0 * G4; - var z4 = z0 - 1.0 + 4.0 * G4; - var w4 = w0 - 1.0 + 4.0 * G4; + const x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords + const y1 = y0 - j1 + G4; + const z1 = z0 - k1 + G4; + const w1 = w0 - l1 + G4; + const x2 = x0 - i2 + 2.0 * G4; // Offsets for third corner in (x,y,z,w) coords + const y2 = y0 - j2 + 2.0 * G4; + const z2 = z0 - k2 + 2.0 * G4; + const w2 = w0 - l2 + 2.0 * G4; + const x3 = x0 - i3 + 3.0 * G4; // Offsets for fourth corner in (x,y,z,w) coords + const y3 = y0 - j3 + 3.0 * G4; + const z3 = z0 - k3 + 3.0 * G4; + const w3 = w0 - l3 + 3.0 * G4; + const x4 = x0 - 1.0 + 4.0 * G4; // Offsets for last corner in (x,y,z,w) coords + const y4 = y0 - 1.0 + 4.0 * G4; + const z4 = z0 - 1.0 + 4.0 * G4; + const w4 = w0 - 1.0 + 4.0 * G4; // Work out the hashed gradient indices of the five simplex corners - var ii = i & 255; - var jj = j & 255; - var kk = k & 255; - var ll = l & 255; + const ii = i & 255; + const jj = j & 255; + const kk = k & 255; + const ll = l & 255; // Calculate the contribution from the five corners - var t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; + let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; if (t0 < 0) n0 = 0.0; else { - var gi0 = (perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32) * 4; + const gi0 = (perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32) * 4; t0 *= t0; n0 = t0 * t0 * (grad4[gi0] * x0 + grad4[gi0 + 1] * y0 + grad4[gi0 + 2] * z0 + grad4[gi0 + 3] * w0); } - var t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; + let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; if (t1 < 0) n1 = 0.0; else { - var gi1 = (perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32) * 4; + const gi1 = (perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32) * 4; t1 *= t1; n1 = t1 * t1 * (grad4[gi1] * x1 + grad4[gi1 + 1] * y1 + grad4[gi1 + 2] * z1 + grad4[gi1 + 3] * w1); } - var t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; + let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; if (t2 < 0) n2 = 0.0; else { - var gi2 = (perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32) * 4; + const gi2 = (perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32) * 4; t2 *= t2; n2 = t2 * t2 * (grad4[gi2] * x2 + grad4[gi2 + 1] * y2 + grad4[gi2 + 2] * z2 + grad4[gi2 + 3] * w2); } - var t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; + let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; if (t3 < 0) n3 = 0.0; else { - var gi3 = (perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32) * 4; + const gi3 = (perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32) * 4; t3 *= t3; n3 = t3 * t3 * (grad4[gi3] * x3 + grad4[gi3 + 1] * y3 + grad4[gi3 + 2] * z3 + grad4[gi3 + 3] * w3); } - var t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; + let t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; if (t4 < 0) n4 = 0.0; else { - var gi4 = (perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32) * 4; + const gi4 = (perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32) * 4; t4 *= t4; n4 = t4 * t4 * (grad4[gi4] * x4 + grad4[gi4 + 1] * y4 + grad4[gi4 + 2] * z4 + grad4[gi4 + 3] * w4); } // Sum up and scale the result to cover the range [-1,1] return 27.0 * (n0 + n1 + n2 + n3 + n4); - } + }, }; @@ -419,28 +419,28 @@ const SimplexNoise = (function () { // Configs - var Configs = { + const Configs = { backgroundColor: '#eee9e9', particleNum: 1000, step: 5, base: 1000, - zInc: 0.001 + zInc: 0.001, }; // Vars - var canvas, - context, - screenWidth, - screenHeight, - centerX, - centerY, - particles = [], - hueBase = 0, - simplexNoise, - zoff = 0, - gui; + let canvas; + let context; + let screenWidth; + let screenHeight; + let centerX; + let centerY; + const particles = []; + let hueBase = 0; + let simplexNoise; + let zoff = 0; + let gui; let initialized = false @@ -475,10 +475,10 @@ const SimplexNoise = (function () { // Functions function getNoise(x, y, z) { - var octaves = 4, - fallout = 0.5, - amp = 1, f = 1, sum = 0, - i; + const octaves = 4; + const fallout = 0.5; + let amp = 1; let f = 1; let sum = 0; + let i; for (i = 0; i < octaves; ++i) { amp *= fallout; @@ -506,9 +506,9 @@ const SimplexNoise = (function () { init(rect, context) } - var step = Configs.step, - base = Configs.base, - p, angle; + const step = Configs.step; + const base = Configs.base; + let p; let angle; for (let i = 0, len = particles.length; i < len; i++) { p = particles[i]; @@ -540,8 +540,12 @@ const SimplexNoise = (function () { /** - * HSLA - */ + * HSLA + * @param h + * @param s + * @param l + * @param a + */ function HSLA(h, s, l, a) { this.h = h || 0; this.s = s || 0; @@ -550,12 +554,15 @@ const SimplexNoise = (function () { } HSLA.prototype.toString = function() { - return 'hsla(' + this.h + ',' + (this.s * 100) + '%,' + (this.l * 100) + '%,' + this.a + ')'; + return `hsla(${ this.h },${ this.s * 100 }%,${ this.l * 100 }%,${ this.a })`; } /** - * Particle - */ + * Particle + * @param x + * @param y + * @param color + */ function Particle(x, y, color) { this.x = x || 0; this.y = y || 0; diff --git a/packages/cli/demo/deck.js b/packages/cli/demo/deck.js index 9ef0bbd7b1..bb436367ba 100644 --- a/packages/cli/demo/deck.js +++ b/packages/cli/demo/deck.js @@ -3,11 +3,11 @@ import { makeIteratorRef } from '@endo/daemon/reader-ref.js'; import { makeSyncArrayGrain } from './grain.js'; export const make = () => { - let cards = makeSyncArrayGrain(); + const cards = makeSyncArrayGrain(); return Far('Deck', { add (card) { cards.push(card); - return; + }, getCards () { return harden(cards.get().slice()); diff --git a/packages/cli/demo/game.js b/packages/cli/demo/game.js index 2833897699..3df67a1cb7 100644 --- a/packages/cli/demo/game.js +++ b/packages/cli/demo/game.js @@ -1,6 +1,6 @@ import { E, Far } from '@endo/far'; import { makeIteratorRef } from '@endo/daemon/reader-ref.js'; -import { makeSyncArrayGrain, makeSyncGrain, makeSyncGrainArrayMap, makeSyncGrainMap, makeDerivedSyncGrain, composeGrainsAsync, composeGrains } from './grain.js'; +import { makeSyncArrayGrain, makeSyncGrain, makeSyncGrainArrayMap, makeSyncGrainMap, makeDerivedSyncGrain, composeGrainsAsync, composeGrains, makeRemoteGrain } from './grain.js'; const playerRemoteToLocal = new Map() class Player { @@ -15,15 +15,20 @@ class Player { async followHand (canceled) { return makeIteratorRef(localPlayer.hand.follow(canceled)) }, + async getHandGrain () { + return makeRemoteGrain(localPlayer.hand) + }, async removeCard (card) { localPlayer.removeCard(card) - } + }, }) playerRemoteToLocal.set(this.remoteInterface, localPlayer) } + addCard (card) { this.hand.push(card) } + removeCard (card) { this.hand.splice(this.hand.get().indexOf(card), 1) } @@ -41,7 +46,7 @@ export function makeGame () { const localPlayers = makeSyncArrayGrain() const remotePlayers = makeDerivedSyncGrain( localPlayers, - localPlayers => localPlayers.map(localPlayer => localPlayer.remoteInterface) + localPlayers => localPlayers.map(localPlayer => localPlayer.remoteInterface), ) const followRemotePlayers = (canceled) => { return remotePlayers.follow(canceled) @@ -54,11 +59,11 @@ export function makeGame () { const currentPlayerIndex = makeSyncGrain(0) const currentLocalPlayer = composeGrains( { localPlayers, currentPlayerIndex }, - ({ localPlayers, currentPlayerIndex }) => localPlayers[currentPlayerIndex] + ({ localPlayers, currentPlayerIndex }) => localPlayers[currentPlayerIndex], ) const currentRemotePlayer = makeDerivedSyncGrain( currentLocalPlayer, - currentLocalPlayer => currentLocalPlayer?.remoteInterface + currentLocalPlayer => currentLocalPlayer?.remoteInterface, ) const followCurrentRemotePlayer = (canceled) => { return currentRemotePlayer.follow(canceled) @@ -78,7 +83,7 @@ export function makeGame () { const currentTurnPhase = makeSyncGrain(0) const currentTurnPhaseName = makeDerivedSyncGrain( currentTurnPhase, - currentTurnPhase => turnPhases.getAtIndex(currentTurnPhase) + currentTurnPhase => turnPhases.getAtIndex(currentTurnPhase), ) const advanceTurnPhase = () => { currentTurnPhase.update(currentTurnPhase => (currentTurnPhase + 1) % turnPhases.length) @@ -123,14 +128,14 @@ export function makeGame () { } return scores }, - [] + [], ) // deck const deckGrain = makeSyncArrayGrain() const deckCardsRemaining = makeDerivedSyncGrain( deckGrain, - deck => deck.length + deck => deck.length, ) const addCardToDeck = (card) => { deckGrain.push(card) @@ -245,7 +250,7 @@ export function makeGame () { log: logGrain, currentPlayer: currentPlayerIndex, currentTurnPhase: currentTurnPhaseName, - locations: locations, + locations, scores: scoresGrain, deck: deckGrain, deckCardsRemaining, @@ -256,6 +261,7 @@ export function makeGame () { // Far const game = { + state, addPlayer, start, playCardFromHand, @@ -296,5 +302,12 @@ export const make = (powers) => { 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)) + }, + async getStateGrain () { + return makeRemoteGrain(game.state) + }, }); }; \ No newline at end of file diff --git a/packages/cli/demo/grain.js b/packages/cli/demo/grain.js index 111185ee59..c27d5cbecd 100644 --- a/packages/cli/demo/grain.js +++ b/packages/cli/demo/grain.js @@ -1,8 +1,42 @@ import { makePromiseKit } from '@endo/promise-kit'; import { makeChangeTopic } from '@endo/daemon/pubsub.js'; +import { E, Far } from '@endo/far'; +import { makeIteratorRef } from '@endo/daemon/reader-ref.js'; +import { makeRefIterator } from '@endo/daemon/ref-reader.js'; + + +/* + +Design Notes + +Destroy: + + The "destroy" method is used to cancel subscriptions and prevent reads and writes. + It has confused the design more than any other element. + + I originally added it as a way of canceling subscriptions on the grain. + Then I found it useful for "makeSyncGrainFromFollow" so that when its + "follow" has ended, it should destroy itself so others dont read its stale value. + + but what about readonly iterfaces? should they expose a destroy bc you can subscribe to them + but should they affect their original writable grain's ubscriptions? we dont track them seperately + + i am confuse + +Derived Grains: + + Derived grains are grains that derive their value from another grain. + They include "makeDerivedSyncGrain", "makeAsyncDerivedSyncGrain", and "makeSyncGrainMap". + All of them can and should be made "lazy" where they only derive their value when they are read. + Additionally, they should only be subscribed to their source grain when they are read. + This allows a chain of derived grains to remain lazy throughout. + +*/ + const never = makePromiseKit().promise +// a helper for tracking grain lifecycle const makeDestroyController = () => { const destroyed = makePromiseKit() let isDestroyed = false @@ -17,6 +51,7 @@ const makeDestroyController = () => { } } +// a helper for making the follow method const makeFollowFromSubscribe = (subscribe, lifecycle, get) => { return (canceled = never) => { if (lifecycle.isDestroyed()) { @@ -26,7 +61,7 @@ const makeFollowFromSubscribe = (subscribe, lifecycle, get) => { const unsubscribe = subscribe(value => { topic.publisher.next(value); }) - let isDestroyed = false + const isDestroyed = false const destroy = () => { if (isDestroyed) return unsubscribe() @@ -42,6 +77,7 @@ const makeFollowFromSubscribe = (subscribe, lifecycle, get) => { } } +// the base grain, stores a single value export const makeSyncGrain = initValue => { const lifecycle = makeDestroyController() let value = initValue @@ -108,27 +144,26 @@ export const makeSyncGrain = initValue => { } return { - get, + ...readonly(), set, update, destroy, - readonly, - subscribe, - follow, } } -export const makeSyncArrayGrain = (initValue = []) => { - if (!Array.isArray(initValue)) { - throw new Error('initValue must be an array') +// a helper, adds array specific methods to a sync grain +// it also validates that the value is an array +// TODO: could also allow the value to be undefined, +// to imply an unsynced value from a remote grain +// see "makeReadonlyArrayGrainFromRemote" +export const makeArrayGrainFromSyncGrain = (syncGrain) => { + if (!Array.isArray(syncGrain.get())) { + throw new Error('grain value must be an array') } const { get, set: _set, - subscribe, - follow, - destroy, - } = makeSyncGrain(initValue) + } = syncGrain // override set to ensure an array const set = (newValue) => { if (!Array.isArray(newValue)) { @@ -182,21 +217,23 @@ export const makeSyncArrayGrain = (initValue = []) => { } const readonly = () => { return { - get, + ...syncGrain.readonly(), + // sync grain override methods + readonly, + // array grain methods getAtIndex, get length() { return getLength() }, - readonly, - subscribe, - follow, } } return { - get, + ...syncGrain, + // sync grain override methods set, - getAtIndex, + // array grain methods + ...readonly(), setAtIndex, updateAtIndex, push, @@ -204,20 +241,24 @@ export const makeSyncArrayGrain = (initValue = []) => { shift, unshift, splice, - get length() { - return getLength() - }, set length(length) { setLength(length) }, - destroy, - readonly, - subscribe, - follow, } +} +// makes a grain whose value is an array, with convenience methods +// TODO: could provide an optimized "followArray" method that sends mutations, +// instead of the whole array +export const makeSyncArrayGrain = (initValue = []) => { + if (!Array.isArray(initValue)) { + throw new Error('initValue must be an array') + } + const syncGrain = makeSyncGrain(initValue) + return makeArrayGrainFromSyncGrain(syncGrain) } +// makes a readonly grain whose value is updated from a "follow" async iterator export const makeSyncGrainFromFollow = (iterator, initValue) => { const grain = makeSyncGrain(initValue); (async () => { @@ -229,6 +270,9 @@ export const makeSyncGrainFromFollow = (iterator, initValue) => { return grain.readonly() } +// makes a grain whose value is mapped from another grain +// with a sync map function +// TODO: make lazy export const makeDerivedSyncGrain = (grain, deriveFn) => { const derivedGrain = makeSyncGrain(deriveFn(grain.get())) grain.subscribe(value => { @@ -237,6 +281,10 @@ export const makeDerivedSyncGrain = (grain, deriveFn) => { return derivedGrain.readonly() } +// makes a grain whose value is mapped from another grain +// with an async map function +// The map function is async, so you may want to provide an initial value +// TODO: make lazy export const makeAsyncDerivedSyncGrain = (grain, derive, initValue) => { const derivedGrain = makeSyncGrain(initValue) grain.subscribe(async value => { @@ -245,6 +293,7 @@ export const makeAsyncDerivedSyncGrain = (grain, derive, initValue) => { return derivedGrain.readonly() } +// this is an attempt to rewrite "makeDerivedSyncGrain" to be lazy // TODO: propagate destruction of the grain to the derived grain export const makeLazyDerivedSyncGrain = (grain, deriveFn) => { const lifecycle = makeDestroyController() @@ -315,7 +364,10 @@ export const makeLazyDerivedSyncGrain = (grain, deriveFn) => { return readonly() } -// TODO: can be made lazy +// makes a grain whose value is an object, with keys and values that +// match the keys and values of the provided grains +// TODO: make lazy +// TODO: allow overwriting grain keys, unsubscribing from old grain // TODO: propagate destroy export const makeSyncGrainMap = (grains = {}) => { const lifecycle = makeDestroyController() @@ -391,6 +443,8 @@ export const makeSyncGrainMap = (grains = {}) => { } } +// makes a grain whose value is an "ArrayMap", +// a map whose values are always initialized arrays export const makeSyncGrainArrayMap = (grains = {}) => { const grainMap = makeSyncGrainMap(grains) @@ -404,37 +458,136 @@ export const makeSyncGrainArrayMap = (grains = {}) => { getGrain(key).push(item) } - const { - get, - hasGrain, - setGrain, - destroy, - readonly, - subscribe, - follow, - } = grainMap; + const readonly = () => { + const getGrainReadOnly = () => { + return getGrain(key).readonly() + } + return { + ...grainMap.readonly(), + getGrain: getGrainReadOnly, + } + } return { - get, - hasGrain, + ...grainMap, + // grainMap override methods getGrain, - setGrain, - destroy, - readonly, - subscribe, - follow, push, + readonly, } } +// makes a grain whose value is mapped from many grains +// with a sync map function. under the hood, it uses a GrainMap export const composeGrains = (grains, deriveFn) => { const grainMap = makeSyncGrainMap(grains) const grain = makeDerivedSyncGrain(grainMap, deriveFn) return grain } +// makes a grain whose value is mapped from many grains +// with a sync map function. under the hood, it uses a GrainMap +// The map function is async, so you may want to provide an initial value export const composeGrainsAsync = (grains, deriveFn, initValue) => { const grainMap = makeSyncGrainMap(grains) const grain = makeAsyncDerivedSyncGrain(grainMap, deriveFn, initValue) return grain +} + +// given an AsyncGrain, returns a readonly SyncGrain that is subscribed to the remote grain +export const makeSubscribedSyncGrainFromAsyncGrain = (asyncGrain, initialValue) => { + const { promise: canceled, resolve: cancel } = makePromiseKit() + const syncGrain = makeSyncGrainFromFollow( + asyncGrain.follow(canceled), + initialValue, + ) + const destroy = () => { + cancel() + syncGrain.destroy?.() + } + // TODO: weird. is readonly but has a destroy method + return { + ...syncGrain.readonly(), + destroy, + } +} + +// +// captp +// + +// given a grain, returns a remote grain for sending over captp +export const makeRemoteGrain = (grain, name = 'grain') => { + return Far(name, { + ...grain, + follow: async (canceled) => { + return makeIteratorRef(E(grain).follow(canceled)) + }, + }) +} + +// an async grain is like a normal grain with an async interface +// its mostly useful for wrapping a remote grain, as we do here +export const makeLocalAsyncGrainFromRemote = (remoteGrain) => { + const get = async () => { + return E(remoteGrain).get() + } + const set = async (value) => { + return E(remoteGrain).set(value) + } + const update = async (update) => { + return E(remoteGrain).update(update) + } + const destroy = async () => { + return E(remoteGrain).destroy() + } + // TODO: the handler likely needs a Far wrapper to placate captp + const subscribe = async (handler) => { + return E(remoteGrain).subscribe(handler) + } + const follow = (canceled) => { + return makeRefIterator(E(remoteGrain).follow(canceled)) + } + // this is convenient but unless you provide an initial value, it will be uninitialized + const makeSubscribedSyncGrain = (initialValue) => { + return makeSubscribedSyncGrainFromAsyncGrain(remoteGrain, initialValue).readonly() + } + // this waits for the first value, at the cost of being async + const makeSubscribedSyncGrainAndInitialize = async () => { + const { promise: canceled, resolve: cancel } = makePromiseKit() + const grain = makeSubscribedSyncGrain() + // wait for first value, then unsubscribe + await grain.follow(canceled).next() + cancel() + // return initialized readonly grain + return grain + } + + const readonly = () => { + return { + get, + readonly, + subscribe, + follow, + makeSubscribedSyncGrain, + makeSubscribedSyncGrainAndInitialize, + } + } + + return { + ...readonly(), + set, + update, + destroy, + } +} + +// given a remote grain, returns a readonly array grain that is subscribed to the remote grain +// it is initialized with an empty array +// TODO: we could allow it to be initialized undefined to convey that the value is not synced yet +export const makeReadonlyArrayGrainFromRemote = (remoteGrain, initValue = []) => { + const localAsyncGrain = makeLocalAsyncGrainFromRemote(remoteGrain) + const localSyncGrain = localAsyncGrain.makeSubscribedSyncGrain(initValue) + const localArrayGrain = makeArrayGrainFromSyncGrain(localSyncGrain).readonly() + return localArrayGrain } \ No newline at end of file diff --git a/packages/cli/demo/util.js b/packages/cli/demo/util.js index e3cae3525c..30a12bcfa0 100644 --- a/packages/cli/demo/util.js +++ b/packages/cli/demo/util.js @@ -99,7 +99,7 @@ export const makeTrackedValue = (initValue) => { trackedValue.follow = () => { return (async function* currentAndSubsequentEntries() { const changes = topic.subscribe(); - yield { value: value }; + yield { value }; yield* changes; })(); }