Skip to content

Commit 9b1aefa

Browse files
committedSep 6, 2020
make Encoder/Decoder instances easily reusable
1 parent 8a1ecc9 commit 9b1aefa

7 files changed

+90
-26
lines changed
 

‎benchmark/benchmark-from-msgpack-lite.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ var msgpack_lite = try_require("msgpack-lite");
88
var msgpack_js = try_require("msgpack-js");
99
var msgpack_js_v5 = try_require("msgpack-js-v5");
1010
var msgpack5 = try_require("msgpack5");
11-
var msgpack_unpack = try_require("msgpack-unpack");
1211
var notepack = try_require("notepack");
1312

1413
msgpack5 = msgpack5 && msgpack5();
@@ -64,6 +63,12 @@ if (msgpack_msgpack) {
6463
buf = bench('buf = require("@msgpack/msgpack").encode(obj);', msgpack_msgpack.encode, data);
6564
obj = bench('obj = require("@msgpack/msgpack").decode(buf);', msgpack_msgpack.decode, buf);
6665
runTest(obj);
66+
67+
const encoder = new msgpack_msgpack.Encoder();
68+
const decoder = new msgpack_msgpack.Decoder();
69+
buf = bench('buf = /* @msgpack/msgpack */ encoder.encode(obj);', (data) => encoder.encode(data), data);
70+
obj = bench('obj = /* @msgpack/msgpack */ decoder.decodeSync(buf);', (buf) => decoder.decodeSync(buf), buf);
71+
runTest(obj);
6772
}
6873

