From aec5c23eb8ee9d8e3830878e5d2f59ebdc930efa Mon Sep 17 00:00:00 2001 From: democritus Date: Tue, 7 Nov 2023 19:54:24 +0100 Subject: [PATCH 1/5] phasher --- examples/react-phaser-example/readme.md | 22 +- examples/react-phaser-example/src/App.css | 0 examples/react-phaser-example/src/App.tsx | 6 +- .../react-phaser-example/src/DojoContext.tsx | 93 +++++++ .../src/dojo/createNetworkLayer.ts | 17 +- .../react-phaser-example/src/dojo/setup.ts | 2 +- .../src/dojo/setupNetwork.ts | 8 - .../react-phaser-example/src/dojo/world.ts | 2 +- .../src/generated/graphql.ts | 252 ------------------ .../src/graphql/schema.graphql | 19 -- .../src/phaser/systems/spawn.ts | 12 +- .../react-phaser-example/src/ui/spawnbtn.tsx | 43 +-- 12 files changed, 144 insertions(+), 332 deletions(-) delete mode 100644 examples/react-phaser-example/src/App.css create mode 100644 examples/react-phaser-example/src/DojoContext.tsx delete mode 100644 examples/react-phaser-example/src/generated/graphql.ts delete mode 100644 examples/react-phaser-example/src/graphql/schema.graphql diff --git a/examples/react-phaser-example/readme.md b/examples/react-phaser-example/readme.md index 630c96d2..8012680b 100644 --- a/examples/react-phaser-example/readme.md +++ b/examples/react-phaser-example/readme.md @@ -1,21 +1 @@ -## Dojo create-phaser-app - -The pattern here is inherited from mud.dev and is a simple example of how to use the dojo engine with create-phaser-app. - -### Steps to Execute the Example - -Firstly, clone the [dojo-starter](https://github.com/dojoengine/dojo-starter) project and follow the given instructions to ensure it's functioning correctly. This step is crucial as this client displays data obtained from the project. - -Subsequently, clone this project and execute the following commands in the terminal: - -```console -yarn - -yarn generate - -yarn dev -``` - -### Notes - -This is just a simple example of how to setup a project, but in by no means is the only way. \ No newline at end of file +## Integrate Phaser with Dojo: A Quick Start Guide diff --git a/examples/react-phaser-example/src/App.css b/examples/react-phaser-example/src/App.css deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/react-phaser-example/src/App.tsx b/examples/react-phaser-example/src/App.tsx index 4f2a4011..afefae08 100644 --- a/examples/react-phaser-example/src/App.tsx +++ b/examples/react-phaser-example/src/App.tsx @@ -1,9 +1,9 @@ -import "./App.css"; import { useEffect } from "react"; import { useNetworkLayer } from "./hooks/useNetworkLayer"; import { PhaserLayer } from "./phaser/phaserLayer"; import { store } from "./store/store"; import { UI } from "./ui"; +import { DojoProvider } from "./DojoContext"; function App() { const networkLayer = useNetworkLayer(); @@ -20,7 +20,9 @@ function App() {
- + + +
); } diff --git a/examples/react-phaser-example/src/DojoContext.tsx b/examples/react-phaser-example/src/DojoContext.tsx new file mode 100644 index 00000000..4b9664da --- /dev/null +++ b/examples/react-phaser-example/src/DojoContext.tsx @@ -0,0 +1,93 @@ +import { BurnerProvider, useBurner } from "@dojoengine/create-burner"; +import { ReactNode, createContext, useContext, useMemo } from "react"; +import { Account, RpcProvider } from "starknet"; + +interface DojoContextType { + masterAccount: Account; +} + +const DojoContext = createContext(null); + +const requiredEnvs = [ + "VITE_PUBLIC_MASTER_ADDRESS", + "VITE_PUBLIC_MASTER_PRIVATE_KEY", + "VITE_PUBLIC_ACCOUNT_CLASS_HASH", +]; + +for (const env of requiredEnvs) { + if (!import.meta.env[env]) { + throw new Error(`Environment variable ${env} is not set!`); + } +} + +type DojoProviderProps = { + children: ReactNode; +}; + +export const DojoProvider = ({ children }: DojoProviderProps) => { + const currentValue = useContext(DojoContext); + if (currentValue) throw new Error("DojoProvider can only be used once"); + + const rpcProvider = useMemo( + () => + new RpcProvider({ + nodeUrl: + import.meta.env.VITE_PUBLIC_NODE_URL || + "http://localhost:5050", + }), + [] + ); + + const masterAddress = import.meta.env.VITE_PUBLIC_MASTER_ADDRESS; + const privateKey = import.meta.env.VITE_PUBLIC_MASTER_PRIVATE_KEY; + const accountClassHash = import.meta.env.VITE_PUBLIC_ACCOUNT_CLASS_HASH; + const masterAccount = useMemo( + () => new Account(rpcProvider, masterAddress, privateKey), + [rpcProvider, masterAddress, privateKey] + ); + + return ( + + + {children} + + + ); +}; + +// UI Wrapper for Burner. This could be done better. +export const useAccount = () => { + const contextValue = useContext(DojoContext); + if (!contextValue) + throw new Error( + "The `useAccount` hook must be used within a `DojoProvider`" + ); + + const { + create, + list, + get, + account, + select, + isDeploying, + clear, + copyToClipboard, + applyFromClipboard, + } = useBurner(); + + return { + account: { + create, + list, + get, + select, + clear, + account: account ?? contextValue.masterAccount, + isDeploying, + copyToClipboard, + applyFromClipboard, + }, + }; +}; diff --git a/examples/react-phaser-example/src/dojo/createNetworkLayer.ts b/examples/react-phaser-example/src/dojo/createNetworkLayer.ts index 0a1194ff..242f4f02 100644 --- a/examples/react-phaser-example/src/dojo/createNetworkLayer.ts +++ b/examples/react-phaser-example/src/dojo/createNetworkLayer.ts @@ -1,23 +1,34 @@ import { world } from "./world"; import { setup } from "./setup"; import { Account, RpcProvider } from "starknet"; +import { BurnerManager } from "@dojoengine/create-burner"; export type NetworkLayer = Awaited>; export const createNetworkLayer = async () => { const { components, systemCalls, network } = await setup(); - const provider = new RpcProvider({ + const rpcProvider = new RpcProvider({ nodeUrl: import.meta.env.VITE_PUBLIC_NODE_URL!, }); + const accountClassHash = import.meta.env.VITE_PUBLIC_ACCOUNT_CLASS_HASH!; + // TODO: Make Burner System - const account = new Account( - provider, + const masterAccount = new Account( + rpcProvider, import.meta.env.VITE_PUBLIC_MASTER_ADDRESS!, import.meta.env.VITE_PUBLIC_MASTER_PRIVATE_KEY! ); + const burnerManager = new BurnerManager({ + masterAccount, + accountClassHash, + rpcProvider, + }); + + const account = burnerManager.account || masterAccount; + return { world, components, diff --git a/examples/react-phaser-example/src/dojo/setup.ts b/examples/react-phaser-example/src/dojo/setup.ts index 1fb9e411..4e2a7f73 100644 --- a/examples/react-phaser-example/src/dojo/setup.ts +++ b/examples/react-phaser-example/src/dojo/setup.ts @@ -13,4 +13,4 @@ export async function setup() { components, systemCalls, }; -} \ No newline at end of file +} diff --git a/examples/react-phaser-example/src/dojo/setupNetwork.ts b/examples/react-phaser-example/src/dojo/setupNetwork.ts index 5223a7fd..d466fc66 100644 --- a/examples/react-phaser-example/src/dojo/setupNetwork.ts +++ b/examples/react-phaser-example/src/dojo/setupNetwork.ts @@ -2,8 +2,6 @@ import { defineContractComponents } from "./contractComponents"; import { world } from "./world"; import { RPCProvider } from "@dojoengine/core"; import { Account, num } from "starknet"; -import { GraphQLClient } from "graphql-request"; -import { getSdk } from "../generated/graphql"; import manifest from "../../../dojo-starter/target/dev/manifest.json"; import * as torii from "@dojoengine/torii-client"; @@ -24,9 +22,6 @@ export async function setupNetwork() { VITE_PUBLIC_NODE_URL ); - const createGraphSdk = () => - getSdk(new GraphQLClient(VITE_PUBLIC_TORII + "/graphql")); - const torii_client = await torii.createClient([], { rpcUrl: VITE_PUBLIC_NODE_URL, toriiUrl: VITE_PUBLIC_TORII + "/grpc", @@ -42,9 +37,6 @@ export async function setupNetwork() { // Define contract components for the world. contractComponents: defineContractComponents(world), - // Define the graph SDK instance. - graphSdk: createGraphSdk, - // Execute function. execute: async ( signer: Account, diff --git a/examples/react-phaser-example/src/dojo/world.ts b/examples/react-phaser-example/src/dojo/world.ts index 19bf1912..960676e5 100644 --- a/examples/react-phaser-example/src/dojo/world.ts +++ b/examples/react-phaser-example/src/dojo/world.ts @@ -1,3 +1,3 @@ import { createWorld } from "@dojoengine/recs"; -export const world = createWorld(); \ No newline at end of file +export const world = createWorld(); diff --git a/examples/react-phaser-example/src/generated/graphql.ts b/examples/react-phaser-example/src/generated/graphql.ts deleted file mode 100644 index 8ff86c1c..00000000 --- a/examples/react-phaser-example/src/generated/graphql.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { GraphQLClient } from 'graphql-request'; -import { GraphQLClientRequestHeaders } from 'graphql-request/build/cjs/types'; -import { print } from 'graphql' -import gql from 'graphql-tag'; -export type Maybe = T | null; -export type InputMaybe = Maybe; -export type Exact = { [K in keyof T]: T[K] }; -export type MakeOptional = Omit & { [SubKey in K]?: Maybe }; -export type MakeMaybe = Omit & { [SubKey in K]: Maybe }; -export type MakeEmpty = { [_ in K]?: never }; -export type Incremental = T | { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }; -/** All built-in and custom scalars, mapped to their actual values */ -export type Scalars = { - ID: { input: string; output: string; } - String: { input: string; output: string; } - Boolean: { input: boolean; output: boolean; } - Int: { input: number; output: number; } - Float: { input: number; output: number; } - Cursor: { input: any; output: any; } - DateTime: { input: any; output: any; } - felt252: { input: any; output: any; } - u8: { input: any; output: any; } - u32: { input: any; output: any; } -}; - -export type ComponentUnion = Moves | Position; - -export type Entity = { - __typename?: 'Entity'; - componentNames?: Maybe; - components?: Maybe>>; - createdAt?: Maybe; - id?: Maybe; - keys?: Maybe; - updatedAt?: Maybe; -}; - -export type EntityConnection = { - __typename?: 'EntityConnection'; - edges?: Maybe>>; - totalCount: Scalars['Int']['output']; -}; - -export type EntityEdge = { - __typename?: 'EntityEdge'; - cursor: Scalars['Cursor']['output']; - node?: Maybe; -}; - -export type Event = { - __typename?: 'Event'; - createdAt?: Maybe; - data?: Maybe; - id?: Maybe; - keys?: Maybe; - systemCall: SystemCall; - systemCallId?: Maybe; -}; - -export type EventConnection = { - __typename?: 'EventConnection'; - edges?: Maybe>>; - totalCount: Scalars['Int']['output']; -}; - -export type EventEdge = { - __typename?: 'EventEdge'; - cursor: Scalars['Cursor']['output']; - node?: Maybe; -}; - -export type Moves = { - __typename?: 'Moves'; - entity?: Maybe; - remaining?: Maybe; -}; - -export type MovesConnection = { - __typename?: 'MovesConnection'; - edges?: Maybe>>; - totalCount: Scalars['Int']['output']; -}; - -export type MovesEdge = { - __typename?: 'MovesEdge'; - cursor: Scalars['Cursor']['output']; - node?: Maybe; -}; - -export type Position = { - __typename?: 'Position'; - entity?: Maybe; - x?: Maybe; - y?: Maybe; -}; - -export type PositionConnection = { - __typename?: 'PositionConnection'; - edges?: Maybe>>; - totalCount: Scalars['Int']['output']; -}; - -export type PositionEdge = { - __typename?: 'PositionEdge'; - cursor: Scalars['Cursor']['output']; - node?: Maybe; -}; - -export type Query = { - __typename?: 'Query'; - entities?: Maybe; - entity: Entity; - event: Event; - events?: Maybe; - movesComponents?: Maybe; - positionComponents?: Maybe; - system: System; - systemCall: SystemCall; - systemCalls?: Maybe; - systems?: Maybe; -}; - - -export type QueryEntitiesArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - keys: Array; - last?: InputMaybe; -}; - - -export type QueryEntityArgs = { - id: Scalars['ID']['input']; -}; - - -export type QueryEventArgs = { - id: Scalars['ID']['input']; -}; - - -export type QueryMovesComponentsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - -export type QueryPositionComponentsArgs = { - after?: InputMaybe; - before?: InputMaybe; - first?: InputMaybe; - last?: InputMaybe; -}; - - -export type QuerySystemArgs = { - id: Scalars['ID']['input']; -}; - - -export type QuerySystemCallArgs = { - id: Scalars['Int']['input']; -}; - -export type System = { - __typename?: 'System'; - classHash?: Maybe; - createdAt?: Maybe; - id?: Maybe; - name?: Maybe; - systemCalls: Array; - transactionHash?: Maybe; -}; - -export type SystemCall = { - __typename?: 'SystemCall'; - createdAt?: Maybe; - data?: Maybe; - id?: Maybe; - system: System; - systemId?: Maybe; - transactionHash?: Maybe; -}; - -export type SystemCallConnection = { - __typename?: 'SystemCallConnection'; - edges?: Maybe>>; - totalCount: Scalars['Int']['output']; -}; - -export type SystemCallEdge = { - __typename?: 'SystemCallEdge'; - cursor: Scalars['Cursor']['output']; - node?: Maybe; -}; - -export type SystemConnection = { - __typename?: 'SystemConnection'; - edges?: Maybe>>; - totalCount: Scalars['Int']['output']; -}; - -export type SystemEdge = { - __typename?: 'SystemEdge'; - cursor: Scalars['Cursor']['output']; - node?: Maybe; -}; - -export type GetEntitiesQueryVariables = Exact<{ [key: string]: never; }>; - - -export type GetEntitiesQuery = { __typename?: 'Query', entities?: { __typename?: 'EntityConnection', edges?: Array<{ __typename?: 'EntityEdge', node?: { __typename?: 'Entity', keys?: string | null, components?: Array<{ __typename: 'Moves', remaining?: any | null } | { __typename: 'Position', x?: any | null, y?: any | null } | null> | null } | null } | null> | null } | null }; - - -export const GetEntitiesDocument = gql` - query getEntities { - entities(keys: ["%"]) { - edges { - node { - keys - components { - __typename - ... on Moves { - remaining - } - ... on Position { - x - y - } - } - } - } - } -} - `; - -export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string) => Promise; - - -const defaultWrapper: SdkFunctionWrapper = (action, _operationName, _operationType) => action(); -const GetEntitiesDocumentString = print(GetEntitiesDocument); -export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) { - return { - getEntities(variables?: GetEntitiesQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<{ data: GetEntitiesQuery; extensions?: any; headers: Dom.Headers; status: number; }> { - return withWrapper((wrappedRequestHeaders) => client.rawRequest(GetEntitiesDocumentString, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getEntities', 'query'); - } - }; -} -export type Sdk = ReturnType; \ No newline at end of file diff --git a/examples/react-phaser-example/src/graphql/schema.graphql b/examples/react-phaser-example/src/graphql/schema.graphql deleted file mode 100644 index 3c9bd8a8..00000000 --- a/examples/react-phaser-example/src/graphql/schema.graphql +++ /dev/null @@ -1,19 +0,0 @@ -query getEntities { - entities(keys: ["%"]) { - edges { - node { - keys - components { - __typename - ... on Moves { - remaining - } - ... on Position { - x - y - } - } - } - } - } -} \ No newline at end of file diff --git a/examples/react-phaser-example/src/phaser/systems/spawn.ts b/examples/react-phaser-example/src/phaser/systems/spawn.ts index 67276b35..a90dacd2 100644 --- a/examples/react-phaser-example/src/phaser/systems/spawn.ts +++ b/examples/react-phaser-example/src/phaser/systems/spawn.ts @@ -1,20 +1,22 @@ import { PhaserLayer } from ".."; export const spawn = (layer: PhaserLayer) => { - const { scenes: { Main: { input }, }, networkLayer: { systemCalls: { spawn }, - account + account, }, } = layer; + console.log(account); + input.onKeyPress( - keys => keys.has("SPACE"), + (keys) => keys.has("SPACE"), () => { spawn(account); - }); -}; \ No newline at end of file + } + ); +}; diff --git a/examples/react-phaser-example/src/ui/spawnbtn.tsx b/examples/react-phaser-example/src/ui/spawnbtn.tsx index 61018ae6..5dd25603 100644 --- a/examples/react-phaser-example/src/ui/spawnbtn.tsx +++ b/examples/react-phaser-example/src/ui/spawnbtn.tsx @@ -1,39 +1,42 @@ +import { useAccount } from "../DojoContext"; import { useDojo } from "../hooks/useDojo"; import { ClickWrapper } from "./clickWrapper"; export const SpawnBtn = () => { - // const { - // account: { - // create, - // list, - // get, - // account, - // select, - // isDeploying - // }, - // networkLayer: { - // systemCalls: { spawn }, - // }, - // } = useDojo(); + const { + networkLayer: { + systemCalls: { spawn }, + }, + } = useDojo(); + + const { + account: { account, list, select, create, isDeploying }, + } = useAccount(); return ( - {/* +
select signer:{" "} - select(e.target.value)}> {list().map((account, index) => { - return + return ( + + ); })} -
*/} - {/* */} +
); -}; \ No newline at end of file +}; From c932062ac93d1dc02ebb5f7d3678c8583584f6f3 Mon Sep 17 00:00:00 2001 From: democritus Date: Tue, 7 Nov 2023 20:52:18 +0100 Subject: [PATCH 2/5] improve example --- examples/react-phaser-example/src/App.tsx | 5 +-- .../src/dojo/createNetworkLayer.ts | 6 ++- .../src/hooks/useDojo.tsx | 37 ------------------- .../src/phaser/systems/controls.ts | 24 ++++++------ .../src/phaser/systems/spawn.ts | 2 +- .../react-phaser-example/src/ui/spawnbtn.tsx | 31 ++++++++-------- 6 files changed, 34 insertions(+), 71 deletions(-) delete mode 100644 examples/react-phaser-example/src/hooks/useDojo.tsx diff --git a/examples/react-phaser-example/src/App.tsx b/examples/react-phaser-example/src/App.tsx index afefae08..40982626 100644 --- a/examples/react-phaser-example/src/App.tsx +++ b/examples/react-phaser-example/src/App.tsx @@ -3,7 +3,6 @@ import { useNetworkLayer } from "./hooks/useNetworkLayer"; import { PhaserLayer } from "./phaser/phaserLayer"; import { store } from "./store/store"; import { UI } from "./ui"; -import { DojoProvider } from "./DojoContext"; function App() { const networkLayer = useNetworkLayer(); @@ -20,9 +19,7 @@ function App() {
- - - +
); } diff --git a/examples/react-phaser-example/src/dojo/createNetworkLayer.ts b/examples/react-phaser-example/src/dojo/createNetworkLayer.ts index 242f4f02..2a86d0e6 100644 --- a/examples/react-phaser-example/src/dojo/createNetworkLayer.ts +++ b/examples/react-phaser-example/src/dojo/createNetworkLayer.ts @@ -27,13 +27,15 @@ export const createNetworkLayer = async () => { rpcProvider, }); - const account = burnerManager.account || masterAccount; + burnerManager.init(); + + // const account = burnerManager.account || masterAccount; return { world, components, systemCalls, network, - account, + account: burnerManager, }; }; diff --git a/examples/react-phaser-example/src/hooks/useDojo.tsx b/examples/react-phaser-example/src/hooks/useDojo.tsx deleted file mode 100644 index 180133ad..00000000 --- a/examples/react-phaser-example/src/hooks/useDojo.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Account, RpcProvider } from "starknet"; -import { NetworkLayer } from "../dojo/createNetworkLayer"; -import { PhaserLayer } from "../phaser"; -import { store } from "../store/store"; -import { useBurner } from "@dojoengine/create-burner"; - -export type UIStore = ReturnType; - -export const useDojo = () => { - const { networkLayer, phaserLayer } = store(); - - const provider = new RpcProvider({ - nodeUrl: import.meta.env.VITE_PUBLIC_NODE_URL, - }); - - // todo: allow connection with wallet providers - const masterAccount = new Account(provider, import.meta.env.VITE_PUBLIC_MASTER_ADDRESS!, import.meta.env.VITE_PUBLIC_MASTER_PRIVATE_KEY!) - - const { create, list, get, account, select, isDeploying } = useBurner(); - - if (phaserLayer === null) { - throw new Error("Store not initialized"); - } - - return { - networkLayer: networkLayer as NetworkLayer, - phaserLayer: phaserLayer as PhaserLayer, - account: { - create, - list, - get, - account: account ? account : masterAccount, - select, - isDeploying - } - } -}; \ No newline at end of file diff --git a/examples/react-phaser-example/src/phaser/systems/controls.ts b/examples/react-phaser-example/src/phaser/systems/controls.ts index 788ee38c..a0e91b0b 100644 --- a/examples/react-phaser-example/src/phaser/systems/controls.ts +++ b/examples/react-phaser-example/src/phaser/systems/controls.ts @@ -2,41 +2,41 @@ import { PhaserLayer } from ".."; import { Direction } from "../../utils"; export const controls = (layer: PhaserLayer) => { - const { scenes: { Main: { input }, }, networkLayer: { systemCalls: { move }, - account + account, }, } = layer; input.onKeyPress( - keys => keys.has("W"), + (keys) => keys.has("W"), () => { - move(account, Direction.Up); - }); + move(account.getActiveAccount()!, Direction.Up); + } + ); input.onKeyPress( - keys => keys.has("A"), + (keys) => keys.has("A"), () => { - move(account, Direction.Left); + move(account.getActiveAccount()!, Direction.Left); } ); input.onKeyPress( - keys => keys.has("S"), + (keys) => keys.has("S"), () => { - move(account, Direction.Down); + move(account.getActiveAccount()!, Direction.Down); } ); input.onKeyPress( - keys => keys.has("D"), + (keys) => keys.has("D"), () => { - move(account, Direction.Right); + move(account.getActiveAccount()!, Direction.Right); } ); -}; \ No newline at end of file +}; diff --git a/examples/react-phaser-example/src/phaser/systems/spawn.ts b/examples/react-phaser-example/src/phaser/systems/spawn.ts index a90dacd2..03c20e90 100644 --- a/examples/react-phaser-example/src/phaser/systems/spawn.ts +++ b/examples/react-phaser-example/src/phaser/systems/spawn.ts @@ -16,7 +16,7 @@ export const spawn = (layer: PhaserLayer) => { input.onKeyPress( (keys) => keys.has("SPACE"), () => { - spawn(account); + spawn(account.getActiveAccount()!); } ); }; diff --git a/examples/react-phaser-example/src/ui/spawnbtn.tsx b/examples/react-phaser-example/src/ui/spawnbtn.tsx index 5dd25603..f516df1a 100644 --- a/examples/react-phaser-example/src/ui/spawnbtn.tsx +++ b/examples/react-phaser-example/src/ui/spawnbtn.tsx @@ -1,27 +1,28 @@ -import { useAccount } from "../DojoContext"; -import { useDojo } from "../hooks/useDojo"; +import { store } from "../store/store"; import { ClickWrapper } from "./clickWrapper"; export const SpawnBtn = () => { - const { - networkLayer: { - systemCalls: { spawn }, - }, - } = useDojo(); + const networkLayer = store.getState().networkLayer; - const { - account: { account, list, select, create, isDeploying }, - } = useAccount(); + const account = networkLayer?.account; + + const calls = networkLayer?.systemCalls; + + const deployAccount = () => { + account?.create(); + }; return ( -
select signer:{" "} - account?.select(e.target.value)}> + {account?.list().map((account, index) => { return (
-
- select signer:{" "} - +
+
+ + +
+ +
+
signer:
+ + +
+
+ +
- ); }; diff --git a/examples/react-phaser-example/tailwind.config.js b/examples/react-phaser-example/tailwind.config.js new file mode 100644 index 00000000..40eda665 --- /dev/null +++ b/examples/react-phaser-example/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/packages/create-burner/src/hooks/index.ts b/packages/create-burner/src/hooks/index.ts index b115b1c2..53e3e02b 100644 --- a/packages/create-burner/src/hooks/index.ts +++ b/packages/create-burner/src/hooks/index.ts @@ -1 +1,2 @@ export * from "./useBurner"; +export * from "./useBurnerManager"; diff --git a/packages/create-burner/src/hooks/useBurnerManager.ts b/packages/create-burner/src/hooks/useBurnerManager.ts new file mode 100644 index 00000000..f34475f0 --- /dev/null +++ b/packages/create-burner/src/hooks/useBurnerManager.ts @@ -0,0 +1,144 @@ +import { useCallback, useEffect, useState } from "react"; +import { Account } from "starknet"; +import { BurnerConnector } from ".."; +import { BurnerManager } from "../manager/burnerManager"; +import { Burner } from "../types"; + +/** + * A React hook that takes the Burner Manager object avoiding the React Context. + * Useful for building apps without React Context. + * + * @returns An object with utility methods and properties. + */ +export const useBurnerManager = ({ + burnerManager, +}: { + burnerManager: BurnerManager; // Accepts the BurnerManager class as an optional parameter +}) => { + if (!burnerManager.masterAccount) { + throw new Error("BurnerManagerClass must be provided"); + } + + // State to manage the current active account. + const [account, setAccount] = useState(null); + + const [burnerUpdate, setBurnerUpdate] = useState(0); + + const [isDeploying, setIsDeploying] = useState(false); + + // On mount, initialize the burner manager and set the active account. + useEffect(() => { + burnerManager.init(); + setAccount(burnerManager.getActiveAccount()); + }, []); + + /** + * Lists all the burners available in the storage. + * + * @returns An array of Burner accounts. + */ + const list = useCallback((): Burner[] => { + return burnerManager.list(); + }, [burnerManager.list(), burnerUpdate]); + + /** + * Selects and sets a burner as the active account. + * + * @param address - The address of the burner account to set as active. + */ + const select = useCallback( + (address: string): void => { + burnerManager.select(address); + setAccount(burnerManager.getActiveAccount()); + }, + [burnerManager] + ); + + /** + * Retrieves a burner account based on its address. + * + * @param address - The address of the burner account to retrieve. + * @returns The Burner account corresponding to the provided address. + */ + const get = useCallback( + (address: string): Account => { + return burnerManager.get(address); + }, + [burnerManager] + ); + + /** + * Clears a burner account based on its address. + * + * @param address - The address of the burner account to retrieve. + * @returns The Burner account corresponding to the provided address. + */ + const clear = useCallback(() => { + burnerManager.clear(); + setBurnerUpdate((prev) => prev + 1); + }, [burnerManager]); + + /** + * Creates a new burner account and sets it as the active account. + * + * @returns A promise that resolves to the newly created Burner account. + */ + const create = useCallback(async (): Promise => { + burnerManager.setIsDeployingCallback(setIsDeploying); + const newAccount = await burnerManager.create(); + setAccount(newAccount); + return newAccount; + }, [burnerManager]); + + /** + * Generates a list of BurnerConnector instances for each burner account. These can be added to Starknet React. + * + * @returns An array of BurnerConnector instances. + */ + const listConnectors = useCallback((): BurnerConnector[] => { + // Retrieve all the burners. + const burners = list(); + + // Map each burner to its respective BurnerConnector instance. + return burners.map((burner) => { + return new BurnerConnector( + { + options: { + id: burner.address, + }, + }, + get(burner.address) + ); + }); + }, [burnerManager.isDeploying]); + + /** + * Copy burners to clipboard + */ + const copyToClipboard = useCallback(async () => { + await burnerManager.copyBurnersToClipboard(); + }, [burnerManager]); + + /** + * Set burners from clipboard + */ + const applyFromClipboard = useCallback(async () => { + await burnerManager.setBurnersFromClipboard(); + setAccount(burnerManager.getActiveAccount()); // set the active account + setBurnerUpdate((prev) => prev + 1); // re-fetch of the list + }, [burnerManager]); + + // Expose methods and properties for the consumers of this hook. + return { + get, + list, + select, + create, + listConnectors, + clear, + account, + isDeploying, + copyToClipboard, + applyFromClipboard, + }; +}; diff --git a/packages/create-burner/src/manager/burnerManager.ts b/packages/create-burner/src/manager/burnerManager.ts index 6eaf753a..2eed8623 100644 --- a/packages/create-burner/src/manager/burnerManager.ts +++ b/packages/create-burner/src/manager/burnerManager.ts @@ -1,22 +1,14 @@ -import { - Account, - AccountInterface, - CallData, - ec, - hash, - RpcProvider, - stark, -} from "starknet"; +import { Account, CallData, ec, hash, RpcProvider, stark } from "starknet"; import { Burner, BurnerManagerOptions, BurnerStorage } from "../types"; import Storage from "../utils/storage"; import { prefundAccount } from "./prefundAccount"; export class BurnerManager { - public masterAccount?: AccountInterface | Account; + public masterAccount: Account; public accountClassHash: string; public provider: RpcProvider; - public account: Account | null = null; + public account: Account; public isDeploying: boolean = false; public burnerAccounts: Burner[] = []; @@ -30,6 +22,7 @@ export class BurnerManager { this.masterAccount = masterAccount; this.accountClassHash = accountClassHash; this.provider = rpcProvider; + this.account = masterAccount; } public setIsDeployingCallback( @@ -71,7 +64,7 @@ export class BurnerManager { ?.getTransactionReceipt(storage[firstAddr].deployTx) .then((response) => { if (!response) { - this.account = null; + // this.account = this.masterAccount; Storage.remove("burners"); throw new Error( "Burners not deployed, chain may have restarted" diff --git a/packages/create-burner/src/types/index.ts b/packages/create-burner/src/types/index.ts index 7158e048..d341a77d 100644 --- a/packages/create-burner/src/types/index.ts +++ b/packages/create-burner/src/types/index.ts @@ -1,4 +1,4 @@ -import { Account, AccountInterface, RpcProvider } from "starknet"; +import { Account, RpcProvider } from "starknet"; export type BurnerStorage = { [address: string]: { @@ -15,7 +15,7 @@ export type Burner = { }; export interface BurnerManagerOptions { - masterAccount?: AccountInterface | Account; + masterAccount: Account; accountClassHash: string; rpcProvider: RpcProvider; } diff --git a/packages/utils/package.json b/packages/utils/package.json index 48737004..4336b23f 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,35 +1,36 @@ { - "name": "@dojoengine/utils", - "version": "0.1.36", - "description": "Helpful Dojo Utils", - "type": "module", - "scripts": { - "build": "tsup", - "test": "jest" - }, - "exports": { - ".": { - "import": "./dist/index.js", - "types": "./dist/index.d.ts" - } - }, - "author": "Loaf", - "license": "MIT", - "main": "dist/index.js", - "devDependencies": { - "@types/elliptic": "^6.4.14", - "@types/jest": "^29.5.0", - "@types/mocha": "^10.0.1", - "bun-types": "^1.0.1", - "fetch-mock": "^9.11.0", - "jest": "^29.5.0", - "ts-jest": "^29.1.0", - "typescript": "^5.0.3" - }, - "dependencies": { - "@dojoengine/recs": "0.1.35", - "micro-starknet": "^0.2.3", - "@latticexyz/utils": "^2.0.0-next.11", - "starknet": "^5.19.5" + "name": "@dojoengine/utils", + "version": "0.1.36", + "description": "Helpful Dojo Utils", + "type": "module", + "scripts": { + "build": "tsup", + "test": "jest" + }, + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" } + }, + "author": "Loaf", + "license": "MIT", + "main": "dist/index.js", + "devDependencies": { + "@types/elliptic": "^6.4.14", + "@types/jest": "^29.5.0", + "@types/mocha": "^10.0.1", + "bun-types": "^1.0.1", + "fetch-mock": "^9.11.0", + "jest": "^29.5.0", + "ts-jest": "^29.1.0", + "typescript": "^5.0.3" + }, + "dependencies": { + "@dojoengine/recs": "0.1.35", + "@latticexyz/utils": "^2.0.0-next.11", + "mathjs": "^12.0.0", + "micro-starknet": "^0.2.3", + "starknet": "^5.19.5" + } } diff --git a/packages/utils/src/game/index.ts b/packages/utils/src/game/index.ts new file mode 100644 index 00000000..e5fa4698 --- /dev/null +++ b/packages/utils/src/game/index.ts @@ -0,0 +1 @@ +export * from "./simplex"; diff --git a/packages/utils/src/game/simplex.ts b/packages/utils/src/game/simplex.ts new file mode 100644 index 00000000..f0d6b307 --- /dev/null +++ b/packages/utils/src/game/simplex.ts @@ -0,0 +1,164 @@ +import * as math from "mathjs"; + +const multiply = (a: any, b: any) => { + if (!Array.isArray(a) || !Array.isArray(b)) return math.multiply(a, b); + return a.map((v, i) => v * b[i]); +}; + +const floor = (a: any) => { + return a.map((v: any) => Math.floor(v)); +}; + +const step = (a: any, b: any) => { + return a.map((v: any, i: any) => (b[i] <= v ? 0 : 1)); +}; + +const mod289 = (x: any) => { + return x.map((v: any) => v - Math.floor(v * (1.0 / 289.0)) * 289.0); +}; + +const permute = (x: any) => { + x = x.map((v: any) => (v * 34.0 + 1.0) * v); + return mod289(x); +}; + +const taylorInvSqrt = (r: any) => { + return r.map((v: any) => 1.79284291400159 - 0.85373472095314 * v); +}; + +export const snoise = (v: any) => { + const C = [1.0 / 6.0, 1.0 / 3.0]; + const D = [0.0, 0.5, 1.0, 2.0]; + + // First corner + let i = floor(math.add(v, math.dot(v, [C[1], C[1], C[1]]))); + let x0 = math.add(math.subtract(v, i), math.dot(i, [C[0], C[0], C[0]])); + + // Other corners + let g = step([x0[1], x0[2], x0[0]], [x0[0], x0[1], x0[2]]); + let l = math.subtract(1.0, g); + let i1 = math.min( + [ + [g[0], g[1], g[2]], + [l[2], l[0], l[1]], + ], + 0 + ); + let i2 = math.max( + [ + [g[0], g[1], g[2]], + [l[2], l[0], l[1]], + ], + 0 + ); + + // x0 = x0 - 0.0 + 0.0 * C.xxx; + // x1 = x0 - i1 + 1.0 * C.xxx; + // x2 = x0 - i2 + 2.0 * C.xxx; + // x3 = x0 - 1.0 + 3.0 * C.xxx; + let x1 = math.add(math.subtract(x0, i1), [C[0], C[0], C[0]]); + let x2 = math.add(math.subtract(x0, i2), [C[1], C[1], C[1]]); // 2.0*C.x = 1/3 = C.y + let x3 = math.subtract(x0, [D[1], D[1], D[1]]); // -1.0+3.0*C.x = -0.5 = -D.y + + // Permutations + let p1 = permute(math.add(i[2], [0.0, i1[2], i2[2], 1.0])); + let p2 = permute(math.add(math.add(p1, i[1]), [0.0, i1[1], i2[1], 1.0])); + let p = permute(math.add(math.add(p2, i[0]), [0.0, i1[0], i2[0], 1.0])); + + // Gradients: 7x7 points over a square, mapped onto an octahedron. + // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) + let ns = [0.285714285714286, -0.928571428571428, 0.142857142857143]; // these must be *exact* + let j = math.subtract(p, multiply(49, floor(multiply(p, ns[2] * ns[2])))); // mod(p,7*7) + + let x_ = floor(multiply(j, ns[2])); + let y_ = floor(math.subtract(j, multiply(7, x_))); // mod(j,N) + + let x = math.add(multiply(x_, ns[0]), [ns[1], ns[1], ns[1], ns[1]]); + let y = math.add(multiply(y_, ns[0]), [ns[1], ns[1], ns[1], ns[1]]); + let h = math.subtract(math.subtract(1.0, math.abs(x)), math.abs(y)); + + let b0 = [x[0], x[1], y[0], y[1]]; + let b1 = [x[2], x[3], y[2], y[3]]; + //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; + //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; + let s0 = math.add(multiply(floor(b0), 2.0), 1.0); + let s1 = math.add(multiply(floor(b1), 2.0), 1.0); + let sh = multiply(-1, step(h, [0, 0, 0, 0])); + + let a0 = math.add( + [b0[0], b0[2], b0[1], b0[3]], + multiply([s0[0], s0[2], s0[1], s0[3]], [sh[0], sh[0], sh[1], sh[1]]) + ); + let a1 = math.add( + [b1[0], b1[2], b1[1], b1[3]], + multiply([s1[0], s1[2], s1[1], s1[3]], [sh[2], sh[2], sh[3], sh[3]]) + ); + + let p0 = [a0[0], a0[1], h[0]]; + p1 = [a0[2], a0[3], h[1]]; + p2 = [a1[0], a1[1], h[2]]; + let p3 = [a1[2], a1[3], h[3]]; + + // Normalise gradients + let norm = taylorInvSqrt([ + math.dot(p0, p0), + math.dot(p1, p1), + math.dot(p2, p2), + math.dot(p3, p3), + ]); + p0 = multiply(p0, norm[0]); + p1 = multiply(p1, norm[1]); + p2 = multiply(p2, norm[2]); + p3 = multiply(p3, norm[3]); + + // Mix final noise value + //@ts-ignore + let m = math.max( + [ + //@ts-ignore + math.subtract(0.5, [ + math.dot(x0, x0), + math.dot(x1, x1), + math.dot(x2, x2), + math.dot(x3, x3), + ]), + [0, 0, 0, 0], + ], + 0 + ); + m = multiply(m, m); + m = multiply(m, m); + let noise = multiply( + 105.0, + //@ts-ignore + math.dot(m, [ + math.dot(p0, x0), + math.dot(p1, x1), + math.dot(p2, x2), + math.dot(p3, x3), + ]) + ); + return noise; +}; + +export const recursiveSNoise = (p: any, pers: any, octaves: any) => { + let total = 0.0; + let frequency = 1.0; + let amplitude = 1.0; + let maxValue = 0.0; + + for (let i = 0; i < octaves; i++) { + total += snoise(multiply(p, frequency)) * amplitude; + maxValue += amplitude; + amplitude *= pers; + frequency *= 2.0; + } + + const noise = total / maxValue; + return noise; +}; + +export default { + snoise, + recursiveSNoise, +}; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 178cd64f..dbe308e7 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1 +1,2 @@ export * from "./utils"; +export * from "./game"; From a035d36998429d63baadc6642704488ae5112041 Mon Sep 17 00:00:00 2001 From: democritus Date: Wed, 8 Nov 2023 10:48:46 +0100 Subject: [PATCH 4/5] fix burner in example --- examples/dojo-starter | 2 +- .../react-phaser-example/src/DojoContext.tsx | 93 ------------------- .../src/dojo/createNetworkLayer.ts | 6 +- .../src/dojo/createSystemCalls.ts | 4 +- .../src/hooks/useDojo.tsx | 7 +- .../src/phaser/systems/controls.ts | 2 +- .../src/phaser/systems/spawn.ts | 2 +- .../react-phaser-example/src/ui/spawnbtn.tsx | 1 + .../src/manager/burnerManager.ts | 5 +- 9 files changed, 17 insertions(+), 105 deletions(-) delete mode 100644 examples/react-phaser-example/src/DojoContext.tsx diff --git a/examples/dojo-starter b/examples/dojo-starter index 9e247521..4dfa7bd5 160000 --- a/examples/dojo-starter +++ b/examples/dojo-starter @@ -1 +1 @@ -Subproject commit 9e247521af49c2ab2bf2f1ee5c79a61bbbf6c4a7 +Subproject commit 4dfa7bd54b161d123fa5e259881150fb03ae313c diff --git a/examples/react-phaser-example/src/DojoContext.tsx b/examples/react-phaser-example/src/DojoContext.tsx deleted file mode 100644 index 4b9664da..00000000 --- a/examples/react-phaser-example/src/DojoContext.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { BurnerProvider, useBurner } from "@dojoengine/create-burner"; -import { ReactNode, createContext, useContext, useMemo } from "react"; -import { Account, RpcProvider } from "starknet"; - -interface DojoContextType { - masterAccount: Account; -} - -const DojoContext = createContext(null); - -const requiredEnvs = [ - "VITE_PUBLIC_MASTER_ADDRESS", - "VITE_PUBLIC_MASTER_PRIVATE_KEY", - "VITE_PUBLIC_ACCOUNT_CLASS_HASH", -]; - -for (const env of requiredEnvs) { - if (!import.meta.env[env]) { - throw new Error(`Environment variable ${env} is not set!`); - } -} - -type DojoProviderProps = { - children: ReactNode; -}; - -export const DojoProvider = ({ children }: DojoProviderProps) => { - const currentValue = useContext(DojoContext); - if (currentValue) throw new Error("DojoProvider can only be used once"); - - const rpcProvider = useMemo( - () => - new RpcProvider({ - nodeUrl: - import.meta.env.VITE_PUBLIC_NODE_URL || - "http://localhost:5050", - }), - [] - ); - - const masterAddress = import.meta.env.VITE_PUBLIC_MASTER_ADDRESS; - const privateKey = import.meta.env.VITE_PUBLIC_MASTER_PRIVATE_KEY; - const accountClassHash = import.meta.env.VITE_PUBLIC_ACCOUNT_CLASS_HASH; - const masterAccount = useMemo( - () => new Account(rpcProvider, masterAddress, privateKey), - [rpcProvider, masterAddress, privateKey] - ); - - return ( - - - {children} - - - ); -}; - -// UI Wrapper for Burner. This could be done better. -export const useAccount = () => { - const contextValue = useContext(DojoContext); - if (!contextValue) - throw new Error( - "The `useAccount` hook must be used within a `DojoProvider`" - ); - - const { - create, - list, - get, - account, - select, - isDeploying, - clear, - copyToClipboard, - applyFromClipboard, - } = useBurner(); - - return { - account: { - create, - list, - get, - select, - clear, - account: account ?? contextValue.masterAccount, - isDeploying, - copyToClipboard, - applyFromClipboard, - }, - }; -}; diff --git a/examples/react-phaser-example/src/dojo/createNetworkLayer.ts b/examples/react-phaser-example/src/dojo/createNetworkLayer.ts index 76dda2bc..fbb828b0 100644 --- a/examples/react-phaser-example/src/dojo/createNetworkLayer.ts +++ b/examples/react-phaser-example/src/dojo/createNetworkLayer.ts @@ -24,15 +24,15 @@ export const createNetworkLayer = async () => { rpcProvider, }); + // TODO: Currently if you change wallets in the UI, phaser will not update. burnerManager.init(); - // const account = burnerManager.account || masterAccount; - return { world, components, systemCalls, network, - account: burnerManager, + account: burnerManager.account as Account, + burnerManager, }; }; diff --git a/examples/react-phaser-example/src/dojo/createSystemCalls.ts b/examples/react-phaser-example/src/dojo/createSystemCalls.ts index 6828a3b1..5ad180cd 100644 --- a/examples/react-phaser-example/src/dojo/createSystemCalls.ts +++ b/examples/react-phaser-example/src/dojo/createSystemCalls.ts @@ -22,6 +22,8 @@ export function createSystemCalls( ) { const spawn = async (props: SystemSigner) => { const signer = props.signer; + + console.log("spawn", signer.address); const entityId = signer.address.toString() as Entity; const positionId = uuid(); @@ -35,7 +37,7 @@ export function createSystemCalls( entity: entityId, value: { player: BigInt(entityId), - remaining: 10, + remaining: 100, last_direction: 0, }, }); diff --git a/examples/react-phaser-example/src/hooks/useDojo.tsx b/examples/react-phaser-example/src/hooks/useDojo.tsx index 309493d8..36e8382e 100644 --- a/examples/react-phaser-example/src/hooks/useDojo.tsx +++ b/examples/react-phaser-example/src/hooks/useDojo.tsx @@ -1,3 +1,4 @@ +import { Account } from "starknet"; import { NetworkLayer } from "../dojo/createNetworkLayer"; import { PhaserLayer } from "../phaser"; import { store } from "../store/store"; @@ -14,14 +15,16 @@ export const useDojo = () => { const { account, get, create, select, list, isDeploying, clear } = useBurnerManager({ - burnerManager: networkLayer.account, + burnerManager: networkLayer.burnerManager, }); + console.log("account", account); + return { networkLayer: networkLayer as NetworkLayer, phaserLayer: phaserLayer as PhaserLayer, account: { - account: account ? account : networkLayer.account.masterAccount, + account: account as Account, get, create, select, diff --git a/examples/react-phaser-example/src/phaser/systems/controls.ts b/examples/react-phaser-example/src/phaser/systems/controls.ts index bc3d7607..7f0cf57b 100644 --- a/examples/react-phaser-example/src/phaser/systems/controls.ts +++ b/examples/react-phaser-example/src/phaser/systems/controls.ts @@ -8,7 +8,7 @@ export const controls = (layer: PhaserLayer) => { }, networkLayer: { systemCalls: { move }, - account: { account }, + account, }, } = layer; diff --git a/examples/react-phaser-example/src/phaser/systems/spawn.ts b/examples/react-phaser-example/src/phaser/systems/spawn.ts index c60ba7fc..88f77fa3 100644 --- a/examples/react-phaser-example/src/phaser/systems/spawn.ts +++ b/examples/react-phaser-example/src/phaser/systems/spawn.ts @@ -7,7 +7,7 @@ export const spawn = (layer: PhaserLayer) => { }, networkLayer: { systemCalls: { spawn }, - account: { account }, + account, }, } = layer; diff --git a/examples/react-phaser-example/src/ui/spawnbtn.tsx b/examples/react-phaser-example/src/ui/spawnbtn.tsx index 8f8783e3..9befcaf9 100644 --- a/examples/react-phaser-example/src/ui/spawnbtn.tsx +++ b/examples/react-phaser-example/src/ui/spawnbtn.tsx @@ -6,6 +6,7 @@ export const SpawnBtn = () => { account: { account, create, isDeploying, select, list, clear }, systemCalls: { spawn }, } = useDojo(); + return (
diff --git a/packages/create-burner/src/manager/burnerManager.ts b/packages/create-burner/src/manager/burnerManager.ts index 2eed8623..1748ed68 100644 --- a/packages/create-burner/src/manager/burnerManager.ts +++ b/packages/create-burner/src/manager/burnerManager.ts @@ -8,7 +8,7 @@ export class BurnerManager { public accountClassHash: string; public provider: RpcProvider; - public account: Account; + public account: Account | null = null; public isDeploying: boolean = false; public burnerAccounts: Burner[] = []; @@ -22,7 +22,6 @@ export class BurnerManager { this.masterAccount = masterAccount; this.accountClassHash = accountClassHash; this.provider = rpcProvider; - this.account = masterAccount; } public setIsDeployingCallback( @@ -64,7 +63,7 @@ export class BurnerManager { ?.getTransactionReceipt(storage[firstAddr].deployTx) .then((response) => { if (!response) { - // this.account = this.masterAccount; + this.account = null; Storage.remove("burners"); throw new Error( "Burners not deployed, chain may have restarted" From 3ecfe58c07afaf1a9d7f670a1be9fbf4bddeab0f Mon Sep 17 00:00:00 2001 From: democritus Date: Wed, 8 Nov 2023 12:29:37 +0100 Subject: [PATCH 5/5] sync manager --- .../src/dojo/createNetworkLayer.ts | 15 ++++ .../src/hooks/useDojo.tsx | 4 +- packages/react/src/index.ts | 1 + packages/react/src/syncManager/index.ts | 1 + packages/react/src/syncManager/syncManager.ts | 85 +++++++++++++++++++ 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 packages/react/src/syncManager/index.ts create mode 100644 packages/react/src/syncManager/syncManager.ts diff --git a/examples/react-phaser-example/src/dojo/createNetworkLayer.ts b/examples/react-phaser-example/src/dojo/createNetworkLayer.ts index fbb828b0..5d6a3f34 100644 --- a/examples/react-phaser-example/src/dojo/createNetworkLayer.ts +++ b/examples/react-phaser-example/src/dojo/createNetworkLayer.ts @@ -2,6 +2,7 @@ import { world } from "./world"; import { setup } from "./setup"; import { Account, RpcProvider } from "starknet"; import { BurnerManager } from "@dojoengine/create-burner"; +import { SyncManager } from "@dojoengine/react"; export type NetworkLayer = Awaited>; @@ -27,6 +28,20 @@ export const createNetworkLayer = async () => { // TODO: Currently if you change wallets in the UI, phaser will not update. burnerManager.init(); + if (burnerManager.account) { + // sync manager to active address + new SyncManager(network.torii_client, [ + { + model: network.contractComponents.Position, + keys: [burnerManager.account?.address], + }, + { + model: network.contractComponents.Moves as any, + keys: [burnerManager.account?.address], + }, + ]); + } + return { world, components, diff --git a/examples/react-phaser-example/src/hooks/useDojo.tsx b/examples/react-phaser-example/src/hooks/useDojo.tsx index 36e8382e..9528d619 100644 --- a/examples/react-phaser-example/src/hooks/useDojo.tsx +++ b/examples/react-phaser-example/src/hooks/useDojo.tsx @@ -18,8 +18,6 @@ export const useDojo = () => { burnerManager: networkLayer.burnerManager, }); - console.log("account", account); - return { networkLayer: networkLayer as NetworkLayer, phaserLayer: phaserLayer as PhaserLayer, @@ -33,5 +31,7 @@ export const useDojo = () => { isDeploying, }, systemCalls: networkLayer.systemCalls, + toriiClient: networkLayer.network.torii_client, + contractComponents: networkLayer.network.contractComponents, }; }; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 2d430d5b..fcd91e45 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -3,3 +3,4 @@ export * from "./useSync"; export * from "./useComponentValue"; export * from "./usePromise"; export * from "./usePromise"; +export * from "./syncManager"; diff --git a/packages/react/src/syncManager/index.ts b/packages/react/src/syncManager/index.ts new file mode 100644 index 00000000..c019356a --- /dev/null +++ b/packages/react/src/syncManager/index.ts @@ -0,0 +1 @@ +export { SyncManager } from "./syncManager"; diff --git a/packages/react/src/syncManager/syncManager.ts b/packages/react/src/syncManager/syncManager.ts new file mode 100644 index 00000000..4940a03b --- /dev/null +++ b/packages/react/src/syncManager/syncManager.ts @@ -0,0 +1,85 @@ +import { Client } from "@dojoengine/torii-client"; +import { + Component, + Schema, + Metadata, + Entity, + setComponent, +} from "@dojoengine/recs"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; +import { convertValues } from "../utils"; + +// type KeyType = any; // Replace 'any' with your actual key type +type ModelEntry = { + model: Component; + keys: any[]; +}; + +export class SyncManager { + private client: Client; + private modelEntries: ModelEntry[]; + private isMounted: boolean; + + constructor(client: Client, modelEntries: ModelEntry[]) { + this.client = client; + this.modelEntries = modelEntries; + this.isMounted = true; + + console.log("init"); + this.init(); + } + + private async fetchAndSetModelValue( + modelEntry: ModelEntry + ): Promise { + const { model, keys } = modelEntry; + const componentName = model.metadata?.name as string; + const keysToStrings = keys.map((key) => key.toString()); + const entityIndex: Entity = + keys.length === 1 ? keys[0].toString() : getEntityIdFromKeys(keys); + + try { + if (this.isMounted) { + const modelValue = await this.client.getModelValue( + componentName, + keysToStrings + ); + const convertedValue = convertValues(model.schema, modelValue); + setComponent(model, entityIndex, convertedValue as any); + } + } catch (error) { + console.error("Failed to fetch or set model value:", error); + } + } + + private init(): void { + this.modelEntries.forEach((modelEntry) => { + this.fetchAndSetModelValue(modelEntry); + this.client.addEntitiesToSync([ + { + model: modelEntry.model.metadata?.name as string, + keys: modelEntry.keys.map((k) => k.toString()), + }, + ]); + }); + } + + public cleanup(): void { + this.isMounted = false; + this.modelEntries.forEach((modelEntry) => { + this.client + .removeEntitiesToSync([ + { + model: modelEntry.model.metadata?.name as string, + keys: modelEntry.keys.map((k) => k.toString()), + }, + ]) + .catch((error) => { + console.error( + "Failed to remove entities on cleanup", + error + ); + }); + }); + } +}