@colyseus/schema 4.0.26 → 4.0.27

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/schema",
3
- "version": "4.0.26",
3
+ "version": "4.0.27",
4
4
  "description": "Binary state serializer with delta encoding for games",
5
5
  "type": "module",
6
6
  "bin": {
@@ -126,36 +126,23 @@ export class Encoder<T extends Schema = any> {
126
126
  }
127
127
  }
128
128
 
129
- if (it.offset > buffer.byteLength) {
130
- // we can assume that n + 1 BUFFER_SIZE will suffice given that we are likely done with encoding at this point
131
- // multiples of BUFFER_SIZE are faster to allocate than arbitrary sizes
132
- const newSize = Math.ceil(it.offset / Encoder.BUFFER_SIZE) * Encoder.BUFFER_SIZE;
129
+ if (it.offset <= buffer.byteLength) {
130
+ return buffer.subarray(0, it.offset);
131
+ }
132
+
133
+ // Overflowed: grow and re-encode. Reuse the same iterator so `it.offset`
134
+ // ends accurate — a fresh one strands it at the overflow value and
135
+ // corrupts the next view's `viewOffset` in a multi-view encode.
136
+ buffer = this.ensureCapacity(buffer, it.offset);
133
137
 
134
- console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
138
+ console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
135
139
 
136
140
  import { Encoder } from "@colyseus/schema";
137
- Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
141
+ Encoder.BUFFER_SIZE = ${Math.round(buffer.byteLength / 1024)} * 1024; // ${Math.round(buffer.byteLength / 1024)} KB
138
142
  `);
139
143
 
140
- //
141
- // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
142
- // -> No we probably can't unless we catch the need for resize before encoding which is likely more computationally expensive than resizing on demand
143
- //
144
- const newBuffer = new Uint8Array(newSize);
145
- newBuffer.set(buffer); // copy previous encoding steps beyond the initialOffset
146
- buffer = newBuffer;
147
-
148
- // assign resized buffer to local sharedBuffer
149
- if (buffer === this.sharedBuffer) {
150
- this.sharedBuffer = buffer;
151
- }
152
-
153
- return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
154
-
155
- } else {
156
-
157
- return buffer.subarray(0, it.offset);
158
- }
144
+ it.offset = initialOffset;
145
+ return this.encode(it, view, buffer, changeSetName, isEncodeAll);
159
146
  }
160
147
 
161
148
  encodeAll(
@@ -173,8 +160,8 @@ export class Encoder<T extends Schema = any> {
173
160
  ) {
174
161
  const viewOffset = it.offset;
175
162
 
176
- // try to encode "filtered" changes
177
- this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
163
+ // encode() may reallocate the buffer — keep its return, not the stale `bytes`.
164
+ bytes = this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
178
165
 
179
166
  return concatBytes(
180
167
  bytes.subarray(0, sharedOffset),
@@ -182,6 +169,16 @@ export class Encoder<T extends Schema = any> {
182
169
  );
183
170
  }
184
171
 
172
+ /** Grow `buffer` to keep BUFFER_SIZE free bytes past `offset`, preserving `[0, offset)`. */
173
+ protected ensureCapacity(buffer: Uint8Array, offset: number): Uint8Array {
174
+ if (offset + Encoder.BUFFER_SIZE <= buffer.byteLength) { return buffer; }
175
+ const size = Math.ceil((offset + Encoder.BUFFER_SIZE) / Encoder.BUFFER_SIZE) * Encoder.BUFFER_SIZE;
176
+ const grown = new Uint8Array(size);
177
+ grown.set(buffer.subarray(0, offset));
178
+ if (buffer === this.sharedBuffer) { this.sharedBuffer = grown; }
179
+ return grown;
180
+ }
181
+
185
182
  encodeView(
186
183
  view: StateView,
187
184
  sharedOffset: number,
@@ -232,6 +229,10 @@ export class Encoder<T extends Schema = any> {
232
229
  const encoder = ctor[$encoder];
233
230
  const metadata = ctor[Symbol.metadata];
234
231
 
232
+ // These writes are unguarded and unrecoverable (view.changes is cleared
233
+ // below), so unlike encode() they can't re-encode on overflow — grow ahead.
234
+ bytes = this.ensureCapacity(bytes, it.offset);
235
+
235
236
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
236
237
  encode.number(bytes, ref[$refId], it);
237
238
 
@@ -255,8 +256,10 @@ export class Encoder<T extends Schema = any> {
255
256
  view.changes.clear();
256
257
  view.changesOutOfOrder = false;
257
258
 
258
- // try to encode "filtered" changes
259
- this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
259
+ // encode() may reallocate the buffer — keep its return, not the stale `bytes`.
260
+ // Anchor the re-encode at the current offset (default), not `viewOffset`: a
261
+ // resize must not clobber the view.changes already written at [viewOffset, ).
262
+ bytes = this.encode(it, view, bytes, "filteredChanges", false);
260
263
 
261
264
  return concatBytes(
262
265
  bytes.subarray(0, sharedOffset),