-
Notifications
You must be signed in to change notification settings - Fork 44
Let the game render before a user being available #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| v18.20.4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,6 @@ | |
| "typescript": "5.4.2" | ||
| }, | ||
| "engines": { | ||
| "node": "18.x", | ||
| "pnpm": "8.x" | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Works perfectly fine with the latest pnpm version, so removed this to avoid further usage headaches. |
||
| "node": "18.x" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Emojimon</title> | ||
| <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🤠</text></svg>" /> | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoids console error when doing |
||
| </head> | ||
| <body class="bg-black text-white"> | ||
| <div id="react-root"></div> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,31 +1,35 @@ | ||
| import { useComponentValue } from "@latticexyz/react"; | ||
| import { SyncStep } from "@latticexyz/store-sync"; | ||
| import { singletonEntity } from "@latticexyz/store-sync/recs"; | ||
| import { useMUD } from "./MUDContext"; | ||
| import { GameBoard } from "./GameBoard"; | ||
| import { useState } from "react"; | ||
| import { ToastContainer } from "react-toastify"; | ||
| import { LoadingWrapper } from "./LoadingWrapper"; | ||
| import { createBurnerWalletClient } from "./mud/account"; | ||
| import { useDevTools } from "./mud/devTools"; | ||
| import { useFaucet } from "./mud/faucet"; | ||
| import { useSetup } from "./mud/setup"; | ||
| import { WalletClientWithAccount } from "./mud/types"; | ||
| import { MUDProvider } from "./MUDContext"; | ||
|
|
||
| const walletClient = createBurnerWalletClient() | ||
|
|
||
| export const App = () => { | ||
| const { | ||
| components: { SyncProgress }, | ||
| } = useMUD(); | ||
| const [wallet, setWallet] = useState<WalletClientWithAccount | undefined>(); | ||
| const mud = useSetup(wallet); | ||
| useFaucet(wallet?.account.address); | ||
| useDevTools(mud); | ||
|
|
||
| const loadingState = useComponentValue(SyncProgress, singletonEntity, { | ||
| step: SyncStep.INITIALIZE, | ||
| message: "Connecting", | ||
| percentage: 0, | ||
| latestBlockNumber: 0n, | ||
| lastBlockNumberProcessed: 0n, | ||
| }); | ||
| const connect = () => setWallet(walletClient); | ||
| const disconnect = () => setWallet(undefined); | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Obviously silly here since we have the burner from the get-go, but any login/wallet system can inject its own wallet client. |
||
|
|
||
| return ( | ||
| <div className="w-screen h-screen flex items-center justify-center"> | ||
| {loadingState.step !== SyncStep.LIVE ? ( | ||
| <div> | ||
| {loadingState.message} ({loadingState.percentage.toFixed(2)}%) | ||
| return mud | ||
| ? <MUDProvider value={mud}> | ||
| <div style={{ position: "absolute", top: "0", right: "0", padding: "20px" }}> | ||
| {!wallet | ||
| ? <button onClick={connect} style={{ cursor: "pointer" }}>Connect</button> | ||
| : <button onClick={disconnect} style={{ cursor: "pointer" }}>Disconnect</button>} | ||
| </div> | ||
| ) : ( | ||
| <GameBoard /> | ||
| )} | ||
| </div> | ||
| ); | ||
| <LoadingWrapper/>; | ||
| <ToastContainer position="bottom-right" draggable={false} theme="dark"/> | ||
| </MUDProvider> | ||
| : <div className="w-screen h-screen flex items-center justify-center"> | ||
| <div>Setting up ...</div> | ||
| </div> | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { useComponentValue } from "@latticexyz/react"; | ||
| import { SyncStep } from "@latticexyz/store-sync"; | ||
| import { singletonEntity } from "@latticexyz/store-sync/recs"; | ||
| import { useMUD } from "./MUDContext"; | ||
| import { GameBoard } from "./GameBoard"; | ||
|
|
||
| export const LoadingWrapper = () => { | ||
| const { | ||
| components: { SyncProgress }, | ||
| } = useMUD(); | ||
|
|
||
| const loadingState = useComponentValue(SyncProgress, singletonEntity, { | ||
| step: SyncStep.INITIALIZE, | ||
| message: "Connecting", | ||
| percentage: 0, | ||
| latestBlockNumber: 0n, | ||
| lastBlockNumberProcessed: 0n, | ||
| }); | ||
|
|
||
| return ( | ||
| <div className="w-screen h-screen flex items-center justify-center"> | ||
| {loadingState.step !== SyncStep.LIVE ? ( | ||
| <div> | ||
| {loadingState.message} ({loadingState.percentage.toFixed(2)}%) | ||
| </div> | ||
| ) : ( | ||
| <GameBoard /> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| import { networkConfig } from "./networkConfig"; | ||
| import { ContractWrite, createBurnerAccount, transportObserver } from "@latticexyz/common"; | ||
| import { transactionQueue, writeObserver } from "@latticexyz/common/actions"; | ||
| import { Subject } from "rxjs"; | ||
| import { | ||
| Account, | ||
| Address, | ||
| ClientConfig, | ||
| createPublicClient, | ||
| createWalletClient, | ||
| fallback, | ||
| Hex, | ||
| http, | ||
| Transport, | ||
| webSocket, | ||
| } from "viem"; | ||
|
|
||
| const clientOptions = { | ||
| chain: networkConfig.chain, | ||
| transport: transportObserver(fallback([webSocket(), http()])), | ||
| pollingInterval: 1000, | ||
| } as const satisfies ClientConfig; | ||
|
|
||
| /* | ||
| * Create a viem public (read only) client | ||
| * (https://viem.sh/docs/clients/public.html) | ||
| */ | ||
| export const publicClient = createPublicClient(clientOptions); | ||
|
|
||
| /* | ||
| * Create an observable for contract writes that we can | ||
| * pass into MUD dev tools for transaction observability. | ||
| */ | ||
| export const write$ = new Subject<ContractWrite>(); | ||
|
|
||
| export function createEmojimonWalletClient(account: Account | Address, transport?: Transport) { | ||
| return createWalletClient({ | ||
| ...clientOptions, | ||
| transport: transport ?? clientOptions.transport, | ||
| account, | ||
| }) | ||
| .extend(transactionQueue()) | ||
| .extend(writeObserver({ onWrite: (write) => write$.next(write) })); | ||
| } | ||
|
|
||
| export function createBurnerWalletClient() { | ||
| const burnerAccount = createBurnerAccount(networkConfig.burnerPrivateKey as Hex); | ||
| return createEmojimonWalletClient(burnerAccount); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import mudConfig from "contracts/mud.config" | ||
| import { useEffect, useRef } from "react" | ||
| import { Destructor, MUDInterface, noop } from "./types" | ||
|
|
||
| const noopPromise = Promise.resolve(noop); | ||
|
|
||
| /** | ||
| * Mounts the dev tools in development mode. | ||
| */ | ||
| export function useDevTools(mud?: MUDInterface) { | ||
| const unmountDevtoolsPromise = useRef(noopPromise); | ||
|
|
||
| useEffect(() => { | ||
| if (!mud) return noop; | ||
|
|
||
| unmountDevtoolsPromise.current.then(async (unmount) => { | ||
| unmount() | ||
| unmountDevtoolsPromise.current = mountDevTools(mud); | ||
| }) | ||
|
|
||
| return () => { | ||
| unmountDevtoolsPromise.current.then(async (unmount) => { | ||
| unmount() | ||
| unmountDevtoolsPromise.current = noopPromise; | ||
| }); | ||
| } | ||
| }, [mud]); | ||
| } | ||
|
|
||
| async function mountDevTools(mud: MUDInterface): Promise<Destructor> { | ||
| if (!import.meta.env.DEV) return noop; | ||
|
|
||
| // Avoid loading when not in dev mode. | ||
| const { mount } = await import("@latticexyz/dev-tools"); | ||
|
|
||
| return await mount({ | ||
| config: mudConfig, | ||
| publicClient: mud.network.publicClient, | ||
| walletClient: mud.network.walletClient!, // this will work even if undefined | ||
| latestBlock$: mud.network.latestBlock$, | ||
| storedBlockLogs$: mud.network.storedBlockLogs$, | ||
| worldAddress: mud.network.worldContract.address, | ||
| worldAbi: mud.network.worldContract.abi, | ||
| write$: mud.network.write$, | ||
| recsWorld: mud.network.world, | ||
| }) ?? noop; | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| import { useEffect } from "react" | ||
| import { noop } from "rxjs" | ||
| import { Address, parseEther } from "viem" | ||
| import { publicClient } from "./account" | ||
| import { networkConfig } from "./networkConfig" | ||
| import { Destructor } from "./types" | ||
| import { createFaucetService } from "@latticexyz/services/faucet"; | ||
|
|
||
| /* | ||
| * If there is a faucet, request (test) ETH if you have less than 1 ETH. | ||
| * Repeat every 20 seconds to ensure you don't run out. | ||
| */ | ||
| export function useFaucet(address?: Address) { | ||
| return useEffect(() => { | ||
| if (!address) return noop; | ||
| const stopFaucet = startFaucet(address); | ||
| return () => { | ||
| stopFaucet(); | ||
| }; | ||
| }, [address]); | ||
| } | ||
|
|
||
| function startFaucet(address: Address): Destructor { | ||
| if (!address || !networkConfig.faucetServiceUrl) return noop; | ||
|
|
||
| console.info("[Dev Faucet]: Player address -> ", address); | ||
|
|
||
| const faucet = createFaucetService(networkConfig.faucetServiceUrl); | ||
|
|
||
| const requestDrip = async () => { | ||
| const balance = await publicClient.getBalance({ address }); | ||
| console.info(`[Dev Faucet]: Player balance -> ${balance}`); | ||
| const lowBalance = balance < parseEther("1"); | ||
| if (lowBalance) { | ||
| console.info("[Dev Faucet]: Balance is low, dripping funds to player"); | ||
| // Double drip | ||
| await faucet.dripDev({ address }); | ||
| await faucet.dripDev({ address }); | ||
| } | ||
| }; | ||
|
|
||
| // Request a drip now, then every 20 seconds | ||
| void requestDrip(); | ||
| const interval = setInterval(requestDrip, 20000); | ||
|
|
||
| return () => clearInterval(interval); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LIttle nicety since this expects an older Node version than the curren stable. It unfortunately doesn't work with the newer node (I checked), something about asserts. Most likely this go away if the packages are updated, which I have not done here (but can do).