feat(struct): include polyfill for bigint types

This commit is contained in:
Simon Chan 2022-02-02 18:14:47 +08:00
parent 09e42bb0e7
commit 594648d033
12 changed files with 251 additions and 92 deletions

View file

@ -4,7 +4,11 @@ Polyfill for `DataView#getBigInt64`, `DataView#getBigUint64`, `DataView#setBigIn
Requires native `BigInt` support. Requires native `BigInt` support.
## Import functions ## Flavors
It ships with 3 flavors:
### Pure: provide alternative implementations.
```ts ```ts
import { getBigInt64, getBigUint64, setBigInt64, setBigUint64 } from '@yume-chan/dataview-bigint-polyfill'; import { getBigInt64, getBigUint64, setBigInt64, setBigUint64 } from '@yume-chan/dataview-bigint-polyfill';
@ -15,8 +19,24 @@ setBigInt64(dataView, byteOffset, value, littleEndian);
setBigUint64(dataView, byteOffset, value, littleEndian); setBigUint64(dataView, byteOffset, value, littleEndian);
``` ```
## Polyfill `DataView` ### Fallback: use native implementations if available.
```ts
import { getBigInt64, getBigUint64, setBigInt64, setBigUint64 } from '@yume-chan/dataview-bigint-polyfill/esm/fallback';
getBigInt64(dataView, byteOffset, littleEndian);
getBigUint64(dataView, byteOffset, littleEndian);
setBigInt64(dataView, byteOffset, value, littleEndian);
setBigUint64(dataView, byteOffset, value, littleEndian);
```
### Polyfill: patch `DataView.prototype` when native support is not available.
```ts ```ts
import '@yume-chan/dataview-bigint-polyfill/esm/polyfill.js'; import '@yume-chan/dataview-bigint-polyfill/esm/polyfill.js';
dataView.getBigInt64(byteOffset, littleEndian);
dataView.getBigUint64(byteOffset, littleEndian);
dataView.setBigInt64(byteOffset, value, littleEndian);
dataView.setBigUint64(byteOffset, value, littleEndian);
``` ```

View file

@ -0,0 +1,21 @@
import { getBigInt64 as fallbackGetBigInt64, getBigUint64 as fallbackGetBigUint64, setBigInt64 as fallbackSetBigInt64, setBigUint64 as fallbackSetBigUint64 } from './pure';
export const getBigInt64 =
'getBigInt64' in DataView.prototype ?
(dataView: DataView, byteOffset: number, littleEndian: boolean | undefined) => dataView.getBigInt64(byteOffset, littleEndian) :
fallbackGetBigInt64;
export const getBigUint64 =
'getBigUint64' in DataView.prototype ?
(dataView: DataView, byteOffset: number, littleEndian: boolean | undefined) => dataView.getBigUint64(byteOffset, littleEndian) :
fallbackGetBigUint64;
export const setBigInt64 =
'setBigInt64' in DataView.prototype ?
(dataView: DataView, byteOffset: number, value: bigint, littleEndian: boolean | undefined) => dataView.setBigInt64(byteOffset, value, littleEndian) :
fallbackSetBigInt64;
export const setBigUint64 =
'setBigUint64' in DataView.prototype ?
(dataView: DataView, byteOffset: number, value: bigint, littleEndian: boolean | undefined) => dataView.setBigUint64(byteOffset, value, littleEndian) :
fallbackSetBigUint64;

View file

@ -1,5 +1,20 @@
const BigInt32 = BigInt(32); const BigInt32 = BigInt(32);
export function getBigInt64(dataView: DataView, byteOffset: number, littleEndian: boolean | undefined): bigint {
const littleEndianMask = Number(!!littleEndian);
const bigEndianMask = Number(!littleEndian);
return (
BigInt(
dataView.getInt32(byteOffset, littleEndian) * bigEndianMask +
dataView.getInt32(byteOffset + 4, littleEndian) * littleEndianMask
) << BigInt32) |
BigInt(
dataView.getUint32(byteOffset, littleEndian) * littleEndianMask +
dataView.getUint32(byteOffset + 4, littleEndian) * bigEndianMask
);
}
export function getBigUint64(dataView: DataView, byteOffset: number, littleEndian: boolean | undefined): bigint { export function getBigUint64(dataView: DataView, byteOffset: number, littleEndian: boolean | undefined): bigint {
const a = dataView.getUint32(byteOffset, littleEndian); const a = dataView.getUint32(byteOffset, littleEndian);
const b = dataView.getUint32(byteOffset + 4, littleEndian); const b = dataView.getUint32(byteOffset + 4, littleEndian);
@ -14,27 +29,6 @@ export function getBigUint64(dataView: DataView, byteOffset: number, littleEndia
BigInt(a * littleEndianMask + b * bigEndianMask); BigInt(a * littleEndianMask + b * bigEndianMask);
}; };
export function setBigUint64(dataView: DataView, byteOffset: number, value: bigint, littleEndian: boolean | undefined) {
const hi = Number(value >> BigInt32);
const lo = Number(value & BigInt(0xFFFFFFFF));
if (littleEndian) {
dataView.setUint32(byteOffset + 4, hi, littleEndian);
dataView.setUint32(byteOffset, lo, littleEndian);
} else {
dataView.setUint32(byteOffset, hi, littleEndian);
dataView.setUint32(byteOffset + 4, lo, littleEndian);
}
};
export function getBigInt64(dataView: DataView, byteOffset: number, littleEndian: boolean | undefined): bigint {
const littleEndianMask = Number(!!littleEndian);
const bigEndianMask = Number(!littleEndian);
return (BigInt(dataView.getInt32(byteOffset, littleEndian) * bigEndianMask + dataView.getInt32(byteOffset + 4, littleEndian) * littleEndianMask) << BigInt32) |
BigInt(dataView.getUint32(byteOffset, littleEndian) * littleEndianMask + dataView.getUint32(byteOffset + 4, littleEndian) * bigEndianMask);
}
export function setBigInt64(dataView: DataView, byteOffset: number, value: bigint, littleEndian: boolean | undefined) { export function setBigInt64(dataView: DataView, byteOffset: number, value: bigint, littleEndian: boolean | undefined) {
const hi = Number(value >> BigInt32); const hi = Number(value >> BigInt32);
const lo = Number(value & BigInt(0xFFFFFFFF)); const lo = Number(value & BigInt(0xFFFFFFFF));
@ -47,3 +41,16 @@ export function setBigInt64(dataView: DataView, byteOffset: number, value: bigin
dataView.setUint32(byteOffset + 4, lo, littleEndian); dataView.setUint32(byteOffset + 4, lo, littleEndian);
} }
} }
export function setBigUint64(dataView: DataView, byteOffset: number, value: bigint, littleEndian: boolean | undefined) {
const hi = Number(value >> BigInt32);
const lo = Number(value & BigInt(0xFFFFFFFF));
if (littleEndian) {
dataView.setUint32(byteOffset + 4, hi, littleEndian);
dataView.setUint32(byteOffset, lo, littleEndian);
} else {
dataView.setUint32(byteOffset, hi, littleEndian);
dataView.setUint32(byteOffset + 4, lo, littleEndian);
}
};

View file

@ -70,28 +70,25 @@ const buffer = MyStruct.serialize({
## Compatibility ## Compatibility
| | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js | | | Chrome | Edge | Firefox | Internet Explorer | Safari | Node.js |
| --------------------------------------------------------------- | ------ | ---- | ------- | ----------------- | ------------------ | -------------------- | | ------------------------------------------------------------ | ------ | ---- | ------- | ----------------- | ------ | ------------------- |
| **Basic usage** | 32 | 12 | 29 | 10<sup>1</sup> | 8 | 0.12 | | **Basic usage** | 32 | 12 | 29 | 10<sup>1</sup> | 8 | 0.12 |
| &nbsp;&nbsp;&nbsp;&nbsp;[`Promise`][MDN_Promise] | 32 | 12 | 29 | No<sup>1</sup> | 8 | 0.12 | | &nbsp;&nbsp;&nbsp;&nbsp;[`Promise`][MDN_Promise] | 32 | 12 | 29 | No<sup>1</sup> | 8 | 0.12 |
| &nbsp;&nbsp;&nbsp;&nbsp;[`ArrayBuffer`][MDN_ArrayBuffer] | 7 | 12 | 4 | 10 | 5.1 | 0.10 | | &nbsp;&nbsp;&nbsp;&nbsp;[`ArrayBuffer`][MDN_ArrayBuffer] | 7 | 12 | 4 | 10 | 5.1 | 0.10 |
| &nbsp;&nbsp;&nbsp;&nbsp;[`Uint8Array`][MDN_Uint8Array] | 7 | 12 | 4 | 10 | 5.1 | 0.10 | | &nbsp;&nbsp;&nbsp;&nbsp;[`Uint8Array`][MDN_Uint8Array] | 7 | 12 | 4 | 10 | 5.1 | 0.10 |
| &nbsp;&nbsp;&nbsp;&nbsp;[`DataView`][MDN_DataView] | 9 | 12 | 15 | 10 | 5.1 | 0.10 | | &nbsp;&nbsp;&nbsp;&nbsp;[`DataView`][MDN_DataView] | 9 | 12 | 15 | 10 | 5.1 | 0.10 |
| **Use [`int64`/`uint64`](#int64uint64) API** | 67 | 79 | 68 | No<sup>2</sup> | 14<sup>3</sup>, 15 | 10.4<sup>5</sup>, 11 | | **Use [`int64`/`uint64`](#int64uint64) type** | 67 | 79 | 68 | No<sup>2</sup> | 14 | 10.4 |
| &nbsp;&nbsp;&nbsp;&nbsp;[`BigInt`][MDN_BigInt] | 67 | 79 | 68 | No<sup>2</sup> | 14 | 10.4 | | &nbsp;&nbsp;&nbsp;&nbsp;[`BigInt`][MDN_BigInt] | 67 | 79 | 68 | No<sup>2</sup> | 14 | 10.4 |
| &nbsp;&nbsp;&nbsp;&nbsp;[`DataView`][MDN_DataView] `BigInt` API | 67 | 79 | 68 | No | 15 | 10.4 | | **Use [`string`](#arraybufferuint8clampedarraystring) type** | 38 | 79 | 29 | 10<sup>3</sup> | 10.1 | 8.3<sup>4</sup>, 11 |
| **Use [`string`](#arraybufferuint8clampedarraystring) API** | 38 | 79 | 29 | 10<sup>4</sup> | 10.1 | 8.3<sup>5</sup>, 11 | | &nbsp;&nbsp;&nbsp;&nbsp;[`TextEncoder`][MDN_TextEncoder] | 38 | 79 | 19 | No | 10.1 | 11 |
| &nbsp;&nbsp;&nbsp;&nbsp;[`TextEncoder`][MDN_TextEncoder] | 38 | 79 | 19 | No | 10.1 | 11 |
<sup>1</sup> Requires a polyfill for Promise (e.g. [promise-polyfill](https://www.npmjs.com/package/promise-polyfill)) <sup>1</sup> Requires a polyfill for Promise (e.g. [promise-polyfill](https://www.npmjs.com/package/promise-polyfill))
<sup>2</sup> `BigInt` can't be polyfilled <sup>2</sup> `BigInt` can't be polyfilled
<sup>3</sup> `int64` requires a polyfill for `DataView#getBigUint64` and `DataView#setBigUint64`; `uint64` requires a polyfill for `DataView#getBigInt64` and `DataView#setBigInt64` <sup>3</sup> Requires a polyfill for `TextEncoder` and `TextDecoder` (e.g. [fast-text-encoding](https://www.npmjs.com/package/fast-text-encoding))
<sup>4</sup> Requires a polyfill for `TextEncoder` and `TextDecoder` (e.g. [fast-text-encoding](https://www.npmjs.com/package/fast-text-encoding)) <sup>4</sup> `TextEncoder` and `TextDecoder` are only available in `util` module. Must be assigned to `globalThis`.
<sup>5</sup> `TextEncoder` and `TextDecoder` are only available in `util` module. Must be assigned to global object.
[MDN_Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise [MDN_Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
[MDN_ArrayBuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer [MDN_ArrayBuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer

View file

@ -34,7 +34,8 @@
}, },
"dependencies": { "dependencies": {
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"tslib": "^2.3.1" "tslib": "^2.3.1",
"@yume-chan/dataview-bigint-polyfill": "0.0.10"
}, },
"devDependencies": { "devDependencies": {
"jest": "^26.6.3", "jest": "^26.6.3",

View file

@ -29,11 +29,11 @@ export class StructValue {
} }
/** /**
* Gets a previously `StructFieldValue` for `key` * Gets the `StructFieldValue` for `key`
* *
* @param key The field name * @param key The field name
*/ */
public get(key: PropertyKey): StructFieldValue { public get(key: PropertyKey): StructFieldValue {
return this.fieldValues[key]; return this.fieldValues[key]!;
} }
} }

View file

@ -1,6 +1,6 @@
import { StructAsyncDeserializeStream, StructDefaultOptions, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from './basic'; import { StructAsyncDeserializeStream, StructDefaultOptions, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from './basic';
import { Struct } from './struct'; import { Struct } from './struct';
import { ArrayBufferFieldType, FixedLengthArrayBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition } from './types'; import { ArrayBufferFieldType, BigIntFieldDefinition, BigIntFieldType, FixedLengthArrayBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition } from './types';
import { ValueOrPromise } from './utils'; import { ValueOrPromise } from './utils';
class MockDeserializationStream implements StructDeserializeStream { class MockDeserializationStream implements StructDeserializeStream {
@ -86,7 +86,7 @@ describe('Struct', () => {
struct.int8('foo'); struct.int8('foo');
expect(struct).toHaveProperty('size', 1); expect(struct).toHaveProperty('size', 1);
const definition = struct['_fields'][0][1] as NumberFieldDefinition; const definition = struct['_fields'][0]![1] as NumberFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(NumberFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Int8); expect(definition.type).toBe(NumberFieldType.Int8);
}); });
@ -96,7 +96,7 @@ describe('Struct', () => {
struct.uint8('foo'); struct.uint8('foo');
expect(struct).toHaveProperty('size', 1); expect(struct).toHaveProperty('size', 1);
const definition = struct['_fields'][0][1] as NumberFieldDefinition; const definition = struct['_fields'][0]![1] as NumberFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(NumberFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Uint8); expect(definition.type).toBe(NumberFieldType.Uint8);
}); });
@ -106,7 +106,7 @@ describe('Struct', () => {
struct.int16('foo'); struct.int16('foo');
expect(struct).toHaveProperty('size', 2); expect(struct).toHaveProperty('size', 2);
const definition = struct['_fields'][0][1] as NumberFieldDefinition; const definition = struct['_fields'][0]![1] as NumberFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(NumberFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Int16); expect(definition.type).toBe(NumberFieldType.Int16);
}); });
@ -116,7 +116,7 @@ describe('Struct', () => {
struct.uint16('foo'); struct.uint16('foo');
expect(struct).toHaveProperty('size', 2); expect(struct).toHaveProperty('size', 2);
const definition = struct['_fields'][0][1] as NumberFieldDefinition; const definition = struct['_fields'][0]![1] as NumberFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(NumberFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Uint16); expect(definition.type).toBe(NumberFieldType.Uint16);
}); });
@ -126,7 +126,7 @@ describe('Struct', () => {
struct.int32('foo'); struct.int32('foo');
expect(struct).toHaveProperty('size', 4); expect(struct).toHaveProperty('size', 4);
const definition = struct['_fields'][0][1] as NumberFieldDefinition; const definition = struct['_fields'][0]![1] as NumberFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(NumberFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Int32); expect(definition.type).toBe(NumberFieldType.Int32);
}); });
@ -136,7 +136,7 @@ describe('Struct', () => {
struct.uint32('foo'); struct.uint32('foo');
expect(struct).toHaveProperty('size', 4); expect(struct).toHaveProperty('size', 4);
const definition = struct['_fields'][0][1] as NumberFieldDefinition; const definition = struct['_fields'][0]![1] as NumberFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(NumberFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Uint32); expect(definition.type).toBe(NumberFieldType.Uint32);
}); });
@ -146,9 +146,9 @@ describe('Struct', () => {
struct.int64('foo'); struct.int64('foo');
expect(struct).toHaveProperty('size', 8); expect(struct).toHaveProperty('size', 8);
const definition = struct['_fields'][0][1] as NumberFieldDefinition; const definition = struct['_fields'][0]![1] as BigIntFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(BigIntFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Int64); expect(definition.type).toBe(BigIntFieldType.Int64);
}); });
it('`uint64` should append an `uint64` field', () => { it('`uint64` should append an `uint64` field', () => {
@ -156,9 +156,9 @@ describe('Struct', () => {
struct.uint64('foo'); struct.uint64('foo');
expect(struct).toHaveProperty('size', 8); expect(struct).toHaveProperty('size', 8);
const definition = struct['_fields'][0][1] as NumberFieldDefinition; const definition = struct['_fields'][0]![1] as BigIntFieldDefinition;
expect(definition).toBeInstanceOf(NumberFieldDefinition); expect(definition).toBeInstanceOf(BigIntFieldDefinition);
expect(definition.type).toBe(NumberFieldType.Uint64); expect(definition.type).toBe(BigIntFieldType.Uint64);
}); });
describe('#arrayBufferLike', () => { describe('#arrayBufferLike', () => {
@ -168,7 +168,7 @@ describe('Struct', () => {
struct.arrayBuffer('foo', { length: 10 }); struct.arrayBuffer('foo', { length: 10 });
expect(struct).toHaveProperty('size', 10); expect(struct).toHaveProperty('size', 10);
const definition = struct['_fields'][0][1] as FixedLengthArrayBufferLikeFieldDefinition; const definition = struct['_fields'][0]![1] as FixedLengthArrayBufferLikeFieldDefinition;
expect(definition).toBeInstanceOf(FixedLengthArrayBufferLikeFieldDefinition); expect(definition).toBeInstanceOf(FixedLengthArrayBufferLikeFieldDefinition);
expect(definition.type).toBeInstanceOf(ArrayBufferFieldType); expect(definition.type).toBeInstanceOf(ArrayBufferFieldType);
expect(definition.options.length).toBe(10); expect(definition.options.length).toBe(10);
@ -179,7 +179,7 @@ describe('Struct', () => {
struct.uint8ClampedArray('foo', { length: 10 }); struct.uint8ClampedArray('foo', { length: 10 });
expect(struct).toHaveProperty('size', 10); expect(struct).toHaveProperty('size', 10);
const definition = struct['_fields'][0][1] as FixedLengthArrayBufferLikeFieldDefinition; const definition = struct['_fields'][0]![1] as FixedLengthArrayBufferLikeFieldDefinition;
expect(definition).toBeInstanceOf(FixedLengthArrayBufferLikeFieldDefinition); expect(definition).toBeInstanceOf(FixedLengthArrayBufferLikeFieldDefinition);
expect(definition.type).toBeInstanceOf(Uint8ClampedArrayFieldType); expect(definition.type).toBeInstanceOf(Uint8ClampedArrayFieldType);
expect(definition.options.length).toBe(10); expect(definition.options.length).toBe(10);
@ -190,7 +190,7 @@ describe('Struct', () => {
struct.string('foo', { length: 10 }); struct.string('foo', { length: 10 });
expect(struct).toHaveProperty('size', 10); expect(struct).toHaveProperty('size', 10);
const definition = struct['_fields'][0][1] as FixedLengthArrayBufferLikeFieldDefinition; const definition = struct['_fields'][0]![1] as FixedLengthArrayBufferLikeFieldDefinition;
expect(definition).toBeInstanceOf(FixedLengthArrayBufferLikeFieldDefinition); expect(definition).toBeInstanceOf(FixedLengthArrayBufferLikeFieldDefinition);
expect(definition.type).toBeInstanceOf(StringFieldType); expect(definition.type).toBeInstanceOf(StringFieldType);
expect(definition.options.length).toBe(10); expect(definition.options.length).toBe(10);
@ -205,7 +205,7 @@ describe('Struct', () => {
struct.arrayBuffer('bar', { lengthField: 'barLength' }); struct.arrayBuffer('bar', { lengthField: 'barLength' });
expect(struct).toHaveProperty('size', 1); expect(struct).toHaveProperty('size', 1);
const definition = struct['_fields'][1][1] as VariableLengthArrayBufferLikeFieldDefinition; const definition = struct['_fields'][1]![1] as VariableLengthArrayBufferLikeFieldDefinition;
expect(definition).toBeInstanceOf(VariableLengthArrayBufferLikeFieldDefinition); expect(definition).toBeInstanceOf(VariableLengthArrayBufferLikeFieldDefinition);
expect(definition.type).toBeInstanceOf(ArrayBufferFieldType); expect(definition.type).toBeInstanceOf(ArrayBufferFieldType);
expect(definition.options.lengthField).toBe('barLength'); expect(definition.options.lengthField).toBe('barLength');
@ -218,7 +218,7 @@ describe('Struct', () => {
struct.uint8ClampedArray('bar', { lengthField: 'barLength' }); struct.uint8ClampedArray('bar', { lengthField: 'barLength' });
expect(struct).toHaveProperty('size', 1); expect(struct).toHaveProperty('size', 1);
const definition = struct['_fields'][1][1] as VariableLengthArrayBufferLikeFieldDefinition; const definition = struct['_fields'][1]![1] as VariableLengthArrayBufferLikeFieldDefinition;
expect(definition).toBeInstanceOf(VariableLengthArrayBufferLikeFieldDefinition); expect(definition).toBeInstanceOf(VariableLengthArrayBufferLikeFieldDefinition);
expect(definition.type).toBeInstanceOf(Uint8ClampedArrayFieldType); expect(definition.type).toBeInstanceOf(Uint8ClampedArrayFieldType);
expect(definition.options.lengthField).toBe('barLength'); expect(definition.options.lengthField).toBe('barLength');
@ -232,7 +232,7 @@ describe('Struct', () => {
struct.string('bar', { lengthField: 'barLength' }); struct.string('bar', { lengthField: 'barLength' });
expect(struct).toHaveProperty('size', 1); expect(struct).toHaveProperty('size', 1);
const definition = struct['_fields'][1][1] as VariableLengthArrayBufferLikeFieldDefinition; const definition = struct['_fields'][1]![1] as VariableLengthArrayBufferLikeFieldDefinition;
expect(definition).toBeInstanceOf(VariableLengthArrayBufferLikeFieldDefinition); expect(definition).toBeInstanceOf(VariableLengthArrayBufferLikeFieldDefinition);
expect(definition.type).toBeInstanceOf(StringFieldType); expect(definition.type).toBeInstanceOf(StringFieldType);
expect(definition.options.lengthField).toBe('barLength'); expect(definition.options.lengthField).toBe('barLength');
@ -251,21 +251,21 @@ describe('Struct', () => {
.fields(sub) .fields(sub)
.int64('int64'); .int64('int64');
const field0 = struct['_fields'][0]; const field0 = struct['_fields'][0]!;
expect(field0).toHaveProperty('0', 'int8'); expect(field0).toHaveProperty('0', 'int8');
expect(field0[1]).toHaveProperty('type', NumberFieldType.Int8); expect(field0[1]).toHaveProperty('type', NumberFieldType.Int8);
const field1 = struct['_fields'][1]; const field1 = struct['_fields'][1]!;
expect(field1).toHaveProperty('0', 'int16'); expect(field1).toHaveProperty('0', 'int16');
expect(field1[1]).toHaveProperty('type', NumberFieldType.Int16); expect(field1[1]).toHaveProperty('type', NumberFieldType.Int16);
const field2 = struct['_fields'][2]; const field2 = struct['_fields'][2]!;
expect(field2).toHaveProperty('0', 'int32'); expect(field2).toHaveProperty('0', 'int32');
expect(field2[1]).toHaveProperty('type', NumberFieldType.Int32); expect(field2[1]).toHaveProperty('type', NumberFieldType.Int32);
const field3 = struct['_fields'][3]; const field3 = struct['_fields'][3]!;
expect(field3).toHaveProperty('0', 'int64'); expect(field3).toHaveProperty('0', 'int64');
expect(field3[1]).toHaveProperty('type', NumberFieldType.Int64); expect(field3[1]).toHaveProperty('type', BigIntFieldType.Int64);
}); });
}); });
@ -396,7 +396,6 @@ describe('Struct', () => {
.int8('foo') .int8('foo')
.int16('bar'); .int16('bar');
const stream = new MockDeserializationStream();
const result = new Uint8Array(struct.serialize({ foo: 0x42, bar: 0x1024 })); const result = new Uint8Array(struct.serialize({ foo: 0x42, bar: 0x1024 }));
expect(result).toEqual(new Uint8Array([0x42, 0x10, 0x24])); expect(result).toEqual(new Uint8Array([0x42, 0x10, 0x24]));
@ -407,7 +406,6 @@ describe('Struct', () => {
.int8('fooLength') .int8('fooLength')
.arrayBuffer('foo', { lengthField: 'fooLength' }); .arrayBuffer('foo', { lengthField: 'fooLength' });
const stream = new MockDeserializationStream();
const result = new Uint8Array(struct.serialize({ foo: new Uint8Array([0x03, 0x04, 0x05]).buffer })); const result = new Uint8Array(struct.serialize({ foo: new Uint8Array([0x03, 0x04, 0x05]).buffer }));
expect(result).toEqual(new Uint8Array([0x03, 0x03, 0x04, 0x05])); expect(result).toEqual(new Uint8Array([0x03, 0x03, 0x04, 0x05]));

View file

@ -1,7 +1,7 @@
import type { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions } from './basic'; import type { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions } from './basic';
import { StructDefaultOptions, StructValue } from './basic'; import { StructDefaultOptions, StructValue } from './basic';
import { Syncbird } from "./syncbird"; import { Syncbird } from "./syncbird";
import { ArrayBufferFieldType, ArrayBufferLikeFieldType, FixedLengthArrayBufferLikeFieldDefinition, FixedLengthArrayBufferLikeFieldOptions, LengthField, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldOptions } from './types'; import { ArrayBufferFieldType, ArrayBufferLikeFieldType, BigIntFieldDefinition, BigIntFieldType, FixedLengthArrayBufferLikeFieldDefinition, FixedLengthArrayBufferLikeFieldOptions, LengthField, NumberFieldDefinition, NumberFieldType, StringFieldType, Uint8ClampedArrayFieldType, VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldOptions } from './types';
import { Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils"; import { Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils";
export interface StructLike<TValue> { export interface StructLike<TValue> {
@ -185,7 +185,7 @@ export class Struct<
private _extra: PropertyDescriptorMap = {}; private _extra: PropertyDescriptorMap = {};
private _postDeserialized?: StructPostDeserialized<any, any>; private _postDeserialized?: StructPostDeserialized<any, any> | undefined;
public constructor(options?: Partial<Readonly<StructOptions>>) { public constructor(options?: Partial<Readonly<StructOptions>>) {
this.options = { ...StructDefaultOptions, ...options }; this.options = { ...StructDefaultOptions, ...options };
@ -359,6 +359,21 @@ export class Struct<
); );
} }
private bigint<
TName extends PropertyKey,
TType extends BigIntFieldType = BigIntFieldType,
TTypeScriptType = TType['TTypeScriptType']
>(
name: TName,
type: TType,
_typescriptType?: TTypeScriptType,
) {
return this.field(
name,
new BigIntFieldDefinition(type, _typescriptType),
);
}
/** /**
* Appends an `int64` field to the `Struct` * Appends an `int64` field to the `Struct`
* *
@ -366,14 +381,14 @@ export class Struct<
*/ */
public int64< public int64<
TName extends PropertyKey, TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Int64']['TTypeScriptType'] TTypeScriptType = BigIntFieldType['TTypeScriptType']
>( >(
name: TName, name: TName,
_typescriptType?: TTypeScriptType, _typescriptType?: TTypeScriptType,
) { ) {
return this.number( return this.bigint(
name, name,
NumberFieldType.Int64, BigIntFieldType.Int64,
_typescriptType _typescriptType
); );
} }
@ -385,14 +400,14 @@ export class Struct<
*/ */
public uint64< public uint64<
TName extends PropertyKey, TName extends PropertyKey,
TTypeScriptType = (typeof NumberFieldType)['Uint64']['TTypeScriptType'] TTypeScriptType = BigIntFieldType['TTypeScriptType']
>( >(
name: TName, name: TName,
_typescriptType?: TTypeScriptType, _typescriptType?: TTypeScriptType,
) { ) {
return this.number( return this.bigint(
name, name,
NumberFieldType.Uint64, BigIntFieldType.Int64,
_typescriptType _typescriptType
); );
} }

View file

@ -0,0 +1,101 @@
import { getBigInt64, getBigUint64, setBigInt64, setBigUint64 } from '@yume-chan/dataview-bigint-polyfill/esm/fallback';
import { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from "../basic";
import { Syncbird } from "../syncbird";
import { ValueOrPromise } from "../utils";
type DataViewBigInt64Getter = (dataView: DataView, byteOffset: number, littleEndian: boolean | undefined) => bigint;
type DataViewBigInt64Setter = (dataView: DataView, byteOffset: number, value: bigint, littleEndian: boolean | undefined) => void;
export class BigIntFieldType {
public readonly TTypeScriptType!: bigint;
public readonly size: number;
public readonly getter: DataViewBigInt64Getter;
public readonly setter: DataViewBigInt64Setter;
public constructor(
size: number,
getter: DataViewBigInt64Getter,
setter: DataViewBigInt64Setter,
) {
this.size = size;
this.getter = getter;
this.setter = setter;
}
public static readonly Int64 = new BigIntFieldType(8, getBigInt64, setBigInt64);
public static readonly Uint64 = new BigIntFieldType(8, getBigUint64, setBigUint64);
}
export class BigIntFieldDefinition<
TType extends BigIntFieldType = BigIntFieldType,
TTypeScriptType = TType["TTypeScriptType"],
> extends StructFieldDefinition<
void,
TTypeScriptType
> {
public readonly type: TType;
public constructor(type: TType, _typescriptType?: TTypeScriptType) {
super();
this.type = type;
}
public getSize(): number {
return this.type.size;
}
public create(
options: Readonly<StructOptions>,
struct: StructValue,
value: TTypeScriptType,
): BigIntFieldValue<this> {
return new BigIntFieldValue(this, options, struct, value);
}
public override deserialize(
options: Readonly<StructOptions>,
stream: StructDeserializeStream,
struct: StructValue,
): BigIntFieldValue<this>;
public override deserialize(
options: Readonly<StructOptions>,
stream: StructAsyncDeserializeStream,
struct: StructValue,
): Promise<BigIntFieldValue<this>>;
public override deserialize(
options: Readonly<StructOptions>,
stream: StructDeserializeStream | StructAsyncDeserializeStream,
struct: StructValue,
): ValueOrPromise<BigIntFieldValue<this>> {
return Syncbird.try(() => {
return stream.read(this.getSize());
}).then(buffer => {
const view = new DataView(buffer);
const value = this.type.getter(
view,
0,
options.littleEndian
);
return this.create(options, struct, value as any);
}).valueOrPromise();
}
}
export class BigIntFieldValue<
TDefinition extends BigIntFieldDefinition<BigIntFieldType, any>,
> extends StructFieldValue<TDefinition> {
public serialize(dataView: DataView, offset: number): void {
this.definition.type.setter(
dataView,
offset,
this.value!,
this.options.littleEndian
);
}
}

View file

@ -1,4 +1,5 @@
export * from './array-buffer'; export * from './array-buffer';
export * from './bigint';
export * from './fixed-length-array-buffer'; export * from './fixed-length-array-buffer';
export * from './number'; export * from './number';
export * from './variable-length-array-buffer'; export * from './variable-length-array-buffer';

View file

@ -8,8 +8,8 @@ export type DataViewGetters =
export type DataViewSetters = export type DataViewSetters =
{ [TKey in keyof DataView]: TKey extends `set${string}` ? TKey : never }[keyof DataView]; { [TKey in keyof DataView]: TKey extends `set${string}` ? TKey : never }[keyof DataView];
export class NumberFieldType<TTypeScriptType extends number | bigint = number | bigint> { export class NumberFieldType {
public readonly TTypeScriptType!: TTypeScriptType; public readonly TTypeScriptType!: number;
public readonly size: number; public readonly size: number;
@ -27,26 +27,22 @@ export class NumberFieldType<TTypeScriptType extends number | bigint = number |
this.dataViewSetter = dataViewSetter; this.dataViewSetter = dataViewSetter;
} }
public static readonly Int8 = new NumberFieldType<number>(1, 'getInt8', 'setInt8'); public static readonly Int8 = new NumberFieldType(1, 'getInt8', 'setInt8');
public static readonly Uint8 = new NumberFieldType<number>(1, 'getUint8', 'setUint8'); public static readonly Uint8 = new NumberFieldType(1, 'getUint8', 'setUint8');
public static readonly Int16 = new NumberFieldType<number>(2, 'getInt16', 'setInt16'); public static readonly Int16 = new NumberFieldType(2, 'getInt16', 'setInt16');
public static readonly Uint16 = new NumberFieldType<number>(2, 'getUint16', 'setUint16'); public static readonly Uint16 = new NumberFieldType(2, 'getUint16', 'setUint16');
public static readonly Int32 = new NumberFieldType<number>(4, 'getInt32', 'setInt32'); public static readonly Int32 = new NumberFieldType(4, 'getInt32', 'setInt32');
public static readonly Uint32 = new NumberFieldType<number>(4, 'getUint32', 'setUint32'); public static readonly Uint32 = new NumberFieldType(4, 'getUint32', 'setUint32');
public static readonly Int64 = new NumberFieldType<bigint>(8, 'getBigInt64', 'setBigInt64');
public static readonly Uint64 = new NumberFieldType<bigint>(8, 'getBigUint64', 'setBigUint64');
} }
export class NumberFieldDefinition< export class NumberFieldDefinition<
TType extends NumberFieldType = NumberFieldType, TType extends NumberFieldType = NumberFieldType,
TTypeScriptType = TType['TTypeScriptType'], TTypeScriptType = TType["TTypeScriptType"],
> extends StructFieldDefinition< > extends StructFieldDefinition<
void, void,
TTypeScriptType TTypeScriptType
@ -92,8 +88,8 @@ export class NumberFieldDefinition<
const value = view[this.type.dataViewGetter]( const value = view[this.type.dataViewGetter](
0, 0,
options.littleEndian options.littleEndian
) as any; );
return this.create(options, struct, value); return this.create(options, struct, value as any);
}).valueOrPromise(); }).valueOrPromise();
} }
} }

View file

@ -438,7 +438,9 @@
}, },
{ {
"packageName": "@yume-chan/dataview-bigint-polyfill", "packageName": "@yume-chan/dataview-bigint-polyfill",
"projectFolder": "libraries/dataview-bigint-polyfill" "projectFolder": "libraries/dataview-bigint-polyfill",
"shouldPublish": true,
"versionPolicyName": "adb"
}, },
{ {
"packageName": "@yume-chan/event", "packageName": "@yume-chan/event",