mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 02:39:26 +02:00
refactor(struct): performance optimization
This commit is contained in:
parent
7d5445aeae
commit
30faaa1438
42 changed files with 2765 additions and 2587 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -11,6 +11,7 @@
|
|||
"CLSE",
|
||||
"CNXN",
|
||||
"Deserialization",
|
||||
"DESERIALIZERS",
|
||||
"Embedder",
|
||||
"fluentui",
|
||||
"genymobile",
|
||||
|
|
1282
common/config/rush/pnpm-lock.yaml
generated
1282
common/config/rush/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -32,7 +32,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"typescript": "4.7.1-rc",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
"tslib": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^27.5.1",
|
||||
"jest": "^28.1.0",
|
||||
"typescript": "4.7.1-rc",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
"build:watch": "build-ts-package --incremental"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^27.5.1",
|
||||
"jest": "^28.1.0",
|
||||
"typescript": "4.7.1-rc",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
},
|
||||
|
|
|
@ -39,12 +39,12 @@
|
|||
"web-streams-polyfill": "^4.0.0-beta.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^28.1.0",
|
||||
"@types/node": "^17.0.17",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"jest": "^27.5.1",
|
||||
"ts-jest": "^27.1.3",
|
||||
"jest": "^28.1.0",
|
||||
"ts-jest": "^28.0.2",
|
||||
"typescript": "4.7.1-rc"
|
||||
}
|
||||
}
|
||||
|
|
125
libraries/adb/src/stream/buffered.spec.ts
Normal file
125
libraries/adb/src/stream/buffered.spec.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
import { describe, expect, it } from '@jest/globals';
|
||||
|
||||
import { BufferedStream } from "./buffered.js";
|
||||
import { ReadableStream } from "./detect.js";
|
||||
|
||||
function randomUint8Array(length: number) {
|
||||
const array = new Uint8Array(length);
|
||||
for (let i = 0; i < length; i++) {
|
||||
array[i] = Math.floor(Math.random() * 256);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
class MockReadableStream extends ReadableStream<Uint8Array> {
|
||||
constructor(buffers: Uint8Array[]) {
|
||||
let index = 0;
|
||||
super({
|
||||
pull(controller) {
|
||||
if (index === buffers.length) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
controller.enqueue(buffers[index]!);
|
||||
index += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function runTest(inputSizes: number[], readSizes: number[]) {
|
||||
const totalSize = inputSizes.reduce((a, b) => a + b, 0);
|
||||
const input = randomUint8Array(totalSize);
|
||||
const buffers: Uint8Array[] = [];
|
||||
let index = 0;
|
||||
for (const size of inputSizes) {
|
||||
buffers.push(input.subarray(index, index + size));
|
||||
index += size;
|
||||
}
|
||||
|
||||
const stream = new MockReadableStream(buffers);
|
||||
const buffered = new BufferedStream(stream);
|
||||
|
||||
index = 0;
|
||||
for (const size of readSizes) {
|
||||
const buffer = await buffered.read(size);
|
||||
expect(buffer).toEqual(input.subarray(index, index + size));
|
||||
index += size;
|
||||
}
|
||||
}
|
||||
|
||||
describe('BufferedStream', () => {
|
||||
describe('read 1 time', () => {
|
||||
it('read 0 buffer', async () => {
|
||||
const source = new MockReadableStream([]);
|
||||
const buffered = new BufferedStream(source);
|
||||
await expect(buffered.read(10)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('input 1 exact buffer', async () => {
|
||||
const input = randomUint8Array(10);
|
||||
const source = new MockReadableStream([input]);
|
||||
const buffered = new BufferedStream(source);
|
||||
await expect(buffered.read(10)).resolves.toBe(input);
|
||||
});
|
||||
|
||||
it('input 1 large buffer', () => {
|
||||
return runTest([20], [10]);
|
||||
});
|
||||
|
||||
it('read 1 small buffer', async () => {
|
||||
const source = new MockReadableStream([randomUint8Array(5)]);
|
||||
const buffered = new BufferedStream(source);
|
||||
await expect(buffered.read(10)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('input 2 small buffers', () => {
|
||||
return runTest([5, 5], [10]);
|
||||
});
|
||||
|
||||
it('read 2 small buffers', async () => {
|
||||
const source = new MockReadableStream([randomUint8Array(5), randomUint8Array(5)]);
|
||||
const buffered = new BufferedStream(source);
|
||||
await expect(buffered.read(20)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('input 2 small + large buffers', () => {
|
||||
return runTest([5, 10], [10]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('read 2 times', () => {
|
||||
it('input 1 exact buffer', () => {
|
||||
return runTest([10], [5, 5]);
|
||||
});
|
||||
|
||||
it('input 1 large buffer', () => {
|
||||
return runTest([20], [5, 5]);
|
||||
});
|
||||
|
||||
it('input 2 exact buffers', () => {
|
||||
return runTest([5, 5], [5, 5]);
|
||||
});
|
||||
|
||||
it('input 2 exact + large buffers', () => {
|
||||
return runTest([5, 10], [5, 8]);
|
||||
});
|
||||
|
||||
it('input 2 small + large buffers', () => {
|
||||
return runTest([5, 10], [7, 8]);
|
||||
});
|
||||
|
||||
it('input 2 large buffers', () => {
|
||||
return runTest([10, 10], [8, 8]);
|
||||
});
|
||||
|
||||
it('input 3 small buffers', () => {
|
||||
return runTest([3, 3, 3], [5, 4]);
|
||||
});
|
||||
|
||||
it('input 3 small buffers 2', () => {
|
||||
return runTest([3, 3, 3], [7, 2]);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
|
@ -13,7 +13,9 @@ export class BufferedStreamEndedError extends Error {
|
|||
}
|
||||
|
||||
export class BufferedStream {
|
||||
private buffer: Uint8Array | undefined;
|
||||
private buffered: Uint8Array | undefined;
|
||||
private bufferedOffset = 0;
|
||||
private bufferedLength = 0;
|
||||
|
||||
protected readonly stream: ReadableStream<Uint8Array>;
|
||||
|
||||
|
@ -30,63 +32,74 @@ export class BufferedStream {
|
|||
* @returns
|
||||
*/
|
||||
public async read(length: number): Promise<Uint8Array> {
|
||||
let array: Uint8Array;
|
||||
let result: Uint8Array;
|
||||
let index: number;
|
||||
if (this.buffer) {
|
||||
const buffer = this.buffer;
|
||||
if (buffer.byteLength > length) {
|
||||
this.buffer = buffer.subarray(length);
|
||||
return buffer.subarray(0, length);
|
||||
|
||||
if (this.buffered) {
|
||||
let array = this.buffered;
|
||||
const offset = this.bufferedOffset;
|
||||
if (this.bufferedLength > length) {
|
||||
// PERF: `subarray` is slow
|
||||
// don't use it until absolutely necessary
|
||||
this.bufferedOffset += length;
|
||||
this.bufferedLength -= length;
|
||||
return array.subarray(offset, offset + length);
|
||||
}
|
||||
|
||||
array = new Uint8Array(length);
|
||||
array.set(buffer);
|
||||
index = buffer.byteLength;
|
||||
this.buffer = undefined;
|
||||
this.buffered = undefined;
|
||||
array = array.subarray(offset);
|
||||
result = new Uint8Array(length);
|
||||
result.set(array);
|
||||
index = this.bufferedLength;
|
||||
length -= this.bufferedLength;
|
||||
} else {
|
||||
const { done, value } = await this.reader.read();
|
||||
const { done, value: array } = await this.reader.read();
|
||||
if (done) {
|
||||
throw new BufferedStreamEndedError();
|
||||
}
|
||||
|
||||
if (value.byteLength === length) {
|
||||
return value;
|
||||
if (array.byteLength === length) {
|
||||
return array;
|
||||
}
|
||||
|
||||
if (value.byteLength > length) {
|
||||
this.buffer = value.subarray(length);
|
||||
return value.subarray(0, length);
|
||||
if (array.byteLength > length) {
|
||||
this.buffered = array;
|
||||
this.bufferedOffset = length;
|
||||
this.bufferedLength = array.byteLength - length;
|
||||
return array.subarray(0, length);
|
||||
}
|
||||
|
||||
array = new Uint8Array(length);
|
||||
array.set(value);
|
||||
index = value.byteLength;
|
||||
result = new Uint8Array(length);
|
||||
result.set(array);
|
||||
index = array.byteLength;
|
||||
length -= array.byteLength;
|
||||
}
|
||||
|
||||
while (index < length) {
|
||||
const left = length - index;
|
||||
|
||||
const { done, value } = await this.reader.read();
|
||||
while (length > 0) {
|
||||
const { done, value: array } = await this.reader.read();
|
||||
if (done) {
|
||||
throw new BufferedStreamEndedError();
|
||||
}
|
||||
|
||||
if (value.byteLength === left) {
|
||||
array.set(value, index);
|
||||
return array;
|
||||
if (array.byteLength === length) {
|
||||
result.set(array, index);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (value.byteLength > left) {
|
||||
array.set(value.subarray(0, left), index);
|
||||
this.buffer = value.subarray(left);
|
||||
return array;
|
||||
if (array.byteLength > length) {
|
||||
this.buffered = array;
|
||||
this.bufferedOffset = length;
|
||||
this.bufferedLength = array.byteLength - length;
|
||||
result.set(array.subarray(0, length), index);
|
||||
return result;
|
||||
}
|
||||
|
||||
array.set(value, index);
|
||||
index += value.byteLength;
|
||||
result.set(array, index);
|
||||
index += array.byteLength;
|
||||
length -= array.byteLength;
|
||||
}
|
||||
|
||||
return array;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,10 +108,10 @@ export class BufferedStream {
|
|||
* @returns A `ReadableStream`
|
||||
*/
|
||||
public release(): ReadableStream<Uint8Array> {
|
||||
if (this.buffer) {
|
||||
if (this.buffered) {
|
||||
return new PushReadableStream<Uint8Array>(async controller => {
|
||||
// Put the remaining data back to the stream
|
||||
await controller.enqueue(this.buffer!);
|
||||
await controller.enqueue(this.buffered!);
|
||||
|
||||
// Manually pipe the stream
|
||||
while (true) {
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
// cspell: ignore ponyfill
|
||||
|
||||
import type { AbortSignal } from "web-streams-polyfill";
|
||||
// TODO: Upgrade to `web-streams-polyfill@4.0.0-beta.2` once released.
|
||||
// `web-streams-polyfill@4.0.0-beta.1` changed the default export to ponyfill,
|
||||
// But it forgot to include `type` export so it's unusable.
|
||||
// See https://github.com/MattiasBuelens/web-streams-polyfill/pull/107
|
||||
export * from 'web-streams-polyfill';
|
||||
|
||||
/** A controller object that allows you to abort one or more DOM requests as and when desired. */
|
||||
|
|
|
@ -3,3 +3,5 @@
|
|||
// Always use polyfilled version because
|
||||
// Vercel doesn't support Node.js 16 (`streams/web` module) yet
|
||||
export * from './detect.polyfill.js';
|
||||
|
||||
// export * from './detect.native.js';
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { ReadableStream } from "./index.js";
|
||||
import { ReadableStream } from "./detect.js";
|
||||
import { DuplexStreamFactory } from './transform.js';
|
||||
|
||||
describe('DuplexStreamFactory', () => {
|
||||
it('should close all readable', async () => {
|
||||
const factory = new DuplexStreamFactory();
|
||||
const readable = factory.wrapReadable(new ReadableStream());
|
||||
const readable = factory.wrapReadable(new ReadableStream() as any);
|
||||
const reader = readable.getReader();
|
||||
await factory.close();
|
||||
await reader.closed;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
|
||||
"references": [
|
||||
{
|
||||
"path": "../event/tsconfig.json"
|
||||
},
|
||||
{
|
||||
"path": "../struct/tsconfig.json"
|
||||
"path": "../struct/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.test.json"
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
"extends": "./tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"node",
|
||||
"jest",
|
||||
"node"
|
||||
],
|
||||
},
|
||||
"exclude": []
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// cspell: ignore logcat
|
||||
|
||||
import { AdbCommandBase, BufferedStream, BufferedStreamEndedError, DecodeUtf8Stream, ReadableStream, SplitLineStream, WritableStream } from "@yume-chan/adb";
|
||||
import { AdbCommandBase, AdbSubprocessNoneProtocol, BufferedStream, BufferedStreamEndedError, DecodeUtf8Stream, ReadableStream, SplitLineStream, WritableStream } from "@yume-chan/adb";
|
||||
import Struct, { StructAsyncDeserializeStream } from "@yume-chan/struct";
|
||||
|
||||
// `adb logcat` is an alias to `adb shell logcat`
|
||||
|
@ -63,7 +63,9 @@ export interface LogMessage extends LoggerEntry {
|
|||
|
||||
export async function deserializeLogMessage(stream: StructAsyncDeserializeStream): Promise<LogMessage> {
|
||||
const entry = await LoggerEntry.deserialize(stream);
|
||||
await stream.read(entry.headerSize - LoggerEntry.size);
|
||||
if (entry.headerSize !== LoggerEntry.size) {
|
||||
await stream.read(entry.headerSize - LoggerEntry.size);
|
||||
}
|
||||
const priority = (await stream.read(1))[0] as LogPriority;
|
||||
const payload = await stream.read(entry.payloadSize - 1);
|
||||
(entry as any).priority = priority;
|
||||
|
@ -165,7 +167,10 @@ export class Logcat extends AdbCommandBase {
|
|||
'-B',
|
||||
...(options?.pid ? ['--pid', options.pid.toString()] : []),
|
||||
...(options?.ids ? ['-b', Logcat.joinLogId(options.ids)] : [])
|
||||
]);
|
||||
], {
|
||||
// PERF: None protocol is 25% faster then Shell protocol
|
||||
protocols: [AdbSubprocessNoneProtocol],
|
||||
});
|
||||
bufferedStream = new BufferedStream(stdout);
|
||||
},
|
||||
async pull(controller) {
|
||||
|
|
|
@ -31,14 +31,14 @@
|
|||
"scripts": {
|
||||
"build": "build-ts-package",
|
||||
"build:watch": "build-ts-package --incremental",
|
||||
"test": "jest --coverage",
|
||||
"//test": "jest --coverage",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^27.5.1",
|
||||
"jest": "^28.1.0",
|
||||
"typescript": "4.7.1-rc",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
"scripts": {
|
||||
"build": "build-ts-package",
|
||||
"build:watch": "build-ts-package --incremental",
|
||||
"test": "jest --coverage",
|
||||
"//test": "jest --coverage",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -36,7 +36,7 @@
|
|||
"tslib": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^27.5.1",
|
||||
"jest": "^28.1.0",
|
||||
"typescript": "4.7.1-rc",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0"
|
||||
}
|
||||
|
|
|
@ -42,11 +42,11 @@
|
|||
"tslib": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "^28.1.0",
|
||||
"@types/dom-webcodecs": "^0.1.3",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0",
|
||||
"gh-release-fetch": "^2.0.4",
|
||||
"jest": "^27.5.1",
|
||||
"jest": "^28.1.0",
|
||||
"tinyh264": "^0.0.7",
|
||||
"typescript": "4.7.1-rc",
|
||||
"yuv-buffer": "^1.0.0",
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
module.exports = {
|
||||
testMatch: ['<rootDir>/cjs/**/*.spec.js'],
|
||||
testEnvironment: 'node',
|
||||
/** @type {import('ts-jest').InitialOptionsTsJest} */
|
||||
export default {
|
||||
preset: "ts-jest/presets/default-esm",
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: 'tsconfig.test.json',
|
||||
useESM: true,
|
||||
},
|
||||
},
|
||||
moduleNameMapper: {
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -27,9 +27,9 @@
|
|||
"main": "esm/index.js",
|
||||
"types": "esm/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "build-ts-package",
|
||||
"build:watch": "build-ts-package --incremental",
|
||||
"test": "jest --coverage",
|
||||
"build": "tsc -b tsconfig.build.json",
|
||||
"build:watch": "tsc -b tsconfig.build.json",
|
||||
"test": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --coverage",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -37,10 +37,11 @@
|
|||
"tslib": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^27.5.1",
|
||||
"typescript": "4.7.1-rc",
|
||||
"@jest/globals": "^28.1.0",
|
||||
"@yume-chan/ts-package-builder": "^1.0.0",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/bluebird": "^3.5.36"
|
||||
"cross-env": "^7.0.3",
|
||||
"jest": "^28.1.0",
|
||||
"ts-jest": "^28.0.2",
|
||||
"typescript": "4.7.1-rc"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { describe, expect, it } from '@jest/globals';
|
||||
|
||||
import type { ValueOrPromise } from '../utils.js';
|
||||
import { StructFieldDefinition } from './definition.js';
|
||||
import type { StructFieldValue } from './field-value.js';
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// cspell: ignore Syncbird
|
||||
|
||||
import type { StructAsyncDeserializeStream, StructDeserializeStream } from "./stream.js";
|
||||
import type { StructFieldValue } from "./field-value.js";
|
||||
import type { StructValue } from "./struct-value.js";
|
||||
|
@ -47,7 +45,7 @@ export abstract class StructFieldDefinition<
|
|||
*/
|
||||
public abstract create(
|
||||
options: Readonly<StructOptions>,
|
||||
struct: StructValue,
|
||||
structValue: StructValue,
|
||||
value: TValue,
|
||||
): StructFieldValue<this>;
|
||||
|
||||
|
@ -55,12 +53,12 @@ export abstract class StructFieldDefinition<
|
|||
* When implemented in derived classes,It must be synchronous (returns a value) or asynchronous (returns a `Promise`) depending
|
||||
* on the type of `stream`. reads and creates a `StructFieldValue` from `stream`.
|
||||
*
|
||||
* `Syncbird` can be used to make the implementation easier.
|
||||
* `SyncPromise` can be used to simplify implementation.
|
||||
*/
|
||||
public abstract deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
stream: StructDeserializeStream,
|
||||
struct: StructValue,
|
||||
structValue: StructValue,
|
||||
): StructFieldValue<this>;
|
||||
public abstract deserialize(
|
||||
options: Readonly<StructOptions>,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { describe, expect, it } from '@jest/globals';
|
||||
|
||||
import type { ValueOrPromise } from "../utils.js";
|
||||
import { StructFieldDefinition } from "./definition.js";
|
||||
import { StructFieldValue } from "./field-value.js";
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { describe, expect, it } from "@jest/globals";
|
||||
import { StructDefaultOptions } from './options.js';
|
||||
|
||||
describe('StructDefaultOptions', () => {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import { StructValue } from "./struct-value.js";
|
||||
|
||||
describe('StructValue', () => {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import type { StructFieldValue } from "./field-value.js";
|
||||
import { StructFieldValue } from "./field-value.js";
|
||||
|
||||
export const STRUCT_VALUE_SYMBOL = Symbol("struct-value");
|
||||
|
||||
/**
|
||||
* A struct value is a map between keys in a struct and their field values.
|
||||
|
@ -11,29 +13,43 @@ export class StructValue {
|
|||
*/
|
||||
public readonly value: Record<PropertyKey, unknown> = {};
|
||||
|
||||
public constructor() {
|
||||
Object.defineProperty(
|
||||
this.value,
|
||||
STRUCT_VALUE_SYMBOL,
|
||||
{ enumerable: false, value: this }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a `StructFieldValue` for `key`
|
||||
*
|
||||
* @param key The field name
|
||||
* @param value The associated `StructFieldValue`
|
||||
* @param name The field name
|
||||
* @param fieldValue The associated `StructFieldValue`
|
||||
*/
|
||||
public set(key: PropertyKey, value: StructFieldValue): void {
|
||||
this.fieldValues[key] = value;
|
||||
public set(name: PropertyKey, fieldValue: StructFieldValue): void {
|
||||
this.fieldValues[name] = fieldValue;
|
||||
|
||||
Object.defineProperty(this.value, key, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() { return value.get(); },
|
||||
set(v) { value.set(v); },
|
||||
});
|
||||
// PERF: `Object.defineProperty` is slow
|
||||
if (fieldValue.get !== StructFieldValue.prototype.get ||
|
||||
fieldValue.set !== StructFieldValue.prototype.set) {
|
||||
Object.defineProperty(this.value, name, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get() { return fieldValue.get(); },
|
||||
set(v) { fieldValue.set(v); },
|
||||
});
|
||||
} else {
|
||||
this.value[name] = fieldValue.get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `StructFieldValue` for `key`
|
||||
*
|
||||
* @param key The field name
|
||||
* @param name The field name
|
||||
*/
|
||||
public get(key: PropertyKey): StructFieldValue {
|
||||
return this.fieldValues[key]!;
|
||||
public get(name: PropertyKey): StructFieldValue {
|
||||
return this.fieldValues[name]!;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { describe, expect, it } from '@jest/globals';
|
||||
|
||||
import Struct from './index.js';
|
||||
|
||||
describe('Struct', () => {
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import { StructAsyncDeserializeStream, StructDefaultOptions, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from './basic/index.js';
|
||||
import { BigIntFieldDefinition, BigIntFieldType, BufferFieldSubType, FixedLengthBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, VariableLengthBufferLikeFieldDefinition } from "./index.js";
|
||||
import { Struct } from './struct.js';
|
||||
import { ArrayBufferFieldType, BigIntFieldDefinition, BigIntFieldType, FixedLengthArrayBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, StringFieldType, ArrayBufferViewFieldType, VariableLengthArrayBufferLikeFieldDefinition } from './types/index.js';
|
||||
import { ValueOrPromise } from './utils.js';
|
||||
import type { ValueOrPromise } from './utils.js';
|
||||
|
||||
class MockDeserializationStream implements StructDeserializeStream {
|
||||
public buffer = new ArrayBuffer(0);
|
||||
public buffer = new Uint8Array(0);
|
||||
|
||||
public read = jest.fn((length: number) => this.buffer);
|
||||
}
|
||||
|
@ -161,27 +163,16 @@ describe('Struct', () => {
|
|||
expect(definition.type).toBe(BigIntFieldType.Uint64);
|
||||
});
|
||||
|
||||
describe('#arrayBufferLike', () => {
|
||||
describe('FixedLengthArrayBufferLikeFieldDefinition', () => {
|
||||
it('`#arrayBuffer` with fixed length', () => {
|
||||
describe('#uint8ArrayLike', () => {
|
||||
describe('FixedLengthBufferLikeFieldDefinition', () => {
|
||||
it('`#uint8Array` with fixed length', () => {
|
||||
let struct = new Struct();
|
||||
struct.arrayBuffer('foo', { length: 10 });
|
||||
struct.uint8Array('foo', { length: 10 });
|
||||
expect(struct).toHaveProperty('size', 10);
|
||||
|
||||
const definition = struct['_fields'][0]![1] as FixedLengthArrayBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(FixedLengthArrayBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(ArrayBufferFieldType);
|
||||
expect(definition.options.length).toBe(10);
|
||||
});
|
||||
|
||||
it('`#uint8ClampedArray` with fixed length', () => {
|
||||
let struct = new Struct();
|
||||
struct.uint8ClampedArray('foo', { length: 10 });
|
||||
expect(struct).toHaveProperty('size', 10);
|
||||
|
||||
const definition = struct['_fields'][0]![1] as FixedLengthArrayBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(FixedLengthArrayBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(ArrayBufferViewFieldType);
|
||||
const definition = struct['_fields'][0]![1] as FixedLengthBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(FixedLengthBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(BufferFieldSubType);
|
||||
expect(definition.options.length).toBe(10);
|
||||
});
|
||||
|
||||
|
@ -190,41 +181,27 @@ describe('Struct', () => {
|
|||
struct.string('foo', { length: 10 });
|
||||
expect(struct).toHaveProperty('size', 10);
|
||||
|
||||
const definition = struct['_fields'][0]![1] as FixedLengthArrayBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(FixedLengthArrayBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(StringFieldType);
|
||||
const definition = struct['_fields'][0]![1] as FixedLengthBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(FixedLengthBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(BufferFieldSubType);
|
||||
expect(definition.options.length).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('VariableLengthArrayBufferLikeFieldDefinition', () => {
|
||||
it('`#arrayBuffer` with variable length', () => {
|
||||
describe('VariableLengthBufferLikeFieldDefinition', () => {
|
||||
it('`#uint8Array` with variable length', () => {
|
||||
const struct = new Struct().int8('barLength');
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
||||
struct.arrayBuffer('bar', { lengthField: 'barLength' });
|
||||
struct.uint8Array('bar', { lengthField: 'barLength' });
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
||||
const definition = struct['_fields'][1]![1] as VariableLengthArrayBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(VariableLengthArrayBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(ArrayBufferFieldType);
|
||||
const definition = struct['_fields'][1]![1] as VariableLengthBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(VariableLengthBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(BufferFieldSubType);
|
||||
expect(definition.options.lengthField).toBe('barLength');
|
||||
});
|
||||
|
||||
it('`#uint8ClampedArray` with variable length', () => {
|
||||
const struct = new Struct().int8('barLength');
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
||||
struct.uint8ClampedArray('bar', { lengthField: 'barLength' });
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
||||
const definition = struct['_fields'][1]![1] as VariableLengthArrayBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(VariableLengthArrayBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(ArrayBufferViewFieldType);
|
||||
expect(definition.options.lengthField).toBe('barLength');
|
||||
});
|
||||
|
||||
|
||||
it('`#string` with variable length', () => {
|
||||
const struct = new Struct().int8('barLength');
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
@ -232,9 +209,9 @@ describe('Struct', () => {
|
|||
struct.string('bar', { lengthField: 'barLength' });
|
||||
expect(struct).toHaveProperty('size', 1);
|
||||
|
||||
const definition = struct['_fields'][1]![1] as VariableLengthArrayBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(VariableLengthArrayBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(StringFieldType);
|
||||
const definition = struct['_fields'][1]![1] as VariableLengthBufferLikeFieldDefinition;
|
||||
expect(definition).toBeInstanceOf(VariableLengthBufferLikeFieldDefinition);
|
||||
expect(definition.type).toBeInstanceOf(BufferFieldSubType);
|
||||
expect(definition.options.lengthField).toBe('barLength');
|
||||
});
|
||||
});
|
||||
|
@ -277,8 +254,8 @@ describe('Struct', () => {
|
|||
|
||||
const stream = new MockDeserializationStream();
|
||||
stream.read
|
||||
.mockReturnValueOnce(new Uint8Array([2]).buffer)
|
||||
.mockReturnValueOnce(new Uint8Array([0, 16]).buffer);
|
||||
.mockReturnValueOnce(new Uint8Array([2]))
|
||||
.mockReturnValueOnce(new Uint8Array([0, 16]));
|
||||
|
||||
const result = await struct.deserialize(stream);
|
||||
expect(result).toEqual({ foo: 2, bar: 16 });
|
||||
|
@ -291,15 +268,15 @@ describe('Struct', () => {
|
|||
it('should deserialize with dynamic size fields', async () => {
|
||||
const struct = new Struct()
|
||||
.int8('fooLength')
|
||||
.uint8ClampedArray('foo', { lengthField: 'fooLength' });
|
||||
.uint8Array('foo', { lengthField: 'fooLength' });
|
||||
|
||||
const stream = new MockDeserializationStream();
|
||||
stream.read
|
||||
.mockReturnValueOnce(new Uint8Array([2]).buffer)
|
||||
.mockReturnValueOnce(new Uint8Array([3, 4]).buffer);
|
||||
.mockReturnValueOnce(new Uint8Array([2]))
|
||||
.mockReturnValueOnce(new Uint8Array([3, 4]));
|
||||
|
||||
const result = await struct.deserialize(stream);
|
||||
expect(result).toEqual({ fooLength: 2, foo: new Uint8ClampedArray([3, 4]) });
|
||||
expect(result).toEqual({ fooLength: 2, foo: new Uint8Array([3, 4]) });
|
||||
expect(stream.read).toBeCalledTimes(2);
|
||||
expect(stream.read).nthCalledWith(1, 1);
|
||||
expect(stream.read).nthCalledWith(2, 2);
|
||||
|
@ -339,40 +316,40 @@ describe('Struct', () => {
|
|||
});
|
||||
|
||||
describe('#postDeserialize', () => {
|
||||
it('can throw errors', async () => {
|
||||
it('can throw errors', () => {
|
||||
const struct = new Struct();
|
||||
const callback = jest.fn(() => { throw new Error('mock'); });
|
||||
struct.postDeserialize(callback);
|
||||
|
||||
const stream = new MockDeserializationStream();
|
||||
expect(struct.deserialize(stream)).rejects.toThrowError('mock');
|
||||
expect(() => struct.deserialize(stream)).toThrowError('mock');
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it('can replace return value', async () => {
|
||||
it('can replace return value', () => {
|
||||
const struct = new Struct();
|
||||
const callback = jest.fn(() => 'mock');
|
||||
struct.postDeserialize(callback);
|
||||
|
||||
const stream = new MockDeserializationStream();
|
||||
expect(struct.deserialize(stream)).resolves.toBe('mock');
|
||||
expect(struct.deserialize(stream)).toBe('mock');
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
expect(callback).toBeCalledWith({});
|
||||
});
|
||||
|
||||
it('can return nothing', async () => {
|
||||
it('can return nothing', () => {
|
||||
const struct = new Struct();
|
||||
const callback = jest.fn();
|
||||
struct.postDeserialize(callback);
|
||||
|
||||
const stream = new MockDeserializationStream();
|
||||
const result = await struct.deserialize(stream);
|
||||
const result = struct.deserialize(stream);
|
||||
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
expect(callback).toBeCalledWith(result);
|
||||
});
|
||||
|
||||
it('should overwrite callback', async () => {
|
||||
it('should overwrite callback', () => {
|
||||
const struct = new Struct();
|
||||
|
||||
const callback1 = jest.fn();
|
||||
|
@ -382,7 +359,7 @@ describe('Struct', () => {
|
|||
struct.postDeserialize(callback2);
|
||||
|
||||
const stream = new MockDeserializationStream();
|
||||
await struct.deserialize(stream);
|
||||
struct.deserialize(stream);
|
||||
|
||||
expect(callback1).toBeCalledTimes(0);
|
||||
expect(callback2).toBeCalledTimes(1);
|
||||
|
@ -404,9 +381,9 @@ describe('Struct', () => {
|
|||
it('should serialize with dynamic size fields', () => {
|
||||
const struct = new Struct()
|
||||
.int8('fooLength')
|
||||
.arrayBuffer('foo', { lengthField: 'fooLength' });
|
||||
.uint8Array('foo', { lengthField: 'fooLength' });
|
||||
|
||||
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]) }));
|
||||
|
||||
expect(result).toEqual(new Uint8Array([0x03, 0x03, 0x04, 0x05]));
|
||||
});
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
// cspell: ignore Syncbird
|
||||
|
||||
import type { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions } from './basic/index.js';
|
||||
import { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, STRUCT_VALUE_SYMBOL } from './basic/index.js';
|
||||
import { StructDefaultOptions, StructValue } from './basic/index.js';
|
||||
import { SyncPromise } from "./sync-promise.js";
|
||||
import { BigIntFieldDefinition, BigIntFieldType, BufferFieldSubType, FixedLengthBufferLikeFieldDefinition, NumberFieldDefinition, NumberFieldType, StringBufferFieldSubType, Uint8ArrayBufferFieldSubType, VariableLengthBufferLikeFieldDefinition, type FixedLengthBufferLikeFieldOptions, type LengthField, type VariableLengthBufferLikeFieldOptions } from './types/index.js';
|
||||
|
@ -542,21 +540,25 @@ export class Struct<
|
|||
public deserialize(
|
||||
stream: StructDeserializeStream | StructAsyncDeserializeStream,
|
||||
): ValueOrPromise<StructDeserializedResult<TFields, TExtra, TPostDeserialized>> {
|
||||
const value = new StructValue();
|
||||
Object.defineProperties(value.value, this._extra);
|
||||
const structValue = new StructValue();
|
||||
Object.defineProperties(structValue.value, this._extra);
|
||||
|
||||
return SyncPromise
|
||||
.each(this._fields, ([name, definition]) => {
|
||||
return SyncPromise
|
||||
.try(() => {
|
||||
return definition.deserialize(this.options, stream as any, value);
|
||||
})
|
||||
.then(fieldValue => {
|
||||
value.set(name, fieldValue);
|
||||
});
|
||||
.try(() => {
|
||||
let result = SyncPromise.resolve();
|
||||
for (const [name, definition] of this._fields) {
|
||||
result = result
|
||||
.then(() =>
|
||||
definition.deserialize(this.options, stream as any, structValue)
|
||||
)
|
||||
.then(fieldValue => {
|
||||
structValue.set(name, fieldValue);
|
||||
});
|
||||
}
|
||||
return result;
|
||||
})
|
||||
.then(() => {
|
||||
const object = value.value;
|
||||
const object = structValue.value;
|
||||
|
||||
// Run `postDeserialized`
|
||||
if (this._postDeserialized) {
|
||||
|
@ -576,18 +578,32 @@ export class Struct<
|
|||
public serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>): Uint8Array;
|
||||
public serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>, output: Uint8Array): number;
|
||||
public serialize(init: Evaluate<Omit<TFields, TOmitInitKey>>, output?: Uint8Array): Uint8Array | number {
|
||||
const value = new StructValue();
|
||||
|
||||
for (const [name, definition] of this._fields) {
|
||||
const fieldValue = definition.create(this.options, value, (init as any)[name]);
|
||||
value.set(name, fieldValue);
|
||||
let structValue: StructValue;
|
||||
if (STRUCT_VALUE_SYMBOL in init) {
|
||||
structValue = (init as any)[STRUCT_VALUE_SYMBOL];
|
||||
for (const [key, value] of Object.entries(init)) {
|
||||
const fieldValue = structValue.get(key);
|
||||
if (fieldValue) {
|
||||
fieldValue.set(value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
structValue = new StructValue();
|
||||
for (const [name, definition] of this._fields) {
|
||||
const fieldValue = definition.create(
|
||||
this.options,
|
||||
structValue,
|
||||
(init as any)[name]
|
||||
);
|
||||
structValue.set(name, fieldValue);
|
||||
}
|
||||
}
|
||||
|
||||
let structSize = 0;
|
||||
const fieldsInfo: { fieldValue: StructFieldValue, size: number; }[] = [];
|
||||
|
||||
for (const [name] of this._fields) {
|
||||
const fieldValue = value.get(name);
|
||||
const fieldValue = structValue.get(name);
|
||||
const size = fieldValue.getSize();
|
||||
fieldsInfo.push({ fieldValue, size });
|
||||
structSize += size;
|
||||
|
|
189
libraries/struct/src/sync-promise.spec.ts
Normal file
189
libraries/struct/src/sync-promise.spec.ts
Normal file
|
@ -0,0 +1,189 @@
|
|||
import { describe, expect, it, jest, test } from "@jest/globals";
|
||||
|
||||
import { SyncPromise } from "./sync-promise.js";
|
||||
|
||||
describe('SyncPromise', () => {
|
||||
describe('constructor', () => {
|
||||
it('should call executor', () => {
|
||||
const executor = jest.fn((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(42);
|
||||
}, 10);
|
||||
});
|
||||
new SyncPromise(executor);
|
||||
expect(executor).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should asynchronously resolve', async () => {
|
||||
const promise = new SyncPromise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(42);
|
||||
}, 10);
|
||||
});
|
||||
await expect(promise).resolves.toBe(42);
|
||||
await expect(promise.then()).resolves.toBe(42);
|
||||
await expect(promise.valueOrPromise()).resolves.toBe(42);
|
||||
});
|
||||
|
||||
it('should asynchronously reject', async () => {
|
||||
const promise = new SyncPromise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('error'));
|
||||
}, 10);
|
||||
});
|
||||
await expect(promise).rejects.toThrow('error');
|
||||
await expect(promise.then()).rejects.toThrow('error');
|
||||
await expect(promise.valueOrPromise()).rejects.toThrow('error');
|
||||
});
|
||||
|
||||
it('should synchronously resolve with value', async () => {
|
||||
const promise = new SyncPromise((resolve) => {
|
||||
resolve(42);
|
||||
});
|
||||
await expect(promise).resolves.toBe(42);
|
||||
await expect(promise.then()).resolves.toBe(42);
|
||||
expect(promise.valueOrPromise()).toBe(42);
|
||||
});
|
||||
|
||||
it('should synchronously resolve with promise', async () => {
|
||||
const promise = new SyncPromise((resolve) => {
|
||||
resolve(
|
||||
new Promise(
|
||||
resolve =>
|
||||
setTimeout(
|
||||
() => resolve(42),
|
||||
10
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
await expect(promise).resolves.toBe(42);
|
||||
await expect(promise.then()).resolves.toBe(42);
|
||||
await expect(promise.valueOrPromise()).resolves.toBe(42);
|
||||
});
|
||||
|
||||
it('should synchronously resolve with resolved SyncPromise', async () => {
|
||||
const promise = new SyncPromise((resolve) => {
|
||||
resolve(
|
||||
new SyncPromise(
|
||||
resolve =>
|
||||
resolve(42),
|
||||
)
|
||||
);
|
||||
});
|
||||
await expect(promise).resolves.toBe(42);
|
||||
await expect(promise.then()).resolves.toBe(42);
|
||||
expect(promise.valueOrPromise()).toBe(42);
|
||||
});
|
||||
|
||||
it('should synchronously resolve with rejected SyncPromise', async () => {
|
||||
const promise = new SyncPromise((resolve) => {
|
||||
resolve(
|
||||
new SyncPromise(
|
||||
(_, reject) =>
|
||||
reject(new Error('error'))
|
||||
)
|
||||
);
|
||||
});
|
||||
await expect(promise).rejects.toThrowError('error');
|
||||
await expect(promise.then()).rejects.toThrowError('error');
|
||||
expect(() => promise.valueOrPromise()).toThrowError('error');
|
||||
});
|
||||
|
||||
it('should synchronously resolve with unsettled SyncPromise', async () => {
|
||||
const promise = new SyncPromise((resolve) => {
|
||||
resolve(
|
||||
new SyncPromise(
|
||||
resolve =>
|
||||
setTimeout(
|
||||
() => resolve(42),
|
||||
10
|
||||
)
|
||||
)
|
||||
);
|
||||
});
|
||||
await expect(promise).resolves.toBe(42);
|
||||
await expect(promise.then()).resolves.toBe(42);
|
||||
await expect(promise.valueOrPromise()).resolves.toBe(42);
|
||||
});
|
||||
|
||||
it('should synchronously reject with error', async () => {
|
||||
const promise = new SyncPromise((_, reject) => {
|
||||
reject(new Error('error'));
|
||||
});
|
||||
await expect(promise).rejects.toThrow('error');
|
||||
await expect(promise.then()).rejects.toThrow('error');
|
||||
expect(() => promise.valueOrPromise()).toThrow('error');
|
||||
});
|
||||
|
||||
it('should catch synchronous error', async () => {
|
||||
const promise = new SyncPromise(() => {
|
||||
throw new Error('error');
|
||||
});
|
||||
await expect(promise).rejects.toThrow('error');
|
||||
await expect(promise.then()).rejects.toThrow('error');
|
||||
expect(() => promise.valueOrPromise()).toThrow('error');
|
||||
});
|
||||
|
||||
describe('should ignore multiple result', () => {
|
||||
test('multiple resolves', async () => {
|
||||
const promise = new SyncPromise((resolve) => {
|
||||
resolve(42);
|
||||
resolve(43);
|
||||
});
|
||||
await expect(promise).resolves.toBe(42);
|
||||
});
|
||||
|
||||
test('multiple rejects', async () => {
|
||||
const promise = new SyncPromise((_, reject) => {
|
||||
reject(new Error('error'));
|
||||
reject(new Error('error2'));
|
||||
});
|
||||
await expect(promise).rejects.toThrow('error');
|
||||
});
|
||||
|
||||
test('mixed', async () => {
|
||||
const promise = new SyncPromise((resolve, reject) => {
|
||||
resolve(42);
|
||||
reject(new Error('error2'));
|
||||
});
|
||||
await expect(promise).resolves.toBe(42);
|
||||
});
|
||||
|
||||
test('mixed with throw', async () => {
|
||||
const promise = new SyncPromise((resolve) => {
|
||||
resolve(42);
|
||||
throw new Error('error2');
|
||||
});
|
||||
await expect(promise).resolves.toBe(42);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#then', () => {
|
||||
it('chain a sync value', async () => {
|
||||
let promise = new SyncPromise(resolve => resolve(42));
|
||||
promise = promise.then(() => 'foo');
|
||||
await expect(promise).resolves.toBe('foo');
|
||||
await expect(promise.then()).resolves.toBe('foo');
|
||||
expect(promise.valueOrPromise()).toBe('foo');
|
||||
});
|
||||
|
||||
it('chain a async value', async () => {
|
||||
let promise = new SyncPromise(resolve => resolve(42));
|
||||
promise = promise.then(
|
||||
() =>
|
||||
new Promise(
|
||||
(resolve) =>
|
||||
setTimeout(
|
||||
() => resolve('foo'),
|
||||
10
|
||||
)
|
||||
)
|
||||
);
|
||||
await expect(promise).resolves.toBe('foo');
|
||||
await expect(promise.then()).resolves.toBe('foo');
|
||||
expect(promise.valueOrPromise()).resolves.toBe('foo');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,48 +1,136 @@
|
|||
export class SyncPromise<T> extends Promise<T> {
|
||||
private resolved: boolean;
|
||||
private rejected: boolean;
|
||||
private result: unknown;
|
||||
// PERF: Once a promise becomes async, it can't go back to sync again,
|
||||
// so bypass all checks in `SyncPromise`
|
||||
class AsyncPromise<T> extends Promise<T> {
|
||||
public valueOrPromise(): T | PromiseLike<T> {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
enum State {
|
||||
Pending,
|
||||
Fulfilled,
|
||||
Rejected,
|
||||
}
|
||||
|
||||
function fulfilledThen<T, TResult1 = T>(
|
||||
this: SyncPromise<T>,
|
||||
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
||||
): SyncPromise<TResult1> {
|
||||
if (onfulfilled) {
|
||||
return SyncPromise.try(() => onfulfilled(this.result as T));
|
||||
}
|
||||
return this as unknown as SyncPromise<TResult1>;
|
||||
}
|
||||
|
||||
function fulfilledValue<T>(
|
||||
this: SyncPromise<T>,
|
||||
) {
|
||||
return this.result as T;
|
||||
}
|
||||
|
||||
function rejectedThen<T, TResult1 = T, TResult2 = never>(
|
||||
this: SyncPromise<T>,
|
||||
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
||||
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
|
||||
) {
|
||||
if (onrejected) {
|
||||
return SyncPromise.try(() => onrejected(this.result));
|
||||
}
|
||||
return this as unknown as SyncPromise<TResult1 | TResult2>;
|
||||
}
|
||||
|
||||
function rejectedValue<T>(
|
||||
this: SyncPromise<T>,
|
||||
): never {
|
||||
throw this.result;
|
||||
}
|
||||
|
||||
interface SyncPromiseThen<T> {
|
||||
<TResult1 = T, TResult2 = never>(
|
||||
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,
|
||||
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined
|
||||
): SyncPromise<TResult1 | TResult2>;
|
||||
}
|
||||
|
||||
export class SyncPromise<T> extends AsyncPromise<T> {
|
||||
// Because `super.then` will only be called when `this` is asynchronously fulfilled,
|
||||
// let it create `AsyncPromise` instead, so asynchronous path won't check for sync state anymore.
|
||||
public static [Symbol.species] = AsyncPromise;
|
||||
|
||||
public static override reject<T = never>(reason?: any): SyncPromise<T> {
|
||||
return new SyncPromise<T>(
|
||||
(resolve, reject) => {
|
||||
reject(reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static override resolve(): SyncPromise<void>;
|
||||
public static override resolve<T>(value: T | PromiseLike<T>): SyncPromise<T>;
|
||||
public static override resolve<T>(value?: T | PromiseLike<T>): SyncPromise<T> {
|
||||
return new SyncPromise((resolve, reject) => {
|
||||
resolve(value!);
|
||||
});
|
||||
}
|
||||
// `Promise.resolve` asynchronously calls `resolve`
|
||||
// So we need to write our own.
|
||||
if (value instanceof SyncPromise) {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static each<T>(array: T[], callback: (item: T, index: number) => SyncPromise<void>): SyncPromise<void> {
|
||||
return array.reduce((prev, item, index) => {
|
||||
return prev.then(() => {
|
||||
return callback(item, index);
|
||||
});
|
||||
}, SyncPromise.resolve());
|
||||
return new SyncPromise<T>(
|
||||
(resolve) => {
|
||||
resolve(value!);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static try<T>(executor: () => T | PromiseLike<T>): SyncPromise<T> {
|
||||
return new SyncPromise((resolve, reject) => {
|
||||
try {
|
||||
resolve(executor());
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
try {
|
||||
return SyncPromise.resolve(executor());
|
||||
} catch (e) {
|
||||
return SyncPromise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
public state: State;
|
||||
public result: unknown;
|
||||
|
||||
public constructor(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void) {
|
||||
let sync = true;
|
||||
let settled = false;
|
||||
let resolved = false;
|
||||
let rejected = false;
|
||||
let state: State = State.Pending;
|
||||
let result: unknown = undefined;
|
||||
let handleThen: SyncPromise<T>['then'] = Promise.prototype.then as any;
|
||||
let handleValueOrPromise: () => T | PromiseLike<T> = () => this;
|
||||
|
||||
const handleResolveCore = (value: T) => {
|
||||
state = State.Fulfilled;
|
||||
result = value;
|
||||
handleThen = fulfilledThen;
|
||||
handleValueOrPromise = fulfilledValue;
|
||||
};
|
||||
|
||||
const handleRejectCore = (reason?: any) => {
|
||||
state = State.Rejected;
|
||||
result = reason;
|
||||
handleThen = rejectedThen;
|
||||
handleValueOrPromise = rejectedValue;
|
||||
};
|
||||
|
||||
let settled = false;
|
||||
let sync = true;
|
||||
|
||||
super((resolve, reject) => {
|
||||
try {
|
||||
executor((value) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
const handleReject = (reason?: any) => {
|
||||
if (settled) { return; }
|
||||
settled = true;
|
||||
|
||||
if (!sync) {
|
||||
reject(reason);
|
||||
return;
|
||||
}
|
||||
|
||||
handleRejectCore(reason);
|
||||
};
|
||||
|
||||
try {
|
||||
executor((value: T | PromiseLike<T>) => {
|
||||
if (settled) { return; }
|
||||
settled = true;
|
||||
|
||||
if (!sync) {
|
||||
|
@ -50,115 +138,41 @@ export class SyncPromise<T> extends Promise<T> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (typeof value === 'object' &&
|
||||
value !== null &&
|
||||
'then' in value &&
|
||||
typeof value.then === 'function'
|
||||
) {
|
||||
if (typeof value === 'object' && value !== null && typeof (value as any).then === 'function') {
|
||||
if (value instanceof SyncPromise) {
|
||||
if (value.resolved) {
|
||||
resolved = true;
|
||||
result = value.result;
|
||||
return;
|
||||
} else if (value.rejected) {
|
||||
rejected = true;
|
||||
result = value.result;
|
||||
return;
|
||||
switch (value.state) {
|
||||
case State.Fulfilled:
|
||||
handleResolveCore(value.result as T);
|
||||
return;
|
||||
case State.Rejected:
|
||||
handleRejectCore(value.result);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
resolve(value);
|
||||
return;
|
||||
} else {
|
||||
handleResolveCore(value as T);
|
||||
}
|
||||
|
||||
resolved = true;
|
||||
result = value;
|
||||
}, (reason) => {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
|
||||
settled = true;
|
||||
|
||||
if (!sync) {
|
||||
reject(reason);
|
||||
return;
|
||||
}
|
||||
|
||||
rejected = true;
|
||||
result = reason;
|
||||
});
|
||||
}, handleReject);
|
||||
} catch (e) {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
|
||||
settled = true;
|
||||
|
||||
if (!sync) {
|
||||
reject(e);
|
||||
return;
|
||||
}
|
||||
|
||||
rejected = true;
|
||||
result = e;
|
||||
handleReject(e);
|
||||
}
|
||||
});
|
||||
|
||||
sync = false;
|
||||
this.resolved = resolved;
|
||||
this.rejected = rejected;
|
||||
this.state = state;
|
||||
this.result = result;
|
||||
this.then = handleThen;
|
||||
this.valueOrPromise = handleValueOrPromise;
|
||||
|
||||
sync = false;
|
||||
}
|
||||
|
||||
public override then<TResult1 = T, TResult2 = never>(
|
||||
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null | undefined,
|
||||
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined
|
||||
): SyncPromise<TResult1 | TResult2> {
|
||||
if (this.resolved) {
|
||||
if (onfulfilled) {
|
||||
return new SyncPromise((resolve, reject) => {
|
||||
try {
|
||||
resolve(onfulfilled(this.result as T));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this as unknown as SyncPromise<TResult1 | TResult2>;
|
||||
}
|
||||
|
||||
if (this.rejected) {
|
||||
if (onrejected) {
|
||||
return new SyncPromise((resolve, reject) => {
|
||||
try {
|
||||
resolve(onrejected(this.result));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this as unknown as SyncPromise<TResult1 | TResult2>;
|
||||
}
|
||||
|
||||
return super.then(onfulfilled, onrejected) as unknown as SyncPromise<TResult1 | TResult2>;
|
||||
}
|
||||
public override then = Promise.prototype.then as SyncPromiseThen<T>;
|
||||
|
||||
public override catch<TResult = never>(
|
||||
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null | undefined
|
||||
): Promise<T | TResult> {
|
||||
): SyncPromise<T | TResult> {
|
||||
return this.then(undefined, onrejected);
|
||||
}
|
||||
|
||||
public valueOrPromise(): T | PromiseLike<T> {
|
||||
if (this.resolved) {
|
||||
return this.result as T;
|
||||
}
|
||||
|
||||
if (this.rejected) {
|
||||
throw this.result;
|
||||
}
|
||||
|
||||
return this as Promise<T>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// cspell: ignore syncbird
|
||||
|
||||
import { getBigInt64, getBigUint64, setBigInt64, setBigUint64 } from '@yume-chan/dataview-bigint-polyfill/esm/fallback.js';
|
||||
import { StructFieldDefinition, StructFieldValue, StructValue, type StructAsyncDeserializeStream, type StructDeserializeStream, type StructOptions } from "../basic/index.js";
|
||||
import { SyncPromise } from "../sync-promise.js";
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import { StructDefaultOptions, StructDeserializeStream, StructValue } from '../../basic/index.js';
|
||||
import { BufferFieldSubType, BufferLikeFieldDefinition, EMPTY_UINT8_ARRAY, StringBufferFieldSubType, Uint8ArrayBufferFieldSubType } from './base.js';
|
||||
|
||||
|
@ -25,8 +27,8 @@ describe('Types', () => {
|
|||
});
|
||||
|
||||
it('`#getSize` should return the `byteLength` of the `Uint8Array`', () => {
|
||||
const arrayBuffer = new Uint8Array(10);
|
||||
expect(Uint8ArrayBufferFieldSubType.Instance.getSize(arrayBuffer)).toBe(10);
|
||||
const array = new Uint8Array(10);
|
||||
expect(Uint8ArrayBufferFieldSubType.Instance.getSize(array)).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -72,7 +74,7 @@ describe('Types', () => {
|
|||
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
|
||||
expect(context.read).toBeCalledTimes(1);
|
||||
expect(context.read).toBeCalledWith(size);
|
||||
expect(fieldValue).toHaveProperty('arrayBuffer', array);
|
||||
expect(fieldValue).toHaveProperty('array', array);
|
||||
|
||||
expect(fieldValue.get()).toBe(array);
|
||||
});
|
||||
|
@ -99,7 +101,7 @@ describe('Types', () => {
|
|||
|
||||
describe('ArrayBufferLikeFieldValue', () => {
|
||||
describe('#set', () => {
|
||||
it('should clear `arrayBuffer` field', async () => {
|
||||
it('should clear `array` field', async () => {
|
||||
const size = 0;
|
||||
const definition = new MockArrayBufferFieldDefinition(Uint8ArrayBufferFieldSubType.Instance, size);
|
||||
|
||||
|
@ -113,12 +115,12 @@ describe('Types', () => {
|
|||
const newValue = new Uint8Array(20);
|
||||
fieldValue.set(newValue);
|
||||
expect(fieldValue.get()).toBe(newValue);
|
||||
expect(fieldValue).toHaveProperty('arrayBuffer', undefined);
|
||||
expect(fieldValue).toHaveProperty('array', undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#serialize', () => {
|
||||
it('should be able to serialize with cached `arrayBuffer`', async () => {
|
||||
it('should be able to serialize with cached `array`', async () => {
|
||||
const size = 0;
|
||||
const definition = new MockArrayBufferFieldDefinition(Uint8ArrayBufferFieldSubType.Instance, size);
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// cspell: ignore syncbird
|
||||
|
||||
import { StructFieldDefinition, StructFieldValue, StructValue, type StructAsyncDeserializeStream, type StructDeserializeStream, type StructOptions } from '../../basic/index.js';
|
||||
import { SyncPromise } from "../../sync-promise.js";
|
||||
import { decodeUtf8, encodeUtf8, type ValueOrPromise } from "../../utils.js";
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { describe, expect, it } from '@jest/globals';
|
||||
|
||||
import { Uint8ArrayBufferFieldSubType } from "./base.js";
|
||||
import { FixedLengthBufferLikeFieldDefinition } from "./fixed-length.js";
|
||||
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import { StructDefaultOptions, StructFieldValue, StructValue } from "../../basic/index.js";
|
||||
import { BufferFieldSubType, EMPTY_UINT8_ARRAY, Uint8ArrayBufferFieldSubType } from "./base.js";
|
||||
import { VariableLengthBufferLikeFieldDefinition, VariableLengthBufferLikeFieldLengthValue, VariableLengthBufferLikeStructFieldValue } from "./variable-length.js";
|
||||
|
||||
class MockOriginalFieldValue extends StructFieldValue {
|
||||
class MockLengthFieldValue extends StructFieldValue {
|
||||
public constructor() {
|
||||
super({} as any, {} as any, {} as any, {});
|
||||
}
|
||||
|
@ -21,8 +23,8 @@ class MockOriginalFieldValue extends StructFieldValue {
|
|||
}
|
||||
|
||||
describe("Types", () => {
|
||||
describe("VariableLengthArrayBufferLikeFieldLengthValue", () => {
|
||||
class MockArrayBufferFieldValue extends StructFieldValue {
|
||||
describe("VariableLengthBufferLikeFieldLengthValue", () => {
|
||||
class MockBufferLikeFieldValue extends StructFieldValue {
|
||||
public constructor() {
|
||||
super({ options: {} } as any, {} as any, {} as any, {});
|
||||
}
|
||||
|
@ -38,8 +40,8 @@ describe("Types", () => {
|
|||
|
||||
describe("#getSize", () => {
|
||||
it("should return size of its original field value", () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const mockOriginalFieldValue = new MockLengthFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockBufferLikeFieldValue();
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
|
@ -58,8 +60,8 @@ describe("Types", () => {
|
|||
|
||||
describe("#get", () => {
|
||||
it("should return size of its `arrayBufferField`", async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const mockOriginalFieldValue = new MockLengthFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockBufferLikeFieldValue();
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
|
@ -80,8 +82,8 @@ describe("Types", () => {
|
|||
});
|
||||
|
||||
it("should return size of its `arrayBufferField` as string", async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const mockOriginalFieldValue = new MockLengthFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockBufferLikeFieldValue();
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
|
@ -104,8 +106,8 @@ describe("Types", () => {
|
|||
|
||||
describe("#set", () => {
|
||||
it("should does nothing", async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const mockOriginalFieldValue = new MockLengthFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockBufferLikeFieldValue();
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
|
@ -122,8 +124,8 @@ describe("Types", () => {
|
|||
|
||||
describe("#serialize", () => {
|
||||
it("should call `serialize` of its `originalField`", async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const mockOriginalFieldValue = new MockLengthFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockBufferLikeFieldValue();
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
|
@ -153,8 +155,8 @@ describe("Types", () => {
|
|||
});
|
||||
|
||||
it("should stringify its length if `originalField` is a string", async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const mockOriginalFieldValue = new MockLengthFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockBufferLikeFieldValue();
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
|
@ -184,8 +186,8 @@ describe("Types", () => {
|
|||
});
|
||||
|
||||
it("should stringify its length in specified radix if `originalField` is a string", async () => {
|
||||
const mockOriginalFieldValue = new MockOriginalFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
|
||||
const mockOriginalFieldValue = new MockLengthFieldValue();
|
||||
const mockArrayBufferFieldValue = new MockBufferLikeFieldValue();
|
||||
const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
|
||||
mockOriginalFieldValue,
|
||||
mockArrayBufferFieldValue,
|
||||
|
@ -225,7 +227,7 @@ describe("Types", () => {
|
|||
const struct = new StructValue();
|
||||
|
||||
const lengthField = "foo";
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
const originalLengthFieldValue = new MockLengthFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||
|
@ -235,26 +237,26 @@ describe("Types", () => {
|
|||
|
||||
const value = EMPTY_UINT8_ARRAY;
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
const bufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
value,
|
||||
);
|
||||
|
||||
expect(arrayBufferFieldValue).toHaveProperty("definition", arrayBufferFieldDefinition);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("options", StructDefaultOptions);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("struct", struct);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("value", value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("arrayBuffer", undefined);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("length", undefined);
|
||||
expect(bufferFieldValue).toHaveProperty("definition", arrayBufferFieldDefinition);
|
||||
expect(bufferFieldValue).toHaveProperty("options", StructDefaultOptions);
|
||||
expect(bufferFieldValue).toHaveProperty("struct", struct);
|
||||
expect(bufferFieldValue).toHaveProperty("value", value);
|
||||
expect(bufferFieldValue).toHaveProperty("array", undefined);
|
||||
expect(bufferFieldValue).toHaveProperty("length", undefined);
|
||||
});
|
||||
|
||||
it("should forward parameters with `arrayBuffer`", () => {
|
||||
const struct = new StructValue();
|
||||
|
||||
const lengthField = "foo";
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
const originalLengthFieldValue = new MockLengthFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||
|
@ -264,7 +266,7 @@ describe("Types", () => {
|
|||
|
||||
const value = new Uint8Array(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
const bufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -272,19 +274,19 @@ describe("Types", () => {
|
|||
value,
|
||||
);
|
||||
|
||||
expect(arrayBufferFieldValue).toHaveProperty("definition", arrayBufferFieldDefinition);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("options", StructDefaultOptions);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("struct", struct);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("value", value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("array", value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("length", 100);
|
||||
expect(bufferFieldValue).toHaveProperty("definition", arrayBufferFieldDefinition);
|
||||
expect(bufferFieldValue).toHaveProperty("options", StructDefaultOptions);
|
||||
expect(bufferFieldValue).toHaveProperty("struct", struct);
|
||||
expect(bufferFieldValue).toHaveProperty("value", value);
|
||||
expect(bufferFieldValue).toHaveProperty("array", value);
|
||||
expect(bufferFieldValue).toHaveProperty("length", 100);
|
||||
});
|
||||
|
||||
it("should replace `lengthField` on `struct`", () => {
|
||||
const struct = new StructValue();
|
||||
|
||||
const lengthField = "foo";
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
const originalLengthFieldValue = new MockLengthFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||
|
@ -294,15 +296,15 @@ describe("Types", () => {
|
|||
|
||||
const value = EMPTY_UINT8_ARRAY;
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
const bufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
value,
|
||||
);
|
||||
|
||||
expect(arrayBufferFieldValue["lengthFieldValue"]).toBeInstanceOf(StructFieldValue);
|
||||
expect(struct.fieldValues[lengthField]).toBe(arrayBufferFieldValue["lengthFieldValue"]);
|
||||
expect(bufferFieldValue["lengthFieldValue"]).toBeInstanceOf(StructFieldValue);
|
||||
expect(struct.fieldValues[lengthField]).toBe(bufferFieldValue["lengthFieldValue"]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -327,7 +329,7 @@ describe("Types", () => {
|
|||
const struct = new StructValue();
|
||||
|
||||
const lengthField = "foo";
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
const originalLengthFieldValue = new MockLengthFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
||||
|
@ -338,7 +340,7 @@ describe("Types", () => {
|
|||
|
||||
const value = new Uint8Array(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
const bufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -346,7 +348,7 @@ describe("Types", () => {
|
|||
value,
|
||||
);
|
||||
|
||||
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
||||
expect(bufferFieldValue.getSize()).toBe(100);
|
||||
expect(arrayBufferFieldType.toValue).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.getSize).toBeCalledTimes(0);
|
||||
|
@ -356,7 +358,7 @@ describe("Types", () => {
|
|||
const struct = new StructValue();
|
||||
|
||||
const lengthField = "foo";
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
const originalLengthFieldValue = new MockLengthFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
||||
|
@ -367,7 +369,7 @@ describe("Types", () => {
|
|||
|
||||
const value = new Uint8Array(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
const bufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -375,19 +377,19 @@ describe("Types", () => {
|
|||
);
|
||||
|
||||
arrayBufferFieldType.size = 100;
|
||||
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
||||
expect(bufferFieldValue.getSize()).toBe(100);
|
||||
expect(arrayBufferFieldType.toValue).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toBuffer).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.getSize).toBeCalledTimes(1);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("arrayBuffer", undefined);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("length", 100);
|
||||
expect(bufferFieldValue).toHaveProperty("array", undefined);
|
||||
expect(bufferFieldValue).toHaveProperty("length", 100);
|
||||
});
|
||||
|
||||
it("should call `toArrayBuffer` of its `type` if it does not support `getSize`", () => {
|
||||
const struct = new StructValue();
|
||||
|
||||
const lengthField = "foo";
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
const originalLengthFieldValue = new MockLengthFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldType = new MockArrayBufferFieldType();
|
||||
|
@ -398,7 +400,7 @@ describe("Types", () => {
|
|||
|
||||
const value = new Uint8Array(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
const bufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -406,12 +408,12 @@ describe("Types", () => {
|
|||
);
|
||||
|
||||
arrayBufferFieldType.size = -1;
|
||||
expect(arrayBufferFieldValue.getSize()).toBe(100);
|
||||
expect(bufferFieldValue.getSize()).toBe(100);
|
||||
expect(arrayBufferFieldType.toValue).toBeCalledTimes(0);
|
||||
expect(arrayBufferFieldType.toBuffer).toBeCalledTimes(1);
|
||||
expect(arrayBufferFieldType.getSize).toBeCalledTimes(1);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("arrayBuffer", value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("length", 100);
|
||||
expect(bufferFieldValue).toHaveProperty("array", value);
|
||||
expect(bufferFieldValue).toHaveProperty("length", 100);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -420,7 +422,7 @@ describe("Types", () => {
|
|||
const struct = new StructValue();
|
||||
|
||||
const lengthField = "foo";
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
const originalLengthFieldValue = new MockLengthFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||
|
@ -430,7 +432,7 @@ describe("Types", () => {
|
|||
|
||||
const value = new Uint8Array(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
const bufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -439,16 +441,16 @@ describe("Types", () => {
|
|||
);
|
||||
|
||||
const newValue = new ArrayBuffer(100);
|
||||
arrayBufferFieldValue.set(newValue);
|
||||
expect(arrayBufferFieldValue.get()).toBe(newValue);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("arrayBuffer", undefined);
|
||||
bufferFieldValue.set(newValue);
|
||||
expect(bufferFieldValue.get()).toBe(newValue);
|
||||
expect(bufferFieldValue).toHaveProperty("array", undefined);
|
||||
});
|
||||
|
||||
it("should clear length", () => {
|
||||
const struct = new StructValue();
|
||||
|
||||
const lengthField = "foo";
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
const originalLengthFieldValue = new MockLengthFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
|
||||
|
@ -458,7 +460,7 @@ describe("Types", () => {
|
|||
|
||||
const value = new Uint8Array(100);
|
||||
|
||||
const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
const bufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
|
||||
arrayBufferFieldDefinition,
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
|
@ -467,8 +469,8 @@ describe("Types", () => {
|
|||
);
|
||||
|
||||
const newValue = new ArrayBuffer(100);
|
||||
arrayBufferFieldValue.set(newValue);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("length", undefined);
|
||||
bufferFieldValue.set(newValue);
|
||||
expect(bufferFieldValue).toHaveProperty("length", undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -489,7 +491,7 @@ describe("Types", () => {
|
|||
const struct = new StructValue();
|
||||
|
||||
const lengthField = "foo";
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
const originalLengthFieldValue = new MockLengthFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||
|
@ -511,7 +513,7 @@ describe("Types", () => {
|
|||
const struct = new StructValue();
|
||||
|
||||
const lengthField = "foo";
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
const originalLengthFieldValue = new MockLengthFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||
|
@ -533,7 +535,7 @@ describe("Types", () => {
|
|||
const struct = new StructValue();
|
||||
|
||||
const lengthField = "foo";
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
const originalLengthFieldValue = new MockLengthFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const radix = 8;
|
||||
|
@ -558,7 +560,7 @@ describe("Types", () => {
|
|||
const struct = new StructValue();
|
||||
|
||||
const lengthField = "foo";
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
const originalLengthFieldValue = new MockLengthFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||
|
@ -567,25 +569,25 @@ describe("Types", () => {
|
|||
);
|
||||
|
||||
const value = new Uint8Array(100);
|
||||
const arrayBufferFieldValue = definition.create(
|
||||
const bufferFieldValue = definition.create(
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
value,
|
||||
);
|
||||
|
||||
expect(arrayBufferFieldValue).toHaveProperty("definition", definition);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("options", StructDefaultOptions);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("struct", struct);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("value", value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("arrayBuffer", undefined);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("length", undefined);
|
||||
expect(bufferFieldValue).toHaveProperty("definition", definition);
|
||||
expect(bufferFieldValue).toHaveProperty("options", StructDefaultOptions);
|
||||
expect(bufferFieldValue).toHaveProperty("struct", struct);
|
||||
expect(bufferFieldValue).toHaveProperty("value", value);
|
||||
expect(bufferFieldValue).toHaveProperty("array", undefined);
|
||||
expect(bufferFieldValue).toHaveProperty("length", undefined);
|
||||
});
|
||||
|
||||
it("should create a `VariableLengthArrayBufferLikeFieldValue` with `arrayBuffer`", () => {
|
||||
const struct = new StructValue();
|
||||
|
||||
const lengthField = "foo";
|
||||
const originalLengthFieldValue = new MockOriginalFieldValue();
|
||||
const originalLengthFieldValue = new MockLengthFieldValue();
|
||||
struct.set(lengthField, originalLengthFieldValue);
|
||||
|
||||
const definition = new VariableLengthBufferLikeFieldDefinition(
|
||||
|
@ -594,19 +596,19 @@ describe("Types", () => {
|
|||
);
|
||||
|
||||
const value = new Uint8Array(100);
|
||||
const arrayBufferFieldValue = definition.create(
|
||||
const bufferFieldValue = definition.create(
|
||||
StructDefaultOptions,
|
||||
struct,
|
||||
value,
|
||||
value,
|
||||
);
|
||||
|
||||
expect(arrayBufferFieldValue).toHaveProperty("definition", definition);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("options", StructDefaultOptions);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("struct", struct);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("value", value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("arrayBuffer", value);
|
||||
expect(arrayBufferFieldValue).toHaveProperty("length", 100);
|
||||
expect(bufferFieldValue).toHaveProperty("definition", definition);
|
||||
expect(bufferFieldValue).toHaveProperty("options", StructDefaultOptions);
|
||||
expect(bufferFieldValue).toHaveProperty("struct", struct);
|
||||
expect(bufferFieldValue).toHaveProperty("value", value);
|
||||
expect(bufferFieldValue).toHaveProperty("array", value);
|
||||
expect(bufferFieldValue).toHaveProperty("length", 100);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,50 +1,137 @@
|
|||
import { describe, expect, it, jest, test } from '@jest/globals';
|
||||
|
||||
import { StructDefaultOptions, StructDeserializeStream, StructValue } from "../basic/index.js";
|
||||
import { NumberFieldDefinition, NumberFieldType } from "./number.js";
|
||||
|
||||
function testEndian(type: NumberFieldType, min: number, max: number, littleEndian: boolean) {
|
||||
test('min', () => {
|
||||
const buffer = new ArrayBuffer(type.size);
|
||||
const view = new DataView(buffer);
|
||||
(view[`set${type.signed ? 'I' : 'Ui'}nt${type.size * 8}` as keyof DataView] as any)(0, min, littleEndian);
|
||||
let output = type.deserializer(new Uint8Array(buffer), littleEndian);
|
||||
output = type.convertSign(output);
|
||||
expect(output).toBe(min);
|
||||
});
|
||||
|
||||
test('1', () => {
|
||||
const buffer = new ArrayBuffer(type.size);
|
||||
const view = new DataView(buffer);
|
||||
const input = 1;
|
||||
(view[`set${type.signed ? 'I' : 'Ui'}nt${type.size * 8}` as keyof DataView] as any)(0, input, littleEndian);
|
||||
let output = type.deserializer(new Uint8Array(buffer), littleEndian);
|
||||
output = type.convertSign(output);
|
||||
expect(output).toBe(input);
|
||||
});
|
||||
|
||||
test('max', () => {
|
||||
const buffer = new ArrayBuffer(type.size);
|
||||
const view = new DataView(buffer);
|
||||
(view[`set${type.signed ? 'I' : 'Ui'}nt${type.size * 8}` as keyof DataView] as any)(0, max, littleEndian);
|
||||
let output = type.deserializer(new Uint8Array(buffer), littleEndian);
|
||||
output = type.convertSign(output);
|
||||
expect(output).toBe(max);
|
||||
});
|
||||
}
|
||||
|
||||
function testDeserialize(type: NumberFieldType) {
|
||||
if (type.size === 1) {
|
||||
if (type.signed) {
|
||||
testEndian(type, 2 ** (type.size * 8) / -2, 2 ** (type.size * 8) / 2 - 1, false);
|
||||
} else {
|
||||
testEndian(type, 0, 2 ** (type.size * 8) - 1, false);
|
||||
}
|
||||
} else {
|
||||
if (type.signed) {
|
||||
describe('big endian', () => {
|
||||
testEndian(type, 2 ** (type.size * 8) / -2, 2 ** (type.size * 8) / 2 - 1, false);
|
||||
});
|
||||
|
||||
describe('little endian', () => {
|
||||
testEndian(type, 2 ** (type.size * 8) / -2, 2 ** (type.size * 8) / 2 - 1, true);
|
||||
});
|
||||
} else {
|
||||
describe('big endian', () => {
|
||||
testEndian(type, 0, 2 ** (type.size * 8) - 1, false);
|
||||
});
|
||||
|
||||
describe('little endian', () => {
|
||||
testEndian(type, 0, 2 ** (type.size * 8) - 1, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("Types", () => {
|
||||
describe("Number", () => {
|
||||
describe("NumberFieldType", () => {
|
||||
it("Int8 validation", () => {
|
||||
describe('Int8', () => {
|
||||
const key = "Int8";
|
||||
expect(NumberFieldType[key]).toHaveProperty("size", 1);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewGetter", "get" + key);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewSetter", "set" + key);
|
||||
|
||||
test('basic', () => {
|
||||
expect(NumberFieldType[key]).toHaveProperty("size", 1);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewSetter", "set" + key);
|
||||
});
|
||||
|
||||
testDeserialize(NumberFieldType[key]);
|
||||
});
|
||||
|
||||
it("Uint8 validation", () => {
|
||||
describe('Uint8', () => {
|
||||
const key = "Uint8";
|
||||
expect(NumberFieldType[key]).toHaveProperty("size", 1);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewGetter", "get" + key);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewSetter", "set" + key);
|
||||
|
||||
test('basic', () => {
|
||||
expect(NumberFieldType[key]).toHaveProperty("size", 1);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewSetter", "set" + key);
|
||||
});
|
||||
|
||||
testDeserialize(NumberFieldType[key]);
|
||||
});
|
||||
|
||||
it("Int16 validation", () => {
|
||||
describe('Int16', () => {
|
||||
const key = "Int16";
|
||||
expect(NumberFieldType[key]).toHaveProperty("size", 2);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewGetter", "get" + key);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewSetter", "set" + key);
|
||||
|
||||
test('basic', () => {
|
||||
expect(NumberFieldType[key]).toHaveProperty("size", 2);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewSetter", "set" + key);
|
||||
});
|
||||
|
||||
testDeserialize(NumberFieldType[key]);
|
||||
});
|
||||
|
||||
it("Uint16 validation", () => {
|
||||
describe('Uint16', () => {
|
||||
const key = "Uint16";
|
||||
expect(NumberFieldType[key]).toHaveProperty("size", 2);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewGetter", "get" + key);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewSetter", "set" + key);
|
||||
|
||||
test('basic', () => {
|
||||
expect(NumberFieldType[key]).toHaveProperty("size", 2);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewSetter", "set" + key);
|
||||
});
|
||||
|
||||
testDeserialize(NumberFieldType[key]);
|
||||
});
|
||||
|
||||
it("Int32 validation", () => {
|
||||
|
||||
describe('Int32', () => {
|
||||
const key = "Int32";
|
||||
expect(NumberFieldType[key]).toHaveProperty("size", 4);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewGetter", "get" + key);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewSetter", "set" + key);
|
||||
|
||||
test('basic', () => {
|
||||
expect(NumberFieldType[key]).toHaveProperty("size", 4);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewSetter", "set" + key);
|
||||
});
|
||||
|
||||
testDeserialize(NumberFieldType[key]);
|
||||
});
|
||||
|
||||
it("Uint32 validation", () => {
|
||||
describe('Uint32', () => {
|
||||
const key = "Uint32";
|
||||
expect(NumberFieldType[key]).toHaveProperty("size", 4);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewGetter", "get" + key);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewSetter", "set" + key);
|
||||
|
||||
test('basic', () => {
|
||||
expect(NumberFieldType[key]).toHaveProperty("size", 4);
|
||||
expect(NumberFieldType[key]).toHaveProperty("dataViewSetter", "set" + key);
|
||||
});
|
||||
|
||||
testDeserialize(NumberFieldType[key]);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe("NumberFieldDefinition", () => {
|
||||
|
@ -60,13 +147,13 @@ describe("Types", () => {
|
|||
});
|
||||
|
||||
describe("#deserialize", () => {
|
||||
it("should deserialize Uint8", async () => {
|
||||
it("should deserialize Uint8", () => {
|
||||
const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]));
|
||||
const stream: StructDeserializeStream = { read };
|
||||
|
||||
const definition = new NumberFieldDefinition(NumberFieldType.Uint8);
|
||||
const struct = new StructValue();
|
||||
const value = await definition.deserialize(
|
||||
const value = definition.deserialize(
|
||||
StructDefaultOptions,
|
||||
stream,
|
||||
struct,
|
||||
|
@ -77,13 +164,13 @@ describe("Types", () => {
|
|||
expect(read).lastCalledWith(NumberFieldType.Uint8.size);
|
||||
});
|
||||
|
||||
it("should deserialize Uint16", async () => {
|
||||
it("should deserialize Uint16", () => {
|
||||
const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]));
|
||||
const stream: StructDeserializeStream = { read };
|
||||
|
||||
const definition = new NumberFieldDefinition(NumberFieldType.Uint16);
|
||||
const struct = new StructValue();
|
||||
const value = await definition.deserialize(
|
||||
const value = definition.deserialize(
|
||||
StructDefaultOptions,
|
||||
stream,
|
||||
struct,
|
||||
|
@ -94,13 +181,13 @@ describe("Types", () => {
|
|||
expect(read).lastCalledWith(NumberFieldType.Uint16.size);
|
||||
});
|
||||
|
||||
it("should deserialize Uint16LE", async () => {
|
||||
it("should deserialize Uint16LE", () => {
|
||||
const read = jest.fn((length: number) => new Uint8Array([1, 2, 3, 4]));
|
||||
const stream: StructDeserializeStream = { read };
|
||||
|
||||
const definition = new NumberFieldDefinition(NumberFieldType.Uint16);
|
||||
const struct = new StructValue();
|
||||
const value = await definition.deserialize(
|
||||
const value = definition.deserialize(
|
||||
{ ...StructDefaultOptions, littleEndian: true },
|
||||
stream,
|
||||
struct,
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
// cspell: ignore syncbird
|
||||
|
||||
import { StructFieldDefinition, StructFieldValue, StructValue, type StructAsyncDeserializeStream, type StructDeserializeStream, type StructOptions } from '../basic/index.js';
|
||||
import { SyncPromise } from "../sync-promise.js";
|
||||
import type { ValueOrPromise } from "../utils.js";
|
||||
|
||||
export type DataViewGetters =
|
||||
{ [TKey in keyof DataView]: TKey extends `get${string}` ? TKey : never }[keyof DataView];
|
||||
type NumberTypeDeserializer = (array: Uint8Array, littleEndian: boolean) => number;
|
||||
|
||||
const DESERIALIZERS: Record<number, NumberTypeDeserializer> = {
|
||||
1: (array, littleEndian) =>
|
||||
array[0]!,
|
||||
2: (array, littleEndian) =>
|
||||
((array[1]! << 8) | array[0]!) * (littleEndian as any) |
|
||||
((array[0]! << 8) | array[1]!) * (!littleEndian as any),
|
||||
4: (array, littleEndian) =>
|
||||
((array[3]! << 24) | (array[2]! << 16) | (array[1]! << 8) | array[0]!) * (littleEndian as any) |
|
||||
((array[0]! << 24) | (array[1]! << 16) | (array[2]! << 8) | array[3]!) * (!littleEndian as any),
|
||||
};
|
||||
|
||||
export type DataViewSetters =
|
||||
{ [TKey in keyof DataView]: TKey extends `set${string}` ? TKey : never }[keyof DataView];
|
||||
|
@ -13,33 +21,39 @@ export type DataViewSetters =
|
|||
export class NumberFieldType {
|
||||
public readonly TTypeScriptType!: number;
|
||||
|
||||
public readonly signed: boolean;
|
||||
|
||||
public readonly size: number;
|
||||
|
||||
public readonly dataViewGetter: DataViewGetters;
|
||||
public readonly deserializer: NumberTypeDeserializer;
|
||||
public readonly convertSign: (value: number) => number;
|
||||
|
||||
public readonly dataViewSetter: DataViewSetters;
|
||||
|
||||
public constructor(
|
||||
size: number,
|
||||
dataViewGetter: DataViewGetters,
|
||||
signed: boolean,
|
||||
convertSign: (value: number) => number,
|
||||
dataViewSetter: DataViewSetters
|
||||
) {
|
||||
this.size = size;
|
||||
this.dataViewGetter = dataViewGetter;
|
||||
this.signed = signed;
|
||||
this.deserializer = DESERIALIZERS[size]!;
|
||||
this.convertSign = convertSign;
|
||||
this.dataViewSetter = dataViewSetter;
|
||||
}
|
||||
|
||||
public static readonly Int8 = new NumberFieldType(1, 'getInt8', 'setInt8');
|
||||
public static readonly Int8 = new NumberFieldType(1, true, value => value << 24 >> 24, 'setInt8');
|
||||
|
||||
public static readonly Uint8 = new NumberFieldType(1, 'getUint8', 'setUint8');
|
||||
public static readonly Uint8 = new NumberFieldType(1, false, value => value, 'setUint8');
|
||||
|
||||
public static readonly Int16 = new NumberFieldType(2, 'getInt16', 'setInt16');
|
||||
public static readonly Int16 = new NumberFieldType(2, true, value => value << 16 >> 16, 'setInt16');
|
||||
|
||||
public static readonly Uint16 = new NumberFieldType(2, 'getUint16', 'setUint16');
|
||||
public static readonly Uint16 = new NumberFieldType(2, false, value => value, 'setUint16');
|
||||
|
||||
public static readonly Int32 = new NumberFieldType(4, 'getInt32', 'setInt32');
|
||||
public static readonly Int32 = new NumberFieldType(4, true, value => value, 'setInt32');
|
||||
|
||||
public static readonly Uint32 = new NumberFieldType(4, 'getUint32', 'setUint32');
|
||||
public static readonly Uint32 = new NumberFieldType(4, false, value => value >>> 0, 'setUint32');
|
||||
}
|
||||
|
||||
export class NumberFieldDefinition<
|
||||
|
@ -88,11 +102,9 @@ export class NumberFieldDefinition<
|
|||
return stream.read(this.getSize());
|
||||
})
|
||||
.then(array => {
|
||||
const view = new DataView(array.buffer, array.byteOffset, array.byteLength);
|
||||
const value = view[this.type.dataViewGetter](
|
||||
0,
|
||||
options.littleEndian
|
||||
);
|
||||
let value: number;
|
||||
value = this.type.deserializer(array, options.littleEndian);
|
||||
value = this.type.convertSign(value);
|
||||
return this.create(options, struct, value as any);
|
||||
})
|
||||
.valueOrPromise();
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { describe, expect, it } from '@jest/globals';
|
||||
|
||||
import { placeholder } from './utils.js';
|
||||
|
||||
describe('placeholder', () => {
|
||||
|
|
3
libraries/struct/tsconfig.build.json
Normal file
3
libraries/struct/tsconfig.build.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "./node_modules/@yume-chan/ts-package-builder/tsconfig.base.json"
|
||||
}
|
|
@ -1,3 +1,10 @@
|
|||
{
|
||||
"extends": "./node_modules/@yume-chan/ts-package-builder/tsconfig.base.json",
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.test.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
8
libraries/struct/tsconfig.test.json
Normal file
8
libraries/struct/tsconfig.test.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "./tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
],
|
||||
},
|
||||
"exclude": []
|
||||
}
|
|
@ -16,8 +16,7 @@
|
|||
"@types/node": "^17.0.17"
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": "^2.2.0",
|
||||
"@types/jest": "^27.4.1"
|
||||
"json5": "^2.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^4.0.0"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue