Interacting with MUD from a client
MUD is client agnostic: any client -- a browser, a game engine, an iOS app -- can implement the synchronization protocol and a client-side cache to replicate Store tables, along with the necessary infrastructure to send transactions to the World.
However, currently we only officially support the browser and Node. We provide tooling for representing the onchain state, replicate it from an RPC or MODE via a network stack, and send transactions to the World.
There also exists tooling for Unity (opens in a new tab) from the folks at emergence.land (opens in a new tab).
Representing state in the browser
When using MUD in the browser, we provide two different ways to query the Store and react to changes:
recs
:recs
is a client-side store optimize for ECS modelling. If you model your MUD data using ECS, this is a great choice.store-cache
:store-cache
is reactive tuple-database. It has a more powerful query system thanrecs
, and support multiple keys. If don't use the ECS data model (eg: you have multi-key tables, like one-to-many relationship), we recommend usingstore-cache
.
Note: when using the default MUD templates (created with pnpm create mud@canary
), the Store data will be available in both client-side stores. You are free to query with the one that makes the most sense.
Over time, we will migrate to store-cache
as the default and surface a opinionated layer for ECS data on top.
recs
: the reactive ECS database
recs
has been designed to represent and query ECS data.
If your MUD tables are set up with a single bytes32
key (see the ECS modelling guide), and you plan to represent an entity and its associated components as the same bytes32
key on multiple tables, then recs
will let you query your data in a natural way.
To read data and react to changes with recs
, there exists two APIs:
- Reading component value directly
- Subscribing to a query that returns a set of matching entities
Reading component value directly
If you have a specific bytes32
key and a reference to a component, you can query its value directly, or use a React hook that will re-render when the corresponding component value updates.
Reading component value in vanilla javascript
import { getComponentValueStrict } from "@latticexyz/recs";
// note: NameComponent is an recs component; components could come from the `setup` function of MUD.
const { NameComponent } = components
// get a reference to the recs world; as an example from the `setup` function of MUD.
const world = [...]
// you need a reference to an `Entity` from recs. For the sake of the example, let's say we know that the bytes32 key is "0xDEAD"
// you wouldn't do normally: entities would be found via queries.
const entityID = "0xDEAD" as EntityID;
const entity = world.registerEntity({ id: "0xDEAD" as EntityI })
// now we can fetch the value of the NameComponent on our entity
const name = getComponentValueStrict(NameComponent, entity)
console.log(name) // "Hello!"
Reading component value in react with @latticexyz/react
import { useComponentValue } from "@latticexyz/react";
function ExampleComponent() {
const { components, world } = useMUD;
const { NameComponent } = components;
// you need a reference to an `Entity` from recs. For the sake of the example, let's say we know that the bytes32 key is "0xDEAD"
// you wouldn't do normally: entities would be found via queries.
const entityID = "0xDEAD" as EntityID;
const entity = world.registerEntity({ id: "0xDEAD" as EntityI });
// now we can fetch the value of the NameComponent on our entity
const name = useComponentValue(NameComponent, entity);
// this will re-render when the component value changes!
return <p>{name ?? "<no name>"}</p>;
}
Running queries
A common way of working with ECS data is by using queries. A query is a function that returns a list of entities that match a set of predicates called query fragments.
There are 4 types of query fragments:
Has
: matches entities that have a specific component.Not
: matches entities that do not have a specific component.HasValue
: matches entities that have a specific componenet with a set value.NotValue
: matches entities that do not have a component with a set value (will also match if it doesn't have the component at all)
Running query with vanilla javsascript
import { runQuery, Has, HasValue, getComponentValueStrict } from "@latticexyz/recs";
const { PlayerComponent, PositionComponent, NameComponent } = components
// query for all named players at the center of the universe
const matchingEntities = runQuery([
Has(PlayerComponent),
Has(Name),
HasValue(PositionCompoennt, {x: 0, y: 0})
])
// now you can map these to their name as an example
const names = matchingEntities.map(
playerEntity => getComponentValueStrict(NameComponent, playerEntity
)
Reacting to queries with vanilla javascript
import { defineSystem, Has, HasValue, getComponentValueStrict, UpdateType } from "@latticexyz/recs";
const { PlayerComponent, PositionComponent, NameComponent } = components
// get a reference to the recs world; as an example from the `setup` function of MUD.
const world = [...]
defineSystem(world, [Has(PlayerComponent), Has(Name), HasValue(PositionComponent, {x: 0, y: 0}], ({entity, component, value, type}) => {
// every time an entity enter, exit, or get updated within a query; this callback with fire
// the "type" will match these: UpdateType.Enter, UpdateType.Exit, UpdateType.Update
// as an example, if a new entity is named, has a player component, and is at the center of the universe; the callback fires
// if the name of a player at the center of the universe changes, the callback will also fire for that entity
// let's only log when a new entity matches this query
// every time a named player reaches {0, 0}; we want to log their name
if(type !== UpdateType.Enter) return
console.log(getComponentValueStrict(NameComponent, entity) + " reached the center!")
})
Reacting to queries with React and @latticexyz/react
import { useEntityQuery } from "@latticexyz/react";
import { Has, HasValue, getComponentValueStrict } from "@latticexyz/recs";
function ExampleComponent() {
const { components, world } = useMUD;
const { NameComponent, PlayerComponent, PositionCompoennt } = components
// get a list of all entities that are named, players, and at the center of the universe
// it is reactive and will trigger a re-render when the set of matching queries update
const entities = useEntityQuery([Has(PlayerComponent), Has(Name), HasValue(PositionComponent, {x: 0, y: 0}])
return(
<div>
<span>Players at the center:</span>
<ul>
entities.map(entity => (
<li>{getComponentValueStrict(NameComponent, entity)}</li>
))
</ul>
</div>
)
return(
<p>{name ?? "<no name>"}</p>
)
}