Skip to content

Commit addb8fc

Browse files
committed
Allow the data alignment to support zero-copy decoding
1 parent 12046e7 commit addb8fc

File tree

4 files changed

+58
-2
lines changed

4 files changed

+58
-2
lines changed

src/Encoder.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,21 @@ export class Encoder<ContextType = undefined> {
396396
}
397397

398398
private encodeExtension(ext: ExtData) {
399+
if (typeof ext.data === "function") {
400+
const data = ext.data(this.pos + 6);
401+
const size = data.length;
402+
403+
if (size >= 0x100000000) {
404+
throw new Error(`Too large extension object: ${size}`);
405+
}
406+
407+
this.writeU8(0xc9);
408+
this.writeU32(size);
409+
this.writeI8(ext.type);
410+
this.writeU8a(data);
411+
return;
412+
}
413+
399414
const size = ext.data.length;
400415
if (size === 1) {
401416
// fixext 1

src/ExtData.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
export class ExtData {
55
constructor(
66
readonly type: number,
7-
readonly data: Uint8Array,
7+
readonly data: Uint8Array | ((pos: number) => Uint8Array),
88
) {}
99
}

src/ExtensionCodec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ export type ExtensionDecoderType<ContextType> = (
99
context: ContextType,
1010
) => unknown;
1111

12-
export type ExtensionEncoderType<ContextType> = (input: unknown, context: ContextType) => Uint8Array | null;
12+
export type ExtensionEncoderType<ContextType> = (
13+
input: unknown,
14+
context: ContextType,
15+
) => Uint8Array | ((dataPos: number) => Uint8Array) | null;
1316

1417
// immutable interface to ExtensionCodec
1518
export type ExtensionCodecType<ContextType> = {

test/ExtensionCodec.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,42 @@ describe("ExtensionCodec", () => {
196196
]);
197197
});
198198
});
199+
200+
context("custom extensions with alignment", () => {
201+
const extensionCodec = new ExtensionCodec();
202+
203+
extensionCodec.register({
204+
type: 0x01,
205+
encode: (object: unknown) => {
206+
if (object instanceof Float32Array) {
207+
return (pos: number) => {
208+
const align = 4;
209+
const padding = 1 + ((align - ((pos + 1) % align)) % align);
210+
const data = new Uint8Array(object.buffer);
211+
const result = new Uint8Array(padding + data.length);
212+
result[0] = padding;
213+
result.set(data, padding);
214+
return result;
215+
};
216+
}
217+
return null;
218+
},
219+
decode: (data: Uint8Array) => {
220+
const padding = data[0]!;
221+
const offset = data.byteOffset + padding;
222+
const length = data.byteLength - padding;
223+
return new Float32Array(data.buffer, offset, length / Float32Array.BYTES_PER_ELEMENT);
224+
},
225+
});
226+
227+
it("encodes and decodes Float32Array type with zero-copy", () => {
228+
const data = {
229+
position: new Float32Array([1.1, 2.2, 3.3, 4.4, 5.5]),
230+
};
231+
const encoded = encode(data, { extensionCodec });
232+
const decoded = decode(encoded, { extensionCodec });
233+
assert.deepStrictEqual(decoded, data);
234+
assert.strictEqual(decoded.position.buffer, encoded.buffer);
235+
});
236+
});
199237
});

0 commit comments

Comments
 (0)