Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(create-mud): use new sync packages #1214

Merged
merged 83 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
6bc4dcd
migrate vanilla template to new sync stack
holic Jul 31, 2023
960300d
update types
holic Jul 31, 2023
9858d12
simplify
holic Jul 31, 2023
97f8a3c
migrate react client
holic Jul 31, 2023
8d7b706
migrate threejs
holic Jul 31, 2023
35c175c
migrate phaser template
holic Jul 31, 2023
5eb8bf1
update phaser example
holic Jul 31, 2023
b29a6fc
migrate example react
holic Jul 31, 2023
c6761eb
migrate example vanilla
holic Jul 31, 2023
fffdb10
start migrating e2e
holic Jul 31, 2023
d79c9d7
fix type
holic Jul 31, 2023
272cd6a
regen components
holic Aug 1, 2023
6cf4c1b
e2e wip
holic Aug 1, 2023
9f055af
fix node warning
holic Aug 1, 2023
07e9780
more wip
holic Aug 1, 2023
a5428f7
some e2e fixes
alvrs Aug 1, 2023
68f18ec
fix tests
holic Aug 2, 2023
9d9e5b4
removed unused method
holic Aug 2, 2023
3bf4eb8
make e2e tests work
holic Aug 2, 2023
0cb6cd9
bump timeout
holic Aug 2, 2023
7a150f6
disable that for now
holic Aug 2, 2023
0c34c1e
maybe fix tests
holic Aug 2, 2023
447c805
latestBlockNumberProcessed -> lastBlockNumberProcessed
holic Aug 2, 2023
89a711e
Merge remote-tracking branch 'origin/main' into holic/template-sync-s…
holic Aug 4, 2023
0db5373
update singleton entity
holic Aug 4, 2023
6bb33d8
attempt to override gas with custom transport and chain formatter
holic Aug 4, 2023
212c78c
nope, custom account doesn't work either
holic Aug 4, 2023
a0235f1
get zero fees working
holic Aug 4, 2023
08023c9
update templates etc
holic Aug 4, 2023
e46787c
bump es target
holic Aug 4, 2023
917c6d9
remove dupe keys after merge
holic Aug 4, 2023
490700f
Merge remote-tracking branch 'origin/main' into holic/template-sync-s…
holic Aug 4, 2023
f05eb13
Create tame-lemons-play.md
holic Aug 4, 2023
18b1476
a few more updates
holic Aug 4, 2023
c8b7cbd
Update tame-lemons-play.md
holic Aug 4, 2023
2b2a474
Update tame-lemons-play.md
holic Aug 4, 2023
8f5eff7
prettier
holic Aug 4, 2023
ba6f360
Update tame-lemons-play.md
holic Aug 7, 2023
f90031f
remove anvil.db before starting indexer
alvrs Aug 7, 2023
15c7ec3
Merge branch 'main' into holic/template-sync-stack
alvrs Aug 7, 2023
8a9a760
rework sqlite file handling
holic Aug 7, 2023
910eb24
rebuild recs components
holic Aug 7, 2023
a1d300f
fix start command
holic Aug 7, 2023
6840edb
I don't think this will help
holic Aug 7, 2023
3dceee3
console.log the indexer failures for playwright
holic Aug 7, 2023
eecfbad
move things around a bit
holic Aug 7, 2023
00b9b6c
log initial state
holic Aug 7, 2023
cb70bb8
this feels better
holic Aug 7, 2023
d463df2
Revert "log initial state"
holic Aug 7, 2023
eeb0a17
clean up
holic Aug 7, 2023
de05378
Update tame-lemons-play.md
holic Aug 7, 2023
39d160e
truncate instead of rm
alvrs Aug 7, 2023
08cbfb9
consolidate client config
holic Aug 7, 2023
2a44ceb
use anvil.js
holic Aug 7, 2023
81cb59e
don't use ws
holic Aug 7, 2023
f4defdb
get anvil.js working
holic Aug 8, 2023
7c1d06d
fix indexer shutdown
holic Aug 8, 2023
1b1e190
try our own exit tracking
holic Aug 8, 2023
05dc486
try an indexer instance per test
holic Aug 8, 2023
0666626
small bit of clean up
holic Aug 8, 2023
8d907e2
wip mud contract wrapper
holic Aug 8, 2023
063ea59
wip contract wrapper with nonce handling
holic Aug 8, 2023
2014146
add todo
holic Aug 8, 2023
9bb6c27
Merge remote-tracking branch 'origin/main' into holic/template-sync-s…
holic Aug 8, 2023
e89b317
clean up
holic Aug 8, 2023
891549a
throw on non-nonce issues
holic Aug 8, 2023
8a989d1
move getTableIds
holic Aug 9, 2023
cf7474c
simulate before write so we know when to increment nonce
holic Aug 9, 2023
397b8c7
deprecate/move TableId utils, replace with pure functions
holic Aug 9, 2023
945d75c
rename new utils
holic Aug 9, 2023
4f192b1
fix export
holic Aug 9, 2023
51a8017
missed an import
holic Aug 9, 2023
48ddd5a
Merge remote-tracking branch 'origin/main' into holic/template-sync-s…
holic Aug 9, 2023
8b9ede7
Merge remote-tracking branch 'origin/main' into holic/template-sync-s…
holic Aug 9, 2023
cf8aef2
revert changeset
holic Aug 9, 2023
0a810f5
update changeset
holic Aug 9, 2023
090b008
getContract -> createContract
holic Aug 9, 2023
051c510
move zero gas fee
holic Aug 9, 2023
2e270d1
move UnionOmit
holic Aug 9, 2023
f13d6d6
Merge remote-tracking branch 'origin/main' into holic/template-sync-s…
holic Aug 9, 2023
afe9613
don't need this anymore
holic Aug 9, 2023
575748e
Update .changeset/tame-lemons-play.md
holic Aug 9, 2023
d4984cc
Update tame-lemons-play.md
holic Aug 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions .changeset/tame-lemons-play.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

