murow 0.0.1

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 (103) hide show
  1. package/README.md +61 -0
  2. package/dist/core/binary-codec/binary-codec.d.ts +159 -0
  3. package/dist/core/binary-codec/binary-codec.js +336 -0
  4. package/dist/core/binary-codec/index.d.ts +1 -0
  5. package/dist/core/binary-codec/index.js +1 -0
  6. package/dist/core/events/event-system.d.ts +71 -0
  7. package/dist/core/events/event-system.js +88 -0
  8. package/dist/core/events/index.d.ts +1 -0
  9. package/dist/core/events/index.js +1 -0
  10. package/dist/core/fixed-ticker/fixed-ticker.d.ts +105 -0
  11. package/dist/core/fixed-ticker/fixed-ticker.js +91 -0
  12. package/dist/core/fixed-ticker/index.d.ts +1 -0
  13. package/dist/core/fixed-ticker/index.js +1 -0
  14. package/dist/core/generate-id/generate-id.d.ts +21 -0
  15. package/dist/core/generate-id/generate-id.js +25 -0
  16. package/dist/core/generate-id/index.d.ts +1 -0
  17. package/dist/core/generate-id/index.js +1 -0
  18. package/dist/core/index.d.ts +8 -0
  19. package/dist/core/index.js +8 -0
  20. package/dist/core/lerp/index.d.ts +1 -0
  21. package/dist/core/lerp/index.js +1 -0
  22. package/dist/core/lerp/lerp.d.ts +40 -0
  23. package/dist/core/lerp/lerp.js +42 -0
  24. package/dist/core/navmesh/index.d.ts +1 -0
  25. package/dist/core/navmesh/index.js +1 -0
  26. package/dist/core/navmesh/navmesh.d.ts +116 -0
  27. package/dist/core/navmesh/navmesh.js +666 -0
  28. package/dist/core/pooled-codec/index.d.ts +1 -0
  29. package/dist/core/pooled-codec/index.js +1 -0
  30. package/dist/core/pooled-codec/pooled-codec.d.ts +140 -0
  31. package/dist/core/pooled-codec/pooled-codec.js +213 -0
  32. package/dist/core/prediction/index.d.ts +1 -0
  33. package/dist/core/prediction/index.js +1 -0
  34. package/dist/core/prediction/prediction.d.ts +64 -0
  35. package/dist/core/prediction/prediction.js +90 -0
  36. package/dist/core.esm.js +1 -0
  37. package/dist/core.js +1 -0
  38. package/dist/index.d.ts +16 -0
  39. package/dist/index.js +18 -0
  40. package/dist/protocol/index.d.ts +43 -0
  41. package/dist/protocol/index.js +43 -0
  42. package/dist/protocol/intent/index.d.ts +39 -0
  43. package/dist/protocol/intent/index.js +38 -0
  44. package/dist/protocol/intent/intent-registry.d.ts +54 -0
  45. package/dist/protocol/intent/intent-registry.js +73 -0
  46. package/dist/protocol/intent/intent.d.ts +12 -0
  47. package/dist/protocol/intent/intent.js +1 -0
  48. package/dist/protocol/snapshot/index.d.ts +44 -0
  49. package/dist/protocol/snapshot/index.js +43 -0
  50. package/dist/protocol/snapshot/snapshot-codec.d.ts +48 -0
  51. package/dist/protocol/snapshot/snapshot-codec.js +56 -0
  52. package/dist/protocol/snapshot/snapshot-registry.d.ts +100 -0
  53. package/dist/protocol/snapshot/snapshot-registry.js +136 -0
  54. package/dist/protocol/snapshot/snapshot.d.ts +19 -0
  55. package/dist/protocol/snapshot/snapshot.js +30 -0
  56. package/package.json +54 -0
  57. package/src/core/binary-codec/README.md +60 -0
  58. package/src/core/binary-codec/binary-codec.test.ts +300 -0
  59. package/src/core/binary-codec/binary-codec.ts +430 -0
  60. package/src/core/binary-codec/index.ts +1 -0
  61. package/src/core/events/README.md +47 -0
  62. package/src/core/events/event-system.test.ts +243 -0
  63. package/src/core/events/event-system.ts +140 -0
  64. package/src/core/events/index.ts +1 -0
  65. package/src/core/fixed-ticker/README.md +77 -0
  66. package/src/core/fixed-ticker/fixed-ticker.test.ts +151 -0
  67. package/src/core/fixed-ticker/fixed-ticker.ts +158 -0
  68. package/src/core/fixed-ticker/index.ts +1 -0
  69. package/src/core/generate-id/README.md +18 -0
  70. package/src/core/generate-id/generate-id.test.ts +79 -0
  71. package/src/core/generate-id/generate-id.ts +37 -0
  72. package/src/core/generate-id/index.ts +1 -0
  73. package/src/core/index.ts +8 -0
  74. package/src/core/lerp/README.md +79 -0
  75. package/src/core/lerp/index.ts +1 -0
  76. package/src/core/lerp/lerp.test.ts +90 -0
  77. package/src/core/lerp/lerp.ts +42 -0
  78. package/src/core/navmesh/README.md +124 -0
  79. package/src/core/navmesh/index.ts +1 -0
  80. package/src/core/navmesh/navmesh.test.ts +344 -0
  81. package/src/core/navmesh/navmesh.ts +850 -0
  82. package/src/core/pooled-codec/README.md +70 -0
  83. package/src/core/pooled-codec/index.ts +1 -0
  84. package/src/core/pooled-codec/pooled-codec.test.ts +349 -0
  85. package/src/core/pooled-codec/pooled-codec.ts +239 -0
  86. package/src/core/prediction/README.md +64 -0
  87. package/src/core/prediction/index.ts +1 -0
  88. package/src/core/prediction/prediction.test.ts +422 -0
  89. package/src/core/prediction/prediction.ts +101 -0
  90. package/src/index.ts +20 -0
  91. package/src/protocol/README.md +310 -0
  92. package/src/protocol/index.ts +44 -0
  93. package/src/protocol/intent/index.ts +40 -0
  94. package/src/protocol/intent/intent-registry.test.ts +237 -0
  95. package/src/protocol/intent/intent-registry.ts +88 -0
  96. package/src/protocol/intent/intent.ts +12 -0
  97. package/src/protocol/snapshot/index.ts +45 -0
  98. package/src/protocol/snapshot/snapshot-codec.test.ts +138 -0
  99. package/src/protocol/snapshot/snapshot-codec.ts +71 -0
  100. package/src/protocol/snapshot/snapshot-registry.test.ts +302 -0
  101. package/src/protocol/snapshot/snapshot-registry.ts +162 -0
  102. package/src/protocol/snapshot/snapshot.test.ts +76 -0
  103. package/src/protocol/snapshot/snapshot.ts +41 -0
