@colyseus/schema 5.0.10 → 5.0.11

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/build/index.js CHANGED
@@ -2598,7 +2598,6 @@
2598
2598
  // per-view WeakSet lookups with direct bitwise ops.
2599
2599
  // Lazy: undefined until the tree participates in any view.
2600
2600
  visibleViews;
2601
- invisibleViews;
2602
2601
  // Per-(view, tag) bitmap, indexed by tag. Custom tags only —
2603
2602
  // DEFAULT_VIEW_TAG visibility lives in `visibleViews`.
2604
2603
  tagViews;
@@ -2950,7 +2949,6 @@
2950
2949
  // per-view visibility lives on the tree (NOT keyed by refId), so a
2951
2950
  // recycled tree must not inherit its previous life's view membership.
2952
2951
  this.visibleViews = undefined;
2953
- this.invisibleViews = undefined;
2954
2952
  this.tagViews = undefined;
2955
2953
  this.subscribedViews = undefined;
2956
2954
  }
@@ -8010,17 +8008,7 @@
8010
8008
  // Visibility gate: when a view is active, a non-visible tree contributes
8011
8009
  // nothing itself but we still recurse so descendants (possibly added to
8012
8010
  // the view explicitly) are reachable.
8013
- let visibleHere = true;
8014
- if (ctx.hasView) {
8015
- const view = ctx.view;
8016
- if (!view.isChangeTreeVisible(changeTree)) {
8017
- view.markInvisible(changeTree);
8018
- visibleHere = false;
8019
- }
8020
- else {
8021
- view.unmarkInvisible(changeTree);
8022
- }
8023
- }
8011
+ const visibleHere = !ctx.hasView || ctx.view.isChangeTreeVisible(changeTree);
8024
8012
  if (visibleHere) {
8025
8013
  const desc = changeTree.encDescriptor;
8026
8014
  ctx.changeTree = changeTree;
@@ -8169,12 +8157,8 @@
8169
8157
  let current = queue;
8170
8158
  while (current = current.next) {
8171
8159
  const changeTree = current.changeTree;
8172
- if (hasView) {
8173
- if (!view.isChangeTreeVisible(changeTree)) {
8174
- view.markInvisible(changeTree);
8175
- continue;
8176
- }
8177
- view.unmarkInvisible(changeTree);
8160
+ if (hasView && !view.isChangeTreeVisible(changeTree)) {
8161
+ continue;
8178
8162
  }
8179
8163
  const recorder = unreliable ? changeTree.unreliableRecorder : changeTree;
8180
8164
  if (!recorder || !recorder.has()) {
@@ -9866,9 +9850,6 @@
9866
9850
  const v = tree.visibleViews;
9867
9851
  if (v !== undefined && slot < v.length)
9868
9852
  v[slot] &= clearMask;
9869
- const i = tree.invisibleViews;
9870
- if (i !== undefined && slot < i.length)
9871
- i[slot] &= clearMask;
9872
9853
  const s = tree.subscribedViews;
9873
9854
  if (s !== undefined && slot < s.length)
9874
9855
  s[slot] &= clearMask;
@@ -10020,32 +10001,6 @@
10020
10001
  if (slot < arr.length)
10021
10002
  arr[slot] &= ~this._bit;
10022
10003
  }
10023
- /** True iff this view has previously marked `tree` as invisible. */
10024
- isInvisible(tree) {
10025
- const arr = tree.invisibleViews;
10026
- const slot = this._slot;
10027
- return arr !== undefined && slot < arr.length && (arr[slot] & this._bit) !== 0;
10028
- }
10029
- /** Mark `tree` as invisible to this view (used by encode loop). */
10030
- markInvisible(tree) {
10031
- const slot = this._slot;
10032
- let arr = tree.invisibleViews;
10033
- if (arr === undefined) {
10034
- arr = tree.invisibleViews = [];
10035
- }
10036
- while (arr.length <= slot)
10037
- arr.push(0);
10038
- arr[slot] |= this._bit;
10039
- }
10040
- /** Clear invisible bit. */
10041
- unmarkInvisible(tree) {
10042
- const arr = tree.invisibleViews;
10043
- if (arr === undefined)
10044
- return;
10045
- const slot = this._slot;
10046
- if (slot < arr.length)
10047
- arr[slot] &= ~this._bit;
10048
- }
10049
10004
  // ──────────────────────────────────────────────────────────────────
10050
10005
  // Per-tag, per-view bitmap. Replaces the legacy
10051
10006
  // `tags: WeakMap<ChangeTree, Set<number>>` storage. Hot read site is
@@ -10189,11 +10144,18 @@
10189
10144
  // below use `metadata?.[...]` null-safe access. Only Schema
10190
10145
  // subclasses yield a real Metadata object.
10191
10146
  const metadata = obj.constructor[Symbol.metadata];
10192
- this.markVisible(changeTree);
10193
- // add to iterable list (only the explicitly added items)
10194
- if (this.iterable && checkIncludeParent) {
10147
+ // Add to iterable list (only the explicitly added items), deduping
10148
+ // re-adds of an already-visible instance. isVisible must be read
10149
+ // BEFORE markVisible; indexOf runs only on the re-add path.
10150
+ // NOTE: dedup applies to `items` only — a re-add still re-queues the
10151
+ // full snapshot on purpose (shared-view bootstrap re-add: a
10152
+ // late-attached client may not have consumed earlier drains).
10153
+ // Callers wanting cheap idempotence can guard with `view.has(obj)`.
10154
+ if (this.iterable && checkIncludeParent
10155
+ && (!this.isVisible(changeTree) || this.items.indexOf(obj) === -1)) {
10195
10156
  this.items.push(obj);
10196
10157
  }
10158
+ this.markVisible(changeTree);
10197
10159
  // add parent ChangeTree's
10198
10160
  // - if it was invisible to this view
10199
10161
  // - if it were previously filtered out
@@ -10276,18 +10238,20 @@
10276
10238
  }
10277
10239
  else if (!changeTree.isNew || isChildAdded) {
10278
10240
  // new structures will be added as part of .encode() call, no need to force it to .encodeView()
10279
- const isInvisible = this.isInvisible(changeTree);
10280
10241
  // Full-sync snapshot: walk the live ref structurally instead of
10281
10242
  // iterating a cumulative recorder bucket. Every populated index
10282
10243
  // is emitted as ADD (matching the op-coercion previously done
10283
10244
  // at encode time). Per-field tags come from the descriptor's
10284
10245
  // precomputed `tags[]` array — direct index vs a metadata[i].tag
10285
10246
  // object hop.
10247
+ //
10248
+ // Non-matching custom-tagged fields are NEVER included here —
10249
+ // `view.changes` is drained without a per-field tag re-check,
10250
+ // so anything added leaks straight to the wire.
10286
10251
  const tags = changeTree.encDescriptor.tags;
10287
10252
  changeTree.forEachLive((index) => {
10288
10253
  const tagAtIndex = tags[index];
10289
- if (isInvisible || // if "invisible", include all
10290
- tagAtIndex === undefined || // "all change" with no tag
10254
+ if (tagAtIndex === undefined || // "all change" with no tag
10291
10255
  tagAtIndex === DEFAULT_VIEW_TAG || // visible to all clients
10292
10256
  (tag !== DEFAULT_VIEW_TAG && (tagAtIndex & tag) !== 0) // tag bits overlap
10293
10257
  ) {
@@ -10439,7 +10403,7 @@
10439
10403
  }
10440
10404
  // ── Streamable-collection unsubscribe (the stream itself) ─────
10441
10405
  // Flush DELETE for every sent position and drop pending. After
10442
- // this, the stream is marked invisible to this view — any future
10406
+ // this, the stream is no longer visible to this view — any future
10443
10407
  // `stream.add()` would still seed broadcast pending (if no views)
10444
10408
  // but would NOT re-seed per-view pending (user must re-subscribe).
10445
10409
  if (changeTree.isStreamCollection) {
package/build/index.mjs CHANGED
@@ -2592,7 +2592,6 @@ class ChangeTree {
2592
2592
  // per-view WeakSet lookups with direct bitwise ops.
2593
2593
  // Lazy: undefined until the tree participates in any view.
2594
2594
  visibleViews;
2595
- invisibleViews;
2596
2595
  // Per-(view, tag) bitmap, indexed by tag. Custom tags only —
2597
2596
  // DEFAULT_VIEW_TAG visibility lives in `visibleViews`.
2598
2597
  tagViews;
@@ -2944,7 +2943,6 @@ class ChangeTree {
2944
2943
  // per-view visibility lives on the tree (NOT keyed by refId), so a
2945
2944
  // recycled tree must not inherit its previous life's view membership.
2946
2945
  this.visibleViews = undefined;
2947
- this.invisibleViews = undefined;
2948
2946
  this.tagViews = undefined;
2949
2947
  this.subscribedViews = undefined;
2950
2948
  }
@@ -8004,17 +8002,7 @@ function _fullSyncWalk(ctx, changeTree) {
8004
8002
  // Visibility gate: when a view is active, a non-visible tree contributes
8005
8003
  // nothing itself but we still recurse so descendants (possibly added to
8006
8004
  // the view explicitly) are reachable.
8007
- let visibleHere = true;
8008
- if (ctx.hasView) {
8009
- const view = ctx.view;
8010
- if (!view.isChangeTreeVisible(changeTree)) {
8011
- view.markInvisible(changeTree);
8012
- visibleHere = false;
8013
- }
8014
- else {
8015
- view.unmarkInvisible(changeTree);
8016
- }
8017
- }
8005
+ const visibleHere = !ctx.hasView || ctx.view.isChangeTreeVisible(changeTree);
8018
8006
  if (visibleHere) {
8019
8007
  const desc = changeTree.encDescriptor;
8020
8008
  ctx.changeTree = changeTree;
@@ -8163,12 +8151,8 @@ class Encoder {
8163
8151
  let current = queue;
8164
8152
  while (current = current.next) {
8165
8153
  const changeTree = current.changeTree;
8166
- if (hasView) {
8167
- if (!view.isChangeTreeVisible(changeTree)) {
8168
- view.markInvisible(changeTree);
8169
- continue;
8170
- }
8171
- view.unmarkInvisible(changeTree);
8154
+ if (hasView && !view.isChangeTreeVisible(changeTree)) {
8155
+ continue;
8172
8156
  }
8173
8157
  const recorder = unreliable ? changeTree.unreliableRecorder : changeTree;
8174
8158
  if (!recorder || !recorder.has()) {
@@ -9860,9 +9844,6 @@ function _clearViewBitFromAllTrees(root, slot, bit) {
9860
9844
  const v = tree.visibleViews;
9861
9845
  if (v !== undefined && slot < v.length)
9862
9846
  v[slot] &= clearMask;
9863
- const i = tree.invisibleViews;
9864
- if (i !== undefined && slot < i.length)
9865
- i[slot] &= clearMask;
9866
9847
  const s = tree.subscribedViews;
9867
9848
  if (s !== undefined && slot < s.length)
9868
9849
  s[slot] &= clearMask;
@@ -10014,32 +9995,6 @@ class StateView {
10014
9995
  if (slot < arr.length)
10015
9996
  arr[slot] &= ~this._bit;
10016
9997
  }
10017
- /** True iff this view has previously marked `tree` as invisible. */
10018
- isInvisible(tree) {
10019
- const arr = tree.invisibleViews;
10020
- const slot = this._slot;
10021
- return arr !== undefined && slot < arr.length && (arr[slot] & this._bit) !== 0;
10022
- }
10023
- /** Mark `tree` as invisible to this view (used by encode loop). */
10024
- markInvisible(tree) {
10025
- const slot = this._slot;
10026
- let arr = tree.invisibleViews;
10027
- if (arr === undefined) {
10028
- arr = tree.invisibleViews = [];
10029
- }
10030
- while (arr.length <= slot)
10031
- arr.push(0);
10032
- arr[slot] |= this._bit;
10033
- }
10034
- /** Clear invisible bit. */
10035
- unmarkInvisible(tree) {
10036
- const arr = tree.invisibleViews;
10037
- if (arr === undefined)
10038
- return;
10039
- const slot = this._slot;
10040
- if (slot < arr.length)
10041
- arr[slot] &= ~this._bit;
10042
- }
10043
9998
  // ──────────────────────────────────────────────────────────────────
10044
9999
  // Per-tag, per-view bitmap. Replaces the legacy
10045
10000
  // `tags: WeakMap<ChangeTree, Set<number>>` storage. Hot read site is
@@ -10183,11 +10138,18 @@ class StateView {
10183
10138
  // below use `metadata?.[...]` null-safe access. Only Schema
10184
10139
  // subclasses yield a real Metadata object.
10185
10140
  const metadata = obj.constructor[Symbol.metadata];
10186
- this.markVisible(changeTree);
10187
- // add to iterable list (only the explicitly added items)
10188
- if (this.iterable && checkIncludeParent) {
10141
+ // Add to iterable list (only the explicitly added items), deduping
10142
+ // re-adds of an already-visible instance. isVisible must be read
10143
+ // BEFORE markVisible; indexOf runs only on the re-add path.
10144
+ // NOTE: dedup applies to `items` only — a re-add still re-queues the
10145
+ // full snapshot on purpose (shared-view bootstrap re-add: a
10146
+ // late-attached client may not have consumed earlier drains).
10147
+ // Callers wanting cheap idempotence can guard with `view.has(obj)`.
10148
+ if (this.iterable && checkIncludeParent
10149
+ && (!this.isVisible(changeTree) || this.items.indexOf(obj) === -1)) {
10189
10150
  this.items.push(obj);
10190
10151
  }
10152
+ this.markVisible(changeTree);
10191
10153
  // add parent ChangeTree's
10192
10154
  // - if it was invisible to this view
10193
10155
  // - if it were previously filtered out
@@ -10270,18 +10232,20 @@ class StateView {
10270
10232
  }
10271
10233
  else if (!changeTree.isNew || isChildAdded) {
10272
10234
  // new structures will be added as part of .encode() call, no need to force it to .encodeView()
10273
- const isInvisible = this.isInvisible(changeTree);
10274
10235
  // Full-sync snapshot: walk the live ref structurally instead of
10275
10236
  // iterating a cumulative recorder bucket. Every populated index
10276
10237
  // is emitted as ADD (matching the op-coercion previously done
10277
10238
  // at encode time). Per-field tags come from the descriptor's
10278
10239
  // precomputed `tags[]` array — direct index vs a metadata[i].tag
10279
10240
  // object hop.
10241
+ //
10242
+ // Non-matching custom-tagged fields are NEVER included here —
10243
+ // `view.changes` is drained without a per-field tag re-check,
10244
+ // so anything added leaks straight to the wire.
10280
10245
  const tags = changeTree.encDescriptor.tags;
10281
10246
  changeTree.forEachLive((index) => {
10282
10247
  const tagAtIndex = tags[index];
10283
- if (isInvisible || // if "invisible", include all
10284
- tagAtIndex === undefined || // "all change" with no tag
10248
+ if (tagAtIndex === undefined || // "all change" with no tag
10285
10249
  tagAtIndex === DEFAULT_VIEW_TAG || // visible to all clients
10286
10250
  (tag !== DEFAULT_VIEW_TAG && (tagAtIndex & tag) !== 0) // tag bits overlap
10287
10251
  ) {
@@ -10433,7 +10397,7 @@ class StateView {
10433
10397
  }
10434
10398
  // ── Streamable-collection unsubscribe (the stream itself) ─────
10435
10399
  // Flush DELETE for every sent position and drop pending. After
10436
- // this, the stream is marked invisible to this view — any future
10400
+ // this, the stream is no longer visible to this view — any future
10437
10401
  // `stream.add()` would still seed broadcast pending (if no views)
10438
10402
  // but would NOT re-seed per-view pending (user must re-subscribe).
10439
10403
  if (changeTree.isStreamCollection) {