Skip to content

Commit 46b79fc

Browse files
feat: move to sdk
1 parent 2d8cb57 commit 46b79fc

File tree

8 files changed

+167
-123
lines changed

8 files changed

+167
-123
lines changed

examples/example-vite-react-sdk/src/App.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { useEffect } from "react";
22
import "./App.css";
3-
import { SDK } from "@dojoengine/sdk";
3+
import { SDK, createDojoStore } from "@dojoengine/sdk";
44
import { Schema } from "./bindings.ts";
5-
import { useGameState } from "./state.ts";
65

76
import { v4 as uuidv4 } from "uuid";
87

8+
export const useGameState = createDojoStore<Schema>();
9+
910
function App({ db }: { db: SDK<Schema> }) {
1011
const state = useGameState((state) => state);
1112
const entities = useGameState((state) => state.entities);
@@ -126,9 +127,11 @@ function App({ db }: { db: SDK<Schema> }) {
126127
Player:{" "}
127128
{entity.models.dojo_starter.Position?.player ?? "N/A"}
128129
<br />
129-
X: {entity.models.dojo_starter.Position?.vec.x ?? "N/A"}
130+
X:{" "}
131+
{entity.models.dojo_starter.Position?.vec?.x ?? "N/A"}
130132
<br />
131-
Y: {entity.models.dojo_starter.Position?.vec.y ?? "N/A"}
133+
Y:{" "}
134+
{entity.models.dojo_starter.Position?.vec?.y ?? "N/A"}
132135
</p>
133136
<h3>Moves</h3>
134137
<p>

examples/example-vite-react-sdk/src/state.ts

-118
This file was deleted.

packages/sdk/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"dependencies": {
3737
"@dojoengine/torii-client": "workspace:*",
3838
"axios": "^0.27.2",
39+
"immer": "^10.1.1",
3940
"lodash": "^4.17.21",
4041
"vite-plugin-wasm": "^3.3.0",
4142
"zustand": "^4.5.5"

packages/sdk/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { SchemaType, SDK, UnionOfModelData } from "./types";
88
import { Account, Signature, StarknetDomain, TypedData } from "starknet";
99

1010
export * from "./types";
11+
export * from "./state";
1112

1213
interface SDKConfig {
1314
client: torii.ClientConfig;

packages/sdk/src/state/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./zustand";

packages/sdk/src/state/zustand.ts

+151
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { create } from "zustand";
2+
import { immer } from "zustand/middleware/immer";
3+
import {
4+
Draft,
5+
Patch,
6+
WritableDraft,
7+
applyPatches,
8+
produceWithPatches,
9+
} from "immer";
10+
11+
import { enablePatches } from "immer";
12+
import { subscribeWithSelector } from "zustand/middleware";
13+
import { ParsedEntity, SchemaType } from "../types";
14+
15+
enablePatches();
16+
17+
interface PendingTransaction {
18+
transactionId: string;
19+
patches: Patch[];
20+
inversePatches: Patch[];
21+
}
22+
23+
interface GameState<T extends SchemaType> {
24+
entities: Record<string, ParsedEntity<T>>;
25+
pendingTransactions: Record<string, PendingTransaction>;
26+
setEntities: (entities: ParsedEntity<T>[]) => void;
27+
updateEntity: (entity: Partial<ParsedEntity<T>>) => void;
28+
applyOptimisticUpdate: (
29+
transactionId: string,
30+
updateFn: (draft: Draft<GameState<T>>) => void
31+
) => void;
32+
revertOptimisticUpdate: (transactionId: string) => void;
33+
confirmTransaction: (transactionId: string) => void;
34+
subscribeToEntity: (
35+
entityId: string,
36+
listener: (entity: ParsedEntity<T> | undefined) => void
37+
) => () => void;
38+
waitForEntityChange: (
39+
entityId: string,
40+
predicate: (entity: ParsedEntity<T> | undefined) => boolean,
41+
timeout?: number
42+
) => Promise<ParsedEntity<T> | undefined>;
43+
}
44+
45+
/**
46+
* Factory function to create a Zustand store based on a given SchemaType.
47+
*
48+
* @template T - The schema type.
49+
* @returns A Zustand hook tailored to the provided schema.
50+
*/
51+
export function createDojoStore<T extends SchemaType>() {
52+
const useStore = create<GameState<T>>()(
53+
subscribeWithSelector(
54+
immer((set, get) => ({
55+
entities: {},
56+
pendingTransactions: {},
57+
setEntities: (entities: ParsedEntity<T>[]) => {
58+
set((state: Draft<GameState<T>>) => {
59+
entities.forEach((entity) => {
60+
state.entities[entity.entityId] =
61+
entity as WritableDraft<ParsedEntity<T>>;
62+
});
63+
});
64+
},
65+
updateEntity: (entity: Partial<ParsedEntity<T>>) => {
66+
set((state: Draft<GameState<T>>) => {
67+
if (
68+
entity.entityId &&
69+
state.entities[entity.entityId]
70+
) {
71+
Object.assign(
72+
state.entities[entity.entityId],
73+
entity
74+
);
75+
}
76+
});
77+
},
78+
applyOptimisticUpdate: (transactionId, updateFn) => {
79+
const currentState = get();
80+
const [nextState, patches, inversePatches] =
81+
produceWithPatches(
82+
currentState,
83+
(draftState: Draft<GameState<T>>) => {
84+
updateFn(draftState);
85+
}
86+
);
87+
88+
set(() => nextState);
89+
90+
set((state: Draft<GameState<T>>) => {
91+
state.pendingTransactions[transactionId] = {
92+
transactionId,
93+
patches,
94+
inversePatches,
95+
};
96+
});
97+
},
98+
revertOptimisticUpdate: (transactionId) => {
99+
const transaction =
100+
get().pendingTransactions[transactionId];
101+
if (transaction) {
102+
set((state: Draft<GameState<T>>) =>
103+
applyPatches(state, transaction.inversePatches)
104+
);
105+
set((state: Draft<GameState<T>>) => {
106+
delete state.pendingTransactions[transactionId];
107+
});
108+
}
109+
},
110+
confirmTransaction: (transactionId) => {
111+
set((state: Draft<GameState<T>>) => {
112+
delete state.pendingTransactions[transactionId];
113+
});
114+
},
115+
subscribeToEntity: (entityId, listener): (() => void) => {
116+
return useStore.subscribe((state) => {
117+
const entity = state.entities[entityId];
118+
listener(entity);
119+
});
120+
},
121+
waitForEntityChange: (entityId, predicate, timeout = 6000) => {
122+
return new Promise<ParsedEntity<T> | undefined>(
123+
(resolve, reject) => {
124+
const unsubscribe = useStore.subscribe(
125+
(state) => state.entities[entityId],
126+
(entity) => {
127+
if (predicate(entity)) {
128+
clearTimeout(timer);
129+
unsubscribe();
130+
resolve(entity);
131+
}
132+
}
133+
);
134+
135+
const timer = setTimeout(() => {
136+
unsubscribe();
137+
reject(
138+
new Error(
139+
`waitForEntityChange: Timeout of ${timeout}ms exceeded`
140+
)
141+
);
142+
}, timeout);
143+
}
144+
);
145+
},
146+
}))
147+
)
148+
);
149+
150+
return useStore;
151+
}

packages/sdk/src/types.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,9 @@ export type ParsedEntity<T extends SchemaType> = {
226226
entityId: string;
227227
models: {
228228
[K in keyof T]: {
229-
[M in keyof T[K]]?: T[K][M];
229+
[M in keyof T[K]]?: T[K][M] extends object
230+
? Partial<T[K][M]>
231+
: T[K][M];
230232
};
231233
};
232234
};

pnpm-lock.yaml

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)