murow 0.0.1 → 0.0.3

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.
Files changed (81) hide show
  1. package/README.md +15 -1
  2. package/dist/core/binary-codec/binary-codec.d.ts +30 -0
  3. package/dist/core/binary-codec/binary-codec.js +18 -0
  4. package/dist/core/pooled-codec/pooled-codec.d.ts +88 -16
  5. package/dist/core/pooled-codec/pooled-codec.js +207 -10
  6. package/dist/core/prediction/prediction.d.ts +2 -2
  7. package/dist/core/prediction/prediction.js +13 -7
  8. package/dist/core.esm.js +1 -1
  9. package/dist/core.js +1 -1
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +3 -0
  12. package/dist/net/adapters/browser-websocket.d.ts +24 -0
  13. package/dist/net/adapters/browser-websocket.js +66 -0
  14. package/dist/net/adapters/bun-websocket.d.ts +80 -0
  15. package/dist/net/adapters/bun-websocket.js +232 -0
  16. package/dist/net/buffer-pool.d.ts +49 -0
  17. package/dist/net/buffer-pool.js +89 -0
  18. package/dist/net/client.d.ts +221 -0
  19. package/dist/net/client.js +533 -0
  20. package/dist/net/index.d.ts +57 -0
  21. package/dist/net/index.js +58 -0
  22. package/dist/net/server.d.ts +376 -0
  23. package/dist/net/server.js +937 -0
  24. package/dist/net/types.d.ts +169 -0
  25. package/dist/net/types.js +31 -0
  26. package/dist/net/validators.d.ts +54 -0
  27. package/dist/net/validators.js +88 -0
  28. package/dist/protocol/index.d.ts +54 -5
  29. package/dist/protocol/index.js +54 -5
  30. package/dist/protocol/intent/define-intent.d.ts +132 -0
  31. package/dist/protocol/intent/define-intent.js +125 -0
  32. package/dist/protocol/intent/index.d.ts +56 -2
  33. package/dist/protocol/intent/index.js +55 -2
  34. package/dist/protocol/intent/intent-registry.d.ts +13 -3
  35. package/dist/protocol/intent/intent-registry.js +24 -6
  36. package/dist/protocol/rpc/define-rpc.d.ts +72 -0
  37. package/dist/protocol/rpc/define-rpc.js +84 -0
  38. package/dist/protocol/rpc/index.d.ts +3 -0
  39. package/dist/protocol/rpc/index.js +3 -0
  40. package/dist/protocol/rpc/rpc-registry.d.ts +105 -0
  41. package/dist/protocol/rpc/rpc-registry.js +142 -0
  42. package/dist/protocol/rpc/rpc.d.ts +34 -0
  43. package/dist/protocol/rpc/rpc.js +12 -0
  44. package/dist/protocol/snapshot/snapshot-codec.d.ts +4 -0
  45. package/dist/protocol/snapshot/snapshot-codec.js +13 -2
  46. package/dist/protocol/snapshot/snapshot-registry.d.ts +18 -10
  47. package/dist/protocol/snapshot/snapshot-registry.js +27 -12
  48. package/package.json +2 -2
  49. package/src/core/binary-codec/binary-codec.ts +18 -0
  50. package/src/core/pooled-codec/pooled-codec.test.ts +337 -0
  51. package/src/core/pooled-codec/pooled-codec.ts +286 -21
  52. package/src/core/prediction/prediction.test.ts +8 -7
  53. package/src/core/prediction/prediction.ts +18 -11
  54. package/src/index.ts +4 -0
  55. package/src/net/README.md +470 -0
  56. package/src/net/adapters/browser-websocket.ts +78 -0
  57. package/src/net/adapters/bun-websocket.ts +277 -0
  58. package/src/net/buffer-pool.ts +106 -0
  59. package/src/net/client.test.ts +526 -0
  60. package/src/net/client.ts +634 -0
  61. package/src/net/index.ts +60 -0
  62. package/src/net/server.test.ts +799 -0
  63. package/src/net/server.ts +1115 -0
  64. package/src/net/types.ts +201 -0
  65. package/src/net/validators.ts +104 -0
  66. package/src/protocol/README.md +237 -78
  67. package/src/protocol/index.ts +54 -5
  68. package/src/protocol/intent/define-intent.test.ts +397 -0
  69. package/src/protocol/intent/define-intent.ts +182 -0
  70. package/src/protocol/intent/index.ts +56 -2
  71. package/src/protocol/intent/intent-registry.test.ts +56 -95
  72. package/src/protocol/intent/intent-registry.ts +30 -6
  73. package/src/protocol/rpc/define-rpc.test.ts +141 -0
  74. package/src/protocol/rpc/define-rpc.ts +113 -0
  75. package/src/protocol/rpc/index.ts +3 -0
  76. package/src/protocol/rpc/rpc-registry.test.ts +168 -0
  77. package/src/protocol/rpc/rpc-registry.ts +156 -0
  78. package/src/protocol/rpc/rpc.ts +37 -0
  79. package/src/protocol/snapshot/snapshot-codec.ts +19 -3
  80. package/src/protocol/snapshot/snapshot-registry.test.ts +15 -7
  81. package/src/protocol/snapshot/snapshot-registry.ts +37 -18
package/README.md CHANGED
@@ -35,7 +35,7 @@ import { FixedTicker } from 'murow/core';
35
35
  - `generateId`: Cryptographically secure ID generation
36
36
  - `lerp`: Linear interpolation utility
37
37
  - `NavMesh`: Pathfinding with dynamic obstacles
38
- - `PooledCodec`: Object-pooled binary codec
38
+ - `PooledCodec`: Object-pooled binary codec with array support (via `PooledCodec.array()`) for efficient snapshot encoding. Supports zero-copy encoding with `encodeInto()` for minimal allocations
39
39
  - `IntentTracker` & `Reconciliator`: Client-side prediction
40
40
 
41
41
  ### Protocol Layer
