@colyseus/react 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +46 -35
- package/dist/index.mjs +47 -36
- package/package.json +2 -6
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,14 @@ let _colyseus_schema = require("@colyseus/schema");
|
|
|
3
3
|
|
|
4
4
|
//#region src/schema/createSnapshot.ts
|
|
5
5
|
/**
|
|
6
|
+
* Returns the `@type`-decorated field names for a Schema class,
|
|
7
|
+
* reading from `Symbol.metadata` set by v4's `@type()` decorators.
|
|
8
|
+
*/
|
|
9
|
+
function getSchemaFieldNames(node) {
|
|
10
|
+
const metadata = node.constructor?.[Symbol.metadata];
|
|
11
|
+
if (metadata && typeof metadata === "object") return Object.values(metadata).map((f) => f.name);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
6
14
|
* Builds a reverse lookup map from objects to their refIds.
|
|
7
15
|
*/
|
|
8
16
|
function buildObjectToRefIdMap(refs) {
|
|
@@ -64,8 +72,9 @@ function createSnapshotForArraySchema(node, previousResult, ctx) {
|
|
|
64
72
|
function createSnapshotForSchema(node, previousResult, ctx) {
|
|
65
73
|
const snapshotted = {};
|
|
66
74
|
let hasChanged = previousResult === void 0;
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
75
|
+
const fieldNames = getSchemaFieldNames(node);
|
|
76
|
+
if (!fieldNames) throw new Error(`createSnapshotForSchema: no field metadata found on ${node.constructor?.name ?? "unknown"}. Is @colyseus/schema v4 installed?`);
|
|
77
|
+
for (const fieldName of fieldNames) {
|
|
69
78
|
const value = node[fieldName];
|
|
70
79
|
if (typeof value !== "function") {
|
|
71
80
|
const snapshottedValue = createSnapshot(value, ctx);
|
|
@@ -73,15 +82,6 @@ function createSnapshotForSchema(node, previousResult, ctx) {
|
|
|
73
82
|
if (!hasChanged && previousResult && previousResult[fieldName] !== snapshottedValue) hasChanged = true;
|
|
74
83
|
}
|
|
75
84
|
}
|
|
76
|
-
else for (const key in node) {
|
|
77
|
-
if (key.startsWith("_") || key.startsWith("$")) continue;
|
|
78
|
-
const value = node[key];
|
|
79
|
-
if (typeof value !== "function") {
|
|
80
|
-
const snapshottedValue = createSnapshot(value, ctx);
|
|
81
|
-
snapshotted[key] = snapshottedValue;
|
|
82
|
-
if (!hasChanged && previousResult && previousResult[key] !== snapshottedValue) hasChanged = true;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
85
|
return hasChanged ? snapshotted : previousResult;
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
@@ -125,7 +125,14 @@ const subscriptionsByState = /* @__PURE__ */ new WeakMap();
|
|
|
125
125
|
* Gets or creates a subscription for the given room state.
|
|
126
126
|
*
|
|
127
127
|
* This function sets up change notification by wrapping the decoder's
|
|
128
|
-
* `
|
|
128
|
+
* `decode` method to intercept all state changes and notify subscribed
|
|
129
|
+
* React components.
|
|
130
|
+
*
|
|
131
|
+
* We wrap `decode()` rather than assigning `decoder.triggerChanges` because
|
|
132
|
+
* `triggerChanges` is a single-slot callback that gets unconditionally
|
|
133
|
+
* overwritten by `Callbacks.get()` / `getDecoderStateCallbacks()` on every
|
|
134
|
+
* call. Wrapping `decode()` sidesteps this conflict entirely since it is the
|
|
135
|
+
* sole entry point for both full-state syncs and incremental patches.
|
|
129
136
|
*
|
|
130
137
|
* @param roomState - The Colyseus room state Schema instance
|
|
131
138
|
* @param decoder - The Colyseus decoder associated with the room
|
|
@@ -140,27 +147,30 @@ function getOrCreateSubscription(roomState, decoder) {
|
|
|
140
147
|
dirtyRefIds: /* @__PURE__ */ new Set(),
|
|
141
148
|
parentRefIdMap: /* @__PURE__ */ new Map(),
|
|
142
149
|
objectToRefId: void 0,
|
|
143
|
-
cleanupCounter: 0
|
|
150
|
+
cleanupCounter: 0,
|
|
151
|
+
originalDecode: decoder.decode
|
|
144
152
|
};
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
153
|
+
decoder.decode = function(...args) {
|
|
154
|
+
const changes = subscription.originalDecode.apply(decoder, args);
|
|
155
|
+
if (changes && changes.length > 0) {
|
|
156
|
+
const refs = decoder.root?.refs;
|
|
157
|
+
if (refs) {
|
|
158
|
+
subscription.objectToRefId = /* @__PURE__ */ new Map();
|
|
159
|
+
for (const [refId, obj] of refs.entries()) if (obj !== null && typeof obj === "object") subscription.objectToRefId.set(obj, refId);
|
|
160
|
+
}
|
|
161
|
+
for (const change of changes) {
|
|
162
|
+
const refId = subscription.objectToRefId?.get(change.ref) ?? -1;
|
|
163
|
+
if (refId !== -1) {
|
|
164
|
+
let currentRefId = refId;
|
|
165
|
+
while (currentRefId !== void 0) {
|
|
166
|
+
subscription.dirtyRefIds.add(currentRefId);
|
|
167
|
+
currentRefId = subscription.parentRefIdMap.get(currentRefId);
|
|
168
|
+
}
|
|
160
169
|
}
|
|
161
170
|
}
|
|
171
|
+
subscription.listeners.forEach((callback) => callback());
|
|
162
172
|
}
|
|
163
|
-
|
|
173
|
+
return changes;
|
|
164
174
|
};
|
|
165
175
|
subscriptionsByState.set(roomState, subscription);
|
|
166
176
|
return subscription;
|
|
@@ -199,10 +209,12 @@ function useColyseusState(roomState, decoder, selector = (s) => s) {
|
|
|
199
209
|
(0, react.useEffect)(() => {
|
|
200
210
|
if (roomState && decoder) getOrCreateSubscription(roomState, decoder);
|
|
201
211
|
}, [roomState, decoder]);
|
|
202
|
-
const
|
|
212
|
+
const selectorRef = (0, react.useRef)(selector);
|
|
213
|
+
selectorRef.current = selector;
|
|
214
|
+
const getSnapshot = (0, react.useCallback)(() => {
|
|
203
215
|
if (!roomState || !decoder) return;
|
|
204
216
|
const subscription = getOrCreateSubscription(roomState, decoder);
|
|
205
|
-
const selectedState =
|
|
217
|
+
const selectedState = selectorRef.current(roomState);
|
|
206
218
|
const ctx = {
|
|
207
219
|
refs: decoder.root?.refs,
|
|
208
220
|
objectToRefId: subscription.objectToRefId,
|
|
@@ -224,14 +236,13 @@ function useColyseusState(roomState, decoder, selector = (s) => s) {
|
|
|
224
236
|
}
|
|
225
237
|
}
|
|
226
238
|
return result;
|
|
227
|
-
};
|
|
228
|
-
|
|
239
|
+
}, [roomState, decoder]);
|
|
240
|
+
return (0, react.useSyncExternalStore)((0, react.useCallback)((callback) => {
|
|
229
241
|
if (!roomState || !decoder) return () => {};
|
|
230
242
|
const subscription = getOrCreateSubscription(roomState, decoder);
|
|
231
243
|
subscription.listeners.add(callback);
|
|
232
244
|
return () => subscription.listeners.delete(callback);
|
|
233
|
-
};
|
|
234
|
-
return (0, react.useSyncExternalStore)(subscribe, getSnapshot);
|
|
245
|
+
}, [roomState, decoder]), getSnapshot);
|
|
235
246
|
}
|
|
236
247
|
|
|
237
248
|
//#endregion
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import { useEffect, useSyncExternalStore } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, useSyncExternalStore } from "react";
|
|
2
2
|
import { ArraySchema, MapSchema, Schema } from "@colyseus/schema";
|
|
3
3
|
|
|
4
4
|
//#region src/schema/createSnapshot.ts
|
|
5
5
|
/**
|
|
6
|
+
* Returns the `@type`-decorated field names for a Schema class,
|
|
7
|
+
* reading from `Symbol.metadata` set by v4's `@type()` decorators.
|
|
8
|
+
*/
|
|
9
|
+
function getSchemaFieldNames(node) {
|
|
10
|
+
const metadata = node.constructor?.[Symbol.metadata];
|
|
11
|
+
if (metadata && typeof metadata === "object") return Object.values(metadata).map((f) => f.name);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
6
14
|
* Builds a reverse lookup map from objects to their refIds.
|
|
7
15
|
*/
|
|
8
16
|
function buildObjectToRefIdMap(refs) {
|
|
@@ -64,8 +72,9 @@ function createSnapshotForArraySchema(node, previousResult, ctx) {
|
|
|
64
72
|
function createSnapshotForSchema(node, previousResult, ctx) {
|
|
65
73
|
const snapshotted = {};
|
|
66
74
|
let hasChanged = previousResult === void 0;
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
75
|
+
const fieldNames = getSchemaFieldNames(node);
|
|
76
|
+
if (!fieldNames) throw new Error(`createSnapshotForSchema: no field metadata found on ${node.constructor?.name ?? "unknown"}. Is @colyseus/schema v4 installed?`);
|
|
77
|
+
for (const fieldName of fieldNames) {
|
|
69
78
|
const value = node[fieldName];
|
|
70
79
|
if (typeof value !== "function") {
|
|
71
80
|
const snapshottedValue = createSnapshot(value, ctx);
|
|
@@ -73,15 +82,6 @@ function createSnapshotForSchema(node, previousResult, ctx) {
|
|
|
73
82
|
if (!hasChanged && previousResult && previousResult[fieldName] !== snapshottedValue) hasChanged = true;
|
|
74
83
|
}
|
|
75
84
|
}
|
|
76
|
-
else for (const key in node) {
|
|
77
|
-
if (key.startsWith("_") || key.startsWith("$")) continue;
|
|
78
|
-
const value = node[key];
|
|
79
|
-
if (typeof value !== "function") {
|
|
80
|
-
const snapshottedValue = createSnapshot(value, ctx);
|
|
81
|
-
snapshotted[key] = snapshottedValue;
|
|
82
|
-
if (!hasChanged && previousResult && previousResult[key] !== snapshottedValue) hasChanged = true;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
85
|
return hasChanged ? snapshotted : previousResult;
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
@@ -125,7 +125,14 @@ const subscriptionsByState = /* @__PURE__ */ new WeakMap();
|
|
|
125
125
|
* Gets or creates a subscription for the given room state.
|
|
126
126
|
*
|
|
127
127
|
* This function sets up change notification by wrapping the decoder's
|
|
128
|
-
* `
|
|
128
|
+
* `decode` method to intercept all state changes and notify subscribed
|
|
129
|
+
* React components.
|
|
130
|
+
*
|
|
131
|
+
* We wrap `decode()` rather than assigning `decoder.triggerChanges` because
|
|
132
|
+
* `triggerChanges` is a single-slot callback that gets unconditionally
|
|
133
|
+
* overwritten by `Callbacks.get()` / `getDecoderStateCallbacks()` on every
|
|
134
|
+
* call. Wrapping `decode()` sidesteps this conflict entirely since it is the
|
|
135
|
+
* sole entry point for both full-state syncs and incremental patches.
|
|
129
136
|
*
|
|
130
137
|
* @param roomState - The Colyseus room state Schema instance
|
|
131
138
|
* @param decoder - The Colyseus decoder associated with the room
|
|
@@ -140,27 +147,30 @@ function getOrCreateSubscription(roomState, decoder) {
|
|
|
140
147
|
dirtyRefIds: /* @__PURE__ */ new Set(),
|
|
141
148
|
parentRefIdMap: /* @__PURE__ */ new Map(),
|
|
142
149
|
objectToRefId: void 0,
|
|
143
|
-
cleanupCounter: 0
|
|
150
|
+
cleanupCounter: 0,
|
|
151
|
+
originalDecode: decoder.decode
|
|
144
152
|
};
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
153
|
+
decoder.decode = function(...args) {
|
|
154
|
+
const changes = subscription.originalDecode.apply(decoder, args);
|
|
155
|
+
if (changes && changes.length > 0) {
|
|
156
|
+
const refs = decoder.root?.refs;
|
|
157
|
+
if (refs) {
|
|
158
|
+
subscription.objectToRefId = /* @__PURE__ */ new Map();
|
|
159
|
+
for (const [refId, obj] of refs.entries()) if (obj !== null && typeof obj === "object") subscription.objectToRefId.set(obj, refId);
|
|
160
|
+
}
|
|
161
|
+
for (const change of changes) {
|
|
162
|
+
const refId = subscription.objectToRefId?.get(change.ref) ?? -1;
|
|
163
|
+
if (refId !== -1) {
|
|
164
|
+
let currentRefId = refId;
|
|
165
|
+
while (currentRefId !== void 0) {
|
|
166
|
+
subscription.dirtyRefIds.add(currentRefId);
|
|
167
|
+
currentRefId = subscription.parentRefIdMap.get(currentRefId);
|
|
168
|
+
}
|
|
160
169
|
}
|
|
161
170
|
}
|
|
171
|
+
subscription.listeners.forEach((callback) => callback());
|
|
162
172
|
}
|
|
163
|
-
|
|
173
|
+
return changes;
|
|
164
174
|
};
|
|
165
175
|
subscriptionsByState.set(roomState, subscription);
|
|
166
176
|
return subscription;
|
|
@@ -199,10 +209,12 @@ function useColyseusState(roomState, decoder, selector = (s) => s) {
|
|
|
199
209
|
useEffect(() => {
|
|
200
210
|
if (roomState && decoder) getOrCreateSubscription(roomState, decoder);
|
|
201
211
|
}, [roomState, decoder]);
|
|
202
|
-
const
|
|
212
|
+
const selectorRef = useRef(selector);
|
|
213
|
+
selectorRef.current = selector;
|
|
214
|
+
const getSnapshot = useCallback(() => {
|
|
203
215
|
if (!roomState || !decoder) return;
|
|
204
216
|
const subscription = getOrCreateSubscription(roomState, decoder);
|
|
205
|
-
const selectedState =
|
|
217
|
+
const selectedState = selectorRef.current(roomState);
|
|
206
218
|
const ctx = {
|
|
207
219
|
refs: decoder.root?.refs,
|
|
208
220
|
objectToRefId: subscription.objectToRefId,
|
|
@@ -224,14 +236,13 @@ function useColyseusState(roomState, decoder, selector = (s) => s) {
|
|
|
224
236
|
}
|
|
225
237
|
}
|
|
226
238
|
return result;
|
|
227
|
-
};
|
|
228
|
-
|
|
239
|
+
}, [roomState, decoder]);
|
|
240
|
+
return useSyncExternalStore(useCallback((callback) => {
|
|
229
241
|
if (!roomState || !decoder) return () => {};
|
|
230
242
|
const subscription = getOrCreateSubscription(roomState, decoder);
|
|
231
243
|
subscription.listeners.add(callback);
|
|
232
244
|
return () => subscription.listeners.delete(callback);
|
|
233
|
-
};
|
|
234
|
-
return useSyncExternalStore(subscribe, getSnapshot);
|
|
245
|
+
}, [roomState, decoder]), getSnapshot);
|
|
235
246
|
}
|
|
236
247
|
|
|
237
248
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colyseus/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "tsdown --watch --format esm,cjs",
|
|
@@ -22,8 +22,7 @@
|
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"@colyseus/schema": "^4.0.8",
|
|
24
24
|
"@colyseus/sdk": "^0.17.26",
|
|
25
|
-
"react": "
|
|
26
|
-
"react-dom": "^18.3.1"
|
|
25
|
+
"react": ">=18.3.1"
|
|
27
26
|
},
|
|
28
27
|
"devDependencies": {
|
|
29
28
|
"@colyseus/schema": "^4.0.8",
|
|
@@ -31,15 +30,12 @@
|
|
|
31
30
|
"@eslint/js": "^9.9.0",
|
|
32
31
|
"@testing-library/react": "^16.3.1",
|
|
33
32
|
"@types/react": "^18.3.3",
|
|
34
|
-
"@types/react-dom": "^18.3.0",
|
|
35
33
|
"buffer": "^6.0.3",
|
|
36
34
|
"eslint": "^9.9.0",
|
|
37
35
|
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
|
38
36
|
"eslint-plugin-react-refresh": "^0.4.9",
|
|
39
37
|
"globals": "^15.9.0",
|
|
40
38
|
"happy-dom": "^20.0.11",
|
|
41
|
-
"react": "^18.3.1",
|
|
42
|
-
"react-dom": "^18.3.1",
|
|
43
39
|
"reflect-metadata": "^0.2.2",
|
|
44
40
|
"tsdown": "^0.20.1",
|
|
45
41
|
"typescript": "^5.5.3",
|