Hi everybody! Regarding the possible new schema callback API, so far this is what I could came up so far, feedback highly appreciated! Do let me know if you feel I need to clarify anything!
I understand this may be long and hard to grasp, I can provide a side-by-side comparison between the new API and previous one so you can clearly see the difference.
The new API would allow to create all the callbacks at once, after the connection with the room has been established (even on deep structures) - rather than binding callbacks as Schema instances that are created on-demand on the client-side.
class Point extends Schema {
@type("number") x: number = 0;
@type("number") y: number = 0;
}
class Item extends Schema {
@type("string") name: string;
@type("number") price: string;
@type({ map: "string" }) attributes = new MapSchema<string>();
}
class Player extends Schema {
@type('string') name: string;
@type('number') level: number;
@type(Point) position = new Point();
@type({ map: Item }) items = new MapSchema<Item>();
}
class Ball extends Schema {
@type(Point) position: Point = new Point();
}
class State extends Schema {
@type("number") state: MatchState;
@type({ map: Player }) players = new MapSchema<Player>();
@type(Ball) ball = new Ball();
}
A new "snapshots" property would be used to register callbacks on incoming state patches from the server:
let snapshots: Snapshots<State>;
// user would consume this from `room.snapshots`
room.snapshots.on() // ...
snapshots.on("ball").on("position").on("x", (position, value, previousValue) => { });
snapshots.on("ball").on("position").on("y", (position, value, previousValue) => { });
snapshots.on("ball").on("position").onChange((position, changes) => {
changes.forEach((change) => {
if (change.field === "x") {
} else if (change.field === "y") {
}
})
});
snapshots.on("ball").onChange((ball, changes) => {
// ideally this would trigger if any property inside "ball" has changed (after previous registered callback)
});
snapshots.on("players").onAdd((player, sessionId) => { });
snapshots.on("players").onRemove((player, sessionId) => { });
snapshots.on("players").onChange((player, sessionId) => {
// ideally called after any property inside "player" has changed.
// ideally, also, no overhead in case .onChange was not registered.
});
snapshots.on("players").children().on("level", (player, value, previousValue) => {
// this is triggered whenever "level" property is changed inside any "Player" instance.
});
snapshots.on("players").children().on("items").onAdd((item, key) => { });
snapshots.on("players").children().on("items").children().on("attributes").onAdd((attribute, key) => {
// when listening to very deep structures, their parent instances
// could be retrieved via .current(Class), or .currentAt(propertyName)
const player = snapshots.current(Player);
const player = snapshots.currentAt<Player>("players"); // dynamic alternative (when client does not have a concrete "Player" implementation)
const item = snapshots.current(Item);
const item = snapshots.currentAt<Item>("items"); // dynamic alternative (when client does not have a concrete "Player" implementation)
});
snapshots.on("players").children().onChange((player, changes) => {
// whenever any change ocurred on a "Player" instance.
});
// examples removing a bound calback
const onAddPlayers = snapshots.on("players").onAdd((player, key) => {
snapshots.off(onAddPlayers)
});
const ballPositionX = snapshots.on("ball").on("position").on("x", (position, value, previousValue) => {
snapshots.off(ballPositionX);
});
The functionality of this API hasn't been implemented yet, only its feasibility using the TypeScript typing system has been confirmed, as you can see on the video.
I like the new callbacks, even though I don't yet see a big benefit/difference from it. Maybe you could add the side-by-side comparison to clarify. And what video? 😁