@@ -0,0 +1,70 @@
1
+ # Pooled Binary Codec
2
+
3
+ A small library for efficiently encoding and decoding structured binary data in multiplayer games, with object pooling to minimize allocations.
4
+
5
+ ## Features
6
+
7
+ * Reusable **object pool** for any type.
8
+ * **PooledEncoder / PooledDecoder** for single objects.
9
+ * **PooledArrayDecoder / PooledArrayEncoder** for arrays of objects.
10
+ * **PooledCodec**: combined encoder + decoder for easy bidirectional use.
11
+ * Supports primitive types (`u8`, `u16`, `i32`, `f32`, etc.), vectors, colors, booleans, and strings.
12
+ * Automatically initializes objects using `toNil()` from the field schema.
13
+
14
+ ## Installation
15
+
16
+ ```ts
17
+ import { PooledCodec, PooledArrayDecoder } from "./pooled-codec.ts";
18
+ import { BinaryPrimitives } from "./binary-primitives.ts";
19
+ ```
20
+
21
+ ## Usage Example
22
+
23
+ ```ts
24
+ // Define a position schema
25
+ const PositionSchema = {
26
+ x: BinaryPrimitives.f32,
27
+ y: BinaryPrimitives.f32,
28
+ };
29
+
30
+ // Define a snapshot schema using pooled arrays
31
+ const PositionsCodec = new PooledCodec(PositionSchema);
32
+
33
+ const SnapshotSchema = {
34
+ tick: BinaryPrimitives.u16,
35
+ updates: {
36
+ positions: PositionsCodec,
37
+ target: {
38
+ id: BinaryPrimitives.u32,
39
+ name: BinaryPrimitives.string(32),
40
+ },
41
+ },
42
+ };
43
+
44
+ // Create pooled codec for snapshots
45
+ const snapshotCodec = new PooledCodec(SnapshotSchema);
46
+
47
+ // Encode
48
+ const buffer = snapshotCodec.encode({
49
+ tick: 42,
50
+ updates: {
51
+ positions: [
52
+ { x: 10, y: 20 },
53
+ { x: 30, y: 40 },
54
+ ],
55
+ target: { id: 1, name: "Player1" },
56
+ },
57
+ });
58
+
59
+ // Decode
60
+ const snapshot = snapshotCodec.decode(buffer);
61
+
62
+ // Release pooled objects
63
+ snapshotCodec.release(snapshot);
64
+ ```
65
+
66
+ ## Notes
67
+
68
+ * Use **pooled objects** to reduce garbage collection overhead in fast-paced games.
69
+ * Fields with a `toNil()` method are automatically initialized when acquired from the pool.
70
+ * Suitable for real-time multiplayer snapshots, intents, and any high-frequency data.
@@ -0,0 +1 @@
1
+ export * from './pooled-codec';
@@ -0,0 +1,349 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ ObjectPool,
4
+ PooledDecoder,
5
+ PooledEncoder,
6
+ PooledCodec,
7
+ PooledArrayDecoder,
8
+ } from "./pooled-codec";
9
+ import { BinaryCodec, BinaryPrimitives, Schema } from "../binary-codec";
10
+
11
+ describe("ObjectPool", () => {
12
+ test("should create new object when pool is empty", () => {
13
+ const pool = new ObjectPool(() => ({ value: 0 }));
14
+ const obj = pool.acquire();
15
+ expect(obj).toEqual({ value: 0 });
16
+ });
17
+
18
+ test("should reuse released objects", () => {
19
+ const pool = new ObjectPool(() => ({ value: 0 }));
20
+ const obj1 = pool.acquire();
21
+ obj1.value = 42;
22
+ pool.release(obj1);
23
+
24
+ const obj2 = pool.acquire();
25
+ expect(obj2.value).toBe(42);
26
+ expect(obj2).toBe(obj1); // Same object reference
27
+ });
28
+
29
+ test("should handle multiple acquire/release cycles", () => {
30
+ const pool = new ObjectPool(() => ({ count: 0 }));
31
+
32
+ const obj1 = pool.acquire();
33
+ obj1.count = 1;
34
+ pool.release(obj1);
35
+
36
+ const obj2 = pool.acquire();
37
+ obj2.count = 2;
38
+ pool.release(obj2);
39
+
40
+ const obj3 = pool.acquire();
41
+ expect(obj3.count).toBe(2); // Gets the last released object
42
+ });
43
+
44
+ test("should release multiple objects at once", () => {
45
+ const pool = new ObjectPool(() => ({ id: 0 }));
46
+
47
+ const objs = [
48
+ pool.acquire(),
49
+ pool.acquire(),
50
+ pool.acquire(),
51
+ ];
52
+
53
+ objs.forEach((obj, i) => (obj.id = i));
54
+ pool.releaseAll(objs);
55
+
56
+ const reused1 = pool.acquire();
57
+ const reused2 = pool.acquire();
58
+ const reused3 = pool.acquire();
59
+
60
+ expect([reused1.id, reused2.id, reused3.id].sort()).toEqual([0, 1, 2]);
61
+ });
62
+ });
63
+
64
+ describe("PooledDecoder", () => {
65
+ test("should decode data into pooled objects", () => {
66
+ const schema: Schema<{ value: number }> = {
67
+ value: BinaryPrimitives.f32,
68
+ };
69
+
70
+ const decoder = new PooledDecoder(schema);
71
+
72
+ // Use BinaryCodec to encode the data first
73
+ const data = { value: 10.5 };
74
+ const buffer = BinaryCodec.encode(schema, data);
75
+
76
+ const obj = decoder.decode(buffer);
77
+ expect(obj.value).toBeCloseTo(10.5, 5);
78
+ });
79
+
80
+ test("should reuse released objects", () => {
81
+ const schema: Schema<{ value: number }> = {
82
+ value: BinaryPrimitives.u32,
83
+ };
84
+
85
+ const decoder = new PooledDecoder(schema);
86
+
87
+ const buffer = new Uint8Array(4);
88
+ const view = new DataView(buffer.buffer);
89
+ view.setUint32(0, 42, false);
90
+
91
+ const obj1 = decoder.decode(buffer);
92
+ expect(obj1.value).toBe(42);
93
+
94
+ decoder.release(obj1);
95
+
96
+ view.setUint32(0, 100, false);
97
+ const obj2 = decoder.decode(buffer);
98
+ expect(obj2.value).toBe(100);
99
+ expect(obj2).toBe(obj1); // Same object reference
100
+ });
101
+
102
+ test("should decode into existing target object", () => {
103
+ const schema: Schema<{ value: number }> = {
104
+ value: BinaryPrimitives.u8,
105
+ };
106
+
107
+ const decoder = new PooledDecoder(schema);
108
+
109
+ // Use BinaryCodec to encode the data first
110
+ const data = { value: 10 };
111
+ const buffer = BinaryCodec.encode(schema, data);
112
+ const target = { value: 0 };
113
+
114
+ decoder.decodeInto(buffer, target);
115
+ expect(target.value).toBe(10);
116
+ });
117
+ });
118
+
119
+ describe("PooledArrayDecoder", () => {
120
+ test("should decode multiple buffers into pooled objects", () => {
121
+ const schema: Schema<{ id: number }> = {
122
+ id: BinaryPrimitives.u32,
123
+ };
124
+
125
+ const arrayDecoder = new PooledArrayDecoder(schema);
126
+
127
+ const buffers = [
128
+ new Uint8Array([0, 0, 0, 1]),
129
+ new Uint8Array([0, 0, 0, 2]),
130
+ new Uint8Array([0, 0, 0, 3]),
131
+ ];
132
+
133
+ const objs = arrayDecoder.decodeAll(buffers);
134
+ expect(objs.length).toBe(3);
135
+ expect(objs[0].id).toBe(1);
136
+ expect(objs[1].id).toBe(2);
137
+ expect(objs[2].id).toBe(3);
138
+ });
139
+
140
+ test("should release multiple objects", () => {
141
+ const schema: Schema<{ value: number }> = {
142
+ value: BinaryPrimitives.u8,
143
+ };
144
+
145
+ const arrayDecoder = new PooledArrayDecoder(schema);
146
+
147
+ // Use BinaryCodec to encode the data first
148
+ const buffers = [
149
+ BinaryCodec.encode(schema, { value: 10 }),
150
+ BinaryCodec.encode(schema, { value: 20 }),
151
+ BinaryCodec.encode(schema, { value: 30 }),
152
+ ];
153
+
154
+ const objs = arrayDecoder.decodeAll(buffers);
155
+ const objValues = objs.map(o => o.value);
156
+ expect(objValues).toEqual([10, 20, 30]);
157
+
158
+ arrayDecoder.releaseAll(objs);
159
+
160
+ // Decode again and verify objects are reused (checking references)
161
+ const newObjs = arrayDecoder.decodeAll(buffers);
162
+ // Objects should be reused (same references)
163
+ let reuseCount = 0;
164
+ for (const newObj of newObjs) {
165
+ if (objs.includes(newObj)) reuseCount++;
166
+ }
167
+ expect(reuseCount).toBeGreaterThan(0);
168
+ });
169
+ });
170
+
171
+ describe("PooledEncoder", () => {
172
+ test("should encode objects into pooled buffers", () => {
173
+ const schema: Schema<{ x: number; y: number }> = {
174
+ x: BinaryPrimitives.f32,
175
+ y: BinaryPrimitives.f32,
176
+ };
177
+
178
+ const encoder = new PooledEncoder(schema);
179
+ const data = { x: 5.5, y: 10.5 };
180
+
181
+ const buffer = encoder.encode(data);
182
+ expect(buffer.length).toBe(8);
183
+
184
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
185
+ expect(view.getFloat32(0, false)).toBeCloseTo(5.5, 5);
186
+ expect(view.getFloat32(4, false)).toBeCloseTo(10.5, 5);
187
+ });
188
+
189
+ test("should reuse released buffers", () => {
190
+ const schema: Schema<{ value: number }> = {
191
+ value: BinaryPrimitives.u32,
192
+ };
193
+
194
+ const encoder = new PooledEncoder(schema, 16);
195
+ const data1 = { value: 42 };
196
+
197
+ const buffer1 = encoder.encode(data1);
198
+ encoder.release(buffer1);
199
+
200
+ const data2 = { value: 100 };
201
+ const buffer2 = encoder.encode(data2);
202
+
203
+ // Should reuse the same underlying buffer
204
+ expect(buffer2.buffer).toBe(buffer1.buffer);
205
+ });
206
+
207
+ test("should handle custom buffer size", () => {
208
+ const schema: Schema<{ id: number }> = {
209
+ id: BinaryPrimitives.u8,
210
+ };
211
+
212
+ const encoder = new PooledEncoder(schema, 64);
213
+ const data = { id: 5 };
214
+
215
+ const buffer = encoder.encode(data);
216
+ expect(buffer.length).toBe(1); // Only actual data
217
+ });
218
+ });
219
+
220
+ describe("PooledCodec", () => {
221
+ test("should encode and decode with pooling", () => {
222
+ const schema: Schema<{ id: number }> = {
223
+ id: BinaryPrimitives.u32,
224
+ };
225
+
226
+ const codec = new PooledCodec(schema);
227
+ const data = { id: 123 };
228
+
229
+ const encoded = codec.encode(data);
230
+ const decoded = codec.decode(encoded);
231
+
232
+ expect(decoded.id).toBe(123);
233
+ });
234
+
235
+ test("should reuse objects after release", () => {
236
+ const schema: Schema<{ value: number }> = {
237
+ value: BinaryPrimitives.u32,
238
+ };
239
+
240
+ const codec = new PooledCodec(schema);
241
+
242
+ const encoded1 = codec.encode({ value: 42 });
243
+ const decoded1 = codec.decode(encoded1);
244
+ expect(decoded1.value).toBe(42);
245
+
246
+ codec.release(decoded1);
247
+
248
+ const encoded2 = codec.encode({ value: 100 });
249
+ const decoded2 = codec.decode(encoded2);
250
+ expect(decoded2.value).toBe(100);
251
+ expect(decoded2).toBe(decoded1); // Same object
252
+ });
253
+
254
+ test("should handle multiple encode/decode cycles", () => {
255
+ const schema: Schema<{ value: number }> = {
256
+ value: BinaryPrimitives.u16,
257
+ };
258
+
259
+ const codec = new PooledCodec(schema);
260
+
261
+ for (let i = 0; i < 100; i++) {
262
+ const data = { value: i * 10 };
263
+ const encoded = codec.encode(data);
264
+ const decoded = codec.decode(encoded);
265
+
266
+ expect(decoded.value).toBe(i * 10);
267
+
268
+ codec.release(decoded);
269
+ }
270
+ });
271
+
272
+ test("should work with single field schemas", () => {
273
+ const schema: Schema<{
274
+ id: number;
275
+ }> = {
276
+ id: BinaryPrimitives.u32,
277
+ };
278
+
279
+ const codec = new PooledCodec(schema);
280
+ const data = {
281
+ id: 999,
282
+ };
283
+
284
+ const encoded = codec.encode(data);
285
+ const decoded = codec.decode(encoded);
286
+
287
+ expect(decoded.id).toBe(999);
288
+ });
289
+ });
290
+
291
+ describe("PooledCodec - Memory Efficiency", () => {
292
+ test("should reduce allocations with pooling", () => {
293
+ const schema: Schema<{ value: number }> = {
294
+ value: BinaryPrimitives.u32,
295
+ };
296
+
297
+ const codec = new PooledCodec(schema);
298
+ const objects: any[] = [];
299
+
300
+ const times = 10000;
301
+
302
+ // Encode and decode {times} times
303
+ for (let i = 0; i < times; i++) {
304
+ const encoded = codec.encode({ value: i });
305
+ const decoded = codec.decode(encoded);
306
+ objects.push(decoded);
307
+ }
308
+
309
+ // Release all
310
+ objects.forEach((obj) => codec.release(obj));
311
+
312
+ // Decode again - should reuse objects
313
+ const newObjects: any[] = [];
314
+ for (let i = 0; i < times; i++) {
315
+ const encoded = codec.encode({ value: i });
316
+ const decoded = codec.decode(encoded);
317
+ newObjects.push(decoded);
318
+ }
319
+
320
+ // At least some objects should be reused
321
+ let reusedCount = 0;
322
+ for (const newObj of newObjects) {
323
+ if (objects.includes(newObj)) {
324
+ reusedCount++;
325
+ }
326
+ }
327
+
328
+ expect(reusedCount).toBeGreaterThan(0);
329
+ });
330
+
331
+ test("should handle concurrent encode/decode without release", () => {
332
+ const schema: Schema<{ id: number }> = {
333
+ id: BinaryPrimitives.u16,
334
+ };
335
+
336
+ const codec = new PooledCodec(schema);
337
+ const objects: any[] = [];
338
+
339
+ // Create many objects without releasing
340
+ for (let i = 0; i < 50; i++) {
341
+ const encoded = codec.encode({ id: i });
342
+ const decoded = codec.decode(encoded);
343
+ objects.push(decoded);
344
+ }
345
+
346
+ expect(objects.length).toBe(50);
347
+ objects.forEach((obj, i) => expect(obj.id).toBe(i));
348
+ });
349
+ });
@@ -0,0 +1,239 @@
1
+ import { BinaryCodec, Schema } from "../binary-codec";
2
+
3
+ /**
4
+ * Generic object pool for reusing objects and minimizing allocations.
5
+ * @template T Type of objects stored in the pool.
6
+ */
7
+ export class ObjectPool<T> {
8
+ private pool: T[] = [];
9
+
10
+ /**
11
+ * @param factory Function to create a new instance when the pool is empty.
12
+ */
13
+ constructor(private factory: () => T) { }
14
+
15
+ /**
16
+ * Acquire an object from the pool, or create a new one if empty.
17
+ * @returns {T} The acquired object.
18
+ */
19
+ acquire(): T {
20
+ return this.pool.pop() ?? this.factory();
21
+ }
22
+
23
+ /**
24
+ * Return an object to the pool for reuse.
25
+ * @param {T} obj Object to release.
26
+ */
27
+ release(obj: T) {
28
+ this.pool.push(obj);
29
+ }
30
+
31
+ /**
32
+ * Return multiple objects to the pool at once.
33
+ * @param {T[]} objs Array of objects to release.
34
+ */
35
+ releaseAll(objs: T[]) {
36
+ this.pool.push(...objs);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Pooled decoder for single objects or nested schemas.
42
+ * @template T Type of object to decode.
43
+ */
44
+ export class PooledDecoder<T extends object> {
45
+ private pool: ObjectPool<T>;
46
+
47
+ /**
48
+ * @param schema Schema or record describing the object structure.
49
+ * @param initial Initial object used as template for pooling.
50
+ */
51
+ constructor(private schema: Schema<T> | Record<string, any>) {
52
+ this.pool = new ObjectPool(() => this.createNil());
53
+ }
54
+
55
+ private createNil(): T {
56
+ const obj = {} as T;
57
+ for (const key of Object.keys(this.schema) as (keyof T)[]) {
58
+ const field = (this.schema as any)[key];
59
+ obj[key] = "toNil" in field ? field.toNil() : undefined;
60
+ }
61
+ return obj;
62
+ }
63
+
64
+ /**
65
+ * Decode a buffer into a pooled object.
66
+ * @param {Uint8Array} buf Buffer to decode.
67
+ * @returns {T} Decoded object.
68
+ */
69
+ decode(buf: Uint8Array): T {
70
+ const obj = this.pool.acquire();
71
+ this.decodeInto(buf, obj);
72
+ return obj;
73
+ }
74
+
75
+ /**
76
+ * Decode a buffer into a provided target object.
77
+ * @param {Uint8Array} buf Buffer to decode.
78
+ * @param {T} target Object to write decoded data into.
79
+ */
80
+ decodeInto(buf: Uint8Array, target: T) {
81
+ for (const key of Object.keys(this.schema) as (keyof T)[]) {
82
+ const field = (this.schema as any)[key];
83
+ if ("decodeAll" in field) {
84
+ target[key] = field.decodeAll(buf);
85
+ } else if ("decode" in field) {
86
+ target[key] = field.decode(buf);
87
+ } else {
88
+ BinaryCodec.decodeInto({ [key]: field } as Schema<T>, buf, target);
89
+ }
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Release a decoded object back to the pool.
95
+ * @param {T} obj Object to release.
96
+ */
97
+ release(obj: T) {
98
+ this.pool.release(obj);
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Pooled decoder for arrays of objects.
104
+ * @template T Type of object to decode.
105
+ */
106
+ export class PooledArrayDecoder<T extends object> {
107
+ private pooledDecoder: PooledDecoder<T>;
108
+
109
+ /**
110
+ * @param schema Schema or record describing object structure.
111
+ * @param initial Initial object used as template for pooling.
112
+ */
113
+ constructor(schema: Schema<T> | Record<string, any>) {
114
+ this.pooledDecoder = new PooledDecoder(schema);
115
+ }
116
+
117
+ /**
118
+ * Decode multiple buffers into pooled objects.
119
+ * @param {Uint8Array[]} buffers Array of buffers to decode.
120
+ * @returns {T[]} Array of decoded objects.
121
+ */
122
+ decodeAll(buffers: Uint8Array[]): T[] {
123
+ return buffers.map((b) => this.pooledDecoder.decode(b));
124
+ }
125
+
126
+ /**
127
+ * Release multiple decoded objects back to the pool.
128
+ * @param {T[]} objs Array of objects to release.
129
+ */
130
+ releaseAll(objs: T[]) {
131
+ objs.forEach((o) => this.pooledDecoder.release(o));
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Pooled encoder for single objects or nested schemas.
137
+ * @template T Type of object to encode.
138
+ */
139
+ export class PooledEncoder<T extends object> {
140
+ private pool: ObjectPool<Uint8Array>;
141
+
142
+ /**
143
+ * @param schema Schema or record describing object structure.
144
+ * @param bufferSize Size of buffer to allocate per encoding (default: 1024).
145
+ */
146
+ constructor(private schema: Schema<T> | Record<string, any>, private bufferSize = 1024) {
147
+ this.pool = new ObjectPool(() => new Uint8Array(bufferSize));
148
+ }
149
+
150
+ /**
151
+ * Encode an object into a pooled buffer.
152
+ * @param {T} obj Object to encode.
153
+ * @returns {Uint8Array} Encoded buffer.
154
+ */
155
+ encode(obj: T): Uint8Array {
156
+ const buf = this.pool.acquire();
157
+ let offset = 0;
158
+
159
+ for (const key of Object.keys(this.schema) as (keyof T)[]) {
160
+ const field = (this.schema as any)[key];
161
+
162
+ if ("encode" in field) {
163
+ const nested = field.encode(obj[key]);
164
+ buf.set(nested, offset);
165
+ offset += nested.length;
166
+ } else if ("encodeAll" in field) {
167
+ const nestedArr = field.encodeAll(obj[key]);
168
+ let arrOffset = 0;
169
+ for (const item of nestedArr) {
170
+ buf.set(item, offset + arrOffset);
171
+ arrOffset += item.length;
172
+ }
173
+ offset += arrOffset;
174
+ } else {
175
+ const tmp = BinaryCodec.encode({ [key]: field }, { [key]: obj[key] });
176
+ buf.set(tmp, offset);
177
+ offset += tmp.length;
178
+ }
179
+ }
180
+
181
+ return buf.subarray(0, offset);
182
+ }
183
+
184
+ /**
185
+ * Release a buffer back to the pool.
186
+ * @param {Uint8Array} buf Buffer to release.
187
+ */
188
+ release(buf: Uint8Array) {
189
+ this.pool.release(buf);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Combined pooled encoder and decoder for a single schema.
195
+ * Provides a convenient wrapper around PooledEncoder and PooledDecoder.
196
+ * @template T Type of object to encode/decode.
197
+ */
198
+ export class PooledCodec<T extends object> {
199
+ /** Pooled encoder for the schema */
200
+ encoder: PooledEncoder<T>;
201
+
202
+ /** Pooled decoder for the schema */
203
+ decoder: PooledDecoder<T>;
204
+
205
+ /**
206
+ * @param schema Schema describing the object structure.
207
+ * @param initial Initial object used as a template for pooling decoded objects.
208
+ */
209
+ constructor(schema: Schema<T>) {
210
+ this.encoder = new PooledEncoder(schema);
211
+ this.decoder = new PooledDecoder(schema);
212
+ }
213
+
214
+ /**
215
+ * Encode an object into a pooled buffer.
216
+ * @param {T} data Object to encode.
217
+ * @returns {Uint8Array} Encoded buffer.
218
+ */
219
+ encode(data: T) {
220
+ return this.encoder.encode(data);
221
+ }
222
+
223
+ /**
224
+ * Decode a buffer into a pooled object.
225
+ * @param {Uint8Array} buf Buffer to decode.
226
+ * @returns {T} Decoded object.
227
+ */
228
+ decode(buf: Uint8Array) {
229
+ return this.decoder.decode(buf);
230
+ }
231
+
232
+ /**
233
+ * Release a decoded object back to the pool.
234
+ * @param {T} obj Object to release.
235
+ */
236
+ release(obj: T) {
237
+ this.decoder.release(obj);
238
+ }
239
+ }