From a10ad41b450390418a8757e6e968e13a126b1808 Mon Sep 17 00:00:00 2001 From: ponderingdemocritus Date: Sun, 1 Sep 2024 18:07:32 -0400 Subject: [PATCH 1/7] fix: sync --- examples/clients/react/react-app/src/App.tsx | 1 + examples/clients/react/react-app/src/dojo/setup.ts | 11 ++++++++++- .../dojo/dojo-starter/.github/workflows/test.yaml | 2 +- examples/dojo/dojo-starter/README.md | 2 +- examples/dojo/dojo-starter/Scarb.lock | 2 +- examples/dojo/dojo-starter/Scarb.toml | 2 +- examples/dojo/dojo-starter/dojo_dev.toml | 2 +- .../contracts/dojo_starter-actions-7a1c7102.toml | 4 ++-- .../manifests/dev/base/dojo-world.toml | 4 ++-- .../manifests/dev/deployment/manifest.json | 14 +++++++------- .../manifests/dev/deployment/manifest.toml | 14 +++++++------- packages/state/src/recs/index.ts | 14 ++++++-------- 12 files changed, 40 insertions(+), 32 deletions(-) diff --git a/examples/clients/react/react-app/src/App.tsx b/examples/clients/react/react-app/src/App.tsx index 2b302ff0..a8bc06fc 100644 --- a/examples/clients/react/react-app/src/App.tsx +++ b/examples/clients/react/react-app/src/App.tsx @@ -17,6 +17,7 @@ function App() { account, } = useDojo(); + // sync entities useQuerySync(toriiClient, contractComponents as any, []); const [clipboardStatus, setClipboardStatus] = useState({ diff --git a/examples/clients/react/react-app/src/dojo/setup.ts b/examples/clients/react/react-app/src/dojo/setup.ts index e545428d..0ad7139c 100644 --- a/examples/clients/react/react-app/src/dojo/setup.ts +++ b/examples/clients/react/react-app/src/dojo/setup.ts @@ -7,7 +7,7 @@ import { world } from "./world"; import { setupWorld } from "./typescript/contracts.gen"; import { Account, ArraySignatureType } from "starknet"; import { BurnerManager } from "@dojoengine/create-burner"; -import { getSyncEvents } from "@dojoengine/state"; +import { getSyncEvents, getSyncEntities } from "@dojoengine/state"; export type SetupResult = Awaited>; @@ -29,6 +29,7 @@ export async function setup({ ...config }: DojoConfig) { // create dojo provider const dojoProvider = new DojoProvider(config.manifest, config.rpcUrl); + // Sync all events const eventSync = getSyncEvents( toriiClient, contractComponents as any, @@ -36,6 +37,13 @@ export async function setup({ ...config }: DojoConfig) { [] ); + // Sync all entities + const sync = await getSyncEntities( + toriiClient, + contractComponents as any, + [] + ); + // setup world const client = await setupWorld(dojoProvider); @@ -75,5 +83,6 @@ export async function setup({ ...config }: DojoConfig) { burnerManager, toriiClient, eventSync, + sync, }; } diff --git a/examples/dojo/dojo-starter/.github/workflows/test.yaml b/examples/dojo/dojo-starter/.github/workflows/test.yaml index 101b7433..c4f4bcda 100644 --- a/examples/dojo/dojo-starter/.github/workflows/test.yaml +++ b/examples/dojo/dojo-starter/.github/workflows/test.yaml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v3 - run: curl -L https://install.dojoengine.org | bash - - run: /home/runner/.config/.dojo/bin/dojoup -v v1.0.0-alpha.6 + - run: /home/runner/.config/.dojo/bin/dojoup -v v1.0.0-alpha.9 - run: | /home/runner/.config/.dojo/bin/sozo build /home/runner/.config/.dojo/bin/sozo test diff --git a/examples/dojo/dojo-starter/README.md b/examples/dojo/dojo-starter/README.md index 5979ee0d..05061c94 100644 --- a/examples/dojo/dojo-starter/README.md +++ b/examples/dojo/dojo-starter/README.md @@ -41,7 +41,7 @@ sozo build sozo migrate apply # Start Torii -torii --world 0x403b5f047b8c4797139e30801e310473d99ca6877d19e0f27506f353f8f70f7 --allowed-origins "*" +torii --world 0x3b34889efbdf01f707d5d7421f112e8fb85a42fb6f2e5422c75ce3253148b0e --allowed-origins "*" ``` --- diff --git a/examples/dojo/dojo-starter/Scarb.lock b/examples/dojo/dojo-starter/Scarb.lock index 33297f7b..e994b92e 100644 --- a/examples/dojo/dojo-starter/Scarb.lock +++ b/examples/dojo/dojo-starter/Scarb.lock @@ -4,7 +4,7 @@ version = 1 [[package]] name = "dojo" version = "1.0.0-alpha.4" -source = "git+https://github.com/dojoengine/dojo?tag=v1.0.0-alpha.6#ede7930735c708572af8a87f81bb76354a401b3a" +source = "git+https://github.com/dojoengine/dojo?tag=v1.0.0-alpha.9#e42ce0c220a59d75c5b08e87a81de12cfdc27a55" dependencies = [ "dojo_plugin", ] diff --git a/examples/dojo/dojo-starter/Scarb.toml b/examples/dojo/dojo-starter/Scarb.toml index b0bc4a64..1a551d7a 100644 --- a/examples/dojo/dojo-starter/Scarb.toml +++ b/examples/dojo/dojo-starter/Scarb.toml @@ -12,6 +12,6 @@ spawn = "./scripts/spawn.sh" move = "./scripts/move.sh" [dependencies] -dojo = { git = "https://github.com/dojoengine/dojo", tag = "v1.0.0-alpha.6" } +dojo = { git = "https://github.com/dojoengine/dojo", tag = "v1.0.0-alpha.9" } [[target.dojo]] diff --git a/examples/dojo/dojo-starter/dojo_dev.toml b/examples/dojo/dojo-starter/dojo_dev.toml index f73c0596..a4e1ba87 100644 --- a/examples/dojo/dojo-starter/dojo_dev.toml +++ b/examples/dojo/dojo-starter/dojo_dev.toml @@ -20,4 +20,4 @@ rpc_url = "http://localhost:5050/" # Default account for katana with seed = 0 account_address = "0xb3ff441a68610b30fd5e2abbf3a1548eb6ba6f3559f2862bf2dc757e5828ca" private_key = "0x2bbf4f9fd0bbb2e60b0316c1fe0b76cf7a4d0198bd493ced9b8df2a3a24d68a" -world_address = "0x403b5f047b8c4797139e30801e310473d99ca6877d19e0f27506f353f8f70f7" +world_address = "0x3b34889efbdf01f707d5d7421f112e8fb85a42fb6f2e5422c75ce3253148b0e" diff --git a/examples/dojo/dojo-starter/manifests/dev/base/contracts/dojo_starter-actions-7a1c7102.toml b/examples/dojo/dojo-starter/manifests/dev/base/contracts/dojo_starter-actions-7a1c7102.toml index dd755415..9b53c897 100644 --- a/examples/dojo/dojo-starter/manifests/dev/base/contracts/dojo_starter-actions-7a1c7102.toml +++ b/examples/dojo/dojo-starter/manifests/dev/base/contracts/dojo_starter-actions-7a1c7102.toml @@ -1,6 +1,6 @@ kind = "DojoContract" -class_hash = "0x4f10da1ba615523c1a4b9e2ad40df134edbcf2d971a1074efbd9772c86c2173" -original_class_hash = "0x4f10da1ba615523c1a4b9e2ad40df134edbcf2d971a1074efbd9772c86c2173" +class_hash = "0x7304354bfea03508e0a8beaca98c9580c852a0e350b74dcafb55192c62fa3f4" +original_class_hash = "0x7304354bfea03508e0a8beaca98c9580c852a0e350b74dcafb55192c62fa3f4" base_class_hash = "0x0" abi = "manifests/dev/base/abis/contracts/dojo_starter-actions-7a1c7102.json" reads = [] diff --git a/examples/dojo/dojo-starter/manifests/dev/base/dojo-world.toml b/examples/dojo/dojo-starter/manifests/dev/base/dojo-world.toml index 6f56ceed..2b3b1409 100644 --- a/examples/dojo/dojo-starter/manifests/dev/base/dojo-world.toml +++ b/examples/dojo/dojo-starter/manifests/dev/base/dojo-world.toml @@ -1,6 +1,6 @@ kind = "Class" -class_hash = "0x458d0ce5b14a4844092bdb62050f462d53362304a13febbac6d973691d61be2" -original_class_hash = "0x458d0ce5b14a4844092bdb62050f462d53362304a13febbac6d973691d61be2" +class_hash = "0xa349b743d361ce4567361475a89b84a386bb383448c6926954e5fe0b525597" +original_class_hash = "0xa349b743d361ce4567361475a89b84a386bb383448c6926954e5fe0b525597" abi = "manifests/dev/base/abis/dojo-world.json" tag = "dojo-world" manifest_name = "dojo-world" diff --git a/examples/dojo/dojo-starter/manifests/dev/deployment/manifest.json b/examples/dojo/dojo-starter/manifests/dev/deployment/manifest.json index b3c92df3..463cbc47 100644 --- a/examples/dojo/dojo-starter/manifests/dev/deployment/manifest.json +++ b/examples/dojo/dojo-starter/manifests/dev/deployment/manifest.json @@ -1,8 +1,8 @@ { "world": { "kind": "WorldContract", - "class_hash": "0x458d0ce5b14a4844092bdb62050f462d53362304a13febbac6d973691d61be2", - "original_class_hash": "0x458d0ce5b14a4844092bdb62050f462d53362304a13febbac6d973691d61be2", + "class_hash": "0xa349b743d361ce4567361475a89b84a386bb383448c6926954e5fe0b525597", + "original_class_hash": "0xa349b743d361ce4567361475a89b84a386bb383448c6926954e5fe0b525597", "abi": [ { "type": "impl", @@ -1229,8 +1229,8 @@ ] } ], - "address": "0x403b5f047b8c4797139e30801e310473d99ca6877d19e0f27506f353f8f70f7", - "transaction_hash": "0x12df6c5a6c89d2c128c96026395f1f8917c36a7864aca26f0c7b9e2ea152bca", + "address": "0x3b34889efbdf01f707d5d7421f112e8fb85a42fb6f2e5422c75ce3253148b0e", + "transaction_hash": "0x4729b83525f0dddbfce0afd73e0b6a39303de51690efd756691356c21c30067", "block_number": 3, "seed": "dojo_starter", "metadata": { @@ -1250,9 +1250,9 @@ "contracts": [ { "kind": "DojoContract", - "address": "0x36e4506b35e6dfb301d437c95f74b3e1f4f82da5d8841bec894bb8de29ec13", - "class_hash": "0x4f10da1ba615523c1a4b9e2ad40df134edbcf2d971a1074efbd9772c86c2173", - "original_class_hash": "0x4f10da1ba615523c1a4b9e2ad40df134edbcf2d971a1074efbd9772c86c2173", + "address": "0x5e8df8a235031ba8e66e590a7289d27d7d54c026bcdbda62b791c0a69055225", + "class_hash": "0x7304354bfea03508e0a8beaca98c9580c852a0e350b74dcafb55192c62fa3f4", + "original_class_hash": "0x7304354bfea03508e0a8beaca98c9580c852a0e350b74dcafb55192c62fa3f4", "base_class_hash": "0x2427dd10a58850ac9a5ca6ce04b7771b05330fd18f2e481831ad903b969e6b2", "abi": [ { diff --git a/examples/dojo/dojo-starter/manifests/dev/deployment/manifest.toml b/examples/dojo/dojo-starter/manifests/dev/deployment/manifest.toml index bca467c3..41d787c8 100644 --- a/examples/dojo/dojo-starter/manifests/dev/deployment/manifest.toml +++ b/examples/dojo/dojo-starter/manifests/dev/deployment/manifest.toml @@ -1,10 +1,10 @@ [world] kind = "WorldContract" -class_hash = "0x458d0ce5b14a4844092bdb62050f462d53362304a13febbac6d973691d61be2" -original_class_hash = "0x458d0ce5b14a4844092bdb62050f462d53362304a13febbac6d973691d61be2" +class_hash = "0xa349b743d361ce4567361475a89b84a386bb383448c6926954e5fe0b525597" +original_class_hash = "0xa349b743d361ce4567361475a89b84a386bb383448c6926954e5fe0b525597" abi = "manifests/dev/deployment/abis/dojo-world.json" -address = "0x403b5f047b8c4797139e30801e310473d99ca6877d19e0f27506f353f8f70f7" -transaction_hash = "0x12df6c5a6c89d2c128c96026395f1f8917c36a7864aca26f0c7b9e2ea152bca" +address = "0x3b34889efbdf01f707d5d7421f112e8fb85a42fb6f2e5422c75ce3253148b0e" +transaction_hash = "0x4729b83525f0dddbfce0afd73e0b6a39303de51690efd756691356c21c30067" block_number = 3 seed = "dojo_starter" manifest_name = "dojo-world" @@ -23,9 +23,9 @@ manifest_name = "dojo-base" [[contracts]] kind = "DojoContract" -address = "0x36e4506b35e6dfb301d437c95f74b3e1f4f82da5d8841bec894bb8de29ec13" -class_hash = "0x4f10da1ba615523c1a4b9e2ad40df134edbcf2d971a1074efbd9772c86c2173" -original_class_hash = "0x4f10da1ba615523c1a4b9e2ad40df134edbcf2d971a1074efbd9772c86c2173" +address = "0x5e8df8a235031ba8e66e590a7289d27d7d54c026bcdbda62b791c0a69055225" +class_hash = "0x7304354bfea03508e0a8beaca98c9580c852a0e350b74dcafb55192c62fa3f4" +original_class_hash = "0x7304354bfea03508e0a8beaca98c9580c852a0e350b74dcafb55192c62fa3f4" base_class_hash = "0x2427dd10a58850ac9a5ca6ce04b7771b05330fd18f2e481831ad903b969e6b2" abi = "manifests/dev/deployment/abis/contracts/dojo_starter-actions-7a1c7102.json" reads = [] diff --git a/packages/state/src/recs/index.ts b/packages/state/src/recs/index.ts index bd1776fc..c74a3174 100644 --- a/packages/state/src/recs/index.ts +++ b/packages/state/src/recs/index.ts @@ -217,13 +217,13 @@ export const getEntitiesQuery = async ( } : null; - const fetchedEntities = await client.getEntities({ - limit, - offset: cursor, - clause: clause || undefined, - }); - while (continueFetching) { + const fetchedEntities = await client.getEntities({ + limit, + offset: cursor, + clause: clause || undefined, + }); + setEntities(fetchedEntities, components); if (Object.keys(fetchedEntities).length < limit) { @@ -279,8 +279,6 @@ export const syncEvents = async ( return await client.onEventMessageUpdated( entityKeyClause, (fetchedEntities: any, data: any) => { - // Log the fetched entities and data for debugging purposes - console.log("fetchedEntities", data); // Update the local state with the fetched entities and their data setEntities({ [fetchedEntities]: data }, components); } From eb7ab00099111014812e1888d216d0fb6e1d8e24 Mon Sep 17 00:00:00 2001 From: ponderingdemocritus Date: Mon, 2 Sep 2024 09:53:13 -0400 Subject: [PATCH 2/7] fix: dojo init --- packages/create-dojo/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/create-dojo/src/index.ts b/packages/create-dojo/src/index.ts index 9bc3874e..15d29edf 100644 --- a/packages/create-dojo/src/index.ts +++ b/packages/create-dojo/src/index.ts @@ -46,7 +46,7 @@ async function run() { console.log(`Downloading ${template} into client directory...`); spawn.sync("npx", [ "degit", - `dojoengine/dojo.js/examples/react/${template}`, + `dojoengine/dojo.js/examples/clients/react/${template}`, clientPath, // Cloning directly into the client directory ]); @@ -67,7 +67,7 @@ async function run() { console.log("You can then build the starter and run the client.\n"); console.log("For detailed instructions, follow the README here:\n"); - console.log('https://book.dojoengine.org/cairo/hello-dojo'); + console.log('https://book.dojoengine.org'); } catch (e) { console.error(`Error: ${e}`); From 080a9f8221c43c400726c6a674420f3b4c9b08d4 Mon Sep 17 00:00:00 2001 From: ponderingdemocritus Date: Mon, 2 Sep 2024 10:16:26 -0400 Subject: [PATCH 3/7] fix: bump all examples to new codegen --- examples/clients/react/react-app/src/App.tsx | 8 +- .../react-app/src/dojo/createSystemCalls.ts | 37 +--- .../src/dojo/contractComponents.ts | 41 ---- .../src/dojo/createClientComponents.ts | 2 +- .../src/dojo/createNetworkLayer.ts | 3 +- .../src/dojo/createSystemCalls.ts | 86 +++++++- .../src/dojo/generated/contractComponents.ts | 63 ------ .../src/dojo/generated/generated.ts | 60 ------ .../src/dojo/generated/setup.ts | 62 ------ .../src/dojo}/setup.ts | 49 +++-- .../src/dojo/typescript/contracts.gen.ts | 86 ++++++++ .../src/dojo/typescript/models.gen.ts | 193 ++++++++++++++++++ .../src/phaser/config/constants.ts | 8 - .../src/phaser/systems/controls.ts | 18 +- .../clients/react/react-pwa-app/src/App.tsx | 8 +- .../react-pwa-app/src/dojo/DojoContext.tsx | 2 +- .../src/dojo/createClientComponents.ts | 2 +- .../src/dojo/createSystemCalls.ts | 127 ++++++------ .../src/dojo/generated/contractComponents.ts | 63 ------ .../src/dojo/generated/generated.ts | 60 ------ .../react/react-pwa-app/src/dojo/setup.ts | 88 ++++++++ .../src/dojo/typescript/contracts.gen.ts | 86 ++++++++ .../src/dojo/typescript/models.gen.ts | 193 ++++++++++++++++++ .../src/dojo}/world.ts | 0 .../clients/react/react-pwa-app/src/main.tsx | 2 +- .../react-threejs/src/dojo/DojoContext.tsx | 25 ++- .../src/dojo/createClientComponents.ts | 2 +- .../src/dojo/createSystemCalls.ts | 129 ++++++------ .../src/dojo/generated/contractComponents.ts | 63 ------ .../src/dojo/generated/generated.ts | 60 ------ .../src/dojo/{generated => }/setup.ts | 60 +++--- .../src/dojo/typescript/contracts.gen.ts | 86 ++++++++ .../src/dojo/typescript/models.gen.ts | 193 ++++++++++++++++++ .../src/dojo}/world.ts | 0 .../src/gameComponents/Player.tsx | 26 ++- .../clients/react/react-threejs/src/main.tsx | 2 +- .../vanilla/phaser/src/dojo/DojoContext.tsx | 83 ++++++++ ...Component.ts => createClientComponents.ts} | 2 +- .../phaser/src/dojo/createSystemCalls.ts | 109 ++++++++++ .../src/dojo/defineContractComponents.ts | 72 ------- .../phaser/src/dojo/defineContractSystems.ts | 55 ----- .../clients/vanilla/phaser/src/dojo/models.ts | 15 -- .../clients/vanilla/phaser/src/dojo/setup.ts | 154 ++++++-------- .../vanilla/phaser/src/dojo/systems.ts | 123 ----------- .../src/dojo/typescript/contracts.gen.ts | 86 ++++++++ .../phaser/src/dojo/typescript/models.gen.ts | 193 ++++++++++++++++++ .../vanilla/phaser/src/dojo/useDojo.tsx | 15 ++ .../clients/vanilla/phaser/src/dojo/utils.ts | 29 --- .../vanilla/phaser/src/scenes/scene-main.ts | 16 +- examples/clients/vue/vue-app/src/App.vue | 6 +- .../vue/vue-app/src/components/HelloWorld.vue | 40 ---- .../vue/vue-app/src/dojo/DojoContext.tsx | 83 ++++++++ .../src/dojo/createClientComponents.ts | 2 +- .../vue/vue-app/src/dojo/createSystemCalls.ts | 125 ++++++------ .../src/dojo/generated/contractComponents.ts | 63 ------ .../vue-app/src/dojo/generated/generated.ts | 60 ------ .../vue/vue-app/src/dojo/generated/setup.ts | 65 ------ .../vue/vue-app/src/dojo/generated/world.ts | 3 - .../clients/vue/vue-app/src/dojo/setup.ts | 88 ++++++++ .../src/dojo/typescript/contracts.gen.ts | 86 ++++++++ .../vue-app/src/dojo/typescript/models.gen.ts | 193 ++++++++++++++++++ .../clients/vue/vue-app/src/dojo/useDojo.tsx | 15 ++ .../vue-app/src/dojo}/world.ts | 0 63 files changed, 2303 insertions(+), 1471 deletions(-) delete mode 100644 examples/clients/react/react-phaser-example/src/dojo/contractComponents.ts delete mode 100644 examples/clients/react/react-phaser-example/src/dojo/generated/contractComponents.ts delete mode 100644 examples/clients/react/react-phaser-example/src/dojo/generated/generated.ts delete mode 100644 examples/clients/react/react-phaser-example/src/dojo/generated/setup.ts rename examples/clients/react/{react-pwa-app/src/dojo/generated => react-phaser-example/src/dojo}/setup.ts (71%) create mode 100644 examples/clients/react/react-phaser-example/src/dojo/typescript/contracts.gen.ts create mode 100644 examples/clients/react/react-phaser-example/src/dojo/typescript/models.gen.ts delete mode 100644 examples/clients/react/react-pwa-app/src/dojo/generated/contractComponents.ts delete mode 100644 examples/clients/react/react-pwa-app/src/dojo/generated/generated.ts create mode 100644 examples/clients/react/react-pwa-app/src/dojo/setup.ts create mode 100644 examples/clients/react/react-pwa-app/src/dojo/typescript/contracts.gen.ts create mode 100644 examples/clients/react/react-pwa-app/src/dojo/typescript/models.gen.ts rename examples/clients/react/{react-phaser-example/src/dojo/generated => react-pwa-app/src/dojo}/world.ts (100%) delete mode 100644 examples/clients/react/react-threejs/src/dojo/generated/contractComponents.ts delete mode 100644 examples/clients/react/react-threejs/src/dojo/generated/generated.ts rename examples/clients/react/react-threejs/src/dojo/{generated => }/setup.ts (66%) create mode 100644 examples/clients/react/react-threejs/src/dojo/typescript/contracts.gen.ts create mode 100644 examples/clients/react/react-threejs/src/dojo/typescript/models.gen.ts rename examples/clients/react/{react-pwa-app/src/dojo/generated => react-threejs/src/dojo}/world.ts (100%) create mode 100644 examples/clients/vanilla/phaser/src/dojo/DojoContext.tsx rename examples/clients/vanilla/phaser/src/dojo/{createClientComponent.ts => createClientComponents.ts} (87%) create mode 100644 examples/clients/vanilla/phaser/src/dojo/createSystemCalls.ts delete mode 100644 examples/clients/vanilla/phaser/src/dojo/defineContractComponents.ts delete mode 100644 examples/clients/vanilla/phaser/src/dojo/defineContractSystems.ts delete mode 100644 examples/clients/vanilla/phaser/src/dojo/models.ts delete mode 100644 examples/clients/vanilla/phaser/src/dojo/systems.ts create mode 100644 examples/clients/vanilla/phaser/src/dojo/typescript/contracts.gen.ts create mode 100644 examples/clients/vanilla/phaser/src/dojo/typescript/models.gen.ts create mode 100644 examples/clients/vanilla/phaser/src/dojo/useDojo.tsx delete mode 100644 examples/clients/vanilla/phaser/src/dojo/utils.ts delete mode 100644 examples/clients/vue/vue-app/src/components/HelloWorld.vue create mode 100644 examples/clients/vue/vue-app/src/dojo/DojoContext.tsx delete mode 100644 examples/clients/vue/vue-app/src/dojo/generated/contractComponents.ts delete mode 100644 examples/clients/vue/vue-app/src/dojo/generated/generated.ts delete mode 100644 examples/clients/vue/vue-app/src/dojo/generated/setup.ts delete mode 100644 examples/clients/vue/vue-app/src/dojo/generated/world.ts create mode 100644 examples/clients/vue/vue-app/src/dojo/setup.ts create mode 100644 examples/clients/vue/vue-app/src/dojo/typescript/contracts.gen.ts create mode 100644 examples/clients/vue/vue-app/src/dojo/typescript/models.gen.ts create mode 100644 examples/clients/vue/vue-app/src/dojo/useDojo.tsx rename examples/clients/{react/react-threejs/src/dojo/generated => vue/vue-app/src/dojo}/world.ts (100%) diff --git a/examples/clients/react/react-app/src/App.tsx b/examples/clients/react/react-app/src/App.tsx index a8bc06fc..9db2e238 100644 --- a/examples/clients/react/react-app/src/App.tsx +++ b/examples/clients/react/react-app/src/App.tsx @@ -140,7 +140,7 @@ function App() {
diff --git a/examples/clients/react/react-app/src/dojo/createSystemCalls.ts b/examples/clients/react/react-app/src/dojo/createSystemCalls.ts index bd72864c..38475f37 100644 --- a/examples/clients/react/react-app/src/dojo/createSystemCalls.ts +++ b/examples/clients/react/react-app/src/dojo/createSystemCalls.ts @@ -9,9 +9,9 @@ import { } from "@dojoengine/recs"; import { uuid } from "@latticexyz/utils"; import { ClientComponents } from "./createClientComponents"; -import { Direction, updatePositionWithDirection } from "../utils"; import { getEntityIdFromKeys } from "@dojoengine/utils"; import type { IWorld } from "./typescript/contracts.gen"; +import { Direction } from "./typescript/models.gen"; export type SystemCalls = ReturnType; @@ -77,38 +77,10 @@ export function createSystemCalls( }; const move = async (account: Account, direction: Direction) => { - const entityId = getEntityIdFromKeys([ - BigInt(account.address), - ]) as Entity; - - // Update the state before the transaction - // const positionId = uuid(); - // Position.addOverride(positionId, { - // entity: entityId, - // value: { - // player: BigInt(entityId), - // vec: updatePositionWithDirection( - // direction, - // getComponentValue(Position, entityId) as any - // ).vec, - // }, - // }); - - // // Update the state before the transaction - // const movesId = uuid(); - // Moves.addOverride(movesId, { - // entity: entityId, - // value: { - // player: BigInt(entityId), - // remaining: - // (getComponentValue(Moves, entityId)?.remaining || 0) - 1, - // }, - // }); - try { await client.actions.move({ account, - direction: { type: "Left" }, + direction, }); // Wait for the indexer to update the entity @@ -127,11 +99,6 @@ export function createSystemCalls( }); } catch (e) { console.log(e); - // Position.removeOverride(positionId); - // Moves.removeOverride(movesId); - } finally { - // Position.removeOverride(positionId); - // Moves.removeOverride(movesId); } }; diff --git a/examples/clients/react/react-phaser-example/src/dojo/contractComponents.ts b/examples/clients/react/react-phaser-example/src/dojo/contractComponents.ts deleted file mode 100644 index 12430e19..00000000 --- a/examples/clients/react/react-phaser-example/src/dojo/contractComponents.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ - -import { defineComponent, Type as RecsType, World } from "@dojoengine/recs"; - -export function defineContractComponents(world: World) { - return { - Moves: (() => { - return defineComponent( - world, - { - player: RecsType.BigInt, - remaining: RecsType.Number, - last_direction: RecsType.Number, - }, - { - metadata: { - name: "Moves", - types: ["contractaddress", "u8", "enum"], - customTypes: ["Direction"], - }, - } - ); - })(), - Position: (() => { - return defineComponent( - world, - { - player: RecsType.BigInt, - vec: { x: RecsType.Number, y: RecsType.Number }, - }, - { - metadata: { - name: "Position", - types: ["contractaddress", "u32", "u32"], - customTypes: ["Vec2"], - }, - } - ); - })(), - }; -} diff --git a/examples/clients/react/react-phaser-example/src/dojo/createClientComponents.ts b/examples/clients/react/react-phaser-example/src/dojo/createClientComponents.ts index 550ad13b..7b5bb860 100644 --- a/examples/clients/react/react-phaser-example/src/dojo/createClientComponents.ts +++ b/examples/clients/react/react-phaser-example/src/dojo/createClientComponents.ts @@ -1,5 +1,5 @@ import { overridableComponent } from "@dojoengine/recs"; -import { ContractComponents } from "./generated/contractComponents"; +import { ContractComponents } from "./typescript/models.gen"; export type ClientComponents = ReturnType; diff --git a/examples/clients/react/react-phaser-example/src/dojo/createNetworkLayer.ts b/examples/clients/react/react-phaser-example/src/dojo/createNetworkLayer.ts index 3ad74434..1290ade5 100644 --- a/examples/clients/react/react-phaser-example/src/dojo/createNetworkLayer.ts +++ b/examples/clients/react/react-phaser-example/src/dojo/createNetworkLayer.ts @@ -1,8 +1,9 @@ import { world as recsWorld } from "./world"; -import { setup } from "./generated/setup"; + import { dojoConfig } from "../../dojoConfig"; import { createBurner } from "./createBurner"; import { Account } from "starknet"; +import { setup } from "./setup"; export type NetworkLayer = Awaited>; diff --git a/examples/clients/react/react-phaser-example/src/dojo/createSystemCalls.ts b/examples/clients/react/react-phaser-example/src/dojo/createSystemCalls.ts index b1f71a0a..38475f37 100644 --- a/examples/clients/react/react-phaser-example/src/dojo/createSystemCalls.ts +++ b/examples/clients/react/react-phaser-example/src/dojo/createSystemCalls.ts @@ -1,32 +1,102 @@ -import { AccountInterface } from "starknet"; +import { Account, AccountInterface } from "starknet"; +import { + Entity, + Has, + HasValue, + World, + defineSystem, + getComponentValue, +} from "@dojoengine/recs"; +import { uuid } from "@latticexyz/utils"; import { ClientComponents } from "./createClientComponents"; -import { Direction } from "./utils"; -import { ContractComponents } from "./generated/contractComponents"; -import type { IWorld } from "./generated/generated"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; +import type { IWorld } from "./typescript/contracts.gen"; +import { Direction } from "./typescript/models.gen"; export type SystemCalls = ReturnType; export function createSystemCalls( { client }: { client: IWorld }, - contractComponents: ContractComponents, - { Position, Moves }: ClientComponents + { Position, Moves }: ClientComponents, + world: World ) { - const spawn = async (account: AccountInterface) => { + const spawn = async (account: Account) => { + const entityId = getEntityIdFromKeys([ + BigInt(account.address), + ]) as Entity; + + const movesId = uuid(); + Moves.addOverride(movesId, { + entity: entityId, + value: { + player: BigInt(entityId), + remaining: + (getComponentValue(Moves, entityId)?.remaining || 0) + 100, + }, + }); + + const positionId = uuid(); + Position.addOverride(positionId, { + entity: entityId, + value: { + player: BigInt(entityId), + vec: { + x: 10 + (getComponentValue(Position, entityId)?.vec.x || 0), + y: 10 + (getComponentValue(Position, entityId)?.vec.y || 0), + }, + }, + }); + try { await client.actions.spawn({ account, }); + + // Wait for the indexer to update the entity + // By doing this we keep the optimistic UI in sync with the actual state + await new Promise((resolve) => { + defineSystem( + world, + [ + Has(Moves), + HasValue(Moves, { player: BigInt(account.address) }), + ], + () => { + resolve(); + } + ); + }); } catch (e) { console.log(e); + Position.removeOverride(positionId); + Moves.removeOverride(movesId); + } finally { + Position.removeOverride(positionId); + Moves.removeOverride(movesId); } }; - const move = async (account: AccountInterface, direction: Direction) => { + const move = async (account: Account, direction: Direction) => { try { await client.actions.move({ account, direction, }); + + // Wait for the indexer to update the entity + // By doing this we keep the optimistic UI in sync with the actual state + await new Promise((resolve) => { + defineSystem( + world, + [ + Has(Moves), + HasValue(Moves, { player: BigInt(account.address) }), + ], + () => { + resolve(); + } + ); + }); } catch (e) { console.log(e); } diff --git a/examples/clients/react/react-phaser-example/src/dojo/generated/contractComponents.ts b/examples/clients/react/react-phaser-example/src/dojo/generated/contractComponents.ts deleted file mode 100644 index 596871c8..00000000 --- a/examples/clients/react/react-phaser-example/src/dojo/generated/contractComponents.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Autogenerated file. Do not edit manually. - * Generated using @dojoengine/core - * Command: npx @dojoengine/core - */ - -import { defineComponent, Type as RecsType, World } from "@dojoengine/recs"; - -export type ContractComponents = Awaited< - ReturnType ->; - -export function defineContractComponents(world: World) { - return { - DirectionsAvailable: (() => { - return defineComponent( - world, - { player: RecsType.BigInt, directions: RecsType.StringArray }, - { - metadata: { - name: "dojo_starter-DirectionsAvailable", - types: ["contractaddress"], - customTypes: ["Direction"], - }, - } - ); - })(), - Moves: (() => { - return defineComponent( - world, - { - player: RecsType.BigInt, - remaining: RecsType.Number, - last_direction: RecsType.Number, - can_move: RecsType.Boolean, - }, - { - metadata: { - name: "dojo_starter-Moves", - types: ["contractaddress", "u8", "enum", "bool"], - customTypes: ["Direction"], - }, - } - ); - })(), - Position: (() => { - return defineComponent( - world, - { - player: RecsType.BigInt, - vec: { x: RecsType.Number, y: RecsType.Number }, - }, - { - metadata: { - name: "dojo_starter-Position", - types: ["contractaddress", "u32", "u32"], - customTypes: ["Vec2"], - }, - } - ); - })(), - }; -} diff --git a/examples/clients/react/react-phaser-example/src/dojo/generated/generated.ts b/examples/clients/react/react-phaser-example/src/dojo/generated/generated.ts deleted file mode 100644 index fe9bbe9d..00000000 --- a/examples/clients/react/react-phaser-example/src/dojo/generated/generated.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Account, AccountInterface } from "starknet"; -import { DojoProvider } from "@dojoengine/core"; -import { Direction } from "../utils"; - -const NAMESPACE = "dojo_starter"; - -export interface IWorld { - actions: { - spawn: (props: { account: AccountInterface }) => Promise; - move: (props: MoveProps) => Promise; - }; -} - -export interface MoveProps { - account: Account | AccountInterface; - direction: Direction; -} - -const handleError = (action: string, error: unknown) => { - console.error(`Error executing ${action}:`, error); - throw error; -}; - -export const setupWorld = async (provider: DojoProvider): Promise => { - const actions = () => ({ - spawn: async ({ account }: { account: AccountInterface }) => { - try { - return await provider.execute( - account, - { - contractName: "actions", - entrypoint: "spawn", - calldata: [], - }, - NAMESPACE - ); - } catch (error) { - handleError("spawn", error); - } - }, - - move: async ({ account, direction }: MoveProps) => { - try { - return await provider.execute( - account, - { - contractName: "actions", - entrypoint: "move", - calldata: [direction], - }, - NAMESPACE - ); - } catch (error) { - handleError("move", error); - } - }, - }); - - return { actions: actions() }; -}; diff --git a/examples/clients/react/react-phaser-example/src/dojo/generated/setup.ts b/examples/clients/react/react-phaser-example/src/dojo/generated/setup.ts deleted file mode 100644 index 00090229..00000000 --- a/examples/clients/react/react-phaser-example/src/dojo/generated/setup.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { getSyncEntities } from "@dojoengine/state"; -import * as torii from "@dojoengine/torii-client"; -import { createClientComponents } from "../createClientComponents"; -import { createSystemCalls } from "../createSystemCalls"; -import { defineContractComponents } from "./contractComponents"; -import { world } from "./world"; -import { setupWorld } from "./generated"; -import { - DojoConfig, - DojoProvider, - createModelTypedData, -} from "@dojoengine/core"; -import { - ArraySignatureType, - TypedData, - WeierstrassSignatureType, -} from "starknet"; - -export type SetupResult = Awaited>; - -export async function setup({ ...config }: DojoConfig) { - // torii client - const toriiClient = await torii.createClient({ - rpcUrl: config.rpcUrl, - toriiUrl: config.toriiUrl, - relayUrl: config.relayUrl, - worldAddress: config.manifest.world.address || "", - }); - - // create contract components - const contractComponents = defineContractComponents(world); - - // create client components - const clientComponents = createClientComponents({ contractComponents }); - - // fetch all existing entities from torii - const sync = await getSyncEntities( - toriiClient, - contractComponents as any, - [] - ); - - const client = await setupWorld( - new DojoProvider(config.manifest, config.rpcUrl) - ); - - return { - client, - clientComponents, - contractComponents, - systemCalls: createSystemCalls( - { client }, - contractComponents, - clientComponents - ), - publish: (typedData: string, signature: ArraySignatureType) => { - toriiClient.publishMessage(typedData, signature); - }, - config, - sync, - }; -} diff --git a/examples/clients/react/react-pwa-app/src/dojo/generated/setup.ts b/examples/clients/react/react-phaser-example/src/dojo/setup.ts similarity index 71% rename from examples/clients/react/react-pwa-app/src/dojo/generated/setup.ts rename to examples/clients/react/react-phaser-example/src/dojo/setup.ts index 9cd7fada..0ad7139c 100644 --- a/examples/clients/react/react-pwa-app/src/dojo/generated/setup.ts +++ b/examples/clients/react/react-phaser-example/src/dojo/setup.ts @@ -1,22 +1,13 @@ -import { getSyncEntities } from "@dojoengine/state"; -import { - DojoConfig, - DojoProvider, - createModelTypedData, -} from "@dojoengine/core"; +import { DojoConfig, DojoProvider } from "@dojoengine/core"; import * as torii from "@dojoengine/torii-client"; -import { createClientComponents } from "../createClientComponents"; -import { createSystemCalls } from "../createSystemCalls"; -import { defineContractComponents } from "./contractComponents"; +import { createClientComponents } from "./createClientComponents"; +import { createSystemCalls } from "./createSystemCalls"; +import { defineContractComponents } from "./typescript/models.gen"; import { world } from "./world"; -import { setupWorld } from "./generated"; -import { - Account, - ArraySignatureType, - TypedData, - WeierstrassSignatureType, -} from "starknet"; +import { setupWorld } from "./typescript/contracts.gen"; +import { Account, ArraySignatureType } from "starknet"; import { BurnerManager } from "@dojoengine/create-burner"; +import { getSyncEvents, getSyncEntities } from "@dojoengine/state"; export type SetupResult = Awaited>; @@ -25,7 +16,7 @@ export async function setup({ ...config }: DojoConfig) { const toriiClient = await torii.createClient({ rpcUrl: config.rpcUrl, toriiUrl: config.toriiUrl, - relayUrl: config.relayUrl, + relayUrl: "", worldAddress: config.manifest.world.address || "", }); @@ -35,15 +26,23 @@ export async function setup({ ...config }: DojoConfig) { // create client components const clientComponents = createClientComponents({ contractComponents }); - // fetch all existing entities from torii - const sync = await getSyncEntities( + // create dojo provider + const dojoProvider = new DojoProvider(config.manifest, config.rpcUrl); + + // Sync all events + const eventSync = getSyncEvents( toriiClient, contractComponents as any, + undefined, [] ); - // create dojo provider - const dojoProvider = new DojoProvider(config.manifest, config.rpcUrl); + // Sync all entities + const sync = await getSyncEntities( + toriiClient, + contractComponents as any, + [] + ); // setup world const client = await setupWorld(dojoProvider); @@ -75,17 +74,15 @@ export async function setup({ ...config }: DojoConfig) { client, clientComponents, contractComponents, - systemCalls: createSystemCalls( - { client }, - contractComponents, - clientComponents - ), + systemCalls: createSystemCalls({ client }, clientComponents, world), publish: (typedData: string, signature: ArraySignatureType) => { toriiClient.publishMessage(typedData, signature); }, config, dojoProvider, burnerManager, + toriiClient, + eventSync, sync, }; } diff --git a/examples/clients/react/react-phaser-example/src/dojo/typescript/contracts.gen.ts b/examples/clients/react/react-phaser-example/src/dojo/typescript/contracts.gen.ts new file mode 100644 index 00000000..0f7fac11 --- /dev/null +++ b/examples/clients/react/react-phaser-example/src/dojo/typescript/contracts.gen.ts @@ -0,0 +1,86 @@ +// Generated by dojo-bindgen on Thu, 22 Aug 2024 20:04:33 +0000. Do not modify this file manually. +// Import the necessary types from the recs SDK +// generate again with `sozo build --typescript` +import { Account, byteArray } from "starknet"; +import { DojoProvider } from "@dojoengine/core"; +import * as models from "./models.gen"; + +export type IWorld = Awaited>; + +export async function setupWorld(provider: DojoProvider) { + // System definitions for `dojo_starter-actions` contract + function actions() { + const contract_name = "actions"; + + // Call the `spawn` system with the specified Account and calldata + const spawn = async (props: { account: Account }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "spawn", + calldata: [], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + // Call the `move` system with the specified Account and calldata + const move = async (props: { + account: Account; + direction: models.Direction; + }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "move", + calldata: [ + ["None", "Left", "Right", "Up", "Down"].indexOf( + props.direction.type + ), + ], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + // Call the `world` system with the specified Account and calldata + const world = async (props: { account: Account }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "world", + calldata: [], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + return { + spawn, + move, + world, + }; + } + + return { + actions: actions(), + }; +} diff --git a/examples/clients/react/react-phaser-example/src/dojo/typescript/models.gen.ts b/examples/clients/react/react-phaser-example/src/dojo/typescript/models.gen.ts new file mode 100644 index 00000000..051689eb --- /dev/null +++ b/examples/clients/react/react-phaser-example/src/dojo/typescript/models.gen.ts @@ -0,0 +1,193 @@ +// Generated by dojo-bindgen on Thu, 22 Aug 2024 20:04:33 +0000. Do not modify this file manually. +// Import the necessary types from the recs SDK +// generate again with `sozo build --typescript` +import { defineComponent, Type as RecsType, World } from "@dojoengine/recs"; + +export type ContractComponents = Awaited< + ReturnType +>; + +// Type definition for `dojo_starter::models::Direction` enum +export type Direction = + | { type: "None" } + | { type: "Left" } + | { type: "Right" } + | { type: "Up" } + | { type: "Down" }; + +export const DirectionDefinition = { + type: RecsType.String, + value: RecsType.String, +}; + +// Type definition for `dojo::model::layout::Layout` enum +export type Layout = + | { type: "Fixed"; value: RecsType.NumberArray } + | { type: "Struct"; value: RecsType.StringArray } + | { type: "Tuple"; value: RecsType.StringArray } + | { type: "Array"; value: RecsType.StringArray } + | { type: "ByteArray" } + | { type: "Enum"; value: RecsType.StringArray }; + +export const LayoutDefinition = { + type: RecsType.String, + value: RecsType.String, +}; + +// Type definition for `core::byte_array::ByteArray` struct +export interface ByteArray { + data: String[]; + pending_word: BigInt; + pending_word_len: Number; +} +export const ByteArrayDefinition = { + data: RecsType.StringArray, + pending_word: RecsType.BigInt, + pending_word_len: RecsType.Number, +}; + +// Type definition for `dojo::model::layout::FieldLayout` struct +export interface FieldLayout { + selector: BigInt; + layout: Layout; +} +export const FieldLayoutDefinition = { + selector: RecsType.BigInt, + layout: LayoutDefinition, +}; + +// Type definition for `dojo_starter::models::Moves` struct +export interface Moves { + player: BigInt; + remaining: Number; + last_direction: Direction; + can_move: Boolean; +} +export const MovesDefinition = { + player: RecsType.BigInt, + remaining: RecsType.Number, + last_direction: DirectionDefinition, + can_move: RecsType.Boolean, +}; + +// Type definition for `dojo_starter::models::DirectionsAvailable` struct +export interface DirectionsAvailable { + player: BigInt; + directions: String[]; +} +export const DirectionsAvailableDefinition = { + player: RecsType.BigInt, + directions: RecsType.StringArray, +}; + +// Type definition for `dojo_starter::systems::actions::actions::Moved` struct +export interface Moved { + player: BigInt; + direction: Direction; +} +export const MovedDefinition = { + player: RecsType.BigInt, + direction: DirectionDefinition, +}; + +// Type definition for `dojo_starter::models::Vec2` struct +export interface Vec2 { + x: Number; + y: Number; +} +export const Vec2Definition = { + x: RecsType.Number, + y: RecsType.Number, +}; + +// Type definition for `dojo_starter::models::Position` struct +export interface Position { + player: BigInt; + vec: Vec2; +} +export const PositionDefinition = { + player: RecsType.BigInt, + vec: Vec2Definition, +}; + +export function defineContractComponents(world: World) { + return { + // Model definition for `dojo_starter::models::Moves` model + Moves: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + remaining: RecsType.Number, + last_direction: RecsType.String, + can_move: RecsType.Boolean, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Moves", + types: ["ContractAddress", "u8", "Direction", "bool"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::models::DirectionsAvailable` model + DirectionsAvailable: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + directions: RecsType.StringArray, + }, + { + metadata: { + namespace: "dojo_starter", + name: "DirectionsAvailable", + types: ["ContractAddress", "array"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::systems::actions::actions::Moved` model + Moved: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + direction: RecsType.String, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Moved", + types: ["ContractAddress", "Direction"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::models::Position` model + Position: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + vec: Vec2Definition, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Position", + types: ["ContractAddress"], + customTypes: ["Vec2"], + }, + } + ); + })(), + }; +} diff --git a/examples/clients/react/react-phaser-example/src/phaser/config/constants.ts b/examples/clients/react/react-phaser-example/src/phaser/config/constants.ts index 18d3c87c..8bafc664 100644 --- a/examples/clients/react/react-phaser-example/src/phaser/config/constants.ts +++ b/examples/clients/react/react-phaser-example/src/phaser/config/constants.ts @@ -35,14 +35,6 @@ export enum Assets { Tileset = "Tileset", } -export enum Direction { - Unknown, - Up, - Down, - Left, - Right, -} - export const TILE_HEIGHT = 32; export const TILE_WIDTH = 32; diff --git a/examples/clients/react/react-phaser-example/src/phaser/systems/controls.ts b/examples/clients/react/react-phaser-example/src/phaser/systems/controls.ts index d9260b9b..9c3a295e 100644 --- a/examples/clients/react/react-phaser-example/src/phaser/systems/controls.ts +++ b/examples/clients/react/react-phaser-example/src/phaser/systems/controls.ts @@ -1,5 +1,5 @@ +import { DirectionDefinition } from "../../dojo/typescript/models.gen"; import { PhaserLayer } from "../createPhaserLayer"; -import { Direction } from "../../dojo/utils"; export const controls = (layer: PhaserLayer) => { const { @@ -15,56 +15,56 @@ export const controls = (layer: PhaserLayer) => { input.onKeyPress( (keys) => keys.has("W"), () => { - move(account, Direction.Up); + move(account, { type: "Up" }); } ); input.onKeyPress( (keys) => keys.has("A"), () => { - move(account, Direction.Left); + move(account, { type: "Left" }); } ); input.onKeyPress( (keys) => keys.has("S"), () => { - move(account, Direction.Down); + move(account, { type: "Down" }); } ); input.onKeyPress( (keys) => keys.has("D"), () => { - move(account, Direction.Right); + move(account, { type: "Right" }); } ); input.onKeyPress( (keys) => keys.has("UP"), () => { - move(account, Direction.Up); + move(account, { type: "Up" }); } ); input.onKeyPress( (keys) => keys.has("LEFT"), () => { - move(account, Direction.Left); + move(account, { type: "Left" }); } ); input.onKeyPress( (keys) => keys.has("DOWN"), () => { - move(account, Direction.Down); + move(account, { type: "Down" }); } ); input.onKeyPress( (keys) => keys.has("RIGHT"), () => { - move(account, Direction.Right); + move(account, { type: "Right" }); } ); }; diff --git a/examples/clients/react/react-pwa-app/src/App.tsx b/examples/clients/react/react-pwa-app/src/App.tsx index a171c899..f3493462 100644 --- a/examples/clients/react/react-pwa-app/src/App.tsx +++ b/examples/clients/react/react-pwa-app/src/App.tsx @@ -119,7 +119,7 @@ function App() {
diff --git a/examples/clients/react/react-pwa-app/src/dojo/DojoContext.tsx b/examples/clients/react/react-pwa-app/src/dojo/DojoContext.tsx index cebee9dc..ee5ecd6c 100644 --- a/examples/clients/react/react-pwa-app/src/dojo/DojoContext.tsx +++ b/examples/clients/react/react-pwa-app/src/dojo/DojoContext.tsx @@ -1,7 +1,7 @@ import { BurnerAccount, useBurnerManager } from "@dojoengine/create-burner"; import { ReactNode, createContext, useContext, useMemo } from "react"; import { Account } from "starknet"; -import { SetupResult } from "./generated/setup"; +import { SetupResult } from "./setup"; interface DojoContextType extends SetupResult { masterAccount: Account; diff --git a/examples/clients/react/react-pwa-app/src/dojo/createClientComponents.ts b/examples/clients/react/react-pwa-app/src/dojo/createClientComponents.ts index 550ad13b..7b5bb860 100644 --- a/examples/clients/react/react-pwa-app/src/dojo/createClientComponents.ts +++ b/examples/clients/react/react-pwa-app/src/dojo/createClientComponents.ts @@ -1,5 +1,5 @@ import { overridableComponent } from "@dojoengine/recs"; -import { ContractComponents } from "./generated/contractComponents"; +import { ContractComponents } from "./typescript/models.gen"; export type ClientComponents = ReturnType; diff --git a/examples/clients/react/react-pwa-app/src/dojo/createSystemCalls.ts b/examples/clients/react/react-pwa-app/src/dojo/createSystemCalls.ts index 94a204df..38475f37 100644 --- a/examples/clients/react/react-pwa-app/src/dojo/createSystemCalls.ts +++ b/examples/clients/react/react-pwa-app/src/dojo/createSystemCalls.ts @@ -1,57 +1,71 @@ import { Account, AccountInterface } from "starknet"; -import { Entity, getComponentValue } from "@dojoengine/recs"; +import { + Entity, + Has, + HasValue, + World, + defineSystem, + getComponentValue, +} from "@dojoengine/recs"; import { uuid } from "@latticexyz/utils"; import { ClientComponents } from "./createClientComponents"; -import { Direction, updatePositionWithDirection } from "../utils"; -import { - getEntityIdFromKeys, - getEvents, - setComponentsFromEvents, -} from "@dojoengine/utils"; -import { ContractComponents } from "./generated/contractComponents"; -import type { IWorld } from "./generated/generated"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; +import type { IWorld } from "./typescript/contracts.gen"; +import { Direction } from "./typescript/models.gen"; export type SystemCalls = ReturnType; export function createSystemCalls( { client }: { client: IWorld }, - contractComponents: ContractComponents, - { Position, Moves }: ClientComponents + { Position, Moves }: ClientComponents, + world: World ) { - const spawn = async (account: AccountInterface) => { + const spawn = async (account: Account) => { const entityId = getEntityIdFromKeys([ BigInt(account.address), ]) as Entity; - const positionId = uuid(); - Position.addOverride(positionId, { + const movesId = uuid(); + Moves.addOverride(movesId, { entity: entityId, - value: { player: BigInt(entityId), vec: { x: 10, y: 10 } }, + value: { + player: BigInt(entityId), + remaining: + (getComponentValue(Moves, entityId)?.remaining || 0) + 100, + }, }); - const movesId = uuid(); - Moves.addOverride(movesId, { + const positionId = uuid(); + Position.addOverride(positionId, { entity: entityId, value: { player: BigInt(entityId), - remaining: 100, - last_direction: 0, + vec: { + x: 10 + (getComponentValue(Position, entityId)?.vec.x || 0), + y: 10 + (getComponentValue(Position, entityId)?.vec.y || 0), + }, }, }); try { - const { transaction_hash } = await client.actions.spawn({ + await client.actions.spawn({ account, }); - setComponentsFromEvents( - contractComponents, - getEvents( - await account.waitForTransaction(transaction_hash, { - retryInterval: 100, - }) - ) - ); + // Wait for the indexer to update the entity + // By doing this we keep the optimistic UI in sync with the actual state + await new Promise((resolve) => { + defineSystem( + world, + [ + Has(Moves), + HasValue(Moves, { player: BigInt(account.address) }), + ], + () => { + resolve(); + } + ); + }); } catch (e) { console.log(e); Position.removeOverride(positionId); @@ -62,54 +76,29 @@ export function createSystemCalls( } }; - const move = async (account: AccountInterface, direction: Direction) => { - const entityId = getEntityIdFromKeys([ - BigInt(account.address), - ]) as Entity; - - const positionId = uuid(); - Position.addOverride(positionId, { - entity: entityId, - value: { - player: BigInt(entityId), - vec: updatePositionWithDirection( - direction, - getComponentValue(Position, entityId) as any - ).vec, - }, - }); - - const movesId = uuid(); - Moves.addOverride(movesId, { - entity: entityId, - value: { - player: BigInt(entityId), - remaining: - (getComponentValue(Moves, entityId)?.remaining || 0) - 1, - }, - }); - + const move = async (account: Account, direction: Direction) => { try { - const { transaction_hash } = await client.actions.move({ + await client.actions.move({ account, direction, }); - setComponentsFromEvents( - contractComponents, - getEvents( - await account.waitForTransaction(transaction_hash, { - retryInterval: 100, - }) - ) - ); + // Wait for the indexer to update the entity + // By doing this we keep the optimistic UI in sync with the actual state + await new Promise((resolve) => { + defineSystem( + world, + [ + Has(Moves), + HasValue(Moves, { player: BigInt(account.address) }), + ], + () => { + resolve(); + } + ); + }); } catch (e) { console.log(e); - Position.removeOverride(positionId); - Moves.removeOverride(movesId); - } finally { - Position.removeOverride(positionId); - Moves.removeOverride(movesId); } }; diff --git a/examples/clients/react/react-pwa-app/src/dojo/generated/contractComponents.ts b/examples/clients/react/react-pwa-app/src/dojo/generated/contractComponents.ts deleted file mode 100644 index 596871c8..00000000 --- a/examples/clients/react/react-pwa-app/src/dojo/generated/contractComponents.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Autogenerated file. Do not edit manually. - * Generated using @dojoengine/core - * Command: npx @dojoengine/core - */ - -import { defineComponent, Type as RecsType, World } from "@dojoengine/recs"; - -export type ContractComponents = Awaited< - ReturnType ->; - -export function defineContractComponents(world: World) { - return { - DirectionsAvailable: (() => { - return defineComponent( - world, - { player: RecsType.BigInt, directions: RecsType.StringArray }, - { - metadata: { - name: "dojo_starter-DirectionsAvailable", - types: ["contractaddress"], - customTypes: ["Direction"], - }, - } - ); - })(), - Moves: (() => { - return defineComponent( - world, - { - player: RecsType.BigInt, - remaining: RecsType.Number, - last_direction: RecsType.Number, - can_move: RecsType.Boolean, - }, - { - metadata: { - name: "dojo_starter-Moves", - types: ["contractaddress", "u8", "enum", "bool"], - customTypes: ["Direction"], - }, - } - ); - })(), - Position: (() => { - return defineComponent( - world, - { - player: RecsType.BigInt, - vec: { x: RecsType.Number, y: RecsType.Number }, - }, - { - metadata: { - name: "dojo_starter-Position", - types: ["contractaddress", "u32", "u32"], - customTypes: ["Vec2"], - }, - } - ); - })(), - }; -} diff --git a/examples/clients/react/react-pwa-app/src/dojo/generated/generated.ts b/examples/clients/react/react-pwa-app/src/dojo/generated/generated.ts deleted file mode 100644 index b10d2654..00000000 --- a/examples/clients/react/react-pwa-app/src/dojo/generated/generated.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Account, AccountInterface } from "starknet"; -import { DojoProvider } from "@dojoengine/core"; -import { Direction } from "../../utils"; - -const NAMESPACE = "dojo_starter"; - -export interface IWorld { - actions: { - spawn: (props: { account: AccountInterface }) => Promise; - move: (props: MoveProps) => Promise; - }; -} - -export interface MoveProps { - account: Account | AccountInterface; - direction: Direction; -} - -const handleError = (action: string, error: unknown) => { - console.error(`Error executing ${action}:`, error); - throw error; -}; - -export const setupWorld = async (provider: DojoProvider): Promise => { - const actions = () => ({ - spawn: async ({ account }: { account: AccountInterface }) => { - try { - return await provider.execute( - account, - { - contractName: "actions", - entrypoint: "spawn", - calldata: [], - }, - NAMESPACE - ); - } catch (error) { - handleError("spawn", error); - } - }, - - move: async ({ account, direction }: MoveProps) => { - try { - return await provider.execute( - account, - { - contractName: "actions", - entrypoint: "move", - calldata: [direction], - }, - NAMESPACE - ); - } catch (error) { - handleError("move", error); - } - }, - }); - - return { actions: actions() }; -}; diff --git a/examples/clients/react/react-pwa-app/src/dojo/setup.ts b/examples/clients/react/react-pwa-app/src/dojo/setup.ts new file mode 100644 index 00000000..0ad7139c --- /dev/null +++ b/examples/clients/react/react-pwa-app/src/dojo/setup.ts @@ -0,0 +1,88 @@ +import { DojoConfig, DojoProvider } from "@dojoengine/core"; +import * as torii from "@dojoengine/torii-client"; +import { createClientComponents } from "./createClientComponents"; +import { createSystemCalls } from "./createSystemCalls"; +import { defineContractComponents } from "./typescript/models.gen"; +import { world } from "./world"; +import { setupWorld } from "./typescript/contracts.gen"; +import { Account, ArraySignatureType } from "starknet"; +import { BurnerManager } from "@dojoengine/create-burner"; +import { getSyncEvents, getSyncEntities } from "@dojoengine/state"; + +export type SetupResult = Awaited>; + +export async function setup({ ...config }: DojoConfig) { + // torii client + const toriiClient = await torii.createClient({ + rpcUrl: config.rpcUrl, + toriiUrl: config.toriiUrl, + relayUrl: "", + worldAddress: config.manifest.world.address || "", + }); + + // create contract components + const contractComponents = defineContractComponents(world); + + // create client components + const clientComponents = createClientComponents({ contractComponents }); + + // create dojo provider + const dojoProvider = new DojoProvider(config.manifest, config.rpcUrl); + + // Sync all events + const eventSync = getSyncEvents( + toriiClient, + contractComponents as any, + undefined, + [] + ); + + // Sync all entities + const sync = await getSyncEntities( + toriiClient, + contractComponents as any, + [] + ); + + // setup world + const client = await setupWorld(dojoProvider); + + // create burner manager + const burnerManager = new BurnerManager({ + masterAccount: new Account( + { + nodeUrl: config.rpcUrl, + }, + config.masterAddress, + config.masterPrivateKey + ), + accountClassHash: config.accountClassHash, + rpcProvider: dojoProvider.provider, + feeTokenAddress: config.feeTokenAddress, + }); + + try { + await burnerManager.init(); + if (burnerManager.list().length === 0) { + await burnerManager.create(); + } + } catch (e) { + console.error(e); + } + + return { + client, + clientComponents, + contractComponents, + systemCalls: createSystemCalls({ client }, clientComponents, world), + publish: (typedData: string, signature: ArraySignatureType) => { + toriiClient.publishMessage(typedData, signature); + }, + config, + dojoProvider, + burnerManager, + toriiClient, + eventSync, + sync, + }; +} diff --git a/examples/clients/react/react-pwa-app/src/dojo/typescript/contracts.gen.ts b/examples/clients/react/react-pwa-app/src/dojo/typescript/contracts.gen.ts new file mode 100644 index 00000000..0f7fac11 --- /dev/null +++ b/examples/clients/react/react-pwa-app/src/dojo/typescript/contracts.gen.ts @@ -0,0 +1,86 @@ +// Generated by dojo-bindgen on Thu, 22 Aug 2024 20:04:33 +0000. Do not modify this file manually. +// Import the necessary types from the recs SDK +// generate again with `sozo build --typescript` +import { Account, byteArray } from "starknet"; +import { DojoProvider } from "@dojoengine/core"; +import * as models from "./models.gen"; + +export type IWorld = Awaited>; + +export async function setupWorld(provider: DojoProvider) { + // System definitions for `dojo_starter-actions` contract + function actions() { + const contract_name = "actions"; + + // Call the `spawn` system with the specified Account and calldata + const spawn = async (props: { account: Account }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "spawn", + calldata: [], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + // Call the `move` system with the specified Account and calldata + const move = async (props: { + account: Account; + direction: models.Direction; + }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "move", + calldata: [ + ["None", "Left", "Right", "Up", "Down"].indexOf( + props.direction.type + ), + ], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + // Call the `world` system with the specified Account and calldata + const world = async (props: { account: Account }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "world", + calldata: [], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + return { + spawn, + move, + world, + }; + } + + return { + actions: actions(), + }; +} diff --git a/examples/clients/react/react-pwa-app/src/dojo/typescript/models.gen.ts b/examples/clients/react/react-pwa-app/src/dojo/typescript/models.gen.ts new file mode 100644 index 00000000..051689eb --- /dev/null +++ b/examples/clients/react/react-pwa-app/src/dojo/typescript/models.gen.ts @@ -0,0 +1,193 @@ +// Generated by dojo-bindgen on Thu, 22 Aug 2024 20:04:33 +0000. Do not modify this file manually. +// Import the necessary types from the recs SDK +// generate again with `sozo build --typescript` +import { defineComponent, Type as RecsType, World } from "@dojoengine/recs"; + +export type ContractComponents = Awaited< + ReturnType +>; + +// Type definition for `dojo_starter::models::Direction` enum +export type Direction = + | { type: "None" } + | { type: "Left" } + | { type: "Right" } + | { type: "Up" } + | { type: "Down" }; + +export const DirectionDefinition = { + type: RecsType.String, + value: RecsType.String, +}; + +// Type definition for `dojo::model::layout::Layout` enum +export type Layout = + | { type: "Fixed"; value: RecsType.NumberArray } + | { type: "Struct"; value: RecsType.StringArray } + | { type: "Tuple"; value: RecsType.StringArray } + | { type: "Array"; value: RecsType.StringArray } + | { type: "ByteArray" } + | { type: "Enum"; value: RecsType.StringArray }; + +export const LayoutDefinition = { + type: RecsType.String, + value: RecsType.String, +}; + +// Type definition for `core::byte_array::ByteArray` struct +export interface ByteArray { + data: String[]; + pending_word: BigInt; + pending_word_len: Number; +} +export const ByteArrayDefinition = { + data: RecsType.StringArray, + pending_word: RecsType.BigInt, + pending_word_len: RecsType.Number, +}; + +// Type definition for `dojo::model::layout::FieldLayout` struct +export interface FieldLayout { + selector: BigInt; + layout: Layout; +} +export const FieldLayoutDefinition = { + selector: RecsType.BigInt, + layout: LayoutDefinition, +}; + +// Type definition for `dojo_starter::models::Moves` struct +export interface Moves { + player: BigInt; + remaining: Number; + last_direction: Direction; + can_move: Boolean; +} +export const MovesDefinition = { + player: RecsType.BigInt, + remaining: RecsType.Number, + last_direction: DirectionDefinition, + can_move: RecsType.Boolean, +}; + +// Type definition for `dojo_starter::models::DirectionsAvailable` struct +export interface DirectionsAvailable { + player: BigInt; + directions: String[]; +} +export const DirectionsAvailableDefinition = { + player: RecsType.BigInt, + directions: RecsType.StringArray, +}; + +// Type definition for `dojo_starter::systems::actions::actions::Moved` struct +export interface Moved { + player: BigInt; + direction: Direction; +} +export const MovedDefinition = { + player: RecsType.BigInt, + direction: DirectionDefinition, +}; + +// Type definition for `dojo_starter::models::Vec2` struct +export interface Vec2 { + x: Number; + y: Number; +} +export const Vec2Definition = { + x: RecsType.Number, + y: RecsType.Number, +}; + +// Type definition for `dojo_starter::models::Position` struct +export interface Position { + player: BigInt; + vec: Vec2; +} +export const PositionDefinition = { + player: RecsType.BigInt, + vec: Vec2Definition, +}; + +export function defineContractComponents(world: World) { + return { + // Model definition for `dojo_starter::models::Moves` model + Moves: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + remaining: RecsType.Number, + last_direction: RecsType.String, + can_move: RecsType.Boolean, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Moves", + types: ["ContractAddress", "u8", "Direction", "bool"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::models::DirectionsAvailable` model + DirectionsAvailable: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + directions: RecsType.StringArray, + }, + { + metadata: { + namespace: "dojo_starter", + name: "DirectionsAvailable", + types: ["ContractAddress", "array"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::systems::actions::actions::Moved` model + Moved: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + direction: RecsType.String, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Moved", + types: ["ContractAddress", "Direction"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::models::Position` model + Position: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + vec: Vec2Definition, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Position", + types: ["ContractAddress"], + customTypes: ["Vec2"], + }, + } + ); + })(), + }; +} diff --git a/examples/clients/react/react-phaser-example/src/dojo/generated/world.ts b/examples/clients/react/react-pwa-app/src/dojo/world.ts similarity index 100% rename from examples/clients/react/react-phaser-example/src/dojo/generated/world.ts rename to examples/clients/react/react-pwa-app/src/dojo/world.ts diff --git a/examples/clients/react/react-pwa-app/src/main.tsx b/examples/clients/react/react-pwa-app/src/main.tsx index 5af1fa50..5ae7a19c 100644 --- a/examples/clients/react/react-pwa-app/src/main.tsx +++ b/examples/clients/react/react-pwa-app/src/main.tsx @@ -2,9 +2,9 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App.tsx"; import "./index.css"; -import { setup } from "./dojo/generated/setup.ts"; import { DojoProvider } from "./dojo/DojoContext.tsx"; import { dojoConfig } from "../dojoConfig.ts"; +import { setup } from "./dojo/setup.ts"; async function init() { const rootElement = document.getElementById("root"); diff --git a/examples/clients/react/react-threejs/src/dojo/DojoContext.tsx b/examples/clients/react/react-threejs/src/dojo/DojoContext.tsx index 8c82a8f2..ee5ecd6c 100644 --- a/examples/clients/react/react-threejs/src/dojo/DojoContext.tsx +++ b/examples/clients/react/react-threejs/src/dojo/DojoContext.tsx @@ -1,7 +1,7 @@ import { BurnerAccount, useBurnerManager } from "@dojoengine/create-burner"; import { ReactNode, createContext, useContext, useMemo } from "react"; -import { Account, RpcProvider } from "starknet"; -import { SetupResult } from "./generated/setup"; +import { Account } from "starknet"; +import { SetupResult } from "./setup"; interface DojoContextType extends SetupResult { masterAccount: Account; @@ -21,21 +21,20 @@ export const DojoProvider = ({ if (currentValue) throw new Error("DojoProvider can only be used once"); const { - config: { rpcUrl, masterAddress, masterPrivateKey }, + config: { masterAddress, masterPrivateKey }, burnerManager, + dojoProvider, } = value; - const rpcProvider = useMemo( - () => - new RpcProvider({ - nodeUrl: rpcUrl, - }), - [rpcUrl] - ); - const masterAccount = useMemo( - () => new Account(rpcProvider, masterAddress, masterPrivateKey), - [rpcProvider, masterAddress, masterPrivateKey] + () => + new Account( + dojoProvider.provider, + masterAddress, + masterPrivateKey, + "1" + ), + [masterAddress, masterPrivateKey, dojoProvider.provider] ); const { diff --git a/examples/clients/react/react-threejs/src/dojo/createClientComponents.ts b/examples/clients/react/react-threejs/src/dojo/createClientComponents.ts index 550ad13b..7b5bb860 100644 --- a/examples/clients/react/react-threejs/src/dojo/createClientComponents.ts +++ b/examples/clients/react/react-threejs/src/dojo/createClientComponents.ts @@ -1,5 +1,5 @@ import { overridableComponent } from "@dojoengine/recs"; -import { ContractComponents } from "./generated/contractComponents"; +import { ContractComponents } from "./typescript/models.gen"; export type ClientComponents = ReturnType; diff --git a/examples/clients/react/react-threejs/src/dojo/createSystemCalls.ts b/examples/clients/react/react-threejs/src/dojo/createSystemCalls.ts index 55041b11..38475f37 100644 --- a/examples/clients/react/react-threejs/src/dojo/createSystemCalls.ts +++ b/examples/clients/react/react-threejs/src/dojo/createSystemCalls.ts @@ -1,57 +1,71 @@ -import { AccountInterface } from "starknet"; -import { Entity, getComponentValue } from "@dojoengine/recs"; +import { Account, AccountInterface } from "starknet"; +import { + Entity, + Has, + HasValue, + World, + defineSystem, + getComponentValue, +} from "@dojoengine/recs"; import { uuid } from "@latticexyz/utils"; import { ClientComponents } from "./createClientComponents"; -import { Direction, updatePositionWithDirection } from "../utils"; -import { - getEntityIdFromKeys, - getEvents, - setComponentsFromEvents, -} from "@dojoengine/utils"; -import { ContractComponents } from "./generated/contractComponents"; -import type { IWorld } from "./generated/generated"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; +import type { IWorld } from "./typescript/contracts.gen"; +import { Direction } from "./typescript/models.gen"; export type SystemCalls = ReturnType; export function createSystemCalls( { client }: { client: IWorld }, - contractComponents: ContractComponents, - { Position, Moves }: ClientComponents + { Position, Moves }: ClientComponents, + world: World ) { - const spawn = async (account: AccountInterface) => { + const spawn = async (account: Account) => { const entityId = getEntityIdFromKeys([ BigInt(account.address), ]) as Entity; - const positionId = uuid(); - Position.addOverride(positionId, { + const movesId = uuid(); + Moves.addOverride(movesId, { entity: entityId, - value: { player: BigInt(entityId), vec: { x: 10, y: 10 } }, + value: { + player: BigInt(entityId), + remaining: + (getComponentValue(Moves, entityId)?.remaining || 0) + 100, + }, }); - const movesId = uuid(); - Moves.addOverride(movesId, { + const positionId = uuid(); + Position.addOverride(positionId, { entity: entityId, value: { player: BigInt(entityId), - remaining: 100, - last_direction: 0, + vec: { + x: 10 + (getComponentValue(Position, entityId)?.vec.x || 0), + y: 10 + (getComponentValue(Position, entityId)?.vec.y || 0), + }, }, }); try { - const { transaction_hash } = await client.actions.spawn({ + await client.actions.spawn({ account, }); - setComponentsFromEvents( - contractComponents, - getEvents( - await account.waitForTransaction(transaction_hash, { - retryInterval: 100, - }) - ) - ); + // Wait for the indexer to update the entity + // By doing this we keep the optimistic UI in sync with the actual state + await new Promise((resolve) => { + defineSystem( + world, + [ + Has(Moves), + HasValue(Moves, { player: BigInt(account.address) }), + ], + () => { + resolve(); + } + ); + }); } catch (e) { console.log(e); Position.removeOverride(positionId); @@ -62,54 +76,29 @@ export function createSystemCalls( } }; - const move = async (account: AccountInterface, direction: Direction) => { - const entityId = getEntityIdFromKeys([ - BigInt(account.address), - ]) as Entity; - - const positionId = uuid(); - Position.addOverride(positionId, { - entity: entityId, - value: { - player: BigInt(entityId), - vec: updatePositionWithDirection( - direction, - getComponentValue(Position, entityId) as any - ).vec, - }, - }); - - const movesId = uuid(); - Moves.addOverride(movesId, { - entity: entityId, - value: { - player: BigInt(entityId), - remaining: - (getComponentValue(Moves, entityId)?.remaining || 0) - 1, - }, - }); - + const move = async (account: Account, direction: Direction) => { try { - const { transaction_hash } = await client.actions.move({ + await client.actions.move({ account, direction, }); - setComponentsFromEvents( - contractComponents, - getEvents( - await account.waitForTransaction(transaction_hash, { - retryInterval: 100, - }) - ) - ); + // Wait for the indexer to update the entity + // By doing this we keep the optimistic UI in sync with the actual state + await new Promise((resolve) => { + defineSystem( + world, + [ + Has(Moves), + HasValue(Moves, { player: BigInt(account.address) }), + ], + () => { + resolve(); + } + ); + }); } catch (e) { console.log(e); - Position.removeOverride(positionId); - Moves.removeOverride(movesId); - } finally { - Position.removeOverride(positionId); - Moves.removeOverride(movesId); } }; diff --git a/examples/clients/react/react-threejs/src/dojo/generated/contractComponents.ts b/examples/clients/react/react-threejs/src/dojo/generated/contractComponents.ts deleted file mode 100644 index 596871c8..00000000 --- a/examples/clients/react/react-threejs/src/dojo/generated/contractComponents.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Autogenerated file. Do not edit manually. - * Generated using @dojoengine/core - * Command: npx @dojoengine/core - */ - -import { defineComponent, Type as RecsType, World } from "@dojoengine/recs"; - -export type ContractComponents = Awaited< - ReturnType ->; - -export function defineContractComponents(world: World) { - return { - DirectionsAvailable: (() => { - return defineComponent( - world, - { player: RecsType.BigInt, directions: RecsType.StringArray }, - { - metadata: { - name: "dojo_starter-DirectionsAvailable", - types: ["contractaddress"], - customTypes: ["Direction"], - }, - } - ); - })(), - Moves: (() => { - return defineComponent( - world, - { - player: RecsType.BigInt, - remaining: RecsType.Number, - last_direction: RecsType.Number, - can_move: RecsType.Boolean, - }, - { - metadata: { - name: "dojo_starter-Moves", - types: ["contractaddress", "u8", "enum", "bool"], - customTypes: ["Direction"], - }, - } - ); - })(), - Position: (() => { - return defineComponent( - world, - { - player: RecsType.BigInt, - vec: { x: RecsType.Number, y: RecsType.Number }, - }, - { - metadata: { - name: "dojo_starter-Position", - types: ["contractaddress", "u32", "u32"], - customTypes: ["Vec2"], - }, - } - ); - })(), - }; -} diff --git a/examples/clients/react/react-threejs/src/dojo/generated/generated.ts b/examples/clients/react/react-threejs/src/dojo/generated/generated.ts deleted file mode 100644 index b10d2654..00000000 --- a/examples/clients/react/react-threejs/src/dojo/generated/generated.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Account, AccountInterface } from "starknet"; -import { DojoProvider } from "@dojoengine/core"; -import { Direction } from "../../utils"; - -const NAMESPACE = "dojo_starter"; - -export interface IWorld { - actions: { - spawn: (props: { account: AccountInterface }) => Promise; - move: (props: MoveProps) => Promise; - }; -} - -export interface MoveProps { - account: Account | AccountInterface; - direction: Direction; -} - -const handleError = (action: string, error: unknown) => { - console.error(`Error executing ${action}:`, error); - throw error; -}; - -export const setupWorld = async (provider: DojoProvider): Promise => { - const actions = () => ({ - spawn: async ({ account }: { account: AccountInterface }) => { - try { - return await provider.execute( - account, - { - contractName: "actions", - entrypoint: "spawn", - calldata: [], - }, - NAMESPACE - ); - } catch (error) { - handleError("spawn", error); - } - }, - - move: async ({ account, direction }: MoveProps) => { - try { - return await provider.execute( - account, - { - contractName: "actions", - entrypoint: "move", - calldata: [direction], - }, - NAMESPACE - ); - } catch (error) { - handleError("move", error); - } - }, - }); - - return { actions: actions() }; -}; diff --git a/examples/clients/react/react-threejs/src/dojo/generated/setup.ts b/examples/clients/react/react-threejs/src/dojo/setup.ts similarity index 66% rename from examples/clients/react/react-threejs/src/dojo/generated/setup.ts rename to examples/clients/react/react-threejs/src/dojo/setup.ts index 2111ab7a..de26fca9 100644 --- a/examples/clients/react/react-threejs/src/dojo/generated/setup.ts +++ b/examples/clients/react/react-threejs/src/dojo/setup.ts @@ -1,24 +1,13 @@ -import { getSyncEntities } from "@dojoengine/state"; -import { - DojoProvider, - DojoConfig, - createModelTypedData, -} from "@dojoengine/core"; +import { DojoConfig, DojoProvider } from "@dojoengine/core"; import * as torii from "@dojoengine/torii-client"; -import { createClientComponents } from "../createClientComponents"; -import { createSystemCalls } from "../createSystemCalls"; -import { defineContractComponents } from "./contractComponents"; +import { createClientComponents } from "./createClientComponents"; +import { createSystemCalls } from "./createSystemCalls"; +import { defineContractComponents } from "./typescript/models.gen"; import { world } from "./world"; -import { setupWorld } from "./generated"; -import { - Account, - ArraySignatureType, - RpcProvider, - Signature, - TypedData, - WeierstrassSignatureType, -} from "starknet"; +import { setupWorld } from "./typescript/contracts.gen"; +import { Account, ArraySignatureType } from "starknet"; import { BurnerManager } from "@dojoengine/create-burner"; +import { getSyncEvents, getSyncEntities } from "@dojoengine/state"; export type SetupResult = Awaited>; @@ -27,7 +16,7 @@ export async function setup({ ...config }: DojoConfig) { const toriiClient = await torii.createClient({ rpcUrl: config.rpcUrl, toriiUrl: config.toriiUrl, - relayUrl: config.relayUrl, + relayUrl: "", worldAddress: config.manifest.world.address || "", }); @@ -37,20 +26,28 @@ export async function setup({ ...config }: DojoConfig) { // create client components const clientComponents = createClientComponents({ contractComponents }); - // fetch all existing entities from torii - // fetch all existing entities from torii - const sync = await getSyncEntities( + // create dojo provider + const dojoProvider = new DojoProvider(config.manifest, config.rpcUrl); + + // Sync all events + const eventSync = getSyncEvents( toriiClient, contractComponents as any, + undefined, [] ); - const dojoProvider = new DojoProvider(config.manifest, config.rpcUrl); - - const client = await setupWorld( - new DojoProvider(config.manifest, config.rpcUrl) + // Sync all entities + const sync = await getSyncEntities( + toriiClient, + contractComponents as any, + [] ); + // setup world + const client = await setupWorld(dojoProvider); + + // create burner manager const burnerManager = new BurnerManager({ masterAccount: new Account( { @@ -77,17 +74,16 @@ export async function setup({ ...config }: DojoConfig) { client, clientComponents, contractComponents, - systemCalls: createSystemCalls( - { client }, - contractComponents, - clientComponents - ), + systemCalls: createSystemCalls({ client }, clientComponents, world), publish: (typedData: string, signature: ArraySignatureType) => { toriiClient.publishMessage(typedData, signature); }, config, - world, + dojoProvider, burnerManager, + toriiClient, + eventSync, sync, + world, }; } diff --git a/examples/clients/react/react-threejs/src/dojo/typescript/contracts.gen.ts b/examples/clients/react/react-threejs/src/dojo/typescript/contracts.gen.ts new file mode 100644 index 00000000..0f7fac11 --- /dev/null +++ b/examples/clients/react/react-threejs/src/dojo/typescript/contracts.gen.ts @@ -0,0 +1,86 @@ +// Generated by dojo-bindgen on Thu, 22 Aug 2024 20:04:33 +0000. Do not modify this file manually. +// Import the necessary types from the recs SDK +// generate again with `sozo build --typescript` +import { Account, byteArray } from "starknet"; +import { DojoProvider } from "@dojoengine/core"; +import * as models from "./models.gen"; + +export type IWorld = Awaited>; + +export async function setupWorld(provider: DojoProvider) { + // System definitions for `dojo_starter-actions` contract + function actions() { + const contract_name = "actions"; + + // Call the `spawn` system with the specified Account and calldata + const spawn = async (props: { account: Account }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "spawn", + calldata: [], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + // Call the `move` system with the specified Account and calldata + const move = async (props: { + account: Account; + direction: models.Direction; + }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "move", + calldata: [ + ["None", "Left", "Right", "Up", "Down"].indexOf( + props.direction.type + ), + ], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + // Call the `world` system with the specified Account and calldata + const world = async (props: { account: Account }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "world", + calldata: [], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + return { + spawn, + move, + world, + }; + } + + return { + actions: actions(), + }; +} diff --git a/examples/clients/react/react-threejs/src/dojo/typescript/models.gen.ts b/examples/clients/react/react-threejs/src/dojo/typescript/models.gen.ts new file mode 100644 index 00000000..051689eb --- /dev/null +++ b/examples/clients/react/react-threejs/src/dojo/typescript/models.gen.ts @@ -0,0 +1,193 @@ +// Generated by dojo-bindgen on Thu, 22 Aug 2024 20:04:33 +0000. Do not modify this file manually. +// Import the necessary types from the recs SDK +// generate again with `sozo build --typescript` +import { defineComponent, Type as RecsType, World } from "@dojoengine/recs"; + +export type ContractComponents = Awaited< + ReturnType +>; + +// Type definition for `dojo_starter::models::Direction` enum +export type Direction = + | { type: "None" } + | { type: "Left" } + | { type: "Right" } + | { type: "Up" } + | { type: "Down" }; + +export const DirectionDefinition = { + type: RecsType.String, + value: RecsType.String, +}; + +// Type definition for `dojo::model::layout::Layout` enum +export type Layout = + | { type: "Fixed"; value: RecsType.NumberArray } + | { type: "Struct"; value: RecsType.StringArray } + | { type: "Tuple"; value: RecsType.StringArray } + | { type: "Array"; value: RecsType.StringArray } + | { type: "ByteArray" } + | { type: "Enum"; value: RecsType.StringArray }; + +export const LayoutDefinition = { + type: RecsType.String, + value: RecsType.String, +}; + +// Type definition for `core::byte_array::ByteArray` struct +export interface ByteArray { + data: String[]; + pending_word: BigInt; + pending_word_len: Number; +} +export const ByteArrayDefinition = { + data: RecsType.StringArray, + pending_word: RecsType.BigInt, + pending_word_len: RecsType.Number, +}; + +// Type definition for `dojo::model::layout::FieldLayout` struct +export interface FieldLayout { + selector: BigInt; + layout: Layout; +} +export const FieldLayoutDefinition = { + selector: RecsType.BigInt, + layout: LayoutDefinition, +}; + +// Type definition for `dojo_starter::models::Moves` struct +export interface Moves { + player: BigInt; + remaining: Number; + last_direction: Direction; + can_move: Boolean; +} +export const MovesDefinition = { + player: RecsType.BigInt, + remaining: RecsType.Number, + last_direction: DirectionDefinition, + can_move: RecsType.Boolean, +}; + +// Type definition for `dojo_starter::models::DirectionsAvailable` struct +export interface DirectionsAvailable { + player: BigInt; + directions: String[]; +} +export const DirectionsAvailableDefinition = { + player: RecsType.BigInt, + directions: RecsType.StringArray, +}; + +// Type definition for `dojo_starter::systems::actions::actions::Moved` struct +export interface Moved { + player: BigInt; + direction: Direction; +} +export const MovedDefinition = { + player: RecsType.BigInt, + direction: DirectionDefinition, +}; + +// Type definition for `dojo_starter::models::Vec2` struct +export interface Vec2 { + x: Number; + y: Number; +} +export const Vec2Definition = { + x: RecsType.Number, + y: RecsType.Number, +}; + +// Type definition for `dojo_starter::models::Position` struct +export interface Position { + player: BigInt; + vec: Vec2; +} +export const PositionDefinition = { + player: RecsType.BigInt, + vec: Vec2Definition, +}; + +export function defineContractComponents(world: World) { + return { + // Model definition for `dojo_starter::models::Moves` model + Moves: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + remaining: RecsType.Number, + last_direction: RecsType.String, + can_move: RecsType.Boolean, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Moves", + types: ["ContractAddress", "u8", "Direction", "bool"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::models::DirectionsAvailable` model + DirectionsAvailable: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + directions: RecsType.StringArray, + }, + { + metadata: { + namespace: "dojo_starter", + name: "DirectionsAvailable", + types: ["ContractAddress", "array"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::systems::actions::actions::Moved` model + Moved: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + direction: RecsType.String, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Moved", + types: ["ContractAddress", "Direction"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::models::Position` model + Position: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + vec: Vec2Definition, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Position", + types: ["ContractAddress"], + customTypes: ["Vec2"], + }, + } + ); + })(), + }; +} diff --git a/examples/clients/react/react-pwa-app/src/dojo/generated/world.ts b/examples/clients/react/react-threejs/src/dojo/world.ts similarity index 100% rename from examples/clients/react/react-pwa-app/src/dojo/generated/world.ts rename to examples/clients/react/react-threejs/src/dojo/world.ts diff --git a/examples/clients/react/react-threejs/src/gameComponents/Player.tsx b/examples/clients/react/react-threejs/src/gameComponents/Player.tsx index e954c2bf..f990fde3 100644 --- a/examples/clients/react/react-threejs/src/gameComponents/Player.tsx +++ b/examples/clients/react/react-threejs/src/gameComponents/Player.tsx @@ -3,10 +3,11 @@ import { useDojo } from "@/dojo/useDojo"; import { useComponentValue } from "@dojoengine/react"; import { Cone } from "@react-three/drei"; import { getEntityIdFromKeys } from "@dojoengine/utils"; -import { Direction } from "@/utils"; + import { useEffect, useMemo, useRef, useState } from "react"; import { useFrame } from "@react-three/fiber"; import { MAP_SCALE } from "@/config"; +import { Direction } from "@/dojo/typescript/models.gen"; export const Player = (props: any) => { const { @@ -17,9 +18,7 @@ export const Player = (props: any) => { }, } = useDojo(); - const [hoveredTile, setHoveredTile] = useState( - undefined - ); + const [hoveredTile, setHoveredTile] = useState({ type: "None" }); const [startPosition, setStartPosition] = useState< THREE.Vector3 | undefined >(); @@ -49,9 +48,14 @@ export const Player = (props: any) => { [MAP_SCALE] ); - const blueCellsAroundPlayer = [ + interface BlueCell { + direction: Direction; + position: THREE.Vector3; + } + + const blueCellsAroundPlayer: BlueCell[] = [ { - direction: Direction.Up, + direction: { type: "Up" }, position: new THREE.Vector3( (vec.y - 1) * MAP_SCALE, -MAP_SCALE + 0.1, @@ -59,7 +63,7 @@ export const Player = (props: any) => { ), }, { - direction: Direction.Down, + direction: { type: "Down" }, position: new THREE.Vector3( (vec.y + 1) * MAP_SCALE, -MAP_SCALE + 0.1, @@ -67,7 +71,7 @@ export const Player = (props: any) => { ), }, { - direction: Direction.Right, + direction: { type: "Right" }, position: new THREE.Vector3( vec.y * MAP_SCALE, -MAP_SCALE + 0.1, @@ -75,7 +79,7 @@ export const Player = (props: any) => { ), }, { - direction: Direction.Left, + direction: { type: "Left" }, position: new THREE.Vector3( vec.y * MAP_SCALE, -MAP_SCALE + 0.1, @@ -130,7 +134,7 @@ export const Player = (props: any) => { lerpProgress.current = 0; // Reset Hovered Tile - setHoveredTile(undefined); + setHoveredTile({ type: "None" }); }, [coneRef, player, vec.x, vec.y]); useEffect(() => { @@ -178,7 +182,7 @@ export const Player = (props: any) => { // Stop propagation to avoid selecting other cells // onPointerLeave does not stop at the first cell encountered by default e.stopPropagation(); - setHoveredTile(undefined); + setHoveredTile({ type: "None" }); }} > ); diff --git a/examples/clients/react/react-threejs/src/main.tsx b/examples/clients/react/react-threejs/src/main.tsx index 5af1fa50..5ae7a19c 100644 --- a/examples/clients/react/react-threejs/src/main.tsx +++ b/examples/clients/react/react-threejs/src/main.tsx @@ -2,9 +2,9 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App.tsx"; import "./index.css"; -import { setup } from "./dojo/generated/setup.ts"; import { DojoProvider } from "./dojo/DojoContext.tsx"; import { dojoConfig } from "../dojoConfig.ts"; +import { setup } from "./dojo/setup.ts"; async function init() { const rootElement = document.getElementById("root"); diff --git a/examples/clients/vanilla/phaser/src/dojo/DojoContext.tsx b/examples/clients/vanilla/phaser/src/dojo/DojoContext.tsx new file mode 100644 index 00000000..ee5ecd6c --- /dev/null +++ b/examples/clients/vanilla/phaser/src/dojo/DojoContext.tsx @@ -0,0 +1,83 @@ +import { BurnerAccount, useBurnerManager } from "@dojoengine/create-burner"; +import { ReactNode, createContext, useContext, useMemo } from "react"; +import { Account } from "starknet"; +import { SetupResult } from "./setup"; + +interface DojoContextType extends SetupResult { + masterAccount: Account; + account: BurnerAccount; +} + +export const DojoContext = createContext(null); + +export const DojoProvider = ({ + children, + value, +}: { + children: ReactNode; + value: SetupResult; +}) => { + const currentValue = useContext(DojoContext); + if (currentValue) throw new Error("DojoProvider can only be used once"); + + const { + config: { masterAddress, masterPrivateKey }, + burnerManager, + dojoProvider, + } = value; + + const masterAccount = useMemo( + () => + new Account( + dojoProvider.provider, + masterAddress, + masterPrivateKey, + "1" + ), + [masterAddress, masterPrivateKey, dojoProvider.provider] + ); + + const { + create, + list, + get, + select, + deselect, + remove, + clear, + account, + isDeploying, + count, + copyToClipboard, + applyFromClipboard, + checkIsDeployed, + } = useBurnerManager({ + burnerManager, + }); + + return ( + + {children} + + ); +}; diff --git a/examples/clients/vanilla/phaser/src/dojo/createClientComponent.ts b/examples/clients/vanilla/phaser/src/dojo/createClientComponents.ts similarity index 87% rename from examples/clients/vanilla/phaser/src/dojo/createClientComponent.ts rename to examples/clients/vanilla/phaser/src/dojo/createClientComponents.ts index 5555bcf8..7b5bb860 100644 --- a/examples/clients/vanilla/phaser/src/dojo/createClientComponent.ts +++ b/examples/clients/vanilla/phaser/src/dojo/createClientComponents.ts @@ -1,5 +1,5 @@ import { overridableComponent } from "@dojoengine/recs"; -import { ContractComponents } from "./defineContractComponents"; +import { ContractComponents } from "./typescript/models.gen"; export type ClientComponents = ReturnType; diff --git a/examples/clients/vanilla/phaser/src/dojo/createSystemCalls.ts b/examples/clients/vanilla/phaser/src/dojo/createSystemCalls.ts new file mode 100644 index 00000000..38475f37 --- /dev/null +++ b/examples/clients/vanilla/phaser/src/dojo/createSystemCalls.ts @@ -0,0 +1,109 @@ +import { Account, AccountInterface } from "starknet"; +import { + Entity, + Has, + HasValue, + World, + defineSystem, + getComponentValue, +} from "@dojoengine/recs"; +import { uuid } from "@latticexyz/utils"; +import { ClientComponents } from "./createClientComponents"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; +import type { IWorld } from "./typescript/contracts.gen"; +import { Direction } from "./typescript/models.gen"; + +export type SystemCalls = ReturnType; + +export function createSystemCalls( + { client }: { client: IWorld }, + { Position, Moves }: ClientComponents, + world: World +) { + const spawn = async (account: Account) => { + const entityId = getEntityIdFromKeys([ + BigInt(account.address), + ]) as Entity; + + const movesId = uuid(); + Moves.addOverride(movesId, { + entity: entityId, + value: { + player: BigInt(entityId), + remaining: + (getComponentValue(Moves, entityId)?.remaining || 0) + 100, + }, + }); + + const positionId = uuid(); + Position.addOverride(positionId, { + entity: entityId, + value: { + player: BigInt(entityId), + vec: { + x: 10 + (getComponentValue(Position, entityId)?.vec.x || 0), + y: 10 + (getComponentValue(Position, entityId)?.vec.y || 0), + }, + }, + }); + + try { + await client.actions.spawn({ + account, + }); + + // Wait for the indexer to update the entity + // By doing this we keep the optimistic UI in sync with the actual state + await new Promise((resolve) => { + defineSystem( + world, + [ + Has(Moves), + HasValue(Moves, { player: BigInt(account.address) }), + ], + () => { + resolve(); + } + ); + }); + } catch (e) { + console.log(e); + Position.removeOverride(positionId); + Moves.removeOverride(movesId); + } finally { + Position.removeOverride(positionId); + Moves.removeOverride(movesId); + } + }; + + const move = async (account: Account, direction: Direction) => { + try { + await client.actions.move({ + account, + direction, + }); + + // Wait for the indexer to update the entity + // By doing this we keep the optimistic UI in sync with the actual state + await new Promise((resolve) => { + defineSystem( + world, + [ + Has(Moves), + HasValue(Moves, { player: BigInt(account.address) }), + ], + () => { + resolve(); + } + ); + }); + } catch (e) { + console.log(e); + } + }; + + return { + spawn, + move, + }; +} diff --git a/examples/clients/vanilla/phaser/src/dojo/defineContractComponents.ts b/examples/clients/vanilla/phaser/src/dojo/defineContractComponents.ts deleted file mode 100644 index fd566b51..00000000 --- a/examples/clients/vanilla/phaser/src/dojo/defineContractComponents.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ - -import { defineComponent, Type as RecsType, World } from "@dojoengine/recs"; - -export type ContractComponents = Awaited< - ReturnType ->; - -export function defineContractComponents(world: World) { - return { - DirectionsAvailable: (() => { - return defineComponent( - world, - { player: RecsType.BigInt, directions: RecsType.StringArray }, - { - metadata: { - name: "dojo_starter-DirectionsAvailable", - types: ["contractaddress"], - customTypes: [], - }, - } - ); - })(), - Moved: (() => { - return defineComponent( - world, - { player: RecsType.BigInt, direction: RecsType.Number }, - { - metadata: { - name: "dojo_starter-Moved", - types: ["contractaddress", "enum"], - customTypes: ["Direction"], - }, - } - ); - })(), - Moves: (() => { - return defineComponent( - world, - { - player: RecsType.BigInt, - remaining: RecsType.Number, - last_direction: RecsType.Number, - can_move: RecsType.Boolean, - }, - { - metadata: { - name: "dojo_starter-Moves", - types: ["contractaddress", "u8", "enum", "bool"], - customTypes: ["Direction"], - }, - } - ); - })(), - Position: (() => { - return defineComponent( - world, - { - player: RecsType.BigInt, - vec: { x: RecsType.Number, y: RecsType.Number }, - }, - { - metadata: { - name: "dojo_starter-Position", - types: ["contractaddress", "u32", "u32"], - customTypes: ["Vec2"], - }, - } - ); - })(), - }; -} diff --git a/examples/clients/vanilla/phaser/src/dojo/defineContractSystems.ts b/examples/clients/vanilla/phaser/src/dojo/defineContractSystems.ts deleted file mode 100644 index 41066cf9..00000000 --- a/examples/clients/vanilla/phaser/src/dojo/defineContractSystems.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Account, AccountInterface } from "starknet"; -import { DojoProvider } from "@dojoengine/core"; -import { Config } from "../../dojoConfig.ts"; -import { Direction } from "./utils.ts"; -const NAMESPACE = "dojo_starter"; - -export interface MoveProps { - account: Account | AccountInterface; - direction: Direction; -} - -const handleError = (action: string, error: unknown) => { - console.error(`Error executing ${action}:`, error); - throw error; -}; - -export type IWorld = Awaited>; - -export async function setupWorld(provider: DojoProvider, _config: Config) { - const actions = () => ({ - spawn: async ({ account }: { account: AccountInterface }) => { - try { - return await provider.execute( - account, - { - contractName: "actions", - entrypoint: "spawn", - calldata: [], - }, - NAMESPACE - ); - } catch (error) { - handleError("spawn", error); - } - }, - - move: async ({ account, direction }: MoveProps) => { - try { - return await provider.execute( - account, - { - contractName: "actions", - entrypoint: "move", - calldata: [direction], - }, - NAMESPACE - ); - } catch (error) { - handleError("move", error); - } - }, - }); - - return { actions: actions() }; -} diff --git a/examples/clients/vanilla/phaser/src/dojo/models.ts b/examples/clients/vanilla/phaser/src/dojo/models.ts deleted file mode 100644 index 3ca498dc..00000000 --- a/examples/clients/vanilla/phaser/src/dojo/models.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ContractComponents } from "./defineContractComponents"; - -export type ClientModels = ReturnType; - -export function models({ - contractModels, -}: { - contractModels: ContractComponents; -}) { - return { - models: { - ...contractModels, - }, - }; -} diff --git a/examples/clients/vanilla/phaser/src/dojo/setup.ts b/examples/clients/vanilla/phaser/src/dojo/setup.ts index 217617b9..f1ba6ded 100644 --- a/examples/clients/vanilla/phaser/src/dojo/setup.ts +++ b/examples/clients/vanilla/phaser/src/dojo/setup.ts @@ -1,99 +1,65 @@ -import { getSyncEntities } from "@dojoengine/state"; +import { DojoConfig, DojoProvider } from "@dojoengine/core"; import * as torii from "@dojoengine/torii-client"; - -import { models } from "./models.ts"; -import { systems } from "./systems.ts"; -import { defineContractComponents } from "./defineContractComponents.ts"; -import { world } from "./world.ts"; -import { Config } from "../../dojoConfig.ts"; -import { setupWorld } from "./defineContractSystems.ts"; - -import { DojoProvider } from "@dojoengine/core"; +import { createClientComponents } from "./createClientComponents"; +import { createSystemCalls } from "./createSystemCalls"; +import { defineContractComponents } from "./typescript/models.gen"; +import { world } from "./world"; +import { setupWorld } from "./typescript/contracts.gen"; +import { Account, ArraySignatureType } from "starknet"; import { BurnerManager } from "@dojoengine/create-burner"; -import { Account, RpcProvider } from "starknet"; -import { - ClientComponents, - createClientComponents, -} from "./createClientComponent.ts"; +import { getSyncEvents, getSyncEntities } from "@dojoengine/state"; export type SetupResult = Awaited>; -export type IDojo = Awaited>; -export async function setup({ ...config }: Config) { +export async function setup({ ...config }: DojoConfig) { // torii client - let toriiClient = null; - try { - toriiClient = await torii.createClient({ - rpcUrl: config.rpcUrl, - toriiUrl: config.toriiUrl, - relayUrl: "", - worldAddress: config.manifest.world.address || "", - }); - } catch (e) { - console.error("Failed to create torii client:", e); - throw e; - } + const toriiClient = await torii.createClient({ + rpcUrl: config.rpcUrl, + toriiUrl: config.toriiUrl, + relayUrl: "", + worldAddress: config.manifest.world.address || "", + }); // create contract components - let contractModels = null; - try { - contractModels = createClientComponents({ - contractComponents: defineContractComponents(world), - }); - } catch (e) { - console.error("Failed to create contract components:", e); - throw e; - } + const contractComponents = defineContractComponents(world); // create client components - const { models: clientModels } = models({ contractModels }); + const clientComponents = createClientComponents({ contractComponents }); - // fetch all existing entities from torii - let sync = null; - try { - sync = await getSyncEntities( - toriiClient, - contractModels as any, - [], - 1000 - ); - } catch (e) { - console.error("Failed to fetch sync:", e); - throw e; - } + // create dojo provider + const dojoProvider = new DojoProvider(config.manifest, config.rpcUrl); - let client = null; - try { - client = await setupWorld( - new DojoProvider(config.manifest, config.rpcUrl), - config - ); - } catch (e) { - console.error("Failed to create client:", e); - throw e; - } + // Sync all events + const eventSync = getSyncEvents( + toriiClient, + contractComponents as any, + undefined, + [] + ); - const rpcProvider = new RpcProvider({ - nodeUrl: config.rpcUrl, - }); + // Sync all entities + const sync = await getSyncEntities( + toriiClient, + contractComponents as any, + [] + ); - let burnerManager = null; - try { - burnerManager = new BurnerManager({ - masterAccount: new Account( - rpcProvider, - config.masterAddress, - config.masterPrivateKey - ), - feeTokenAddress: config.feeTokenAddress, - accountClassHash: config.accountClassHash, + // setup world + const client = await setupWorld(dojoProvider); - rpcProvider, - }); - } catch (e) { - console.log("Failed to create burner manager:", e); - throw e; - } + // create burner manager + const burnerManager = new BurnerManager({ + masterAccount: new Account( + { + nodeUrl: config.rpcUrl, + }, + config.masterAddress, + config.masterPrivateKey + ), + accountClassHash: config.accountClassHash, + rpcProvider: dojoProvider.provider, + feeTokenAddress: config.feeTokenAddress, + }); try { await burnerManager.init(); @@ -103,26 +69,20 @@ export async function setup({ ...config }: Config) { } catch (e) { console.error(e); } - const actions = systems({ - client, - clientModels: clientModels as ClientComponents, - contractComponents: contractModels, - }); - const account = burnerManager.getActiveAccount(); - if (null === account || undefined === account) { - throw new Error("failed to get active account"); - } return { client, - clientModels, - contractComponents: clientModels, - systemCalls: actions.actions, + clientComponents, + contractComponents, + systemCalls: createSystemCalls({ client }, clientComponents, world), + publish: (typedData: string, signature: ArraySignatureType) => { + toriiClient.publishMessage(typedData, signature); + }, config, - world, - burnerManager, - rpcProvider, + dojoProvider, + account: burnerManager.account as Account, + toriiClient, + eventSync, sync, - account, }; } diff --git a/examples/clients/vanilla/phaser/src/dojo/systems.ts b/examples/clients/vanilla/phaser/src/dojo/systems.ts deleted file mode 100644 index 973dce19..00000000 --- a/examples/clients/vanilla/phaser/src/dojo/systems.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { AccountInterface } from "starknet"; -import { Entity, getComponentValue } from "@dojoengine/recs"; -import { uuid } from "@latticexyz/utils"; -import { ClientComponents } from "./createClientComponent"; -import { Direction, updatePositionWithDirection } from "./utils"; -import { - getEntityIdFromKeys, - getEvents, - setComponentsFromEvents, -} from "@dojoengine/utils"; -import type { IWorld } from "./defineContractSystems"; -import { ContractComponents } from "./defineContractComponents"; - -export type SystemCalls = ReturnType; - -export function systems({ - client, - clientModels: { Position, Moves }, - contractComponents, -}: { - client: IWorld; - clientModels: ClientComponents; - contractComponents: ContractComponents; -}) { - function actions() { - const spawn = async (account: AccountInterface) => { - const entityId = getEntityIdFromKeys([ - BigInt(account.address), - ]) as Entity; - - const positionId = uuid(); - Position.addOverride(positionId, { - entity: entityId, - value: { player: BigInt(entityId), vec: { x: 10, y: 10 } }, - }); - - const movesId = uuid(); - Moves.addOverride(movesId, { - entity: entityId, - value: { - player: BigInt(entityId), - remaining: 100, - last_direction: 0, - }, - }); - - try { - const { transaction_hash } = (await client.actions.spawn({ - account, - })) as { transaction_hash: string }; - setComponentsFromEvents( - contractComponents, - getEvents( - await account.waitForTransaction(transaction_hash, { - retryInterval: 100, - }) - ) - ); - } catch (e) { - console.error(e); - Position.removeOverride(positionId); - Moves.removeOverride(movesId); - } - }; - - const move = async ( - account: AccountInterface, - direction: Direction - ) => { - const entityId = getEntityIdFromKeys([ - BigInt(account.address), - ]) as Entity; - - const positionId = uuid(); - Position.addOverride(positionId, { - entity: entityId, - value: { - player: BigInt(entityId), - vec: updatePositionWithDirection( - direction, - getComponentValue(Position, entityId) as any - ).vec, - }, - }); - - const movesId = uuid(); - Moves.addOverride(movesId, { - entity: entityId, - value: { - player: BigInt(entityId), - remaining: - (getComponentValue(Moves, entityId)?.remaining || 0) - - 1, - }, - }); - - try { - const { transaction_hash } = (await client.actions.move({ - account, - direction, - })) as { transaction_hash: string }; - - setComponentsFromEvents( - contractComponents, - getEvents( - await account.waitForTransaction(transaction_hash, { - retryInterval: 100, - }) - ) - ); - } catch (e) { - console.error(e); - Position.removeOverride(positionId); - Moves.removeOverride(movesId); - } - }; - return { spawn, move }; - } - - return { - actions: actions(), - }; -} diff --git a/examples/clients/vanilla/phaser/src/dojo/typescript/contracts.gen.ts b/examples/clients/vanilla/phaser/src/dojo/typescript/contracts.gen.ts new file mode 100644 index 00000000..0f7fac11 --- /dev/null +++ b/examples/clients/vanilla/phaser/src/dojo/typescript/contracts.gen.ts @@ -0,0 +1,86 @@ +// Generated by dojo-bindgen on Thu, 22 Aug 2024 20:04:33 +0000. Do not modify this file manually. +// Import the necessary types from the recs SDK +// generate again with `sozo build --typescript` +import { Account, byteArray } from "starknet"; +import { DojoProvider } from "@dojoengine/core"; +import * as models from "./models.gen"; + +export type IWorld = Awaited>; + +export async function setupWorld(provider: DojoProvider) { + // System definitions for `dojo_starter-actions` contract + function actions() { + const contract_name = "actions"; + + // Call the `spawn` system with the specified Account and calldata + const spawn = async (props: { account: Account }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "spawn", + calldata: [], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + // Call the `move` system with the specified Account and calldata + const move = async (props: { + account: Account; + direction: models.Direction; + }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "move", + calldata: [ + ["None", "Left", "Right", "Up", "Down"].indexOf( + props.direction.type + ), + ], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + // Call the `world` system with the specified Account and calldata + const world = async (props: { account: Account }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "world", + calldata: [], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + return { + spawn, + move, + world, + }; + } + + return { + actions: actions(), + }; +} diff --git a/examples/clients/vanilla/phaser/src/dojo/typescript/models.gen.ts b/examples/clients/vanilla/phaser/src/dojo/typescript/models.gen.ts new file mode 100644 index 00000000..051689eb --- /dev/null +++ b/examples/clients/vanilla/phaser/src/dojo/typescript/models.gen.ts @@ -0,0 +1,193 @@ +// Generated by dojo-bindgen on Thu, 22 Aug 2024 20:04:33 +0000. Do not modify this file manually. +// Import the necessary types from the recs SDK +// generate again with `sozo build --typescript` +import { defineComponent, Type as RecsType, World } from "@dojoengine/recs"; + +export type ContractComponents = Awaited< + ReturnType +>; + +// Type definition for `dojo_starter::models::Direction` enum +export type Direction = + | { type: "None" } + | { type: "Left" } + | { type: "Right" } + | { type: "Up" } + | { type: "Down" }; + +export const DirectionDefinition = { + type: RecsType.String, + value: RecsType.String, +}; + +// Type definition for `dojo::model::layout::Layout` enum +export type Layout = + | { type: "Fixed"; value: RecsType.NumberArray } + | { type: "Struct"; value: RecsType.StringArray } + | { type: "Tuple"; value: RecsType.StringArray } + | { type: "Array"; value: RecsType.StringArray } + | { type: "ByteArray" } + | { type: "Enum"; value: RecsType.StringArray }; + +export const LayoutDefinition = { + type: RecsType.String, + value: RecsType.String, +}; + +// Type definition for `core::byte_array::ByteArray` struct +export interface ByteArray { + data: String[]; + pending_word: BigInt; + pending_word_len: Number; +} +export const ByteArrayDefinition = { + data: RecsType.StringArray, + pending_word: RecsType.BigInt, + pending_word_len: RecsType.Number, +}; + +// Type definition for `dojo::model::layout::FieldLayout` struct +export interface FieldLayout { + selector: BigInt; + layout: Layout; +} +export const FieldLayoutDefinition = { + selector: RecsType.BigInt, + layout: LayoutDefinition, +}; + +// Type definition for `dojo_starter::models::Moves` struct +export interface Moves { + player: BigInt; + remaining: Number; + last_direction: Direction; + can_move: Boolean; +} +export const MovesDefinition = { + player: RecsType.BigInt, + remaining: RecsType.Number, + last_direction: DirectionDefinition, + can_move: RecsType.Boolean, +}; + +// Type definition for `dojo_starter::models::DirectionsAvailable` struct +export interface DirectionsAvailable { + player: BigInt; + directions: String[]; +} +export const DirectionsAvailableDefinition = { + player: RecsType.BigInt, + directions: RecsType.StringArray, +}; + +// Type definition for `dojo_starter::systems::actions::actions::Moved` struct +export interface Moved { + player: BigInt; + direction: Direction; +} +export const MovedDefinition = { + player: RecsType.BigInt, + direction: DirectionDefinition, +}; + +// Type definition for `dojo_starter::models::Vec2` struct +export interface Vec2 { + x: Number; + y: Number; +} +export const Vec2Definition = { + x: RecsType.Number, + y: RecsType.Number, +}; + +// Type definition for `dojo_starter::models::Position` struct +export interface Position { + player: BigInt; + vec: Vec2; +} +export const PositionDefinition = { + player: RecsType.BigInt, + vec: Vec2Definition, +}; + +export function defineContractComponents(world: World) { + return { + // Model definition for `dojo_starter::models::Moves` model + Moves: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + remaining: RecsType.Number, + last_direction: RecsType.String, + can_move: RecsType.Boolean, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Moves", + types: ["ContractAddress", "u8", "Direction", "bool"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::models::DirectionsAvailable` model + DirectionsAvailable: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + directions: RecsType.StringArray, + }, + { + metadata: { + namespace: "dojo_starter", + name: "DirectionsAvailable", + types: ["ContractAddress", "array"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::systems::actions::actions::Moved` model + Moved: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + direction: RecsType.String, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Moved", + types: ["ContractAddress", "Direction"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::models::Position` model + Position: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + vec: Vec2Definition, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Position", + types: ["ContractAddress"], + customTypes: ["Vec2"], + }, + } + ); + })(), + }; +} diff --git a/examples/clients/vanilla/phaser/src/dojo/useDojo.tsx b/examples/clients/vanilla/phaser/src/dojo/useDojo.tsx new file mode 100644 index 00000000..9a77177e --- /dev/null +++ b/examples/clients/vanilla/phaser/src/dojo/useDojo.tsx @@ -0,0 +1,15 @@ +import { useContext } from "react"; +import { DojoContext } from "./DojoContext"; + +export const useDojo = () => { + const context = useContext(DojoContext); + if (!context) + throw new Error( + "The `useDojo` hook must be used within a `DojoProvider`" + ); + + return { + setup: context, + account: context.account, + }; +}; diff --git a/examples/clients/vanilla/phaser/src/dojo/utils.ts b/examples/clients/vanilla/phaser/src/dojo/utils.ts deleted file mode 100644 index 1ec8b000..00000000 --- a/examples/clients/vanilla/phaser/src/dojo/utils.ts +++ /dev/null @@ -1,29 +0,0 @@ -export enum Direction { - Left = 1, - Right = 2, - Up = 3, - Down = 4, -} - -export function updatePositionWithDirection( - direction: Direction, - value: { vec: { x: number; y: number } } -) { - switch (direction) { - case Direction.Left: - value.vec.x--; - break; - case Direction.Right: - value.vec.x++; - break; - case Direction.Up: - value.vec.y--; - break; - case Direction.Down: - value.vec.y++; - break; - default: - throw new Error("Invalid direction provided"); - } - return value; -} diff --git a/examples/clients/vanilla/phaser/src/scenes/scene-main.ts b/examples/clients/vanilla/phaser/src/scenes/scene-main.ts index 51d8d6fa..69abf35e 100644 --- a/examples/clients/vanilla/phaser/src/scenes/scene-main.ts +++ b/examples/clients/vanilla/phaser/src/scenes/scene-main.ts @@ -1,9 +1,9 @@ import { Scene } from "phaser"; import { Chunk } from "../entities"; -import { IDojo } from "../dojo/setup"; -import { Direction } from "../dojo/utils"; + +import { SetupResult } from "../dojo/setup"; export default class SceneMain extends Scene { - dojo: IDojo; + dojo: SetupResult; chunkSize: number; tileSize: number; cameraSpeed: number; @@ -14,7 +14,7 @@ export default class SceneMain extends Scene { keyA: Phaser.Input.Keyboard.Key | null; keyD: Phaser.Input.Keyboard.Key | null; - constructor(dojo: IDojo) { + constructor(dojo: SetupResult) { super({ key: "MainScene" }); this.dojo = dojo; @@ -133,25 +133,25 @@ export default class SceneMain extends Scene { if (null !== this.keyW && this.keyW.isDown) { this.followPoint.y -= this.cameraSpeed; this.cameras.main.centerOn(this.followPoint.x, this.followPoint.y); - this.dojo.systemCalls.move(this.dojo.account, Direction.Up); + this.dojo.systemCalls.move(this.dojo.account, { type: "Up" }); return; } if (null !== this.keyS && this.keyS.isDown) { this.followPoint.y += this.cameraSpeed; this.cameras.main.centerOn(this.followPoint.x, this.followPoint.y); - this.dojo.systemCalls.move(this.dojo.account, Direction.Down); + this.dojo.systemCalls.move(this.dojo.account, { type: "Down" }); return; } if (null !== this.keyA && this.keyA.isDown) { this.followPoint.x -= this.cameraSpeed; this.cameras.main.centerOn(this.followPoint.x, this.followPoint.y); - this.dojo.systemCalls.move(this.dojo.account, Direction.Left); + this.dojo.systemCalls.move(this.dojo.account, { type: "Left" }); return; } if (null !== this.keyD && this.keyD.isDown) { this.followPoint.x += this.cameraSpeed; this.cameras.main.centerOn(this.followPoint.x, this.followPoint.y); - this.dojo.systemCalls.move(this.dojo.account, Direction.Right); + this.dojo.systemCalls.move(this.dojo.account, { type: "Right" }); return; } diff --git a/examples/clients/vue/vue-app/src/App.vue b/examples/clients/vue/vue-app/src/App.vue index 27ae8529..09455901 100644 --- a/examples/clients/vue/vue-app/src/App.vue +++ b/examples/clients/vue/vue-app/src/App.vue @@ -1,15 +1,15 @@ - - - - diff --git a/examples/clients/vue/vue-app/src/dojo/DojoContext.tsx b/examples/clients/vue/vue-app/src/dojo/DojoContext.tsx new file mode 100644 index 00000000..ee5ecd6c --- /dev/null +++ b/examples/clients/vue/vue-app/src/dojo/DojoContext.tsx @@ -0,0 +1,83 @@ +import { BurnerAccount, useBurnerManager } from "@dojoengine/create-burner"; +import { ReactNode, createContext, useContext, useMemo } from "react"; +import { Account } from "starknet"; +import { SetupResult } from "./setup"; + +interface DojoContextType extends SetupResult { + masterAccount: Account; + account: BurnerAccount; +} + +export const DojoContext = createContext(null); + +export const DojoProvider = ({ + children, + value, +}: { + children: ReactNode; + value: SetupResult; +}) => { + const currentValue = useContext(DojoContext); + if (currentValue) throw new Error("DojoProvider can only be used once"); + + const { + config: { masterAddress, masterPrivateKey }, + burnerManager, + dojoProvider, + } = value; + + const masterAccount = useMemo( + () => + new Account( + dojoProvider.provider, + masterAddress, + masterPrivateKey, + "1" + ), + [masterAddress, masterPrivateKey, dojoProvider.provider] + ); + + const { + create, + list, + get, + select, + deselect, + remove, + clear, + account, + isDeploying, + count, + copyToClipboard, + applyFromClipboard, + checkIsDeployed, + } = useBurnerManager({ + burnerManager, + }); + + return ( + + {children} + + ); +}; diff --git a/examples/clients/vue/vue-app/src/dojo/createClientComponents.ts b/examples/clients/vue/vue-app/src/dojo/createClientComponents.ts index 550ad13b..7b5bb860 100644 --- a/examples/clients/vue/vue-app/src/dojo/createClientComponents.ts +++ b/examples/clients/vue/vue-app/src/dojo/createClientComponents.ts @@ -1,5 +1,5 @@ import { overridableComponent } from "@dojoengine/recs"; -import { ContractComponents } from "./generated/contractComponents"; +import { ContractComponents } from "./typescript/models.gen"; export type ClientComponents = ReturnType; diff --git a/examples/clients/vue/vue-app/src/dojo/createSystemCalls.ts b/examples/clients/vue/vue-app/src/dojo/createSystemCalls.ts index 6c35e7e8..38475f37 100644 --- a/examples/clients/vue/vue-app/src/dojo/createSystemCalls.ts +++ b/examples/clients/vue/vue-app/src/dojo/createSystemCalls.ts @@ -1,57 +1,71 @@ -import { Account } from "starknet"; -import { Entity, getComponentValue } from "@dojoengine/recs"; +import { Account, AccountInterface } from "starknet"; +import { + Entity, + Has, + HasValue, + World, + defineSystem, + getComponentValue, +} from "@dojoengine/recs"; import { uuid } from "@latticexyz/utils"; import { ClientComponents } from "./createClientComponents"; -import { Direction, updatePositionWithDirection } from "../utils"; -import { - getEntityIdFromKeys, - getEvents, - setComponentsFromEvents, -} from "@dojoengine/utils"; -import { ContractComponents } from "./generated/contractComponents"; -import type { IWorld } from "./generated/generated"; +import { getEntityIdFromKeys } from "@dojoengine/utils"; +import type { IWorld } from "./typescript/contracts.gen"; +import { Direction } from "./typescript/models.gen"; export type SystemCalls = ReturnType; export function createSystemCalls( { client }: { client: IWorld }, - contractComponents: ContractComponents, - { Position, Moves }: ClientComponents + { Position, Moves }: ClientComponents, + world: World ) { const spawn = async (account: Account) => { const entityId = getEntityIdFromKeys([ BigInt(account.address), ]) as Entity; - const positionId = uuid(); - Position.addOverride(positionId, { + const movesId = uuid(); + Moves.addOverride(movesId, { entity: entityId, - value: { player: BigInt(entityId), vec: { x: 10, y: 10 } }, + value: { + player: BigInt(entityId), + remaining: + (getComponentValue(Moves, entityId)?.remaining || 0) + 100, + }, }); - const movesId = uuid(); - Moves.addOverride(movesId, { + const positionId = uuid(); + Position.addOverride(positionId, { entity: entityId, value: { player: BigInt(entityId), - remaining: 100, - last_direction: 0, + vec: { + x: 10 + (getComponentValue(Position, entityId)?.vec.x || 0), + y: 10 + (getComponentValue(Position, entityId)?.vec.y || 0), + }, }, }); try { - const { transaction_hash } = await client.actions.spawn({ + await client.actions.spawn({ account, }); - setComponentsFromEvents( - contractComponents, - getEvents( - await account.waitForTransaction(transaction_hash, { - retryInterval: 100, - }) - ) - ); + // Wait for the indexer to update the entity + // By doing this we keep the optimistic UI in sync with the actual state + await new Promise((resolve) => { + defineSystem( + world, + [ + Has(Moves), + HasValue(Moves, { player: BigInt(account.address) }), + ], + () => { + resolve(); + } + ); + }); } catch (e) { console.log(e); Position.removeOverride(positionId); @@ -63,53 +77,28 @@ export function createSystemCalls( }; const move = async (account: Account, direction: Direction) => { - const entityId = getEntityIdFromKeys([ - BigInt(account.address), - ]) as Entity; - - const positionId = uuid(); - Position.addOverride(positionId, { - entity: entityId, - value: { - player: BigInt(entityId), - vec: updatePositionWithDirection( - direction, - getComponentValue(Position, entityId) as any - ).vec, - }, - }); - - const movesId = uuid(); - Moves.addOverride(movesId, { - entity: entityId, - value: { - player: BigInt(entityId), - remaining: - (getComponentValue(Moves, entityId)?.remaining || 0) - 1, - }, - }); - try { - const { transaction_hash } = await client.actions.move({ + await client.actions.move({ account, direction, }); - setComponentsFromEvents( - contractComponents, - getEvents( - await account.waitForTransaction(transaction_hash, { - retryInterval: 100, - }) - ) - ); + // Wait for the indexer to update the entity + // By doing this we keep the optimistic UI in sync with the actual state + await new Promise((resolve) => { + defineSystem( + world, + [ + Has(Moves), + HasValue(Moves, { player: BigInt(account.address) }), + ], + () => { + resolve(); + } + ); + }); } catch (e) { console.log(e); - Position.removeOverride(positionId); - Moves.removeOverride(movesId); - } finally { - Position.removeOverride(positionId); - Moves.removeOverride(movesId); } }; diff --git a/examples/clients/vue/vue-app/src/dojo/generated/contractComponents.ts b/examples/clients/vue/vue-app/src/dojo/generated/contractComponents.ts deleted file mode 100644 index 596871c8..00000000 --- a/examples/clients/vue/vue-app/src/dojo/generated/contractComponents.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Autogenerated file. Do not edit manually. - * Generated using @dojoengine/core - * Command: npx @dojoengine/core - */ - -import { defineComponent, Type as RecsType, World } from "@dojoengine/recs"; - -export type ContractComponents = Awaited< - ReturnType ->; - -export function defineContractComponents(world: World) { - return { - DirectionsAvailable: (() => { - return defineComponent( - world, - { player: RecsType.BigInt, directions: RecsType.StringArray }, - { - metadata: { - name: "dojo_starter-DirectionsAvailable", - types: ["contractaddress"], - customTypes: ["Direction"], - }, - } - ); - })(), - Moves: (() => { - return defineComponent( - world, - { - player: RecsType.BigInt, - remaining: RecsType.Number, - last_direction: RecsType.Number, - can_move: RecsType.Boolean, - }, - { - metadata: { - name: "dojo_starter-Moves", - types: ["contractaddress", "u8", "enum", "bool"], - customTypes: ["Direction"], - }, - } - ); - })(), - Position: (() => { - return defineComponent( - world, - { - player: RecsType.BigInt, - vec: { x: RecsType.Number, y: RecsType.Number }, - }, - { - metadata: { - name: "dojo_starter-Position", - types: ["contractaddress", "u32", "u32"], - customTypes: ["Vec2"], - }, - } - ); - })(), - }; -} diff --git a/examples/clients/vue/vue-app/src/dojo/generated/generated.ts b/examples/clients/vue/vue-app/src/dojo/generated/generated.ts deleted file mode 100644 index b10d2654..00000000 --- a/examples/clients/vue/vue-app/src/dojo/generated/generated.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Account, AccountInterface } from "starknet"; -import { DojoProvider } from "@dojoengine/core"; -import { Direction } from "../../utils"; - -const NAMESPACE = "dojo_starter"; - -export interface IWorld { - actions: { - spawn: (props: { account: AccountInterface }) => Promise; - move: (props: MoveProps) => Promise; - }; -} - -export interface MoveProps { - account: Account | AccountInterface; - direction: Direction; -} - -const handleError = (action: string, error: unknown) => { - console.error(`Error executing ${action}:`, error); - throw error; -}; - -export const setupWorld = async (provider: DojoProvider): Promise => { - const actions = () => ({ - spawn: async ({ account }: { account: AccountInterface }) => { - try { - return await provider.execute( - account, - { - contractName: "actions", - entrypoint: "spawn", - calldata: [], - }, - NAMESPACE - ); - } catch (error) { - handleError("spawn", error); - } - }, - - move: async ({ account, direction }: MoveProps) => { - try { - return await provider.execute( - account, - { - contractName: "actions", - entrypoint: "move", - calldata: [direction], - }, - NAMESPACE - ); - } catch (error) { - handleError("move", error); - } - }, - }); - - return { actions: actions() }; -}; diff --git a/examples/clients/vue/vue-app/src/dojo/generated/setup.ts b/examples/clients/vue/vue-app/src/dojo/generated/setup.ts deleted file mode 100644 index 5edebeba..00000000 --- a/examples/clients/vue/vue-app/src/dojo/generated/setup.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { getSyncEntities } from "@dojoengine/state"; -import { - DojoConfig, - DojoProvider, - createModelTypedData, -} from "@dojoengine/core"; -import * as torii from "@dojoengine/torii-client"; -import { createClientComponents } from "../createClientComponents"; -import { createSystemCalls } from "../createSystemCalls"; -import { defineContractComponents } from "./contractComponents"; -import { world } from "./world"; -import { setupWorld } from "./generated"; -import { - ArraySignatureType, - TypedData, - WeierstrassSignatureType, -} from "starknet"; - -export type SetupResult = Awaited>; - -export async function setup({ ...config }: DojoConfig) { - // torii client - const toriiClient = await torii.createClient({ - rpcUrl: config.rpcUrl, - toriiUrl: config.toriiUrl, - relayUrl: "", - worldAddress: config.manifest.world.address || "", - }); - - // create contract components - const contractComponents = defineContractComponents(world); - - // create client components - const clientComponents = createClientComponents({ contractComponents }); - - // fetch all existing entities from torii - const sync = await getSyncEntities( - toriiClient, - contractComponents as any, - [] - ); - - // create dojo provider - const dojoProvider = new DojoProvider(config.manifest, config.rpcUrl); - - // setup world - const client = await setupWorld(dojoProvider); - - return { - client, - clientComponents, - contractComponents, - systemCalls: createSystemCalls( - { client }, - contractComponents, - clientComponents - ), - publish: (typedData: string, signature: ArraySignatureType) => { - toriiClient.publishMessage(typedData, signature); - }, - config, - dojoProvider, - sync, - }; -} diff --git a/examples/clients/vue/vue-app/src/dojo/generated/world.ts b/examples/clients/vue/vue-app/src/dojo/generated/world.ts deleted file mode 100644 index 960676e5..00000000 --- a/examples/clients/vue/vue-app/src/dojo/generated/world.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createWorld } from "@dojoengine/recs"; - -export const world = createWorld(); diff --git a/examples/clients/vue/vue-app/src/dojo/setup.ts b/examples/clients/vue/vue-app/src/dojo/setup.ts new file mode 100644 index 00000000..f1ba6ded --- /dev/null +++ b/examples/clients/vue/vue-app/src/dojo/setup.ts @@ -0,0 +1,88 @@ +import { DojoConfig, DojoProvider } from "@dojoengine/core"; +import * as torii from "@dojoengine/torii-client"; +import { createClientComponents } from "./createClientComponents"; +import { createSystemCalls } from "./createSystemCalls"; +import { defineContractComponents } from "./typescript/models.gen"; +import { world } from "./world"; +import { setupWorld } from "./typescript/contracts.gen"; +import { Account, ArraySignatureType } from "starknet"; +import { BurnerManager } from "@dojoengine/create-burner"; +import { getSyncEvents, getSyncEntities } from "@dojoengine/state"; + +export type SetupResult = Awaited>; + +export async function setup({ ...config }: DojoConfig) { + // torii client + const toriiClient = await torii.createClient({ + rpcUrl: config.rpcUrl, + toriiUrl: config.toriiUrl, + relayUrl: "", + worldAddress: config.manifest.world.address || "", + }); + + // create contract components + const contractComponents = defineContractComponents(world); + + // create client components + const clientComponents = createClientComponents({ contractComponents }); + + // create dojo provider + const dojoProvider = new DojoProvider(config.manifest, config.rpcUrl); + + // Sync all events + const eventSync = getSyncEvents( + toriiClient, + contractComponents as any, + undefined, + [] + ); + + // Sync all entities + const sync = await getSyncEntities( + toriiClient, + contractComponents as any, + [] + ); + + // setup world + const client = await setupWorld(dojoProvider); + + // create burner manager + const burnerManager = new BurnerManager({ + masterAccount: new Account( + { + nodeUrl: config.rpcUrl, + }, + config.masterAddress, + config.masterPrivateKey + ), + accountClassHash: config.accountClassHash, + rpcProvider: dojoProvider.provider, + feeTokenAddress: config.feeTokenAddress, + }); + + try { + await burnerManager.init(); + if (burnerManager.list().length === 0) { + await burnerManager.create(); + } + } catch (e) { + console.error(e); + } + + return { + client, + clientComponents, + contractComponents, + systemCalls: createSystemCalls({ client }, clientComponents, world), + publish: (typedData: string, signature: ArraySignatureType) => { + toriiClient.publishMessage(typedData, signature); + }, + config, + dojoProvider, + account: burnerManager.account as Account, + toriiClient, + eventSync, + sync, + }; +} diff --git a/examples/clients/vue/vue-app/src/dojo/typescript/contracts.gen.ts b/examples/clients/vue/vue-app/src/dojo/typescript/contracts.gen.ts new file mode 100644 index 00000000..0f7fac11 --- /dev/null +++ b/examples/clients/vue/vue-app/src/dojo/typescript/contracts.gen.ts @@ -0,0 +1,86 @@ +// Generated by dojo-bindgen on Thu, 22 Aug 2024 20:04:33 +0000. Do not modify this file manually. +// Import the necessary types from the recs SDK +// generate again with `sozo build --typescript` +import { Account, byteArray } from "starknet"; +import { DojoProvider } from "@dojoengine/core"; +import * as models from "./models.gen"; + +export type IWorld = Awaited>; + +export async function setupWorld(provider: DojoProvider) { + // System definitions for `dojo_starter-actions` contract + function actions() { + const contract_name = "actions"; + + // Call the `spawn` system with the specified Account and calldata + const spawn = async (props: { account: Account }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "spawn", + calldata: [], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + // Call the `move` system with the specified Account and calldata + const move = async (props: { + account: Account; + direction: models.Direction; + }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "move", + calldata: [ + ["None", "Left", "Right", "Up", "Down"].indexOf( + props.direction.type + ), + ], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + // Call the `world` system with the specified Account and calldata + const world = async (props: { account: Account }) => { + try { + return await provider.execute( + props.account, + { + contractName: contract_name, + entrypoint: "world", + calldata: [], + }, + "dojo_starter" + ); + } catch (error) { + console.error("Error executing spawn:", error); + throw error; + } + }; + + return { + spawn, + move, + world, + }; + } + + return { + actions: actions(), + }; +} diff --git a/examples/clients/vue/vue-app/src/dojo/typescript/models.gen.ts b/examples/clients/vue/vue-app/src/dojo/typescript/models.gen.ts new file mode 100644 index 00000000..051689eb --- /dev/null +++ b/examples/clients/vue/vue-app/src/dojo/typescript/models.gen.ts @@ -0,0 +1,193 @@ +// Generated by dojo-bindgen on Thu, 22 Aug 2024 20:04:33 +0000. Do not modify this file manually. +// Import the necessary types from the recs SDK +// generate again with `sozo build --typescript` +import { defineComponent, Type as RecsType, World } from "@dojoengine/recs"; + +export type ContractComponents = Awaited< + ReturnType +>; + +// Type definition for `dojo_starter::models::Direction` enum +export type Direction = + | { type: "None" } + | { type: "Left" } + | { type: "Right" } + | { type: "Up" } + | { type: "Down" }; + +export const DirectionDefinition = { + type: RecsType.String, + value: RecsType.String, +}; + +// Type definition for `dojo::model::layout::Layout` enum +export type Layout = + | { type: "Fixed"; value: RecsType.NumberArray } + | { type: "Struct"; value: RecsType.StringArray } + | { type: "Tuple"; value: RecsType.StringArray } + | { type: "Array"; value: RecsType.StringArray } + | { type: "ByteArray" } + | { type: "Enum"; value: RecsType.StringArray }; + +export const LayoutDefinition = { + type: RecsType.String, + value: RecsType.String, +}; + +// Type definition for `core::byte_array::ByteArray` struct +export interface ByteArray { + data: String[]; + pending_word: BigInt; + pending_word_len: Number; +} +export const ByteArrayDefinition = { + data: RecsType.StringArray, + pending_word: RecsType.BigInt, + pending_word_len: RecsType.Number, +}; + +// Type definition for `dojo::model::layout::FieldLayout` struct +export interface FieldLayout { + selector: BigInt; + layout: Layout; +} +export const FieldLayoutDefinition = { + selector: RecsType.BigInt, + layout: LayoutDefinition, +}; + +// Type definition for `dojo_starter::models::Moves` struct +export interface Moves { + player: BigInt; + remaining: Number; + last_direction: Direction; + can_move: Boolean; +} +export const MovesDefinition = { + player: RecsType.BigInt, + remaining: RecsType.Number, + last_direction: DirectionDefinition, + can_move: RecsType.Boolean, +}; + +// Type definition for `dojo_starter::models::DirectionsAvailable` struct +export interface DirectionsAvailable { + player: BigInt; + directions: String[]; +} +export const DirectionsAvailableDefinition = { + player: RecsType.BigInt, + directions: RecsType.StringArray, +}; + +// Type definition for `dojo_starter::systems::actions::actions::Moved` struct +export interface Moved { + player: BigInt; + direction: Direction; +} +export const MovedDefinition = { + player: RecsType.BigInt, + direction: DirectionDefinition, +}; + +// Type definition for `dojo_starter::models::Vec2` struct +export interface Vec2 { + x: Number; + y: Number; +} +export const Vec2Definition = { + x: RecsType.Number, + y: RecsType.Number, +}; + +// Type definition for `dojo_starter::models::Position` struct +export interface Position { + player: BigInt; + vec: Vec2; +} +export const PositionDefinition = { + player: RecsType.BigInt, + vec: Vec2Definition, +}; + +export function defineContractComponents(world: World) { + return { + // Model definition for `dojo_starter::models::Moves` model + Moves: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + remaining: RecsType.Number, + last_direction: RecsType.String, + can_move: RecsType.Boolean, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Moves", + types: ["ContractAddress", "u8", "Direction", "bool"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::models::DirectionsAvailable` model + DirectionsAvailable: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + directions: RecsType.StringArray, + }, + { + metadata: { + namespace: "dojo_starter", + name: "DirectionsAvailable", + types: ["ContractAddress", "array"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::systems::actions::actions::Moved` model + Moved: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + direction: RecsType.String, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Moved", + types: ["ContractAddress", "Direction"], + customTypes: [], + }, + } + ); + })(), + + // Model definition for `dojo_starter::models::Position` model + Position: (() => { + return defineComponent( + world, + { + player: RecsType.BigInt, + vec: Vec2Definition, + }, + { + metadata: { + namespace: "dojo_starter", + name: "Position", + types: ["ContractAddress"], + customTypes: ["Vec2"], + }, + } + ); + })(), + }; +} diff --git a/examples/clients/vue/vue-app/src/dojo/useDojo.tsx b/examples/clients/vue/vue-app/src/dojo/useDojo.tsx new file mode 100644 index 00000000..9a77177e --- /dev/null +++ b/examples/clients/vue/vue-app/src/dojo/useDojo.tsx @@ -0,0 +1,15 @@ +import { useContext } from "react"; +import { DojoContext } from "./DojoContext"; + +export const useDojo = () => { + const context = useContext(DojoContext); + if (!context) + throw new Error( + "The `useDojo` hook must be used within a `DojoProvider`" + ); + + return { + setup: context, + account: context.account, + }; +}; diff --git a/examples/clients/react/react-threejs/src/dojo/generated/world.ts b/examples/clients/vue/vue-app/src/dojo/world.ts similarity index 100% rename from examples/clients/react/react-threejs/src/dojo/generated/world.ts rename to examples/clients/vue/vue-app/src/dojo/world.ts From bef89bae46d7ea0b1089f8feb381077bfdd4b710 Mon Sep 17 00:00:00 2001 From: ponderingdemocritus Date: Mon, 2 Sep 2024 10:16:44 -0400 Subject: [PATCH 4/7] fix: lint --- examples/clients/vue/vue-app/src/App.vue | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/clients/vue/vue-app/src/App.vue b/examples/clients/vue/vue-app/src/App.vue index 09455901..8c595ca7 100644 --- a/examples/clients/vue/vue-app/src/App.vue +++ b/examples/clients/vue/vue-app/src/App.vue @@ -1,11 +1,7 @@