mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-04 18:29:23 +02:00
refactor(scrcpy): small optimizations
This commit is contained in:
parent
6b23154694
commit
b5f58227fd
11 changed files with 359 additions and 71 deletions
|
@ -5,16 +5,21 @@ import {
|
|||
import { ScrcpyControlMessageType } from "./type.js";
|
||||
|
||||
/**
|
||||
* On Android, touching the screen with a finger will disable mouse cursor.
|
||||
* However, Scrcpy doesn't do that, and can inject two pointers at the same time.
|
||||
* This can cause finger events to be "ignored" because mouse is still the primary pointer.
|
||||
* On both Android and Windows, while both mouse and touch are supported input devices,
|
||||
* only one of them can be active at a time. Touch the screen with a finger will deactivate mouse,
|
||||
* and move the mouse will deactivate touch.
|
||||
*
|
||||
* This helper class injects an extra `ACTION_UP` event,
|
||||
* On Android, this is achieved by dispatching a `MotionEvent.ACTION_UP` event for the previous input type.
|
||||
* But on Chrome, there is no such event, causing both mouse and touch to be active at the same time.
|
||||
* This can cause the new input to appear as "ignored".
|
||||
*
|
||||
* This helper class synthesis `ACTION_UP` events when a different pointer type appears,
|
||||
* so Scrcpy server can remove the previously hovering pointer.
|
||||
*/
|
||||
export class ScrcpyHoverHelper {
|
||||
// AFAIK, only mouse and pen can have hover state
|
||||
// and you can't have two mouses or pens.
|
||||
// So remember the last hovering pointer is enough.
|
||||
private lastHoverMessage: ScrcpyInjectTouchControlMessage | undefined;
|
||||
|
||||
public process(
|
||||
|
|
|
@ -40,7 +40,7 @@ export class ScrcpyControlMessageSerializer {
|
|||
this.scrollController = options.getScrollController();
|
||||
}
|
||||
|
||||
public getTypeValue(type: ScrcpyControlMessageType): number {
|
||||
public getActualMessageType(type: ScrcpyControlMessageType): number {
|
||||
const value = this.types.indexOf(type);
|
||||
if (value === -1) {
|
||||
throw new Error("Not supported");
|
||||
|
@ -48,14 +48,24 @@ export class ScrcpyControlMessageSerializer {
|
|||
return value;
|
||||
}
|
||||
|
||||
public addMessageType<T extends { type: ScrcpyControlMessageType }>(
|
||||
message: Omit<T, "type">,
|
||||
type: T["type"]
|
||||
): T {
|
||||
(message as T).type = this.getActualMessageType(type);
|
||||
return message as T;
|
||||
}
|
||||
|
||||
public injectKeyCode(
|
||||
message: Omit<ScrcpyInjectKeyCodeControlMessage, "type">
|
||||
) {
|
||||
return this.writer.write(
|
||||
ScrcpyInjectKeyCodeControlMessage.serialize({
|
||||
...message,
|
||||
type: this.getTypeValue(ScrcpyControlMessageType.InjectKeyCode),
|
||||
})
|
||||
ScrcpyInjectKeyCodeControlMessage.serialize(
|
||||
this.addMessageType(
|
||||
message,
|
||||
ScrcpyControlMessageType.InjectKeyCode
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -63,7 +73,9 @@ export class ScrcpyControlMessageSerializer {
|
|||
return this.writer.write(
|
||||
ScrcpyInjectTextControlMessage.serialize({
|
||||
text,
|
||||
type: this.getTypeValue(ScrcpyControlMessageType.InjectText),
|
||||
type: this.getActualMessageType(
|
||||
ScrcpyControlMessageType.InjectText
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -73,10 +85,12 @@ export class ScrcpyControlMessageSerializer {
|
|||
*/
|
||||
public injectTouch(message: Omit<ScrcpyInjectTouchControlMessage, "type">) {
|
||||
return this.writer.write(
|
||||
ScrcpyInjectTouchControlMessage.serialize({
|
||||
...message,
|
||||
type: this.getTypeValue(ScrcpyControlMessageType.InjectTouch),
|
||||
})
|
||||
ScrcpyInjectTouchControlMessage.serialize(
|
||||
this.addMessageType(
|
||||
message,
|
||||
ScrcpyControlMessageType.InjectTouch
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -86,13 +100,10 @@ export class ScrcpyControlMessageSerializer {
|
|||
public injectScroll(
|
||||
message: Omit<ScrcpyInjectScrollControlMessage, "type">
|
||||
) {
|
||||
(message as ScrcpyInjectScrollControlMessage).type = this.getTypeValue(
|
||||
ScrcpyControlMessageType.InjectScroll
|
||||
const data = this.scrollController.serializeScrollMessage(
|
||||
this.addMessageType(message, ScrcpyControlMessageType.InjectScroll)
|
||||
);
|
||||
|
||||
const data = this.scrollController.serializeScrollMessage(
|
||||
message as ScrcpyInjectScrollControlMessage
|
||||
);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
@ -103,7 +114,9 @@ export class ScrcpyControlMessageSerializer {
|
|||
public async backOrScreenOn(action: AndroidKeyEventAction) {
|
||||
const buffer = this.options.serializeBackOrScreenOnControlMessage({
|
||||
action,
|
||||
type: this.getTypeValue(ScrcpyControlMessageType.BackOrScreenOn),
|
||||
type: this.getActualMessageType(
|
||||
ScrcpyControlMessageType.BackOrScreenOn
|
||||
),
|
||||
});
|
||||
|
||||
if (buffer) {
|
||||
|
@ -115,7 +128,7 @@ export class ScrcpyControlMessageSerializer {
|
|||
return this.writer.write(
|
||||
ScrcpySetScreenPowerModeControlMessage.serialize({
|
||||
mode,
|
||||
type: this.getTypeValue(
|
||||
type: this.getActualMessageType(
|
||||
ScrcpyControlMessageType.SetScreenPowerMode
|
||||
),
|
||||
})
|
||||
|
@ -125,7 +138,9 @@ export class ScrcpyControlMessageSerializer {
|
|||
public rotateDevice() {
|
||||
return this.writer.write(
|
||||
ScrcpyRotateDeviceControlMessage.serialize({
|
||||
type: this.getTypeValue(ScrcpyControlMessageType.RotateDevice),
|
||||
type: this.getActualMessageType(
|
||||
ScrcpyControlMessageType.RotateDevice
|
||||
),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -55,8 +55,8 @@ class BitReader {
|
|||
* Split NAL units from a H.264 Annex B stream.
|
||||
*
|
||||
* The input is not modified.
|
||||
* The returned NAL units are views of the input (no memory allocation and copy),
|
||||
* but still contains emulation prevention bytes.
|
||||
* The returned NAL units are views of the input (no memory allocation nor copy),
|
||||
* and still contains emulation prevention bytes.
|
||||
*
|
||||
* This methods returns a generator, so it can be stopped immediately
|
||||
* after the interested NAL unit is found.
|
||||
|
@ -160,14 +160,67 @@ export function removeH264Emulation(buffer: Uint8Array) {
|
|||
let zeroCount = 0;
|
||||
let inEmulation = false;
|
||||
|
||||
for (let i = 0; i < buffer.length; i += 1) {
|
||||
let i = 0;
|
||||
scan: for (; i < buffer.length; i += 1) {
|
||||
const byte = buffer[i]!;
|
||||
|
||||
if (output) {
|
||||
output[outputOffset] = byte;
|
||||
outputOffset += 1;
|
||||
if (byte === 0x00) {
|
||||
zeroCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Current byte is not zero
|
||||
const prevZeroCount = zeroCount;
|
||||
zeroCount = 0;
|
||||
|
||||
if (prevZeroCount < 2) {
|
||||
// zero or one `0x00`s are acceptable
|
||||
continue;
|
||||
}
|
||||
|
||||
if (byte === 0x01) {
|
||||
// Unexpected start code
|
||||
throw new Error("Invalid data");
|
||||
}
|
||||
|
||||
if (prevZeroCount > 2) {
|
||||
// Too much `0x00`s
|
||||
throw new Error("Invalid data");
|
||||
}
|
||||
|
||||
switch (byte) {
|
||||
case 0x02:
|
||||
// Didn't find why, but 7.4.1 NAL unit semantics forbids `0x000002` appearing in NAL units
|
||||
throw new Error("Invalid data");
|
||||
case 0x03:
|
||||
// `0x000003` is the "emulation_prevention_three_byte"
|
||||
// `0x00000300`, `0x00000301`, `0x00000302` and `0x00000303` represent
|
||||
// `0x000000`, `0x000001`, `0x000002` and `0x000003` respectively
|
||||
inEmulation = true;
|
||||
|
||||
// Create output and copy the data before the emulation prevention byte
|
||||
// Output size is unknown, so we use the input size as an upper bound
|
||||
output = new Uint8Array(buffer.length - 1);
|
||||
output.set(buffer.subarray(0, i - prevZeroCount));
|
||||
outputOffset = i - prevZeroCount + 1;
|
||||
break scan;
|
||||
default:
|
||||
// `0x000004` or larger are as-is
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!output) {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// Continue at the byte after the emulation prevention byte
|
||||
for (; i < buffer.length; i += 1) {
|
||||
const byte = buffer[i]!;
|
||||
|
||||
output[outputOffset] = byte;
|
||||
outputOffset += 1;
|
||||
|
||||
if (inEmulation) {
|
||||
if (byte > 0x03) {
|
||||
// `0x00000304` or larger are invalid
|
||||
|
@ -211,15 +264,8 @@ export function removeH264Emulation(buffer: Uint8Array) {
|
|||
// `0x000000`, `0x000001`, `0x000002` and `0x000003` respectively
|
||||
inEmulation = true;
|
||||
|
||||
if (!output) {
|
||||
// Create output and copy the data before the emulation prevention byte
|
||||
output = new Uint8Array(buffer.length - 1);
|
||||
output.set(buffer.subarray(0, i - prevZeroCount));
|
||||
outputOffset = i - prevZeroCount + 1;
|
||||
} else {
|
||||
// Remove the emulation prevention byte
|
||||
outputOffset -= 1;
|
||||
}
|
||||
// Remove the emulation prevention byte
|
||||
outputOffset -= 1;
|
||||
break;
|
||||
default:
|
||||
// `0x000004` or larger are as-is
|
||||
|
@ -227,7 +273,7 @@ export function removeH264Emulation(buffer: Uint8Array) {
|
|||
}
|
||||
}
|
||||
|
||||
return output?.subarray(0, outputOffset) ?? buffer;
|
||||
return output.subarray(0, outputOffset);
|
||||
}
|
||||
|
||||
// 7.3.2.1.1 Sequence parameter set data syntax
|
||||
|
|
90
libraries/scrcpy/src/options/1_16/scroll.spec.ts
Normal file
90
libraries/scrcpy/src/options/1_16/scroll.spec.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
import { describe, expect, it } from "@jest/globals";
|
||||
|
||||
import { ScrcpyControlMessageType } from "../../control/index.js";
|
||||
|
||||
import { ScrcpyScrollController1_16 } from "./scroll.js";
|
||||
|
||||
describe("ScrcpyScrollController1_16", () => {
|
||||
it("should return undefined when scroll distance is less than 1", () => {
|
||||
const controller = new ScrcpyScrollController1_16();
|
||||
const message = controller.serializeScrollMessage({
|
||||
type: ScrcpyControlMessageType.InjectScroll,
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
screenWidth: 0,
|
||||
screenHeight: 0,
|
||||
scrollX: 0.5,
|
||||
scrollY: 0.5,
|
||||
buttons: 0,
|
||||
});
|
||||
expect(message).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return a message when scroll distance is greater than 1", () => {
|
||||
const controller = new ScrcpyScrollController1_16();
|
||||
const message = controller.serializeScrollMessage({
|
||||
type: ScrcpyControlMessageType.InjectScroll,
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
screenWidth: 0,
|
||||
screenHeight: 0,
|
||||
scrollX: 1.5,
|
||||
scrollY: 1.5,
|
||||
buttons: 0,
|
||||
});
|
||||
expect(message).toBeInstanceOf(Uint8Array);
|
||||
expect(message).toHaveProperty("byteLength", 21);
|
||||
});
|
||||
|
||||
it("should return a message when accumulated scroll distance is greater than 1", () => {
|
||||
const controller = new ScrcpyScrollController1_16();
|
||||
controller.serializeScrollMessage({
|
||||
type: ScrcpyControlMessageType.InjectScroll,
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
screenWidth: 0,
|
||||
screenHeight: 0,
|
||||
scrollX: 0.5,
|
||||
scrollY: 0.5,
|
||||
buttons: 0,
|
||||
});
|
||||
const message = controller.serializeScrollMessage({
|
||||
type: ScrcpyControlMessageType.InjectScroll,
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
screenWidth: 0,
|
||||
screenHeight: 0,
|
||||
scrollX: 0.5,
|
||||
scrollY: 0.5,
|
||||
buttons: 0,
|
||||
});
|
||||
expect(message).toBeInstanceOf(Uint8Array);
|
||||
expect(message).toHaveProperty("byteLength", 21);
|
||||
});
|
||||
|
||||
it("should return a message when accumulated scroll distance is less than -1", () => {
|
||||
const controller = new ScrcpyScrollController1_16();
|
||||
controller.serializeScrollMessage({
|
||||
type: ScrcpyControlMessageType.InjectScroll,
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
screenWidth: 0,
|
||||
screenHeight: 0,
|
||||
scrollX: -0.5,
|
||||
scrollY: -0.5,
|
||||
buttons: 0,
|
||||
});
|
||||
const message = controller.serializeScrollMessage({
|
||||
type: ScrcpyControlMessageType.InjectScroll,
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
screenWidth: 0,
|
||||
screenHeight: 0,
|
||||
scrollX: -0.5,
|
||||
scrollY: -0.5,
|
||||
buttons: 0,
|
||||
});
|
||||
expect(message).toBeInstanceOf(Uint8Array);
|
||||
expect(message).toHaveProperty("byteLength", 21);
|
||||
});
|
||||
});
|
13
libraries/scrcpy/src/options/1_22/options.spec.ts
Normal file
13
libraries/scrcpy/src/options/1_22/options.spec.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { describe, expect, it } from "@jest/globals";
|
||||
|
||||
import { ScrcpyOptions1_21 } from "../1_21.js";
|
||||
|
||||
import { ScrcpyOptions1_22 } from "./options.js";
|
||||
|
||||
describe("ScrcpyOptions1_22", () => {
|
||||
it("should return a different scroll controller", () => {
|
||||
const controller1_21 = new ScrcpyOptions1_21({}).getScrollController();
|
||||
const controller1_22 = new ScrcpyOptions1_22({}).getScrollController();
|
||||
expect(controller1_22).not.toBe(controller1_21);
|
||||
});
|
||||
});
|
23
libraries/scrcpy/src/options/1_22/scroll.spec.ts
Normal file
23
libraries/scrcpy/src/options/1_22/scroll.spec.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { describe, expect, it } from "@jest/globals";
|
||||
|
||||
import { ScrcpyControlMessageType } from "../../control/index.js";
|
||||
|
||||
import { ScrcpyScrollController1_22 } from "./scroll.js";
|
||||
|
||||
describe("ScrcpyScrollController1_22", () => {
|
||||
it("should return correct message length", () => {
|
||||
const controller = new ScrcpyScrollController1_22();
|
||||
const message = controller.serializeScrollMessage({
|
||||
type: ScrcpyControlMessageType.InjectScroll,
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
screenWidth: 0,
|
||||
screenHeight: 0,
|
||||
scrollX: 1.5,
|
||||
scrollY: 1.5,
|
||||
buttons: 0,
|
||||
});
|
||||
expect(message).toBeInstanceOf(Uint8Array);
|
||||
expect(message).toHaveProperty("byteLength", 25);
|
||||
});
|
||||
});
|
13
libraries/scrcpy/src/options/1_25/options.spec.ts
Normal file
13
libraries/scrcpy/src/options/1_25/options.spec.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { describe, expect, it } from "@jest/globals";
|
||||
|
||||
import { ScrcpyOptions1_24 } from "../1_24.js";
|
||||
|
||||
import { ScrcpyOptions1_25 } from "./options.js";
|
||||
|
||||
describe("ScrcpyOptions1_25", () => {
|
||||
it("should return a different scroll controller", () => {
|
||||
const controller1_24 = new ScrcpyOptions1_24({}).getScrollController();
|
||||
const controller1_25 = new ScrcpyOptions1_25({}).getScrollController();
|
||||
expect(controller1_25).not.toBe(controller1_24);
|
||||
});
|
||||
});
|
76
libraries/scrcpy/src/options/1_25/scroll.spec.ts
Normal file
76
libraries/scrcpy/src/options/1_25/scroll.spec.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { describe, expect, it } from "@jest/globals";
|
||||
|
||||
import { ScrcpyControlMessageType } from "../../control/index.js";
|
||||
|
||||
import {
|
||||
ScrcpyFloatToInt16NumberType,
|
||||
ScrcpyScrollController1_25,
|
||||
} from "./scroll.js";
|
||||
|
||||
describe("ScrcpyFloatToInt16NumberType", () => {
|
||||
it("should serialize", () => {
|
||||
const dataView = new DataView(new ArrayBuffer(2));
|
||||
ScrcpyFloatToInt16NumberType.serialize(dataView, 0, -1, true);
|
||||
expect(dataView.getInt16(0, true)).toBe(-0x8000);
|
||||
|
||||
ScrcpyFloatToInt16NumberType.serialize(dataView, 0, 0, true);
|
||||
expect(dataView.getInt16(0, true)).toBe(0);
|
||||
|
||||
ScrcpyFloatToInt16NumberType.serialize(dataView, 0, 1, true);
|
||||
expect(dataView.getInt16(0, true)).toBe(0x7fff);
|
||||
});
|
||||
|
||||
it("should clamp input values", () => {
|
||||
const dataView = new DataView(new ArrayBuffer(2));
|
||||
ScrcpyFloatToInt16NumberType.serialize(dataView, 0, -2, true);
|
||||
expect(dataView.getInt16(0, true)).toBe(-0x8000);
|
||||
|
||||
ScrcpyFloatToInt16NumberType.serialize(dataView, 0, 2, true);
|
||||
expect(dataView.getInt16(0, true)).toBe(0x7fff);
|
||||
});
|
||||
|
||||
it("should deserialize", () => {
|
||||
const dataView = new DataView(new ArrayBuffer(2));
|
||||
const view = new Uint8Array(dataView.buffer);
|
||||
|
||||
dataView.setInt16(0, -0x8000, true);
|
||||
expect(ScrcpyFloatToInt16NumberType.deserialize(view, true)).toBe(-1);
|
||||
|
||||
dataView.setInt16(0, 0, true);
|
||||
expect(ScrcpyFloatToInt16NumberType.deserialize(view, true)).toBe(0);
|
||||
|
||||
dataView.setInt16(0, 0x7fff, true);
|
||||
expect(ScrcpyFloatToInt16NumberType.deserialize(view, true)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("ScrcpyScrollController1_25", () => {
|
||||
it("should return a message for each scroll event", () => {
|
||||
const controller = new ScrcpyScrollController1_25();
|
||||
const message1 = controller.serializeScrollMessage({
|
||||
type: ScrcpyControlMessageType.InjectScroll,
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
screenWidth: 0,
|
||||
screenHeight: 0,
|
||||
scrollX: 0.5,
|
||||
scrollY: 0.5,
|
||||
buttons: 0,
|
||||
});
|
||||
expect(message1).toBeInstanceOf(Uint8Array);
|
||||
expect(message1).toHaveProperty("byteLength", 21);
|
||||
|
||||
const message2 = controller.serializeScrollMessage({
|
||||
type: ScrcpyControlMessageType.InjectScroll,
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
screenWidth: 0,
|
||||
screenHeight: 0,
|
||||
scrollX: 1.5,
|
||||
scrollY: 1.5,
|
||||
buttons: 0,
|
||||
});
|
||||
expect(message2).toBeInstanceOf(Uint8Array);
|
||||
expect(message2).toHaveProperty("byteLength", 21);
|
||||
});
|
||||
});
|
|
@ -10,17 +10,18 @@ import {
|
|||
} from "../../control/index.js";
|
||||
import { type ScrcpyScrollController } from "../1_16/index.js";
|
||||
|
||||
const Int16Max = (1 << 15) - 1;
|
||||
|
||||
const ScrcpyFloatToInt16NumberType: NumberFieldType = {
|
||||
export const ScrcpyFloatToInt16NumberType: NumberFieldType = {
|
||||
size: 2,
|
||||
signed: true,
|
||||
deserialize(array, littleEndian) {
|
||||
const value = NumberFieldType.Int16.deserialize(array, littleEndian);
|
||||
return value / Int16Max;
|
||||
// https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/server/src/main/java/com/genymobile/scrcpy/Binary.java#L34
|
||||
return value === 0x7fff ? 1 : value / 0x8000;
|
||||
},
|
||||
serialize(dataView, offset, value, littleEndian) {
|
||||
value = clamp(value, -1, 1) * Int16Max;
|
||||
// https://github.com/Genymobile/scrcpy/blob/1f138aef41de651668043b32c4effc2d4adbfc44/app/src/util/binary.h#L65
|
||||
value = clamp(value, -1, 1);
|
||||
value = value === 1 ? 0x7fff : value * 0x8000;
|
||||
NumberFieldType.Int16.serialize(dataView, offset, value, littleEndian);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ function testEndian(
|
|||
max: number,
|
||||
littleEndian: boolean
|
||||
) {
|
||||
test("min", () => {
|
||||
test(`min = ${min}`, () => {
|
||||
const buffer = new ArrayBuffer(type.size);
|
||||
const view = new DataView(buffer);
|
||||
(
|
||||
|
@ -43,7 +43,7 @@ function testEndian(
|
|||
expect(output).toBe(input);
|
||||
});
|
||||
|
||||
test("max", () => {
|
||||
test(`max = ${max}`, () => {
|
||||
const buffer = new ArrayBuffer(type.size);
|
||||
const view = new DataView(buffer);
|
||||
(
|
||||
|
@ -61,41 +61,30 @@ function testEndian(
|
|||
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
|
||||
);
|
||||
const MIN = -(2 ** (type.size * 8 - 1));
|
||||
const MAX = -MIN - 1;
|
||||
testEndian(type, MIN, MAX, false);
|
||||
} else {
|
||||
testEndian(type, 0, 2 ** (type.size * 8) - 1, false);
|
||||
const MAX = 2 ** (type.size * 8) - 1;
|
||||
testEndian(type, 0, MAX, false);
|
||||
}
|
||||
} else {
|
||||
if (type.signed) {
|
||||
const MIN = -(2 ** (type.size * 8 - 1));
|
||||
const MAX = -MIN - 1;
|
||||
describe("big endian", () => {
|
||||
testEndian(
|
||||
type,
|
||||
2 ** (type.size * 8) / -2,
|
||||
2 ** (type.size * 8) / 2 - 1,
|
||||
false
|
||||
);
|
||||
testEndian(type, MIN, MAX, false);
|
||||
});
|
||||
|
||||
describe("little endian", () => {
|
||||
testEndian(
|
||||
type,
|
||||
2 ** (type.size * 8) / -2,
|
||||
2 ** (type.size * 8) / 2 - 1,
|
||||
true
|
||||
);
|
||||
testEndian(type, MIN, MAX, true);
|
||||
});
|
||||
} else {
|
||||
const MAX = 2 ** (type.size * 8) - 1;
|
||||
describe("big endian", () => {
|
||||
testEndian(type, 0, 2 ** (type.size * 8) - 1, false);
|
||||
testEndian(type, 0, MAX, false);
|
||||
});
|
||||
|
||||
describe("little endian", () => {
|
||||
testEndian(type, 0, 2 ** (type.size * 8) - 1, true);
|
||||
testEndian(type, 0, MAX, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,15 +55,32 @@ export function placeholder<T>(): T {
|
|||
|
||||
// This library can't use `@types/node` or `lib: dom`
|
||||
// because they will pollute the global scope
|
||||
// So `TextEncoder` and `TextDecoder` are not available
|
||||
// So `TextEncoder` and `TextDecoder` types are not available
|
||||
|
||||
// Node.js 8.3 ships `TextEncoder` and `TextDecoder` in `util` module.
|
||||
// But using top level await to load them requires Node.js 14.1.
|
||||
// So there is no point to do that. Let's just assume they exist in global.
|
||||
|
||||
// @ts-expect-error See reason above
|
||||
declare class TextEncoderType {
|
||||
constructor();
|
||||
|
||||
encode(input: string): Uint8Array;
|
||||
}
|
||||
|
||||
declare class TextDecoderType {
|
||||
constructor();
|
||||
|
||||
decode(buffer: ArrayBufferView | ArrayBuffer): string;
|
||||
}
|
||||
|
||||
interface GlobalExtension {
|
||||
TextEncoder: typeof TextEncoderType;
|
||||
TextDecoder: typeof TextDecoderType;
|
||||
}
|
||||
|
||||
const { TextEncoder, TextDecoder } = globalThis as unknown as GlobalExtension;
|
||||
|
||||
const Utf8Encoder = new TextEncoder();
|
||||
// @ts-expect-error See reason above
|
||||
const Utf8Decoder = new TextDecoder();
|
||||
|
||||
export function encodeUtf8(input: string): Uint8Array {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue