Per-client State Visibility with StateView
This feature was introduced in version 0.16. It replaces the previously experimental @filter() and @filterChildren() decorators.
By default, the entire state is visible to all clients. However, you may want to control which parts of the state are visible to each client.
You can do so by:
- Assigning a
StateViewinstance to the client - Tag state fields with the
@view()decorator - Manually
.add()schema instances to theStateView - Manually
.remove()schema instances from theStateView
A StateView instance must be assigned to the client.view.
It is not recommended to rely on StateView for large datasets, as it is not optimized for that yet. However, it is a great way to filter data per client, such as “private fields” per schema instance, “level of detail”, area-based, team-owned data, etc.
Initializing a StateView
import { StateView } from "@colyseus/schema";
// ...
onJoin(client, options) {
client.view = new StateView();
// ...
}
// ...How serialization works
- Each
StateViewinstance is going to add a new encoding step for state serialization. - You may re-use the same
StateViewinstance for multiple clients, or create a new one for each client. - Internally, all “shared” properties (properties not tagged with
@view()) are serialized first, and then eachStateViewis serialized with its own set of properties.
Tagging fields with @view()
The @view() decorator is used to tag a field as only visible to StateView instances that contain that Schema instance.
class Player extends Schema {
// visible to all
@type("string") name: string;
// only visible to clients containing this schema instance on their `StateView`
@view() @type("number") position: number;
}In the example above, the position field is only visible to clients that contain this Player instance in their StateView.
Adding a schema instance to a StateView
To add a schema instance to a StateView, call .add() on the StateView instance:
import { StateView } from "@colyseus/schema";
// ...
onJoin(client, options) {
const player = new Player();
this.state.players.set(client.sessionId, player);
client.view = new StateView();
client.view.add(player);
}The frontend will receive either an “On Add” or “Listen” callback, depending on which structure the schema instance is part of.
Removing a schema instance from a StateView
To remove a schema instance from a StateView, call .remove() on the StateView instance:
client.view.remove(player);The frontend will receive either an “On Remove” or “Listen” callback, depending on which structure the schema instance is part of.
Checking if instance is part of StateView
You can check if a schema instance is part of a StateView by calling .has() on the StateView instance:
if (client.view.has(player)) {
// player is part of this client's StateView
}The frontend will receive either an “On Remove” or “Listen” callback, depending on which structure the schema instance is part of.
Specialized tags with @view(tag: number)
Sometimes you may want to have multiple views with different fields.
class Player extends Schema {
// visible to all
@type("string") name: string;
// any `.add(player)` will see this field
@view() @type("number") health: number;
// only `.add(player, 1)` will see this field
@view(1) @type("number") position: number;
}By assigning a numeric tag to the @view() decorator, that field will only be visible to clients that contain this Schema instance on their StateView with the same tag:
// ...
onJoin(client, options) {
const player = new Player().assign({ name: "Player 1", health: 100, position: 0 });
this.state.players.set(client.sessionId, player);
client.view = new StateView();
client.view.add(player, 1); // add with tag 1 - "position" field is visible
}
// ...In the example above, the position field is only visible to clients that contain this Player instance in their StateView with tag 1, whereas the health field is visible to all clients that contain this Player instance in their StateView.
The following table shows the relation between type annotations and the visibility of each field on the frontend:
| Annotations | Without view.add() | view.add(instance) | view.add(instance, 1) |
|---|---|---|---|
@type(...) | ✅ | ✅ | ✅ |
@view() @type(...) | ❌ | ✅ | ✅ |
@view(1) @type(...) | ❌ | ❌ | ✅ |
Items of ArraySchema and MapSchema
When you tag an array or map with @view(), each element of the array or map must be added to the client’s StateView individually.
class Player extends Schema {
@type("string") name: string;
@type("number") position: number;
}
class MyState extends Schema {
@view() @type({ map: Player }) players = new MapSchema<Player>();
}The instance must be assigned to the state and added to the StateView:
import { StateView } from "@colyseus/schema";
// ...
onJoin(client, options) {
const player = new Player();
this.state.players.set(client.sessionId, player);
client.view = new StateView();
client.view.add(player);
}
// ...