Repository navigation
Conversation
…ArraySchema and MapSchema
- refId now read via the v5 `Symbol.for("$refId")` tag (v4 "~refId" kept
as fallback)
- tolerate v5's mixed Metadata shape (index→field objects + name→index
reverse entries) when deriving field names
- seed a no-op `decoder.triggerChanges` — v5 only collects (and returns)
DataChange[] when a subscriber exists, and the subscription wrapper
relies on the returned changes
- Snapshot<T> strips Schema base internals that v5 declares publicly
(`isTrackingPaused`, symbol-keyed $refId/$values), and accepts plain
Array/Map-shaped interfaces (IArray/IMap) so frontends can type props
without importing @colyseus/schema; compile-time assertions in
snapshotType.test.ts
Assisted-by: Claude Opus 4.8
encode() does not clear the encoder's dirty state — the caller must run discardChanges() after each patch (colyseus core does). Without it, the next encode() re-emits stale ops against a frozen index space, which schema v5 punishes with corrupted sequential array-splice patches (client decoded [C, C] where the server had [C]). Fixes 4 array tests. Also: RoomAvailable lost `publicAddress` in 0.18, and v5's Schema<any> generic needs an explicit cast in the bench comparison walker. Assisted-by: Claude Opus 4.8
Peer + dev deps: @colyseus/sdk ^0.18.1, @colyseus/schema ^5.0.8, @colyseus/shared-types ^0.18.1. Assisted-by: Claude Opus 4.8
New src/predict module, exported standalone and as room-bound variants
from createRoomContext({ predict }):
- usePredict — one shared Predict per room (Predict.get() constructs a
fresh instance per call), ref-counted with StrictMode-safe deferred
dispose
- useInput — the room's InputHandle (room.input() is idempotent)
- usePredictLoop — the frame driver: predict.tick(now) once per frame,
then the caller's send loop; owns rAF, or `{ external: true }` returns
drive(now) for an existing loop (e.g. R3F useFrame at priority -1)
- useReconciler — active prediction with the lifecycle handled: waits
for late spawn, recreates on instance replacement, disposes on unmount
- useAttachAll — passive smoothing as an effect (detach on unmount)
- useEventChannel — defineEvent with teardown plus reactivity: injected
callback wrappers re-render the caller, so has(key) derives work in
render code
- useEntityInstance / useSessionEntity — select decoded instances,
re-rendering only on identity change
- useLatch — the latch-then-consume tap recipe
- getSource(snapshot) — bridges useRoomState snapshots back to their
decoded instance for predict.value() reads (recorded for free during
snapshot creation via WeakMap)
Assisted-by: Claude Opus 4.8
Documents the structure-vs-motion split (snapshots at patch rate, predict reads per frame outside the render cycle) and each hook. Assisted-by: Claude Opus 4.8
"source" said where the object came from, not what you get back. The new name matches the "decoded schema instance" vocabulary used across the docs and the entity hooks. Pre-release rename — no alias kept. Assisted-by: Claude Fable 5
CHANGELOG for the 0.18 branch (predict hooks, schema v5 compat, peer dep bumps) and version bump 0.2.0 → 0.18.0 so @colyseus/react pairs with colyseus's own version numbers from here on. Assisted-by: Claude Fable 5
The IArray/IMap types and structural Snapshot branches were already absorbed into this branch (49bb64a) in extended form — concrete ArraySchema/MapSchema branches kept first for element inference, v5 internals stripped — so conflicts resolve to the branch's version. New from the PR: prepublishOnly → prepare, so git revisions (npm i colyseus/react-tools#0.18) build dist on install. Assisted-by: Claude Fable 5
Assisted-by: Claude Fable 5
"Latch" is electronics jargon; input buffering is the gamedev term for the same mechanism. Latch → InputBuffer, latch() → press(); consume() and peek() unchanged. Docs recipe wording moves to "buffer, then consume" alongside. Assisted-by: Claude Fable 5
The predict layer added three store reads that main does not have — useStoreRoom(), useEntityInstance and useEventChannel — each throwing the same missing-getServerSnapshot error, so each needed its own server snapshot on top of the merge. 0.18.0 is unpublished, so the fix folds into its notes instead of bumping. Assisted-by: Claude Opus 5
A Next.js App Router Server Component importing any hook failed the build outright — "You're importing a component that needs useState" — because the package never declared its own client boundary. Verified against Next 14.2: fails without the directive, builds clean with it. Assisted-by: Claude Opus 5
Every hook is swept under renderToString rather than spot-checked: the missing-getServerSnapshot throw is per-call-site, so any new store read reintroduces it. 23 of these cases were red before #12 landed, including useSessionEntity and useReconciler by way of useEntityInstance. The hydration suite carries a deliberate-mismatch control — without it an empty mismatch list proves nothing. Assisted-by: Claude Opus 5
createSnapshot caches by refId, and a root the selector builds itself —
Array.from(state.players.values()), an object literal — has none, so every
getSnapshot() minted a fresh reference and React re-rendered until it hit
"Maximum update depth exceeded".
Rather than diff the finished trees, the previous result is handed down as that
container's previousResult, letting the structural sharing already in the
Map/Array/Schema helpers settle it in a single pass. Object literals get the
same helper, which also stops them leaking the live schema nodes that
`{ player: state.players.get(id) }` used to hand out.
Assisted-by: Claude Opus 5
Assisted-by: Claude Opus 5
sdk 0.18.2 merges smoothing/damping into one smoothMs (time constant, ms) — the hooks re-export those option types, so only the docs example and the version floor move. Assisted-by: Claude Fable 5
Assisted-by: Claude Fable 5
main's 0.1.19 fix landed on this line as 0.18.1 already, so every code conflict resolves to the 0.18 side — it carries the schema v5 and predict work on top. Only the 0.1.19 changelog entry is new here, filed under the 0.1.x history it belongs to. Assisted-by: Claude Opus 5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Targets Colyseus 0.18 /
@colyseus/schemav5, adds React bindings for the new Predict netcode, and moves the package to0.18.0— from this release on,@colyseus/reactversioning tracks colyseus itself.Netcode hooks (new
src/predictmodule)React bindings for the 0.18 Predict tools (client prediction, reconciliation, remote smoothing). Every hook exists standalone (
useX(room, ...)) and room-bound viacreateRoomContext({ predict }):usePredict— one shared, ref-countedPredictper room (StrictMode-safe deferred dispose)useInput— the room'sInputHandleusePredictLoop— the frame driver; owns rAF, or{ external: true }for an existing loop (e.g. R3FuseFrame)useReconciler— active prediction with lifecycle handled (late spawn, instance replacement, dispose)useAttachAll— passive smoothing as an effectuseEventChannel— optimistic events with teardown plus reactivityuseEntityInstance/useSessionEntity— decoded-instance selection, identity-change re-renders onlyuseInputBuffer— the buffer-then-consume tap recipe (renamed fromuseLatchduring review)getSchemaInstance(snapshot)— bridges snapshots back to their decoded instance forpredict.value()reads (renamed fromgetSourceduring review — self-describing, matches the "decoded schema instance" vocabulary)Schema v5 compatibility
refIdread via v5'sSymbol.for("$refId")(v4~refIdfallback kept)Metadatashape when deriving field namesdecoder.triggerChangessodecode()returnsDataChange[]without an external subscriberSnapshot<T>strips v5's publicly-declared Schema internals and accepts plainIArray/IMapshapes so frontends can type props without importing@colyseus/schemaRelease
@colyseus/sdk^0.18.1,@colyseus/schema^5.0.8,@colyseus/shared-types^0.18.1 (0.17/v4 users stay on 0.1.x)IArray/IMapSnapshottypes (already absorbed on this branch in extended form) plusprepublishOnly→prepare, so git revisions (npm install colyseus/react-tools#0.18) builddist/on installTest plan
npx vitest run— 154 passed, 3 skipped (includes newpredictHooks.test.tsxandsnapshotType.test.tssuites)discardChanges()after eachencode())🤖 Generated with Claude Code