6974
if (msgpack_js_v5) {
@@ -90,11 +95,6 @@ if (notepack) {
9095
runTest(obj);
9196
}
9297

93-
if (msgpack_unpack) {
94-
obj = bench('obj = require("msgpack-unpack").decode(buf);', msgpack_unpack, packed);
95-
runTest(obj);
96-
}
97-
9898
function JSON_stringify(src: any) {
9999
return Buffer.from(JSON.stringify(src));
100100
}

‎src/Decoder.ts

+22-7
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ export class Decoder<ContextType> {
7070
readonly stack: Array<StackState> = [];
7171

7272
constructor(
73-
readonly extensionCodec: ExtensionCodecType<ContextType> = ExtensionCodec.defaultCodec as any,
7473
readonly context: ContextType,
74+
readonly extensionCodec: ExtensionCodecType<ContextType> = ExtensionCodec.defaultCodec as any,
7575
readonly maxStrLength = DEFAULT_MAX_LENGTH,
7676
readonly maxBinLength = DEFAULT_MAX_LENGTH,
7777
readonly maxArrayLength = DEFAULT_MAX_LENGTH,
@@ -80,6 +80,11 @@ export class Decoder<ContextType> {
8080
readonly cachedKeyDecoder: CachedKeyDecoder | null = sharedCachedKeyDecoder,
8181
) {}
8282

83+
private reinitializeState() {
84+
this.totalPos = 0;
85+
this.headByte = HEAD_BYTE_REQUIRED;
86+
}
87+
8388
setBuffer(buffer: ArrayLike<number> | ArrayBuffer): void {
8489
this.bytes = ensureUint8Array(buffer);
8590
this.view = createDataView(this.bytes);
@@ -106,11 +111,21 @@ export class Decoder<ContextType> {
106111

107112
createNoExtraBytesError(posToShow: number): Error {
108113
const { view, pos } = this;
109-
return new RangeError(`Extra ${view.byteLength - pos} byte(s) found at buffer[${posToShow}]`);
114+
return new RangeError(`Extra ${view.byteLength - pos} of ${view.byteLength} byte(s) found at buffer[${posToShow}]`);
115+
}
116+
117+
/**
118+
* A synchronous interface to decode a byte buffer. It mutates the decoder instance.
119+
* @param buffer A byte buffer encoded in MessagePack.
120+
*/
121+
decodeSync(buffer: ArrayLike<number> | ArrayBuffer): unknown {
122+
this.reinitializeState();
123+
this.setBuffer(buffer);
124+
return this.doDecodeSingleSync();
110125
}
111126

112-
decodeSingleSync(): unknown {
113-
const object = this.decodeSync();
127+
private doDecodeSingleSync(): unknown {
128+
const object = this.doDecodeSync();
114129
if (this.hasRemaining()) {
115130
throw this.createNoExtraBytesError(this.pos);
116131
}
@@ -128,7 +143,7 @@ export class Decoder<ContextType> {
128143
this.appendBuffer(buffer);
129144

130145
try {
131-
object = this.decodeSync();
146+
object = this.doDecodeSync();
132147
decoded = true;
133148
} catch (e) {
134149
if (!(e instanceof DataViewIndexOutOfBoundsError)) {
@@ -179,7 +194,7 @@ export class Decoder<ContextType> {
179194

180195
try {
181196
while (true) {
182-
yield this.decodeSync();
197+
yield this.doDecodeSync();
183198
if (--arrayItemsLeft === 0) {
184199
break;
185200
}
@@ -194,7 +209,7 @@ export class Decoder<ContextType> {
194209
}
195210
}
196211

197-
decodeSync(): unknown {
212+
private doDecodeSync(): unknown {
198213
DECODE: while (true) {
199214
const headByte = this.readHeadByte();
200215
let object: unknown;

‎src/Encoder.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,26 @@ export class Encoder<ContextType> {
1313
private bytes = new Uint8Array(this.view.buffer);
1414

1515
constructor(
16-
readonly extensionCodec: ExtensionCodecType<ContextType> = ExtensionCodec.defaultCodec as any,
1716
readonly context: ContextType,
17+
readonly extensionCodec: ExtensionCodecType<ContextType> = ExtensionCodec.defaultCodec as any,
1818
readonly maxDepth = DEFAULT_MAX_DEPTH,
1919
readonly initialBufferSize = DEFAULT_INITIAL_BUFFER_SIZE,
2020
readonly sortKeys = false,
2121
readonly forceFloat32 = false,
2222
readonly ignoreUndefined = false,
2323
) {}
2424

25-
encode(object: unknown, depth: number): void {
25+
private reinitializeState() {
26+
this.pos = 0;
27+
}
28+
29+
encode(object: unknown): Uint8Array {
30+
this.reinitializeState();
31+
this.doEncode(object, 1);
32+
return this.getUint8Array();
33+
}
34+
35+
doEncode(object: unknown, depth: number): void {
2636
if (depth > this.maxDepth) {
2737
throw new Error(`Too deep objects in depth ${depth}`);
2838
}
@@ -228,7 +238,7 @@ export class Encoder<ContextType> {
228238
throw new Error(`Too large array: ${size}`);
229239
}
230240
for (const item of object) {
231-
this.encode(item, depth + 1);
241+
this.doEncode(item, depth + 1);
232242
}
233243
}
234244

@@ -272,7 +282,7 @@ export class Encoder<ContextType> {
272282

273283
if (!(this.ignoreUndefined && value === undefined)) {
274284
this.encodeString(key);
275-
this.encode(value, depth + 1);
285+
this.doEncode(value, depth + 1);
276286
}
277287
}
278288
}

‎src/decode.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,13 @@ export function decode<ContextType>(
4747
options: DecodeOptions<SplitUndefined<ContextType>> = defaultDecodeOptions as any,
4848
): unknown {
4949
const decoder = new Decoder<ContextType>(
50-
options.extensionCodec,
5150
(options as typeof options & { context: any }).context,
51+
options.extensionCodec,
5252
options.maxStrLength,
5353
options.maxBinLength,
5454
options.maxArrayLength,
5555
options.maxMapLength,
5656
options.maxExtLength,
5757
);
58-
decoder.setBuffer(buffer); // decodeSync() requires only one buffer
59-
return decoder.decodeSingleSync();
58+
return decoder.decodeSync(buffer);
6059
}

‎src/decodeAsync.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export async function decodeAsync<ContextType>(
1010
const stream = ensureAsyncIterabe(streamLike);
1111

1212
const decoder = new Decoder<ContextType>(
13-
options.extensionCodec,
1413
(options as typeof options & { context: any }).context,
14+
options.extensionCodec,
1515
options.maxStrLength,
1616
options.maxBinLength,
1717
options.maxArrayLength,
@@ -28,8 +28,8 @@ export function decodeArrayStream<ContextType>(
2828
const stream = ensureAsyncIterabe(streamLike);
2929

3030
const decoder = new Decoder<ContextType>(
31-
options.extensionCodec,
3231
(options as typeof options & { context: any }).context,
32+
options.extensionCodec,
3333
options.maxStrLength,
3434
options.maxBinLength,
3535
options.maxArrayLength,
@@ -47,8 +47,8 @@ export function decodeStream<ContextType>(
4747
const stream = ensureAsyncIterabe(streamLike);
4848

4949
const decoder = new Decoder<ContextType>(
50-
options.extensionCodec,
5150
(options as typeof options & { context: any }).context,
51+
options.extensionCodec,
5252
options.maxStrLength,
5353
options.maxBinLength,
5454
options.maxArrayLength,

‎src/encode.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,13 @@ export function encode<ContextType>(
4040
options: EncodeOptions<SplitUndefined<ContextType>> = defaultEncodeOptions as any,
4141
): Uint8Array {
4242
const encoder = new Encoder<ContextType>(
43-
options.extensionCodec,
4443
(options as typeof options & { context: any }).context,
44+
options.extensionCodec,
4545
options.maxDepth,
4646
options.initialBufferSize,
4747
options.sortKeys,
4848
options.forceFloat32,
4949
options.ignoreUndefined,
5050
);
51-
encoder.encode(value, 1);
52-
return encoder.getUint8Array();
51+
return encoder.encode(value);
5352
}

‎test/reuse-instances.test.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { deepStrictEqual } from "assert";
2+
import { Encoder, Decoder } from "@msgpack/msgpack";
3+
4+
describe("shared instances", () => {
5+
context("Encoder and Decoder", () => {
6+
const encoder = new Encoder(undefined);
7+
const decoder = new Decoder(undefined);
8+
9+
it("runs #1", () => {
10+
const object = {
11+
nil: null,
12+
integer: 1,
13+
float: Math.PI,
14+
string: "Hello, world!",
15+
binary: Uint8Array.from([1, 2, 3]),
16+
array: [10, 20, 30],
17+
map: { foo: "bar" },
18+
timestampExt: new Date(),
19+
};
20+
21+
const encoded: Uint8Array = encoder.encode(object);
22+
deepStrictEqual(decoder.decodeSync(encoded), object);
23+
});
24+
25+
it("runs #2", () => {
26+
const object = {
27+
nil: null,
28+
integer: 1,
29+
float: Math.PI,
30+
string: "Hello, world!",
31+
binary: Uint8Array.from([1, 2, 3]),
32+
array: [10, 20, 30],
33+
map: { foo: "bar" },
34+
timestampExt: new Date(),
35+
};
36+
37+
const encoded: Uint8Array = encoder.encode(object);
38+
deepStrictEqual(decoder.decodeSync(encoded), object);
39+
});
40+
});
41+
});

0 commit comments

Comments
 (0)
Failed to load comments.