big props on this changeset, this is by far the best changeset we have so far

Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
---
"@latticexyz/cli": patch
"@latticexyz/common": major
"@latticexyz/create-mud": major
"@latticexyz/recs": patch
"@latticexyz/store-indexer": patch
---

Templates and examples now use MUD's new sync packages, all built on top of [viem](https://viem.sh/). This greatly speeds up and stabilizes our networking code and improves types throughout.

These new sync packages come with support for our `recs` package, including `encodeEntity` and `decodeEntity` utilities for composite keys.

If you're using `store-cache` and `useRow`/`useRows`, you should wait to upgrade until we have a suitable replacement for those libraries. We're working on a [sql.js](https://github.com/sql-js/sql.js/)-powered sync module that will replace `store-cache`.

**Migrate existing RECS apps to new sync packages**

As you migrate, you may find some features replaced, removed, or not included by default. Please [open an issue](https://github.com/latticexyz/mud/issues/new) and let us know if we missed anything.

1. Add `@latticexyz/store-sync` package to your app's `client` package and make sure `viem` is pinned to version `1.3.1` (otherwise you may get type errors)

2. In your `supportedChains.ts`, replace `foundry` chain with our new `mudFoundry` chain.

```diff
- import { foundry } from "viem/chains";
- import { MUDChain, latticeTestnet } from "@latticexyz/common/chains";
+ import { MUDChain, latticeTestnet, mudFoundry } from "@latticexyz/common/chains";

- export const supportedChains: MUDChain[] = [foundry, latticeTestnet];
+ export const supportedChains: MUDChain[] = [mudFoundry, latticeTestnet];
```
Comment on lines +21 to +30
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this part still necessary? or do we want to keep it so we can add stuff to mudFoundry in the future?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth keeping for now since we've discovered a couple reasons to hook into viem internals

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a reason to keep it: wevm/viem#1006


3. In `getNetworkConfig.ts`, remove the return type (to let TS infer it for now), remove now-unused config values, and add the viem `chain` object.

```diff
- export async function getNetworkConfig(): Promise<NetworkConfig> {
+ export async function getNetworkConfig() {
```

```diff
const initialBlockNumber = params.has("initialBlockNumber")
? Number(params.get("initialBlockNumber"))
- : world?.blockNumber ?? -1; // -1 will attempt to find the block number from RPC
+ : world?.blockNumber ?? 0n;
Comment on lines +42 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we currently have some mechanism to look for the HelloWorld event? or load it from the worlds.json file?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm world?.blockNumber already grabs it from the worlds.json

Copy link
Member Author

@holic holic Aug 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am imagining in the future syncToRecs will attempt to find a HelloStore event emission (which will also have things like protocol version)

```

```diff
+ return {
+ privateKey: getBurnerWallet().value,
+ chain,
+ worldAddress,
+ initialBlockNumber,
+ faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl,
+ };
```

4. In `setupNetwork.ts`, replace `setupMUDV2Network` with `syncToRecs`.

```diff
- import { setupMUDV2Network } from "@latticexyz/std-client";
- import { createFastTxExecutor, createFaucetService, getSnapSyncRecords } from "@latticexyz/network";
+ import { createFaucetService } from "@latticexyz/network";
+ import { createPublicClient, fallback, webSocket, http, createWalletClient, getContract, Hex, parseEther, ClientConfig } from "viem";
+ import { encodeEntity, syncToRecs } from "@latticexyz/store-sync/recs";
+ import { createBurnerAccount, createContract, transportObserver } from "@latticexyz/common";
```

```diff
- const result = await setupMUDV2Network({
- ...
- });

+ const clientOptions = {
+ chain: networkConfig.chain,
+ transport: transportObserver(fallback([webSocket(), http()])),
+ pollingInterval: 1000,
+ } as const satisfies ClientConfig;

+ const publicClient = createPublicClient(clientOptions);

+ const burnerAccount = createBurnerAccount(networkConfig.privateKey as Hex);
+ const burnerWalletClient = createWalletClient({
+ ...clientOptions,
+ account: burnerAccount,
+ });

+ const { components, latestBlock$, blockStorageOperations$, waitForTransaction } = await syncToRecs({
+ world,
+ config: storeConfig,
+ address: networkConfig.worldAddress as Hex,
+ publicClient,
+ components: contractComponents,
+ startBlock: BigInt(networkConfig.initialBlockNumber),
+ indexerUrl: networkConfig.indexerUrl ?? undefined,
+ });

+ const worldContract = createContract({
+ address: networkConfig.worldAddress as Hex,
+ abi: IWorld__factory.abi,
+ publicClient,
+ walletClient: burnerWalletClient,
+ });
```

```diff
// Request drip from faucet
- const signer = result.network.signer.get();
- if (networkConfig.faucetServiceUrl && signer) {
- const address = await signer.getAddress();
+ if (networkConfig.faucetServiceUrl) {
+ const address = burnerAccount.address;
```

```diff
const requestDrip = async () => {
- const balance = await signer.getBalance();
+ const balance = await publicClient.getBalance({ address });
console.info(`[Dev Faucet]: Player balance -> ${balance}`);
- const lowBalance = balance?.lte(utils.parseEther("1"));
+ const lowBalance = balance < parseEther("1");
```

You can remove the previous ethers `worldContract`, snap sync code, and fast transaction executor.

The return of `setupNetwork` is a bit different than before, so you may have to do corresponding app changes.

```diff
+ return {
+ world,
+ components,
+ playerEntity: encodeEntity({ address: "address" }, { address: burnerWalletClient.account.address }),
+ publicClient,
+ walletClient: burnerWalletClient,
+ latestBlock$,
+ blockStorageOperations$,
+ waitForTransaction,
+ worldContract,
+ };
```

5. Update `createSystemCalls` with the new return type of `setupNetwork`.

```diff
export function createSystemCalls(
- { worldSend, txReduced$, singletonEntity }: SetupNetworkResult,
+ { worldContract, waitForTransaction }: SetupNetworkResult,
{ Counter }: ClientComponents
) {
const increment = async () => {
- const tx = await worldSend("increment", []);
- await awaitStreamValue(txReduced$, (txHash) => txHash === tx.hash);
+ const tx = await worldContract.write.increment();
+ await waitForTransaction(tx);
return getComponentValue(Counter, singletonEntity);
};
```

6. (optional) If you still need a clock, you can create it with:

```ts
import { map, filter } from "rxjs";
import { createClock } from "@latticexyz/network";

const clock = createClock({
period: 1000,
initialTime: 0,
syncInterval: 5000,
});

world.registerDisposer(() => clock.dispose());

latestBlock$
.pipe(
map((block) => Number(block.timestamp) * 1000), // Map to timestamp in ms
filter((blockTimestamp) => blockTimestamp !== clock.lastUpdateTime), // Ignore if the clock was already refreshed with this block
filter((blockTimestamp) => blockTimestamp !== clock.currentTime) // Ignore if the current local timestamp is correct
)
.subscribe(clock.update); // Update the local clock
```

If you're using the previous `LoadingState` component, you'll want to migrate to the new `SyncProgress`:

```ts
import { SyncStep, singletonEntity } from "@latticexyz/store-sync/recs";

const syncProgress = useComponentValue(SyncProgress, singletonEntity, {
message: "Connecting",
percentage: 0,
step: SyncStep.INITIALIZE,
});

if (syncProgress.step === SyncStep.LIVE) {
// we're live!
}
```
4 changes: 3 additions & 1 deletion e2e/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
node_modules
node_modules
*.db
*.db-journal
2 changes: 1 addition & 1 deletion e2e/packages/client-vanilla/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
</script>
<div>
<div id="block" title="block" data-testid="block">-1</div>
<div id="sync-state" title="sync-state" data-testid="sync-state"></div>
<div id="sync-step" title="sync-step" data-testid="sync-step"></div>
</div>
</body>
</html>
1 change: 1 addition & 0 deletions e2e/packages/client-vanilla/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@latticexyz/schema-type": "link:../../../packages/schema-type",
"@latticexyz/services": "link:../../../packages/services",
"@latticexyz/std-client": "link:../../../packages/std-client",
"@latticexyz/store-sync": "link:../../../packages/store-sync",
"@latticexyz/utils": "link:../../../packages/utils",
"@latticexyz/world": "link:../../../packages/world",
"async-mutex": "^0.4.0",
Expand Down
27 changes: 12 additions & 15 deletions e2e/packages/client-vanilla/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
import { Component, Entity, getComponentValue } from "@latticexyz/recs";
import { setup } from "./mud/setup";

const {
network: {
storeCache,
network: { blockNumber$ },
worldSend,
worldContract,
components: { LoadingState },
},
network: { components, latestBlock$, worldContract, waitForTransaction },
} = await setup();

const _window = window as any;
_window.storeCache = storeCache;
_window.worldContract = worldContract;
_window.worldSend = worldSend;
_window.waitForTransaction = waitForTransaction;

_window.getComponentValue = (componentName: keyof typeof components, entity: Entity) =>
getComponentValue(components[componentName] as Component, entity);

// Update block number in the UI
blockNumber$.subscribe((blockNumber) => {
latestBlock$.subscribe((block) => {
const element = document.querySelector("#block");
if (element) element.innerHTML = String(blockNumber);
if (element) element.innerHTML = String(block.number);
});

// Update initial sync status in the UI
LoadingState.update$.subscribe((value) => {
const syncState = value.value[0]?.state;
const element = document.querySelector("#sync-state");
if (element) element.innerHTML = String(syncState);
components.SyncProgress.update$.subscribe(({ value }) => {
const syncStep = value[0]?.step;
const element = document.querySelector("#sync-step");
if (element) element.innerHTML = String(syncStep);
});
10 changes: 1 addition & 9 deletions e2e/packages/client-vanilla/src/mud/createSystemCalls.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import { getComponentValue } from "@latticexyz/recs";
import { awaitStreamValue } from "@latticexyz/utils";
import { ClientComponents } from "./createClientComponents";
import { SetupNetworkResult } from "./setupNetwork";

export type SystemCalls = ReturnType<typeof createSystemCalls>;

export function createSystemCalls(network: SetupNetworkResult, components: ClientComponents) {
// const increment = async () => {
// const tx = await worldSend("increment", []);
// await awaitStreamValue(txReduced$, (txHash) => txHash === tx.hash);
// return getComponentValue(Counter, singletonEntity);
// };

return {
// increment,
// TODO
};
}
31 changes: 7 additions & 24 deletions e2e/packages/client-vanilla/src/mud/getNetworkConfig.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import { SetupContractConfig, getBurnerWallet } from "@latticexyz/std-client";
import { getBurnerWallet } from "@latticexyz/std-client";
import worldsJson from "contracts/worlds.json";
import { supportedChains } from "./supportedChains";

const worlds = worldsJson as Partial<Record<string, { address: string; blockNumber?: number }>>;

type NetworkConfig = SetupContractConfig & {
privateKey: string;
faucetServiceUrl?: string;
snapSync?: boolean;
};

export async function getNetworkConfig(): Promise<NetworkConfig> {
export async function getNetworkConfig() {
const params = new URLSearchParams(window.location.search);

const chainId = Number(params.get("chainId") || import.meta.env.VITE_CHAIN_ID || 31337);
const chainId = Number(params.get("chainId") || params.get("chainid") || import.meta.env.VITE_CHAIN_ID || 31337);
const chainIndex = supportedChains.findIndex((c) => c.id === chainId);
const chain = supportedChains[chainIndex];
if (!chain) {
Expand All @@ -28,26 +21,16 @@ export async function getNetworkConfig(): Promise<NetworkConfig> {

const initialBlockNumber = params.has("initialBlockNumber")
? Number(params.get("initialBlockNumber"))
: world?.blockNumber ?? -1; // -1 will attempt to find the block number from RPC
: world?.blockNumber ?? 0n;

return {
clock: {
period: 1000,
initialTime: 0,
syncInterval: 5000,
},
provider: {
chainId,
jsonRpcUrl: params.get("rpc") ?? chain.rpcUrls.default.http[0],
wsRpcUrl: params.get("wsRpc") ?? chain.rpcUrls.default.webSocket?.[0],
},
privateKey: params.get("privateKey") ?? getBurnerWallet().value,
chainId,
modeUrl: params.get("mode") ?? chain.modeUrl,
chain,
faucetServiceUrl: params.get("faucet") ?? chain.faucetUrl,
worldAddress,
initialBlockNumber,
snapSync: params.get("snapSync") === "true",
disableCache: params.get("cache") === "false",
indexerUrl: params.get("indexerUrl"),
rpcHttpUrl: params.get("rpcHttpUrl"),
};
}
Loading