@@ -49,6 +49,20 @@ Works harmoniously with core utilities (`FixedTicker`, `IntentTracker`, `Reconci
49
49
 
50
50
  See [Protocol Layer Documentation](./src/protocol/README.md) for usage.
51
51
 
52
+ ### Network Layer
53
+ Transport-agnostic client/server abstractions:
54
+ - `ServerNetwork`: Multiplayer game server with per-peer snapshot registries
55
+ - `ClientNetwork`: Game client with intent/snapshot handling
56
+ - `TransportAdapter`: Pluggable transport interface
57
+ - `BunWebSocketTransport`: Bun WebSocket implementation (reference)
58
+
59
+ Key features:
60
+ - **Per-peer snapshot registries** for fog of war and interest management
61
+ - **Transport agnostic** - works with WebSocket, WebRTC, UDP, etc.
62
+ - **Type-safe** protocol integration with `IntentRegistry` and `SnapshotRegistry`
63
+
64
+ See [Network Layer Documentation](./src/net/README.md) for usage and [examples/multiplayer-game.ts](./examples/multiplayer-game.ts) for a complete example.
65
+
52
66
  ## Building
53
67
 
54
68
  ```bash
@@ -146,8 +146,38 @@ export declare class BinaryCodec extends BaseBinaryCodec {
146
146
  static readonly u8: Field<number>;
147
147
  /** Unsigned 16-bit integer field */
148
148
  static readonly u16: Field<number>;
149
+ /** Unsigned 32-bit integer field */
150
+ static readonly u32: Field<number>;
151
+ /** Signed 8-bit integer field */
152
+ static readonly i8: Field<number>;
153
+ /** Signed 16-bit integer field */
154
+ static readonly i16: Field<number>;
155
+ /** Signed 32-bit integer field */
156
+ static readonly i32: Field<number>;
149
157
  /** 32-bit floating point field */
150
158
  static readonly f32: Field<number>;
159
+ /** Boolean field */
160
+ static readonly bool: Field<boolean>;
161
+ /** String field with length prefix */
162
+ static string: typeof BinaryPrimitives.string;
163
+ /** 2D vector field */
164
+ static readonly vec2: Field<{
165
+ x: number;
166
+ y: number;
167
+ }>;
168
+ /** 3D vector field */
169
+ static readonly vec3: Field<{
170
+ x: number;
171
+ y: number;
172
+ z: number;
173
+ }>;
174
+ /** RGBA color field */
175
+ static readonly color: Field<{
176
+ r: number;
177
+ g: number;
178
+ b: number;
179
+ a: number;
180
+ }>;
151
181
  /**
152
182
  * Encodes an object into a binary buffer.
153
183
  */
@@ -332,5 +332,23 @@ export class BinaryCodec extends BaseBinaryCodec {
332
332
  BinaryCodec.u8 = BinaryPrimitives.u8;
333
333
  /** Unsigned 16-bit integer field */
334
334
  BinaryCodec.u16 = BinaryPrimitives.u16;
335
+ /** Unsigned 32-bit integer field */
336
+ BinaryCodec.u32 = BinaryPrimitives.u32;
337
+ /** Signed 8-bit integer field */
338
+ BinaryCodec.i8 = BinaryPrimitives.i8;
339
+ /** Signed 16-bit integer field */
340
+ BinaryCodec.i16 = BinaryPrimitives.i16;
341
+ /** Signed 32-bit integer field */
342
+ BinaryCodec.i32 = BinaryPrimitives.i32;
335
343
  /** 32-bit floating point field */
336
344
  BinaryCodec.f32 = BinaryPrimitives.f32;
345
+ /** Boolean field */
346
+ BinaryCodec.bool = BinaryPrimitives.bool;
347
+ /** String field with length prefix */
348
+ BinaryCodec.string = BinaryPrimitives.string;
349
+ /** 2D vector field */
350
+ BinaryCodec.vec2 = BinaryPrimitives.vec2;
351
+ /** 3D vector field */
352
+ BinaryCodec.vec3 = BinaryPrimitives.vec3;
353
+ /** RGBA color field */
354
+ BinaryCodec.color = BinaryPrimitives.color;
@@ -1,4 +1,35 @@
1
- import { Schema } from "../binary-codec";
1
+ import { Schema, Field } from "../binary-codec";
2
+ /**
3
+ * Type marker for array fields in schemas.
4
+ *
5
+ * ArrayField now implements both the Field interface (for use in schemas)
6
+ * and the Codec interface (for standalone use), making it versatile enough
7
+ * to be used directly with SnapshotRegistry or as a field in PooledCodec schemas.
8
+ */
9
+ export type ArrayField<T> = {
10
+ __arrayType?: T[];
11
+ encode(items: T[]): Uint8Array;
12
+ decode(buf: Uint8Array): T[];
13
+ decodeField(buf: Uint8Array): {
14
+ value: T[];
15
+ bytesRead: number;
16
+ };
17
+ toNil(): T[];
18
+ calculateSize(items: T[]): number;
19
+ encodeInto(items: T[], buffer: Uint8Array, offset: number): number;
20
+ };
21
+ /**
22
+ * Extended schema type that supports both regular fields and array fields
23
+ */
24
+ export type ExtendedSchema<T> = {
25
+ [K in keyof T]: T[K] extends any[] ? ArrayField<T[K][number]> : Field<T[K]>;
26
+ };
27
+ /**
28
+ * Infer the type from a schema definition
29
+ */
30
+ type InferSchemaType<S> = {
31
+ [K in keyof S]: S[K] extends ArrayField<infer U> ? U[] : S[K] extends Field<infer V> ? V : unknown;
32
+ };
2
33
  /**
3
34
  * Generic object pool for reusing objects and minimizing allocations.
4
35
  * @template T Type of objects stored in the pool.
@@ -108,33 +139,74 @@ export declare class PooledEncoder<T extends object> {
108
139
  /**
109
140
  * Combined pooled encoder and decoder for a single schema.
110
141
  * Provides a convenient wrapper around PooledEncoder and PooledDecoder.
111
- * @template T Type of object to encode/decode.
142
+ * @template S Schema type
112
143
  */
113
- export declare class PooledCodec<T extends object> {
144
+ export declare class PooledCodec<S extends Record<string, any>> {
145
+ schema: S;
114
146
  /** Pooled encoder for the schema */
115
- encoder: PooledEncoder<T>;
147
+ encoder: PooledEncoder<any>;
116
148
  /** Pooled decoder for the schema */
117
- decoder: PooledDecoder<T>;
149
+ decoder: PooledDecoder<any>;
118
150
  /**
119
151
  * @param schema Schema describing the object structure.
120
- * @param initial Initial object used as a template for pooling decoded objects.
121
152
  */
122
- constructor(schema: Schema<T>);
153
+ constructor(schema: S);
154
+ /**
155
+ * Calculate the size in bytes needed to encode the data.
156
+ * @param data Object to calculate size for.
157
+ * @returns Size in bytes.
158
+ */
159
+ calculateSize(data: InferSchemaType<S>): number;
160
+ /**
161
+ * Encode an object directly into a target buffer at the given offset.
162
+ * This is a zero-copy operation - no intermediate buffers are allocated.
163
+ * @param data Object to encode.
164
+ * @param buffer Target buffer to write into.
165
+ * @param offset Byte offset in the buffer to start writing.
166
+ * @returns Number of bytes written.
167
+ */
168
+ encodeInto(data: InferSchemaType<S>, buffer: Uint8Array, offset: number): number;
123
169
  /**
124
170
  * Encode an object into a pooled buffer.
125
- * @param {T} data Object to encode.
126
- * @returns {Uint8Array} Encoded buffer.
171
+ * @param data Object to encode.
172
+ * @returns Encoded buffer.
127
173
  */
128
- encode(data: T): Uint8Array<ArrayBufferLike>;
174
+ encode(data: InferSchemaType<S>): Uint8Array;
129
175
  /**
130
176
  * Decode a buffer into a pooled object.
131
- * @param {Uint8Array} buf Buffer to decode.
132
- * @returns {T} Decoded object.
177
+ * @param buf Buffer to decode.
178
+ * @returns Decoded object.
133
179
  */
134
- decode(buf: Uint8Array): T;
180
+ decode(buf: Uint8Array): InferSchemaType<S>;
135
181
  /**
136
182
  * Release a decoded object back to the pool.
137
- * @param {T} obj Object to release.
138
- */
139
- release(obj: T): void;
183
+ * @param obj Object to release.
184
+ */
185
+ release(obj: InferSchemaType<S>): void;
186
+ /**
187
+ * Creates an array field descriptor for use in schemas.
188
+ * Encodes array length as u16 followed by each item.
189
+ *
190
+ * @template U Type of items in the array
191
+ * @param itemSchema Schema for individual array items
192
+ * @returns An array field descriptor that can be used in PooledCodec schemas
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * const PlayerSchema = {
197
+ * entityId: BinaryPrimitives.u32,
198
+ * x: BinaryPrimitives.f32,
199
+ * y: BinaryPrimitives.f32,
200
+ * };
201
+ *
202
+ * const UpdateSchema = {
203
+ * tick: BinaryPrimitives.u32,
204
+ * players: PooledCodec.array(PlayerSchema),
205
+ * };
206
+ *
207
+ * const codec = new PooledCodec(UpdateSchema);
208
+ * ```
209
+ */
210
+ static array<U extends object>(itemSchema: Schema<U> | Record<string, any>): ArrayField<U>;
140
211
  }
212
+ export {};
@@ -70,16 +70,31 @@ export class PooledDecoder {
70
70
  * @param {T} target Object to write decoded data into.
71
71
  */
72
72
  decodeInto(buf, target) {
73
+ let offset = 0;
73
74
  for (const key of Object.keys(this.schema)) {
74
75
  const field = this.schema[key];
75
76
  if ("decodeAll" in field) {
76
- target[key] = field.decodeAll(buf);
77
+ const result = field.decodeAll(buf.subarray(offset));
78
+ target[key] = result.value;
79
+ offset += result.bytesRead;
80
+ }
81
+ else if ("decodeField" in field) {
82
+ // Use decodeField for ArrayField (returns { value, bytesRead })
83
+ const result = field.decodeField(buf.subarray(offset));
84
+ target[key] = result.value;
85
+ offset += result.bytesRead;
77
86
  }
78
87
  else if ("decode" in field) {
79
- target[key] = field.decode(buf);
88
+ const result = field.decode(buf.subarray(offset));
89
+ target[key] = result.value;
90
+ offset += result.bytesRead;
80
91
  }
81
92
  else {
82
- BinaryCodec.decodeInto({ [key]: field }, buf, target);
93
+ // For primitive fields, calculate size and decode
94
+ const fieldSize = field.size || 0;
95
+ const fieldBuf = buf.subarray(offset, offset + fieldSize);
96
+ BinaryCodec.decodeInto({ [key]: field }, fieldBuf, target);
97
+ offset += fieldSize;
83
98
  }
84
99
  }
85
100
  }
@@ -176,38 +191,220 @@ export class PooledEncoder {
176
191
  /**
177
192
  * Combined pooled encoder and decoder for a single schema.
178
193
  * Provides a convenient wrapper around PooledEncoder and PooledDecoder.
179
- * @template T Type of object to encode/decode.
194
+ * @template S Schema type
180
195
  */
181
196
  export class PooledCodec {
182
197
  /**
183
198
  * @param schema Schema describing the object structure.
184
- * @param initial Initial object used as a template for pooling decoded objects.
185
199
  */
186
200
  constructor(schema) {
201
+ this.schema = schema;
187
202
  this.encoder = new PooledEncoder(schema);
188
203
  this.decoder = new PooledDecoder(schema);
189
204
  }
205
+ /**
206
+ * Calculate the size in bytes needed to encode the data.
207
+ * @param data Object to calculate size for.
208
+ * @returns Size in bytes.
209
+ */
210
+ calculateSize(data) {
211
+ let size = 0;
212
+ for (const key of Object.keys(this.schema)) {
213
+ const field = this.schema[key];
214
+ if ("size" in field) {
215
+ // Fixed-size primitive field
216
+ size += field.size;
217
+ }
218
+ else if ("calculateSize" in field) {
219
+ // Variable-size field (like arrays)
220
+ size += field.calculateSize(data[key]);
221
+ }
222
+ }
223
+ return size;
224
+ }
225
+ /**
226
+ * Encode an object directly into a target buffer at the given offset.
227
+ * This is a zero-copy operation - no intermediate buffers are allocated.
228
+ * @param data Object to encode.
229
+ * @param buffer Target buffer to write into.
230
+ * @param offset Byte offset in the buffer to start writing.
231
+ * @returns Number of bytes written.
232
+ */
233
+ encodeInto(data, buffer, offset) {
234
+ const view = new DataView(buffer.buffer, buffer.byteOffset);
235
+ let currentOffset = offset;
236
+ for (const key of Object.keys(this.schema)) {
237
+ const field = this.schema[key];
238
+ if ("write" in field) {
239
+ // Primitive field - direct write
240
+ field.write(view, currentOffset, data[key]);
241
+ currentOffset += field.size;
242
+ }
243
+ else if ("encodeInto" in field) {
244
+ // Array or nested field - delegate to its encodeInto
245
+ const bytesWritten = field.encodeInto(data[key], buffer, currentOffset);
246
+ currentOffset += bytesWritten;
247
+ }
248
+ else if ("encode" in field) {
249
+ // Fallback for fields that only have encode()
250
+ const nested = field.encode(data[key]);
251
+ buffer.set(nested, currentOffset);
252
+ currentOffset += nested.length;
253
+ }
254
+ }
255
+ return currentOffset - offset; // bytes written
256
+ }
190
257
  /**
191
258
  * Encode an object into a pooled buffer.
192
- * @param {T} data Object to encode.
193
- * @returns {Uint8Array} Encoded buffer.
259
+ * @param data Object to encode.
260
+ * @returns Encoded buffer.
194
261
  */
195
262
  encode(data) {
196
263
  return this.encoder.encode(data);
197
264
  }
198
265
  /**
199
266
  * Decode a buffer into a pooled object.
200
- * @param {Uint8Array} buf Buffer to decode.
201
- * @returns {T} Decoded object.
267
+ * @param buf Buffer to decode.
268
+ * @returns Decoded object.
202
269
  */
203
270
  decode(buf) {
204
271
  return this.decoder.decode(buf);
205
272
  }
206
273
  /**
207
274
  * Release a decoded object back to the pool.
208
- * @param {T} obj Object to release.
275
+ * @param obj Object to release.
209
276
  */
210
277
  release(obj) {
211
278
  this.decoder.release(obj);
212
279
  }
280
+ /**
281
+ * Creates an array field descriptor for use in schemas.
282
+ * Encodes array length as u16 followed by each item.
283
+ *
284
+ * @template U Type of items in the array
285
+ * @param itemSchema Schema for individual array items
286
+ * @returns An array field descriptor that can be used in PooledCodec schemas
287
+ *
288
+ * @example
289
+ * ```ts
290
+ * const PlayerSchema = {
291
+ * entityId: BinaryPrimitives.u32,
292
+ * x: BinaryPrimitives.f32,
293
+ * y: BinaryPrimitives.f32,
294
+ * };
295
+ *
296
+ * const UpdateSchema = {
297
+ * tick: BinaryPrimitives.u32,
298
+ * players: PooledCodec.array(PlayerSchema),
299
+ * };
300
+ *
301
+ * const codec = new PooledCodec(UpdateSchema);
302
+ * ```
303
+ */
304
+ static array(itemSchema) {
305
+ // Calculate item size once
306
+ let itemSize = 0;
307
+ for (const key of Object.keys(itemSchema)) {
308
+ const field = itemSchema[key];
309
+ itemSize += field.size || 0;
310
+ }
311
+ // Pool for encoding buffers (larger initial size to avoid allocations)
312
+ const bufferPool = new ObjectPool(() => new Uint8Array(16384));
313
+ // Pool for decoded objects to avoid allocations
314
+ const objectPool = new ObjectPool(() => {
315
+ const obj = {};
316
+ for (const key of Object.keys(itemSchema)) {
317
+ const field = itemSchema[key];
318
+ obj[key] = ("toNil" in field ? field.toNil() : undefined);
319
+ }
320
+ return obj;
321
+ });
322
+ // Pool for result arrays to avoid allocations
323
+ const arrayPool = new ObjectPool(() => []);
324
+ // Reusable DataView for encoding
325
+ let encodeView = null;
326
+ // Reusable DataView for decoding
327
+ let decodeView = null;
328
+ return {
329
+ __arrayType: undefined,
330
+ calculateSize(items) {
331
+ return 2 + (items.length * itemSize);
332
+ },
333
+ encodeInto(items, buffer, offset) {
334
+ const view = new DataView(buffer.buffer, buffer.byteOffset);
335
+ // Write array length
336
+ view.setUint16(offset, items.length, false);
337
+ let currentOffset = offset + 2;
338
+ // Write each item directly into buffer
339
+ for (const item of items) {
340
+ for (const key of Object.keys(itemSchema)) {
341
+ const field = itemSchema[key];
342
+ field.write(view, currentOffset, item[key]);
343
+ currentOffset += field.size;
344
+ }
345
+ }
346
+ return currentOffset - offset; // bytes written
347
+ },
348
+ encode(items) {
349
+ const totalSize = 2 + (items.length * itemSize);
350
+ let buffer = bufferPool.acquire();
351
+ // Grow buffer pool if needed (but keep the undersized buffer for next time)
352
+ if (buffer.length < totalSize) {
353
+ bufferPool.release(buffer);
354
+ buffer = new Uint8Array(Math.max(totalSize, buffer.length * 2));
355
+ }
356
+ // Create or reuse DataView for this buffer
357
+ if (!encodeView || encodeView.buffer !== buffer.buffer) {
358
+ encodeView = new DataView(buffer.buffer, buffer.byteOffset);
359
+ }
360
+ // Write array length
361
+ encodeView.setUint16(0, items.length, false);
362
+ // Write each item directly into buffer (zero intermediate allocations)
363
+ let offset = 2;
364
+ for (const item of items) {
365
+ for (const key of Object.keys(itemSchema)) {
366
+ const field = itemSchema[key];
367
+ field.write(encodeView, offset, item[key]);
368
+ offset += field.size;
369
+ }
370
+ }
371
+ // Create a copy to return (caller owns this memory)
372
+ const result = new Uint8Array(offset);
373
+ result.set(buffer.subarray(0, offset));
374
+ // Return buffer to pool
375
+ bufferPool.release(buffer);
376
+ return result;
377
+ },
378
+ decodeField(buf) {
379
+ // Read array length directly from buffer
380
+ const length = (buf[0] << 8) | buf[1];
381
+ // Acquire pooled array and resize if needed
382
+ const items = arrayPool.acquire();
383
+ items.length = length;
384
+ // Create or reuse DataView for reading
385
+ if (!decodeView || decodeView.buffer !== buf.buffer || decodeView.byteOffset !== buf.byteOffset) {
386
+ decodeView = new DataView(buf.buffer, buf.byteOffset);
387
+ }
388
+ // Read each item using pooled objects
389
+ let offset = 2;
390
+ for (let i = 0; i < length; i++) {
391
+ const item = objectPool.acquire();
392
+ // Decode directly into pooled object
393
+ for (const key of Object.keys(itemSchema)) {
394
+ const field = itemSchema[key];
395
+ item[key] = field.read(decodeView, offset);
396
+ offset += field.size;
397
+ }
398
+ items[i] = item;
399
+ }
400
+ return { value: items, bytesRead: offset };
401
+ },
402
+ decode(buf) {
403
+ return this.decodeField(buf).value;
404
+ },
405
+ toNil() {
406
+ return [];
407
+ }
408
+ };
409
+ }
213
410
  }
@@ -5,7 +5,7 @@
5
5
  * Used for prediction and reconciliation in a server-authoritative architecture.
6
6
  */
7
7
  export declare class IntentTracker<T> {
8
- private tracker;
8
+ tracker: Map<number, T[]>;
9
9
  get size(): number;
10
10
  /**
11
11
  * Adds a new intent for a specific tick.
@@ -34,7 +34,7 @@ export declare class IntentTracker<T> {
34
34
  */
35
35
  export declare class Reconciliator<T, U> {
36
36
  private options;
37
- private tracker;
37
+ tracker: IntentTracker<T>;
38
38
  /**
39
39
  * @param {Object} options - Callbacks for applying snapshot state and replaying intents.
40
40
  * @param {(snapshotState: U) => void} options.onLoadState - Called to load authoritative snapshot state.
@@ -17,7 +17,10 @@ export class IntentTracker {
17
17
  * @param {T} intent - The intent data.
18
18
  */
19
19
  track(tick, intent) {
20
- this.tracker.set(tick, intent);
20
+ if (!this.tracker.has(tick)) {
21
+ this.tracker.set(tick, []);
22
+ }
23
+ this.tracker.get(tick).push(intent);
21
24
  return intent;
22
25
  }
23
26
  /**
@@ -28,15 +31,15 @@ export class IntentTracker {
28
31
  */
29
32
  dropUpTo(tick) {
30
33
  const remaining = [];
31
- for (const [t, intent] of this.tracker) {
34
+ for (const [t, intents] of this.tracker) {
32
35
  if (t <= tick)
33
36
  this.tracker.delete(t);
34
37
  else
35
- remaining.push([t, intent]);
38
+ remaining.push([t, intents]);
36
39
  }
37
40
  // sort by tick ascending
38
41
  remaining.sort(([a], [b]) => a - b);
39
- return remaining.map(([_, intent]) => intent);
42
+ return remaining.map(([_, intents]) => intents).flat();
40
43
  }
41
44
  /**
42
45
  * Returns all currently tracked intents in ascending tick order.
@@ -45,7 +48,8 @@ export class IntentTracker {
45
48
  values() {
46
49
  return Array.from(this.tracker.entries())
47
50
  .sort(([a], [b]) => a - b)
48
- .map(([_, intent]) => intent);
51
+ .map(([_, intents]) => intents)
52
+ .flat();
49
53
  }
50
54
  }
51
55
  /**
@@ -84,7 +88,9 @@ export class Reconciliator {
84
88
  this.options.onLoadState(snapshot.state);
85
89
  // 2. Remove confirmed intents and get remaining
86
90
  const remainingIntents = this.tracker.dropUpTo(snapshot.tick);
87
- // 3. Replay remaining intents for prediction
88
- this.options.onReplay(remainingIntents);
91
+ // 3. Only replay if there are actually remaining intents
92
+ if (remainingIntents.length > 0) {
93
+ this.options.onReplay(remainingIntents);
94
+ }
89
95
  }
90
96
  }
package/dist/core.esm.js CHANGED
@@ -1 +1 @@
1
- var U=Symbol("schemaSize");function V(i){let e=i[U];if(e!==void 0)return e;let t=0;for(let s of Object.keys(i))t+=i[s].size;return i[U]=t,t}var w=class{static encodeInto(e,t){let s=V(e),n=new ArrayBuffer(s),c=new DataView(n),r=0;for(let o of Object.keys(e)){let a=e[o];a.write(c,r,t[o]),r+=a.size}return new Uint8Array(n)}static decodeInto(e,t,s){let n=V(e);if(t.byteLength<n)throw new RangeError(`Buffer too small: expected ${n} bytes, got ${t.byteLength}`);let c=new DataView(t.buffer,t.byteOffset,t.byteLength),r=0;for(let o of Object.keys(e)){let a=e[o];s[o]=a.read(c,r),r+=a.size}return s}},y=class{static{this.u8={size:1,write:(e,t,s)=>e.setUint8(t,s),read:(e,t)=>e.getUint8(t),toNil:()=>0}}static{this.u16={size:2,write:(e,t,s)=>e.setUint16(t,s,!1),read:(e,t)=>e.getUint16(t,!1),toNil:()=>0}}static{this.u32={size:4,write:(e,t,s)=>e.setUint32(t,s,!1),read:(e,t)=>e.getUint32(t,!1),toNil:()=>0}}static{this.i8={size:1,write:(e,t,s)=>e.setInt8(t,s),read:(e,t)=>e.getInt8(t),toNil:()=>0}}static{this.i16={size:2,write:(e,t,s)=>e.setInt16(t,s,!1),read:(e,t)=>e.getInt16(t,!1),toNil:()=>0}}static{this.i32={size:4,write:(e,t,s)=>e.setInt32(t,s,!1),read:(e,t)=>e.getInt32(t,!1),toNil:()=>0}}static{this.f32={size:4,write:(e,t,s)=>e.setFloat32(t,s,!1),read:(e,t)=>e.getFloat32(t,!1),toNil:()=>0}}static{this.f64={size:8,write:(e,t,s)=>e.setFloat64(t,s,!1),read:(e,t)=>e.getFloat64(t,!1),toNil:()=>0}}static{this.bool={size:1,write:(e,t,s)=>e.setUint8(t,s?1:0),read:(e,t)=>e.getUint8(t)!==0,toNil:()=>!1}}static string(e){return{size:e+2,write(t,s,n){let r=new TextEncoder().encode(n);if(r.length>e)throw new RangeError(`String too long, max ${e} bytes`);t.setUint16(s,r.length,!1);for(let o=0;o<r.length;o++)t.setUint8(s+2+o,r[o]);for(let o=r.length;o<e;o++)t.setUint8(s+2+o,0)},read(t,s){let n=t.getUint16(s,!1),c=new Uint8Array(n);for(let r=0;r<n;r++)c[r]=t.getUint8(s+2+r);return new TextDecoder().decode(c)},toNil:()=>""}}static{this.vec2={size:8,write(e,t,s){e.setFloat32(t,s.x,!1),e.setFloat32(t+4,s.y,!1)},read(e,t){return{x:e.getFloat32(t,!1),y:e.getFloat32(t+4,!1)}},toNil:()=>({x:0,y:0})}}static{this.vec3={size:12,write(e,t,s){e.setFloat32(t,s.x,!1),e.setFloat32(t+4,s.y,!1),e.setFloat32(t+8,s.z,!1)},read(e,t){return{x:e.getFloat32(t,!1),y:e.getFloat32(t+4,!1),z:e.getFloat32(t+8,!1)}},toNil:()=>({x:0,y:0,z:0})}}static{this.color={size:4,write(e,t,s){e.setUint8(t,s.r),e.setUint8(t+1,s.g),e.setUint8(t+2,s.b),e.setUint8(t+3,s.a)},read(e,t){return{r:e.getUint8(t),g:e.getUint8(t+1),b:e.getUint8(t+2),a:e.getUint8(t+3)}},toNil:()=>({r:0,g:0,b:0,a:0})}}static{this.f32_le={size:4,write:(e,t,s)=>e.setFloat32(t,s,!0),read:(e,t)=>e.getFloat32(t,!0),toNil:()=>0}}static{this.f64_le={size:8,write:(e,t,s)=>e.setFloat64(t,s,!0),read:(e,t)=>e.getFloat64(t,!0),toNil:()=>0}}static{this.u16_le={size:2,write:(e,t,s)=>e.setUint16(t,s,!0),read:(e,t)=>e.getUint16(t,!0),toNil:()=>0}}static{this.u32_le={size:4,write:(e,t,s)=>e.setUint32(t,s,!0),read:(e,t)=>e.getUint32(t,!0),toNil:()=>0}}static{this.i16_le={size:2,write:(e,t,s)=>e.setInt16(t,s,!0),read:(e,t)=>e.getInt16(t,!0),toNil:()=>0}}static{this.i32_le={size:4,write:(e,t,s)=>e.setInt32(t,s,!0),read:(e,t)=>e.getInt32(t,!0),toNil:()=>0}}static{this.vec2_le={size:8,write:(e,t,s)=>{e.setFloat32(t,s[0],!0),e.setFloat32(t+4,s[1],!0)},read:(e,t)=>[e.getFloat32(t,!0),e.getFloat32(t+4,!0)],toNil:()=>[0,0]}}static{this.vec3_le={size:12,write:(e,t,s)=>{e.setFloat32(t,s[0],!0),e.setFloat32(t+4,s[1],!0),e.setFloat32(t+8,s[2],!0)},read:(e,t)=>[e.getFloat32(t,!0),e.getFloat32(t+4,!0),e.getFloat32(t+8,!0)],toNil:()=>[0,0,0]}}static{this.vec4_le={size:16,write:(e,t,s)=>{e.setFloat32(t,s[0],!0),e.setFloat32(t+4,s[1],!0),e.setFloat32(t+8,s[2],!0),e.setFloat32(t+12,s[3],!0)},read:(e,t)=>[e.getFloat32(t,!0),e.getFloat32(t+4,!0),e.getFloat32(t+8,!0),e.getFloat32(t+12,!0)],toNil:()=>[0,0,0,0]}}},x=class extends w{static{this.u8=y.u8}static{this.u16=y.u16}static{this.f32=y.f32}static encode(e,t){return this.encodeInto(e,t)}static decode(e,t,s){return this.decodeInto(e,t,s)}};var C=class{constructor({events:e}){this.callbacks=new Map,this.events=e;for(let t of this.events)this.callbacks.set(t,new Set)}on(e,t){let s=this.callbacks.get(e);if(!s)return console.warn(`Event "${e}" does not exist.`);s.add(t)}once(e,t){let s=n=>{t(n),this.off(e,s)};this.on(e,s)}emit(e,t){let s=this.callbacks.get(e);if(!s)return console.warn(`Event "${e}" does not exist.`);for(let n of s)n(t)}off(e,t){let s=this.callbacks.get(e);if(!s)return console.warn(`Event "${e}" does not exist.`);s.delete(t)}clear(e){if(!e){this.callbacks.clear();for(let s of this.events)this.callbacks.set(s,new Set);return}let t=this.callbacks.get(e);if(!t)return console.warn(`Event "${e}" does not exist.`);t.clear()}};var A=class{constructor({rate:e,onTick:t}){this.accumulator=0;this._tickCount=0;this.rate=e,this.intervalMs=1e3/this.rate,this.onTick=t,this.maxTicksPerFrame=Math.max(1,Math.floor(e/2))}getTicks(e){this.accumulator+=e*1e3;let t=0;for(;this.accumulator>=this.intervalMs&&t<this.maxTicksPerFrame;)this.accumulator-=this.intervalMs,t++;let s=Math.floor(this.accumulator/this.intervalMs);return s>0&&this.onTickSkipped&&this.onTickSkipped(s),t}tick(e){let t=this.getTicks(e);for(let s=0;s<t;s++)this.onTick(1/this.rate,this._tickCount++)}get tickCount(){return this._tickCount}resetTickCount(){this._tickCount=0}get accumulatedTime(){return this.accumulator/1e3}};function re(i={}){let{prefix:e="",size:t=16}=i,s=Math.max(t-e.length,8),n=Math.ceil(s/8),r=crypto.getRandomValues(new Uint32Array(n)).reduce((o,a)=>o+a.toString(16).padStart(8,"0"),"");return r=r.slice(0,s).padStart(s,"0"),`${e}${r}`}function ce(i,e,t){return i+(e-i)*t}var B=[{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}],T=i=>({x:Math.floor(i.x),y:Math.floor(i.y)}),X=i=>({x:i.x+.5,y:i.y+.5}),L=(()=>{let i=1;return()=>i++})(),m=(i,e)=>i&65535|(e&65535)<<16,F=i=>({x:i<<16>>16,y:i>>16}),M=class{constructor(e){this.scoreFn=e;this.heap=[]}push(e){this.heap.push(e),this.bubbleUp(this.heap.length-1)}pop(){let e=this.heap[0],t=this.heap.pop();return this.heap.length>0&&t!==void 0&&(this.heap[0]=t,this.sinkDown(0)),e}get size(){return this.heap.length}bubbleUp(e){let t=this.heap[e],s=this.scoreFn(t);for(;e>0;){let n=(e+1>>1)-1,c=this.heap[n];if(s>=this.scoreFn(c))break;this.heap[n]=t,this.heap[e]=c,e=n}}sinkDown(e){let t=this.heap.length,s=this.heap[e],n=this.scoreFn(s);for(;;){let c=e+1<<1,r=c-1,o=null,a;if(r<t){let l=this.heap[r];a=this.scoreFn(l),a<n&&(o=r)}if(c<t){let l=this.heap[c];this.scoreFn(l)<(o===null?n:a)&&(o=c)}if(o===null)break;this.heap[e]=this.heap[o],this.heap[o]=s,e=o}}},z=class{constructor(e=1){this.grid=new Map;this.obstacleCells=new Map;this.cellSize=e}hash(e,t){let s=Math.floor(e/this.cellSize),n=Math.floor(t/this.cellSize);return m(s,n)}add(e,t){let s=this.getCellsForObstacle(t);for(let n of s)this.grid.has(n)||this.grid.set(n,new Set),this.grid.get(n).add(e);this.obstacleCells.set(e,s)}remove(e){let t=this.obstacleCells.get(e);if(t){for(let s of t){let n=this.grid.get(s);n&&(n.delete(e),n.size===0&&this.grid.delete(s))}this.obstacleCells.delete(e)}}query(e){let t=this.hash(e.x,e.y);return this.grid.get(t)||new Set}clear(){this.grid.clear(),this.obstacleCells.clear()}getCellsForObstacle(e){let t=new Set;if(e.type==="circle"){let s=e.radius,n=Math.floor((e.pos.x-s)/this.cellSize),c=Math.floor((e.pos.x+s)/this.cellSize),r=Math.floor((e.pos.y-s)/this.cellSize),o=Math.floor((e.pos.y+s)/this.cellSize);for(let a=n;a<=c;a++)for(let l=r;l<=o;l++)t.add(m(a,l))}else if(e.type==="rect"){let s=e.pos.x+e.size.x/2,n=e.pos.y+e.size.y/2,c=e.size.x/2,r=e.size.y/2,o=Math.sqrt(c*c+r*r),a=Math.floor((s-o)/this.cellSize),l=Math.floor((s+o)/this.cellSize),h=Math.floor((n-o)/this.cellSize),u=Math.floor((n+o)/this.cellSize);for(let d=a;d<=l;d++)for(let p=h;p<=u;p++)t.add(m(d,p))}else if(e.type==="polygon"){let s=R(e),n=Math.floor(s.minX/this.cellSize),c=Math.floor(s.maxX/this.cellSize),r=Math.floor(s.minY/this.cellSize),o=Math.floor(s.maxY/this.cellSize);for(let a=n;a<=c;a++)for(let l=r;l<=o;l++)t.add(m(a,l))}return t}};function Y(i,e){let t=i.x-e.pos.x,s=i.y-e.pos.y;return t*t+s*s<=e.radius*e.radius}function j(i,e){let t=e.pos.x+e.size.x/2,s=e.pos.y+e.size.y/2;if(e.rotation){let n=Math.cos(-e.rotation),c=Math.sin(-e.rotation),r=i.x-t,o=i.y-s,a=r*n-o*c,l=r*c+o*n;return Math.abs(a)<=e.size.x/2&&Math.abs(l)<=e.size.y/2}return i.x>=e.pos.x&&i.y>=e.pos.y&&i.x<=e.pos.x+e.size.x&&i.y<=e.pos.y+e.size.y}function _(i,e){let t=!1,s=e.points,n=e.rotation?Math.cos(e.rotation):1,c=e.rotation?Math.sin(e.rotation):0;for(let r=0,o=s.length-1;r<s.length;o=r++){let a=s[r].x,l=s[r].y,h=s[o].x,u=s[o].y;if(e.rotation){let p=a*n-l*c,f=a*c+l*n,b=h*n-u*c,g=h*c+u*n;a=p,l=f,h=b,u=g}a+=e.pos.x,l+=e.pos.y,h+=e.pos.x,u+=e.pos.y,l>i.y!=u>i.y&&i.x<(h-a)*(i.y-l)/(u-l)+a&&(t=!t)}return t}function R(i){let e=1/0,t=1/0,s=-1/0,n=-1/0,c=i.rotation?Math.cos(i.rotation):1,r=i.rotation?Math.sin(i.rotation):0;for(let o of i.points){let a=o.x,l=o.y;if(i.rotation){let h=a*c-l*r,u=a*r+l*c;a=h,l=u}a+=i.pos.x,l+=i.pos.y,e=Math.min(e,a),t=Math.min(t,l),s=Math.max(s,a),n=Math.max(n,l)}return{minX:e,minY:t,maxX:s,maxY:n}}var O=class{constructor(){this.items=new Map;this.spatial=new z(1);this._cachedItems=[];this.dirty=!0;this.version=0}add(e){let t=L(),s={...e,id:t};return this.items.set(t,s),this.spatial.add(t,s),this.dirty=!0,this.version++,t}move(e,t){let s=this.items.get(e);if(!s)return;this.spatial.remove(e);let n={...s,pos:{...t}};this.items.set(e,n),this.spatial.add(e,n),this.dirty=!0,this.version++}remove(e){this.spatial.remove(e),this.items.delete(e),this.dirty=!0,this.version++}at(e){let t=this.spatial.query(e);for(let s of t){let n=this.items.get(s);if(!(!n||n.solid===!1)&&(n.type==="circle"&&Y(e,n)||n.type==="rect"&&j(e,n)||n.type==="polygon"&&_(e,n)))return n}}get values(){return this.dirty?(this._cachedItems=[...this.items.values()],this.dirty=!1,this._cachedItems):this._cachedItems}},S=class{constructor(e){this.obstacles=e;this.blocked=new Set}rebuild(){this.blocked.clear();for(let e of this.obstacles.values)if(e.solid!==!1){if(e.type==="circle"){let t=Math.ceil(e.radius),s=Math.floor(e.pos.x),n=Math.floor(e.pos.y);for(let c=-t;c<=t;c++)for(let r=-t;r<=t;r++){let o=s+c,a=n+r,l={x:o+.5,y:a+.5};Y(l,e)&&this.blocked.add(m(o,a))}}else if(e.type==="rect"){let t=e.pos.x+e.size.x/2,s=e.pos.y+e.size.y/2,n=e.size.x/2,c=e.size.y/2,r=Math.sqrt(n*n+c*c),o=Math.floor(t-r),a=Math.ceil(t+r),l=Math.floor(s-r),h=Math.ceil(s+r);for(let u=o;u<=a;u++)for(let d=l;d<=h;d++){let p={x:u+.5,y:d+.5};j(p,e)&&this.blocked.add(m(u,d))}}else if(e.type==="polygon"){let t=R(e),s=Math.floor(t.minX),n=Math.ceil(t.maxX),c=Math.floor(t.minY),r=Math.ceil(t.maxY);for(let o=s;o<=n;o++)for(let a=c;a<=r;a++){let l={x:o+.5,y:a+.5};_(l,e)&&this.blocked.add(m(o,a))}}}}findPath(e,t){return D(T(e),T(t),(s,n)=>!this.blocked.has(m(s,n))).map(X)}},E=class{constructor(e){this.obstacles=e}rebuild(){}findPath(e,t){let s=Math.ceil(Math.hypot(t.x-e.x,t.y-e.y)*2),n=!1;for(let r=1;r<=s;r++){let o=r/s,a={x:e.x+(t.x-e.x)*o,y:e.y+(t.y-e.y)*o};if(this.obstacles.at(a)){n=!0;break}}return n?D(T(e),T(t),(r,o)=>{let a={x:r+.5,y:o+.5};return!this.obstacles.at(a)}).map(X):[e,t]}},P=class{constructor(e){this.type=e;this.lastVersion=-1;this.obstacles=new O,e==="grid"&&(this.grid=new S(this.obstacles)),e==="graph"&&(this.graph=new E(this.obstacles))}addObstacle(e){return this.obstacles.add(e)}moveObstacle(e,t){this.obstacles.move(e,t)}removeObstacle(e){this.obstacles.remove(e)}getObstacles(){return this.obstacles.values}findPath({from:e,to:t}){return this.rebuild(),this.type==="grid"?this.grid.findPath(e,t):this.graph.findPath(e,t)}rebuild(){this.lastVersion!==this.obstacles.version&&(this.grid?.rebuild(),this.graph?.rebuild(),this.lastVersion=this.obstacles.version)}};function D(i,e,t){let s=new Map,n=new Map,c=new Set,r=new Set,o=u=>m(u.x,u.y),a=(u,d)=>Math.abs(u.x-d.x)+Math.abs(u.y-d.y),l=new M(u=>{let d=F(u);return n.get(u)+a(d,e)}),h=o(i);for(n.set(h,0),l.push(h),r.add(h);l.size>0;){let u=l.pop();r.delete(u);let d=F(u);if(d.x===e.x&&d.y===e.y)return G(s,d);c.add(u);for(let p of B){let f={x:d.x+p.x,y:d.y+p.y};if(!t(f.x,f.y))continue;let b=o(f);if(c.has(b))continue;let g=n.get(u)+1;g<(n.get(b)??1/0)&&(n.set(b,g),s.set(b,u),r.has(b)||(l.push(b),r.add(b)))}}return[]}function G(i,e){let t=[e],s=m(e.x,e.y);for(;i.has(s);)s=i.get(s),t.push(F(s));return t.reverse()}var k=class{constructor(e){this.factory=e;this.pool=[]}acquire(){return this.pool.pop()??this.factory()}release(e){this.pool.push(e)}releaseAll(e){this.pool.push(...e)}},v=class{constructor(e){this.schema=e;this.pool=new k(()=>this.createNil())}createNil(){let e={};for(let t of Object.keys(this.schema)){let s=this.schema[t];e[t]="toNil"in s?s.toNil():void 0}return e}decode(e){let t=this.pool.acquire();return this.decodeInto(e,t),t}decodeInto(e,t){for(let s of Object.keys(this.schema)){let n=this.schema[s];"decodeAll"in n?t[s]=n.decodeAll(e):"decode"in n?t[s]=n.decode(e):x.decodeInto({[s]:n},e,t)}}release(e){this.pool.release(e)}},$=class{constructor(e){this.pooledDecoder=new v(e)}decodeAll(e){return e.map(t=>this.pooledDecoder.decode(t))}releaseAll(e){e.forEach(t=>this.pooledDecoder.release(t))}},I=class{constructor(e,t=1024){this.schema=e;this.bufferSize=t;this.pool=new k(()=>new Uint8Array(t))}encode(e){let t=this.pool.acquire(),s=0;for(let n of Object.keys(this.schema)){let c=this.schema[n];if("encode"in c){let r=c.encode(e[n]);t.set(r,s),s+=r.length}else if("encodeAll"in c){let r=c.encodeAll(e[n]),o=0;for(let a of r)t.set(a,s+o),o+=a.length;s+=o}else{let r=x.encode({[n]:c},{[n]:e[n]});t.set(r,s),s+=r.length}}return t.subarray(0,s)}release(e){this.pool.release(e)}},q=class{constructor(e){this.encoder=new I(e),this.decoder=new v(e)}encode(e){return this.encoder.encode(e)}decode(e){return this.decoder.decode(e)}release(e){this.decoder.release(e)}};var N=class{constructor(){this.tracker=new Map}get size(){return this.tracker.size}track(e,t){return this.tracker.set(e,t),t}dropUpTo(e){let t=[];for(let[s,n]of this.tracker)s<=e?this.tracker.delete(s):t.push([s,n]);return t.sort(([s],[n])=>s-n),t.map(([s,n])=>n)}values(){return Array.from(this.tracker.entries()).sort(([e],[t])=>e-t).map(([e,t])=>t)}},K=class{constructor(e){this.options=e;this.tracker=new N}trackIntent(e,t){this.tracker.track(e,t)}onSnapshot(e){this.options.onLoadState(e.state);let t=this.tracker.dropUpTo(e.tick);this.options.onReplay(t)}};export{w as BaseBinaryCodec,x as BinaryCodec,y as BinaryPrimitives,C as EventSystem,A as FixedTicker,N as IntentTracker,P as NavMesh,k as ObjectPool,$ as PooledArrayDecoder,q as PooledCodec,v as PooledDecoder,I as PooledEncoder,K as Reconciliator,re as generateId,ce as lerp};
1
+ var N=Symbol("schemaSize");function V(c){let e=c[N];if(e!==void 0)return e;let t=0;for(let s of Object.keys(c))t+=c[s].size;return c[N]=t,t}var w=class{static encodeInto(e,t){let s=V(e),n=new ArrayBuffer(s),a=new DataView(n),r=0;for(let o of Object.keys(e)){let i=e[o];i.write(a,r,t[o]),r+=i.size}return new Uint8Array(n)}static decodeInto(e,t,s){let n=V(e);if(t.byteLength<n)throw new RangeError(`Buffer too small: expected ${n} bytes, got ${t.byteLength}`);let a=new DataView(t.buffer,t.byteOffset,t.byteLength),r=0;for(let o of Object.keys(e)){let i=e[o];s[o]=i.read(a,r),r+=i.size}return s}},y=class{static{this.u8={size:1,write:(e,t,s)=>e.setUint8(t,s),read:(e,t)=>e.getUint8(t),toNil:()=>0}}static{this.u16={size:2,write:(e,t,s)=>e.setUint16(t,s,!1),read:(e,t)=>e.getUint16(t,!1),toNil:()=>0}}static{this.u32={size:4,write:(e,t,s)=>e.setUint32(t,s,!1),read:(e,t)=>e.getUint32(t,!1),toNil:()=>0}}static{this.i8={size:1,write:(e,t,s)=>e.setInt8(t,s),read:(e,t)=>e.getInt8(t),toNil:()=>0}}static{this.i16={size:2,write:(e,t,s)=>e.setInt16(t,s,!1),read:(e,t)=>e.getInt16(t,!1),toNil:()=>0}}static{this.i32={size:4,write:(e,t,s)=>e.setInt32(t,s,!1),read:(e,t)=>e.getInt32(t,!1),toNil:()=>0}}static{this.f32={size:4,write:(e,t,s)=>e.setFloat32(t,s,!1),read:(e,t)=>e.getFloat32(t,!1),toNil:()=>0}}static{this.f64={size:8,write:(e,t,s)=>e.setFloat64(t,s,!1),read:(e,t)=>e.getFloat64(t,!1),toNil:()=>0}}static{this.bool={size:1,write:(e,t,s)=>e.setUint8(t,s?1:0),read:(e,t)=>e.getUint8(t)!==0,toNil:()=>!1}}static string(e){return{size:e+2,write(t,s,n){let r=new TextEncoder().encode(n);if(r.length>e)throw new RangeError(`String too long, max ${e} bytes`);t.setUint16(s,r.length,!1);for(let o=0;o<r.length;o++)t.setUint8(s+2+o,r[o]);for(let o=r.length;o<e;o++)t.setUint8(s+2+o,0)},read(t,s){let n=t.getUint16(s,!1),a=new Uint8Array(n);for(let r=0;r<n;r++)a[r]=t.getUint8(s+2+r);return new TextDecoder().decode(a)},toNil:()=>""}}static{this.vec2={size:8,write(e,t,s){e.setFloat32(t,s.x,!1),e.setFloat32(t+4,s.y,!1)},read(e,t){return{x:e.getFloat32(t,!1),y:e.getFloat32(t+4,!1)}},toNil:()=>({x:0,y:0})}}static{this.vec3={size:12,write(e,t,s){e.setFloat32(t,s.x,!1),e.setFloat32(t+4,s.y,!1),e.setFloat32(t+8,s.z,!1)},read(e,t){return{x:e.getFloat32(t,!1),y:e.getFloat32(t+4,!1),z:e.getFloat32(t+8,!1)}},toNil:()=>({x:0,y:0,z:0})}}static{this.color={size:4,write(e,t,s){e.setUint8(t,s.r),e.setUint8(t+1,s.g),e.setUint8(t+2,s.b),e.setUint8(t+3,s.a)},read(e,t){return{r:e.getUint8(t),g:e.getUint8(t+1),b:e.getUint8(t+2),a:e.getUint8(t+3)}},toNil:()=>({r:0,g:0,b:0,a:0})}}static{this.f32_le={size:4,write:(e,t,s)=>e.setFloat32(t,s,!0),read:(e,t)=>e.getFloat32(t,!0),toNil:()=>0}}static{this.f64_le={size:8,write:(e,t,s)=>e.setFloat64(t,s,!0),read:(e,t)=>e.getFloat64(t,!0),toNil:()=>0}}static{this.u16_le={size:2,write:(e,t,s)=>e.setUint16(t,s,!0),read:(e,t)=>e.getUint16(t,!0),toNil:()=>0}}static{this.u32_le={size:4,write:(e,t,s)=>e.setUint32(t,s,!0),read:(e,t)=>e.getUint32(t,!0),toNil:()=>0}}static{this.i16_le={size:2,write:(e,t,s)=>e.setInt16(t,s,!0),read:(e,t)=>e.getInt16(t,!0),toNil:()=>0}}static{this.i32_le={size:4,write:(e,t,s)=>e.setInt32(t,s,!0),read:(e,t)=>e.getInt32(t,!0),toNil:()=>0}}static{this.vec2_le={size:8,write:(e,t,s)=>{e.setFloat32(t,s[0],!0),e.setFloat32(t+4,s[1],!0)},read:(e,t)=>[e.getFloat32(t,!0),e.getFloat32(t+4,!0)],toNil:()=>[0,0]}}static{this.vec3_le={size:12,write:(e,t,s)=>{e.setFloat32(t,s[0],!0),e.setFloat32(t+4,s[1],!0),e.setFloat32(t+8,s[2],!0)},read:(e,t)=>[e.getFloat32(t,!0),e.getFloat32(t+4,!0),e.getFloat32(t+8,!0)],toNil:()=>[0,0,0]}}static{this.vec4_le={size:16,write:(e,t,s)=>{e.setFloat32(t,s[0],!0),e.setFloat32(t+4,s[1],!0),e.setFloat32(t+8,s[2],!0),e.setFloat32(t+12,s[3],!0)},read:(e,t)=>[e.getFloat32(t,!0),e.getFloat32(t+4,!0),e.getFloat32(t+8,!0),e.getFloat32(t+12,!0)],toNil:()=>[0,0,0,0]}}},g=class extends w{static{this.u8=y.u8}static{this.u16=y.u16}static{this.u32=y.u32}static{this.i8=y.i8}static{this.i16=y.i16}static{this.i32=y.i32}static{this.f32=y.f32}static{this.bool=y.bool}static{this.string=y.string}static{this.vec2=y.vec2}static{this.vec3=y.vec3}static{this.color=y.color}static encode(e,t){return this.encodeInto(e,t)}static decode(e,t,s){return this.decodeInto(e,t,s)}};var A=class{constructor({events:e}){this.callbacks=new Map,this.events=e;for(let t of this.events)this.callbacks.set(t,new Set)}on(e,t){let s=this.callbacks.get(e);if(!s)return console.warn(`Event "${e}" does not exist.`);s.add(t)}once(e,t){let s=n=>{t(n),this.off(e,s)};this.on(e,s)}emit(e,t){let s=this.callbacks.get(e);if(!s)return console.warn(`Event "${e}" does not exist.`);for(let n of s)n(t)}off(e,t){let s=this.callbacks.get(e);if(!s)return console.warn(`Event "${e}" does not exist.`);s.delete(t)}clear(e){if(!e){this.callbacks.clear();for(let s of this.events)this.callbacks.set(s,new Set);return}let t=this.callbacks.get(e);if(!t)return console.warn(`Event "${e}" does not exist.`);t.clear()}};var C=class{constructor({rate:e,onTick:t}){this.accumulator=0;this._tickCount=0;this.rate=e,this.intervalMs=1e3/this.rate,this.onTick=t,this.maxTicksPerFrame=Math.max(1,Math.floor(e/2))}getTicks(e){this.accumulator+=e*1e3;let t=0;for(;this.accumulator>=this.intervalMs&&t<this.maxTicksPerFrame;)this.accumulator-=this.intervalMs,t++;let s=Math.floor(this.accumulator/this.intervalMs);return s>0&&this.onTickSkipped&&this.onTickSkipped(s),t}tick(e){let t=this.getTicks(e);for(let s=0;s<t;s++)this.onTick(1/this.rate,this._tickCount++)}get tickCount(){return this._tickCount}resetTickCount(){this._tickCount=0}get accumulatedTime(){return this.accumulator/1e3}};function re(c={}){let{prefix:e="",size:t=16}=c,s=Math.max(t-e.length,8),n=Math.ceil(s/8),r=crypto.getRandomValues(new Uint32Array(n)).reduce((o,i)=>o+i.toString(16).padStart(8,"0"),"");return r=r.slice(0,s).padStart(s,"0"),`${e}${r}`}function ce(c,e,t){return c+(e-c)*t}var B=[{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}],k=c=>({x:Math.floor(c.x),y:Math.floor(c.y)}),P=c=>({x:c.x+.5,y:c.y+.5}),L=(()=>{let c=1;return()=>c++})(),m=(c,e)=>c&65535|(e&65535)<<16,F=c=>({x:c<<16>>16,y:c>>16}),z=class{constructor(e){this.scoreFn=e;this.heap=[]}push(e){this.heap.push(e),this.bubbleUp(this.heap.length-1)}pop(){let e=this.heap[0],t=this.heap.pop();return this.heap.length>0&&t!==void 0&&(this.heap[0]=t,this.sinkDown(0)),e}get size(){return this.heap.length}bubbleUp(e){let t=this.heap[e],s=this.scoreFn(t);for(;e>0;){let n=(e+1>>1)-1,a=this.heap[n];if(s>=this.scoreFn(a))break;this.heap[n]=t,this.heap[e]=a,e=n}}sinkDown(e){let t=this.heap.length,s=this.heap[e],n=this.scoreFn(s);for(;;){let a=e+1<<1,r=a-1,o=null,i;if(r<t){let l=this.heap[r];i=this.scoreFn(l),i<n&&(o=r)}if(a<t){let l=this.heap[a];this.scoreFn(l)<(o===null?n:i)&&(o=a)}if(o===null)break;this.heap[e]=this.heap[o],this.heap[o]=s,e=o}}},S=class{constructor(e=1){this.grid=new Map;this.obstacleCells=new Map;this.cellSize=e}hash(e,t){let s=Math.floor(e/this.cellSize),n=Math.floor(t/this.cellSize);return m(s,n)}add(e,t){let s=this.getCellsForObstacle(t);for(let n of s)this.grid.has(n)||this.grid.set(n,new Set),this.grid.get(n).add(e);this.obstacleCells.set(e,s)}remove(e){let t=this.obstacleCells.get(e);if(t){for(let s of t){let n=this.grid.get(s);n&&(n.delete(e),n.size===0&&this.grid.delete(s))}this.obstacleCells.delete(e)}}query(e){let t=this.hash(e.x,e.y);return this.grid.get(t)||new Set}clear(){this.grid.clear(),this.obstacleCells.clear()}getCellsForObstacle(e){let t=new Set;if(e.type==="circle"){let s=e.radius,n=Math.floor((e.pos.x-s)/this.cellSize),a=Math.floor((e.pos.x+s)/this.cellSize),r=Math.floor((e.pos.y-s)/this.cellSize),o=Math.floor((e.pos.y+s)/this.cellSize);for(let i=n;i<=a;i++)for(let l=r;l<=o;l++)t.add(m(i,l))}else if(e.type==="rect"){let s=e.pos.x+e.size.x/2,n=e.pos.y+e.size.y/2,a=e.size.x/2,r=e.size.y/2,o=Math.sqrt(a*a+r*r),i=Math.floor((s-o)/this.cellSize),l=Math.floor((s+o)/this.cellSize),u=Math.floor((n-o)/this.cellSize),d=Math.floor((n+o)/this.cellSize);for(let h=i;h<=l;h++)for(let p=u;p<=d;p++)t.add(m(h,p))}else if(e.type==="polygon"){let s=R(e),n=Math.floor(s.minX/this.cellSize),a=Math.floor(s.maxX/this.cellSize),r=Math.floor(s.minY/this.cellSize),o=Math.floor(s.maxY/this.cellSize);for(let i=n;i<=a;i++)for(let l=r;l<=o;l++)t.add(m(i,l))}return t}};function _(c,e){let t=c.x-e.pos.x,s=c.y-e.pos.y;return t*t+s*s<=e.radius*e.radius}function X(c,e){let t=e.pos.x+e.size.x/2,s=e.pos.y+e.size.y/2;if(e.rotation){let n=Math.cos(-e.rotation),a=Math.sin(-e.rotation),r=c.x-t,o=c.y-s,i=r*n-o*a,l=r*a+o*n;return Math.abs(i)<=e.size.x/2&&Math.abs(l)<=e.size.y/2}return c.x>=e.pos.x&&c.y>=e.pos.y&&c.x<=e.pos.x+e.size.x&&c.y<=e.pos.y+e.size.y}function Y(c,e){let t=!1,s=e.points,n=e.rotation?Math.cos(e.rotation):1,a=e.rotation?Math.sin(e.rotation):0;for(let r=0,o=s.length-1;r<s.length;o=r++){let i=s[r].x,l=s[r].y,u=s[o].x,d=s[o].y;if(e.rotation){let p=i*n-l*a,b=i*a+l*n,f=u*n-d*a,T=u*a+d*n;i=p,l=b,u=f,d=T}i+=e.pos.x,l+=e.pos.y,u+=e.pos.x,d+=e.pos.y,l>c.y!=d>c.y&&c.x<(u-i)*(c.y-l)/(d-l)+i&&(t=!t)}return t}function R(c){let e=1/0,t=1/0,s=-1/0,n=-1/0,a=c.rotation?Math.cos(c.rotation):1,r=c.rotation?Math.sin(c.rotation):0;for(let o of c.points){let i=o.x,l=o.y;if(c.rotation){let u=i*a-l*r,d=i*r+l*a;i=u,l=d}i+=c.pos.x,l+=c.pos.y,e=Math.min(e,i),t=Math.min(t,l),s=Math.max(s,i),n=Math.max(n,l)}return{minX:e,minY:t,maxX:s,maxY:n}}var O=class{constructor(){this.items=new Map;this.spatial=new S(1);this._cachedItems=[];this.dirty=!0;this.version=0}add(e){let t=L(),s={...e,id:t};return this.items.set(t,s),this.spatial.add(t,s),this.dirty=!0,this.version++,t}move(e,t){let s=this.items.get(e);if(!s)return;this.spatial.remove(e);let n={...s,pos:{...t}};this.items.set(e,n),this.spatial.add(e,n),this.dirty=!0,this.version++}remove(e){this.spatial.remove(e),this.items.delete(e),this.dirty=!0,this.version++}at(e){let t=this.spatial.query(e);for(let s of t){let n=this.items.get(s);if(!(!n||n.solid===!1)&&(n.type==="circle"&&_(e,n)||n.type==="rect"&&X(e,n)||n.type==="polygon"&&Y(e,n)))return n}}get values(){return this.dirty?(this._cachedItems=[...this.items.values()],this.dirty=!1,this._cachedItems):this._cachedItems}},U=class{constructor(e){this.obstacles=e;this.blocked=new Set}rebuild(){this.blocked.clear();for(let e of this.obstacles.values)if(e.solid!==!1){if(e.type==="circle"){let t=Math.ceil(e.radius),s=Math.floor(e.pos.x),n=Math.floor(e.pos.y);for(let a=-t;a<=t;a++)for(let r=-t;r<=t;r++){let o=s+a,i=n+r,l={x:o+.5,y:i+.5};_(l,e)&&this.blocked.add(m(o,i))}}else if(e.type==="rect"){let t=e.pos.x+e.size.x/2,s=e.pos.y+e.size.y/2,n=e.size.x/2,a=e.size.y/2,r=Math.sqrt(n*n+a*a),o=Math.floor(t-r),i=Math.ceil(t+r),l=Math.floor(s-r),u=Math.ceil(s+r);for(let d=o;d<=i;d++)for(let h=l;h<=u;h++){let p={x:d+.5,y:h+.5};X(p,e)&&this.blocked.add(m(d,h))}}else if(e.type==="polygon"){let t=R(e),s=Math.floor(t.minX),n=Math.ceil(t.maxX),a=Math.floor(t.minY),r=Math.ceil(t.maxY);for(let o=s;o<=n;o++)for(let i=a;i<=r;i++){let l={x:o+.5,y:i+.5};Y(l,e)&&this.blocked.add(m(o,i))}}}}findPath(e,t){return D(k(e),k(t),(s,n)=>!this.blocked.has(m(s,n))).map(P)}},M=class{constructor(e){this.obstacles=e}rebuild(){}findPath(e,t){let s=Math.ceil(Math.hypot(t.x-e.x,t.y-e.y)*2),n=!1;for(let r=1;r<=s;r++){let o=r/s,i={x:e.x+(t.x-e.x)*o,y:e.y+(t.y-e.y)*o};if(this.obstacles.at(i)){n=!0;break}}return n?D(k(e),k(t),(r,o)=>{let i={x:r+.5,y:o+.5};return!this.obstacles.at(i)}).map(P):[e,t]}},j=class{constructor(e){this.type=e;this.lastVersion=-1;this.obstacles=new O,e==="grid"&&(this.grid=new U(this.obstacles)),e==="graph"&&(this.graph=new M(this.obstacles))}addObstacle(e){return this.obstacles.add(e)}moveObstacle(e,t){this.obstacles.move(e,t)}removeObstacle(e){this.obstacles.remove(e)}getObstacles(){return this.obstacles.values}findPath({from:e,to:t}){return this.rebuild(),this.type==="grid"?this.grid.findPath(e,t):this.graph.findPath(e,t)}rebuild(){this.lastVersion!==this.obstacles.version&&(this.grid?.rebuild(),this.graph?.rebuild(),this.lastVersion=this.obstacles.version)}};function D(c,e,t){let s=new Map,n=new Map,a=new Set,r=new Set,o=d=>m(d.x,d.y),i=(d,h)=>Math.abs(d.x-h.x)+Math.abs(d.y-h.y),l=new z(d=>{let h=F(d);return n.get(d)+i(h,e)}),u=o(c);for(n.set(u,0),l.push(u),r.add(u);l.size>0;){let d=l.pop();r.delete(d);let h=F(d);if(h.x===e.x&&h.y===e.y)return G(s,h);a.add(d);for(let p of B){let b={x:h.x+p.x,y:h.y+p.y};if(!t(b.x,b.y))continue;let f=o(b);if(a.has(f))continue;let T=n.get(d)+1;T<(n.get(f)??1/0)&&(n.set(f,T),s.set(f,d),r.has(f)||(l.push(f),r.add(f)))}}return[]}function G(c,e){let t=[e],s=m(e.x,e.y);for(;c.has(s);)s=c.get(s),t.push(F(s));return t.reverse()}var x=class{constructor(e){this.factory=e;this.pool=[]}acquire(){return this.pool.pop()??this.factory()}release(e){this.pool.push(e)}releaseAll(e){this.pool.push(...e)}},v=class{constructor(e){this.schema=e;this.pool=new x(()=>this.createNil())}createNil(){let e={};for(let t of Object.keys(this.schema)){let s=this.schema[t];e[t]="toNil"in s?s.toNil():void 0}return e}decode(e){let t=this.pool.acquire();return this.decodeInto(e,t),t}decodeInto(e,t){let s=0;for(let n of Object.keys(this.schema)){let a=this.schema[n];if("decodeAll"in a){let r=a.decodeAll(e.subarray(s));t[n]=r.value,s+=r.bytesRead}else if("decodeField"in a){let r=a.decodeField(e.subarray(s));t[n]=r.value,s+=r.bytesRead}else if("decode"in a){let r=a.decode(e.subarray(s));t[n]=r.value,s+=r.bytesRead}else{let r=a.size||0,o=e.subarray(s,s+r);g.decodeInto({[n]:a},o,t),s+=r}}}release(e){this.pool.release(e)}},K=class{constructor(e){this.pooledDecoder=new v(e)}decodeAll(e){return e.map(t=>this.pooledDecoder.decode(t))}releaseAll(e){e.forEach(t=>this.pooledDecoder.release(t))}},I=class{constructor(e,t=1024){this.schema=e;this.bufferSize=t;this.pool=new x(()=>new Uint8Array(t))}encode(e){let t=this.pool.acquire(),s=0;for(let n of Object.keys(this.schema)){let a=this.schema[n];if("encode"in a){let r=a.encode(e[n]);t.set(r,s),s+=r.length}else if("encodeAll"in a){let r=a.encodeAll(e[n]),o=0;for(let i of r)t.set(i,s+o),o+=i.length;s+=o}else{let r=g.encode({[n]:a},{[n]:e[n]});t.set(r,s),s+=r.length}}return t.subarray(0,s)}release(e){this.pool.release(e)}},q=class{constructor(e){this.schema=e;this.encoder=new I(e),this.decoder=new v(e)}calculateSize(e){let t=0;for(let s of Object.keys(this.schema)){let n=this.schema[s];"size"in n?t+=n.size:"calculateSize"in n&&(t+=n.calculateSize(e[s]))}return t}encodeInto(e,t,s){let n=new DataView(t.buffer,t.byteOffset),a=s;for(let r of Object.keys(this.schema)){let o=this.schema[r];if("write"in o)o.write(n,a,e[r]),a+=o.size;else if("encodeInto"in o){let i=o.encodeInto(e[r],t,a);a+=i}else if("encode"in o){let i=o.encode(e[r]);t.set(i,a),a+=i.length}}return a-s}encode(e){return this.encoder.encode(e)}decode(e){return this.decoder.decode(e)}release(e){this.decoder.release(e)}static array(e){let t=0;for(let i of Object.keys(e)){let l=e[i];t+=l.size||0}let s=new x(()=>new Uint8Array(16384)),n=new x(()=>{let i={};for(let l of Object.keys(e)){let u=e[l];i[l]="toNil"in u?u.toNil():void 0}return i}),a=new x(()=>[]),r=null,o=null;return{__arrayType:void 0,calculateSize(i){return 2+i.length*t},encodeInto(i,l,u){let d=new DataView(l.buffer,l.byteOffset);d.setUint16(u,i.length,!1);let h=u+2;for(let p of i)for(let b of Object.keys(e)){let f=e[b];f.write(d,h,p[b]),h+=f.size}return h-u},encode(i){let l=2+i.length*t,u=s.acquire();u.length<l&&(s.release(u),u=new Uint8Array(Math.max(l,u.length*2))),(!r||r.buffer!==u.buffer)&&(r=new DataView(u.buffer,u.byteOffset)),r.setUint16(0,i.length,!1);let d=2;for(let p of i)for(let b of Object.keys(e)){let f=e[b];f.write(r,d,p[b]),d+=f.size}let h=new Uint8Array(d);return h.set(u.subarray(0,d)),s.release(u),h},decodeField(i){let l=i[0]<<8|i[1],u=a.acquire();u.length=l,(!o||o.buffer!==i.buffer||o.byteOffset!==i.byteOffset)&&(o=new DataView(i.buffer,i.byteOffset));let d=2;for(let h=0;h<l;h++){let p=n.acquire();for(let b of Object.keys(e)){let f=e[b];p[b]=f.read(o,d),d+=f.size}u[h]=p}return{value:u,bytesRead:d}},decode(i){return this.decodeField(i).value},toNil(){return[]}}}};var E=class{constructor(){this.tracker=new Map}get size(){return this.tracker.size}track(e,t){return this.tracker.has(e)||this.tracker.set(e,[]),this.tracker.get(e).push(t),t}dropUpTo(e){let t=[];for(let[s,n]of this.tracker)s<=e?this.tracker.delete(s):t.push([s,n]);return t.sort(([s],[n])=>s-n),t.map(([s,n])=>n).flat()}values(){return Array.from(this.tracker.entries()).sort(([e],[t])=>e-t).map(([e,t])=>t).flat()}},$=class{constructor(e){this.options=e;this.tracker=new E}trackIntent(e,t){this.tracker.track(e,t)}onSnapshot(e){this.options.onLoadState(e.state);let t=this.tracker.dropUpTo(e.tick);t.length>0&&this.options.onReplay(t)}};export{w as BaseBinaryCodec,g as BinaryCodec,y as BinaryPrimitives,A as EventSystem,C as FixedTicker,E as IntentTracker,j as NavMesh,x as ObjectPool,K as PooledArrayDecoder,q as PooledCodec,v as PooledDecoder,I as PooledEncoder,$ as Reconciliator,re as generateId,ce as lerp};