Skip to content

Commit 360f870

Browse files
committed
use DataView more
1 parent 0aec4f6 commit 360f870

File tree

5 files changed

+80
-110
lines changed

5 files changed

+80
-110
lines changed

src/Encoder.ts

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,7 @@ import { ExtensionCodecType, ExtDataType } from "./ExtensionCodec";
33
import { encodeInt64, encodeUint64 } from "./utils/int";
44
import { ensureUint8Array } from "./utils/typedArrays";
55

6-
type EncodeMethodType = (this: Encoder, object: unknown, depth: number) => void;
7-
86
export class Encoder {
9-
static readonly typeofMap = {
10-
"undefined": Encoder.prototype.encodeNil,
11-
"boolean": Encoder.prototype.encodeBoolean,
12-
"number": Encoder.prototype.encodeNumber,
13-
"bigint": Encoder.prototype.encodeBigInt,
14-
"string": Encoder.prototype.encodeString,
15-
"object": Encoder.prototype.encodeObject,
16-
} as Record<string, EncodeMethodType>;
17-
187
private pos = 0;
198
private view = new DataView(new ArrayBuffer(64));
209

@@ -24,11 +13,22 @@ export class Encoder {
2413
if (depth > this.maxDepth) {
2514
throw new Error(`Too deep objects in depth ${depth}`);
2615
}
27-
const encodeFunc = Encoder.typeofMap[typeof object];
28-
if (!encodeFunc) {
16+
17+
if (object == null) {
18+
this.encodeNil();
19+
} else if (typeof object === "boolean") {
20+
this.encodeBoolean(object);
21+
} else if (typeof object === "number") {
22+
this.encodeNumber(object);
23+
} else if (typeof object === "bigint") {
24+
this.encodeBigInt(object);
25+
} else if (typeof object === "string") {
26+
this.encodeString(object);
27+
} else if (typeof object === "object") {
28+
this.encodeObject(object!, depth);
29+
} else {
2930
throw new Error(`Unrecognized object: ${Object.prototype.toString.apply(object)}`);
3031
}
31-
encodeFunc.call(this, object, depth);
3232
}
3333

3434
getArrayBuffer(): ArrayBuffer {
@@ -84,9 +84,7 @@ export class Encoder {
8484
} else {
8585
// uint 64
8686
this.writeU8(0xcf);
87-
const rv: Array<number> = [];
88-
encodeUint64(rv, object);
89-
this.writeU8v(...rv);
87+
this.writeU64(object);
9088
}
9189
} else {
9290
if (object >= -0x20) {
@@ -107,9 +105,7 @@ export class Encoder {
107105
} else {
108106
// int 64
109107
this.writeU8(0xd3);
110-
const rv: Array<number> = [];
111-
encodeInt64(rv, object);
112-
this.writeU8v(...rv);
108+
this.writeI64(object);
113109
}
114110
}
115111
} else {
@@ -148,11 +144,7 @@ export class Encoder {
148144
this.writeU8v(...bytes);
149145
}
150146

151-
encodeObject(object: object | null, depth: number) {
152-
if (object === null) {
153-
this.encodeNil();
154-
return;
155-
}
147+
encodeObject(object: object, depth: number) {
156148
// try to encode objects with custom codec first of non-primitives
157149
const ext = this.extensionCodec.tryToEncode(object);
158150
if (ext != null) {
@@ -328,4 +320,20 @@ export class Encoder {
328320
this.pos += 8;
329321
this.view.setFloat64(pos, value);
330322
}
323+
324+
writeU64(value: number) {
325+
this.ensureBufferSizeToWrite(8);
326+
327+
const pos = this.pos;
328+
this.pos += 8;
329+
encodeUint64(value, this.view, pos);
330+
}
331+
332+
writeI64(value: number) {
333+
this.ensureBufferSizeToWrite(8);
334+
335+
const pos = this.pos;
336+
this.pos += 8;
337+
encodeInt64(value, this.view, pos);
338+
}
331339
}

src/ExtensionCodec.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BufferType } from "./BufferType";
2-
import { encodeUint32, decodeUint32, encodeInt32, decodeInt32, decodeInt64, encodeInt64 } from "./utils/int";
2+
import { decodeInt64, encodeInt64 } from "./utils/int";
33

44
export const EXT_TIMESTAMP = -1;
55

@@ -15,30 +15,33 @@ export type TimeSpec = {
1515
const TIMESTAMP32_MAX_SEC = 0x100000000; // 32-bit signed int
1616
const TIMESTAMP64_MAX_SEC = 0x400000000; // 34-bit unsigned int
1717

18-
export function encodeTimestampFromTimeSpec({ sec, nsec }: TimeSpec): ReadonlyArray<number> {
18+
export function encodeTimestampFromTimeSpec({ sec, nsec }: TimeSpec): Uint8Array {
1919
if (sec >= 0 && nsec >= 0 && sec < TIMESTAMP64_MAX_SEC) {
2020
// Here sec >= 0 && nsec >= 0
2121
if (nsec === 0 && sec < TIMESTAMP32_MAX_SEC) {
2222
// timestamp 32 = { sec32 (unsigned) }
23-
const rv: Array<number> = [];
24-
encodeUint32(rv, sec);
23+
const rv = new Uint8Array(4);
24+
const view = new DataView(rv.buffer);
25+
view.setUint32(0, sec);
2526
return rv;
2627
} else {
2728
// timestamp 64 = { nsec30 (unsigned), sec34 (unsigned) }
2829
const secHigh = sec / 0x100000000;
2930
const secLow = sec & 0xffffffff;
30-
const rv: Array<number> = [];
31+
const rv = new Uint8Array(8);
32+
const view = new DataView(rv.buffer);
3133
// nsec30 | secHigh2
32-
encodeUint32(rv, (nsec << 2) | (secHigh & 0x3));
34+
view.setUint32(0, (nsec << 2) | (secHigh & 0x3));
3335
// secLow32
34-
encodeUint32(rv, secLow);
36+
view.setUint32(4, secLow);
3537
return rv;
3638
}
3739
} else {
3840
// timestamp 96 = { nsec32 (signed), sec64 (signed) }
39-
const rv: Array<number> = [];
40-
encodeInt32(rv, nsec);
41-
encodeInt64(rv, sec);
41+
const rv = new Uint8Array(12);
42+
const view = new DataView(rv.buffer);
43+
view.setInt32(0, nsec);
44+
encodeInt64(sec, view, 4);
4245
return rv;
4346
}
4447
}
@@ -60,20 +63,28 @@ export const decodeTimestampExtension: ExtensionDecoderType = (data: BufferType)
6063
switch (data.length) {
6164
case 4: {
6265
// timestamp 32 = { sec32 }
63-
const sec = decodeUint32(data[0], data[1], data[2], data[3]);
66+
const a = Uint8Array.from(data);
67+
const view = new DataView(a.buffer);
68+
const sec = view.getUint32(0);
6469
return new Date(sec * 1000);
6570
}
6671
case 8: {
6772
// timestamp 64 = { nsec30, sec34 }
68-
const nsec30AndSecHigh2 = decodeUint32(data[0], data[1], data[2], data[3]);
69-
const secLow32 = decodeUint32(data[4], data[5], data[6], data[7]);
73+
const a = Uint8Array.from(data);
74+
const view = new DataView(a.buffer);
75+
76+
const nsec30AndSecHigh2 = view.getUint32(0);
77+
const secLow32 = view.getUint32(4);
7078
const nsec = nsec30AndSecHigh2 >>> 2;
7179
const sec = (nsec30AndSecHigh2 & 0x3) * 0x100000000 + secLow32;
7280
return new Date(sec * 1000 + nsec / 1e6);
7381
}
7482
case 12: {
7583
// timestamp 96 = { nsec32 (signed), sec64 (signed) }
76-
const nsec = decodeInt32(data[0], data[1], data[2], data[3]);
84+
const a = Uint8Array.from(data);
85+
const view = new DataView(a.buffer);
86+
87+
const nsec = view.getInt32(0)
7788
const sec = decodeInt64(data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11]);
7889

7990
return new Date(sec * 1000 + nsec / 1e6);

src/utils/int.ts

Lines changed: 17 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,24 @@
1-
export function encodeUint32(rv: Array<number>, value: number): void {
2-
rv.push((value >>> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff);
3-
}
4-
5-
export function decodeUint32(b1: number, b2: number, b3: number, b4: number) {
6-
// do not use `b1 << 32` because JavaScript only handles 32-bit integer for bitwise operations
7-
return b1 * 0x1000000 + (b2 << 16) + (b3 << 8) + b4;
8-
}
9-
10-
export const encodeInt32 = encodeUint32;
11-
12-
export function decodeInt32(b1: number, b2: number, b3: number, b4: number): number {
13-
const v = decodeUint32(b1, b2, b3, b4);
14-
return v < 0x80000000 ? v : v - 0x100000000;
15-
}
16-
17-
// the actual range is int52 (a.k.a. safe integer)
18-
export function encodeInt64(rv: Array<number>, value: number): void {
1+
// the actual range is int53 (a.k.a. safe integer)
2+
export function encodeInt64(value: number, view: DataView, offset: number): void {
193
if (value < 0) {
204
const absMinusOne = -value - 1;
215
const high = absMinusOne / 0x100000000;
226
const low = absMinusOne & 0xffffffff;
23-
rv.push(
24-
(((high >> 24) & 0xff) ^ 0xff) | 0x80,
25-
((high >> 16) & 0xff) ^ 0xff,
26-
((high >> 8) & 0xff) ^ 0xff,
27-
(high & 0xff) ^ 0xff,
28-
((low >> 24) & 0xff) ^ 0xff,
29-
((low >> 16) & 0xff) ^ 0xff,
30-
((low >> 8) & 0xff) ^ 0xff,
31-
(low & 0xff) ^ 0xff,
32-
);
7+
8+
view.setUint8(offset, (((high >> 24) & 0xff) ^ 0xff) | 0x80);
9+
view.setUint8(offset + 1, ((high >> 16) & 0xff) ^ 0xff);
10+
view.setUint8(offset + 2, ((high >> 8) & 0xff) ^ 0xff);
11+
view.setUint8(offset + 3, (high & 0xff) ^ 0xff);
12+
view.setUint8(offset + 4, ((low >> 24) & 0xff) ^ 0xff);
13+
view.setUint8(offset + 5, ((low >> 16) & 0xff) ^ 0xff);
14+
view.setUint8(offset + 6, ((low >> 8) & 0xff) ^ 0xff);
15+
view.setUint8(offset + 7, (low & 0xff) ^ 0xff);
3316
} else {
34-
const high = value / 0x100000000;
35-
const low = value & 0xffffffff;
36-
rv.push(
37-
(high >> 24) & 0xff,
38-
(high >> 16) & 0xff,
39-
(high >> 8) & 0xff,
40-
high & 0xff,
41-
(low >> 24) & 0xff,
42-
(low >> 16) & 0xff,
43-
(low >> 8) & 0xff,
44-
low & 0xff,
45-
);
17+
encodeUint64(value, view, offset);
4618
}
4719
}
4820

21+
// the actual range is int53 (a.k.a. safe integer)
4922
export function decodeInt64(
5023
b1: number,
5124
b2: number,
@@ -82,18 +55,11 @@ export function decodeInt64(
8255
);
8356
}
8457

85-
export function encodeUint64(rv: Array<number>, value: number): void {
58+
// the actual range is int53 (a.k.a. safe integer)
59+
export function encodeUint64(value: number, view: DataView, offset: number): void {
8660
const high = value / 0x100000000;
8761
const low = value & 0xffffffff;
8862

89-
rv.push(
90-
(high >> 24) & 0xff,
91-
(high >> 16) & 0xff,
92-
(high >> 8) & 0xff,
93-
high & 0xff,
94-
(low >> 24) & 0xff,
95-
(low >> 16) & 0xff,
96-
(low >> 8) & 0xff,
97-
low & 0xff,
98-
);
63+
view.setUint32(offset, high);
64+
view.setUint32(offset + 4, low);
9965
}

test/codec-int.test.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,26 @@
11
import assert from "assert";
2-
import { encodeInt64, decodeInt64, encodeInt32, decodeInt32 } from "../src/utils/int";
2+
import { encodeInt64, decodeInt64 } from "../src/utils/int";
33

4-
const INT32SPECS = {
4+
const INT64SPECS = {
55
ZERO: 0,
66
ONE: 1,
77
MINUS_ONE: -1,
88
X_FF: 0xff,
99
MINUS_X_FF: -0xff,
1010
INT32_MAX: 0x7fffffff,
1111
INT32_MIN: -0x7fffffff - 1,
12-
} as Record<string, number>;
13-
14-
const INT64SPECS = {
15-
...INT32SPECS,
1612
MAX_SAFE_INTEGER: Number.MAX_SAFE_INTEGER,
1713
MIN_SAFE_INTEGER: Number.MIN_SAFE_INTEGER,
1814
} as Record<string, number>;
1915

2016
describe("codec: encode and decode int 32/64", () => {
21-
context("int 32", () => {
22-
for (const name of Object.keys(INT32SPECS)) {
23-
const value = INT32SPECS[name];
24-
25-
it(`${value} (${value < 0 ? "-" : ""}0x${Math.abs(value).toString(16)})`, () => {
26-
const b: Array<number> = [];
27-
encodeInt32(b, value);
28-
assert.deepStrictEqual(decodeInt32(b[0], b[1], b[2], b[3]), value);
29-
});
30-
}
31-
});
32-
3317
context("int 64", () => {
3418
for (const name of Object.keys(INT64SPECS)) {
3519
const value = INT64SPECS[name];
3620

3721
it(`${value} (${value < 0 ? "-" : ""}0x${Math.abs(value).toString(16)})`, () => {
38-
const b: Array<number> = [];
39-
encodeInt64(b, value);
22+
const b = new Uint8Array(8);
23+
encodeInt64(value, new DataView(b.buffer), 0);
4024
assert.deepStrictEqual(decodeInt64(b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]), value);
4125
});
4226
}

test/msgpack-test-suite.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ describe("msgpack-test-suite", () => {
106106
acc[`k${i}`] = i;
107107
return acc;
108108
}, {}),
109+
MIXED: new Array(0x10).fill(Number.MAX_SAFE_INTEGER),
109110
} as Record<string, any>;
110111

111112
for (const name of Object.keys(SPECS)) {

0 commit comments

Comments
 (0)