feat: migrate to Uint8Array

This commit is contained in:
Simon Chan 2022-02-23 23:09:56 +08:00
parent cb988f5563
commit 96b5807691
39 changed files with 429 additions and 752 deletions

View file

@ -24,7 +24,7 @@ const classNames = mergeStyleSets({
}); });
function serializePacket(packet: AdbPacketInit) { function serializePacket(packet: AdbPacketInit) {
const command = decodeUtf8(new Uint32Array([packet.command]).buffer); const command = decodeUtf8(new Uint32Array([packet.command]));
const parts = [ const parts = [
command, command,
@ -35,7 +35,7 @@ function serializePacket(packet: AdbPacketInit) {
if (packet.payload) { if (packet.payload) {
parts.push( parts.push(
Array.from( Array.from(
new Uint8Array(packet.payload), packet.payload,
byte => byte.toString(16).padStart(2, '0') byte => byte.toString(16).padStart(2, '0')
).join(' ') ).join(' ')
); );

View file

@ -36,16 +36,16 @@ export class AdbTerminal extends AutoDisposable {
this._socketAbortController = new AbortController(); this._socketAbortController = new AbortController();
value.stdout.pipeTo(new WritableStream({ value.stdout.pipeTo(new WritableStream<Uint8Array>({
write: (chunk) => { write: (chunk) => {
this.terminal.write(new Uint8Array(chunk)); this.terminal.write(chunk);
}, },
}), { }), {
signal: this._socketAbortController.signal, signal: this._socketAbortController.signal,
}); });
value.stderr.pipeTo(new WritableStream({ value.stderr.pipeTo(new WritableStream<Uint8Array>({
write: (chunk) => { write: (chunk) => {
this.terminal.write(new Uint8Array(chunk)); this.terminal.write(chunk);
}, },
}), { }), {
signal: this._socketAbortController.signal, signal: this._socketAbortController.signal,

View file

@ -4,7 +4,6 @@ import { getFileTypeIconNameFromExtensionOrType } from '@fluentui/react-file-typ
import { DEFAULT_BASE_URL as FILE_TYPE_ICONS_BASE_URL } from '@fluentui/react-file-type-icons/lib-commonjs/initializeFileTypeIcons'; import { DEFAULT_BASE_URL as FILE_TYPE_ICONS_BASE_URL } from '@fluentui/react-file-type-icons/lib-commonjs/initializeFileTypeIcons';
import { useConst } from '@fluentui/react-hooks'; import { useConst } from '@fluentui/react-hooks';
import { AdbSyncEntryResponse, ADB_SYNC_MAX_PACKET_SIZE, ChunkStream, LinuxFileType, ReadableStream, TransformStream } from '@yume-chan/adb'; import { AdbSyncEntryResponse, ADB_SYNC_MAX_PACKET_SIZE, ChunkStream, LinuxFileType, ReadableStream, TransformStream } from '@yume-chan/adb';
import { ExtractViewBufferStream } from "@yume-chan/adb-backend-direct-sockets";
import { action, autorun, makeAutoObservable, observable, runInAction } from "mobx"; import { action, autorun, makeAutoObservable, observable, runInAction } from "mobx";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { NextPage } from "next"; import { NextPage } from "next";
@ -21,7 +20,7 @@ import { asyncEffect, formatSize, formatSpeed, Icons, pickFile, RouteStackProps
* Because of internal buffer of upstream/downstream streams, * Because of internal buffer of upstream/downstream streams,
* the progress value won't be 100% accurate. But it's usually good enough. * the progress value won't be 100% accurate. But it's usually good enough.
*/ */
export class ProgressStream extends TransformStream<ArrayBuffer, ArrayBuffer> { export class ProgressStream extends TransformStream<Uint8Array, Uint8Array> {
public constructor(onProgress: (value: number) => void) { public constructor(onProgress: (value: number) => void) {
let progress = 0; let progress = 0;
super({ super({
@ -171,13 +170,7 @@ class FileManagerState {
item.name, item.name,
{ size: item.size } { size: item.size }
); );
await readable await readable.pipeTo(writeable);
.pipeThrough(new TransformStream({
transform(chunk, controller) {
controller.enqueue(new Uint8Array(chunk));
},
}))
.pipeTo(writeable);
} catch (e) { } catch (e) {
globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`); globalState.showErrorDialog(e instanceof Error ? e.message : `${e}`);
} finally { } finally {
@ -477,7 +470,6 @@ class FileManagerState {
try { try {
await (file.stream() as unknown as ReadableStream<Uint8Array>) await (file.stream() as unknown as ReadableStream<Uint8Array>)
.pipeThrough(new ExtractViewBufferStream())
.pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE)) .pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
.pipeThrough(new ProgressStream(action((uploaded) => { .pipeThrough(new ProgressStream(action((uploaded) => {
this.uploadedSize = uploaded; this.uploadedSize = uploaded;
@ -558,12 +550,7 @@ const FileManager: NextPage = (): JSX.Element | null => {
const previewImage = useCallback(async (path: string) => { const previewImage = useCallback(async (path: string) => {
const sync = await globalState.device!.sync(); const sync = await globalState.device!.sync();
try { try {
const readable = (await sync.read(path)) const readable = sync.read(path);
.pipeThrough(new TransformStream({
transform(chunk, controller) {
controller.enqueue(new Uint8Array(chunk));
},
}));
const response = new Response(readable); const response = new Response(readable);
const blob = await response.blob(); const blob = await response.blob();
const url = window.URL.createObjectURL(blob); const url = window.URL.createObjectURL(blob);

View file

@ -24,7 +24,7 @@ class FrameBufferState {
setImage(image: AdbFrameBuffer) { setImage(image: AdbFrameBuffer) {
this.width = image.width; this.width = image.width;
this.height = image.height; this.height = image.height;
this.imageData = new ImageData(image.data, image.width, image.height); this.imageData = new ImageData(new Uint8ClampedArray(image.data), image.width, image.height);
} }
toggleDemoModeVisible() { toggleDemoModeVisible() {

View file

@ -1,6 +1,5 @@
import { DefaultButton, ProgressIndicator, Stack } from "@fluentui/react"; import { DefaultButton, ProgressIndicator, Stack } from "@fluentui/react";
import { ADB_SYNC_MAX_PACKET_SIZE, ChunkStream, ReadableStream } from "@yume-chan/adb"; import { ADB_SYNC_MAX_PACKET_SIZE, ChunkStream, ReadableStream } from "@yume-chan/adb";
import { ExtractViewBufferStream } from "@yume-chan/adb-backend-direct-sockets";
import { action, makeAutoObservable, observable, runInAction } from "mobx"; import { action, makeAutoObservable, observable, runInAction } from "mobx";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { NextPage } from "next"; import { NextPage } from "next";
@ -59,7 +58,6 @@ class InstallPageState {
}); });
await (file.stream() as unknown as ReadableStream<Uint8Array>) await (file.stream() as unknown as ReadableStream<Uint8Array>)
.pipeThrough(new ExtractViewBufferStream())
.pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE)) .pipeThrough(new ChunkStream(ADB_SYNC_MAX_PACKET_SIZE))
.pipeThrough(new ProgressStream(action((uploaded) => { .pipeThrough(new ProgressStream(action((uploaded) => {
if (uploaded !== file.size) { if (uploaded !== file.size) {

View file

@ -17,7 +17,7 @@ import { ProgressStream } from "./file-manager";
const SERVER_URL = new URL('@yume-chan/scrcpy/bin/scrcpy-server?url', import.meta.url).toString(); const SERVER_URL = new URL('@yume-chan/scrcpy/bin/scrcpy-server?url', import.meta.url).toString();
class FetchWithProgress { class FetchWithProgress {
public readonly promise: Promise<ArrayBuffer>; public readonly promise: Promise<Uint8Array>;
private _downloaded = 0; private _downloaded = 0;
public get downloaded() { return this._downloaded; } public get downloaded() { return this._downloaded; }
@ -56,7 +56,7 @@ class FetchWithProgress {
result.set(chunk, position); result.set(chunk, position);
position += chunk.byteLength; position += chunk.byteLength;
} }
return result.buffer; return result;
} }
} }
@ -387,7 +387,7 @@ class ScrcpyPageState {
this.debouncedServerDownloadedSize = this.serverDownloadedSize; this.debouncedServerDownloadedSize = this.serverDownloadedSize;
}), 1000); }), 1000);
let serverBuffer: ArrayBuffer; let serverBuffer: Uint8Array;
try { try {
serverBuffer = await fetchServer(action(([downloaded, total]) => { serverBuffer = await fetchServer(action(([downloaded, total]) => {
@ -408,7 +408,7 @@ class ScrcpyPageState {
}), 1000); }), 1000);
try { try {
await new ReadableStream({ await new ReadableStream<Uint8Array>({
start(controller) { start(controller) {
controller.enqueue(serverBuffer); controller.enqueue(serverBuffer);
controller.close(); controller.close();
@ -450,7 +450,7 @@ class ScrcpyPageState {
// Run scrcpy once will delete the server file // Run scrcpy once will delete the server file
// Re-push it // Re-push it
await new ReadableStream({ await new ReadableStream<Uint8Array>({
start(controller) { start(controller) {
controller.enqueue(serverBuffer); controller.enqueue(serverBuffer);
controller.close(); controller.close();
@ -492,7 +492,7 @@ class ScrcpyPageState {
options options
); );
client.stdout.pipeTo(new WritableStream({ client.stdout.pipeTo(new WritableStream<string>({
write: action((line) => { write: action((line) => {
this.log.push(line); this.log.push(line);
}), }),

View file

@ -1,19 +1,4 @@
import { AdbBackend, ReadableStream, ReadableWritablePair, TransformStream, WritableStream } from '@yume-chan/adb'; import { AdbBackend, ReadableStream, WritableStream } from '@yume-chan/adb';
/**
* Transform an `ArrayBufferView` stream to an `ArrayBuffer` stream.
*
* The view must wrap the whole buffer (`byteOffset === 0` && `byteLength === buffer.byteLength`).
*/
export class ExtractViewBufferStream extends TransformStream<ArrayBufferView, ArrayBuffer>{
constructor() {
super({
transform(chunk, controller) {
controller.enqueue(chunk.buffer);
}
});
}
}
declare global { declare global {
interface TCPSocket { interface TCPSocket {
@ -44,28 +29,6 @@ declare global {
} }
} }
export class AdbDirectSocketsBackendStreams implements ReadableWritablePair<ArrayBuffer, ArrayBuffer>{
private socket: TCPSocket;
private _readableTransformStream: ExtractViewBufferStream;
public get readable(): ReadableStream<ArrayBuffer> {
return this._readableTransformStream.readable;
}
public get writable(): WritableStream<ArrayBuffer> {
return this.socket.writable;
}
constructor(socket: TCPSocket) {
this.socket = socket;
// Although Direct Sockets spec didn't say,
// WebTransport spec and File spec all have the `Uint8Array` wraps the whole `ArrayBuffer`.
this._readableTransformStream = new ExtractViewBufferStream();
this.socket.readable.pipeTo(this._readableTransformStream.writable);
}
}
export default class AdbDirectSocketsBackend implements AdbBackend { export default class AdbDirectSocketsBackend implements AdbBackend {
public static isSupported(): boolean { public static isSupported(): boolean {
return typeof window !== 'undefined' && !!window.navigator?.openTCPSocket; return typeof window !== 'undefined' && !!window.navigator?.openTCPSocket;
@ -87,12 +50,10 @@ export default class AdbDirectSocketsBackend implements AdbBackend {
} }
public async connect() { public async connect() {
const socket = await navigator.openTCPSocket({ return await navigator.openTCPSocket({
remoteAddress: this.host, remoteAddress: this.host,
remotePort: this.port, remotePort: this.port,
noDelay: true, noDelay: true,
}); });
return new AdbDirectSocketsBackendStreams(socket);
} }
} }

View file

@ -6,15 +6,15 @@ export const WebUsbDeviceFilter: USBDeviceFilter = {
protocolCode: 1, protocolCode: 1,
}; };
export class AdbWebUsbBackendStream implements ReadableWritablePair<ArrayBuffer, ArrayBuffer>{ export class AdbWebUsbBackendStream implements ReadableWritablePair<Uint8Array, Uint8Array>{
private _readable: ReadableStream<ArrayBuffer>; private _readable: ReadableStream<Uint8Array>;
public get readable() { return this._readable; } public get readable() { return this._readable; }
private _writable: WritableStream<ArrayBuffer>; private _writable: WritableStream<Uint8Array>;
public get writable() { return this._writable; } public get writable() { return this._writable; }
public constructor(device: USBDevice, inEndpoint: USBEndpoint, outEndpoint: USBEndpoint) { public constructor(device: USBDevice, inEndpoint: USBEndpoint, outEndpoint: USBEndpoint) {
this._readable = new ReadableStream({ this._readable = new ReadableStream<Uint8Array>({
pull: async (controller) => { pull: async (controller) => {
let result = await device.transferIn(inEndpoint.endpointNumber, inEndpoint.packetSize); let result = await device.transferIn(inEndpoint.endpointNumber, inEndpoint.packetSize);
@ -24,8 +24,8 @@ export class AdbWebUsbBackendStream implements ReadableWritablePair<ArrayBuffer,
result = await device.transferIn(inEndpoint.endpointNumber, inEndpoint.packetSize); result = await device.transferIn(inEndpoint.endpointNumber, inEndpoint.packetSize);
} }
const { buffer } = result.data!; const view = result.data!;
controller.enqueue(buffer); controller.enqueue(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
}, },
cancel: async () => { cancel: async () => {
await device.close(); await device.close();
@ -35,7 +35,7 @@ export class AdbWebUsbBackendStream implements ReadableWritablePair<ArrayBuffer,
size(chunk) { return chunk.byteLength; }, size(chunk) { return chunk.byteLength; },
}); });
this._writable = new WritableStream({ this._writable = new WritableStream<Uint8Array>({
write: async (chunk) => { write: async (chunk) => {
await device.transferOut(outEndpoint.endpointNumber, chunk); await device.transferOut(outEndpoint.endpointNumber, chunk);
}, },

View file

@ -21,10 +21,10 @@ export default class AdbWsBackend implements AdbBackend {
}; };
}); });
const readable = new ReadableStream({ const readable = new ReadableStream<Uint8Array>({
start: (controller) => { start: (controller) => {
socket.onmessage = ({ data }: { data: ArrayBuffer; }) => { socket.onmessage = ({ data }: { data: ArrayBuffer; }) => {
controller.enqueue(data); controller.enqueue(new Uint8Array(data));
}; };
socket.onclose = () => { socket.onclose = () => {
controller.close(); controller.close();
@ -35,7 +35,7 @@ export default class AdbWsBackend implements AdbBackend {
size(chunk) { return chunk.byteLength; }, size(chunk) { return chunk.byteLength; },
}); });
const writable = new WritableStream({ const writable = new WritableStream<Uint8Array>({
write: (chunk) => { write: (chunk) => {
socket.send(chunk); socket.send(chunk);
}, },

View file

@ -1,16 +1,16 @@
// cspell: ignore RSASSA // cspell: ignore RSASSA
import { type AdbCredentialStore, calculateBase64EncodedLength, calculatePublicKey, calculatePublicKeyLength, decodeBase64, encodeBase64 } from "@yume-chan/adb"; import { calculateBase64EncodedLength, calculatePublicKey, calculatePublicKeyLength, decodeBase64, encodeBase64, type AdbCredentialStore } from "@yume-chan/adb";
const Utf8Encoder = new TextEncoder(); const Utf8Encoder = new TextEncoder();
const Utf8Decoder = new TextDecoder(); const Utf8Decoder = new TextDecoder();
export function encodeUtf8(input: string): ArrayBuffer { export function encodeUtf8(input: string): Uint8Array {
return Utf8Encoder.encode(input).buffer; return Utf8Encoder.encode(input);
} }
export function decodeUtf8(buffer: ArrayBuffer): string { export function decodeUtf8(array: Uint8Array): string {
return Utf8Decoder.decode(buffer); return Utf8Decoder.decode(array);
} }
export default class AdbWebCredentialStore implements AdbCredentialStore { export default class AdbWebCredentialStore implements AdbCredentialStore {
@ -20,14 +20,14 @@ export default class AdbWebCredentialStore implements AdbCredentialStore {
this.localStorageKey = localStorageKey; this.localStorageKey = localStorageKey;
} }
public *iterateKeys(): Generator<ArrayBuffer, void, void> { public *iterateKeys(): Generator<Uint8Array, void, void> {
const privateKey = window.localStorage.getItem(this.localStorageKey); const privateKey = window.localStorage.getItem(this.localStorageKey);
if (privateKey) { if (privateKey) {
yield decodeBase64(privateKey); yield decodeBase64(privateKey);
} }
} }
public async generateKey(): Promise<ArrayBuffer> { public async generateKey(): Promise<Uint8Array> {
const { privateKey: cryptoKey } = await crypto.subtle.generateKey( const { privateKey: cryptoKey } = await crypto.subtle.generateKey(
{ {
name: 'RSASSA-PKCS1-v1_5', name: 'RSASSA-PKCS1-v1_5',
@ -40,7 +40,7 @@ export default class AdbWebCredentialStore implements AdbCredentialStore {
['sign', 'verify'] ['sign', 'verify']
); );
const privateKey = await crypto.subtle.exportKey('pkcs8', cryptoKey!); const privateKey = new Uint8Array(await crypto.subtle.exportKey('pkcs8', cryptoKey!));
window.localStorage.setItem(this.localStorageKey, decodeUtf8(encodeBase64(privateKey))); window.localStorage.setItem(this.localStorageKey, decodeUtf8(encodeBase64(privateKey)));
// The authentication module in core doesn't need public keys. // The authentication module in core doesn't need public keys.
@ -49,7 +49,7 @@ export default class AdbWebCredentialStore implements AdbCredentialStore {
// so also save the public key for their convenience. // so also save the public key for their convenience.
const publicKeyLength = calculatePublicKeyLength(); const publicKeyLength = calculatePublicKeyLength();
const [publicKeyBase64Length] = calculateBase64EncodedLength(publicKeyLength); const [publicKeyBase64Length] = calculateBase64EncodedLength(publicKeyLength);
const publicKeyBuffer = new ArrayBuffer(publicKeyBase64Length); const publicKeyBuffer = new Uint8Array(publicKeyBase64Length);
calculatePublicKey(privateKey, publicKeyBuffer); calculatePublicKey(privateKey, publicKeyBuffer);
encodeBase64(publicKeyBuffer, 0, publicKeyLength, publicKeyBuffer); encodeBase64(publicKeyBuffer, 0, publicKeyLength, publicKeyBuffer);
window.localStorage.setItem(this.localStorageKey + '.pub', decodeUtf8(publicKeyBuffer)); window.localStorage.setItem(this.localStorageKey + '.pub', decodeUtf8(publicKeyBuffer));

View file

@ -52,9 +52,11 @@ export class AdbNoneSubprocessProtocol implements AdbSubprocessProtocol {
state: undefined, state: undefined,
}; };
}, },
async close() { close: async () => {
// Close `stderr` on exit. // Close `stderr` on exit.
stderrController.close(); stderrController.close();
this._exit.resolve(0);
} }
}); });
} }

View file

@ -19,7 +19,7 @@ export function adbSyncPull(
writer: WritableStreamDefaultWriter<Uint8Array>, writer: WritableStreamDefaultWriter<Uint8Array>,
path: string, path: string,
): ReadableStream<Uint8Array> { ): ReadableStream<Uint8Array> {
return new ReadableStream({ return new ReadableStream<Uint8Array>({
async start() { async start() {
await adbSyncWriteRequest(writer, AdbSyncRequestId.Receive, path); await adbSyncWriteRequest(writer, AdbSyncRequestId.Receive, path);
}, },

View file

@ -23,7 +23,7 @@ export function adbSyncPush(
packetSize: number = ADB_SYNC_MAX_PACKET_SIZE, packetSize: number = ADB_SYNC_MAX_PACKET_SIZE,
): WritableStream<Uint8Array> { ): WritableStream<Uint8Array> {
const { readable, writable } = new ChunkStream(packetSize); const { readable, writable } = new ChunkStream(packetSize);
readable.pipeTo(new WritableStream({ readable.pipeTo(new WritableStream<Uint8Array>({
async start() { async start() {
const pathAndMode = `${filename},${mode.toString()}`; const pathAndMode = `${filename},${mode.toString()}`;
await adbSyncWriteRequest(writer, AdbSyncRequestId.Send, pathAndMode); await adbSyncWriteRequest(writer, AdbSyncRequestId.Send, pathAndMode);

View file

@ -30,20 +30,20 @@ export async function adbSyncWriteRequest(
): Promise<void> { ): Promise<void> {
let buffer: Uint8Array; let buffer: Uint8Array;
if (typeof value === 'number') { if (typeof value === 'number') {
buffer = new Uint8Array(AdbSyncNumberRequest.serialize({ buffer = AdbSyncNumberRequest.serialize({
id, id,
arg: value, arg: value,
})); });
} else if (typeof value === 'string') { } else if (typeof value === 'string') {
buffer = new Uint8Array(AdbSyncDataRequest.serialize({ buffer = AdbSyncDataRequest.serialize({
id, id,
data: encodeUtf8(value), data: encodeUtf8(value),
})); });
} else { } else {
buffer = new Uint8Array(AdbSyncDataRequest.serialize({ buffer = AdbSyncDataRequest.serialize({
id, id,
data: value, data: value,
})); });
} }
await writer.write(buffer); await writer.write(buffer);
} }

View file

@ -36,7 +36,7 @@ export class AdbPacketSerializeStream extends TransformStream<AdbPacketInit, Uin
transform: async (init, controller) => { transform: async (init, controller) => {
let checksum: number; let checksum: number;
if (this.calculateChecksum && init.payload) { if (this.calculateChecksum && init.payload) {
const array = new Uint8Array(init.payload); const array = init.payload;
checksum = array.reduce((result, item) => result + item, 0); checksum = array.reduce((result, item) => result + item, 0);
} else { } else {
checksum = 0; checksum = 0;
@ -49,7 +49,7 @@ export class AdbPacketSerializeStream extends TransformStream<AdbPacketInit, Uin
payloadLength: init.payload.byteLength, payloadLength: init.payload.byteLength,
}; };
controller.enqueue(new Uint8Array(AdbPacketHeader.serialize(packet))); controller.enqueue(AdbPacketHeader.serialize(packet));
if (packet.payloadLength) { if (packet.payloadLength) {
controller.enqueue(packet.payload); controller.enqueue(packet.payload);

View file

@ -62,7 +62,7 @@ export class AdbPacketDispatcher extends AutoDisposable {
{ signal: this._abortController.signal, preventCancel: true } { signal: this._abortController.signal, preventCancel: true }
) )
.pipeThrough(new StructDeserializeStream(AdbPacket)) .pipeThrough(new StructDeserializeStream(AdbPacket))
.pipeTo(new WritableStream({ .pipeTo(new WritableStream<AdbPacket>({
write: async (packet) => { write: async (packet) => {
try { try {
this.logger?.onIncomingPacket?.(packet); this.logger?.onIncomingPacket?.(packet);

View file

@ -36,7 +36,7 @@ export class BufferedStream {
const buffer = this.buffer; const buffer = this.buffer;
if (buffer.byteLength > length) { if (buffer.byteLength > length) {
this.buffer = buffer.subarray(length); this.buffer = buffer.subarray(length);
return buffer.slice(0, length); return buffer.subarray(0, length);
} }
array = new Uint8Array(length); array = new Uint8Array(length);
@ -60,11 +60,11 @@ export class BufferedStream {
if (value.byteLength > length) { if (value.byteLength > length) {
this.buffer = value.subarray(length); this.buffer = value.subarray(length);
return value.slice(0, length); return value.subarray(0, length);
} }
array = new Uint8Array(length); array = new Uint8Array(length);
array.set(new Uint8Array(value), 0); array.set(value, 0);
index = value.byteLength; index = value.byteLength;
} }

View file

@ -70,7 +70,7 @@ export class StructSerializeStream<T extends Struct<any, any, any, any>>
constructor(struct: T) { constructor(struct: T) {
super({ super({
transform(chunk, controller) { transform(chunk, controller) {
controller.enqueue(new Uint8Array(struct.serialize(chunk))); controller.enqueue(struct.serialize(chunk));
}, },
}); });
} }
@ -140,6 +140,7 @@ export class WrapReadableStream<T, R extends ReadableStream<T>, S> extends Reada
pull: async (controller) => { pull: async (controller) => {
const result = await this.reader.read(); const result = await this.reader.read();
if (result.done) { if (result.done) {
wrapper.close?.(this.state);
controller.close(); controller.close();
} else { } else {
controller.enqueue(result.value); controller.enqueue(result.value);
@ -153,9 +154,9 @@ export class ChunkStream extends TransformStream<Uint8Array, Uint8Array>{
public constructor(size: number) { public constructor(size: number) {
super({ super({
transform(chunk, controller) { transform(chunk, controller) {
for (let start = 0; start < chunk.length; start += size) { for (let start = 0; start < chunk.byteLength;) {
const end = start + size; const end = start + size;
controller.enqueue(chunk.slice(start, end)); controller.enqueue(chunk.subarray(start, end));
start = end; start = end;
} }
} }

View file

@ -8,6 +8,6 @@ export function encodeUtf8(input: string): Uint8Array {
return Utf8Encoder.encode(input); return Utf8Encoder.encode(input);
} }
export function decodeUtf8(buffer: Uint8Array): string { export function decodeUtf8(buffer: ArrayBufferView | ArrayBuffer): string {
return Utf8Decoder.decode(buffer); return Utf8Decoder.decode(buffer);
} }

View file

@ -50,8 +50,8 @@ export class BugReportZ extends AdbCommandBase {
return version.major > 1 || version.minor >= 2; return version.major > 1 || version.minor >= 2;
} }
public stream(): ReadableStream<ArrayBuffer> { public stream(): ReadableStream<Uint8Array> {
return new WrapReadableStream<ArrayBuffer, ReadableStream<ArrayBuffer>, undefined>({ return new WrapReadableStream<Uint8Array, ReadableStream<Uint8Array>, undefined>({
start: async () => { start: async () => {
const process = await this.adb.subprocess.spawn(['bugreportz', '-s']); const process = await this.adb.subprocess.spawn(['bugreportz', '-s']);
return { return {
@ -86,7 +86,7 @@ export class BugReportZ extends AdbCommandBase {
await process.stdout await process.stdout
.pipeThrough(new DecodeUtf8Stream()) .pipeThrough(new DecodeUtf8Stream())
.pipeThrough(new SplitLineStream()) .pipeThrough(new SplitLineStream())
.pipeTo(new WritableStream({ .pipeTo(new WritableStream<string>({
write(line) { write(line) {
// (Not 100% sure) `BEGIN:` and `PROGRESS:` only appear when `-p` is specified. // (Not 100% sure) `BEGIN:` and `PROGRESS:` only appear when `-p` is specified.
let match = line.match(BugReportZ.PROGRESS_REGEX); let match = line.match(BugReportZ.PROGRESS_REGEX);

View file

@ -124,7 +124,7 @@ export class ScrcpyClient {
private _videoStream: TransformStream<VideoStreamPacket, VideoStreamPacket>; private _videoStream: TransformStream<VideoStreamPacket, VideoStreamPacket>;
public get videoStream() { return this._videoStream.readable; } public get videoStream() { return this._videoStream.readable; }
private _controlStreamWriter: WritableStreamDefaultWriter<ArrayBuffer> | undefined; private _controlStreamWriter: WritableStreamDefaultWriter<Uint8Array> | undefined;
private readonly clipboardChangeEvent = new EventEmitter<string>(); private readonly clipboardChangeEvent = new EventEmitter<string>();
public get onClipboardChange() { return this.clipboardChangeEvent.event; } public get onClipboardChange() { return this.clipboardChangeEvent.event; }
@ -179,7 +179,7 @@ export class ScrcpyClient {
try { try {
while (true) { while (true) {
const type = await buffered.read(1); const type = await buffered.read(1);
switch (new Uint8Array(type)[0]) { switch (type[0]) {
case 0: case 0:
const { content } = await ClipboardMessage.deserialize(buffered); const { content } = await ClipboardMessage.deserialize(buffered);
this.clipboardChangeEvent.fire(content!); this.clipboardChangeEvent.fire(content!);

View file

@ -37,7 +37,7 @@ export class TinyH264Decoder implements H264Decoder {
this._renderer = document.createElement('canvas'); this._renderer = document.createElement('canvas');
this._writable = new WritableStream({ this._writable = new WritableStream<VideoStreamPacket>({
write: async (packet) => { write: async (packet) => {
switch (packet.type) { switch (packet.type) {
case 'configuration': case 'configuration':
@ -49,7 +49,7 @@ export class TinyH264Decoder implements H264Decoder {
} }
const wrapper = await this._initializer.promise; const wrapper = await this._initializer.promise;
wrapper.feed(packet.data); wrapper.feed(packet.data.slice().buffer);
break; break;
} }
} }

View file

@ -32,7 +32,7 @@ export class WebCodecsDecoder implements H264Decoder {
error() { }, error() { },
}); });
this._writable = new WritableStream({ this._writable = new WritableStream<VideoStreamPacket>({
write: async (packet) => { write: async (packet) => {
switch (packet.type) { switch (packet.type) {
case 'configuration': case 'configuration':

View file

@ -5,7 +5,7 @@ import { ScrcpyClientConnection, ScrcpyClientConnectionOptions, ScrcpyClientForw
import { AndroidKeyEventAction, ScrcpyControlMessageType } from "../../message"; import { AndroidKeyEventAction, ScrcpyControlMessageType } from "../../message";
import type { ScrcpyBackOrScreenOnEvent1_18 } from "../1_18"; import type { ScrcpyBackOrScreenOnEvent1_18 } from "../1_18";
import type { ScrcpyInjectScrollControlMessage1_22 } from "../1_22"; import type { ScrcpyInjectScrollControlMessage1_22 } from "../1_22";
import { toScrcpyOptionValue, type VideoStreamPacket, type ScrcpyOptions, type ScrcpyOptionValue } from "../common"; import { toScrcpyOptionValue, type ScrcpyOptions, type ScrcpyOptionValue, type VideoStreamPacket } from "../common";
import { parse_sequence_parameter_set } from "./sps"; import { parse_sequence_parameter_set } from "./sps";
export enum ScrcpyLogLevel { export enum ScrcpyLogLevel {
@ -114,7 +114,7 @@ export const VideoPacket =
new Struct() new Struct()
.int64('pts') .int64('pts')
.uint32('size') .uint32('size')
.arrayBuffer('data', { lengthField: 'size' }); .uint8Array('data', { lengthField: 'size' });
export const NoPts = BigInt(-1); export const NoPts = BigInt(-1);
@ -135,7 +135,7 @@ export const ScrcpyInjectScrollControlMessage1_16 =
export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsInit1_16> implements ScrcpyOptions<T> { export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsInit1_16> implements ScrcpyOptions<T> {
public value: Partial<T>; public value: Partial<T>;
private _streamHeader: ArrayBuffer | undefined; private _streamHeader: Uint8Array | undefined;
public constructor(value: Partial<ScrcpyOptionsInit1_16>) { public constructor(value: Partial<ScrcpyOptionsInit1_16>) {
if (new.target === ScrcpyOptions1_16 && if (new.target === ScrcpyOptions1_16 &&
@ -223,7 +223,7 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
const { pts, data } = await VideoPacket.deserialize(stream); const { pts, data } = await VideoPacket.deserialize(stream);
if (pts === NoPts) { if (pts === NoPts) {
const sequenceParameterSet = parse_sequence_parameter_set(data.slice(0)); const sequenceParameterSet = parse_sequence_parameter_set(data.slice().buffer);
const { const {
profile_idc: profileIndex, profile_idc: profileIndex,
@ -267,12 +267,11 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
}; };
} }
let frameData: ArrayBuffer; let frameData: Uint8Array;
if (this._streamHeader) { if (this._streamHeader) {
frameData = new ArrayBuffer(this._streamHeader.byteLength + data.byteLength); frameData = new Uint8Array(this._streamHeader.byteLength + data.byteLength);
const array = new Uint8Array(frameData); frameData.set(this._streamHeader);
array.set(new Uint8Array(this._streamHeader)); frameData.set(data!, this._streamHeader.byteLength);
array.set(new Uint8Array(data!), this._streamHeader.byteLength);
this._streamHeader = undefined; this._streamHeader = undefined;
} else { } else {
frameData = data; frameData = data;
@ -296,7 +295,7 @@ export class ScrcpyOptions1_16<T extends ScrcpyOptionsInit1_16 = ScrcpyOptionsIn
public serializeInjectScrollControlMessage( public serializeInjectScrollControlMessage(
message: ScrcpyInjectScrollControlMessage1_22, message: ScrcpyInjectScrollControlMessage1_22,
): ArrayBuffer { ): Uint8Array {
return ScrcpyInjectScrollControlMessage1_16.serialize(message); return ScrcpyInjectScrollControlMessage1_16.serialize(message);
} }
} }

View file

@ -1,6 +1,6 @@
import type { Adb } from "@yume-chan/adb"; import type { Adb } from "@yume-chan/adb";
import Struct from "@yume-chan/struct"; import Struct from "@yume-chan/struct";
import { type ScrcpyClientConnection, ScrcpyClientConnectionOptions, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection } from "../connection"; import { ScrcpyClientConnectionOptions, ScrcpyClientForwardConnection, ScrcpyClientReverseConnection, type ScrcpyClientConnection } from "../connection";
import { ScrcpyInjectScrollControlMessage1_16 } from "./1_16"; import { ScrcpyInjectScrollControlMessage1_16 } from "./1_16";
import { ScrcpyOptions1_21, type ScrcpyOptionsInit1_21 } from "./1_21"; import { ScrcpyOptions1_21, type ScrcpyOptionsInit1_21 } from "./1_21";
@ -74,7 +74,7 @@ export class ScrcpyOptions1_22<T extends ScrcpyOptionsInit1_22 = ScrcpyOptionsIn
public override serializeInjectScrollControlMessage( public override serializeInjectScrollControlMessage(
message: ScrcpyInjectScrollControlMessage1_22, message: ScrcpyInjectScrollControlMessage1_22,
): ArrayBuffer { ): Uint8Array {
return ScrcpyInjectScrollControlMessage1_22.serialize(message); return ScrcpyInjectScrollControlMessage1_22.serialize(message);
} }
} }

View file

@ -35,7 +35,7 @@ export interface VideoStreamConfigurationPacket {
export interface VideoStreamFramePacket { export interface VideoStreamFramePacket {
type: 'frame'; type: 'frame';
data: ArrayBuffer; data: Uint8Array;
} }
export type VideoStreamPacket = VideoStreamConfigurationPacket | VideoStreamFramePacket; export type VideoStreamPacket = VideoStreamConfigurationPacket | VideoStreamFramePacket;
@ -53,9 +53,9 @@ export interface ScrcpyOptions<T> {
serializeBackOrScreenOnControlMessage( serializeBackOrScreenOnControlMessage(
message: ScrcpyBackOrScreenOnEvent1_18, message: ScrcpyBackOrScreenOnEvent1_18,
): ArrayBuffer | undefined; ): Uint8Array | undefined;
serializeInjectScrollControlMessage( serializeInjectScrollControlMessage(
message: ScrcpyInjectScrollControlMessage1_22, message: ScrcpyInjectScrollControlMessage1_22,
): ArrayBuffer; ): Uint8Array;
} }

View file

@ -11,7 +11,7 @@ export function pushServer(
) { ) {
const { path = DEFAULT_SERVER_PATH } = options; const { path = DEFAULT_SERVER_PATH } = options;
return new WrapWritableStream<ArrayBuffer, WritableStream<ArrayBuffer>, AdbSync>({ return new WrapWritableStream<Uint8Array, WritableStream<Uint8Array>, AdbSync>({
async start() { async start() {
const sync = await device.sync(); const sync = await device.sync();
return { return {

View file

@ -3,7 +3,7 @@
import type { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions } from './basic'; import type { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions } from './basic';
import { StructDefaultOptions, StructValue } from './basic'; import { StructDefaultOptions, StructValue } from './basic';
import { Syncbird } from "./syncbird"; import { Syncbird } from "./syncbird";
import { ArrayBufferFieldType, ArrayBufferLikeFieldType, BigIntFieldDefinition, BigIntFieldType, FixedLengthArrayBufferLikeFieldDefinition, FixedLengthArrayBufferLikeFieldOptions, LengthField, NumberFieldDefinition, NumberFieldType, StringFieldType, ArrayBufferViewFieldType, VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldOptions } from './types'; import { BigIntFieldDefinition, BigIntFieldType, BufferFieldSubType, FixedLengthBufferLikeFieldDefinition, FixedLengthBufferLikeFieldOptions, LengthField, NumberFieldDefinition, NumberFieldType, StringBufferFieldSubType, Uint8ArrayBufferFieldSubType, VariableLengthBufferLikeFieldDefinition, VariableLengthBufferLikeFieldOptions } from './types';
import { Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils"; import { Evaluate, Identity, Overwrite, ValueOrPromise } from "./utils";
export interface StructLike<TValue> { export interface StructLike<TValue> {
@ -57,12 +57,12 @@ interface ArrayBufferLikeFieldCreator<
*/ */
< <
TName extends PropertyKey, TName extends PropertyKey,
TType extends ArrayBufferLikeFieldType<any, any>, TType extends BufferFieldSubType<any, any>,
TTypeScriptType = TType['TTypeScriptType'], TTypeScriptType = TType['TTypeScriptType'],
>( >(
name: TName, name: TName,
type: TType, type: TType,
options: FixedLengthArrayBufferLikeFieldOptions, options: FixedLengthBufferLikeFieldOptions,
typescriptType?: TTypeScriptType, typescriptType?: TTypeScriptType,
): AddFieldDescriptor< ): AddFieldDescriptor<
TFields, TFields,
@ -70,9 +70,9 @@ interface ArrayBufferLikeFieldCreator<
TExtra, TExtra,
TPostDeserialized, TPostDeserialized,
TName, TName,
FixedLengthArrayBufferLikeFieldDefinition< FixedLengthBufferLikeFieldDefinition<
TType, TType,
FixedLengthArrayBufferLikeFieldOptions FixedLengthBufferLikeFieldOptions
> >
>; >;
@ -81,8 +81,8 @@ interface ArrayBufferLikeFieldCreator<
*/ */
< <
TName extends PropertyKey, TName extends PropertyKey,
TType extends ArrayBufferLikeFieldType<any, any>, TType extends BufferFieldSubType<any, any>,
TOptions extends VariableLengthArrayBufferLikeFieldOptions<TFields>, TOptions extends VariableLengthBufferLikeFieldOptions<TFields>,
TTypeScriptType = TType['TTypeScriptType'], TTypeScriptType = TType['TTypeScriptType'],
>( >(
name: TName, name: TName,
@ -95,7 +95,7 @@ interface ArrayBufferLikeFieldCreator<
TExtra, TExtra,
TPostDeserialized, TPostDeserialized,
TName, TName,
VariableLengthArrayBufferLikeFieldDefinition< VariableLengthBufferLikeFieldDefinition<
TType, TType,
TOptions TOptions
> >
@ -110,14 +110,14 @@ interface BoundArrayBufferLikeFieldDefinitionCreator<
TOmitInitKey extends PropertyKey, TOmitInitKey extends PropertyKey,
TExtra extends object, TExtra extends object,
TPostDeserialized, TPostDeserialized,
TType extends ArrayBufferLikeFieldType<any, any> TType extends BufferFieldSubType<any, any>
> { > {
< <
TName extends PropertyKey, TName extends PropertyKey,
TTypeScriptType = TType['TTypeScriptType'], TTypeScriptType = TType['TTypeScriptType'],
>( >(
name: TName, name: TName,
options: FixedLengthArrayBufferLikeFieldOptions, options: FixedLengthBufferLikeFieldOptions,
typescriptType?: TTypeScriptType, typescriptType?: TTypeScriptType,
): AddFieldDescriptor< ): AddFieldDescriptor<
TFields, TFields,
@ -125,16 +125,16 @@ interface BoundArrayBufferLikeFieldDefinitionCreator<
TExtra, TExtra,
TPostDeserialized, TPostDeserialized,
TName, TName,
FixedLengthArrayBufferLikeFieldDefinition< FixedLengthBufferLikeFieldDefinition<
TType, TType,
FixedLengthArrayBufferLikeFieldOptions FixedLengthBufferLikeFieldOptions
> >
>; >;
< <
TName extends PropertyKey, TName extends PropertyKey,
TLengthField extends LengthField<TFields>, TLengthField extends LengthField<TFields>,
TOptions extends VariableLengthArrayBufferLikeFieldOptions<TFields, TLengthField>, TOptions extends VariableLengthBufferLikeFieldOptions<TFields, TLengthField>,
TTypeScriptType = TType['TTypeScriptType'], TTypeScriptType = TType['TTypeScriptType'],
>( >(
name: TName, name: TName,
@ -146,7 +146,7 @@ interface BoundArrayBufferLikeFieldDefinitionCreator<
TExtra, TExtra,
TPostDeserialized, TPostDeserialized,
TName, TName,
VariableLengthArrayBufferLikeFieldDefinition< VariableLengthBufferLikeFieldDefinition<
TType, TType,
TOptions TOptions
> >
@ -421,59 +421,33 @@ export class Struct<
TPostDeserialized TPostDeserialized
> = ( > = (
name: PropertyKey, name: PropertyKey,
type: ArrayBufferLikeFieldType, type: BufferFieldSubType,
options: FixedLengthArrayBufferLikeFieldOptions | VariableLengthArrayBufferLikeFieldOptions options: FixedLengthBufferLikeFieldOptions | VariableLengthBufferLikeFieldOptions
): any => { ): any => {
if ('length' in options) { if ('length' in options) {
return this.field( return this.field(
name, name,
new FixedLengthArrayBufferLikeFieldDefinition(type, options), new FixedLengthBufferLikeFieldDefinition(type, options),
); );
} else { } else {
return this.field( return this.field(
name, name,
new VariableLengthArrayBufferLikeFieldDefinition(type, options), new VariableLengthBufferLikeFieldDefinition(type, options),
); );
} }
}; };
public arrayBuffer: BoundArrayBufferLikeFieldDefinitionCreator<
TFields,
TOmitInitKey,
TExtra,
TPostDeserialized,
ArrayBufferFieldType
> = (
name: PropertyKey,
options: any
): any => {
return this.arrayBufferLike(name, ArrayBufferFieldType.instance, options);
};
public uint8Array: BoundArrayBufferLikeFieldDefinitionCreator< public uint8Array: BoundArrayBufferLikeFieldDefinitionCreator<
TFields, TFields,
TOmitInitKey, TOmitInitKey,
TExtra, TExtra,
TPostDeserialized, TPostDeserialized,
ArrayBufferViewFieldType<Uint8Array> Uint8ArrayBufferFieldSubType
> = ( > = (
name: PropertyKey, name: PropertyKey,
options: any options: any
): any => { ): any => {
return this.arrayBufferLike(name, ArrayBufferViewFieldType.uint8Array, options); return this.arrayBufferLike(name, Uint8ArrayBufferFieldSubType.Instance, options);
};
public uint8ClampedArray: BoundArrayBufferLikeFieldDefinitionCreator<
TFields,
TOmitInitKey,
TExtra,
TPostDeserialized,
ArrayBufferViewFieldType<Uint8ClampedArray>
> = (
name: PropertyKey,
options: any
): any => {
return this.arrayBufferLike(name, ArrayBufferViewFieldType.uint8ClampedArray, options);
}; };
public string: BoundArrayBufferLikeFieldDefinitionCreator< public string: BoundArrayBufferLikeFieldDefinitionCreator<
@ -481,12 +455,12 @@ export class Struct<
TOmitInitKey, TOmitInitKey,
TExtra, TExtra,
TPostDeserialized, TPostDeserialized,
StringFieldType StringBufferFieldSubType
> = ( > = (
name: PropertyKey, name: PropertyKey,
options: any options: any
): any => { ): any => {
return this.arrayBufferLike(name, StringFieldType.instance, options); return this.arrayBufferLike(name, StringBufferFieldSubType.Instance, options);
}; };
/** /**

View file

@ -1,207 +0,0 @@
import { StructDefaultOptions, StructDeserializeStream, StructValue } from '../basic';
import { ArrayBufferFieldType, ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, StringFieldType, ArrayBufferViewFieldType } from './array-buffer';
class MockDeserializationStream implements StructDeserializeStream {
public buffer = new ArrayBuffer(0);
public read = jest.fn((length: number) => this.buffer);
}
describe('Types', () => {
describe('ArrayBufferLike', () => {
describe('ArrayBufferFieldType', () => {
it('should have a static instance', () => {
expect(ArrayBufferFieldType.instance).toBeInstanceOf(ArrayBufferFieldType);
});
it('`#toArrayBuffer` should return the same `ArrayBuffer`', () => {
const arrayBuffer = new ArrayBuffer(10);
expect(ArrayBufferFieldType.instance.toArrayBuffer(arrayBuffer)).toBe(arrayBuffer);
});
it('`#fromArrayBuffer` should return the same `ArrayBuffer`', () => {
const arrayBuffer = new ArrayBuffer(10);
expect(ArrayBufferFieldType.instance.fromArrayBuffer(arrayBuffer)).toBe(arrayBuffer);
});
it('`#getSize` should return the `byteLength` of the `ArrayBuffer`', () => {
const arrayBuffer = new ArrayBuffer(10);
expect(ArrayBufferFieldType.instance.getSize(arrayBuffer)).toBe(10);
});
});
describe('Uint8ClampedArrayFieldType', () => {
it('should have a static instance', () => {
expect(ArrayBufferViewFieldType.instance).toBeInstanceOf(ArrayBufferViewFieldType);
});
it('`#toArrayBuffer` should return its `buffer`', () => {
const array = new Uint8ClampedArray(10);
const buffer = array.buffer;
expect(ArrayBufferViewFieldType.instance.toArrayBuffer(array)).toBe(buffer);
});
it('`#fromArrayBuffer` should return a view of the `ArrayBuffer`', () => {
const arrayBuffer = new ArrayBuffer(10);
const array = ArrayBufferViewFieldType.instance.fromArrayBuffer(arrayBuffer);
expect(array).toHaveProperty('buffer', arrayBuffer);
expect(array).toHaveProperty('byteOffset', 0);
expect(array).toHaveProperty('byteLength', 10);
});
it('`#getSize` should return the `byteLength` of the `Uint8ClampedArray`', () => {
const array = new Uint8ClampedArray(10);
expect(ArrayBufferViewFieldType.instance.getSize(array)).toBe(10);
});
});
describe('StringFieldType', () => {
it('should have a static instance', () => {
expect(StringFieldType.instance).toBeInstanceOf(StringFieldType);
});
it('`#toArrayBuffer` should return the decoded string', () => {
const text = 'foo';
const arrayBuffer = Buffer.from(text, 'utf-8');
expect(StringFieldType.instance.toArrayBuffer(text)).toEqual(arrayBuffer);
});
it('`#fromArrayBuffer` should return the encoded ArrayBuffer', () => {
const text = 'foo';
const arrayBuffer = Buffer.from(text, 'utf-8');
expect(StringFieldType.instance.fromArrayBuffer(arrayBuffer)).toBe(text);
});
it('`#getSize` should return -1', () => {
expect(StringFieldType.instance.getSize()).toBe(-1);
});
});
class MockArrayBufferFieldDefinition<TType extends ArrayBufferLikeFieldType>
extends ArrayBufferLikeFieldDefinition<TType, number> {
public getSize(): number {
return this.options;
}
}
describe('ArrayBufferLikeFieldDefinition', () => {
it('should work with `ArrayBufferFieldType`', async () => {
const size = 10;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const context = new MockDeserializationStream();
const buffer = new ArrayBuffer(size);
context.buffer = buffer;
const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
expect(context.read).toBeCalledTimes(1);
expect(context.read).toBeCalledWith(size);
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
expect(fieldValue.get()).toBe(buffer);
});
it('should work with `Uint8ClampedArrayFieldType`', async () => {
const size = 10;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferViewFieldType.instance, size);
const context = new MockDeserializationStream();
const buffer = new ArrayBuffer(size);
context.buffer = buffer;
const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
expect(context.read).toBeCalledTimes(1);
expect(context.read).toBeCalledWith(size);
expect(fieldValue).toHaveProperty('arrayBuffer', buffer);
const value = fieldValue.get();
expect(value).toBeInstanceOf(Uint8ClampedArray);
expect(value).toHaveProperty('buffer', buffer);
});
it('should work when `#getSize` returns `0`', async () => {
const size = 0;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const context = new MockDeserializationStream();
const buffer = new ArrayBuffer(size);
context.buffer = buffer;
const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
expect(context.read).toBeCalledTimes(0);
expect(fieldValue['arrayBuffer']).toBeInstanceOf(ArrayBuffer);
expect(fieldValue['arrayBuffer']).toHaveProperty('byteLength', 0);
const value = fieldValue.get();
expect(value).toBeInstanceOf(ArrayBuffer);
expect(value).toHaveProperty('byteLength', 0);
});
});
describe('ArrayBufferLikeFieldValue', () => {
describe('#set', () => {
it('should clear `arrayBuffer` field', async () => {
const size = 0;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const context = new MockDeserializationStream();
const buffer = new ArrayBuffer(size);
context.buffer = buffer;
const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
const newValue = new ArrayBuffer(20);
fieldValue.set(newValue);
expect(fieldValue.get()).toBe(newValue);
expect(fieldValue).toHaveProperty('arrayBuffer', undefined);
});
});
describe('#serialize', () => {
it('should be able to serialize with cached `arrayBuffer`', async () => {
const size = 0;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const context = new MockDeserializationStream();
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
const buffer = sourceArray.buffer;
context.buffer = buffer;
const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
const targetArray = new Uint8Array(size);
const targetView = new DataView(targetArray.buffer);
fieldValue.serialize(targetView, 0);
expect(targetArray).toEqual(sourceArray);
});
it('should be able to serialize a modified value', async () => {
const size = 0;
const definition = new MockArrayBufferFieldDefinition(ArrayBufferFieldType.instance, size);
const context = new MockDeserializationStream();
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
const buffer = sourceArray.buffer;
context.buffer = buffer;
const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
fieldValue.set(sourceArray.buffer);
const targetArray = new Uint8Array(size);
const targetView = new DataView(targetArray.buffer);
fieldValue.serialize(targetView, 0);
expect(targetArray).toEqual(sourceArray);
});
});
});
});
});

View file

@ -1,202 +0,0 @@
// cspell: ignore syncbird
import { StructAsyncDeserializeStream, StructDeserializeStream, StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from '../basic';
import { Syncbird } from "../syncbird";
import { decodeUtf8, encodeUtf8, ValueOrPromise } from "../utils";
/**
* Base class for all types that
* can be converted from an ArrayBuffer when deserialized,
* and need to be converted to an ArrayBuffer when serializing
*
* @template TValue The actual TypeScript type of this type
* @template TTypeScriptType Optional another type (should be compatible with `TType`)
* specified by user when creating field definitions.
*/
export abstract class ArrayBufferLikeFieldType<TValue = unknown, TTypeScriptType = TValue> {
public readonly TTypeScriptType!: TTypeScriptType;
/**
* When implemented in derived classes, converts the type-specific `value` to an `ArrayBuffer`
*
* This function should be "pure", i.e.,
* same `value` should always be converted to `ArrayBuffer`s that have same content.
*/
public abstract toArrayBuffer(value: TValue): ArrayBuffer;
/** When implemented in derived classes, converts the `ArrayBuffer` to a type-specific value */
public abstract fromArrayBuffer(arrayBuffer: ArrayBuffer): TValue;
/**
* When implemented in derived classes, gets the size in byte of the type-specific `value`.
*
* If the size can't be calculated without first converting the `value` back to an `ArrayBuffer`,
* implementer should returns `-1` so the caller will get its size by first converting it to
* an `ArrayBuffer` (and cache the result).
*/
public abstract getSize(value: TValue): number;
}
/** An ArrayBufferLike type that's actually an `ArrayBuffer` */
export class ArrayBufferFieldType extends ArrayBufferLikeFieldType<ArrayBuffer> {
public static readonly instance = new ArrayBufferFieldType();
protected constructor() {
super();
}
public toArrayBuffer(value: ArrayBuffer): ArrayBuffer {
return value;
}
public fromArrayBuffer(arrayBuffer: ArrayBuffer): ArrayBuffer {
return arrayBuffer;
}
public getSize(value: ArrayBuffer): number {
return value.byteLength;
}
}
export type ArrayBufferViewConstructor<T> = new (array: ArrayLike<number> | ArrayBufferLike) => T;
/** Am ArrayBufferLike type that converts between `ArrayBuffer` and `Uint8ClampedArray` */
export class ArrayBufferViewFieldType<T extends ArrayBufferView>
extends ArrayBufferLikeFieldType<T, T> {
public static readonly uint8Array = new ArrayBufferViewFieldType(Uint8Array);
public static readonly uint8ClampedArray = new ArrayBufferViewFieldType(Uint8ClampedArray);
protected type: ArrayBufferViewConstructor<T>;
public constructor(type: ArrayBufferViewConstructor<T>) {
super();
this.type = type;
}
public toArrayBuffer(value: T): ArrayBuffer {
return value.buffer;
}
public fromArrayBuffer(arrayBuffer: ArrayBuffer): T {
return new this.type(arrayBuffer);
}
public getSize(value: T): number {
return value.byteLength;
}
}
/** An ArrayBufferLike type that converts between `ArrayBuffer` and `string` */
export class StringFieldType<TTypeScriptType = string>
extends ArrayBufferLikeFieldType<string, TTypeScriptType> {
public static readonly instance = new StringFieldType();
public toArrayBuffer(value: string): ArrayBuffer {
return encodeUtf8(value);
}
public fromArrayBuffer(arrayBuffer: ArrayBuffer): string {
return decodeUtf8(arrayBuffer);
}
public getSize(): number {
// Return `-1`, so `ArrayBufferLikeFieldDefinition` will
// convert this `value` into an `ArrayBuffer` (and cache the result),
// Then get the size from that `ArrayBuffer`
return -1;
}
}
const EmptyArrayBuffer = new ArrayBuffer(0);
export abstract class ArrayBufferLikeFieldDefinition<
TType extends ArrayBufferLikeFieldType<any, any> = ArrayBufferLikeFieldType<unknown, unknown>,
TOptions = void,
TOmitInitKey extends PropertyKey = never,
> extends StructFieldDefinition<
TOptions,
TType['TTypeScriptType'],
TOmitInitKey
>{
public readonly type: TType;
public constructor(type: TType, options: TOptions) {
super(options);
this.type = type;
}
protected getDeserializeSize(struct: StructValue): number {
return this.getSize();
}
/**
* When implemented in derived classes, creates a `StructFieldValue` for the current field definition.
*/
public create(
options: Readonly<StructOptions>,
struct: StructValue,
value: TType['TTypeScriptType'],
arrayBuffer?: ArrayBuffer,
): ArrayBufferLikeFieldValue<this> {
return new ArrayBufferLikeFieldValue(this, options, struct, value, arrayBuffer);
}
public override deserialize(
options: Readonly<StructOptions>,
stream: StructDeserializeStream,
struct: StructValue,
): ArrayBufferLikeFieldValue<this>;
public override deserialize(
options: Readonly<StructOptions>,
stream: StructAsyncDeserializeStream,
struct: StructValue,
): Promise<ArrayBufferLikeFieldValue<this>>;
public override deserialize(
options: Readonly<StructOptions>,
stream: StructDeserializeStream | StructAsyncDeserializeStream,
struct: StructValue,
): ValueOrPromise<ArrayBufferLikeFieldValue<this>> {
return Syncbird.try(() => {
const size = this.getDeserializeSize(struct);
if (size === 0) {
return EmptyArrayBuffer;
} else {
return stream.read(size);
}
}).then(arrayBuffer => {
const value = this.type.fromArrayBuffer(arrayBuffer);
return this.create(options, struct, value, arrayBuffer);
}).valueOrPromise();
}
}
export class ArrayBufferLikeFieldValue<
TDefinition extends ArrayBufferLikeFieldDefinition<ArrayBufferLikeFieldType<unknown, unknown>, any, any>,
> extends StructFieldValue<TDefinition> {
protected arrayBuffer: ArrayBuffer | undefined;
public constructor(
definition: TDefinition,
options: Readonly<StructOptions>,
struct: StructValue,
value: TDefinition['TValue'],
arrayBuffer?: ArrayBuffer,
) {
super(definition, options, struct, value);
this.arrayBuffer = arrayBuffer;
}
public override set(value: TDefinition['TValue']): void {
super.set(value);
this.arrayBuffer = undefined;
}
public serialize(dataView: DataView, offset: number): void {
if (!this.arrayBuffer) {
this.arrayBuffer = this.definition.type.toArrayBuffer(this.value);
}
new Uint8Array(dataView.buffer)
.set(new Uint8Array(this.arrayBuffer), offset);
}
}

View file

@ -0,0 +1,163 @@
import { StructDefaultOptions, StructDeserializeStream, StructValue } from '../../basic';
import { BufferFieldSubType, BufferLikeFieldDefinition, StringBufferFieldSubType, Uint8ArrayBufferFieldSubType } from './base';
class MockDeserializationStream implements StructDeserializeStream {
public array = new Uint8Array(0);
public read = jest.fn((length: number) => this.array);
}
describe('Types', () => {
describe('Buffer', () => {
describe('Uint8ArrayBufferFieldSubType', () => {
it('should have a static instance', () => {
expect(Uint8ArrayBufferFieldSubType.Instance).toBeInstanceOf(Uint8ArrayBufferFieldSubType);
});
it('`#toBuffer` should return the same `Uint8Array`', () => {
const array = new Uint8Array(10);
expect(Uint8ArrayBufferFieldSubType.Instance.toBuffer(array)).toBe(array);
});
it('`#fromBuffer` should return the same `Uint8Array`', () => {
const buffer = new Uint8Array(10);
expect(Uint8ArrayBufferFieldSubType.Instance.toValue(buffer)).toBe(buffer);
});
it('`#getSize` should return the `byteLength` of the `Uint8Array`', () => {
const arrayBuffer = new Uint8Array(10);
expect(Uint8ArrayBufferFieldSubType.Instance.getSize(arrayBuffer)).toBe(10);
});
});
describe('StringBufferFieldSubType', () => {
it('should have a static instance', () => {
expect(StringBufferFieldSubType.Instance).toBeInstanceOf(StringBufferFieldSubType);
});
it('`#toBuffer` should return the decoded string', () => {
const text = 'foo';
const array = new Uint8Array(Buffer.from(text, 'utf-8'));
expect(StringBufferFieldSubType.Instance.toBuffer(text)).toEqual(array);
});
it('`#fromBuffer` should return the encoded ArrayBuffer', () => {
const text = 'foo';
const array = new Uint8Array(Buffer.from(text, 'utf-8'));
expect(StringBufferFieldSubType.Instance.toValue(array)).toBe(text);
});
it('`#getSize` should return -1', () => {
expect(StringBufferFieldSubType.Instance.getSize()).toBe(-1);
});
});
class MockArrayBufferFieldDefinition<TType extends BufferFieldSubType>
extends BufferLikeFieldDefinition<TType, number> {
public getSize(): number {
return this.options;
}
}
describe('BufferLikeFieldDefinition', () => {
it('should work with `Uint8ArrayBufferFieldSubType`', async () => {
const size = 10;
const definition = new MockArrayBufferFieldDefinition(Uint8ArrayBufferFieldSubType.Instance, size);
const context = new MockDeserializationStream();
const array = new Uint8Array(size);
context.array = array;
const struct = new StructValue();
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.get()).toBe(array);
});
it('should work when `#getSize` returns `0`', async () => {
const size = 0;
const definition = new MockArrayBufferFieldDefinition(Uint8ArrayBufferFieldSubType.Instance, size);
const context = new MockDeserializationStream();
const buffer = new Uint8Array(size);
context.array = buffer;
const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
expect(context.read).toBeCalledTimes(0);
expect(fieldValue['array']).toBeInstanceOf(Uint8Array);
expect(fieldValue['array']).toHaveProperty('byteLength', 0);
const value = fieldValue.get();
expect(value).toBeInstanceOf(Uint8Array);
expect(value).toHaveProperty('byteLength', 0);
});
});
describe('ArrayBufferLikeFieldValue', () => {
describe('#set', () => {
it('should clear `arrayBuffer` field', async () => {
const size = 0;
const definition = new MockArrayBufferFieldDefinition(Uint8ArrayBufferFieldSubType.Instance, size);
const context = new MockDeserializationStream();
const array = new Uint8Array(size);
context.array = array;
const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
const newValue = new Uint8Array(20);
fieldValue.set(newValue);
expect(fieldValue.get()).toBe(newValue);
expect(fieldValue).toHaveProperty('arrayBuffer', undefined);
});
});
describe('#serialize', () => {
it('should be able to serialize with cached `arrayBuffer`', async () => {
const size = 0;
const definition = new MockArrayBufferFieldDefinition(Uint8ArrayBufferFieldSubType.Instance, size);
const context = new MockDeserializationStream();
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
const array = sourceArray;
context.array = array;
const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
const targetArray = new Uint8Array(size);
const targetView = new DataView(targetArray.buffer);
fieldValue.serialize(targetView, 0);
expect(targetArray).toEqual(sourceArray);
});
it('should be able to serialize a modified value', async () => {
const size = 0;
const definition = new MockArrayBufferFieldDefinition(Uint8ArrayBufferFieldSubType.Instance, size);
const context = new MockDeserializationStream();
const sourceArray = new Uint8Array(Array.from({ length: size }, (_, i) => i));
const array = sourceArray;
context.array = array;
const struct = new StructValue();
const fieldValue = await definition.deserialize(StructDefaultOptions, context, struct);
fieldValue.set(sourceArray);
const targetArray = new Uint8Array(size);
const targetView = new DataView(targetArray.buffer);
fieldValue.serialize(targetView, 0);
expect(targetArray).toEqual(sourceArray);
});
});
});
});
});

View file

@ -6,8 +6,8 @@ import { decodeUtf8, encodeUtf8, ValueOrPromise } from "../../utils";
/** /**
* Base class for all types that * Base class for all types that
* can be converted from an ArrayBuffer when deserialized, * can be converted from an `Uint8Array` when deserialized,
* and need to be converted to an ArrayBuffer when serializing * and need to be converted to an `Uint8Array` when serializing
* *
* @template TValue The actual TypeScript type of this type * @template TValue The actual TypeScript type of this type
* @template TTypeScriptType Optional another type (should be compatible with `TType`) * @template TTypeScriptType Optional another type (should be compatible with `TType`)
@ -17,27 +17,27 @@ export abstract class BufferFieldSubType<TValue = unknown, TTypeScriptType = TVa
public readonly TTypeScriptType!: TTypeScriptType; public readonly TTypeScriptType!: TTypeScriptType;
/** /**
* When implemented in derived classes, converts the type-specific `value` to an `ArrayBuffer` * When implemented in derived classes, converts the type-specific `value` to an `Uint8Array`
* *
* This function should be "pure", i.e., * This function should be "pure", i.e.,
* same `value` should always be converted to `ArrayBuffer`s that have same content. * same `value` should always be converted to `Uint8Array`s that have same content.
*/ */
public abstract toBuffer(value: TValue): Uint8Array; public abstract toBuffer(value: TValue): Uint8Array;
/** When implemented in derived classes, converts the `ArrayBuffer` to a type-specific value */ /** When implemented in derived classes, converts the `Uint8Array` to a type-specific value */
public abstract fromBuffer(array: Uint8Array): TValue; public abstract toValue(array: Uint8Array): TValue;
/** /**
* When implemented in derived classes, gets the size in byte of the type-specific `value`. * When implemented in derived classes, gets the size in byte of the type-specific `value`.
* *
* If the size can't be calculated without first converting the `value` back to an `ArrayBuffer`, * If the size can't be calculated without first converting the `value` back to an `Uint8Array`,
* implementer should returns `-1` so the caller will get its size by first converting it to * implementer can returns `-1`, so the caller will get its size by first converting it to
* an `ArrayBuffer` (and cache the result). * an `Uint8Array` (and cache the result).
*/ */
public abstract getSize(value: TValue): number; public abstract getSize(value: TValue): number;
} }
/** An ArrayBufferLike type that's actually an `ArrayBuffer` */ /** An `BufferFieldSubType` that's actually an `Uint8Array` */
export class Uint8ArrayBufferFieldSubType extends BufferFieldSubType<Uint8Array> { export class Uint8ArrayBufferFieldSubType extends BufferFieldSubType<Uint8Array> {
public static readonly Instance = new Uint8ArrayBufferFieldSubType(); public static readonly Instance = new Uint8ArrayBufferFieldSubType();
@ -49,7 +49,7 @@ export class Uint8ArrayBufferFieldSubType extends BufferFieldSubType<Uint8Array>
return value; return value;
} }
public fromBuffer(buffer: Uint8Array): Uint8Array { public toValue(buffer: Uint8Array): Uint8Array {
return buffer; return buffer;
} }
@ -58,7 +58,7 @@ export class Uint8ArrayBufferFieldSubType extends BufferFieldSubType<Uint8Array>
} }
} }
/** An ArrayBufferLike type that converts between `ArrayBuffer` and `string` */ /** An `BufferFieldSubType` that converts between `Uint8Array` and `string` */
export class StringBufferFieldSubType<TTypeScriptType = string> export class StringBufferFieldSubType<TTypeScriptType = string>
extends BufferFieldSubType<string, TTypeScriptType> { extends BufferFieldSubType<string, TTypeScriptType> {
public static readonly Instance = new StringBufferFieldSubType(); public static readonly Instance = new StringBufferFieldSubType();
@ -67,21 +67,21 @@ export class StringBufferFieldSubType<TTypeScriptType = string>
return encodeUtf8(value); return encodeUtf8(value);
} }
public fromBuffer(array: Uint8Array): string { public toValue(array: Uint8Array): string {
return decodeUtf8(array); return decodeUtf8(array);
} }
public getSize(): number { public getSize(): number {
// Return `-1`, so `ArrayBufferLikeFieldDefinition` will // Return `-1`, so `BufferLikeFieldDefinition` will
// convert this `value` into an `ArrayBuffer` (and cache the result), // convert this `value` into an `Uint8Array` (and cache the result),
// Then get the size from that `ArrayBuffer` // Then get the size from that `Uint8Array`
return -1; return -1;
} }
} }
const EmptyArrayBuffer = new ArrayBuffer(0); const EmptyBuffer = new Uint8Array(0);
export abstract class ArrayBufferLikeFieldDefinition< export abstract class BufferLikeFieldDefinition<
TType extends BufferFieldSubType<any, any> = BufferFieldSubType<unknown, unknown>, TType extends BufferFieldSubType<any, any> = BufferFieldSubType<unknown, unknown>,
TOptions = void, TOptions = void,
TOmitInitKey extends PropertyKey = never, TOmitInitKey extends PropertyKey = never,
@ -108,67 +108,67 @@ export abstract class ArrayBufferLikeFieldDefinition<
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
struct: StructValue, struct: StructValue,
value: TType['TTypeScriptType'], value: TType['TTypeScriptType'],
arrayBuffer?: ArrayBuffer, array?: Uint8Array,
): ArrayBufferLikeFieldValue<this> { ): BufferLikeFieldValue<this> {
return new ArrayBufferLikeFieldValue(this, options, struct, value, arrayBuffer); return new BufferLikeFieldValue(this, options, struct, value, array);
} }
public override deserialize( public override deserialize(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
stream: StructDeserializeStream, stream: StructDeserializeStream,
struct: StructValue, struct: StructValue,
): ArrayBufferLikeFieldValue<this>; ): BufferLikeFieldValue<this>;
public override deserialize( public override deserialize(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
stream: StructAsyncDeserializeStream, stream: StructAsyncDeserializeStream,
struct: StructValue, struct: StructValue,
): Promise<ArrayBufferLikeFieldValue<this>>; ): Promise<BufferLikeFieldValue<this>>;
public override deserialize( public override deserialize(
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
stream: StructDeserializeStream | StructAsyncDeserializeStream, stream: StructDeserializeStream | StructAsyncDeserializeStream,
struct: StructValue, struct: StructValue,
): ValueOrPromise<ArrayBufferLikeFieldValue<this>> { ): ValueOrPromise<BufferLikeFieldValue<this>> {
return Syncbird.try(() => { return Syncbird.try(() => {
const size = this.getDeserializeSize(struct); const size = this.getDeserializeSize(struct);
if (size === 0) { if (size === 0) {
return EmptyArrayBuffer; return EmptyBuffer;
} else { } else {
return stream.read(size); return stream.read(size);
} }
}).then(arrayBuffer => { }).then(array => {
const value = this.type.fromBuffer(arrayBuffer); const value = this.type.toValue(array);
return this.create(options, struct, value, arrayBuffer); return this.create(options, struct, value, array);
}).valueOrPromise(); }).valueOrPromise();
} }
} }
export class ArrayBufferLikeFieldValue< export class BufferLikeFieldValue<
TDefinition extends ArrayBufferLikeFieldDefinition<BufferFieldSubType<unknown, unknown>, any, any>, TDefinition extends BufferLikeFieldDefinition<BufferFieldSubType<unknown, unknown>, any, any>,
> extends StructFieldValue<TDefinition> { > extends StructFieldValue<TDefinition> {
protected arrayBuffer: ArrayBuffer | undefined; protected array: Uint8Array | undefined;
public constructor( public constructor(
definition: TDefinition, definition: TDefinition,
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
struct: StructValue, struct: StructValue,
value: TDefinition['TValue'], value: TDefinition['TValue'],
arrayBuffer?: ArrayBuffer, array?: Uint8Array,
) { ) {
super(definition, options, struct, value); super(definition, options, struct, value);
this.arrayBuffer = arrayBuffer; this.array = array;
} }
public override set(value: TDefinition['TValue']): void { public override set(value: TDefinition['TValue']): void {
super.set(value); super.set(value);
this.arrayBuffer = undefined; this.array = undefined;
} }
public serialize(dataView: DataView, offset: number): void { public serialize(dataView: DataView, offset: number): void {
if (!this.arrayBuffer) { if (!this.array) {
this.arrayBuffer = this.definition.type.toBuffer(this.value); this.array = this.definition.type.toBuffer(this.value);
} }
new Uint8Array(dataView.buffer) new Uint8Array(dataView.buffer, dataView.byteOffset, dataView.byteLength)
.set(new Uint8Array(this.arrayBuffer), offset); .set(this.array, offset);
} }
} }

View file

@ -1,12 +1,12 @@
import { ArrayBufferFieldType } from './array-buffer'; import { Uint8ArrayBufferFieldSubType } from './base';
import { FixedLengthArrayBufferLikeFieldDefinition } from './fixed-length-array-buffer'; import { FixedLengthBufferLikeFieldDefinition } from './fixed-length';
describe('Types', () => { describe('Types', () => {
describe('FixedLengthArrayBufferLikeFieldDefinition', () => { describe('FixedLengthArrayBufferLikeFieldDefinition', () => {
describe('#getSize', () => { describe('#getSize', () => {
it('should return size in its options', () => { it('should return size in its options', () => {
const definition = new FixedLengthArrayBufferLikeFieldDefinition( const definition = new FixedLengthBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, Uint8ArrayBufferFieldSubType.Instance,
{ length: 10 }, { length: 10 },
); );
expect(definition.getSize()).toBe(10); expect(definition.getSize()).toBe(10);

View file

@ -0,0 +1,17 @@
import { BufferFieldSubType, BufferLikeFieldDefinition } from './base';
export interface FixedLengthBufferLikeFieldOptions {
length: number;
}
export class FixedLengthBufferLikeFieldDefinition<
TType extends BufferFieldSubType = BufferFieldSubType,
TOptions extends FixedLengthBufferLikeFieldOptions = FixedLengthBufferLikeFieldOptions,
> extends BufferLikeFieldDefinition<
TType,
TOptions
> {
public getSize(): number {
return this.options.length;
}
};

View file

@ -0,0 +1,3 @@
export * from './base';
export * from './fixed-length';
export * from './variable-length';

View file

@ -1,21 +1,21 @@
import { StructDefaultOptions, StructFieldValue, StructValue } from '../basic'; import { StructDefaultOptions, StructFieldValue, StructValue } from '../../basic';
import { ArrayBufferFieldType, ArrayBufferLikeFieldType } from './array-buffer'; import { BufferFieldSubType, Uint8ArrayBufferFieldSubType } from './base';
import { VariableLengthArrayBufferLikeFieldDefinition, VariableLengthArrayBufferLikeFieldLengthValue, VariableLengthArrayBufferLikeStructFieldValue } from './variable-length-array-buffer'; import { VariableLengthBufferLikeFieldDefinition, VariableLengthBufferLikeFieldLengthValue, VariableLengthBufferLikeStructFieldValue } from './variable-length';
class MockOriginalFieldValue extends StructFieldValue { class MockOriginalFieldValue extends StructFieldValue {
public constructor() { public constructor() {
super({} as any, {} as any, {} as any, {}); super({} as any, {} as any, {} as any, {});
} }
public value: string | number = 0; public override value: string | number = 0;
public get = jest.fn((): string | number => this.value); public override get = jest.fn((): string | number => this.value);
public size = 0; public size = 0;
public getSize = jest.fn((): number => this.size); public override getSize = jest.fn((): number => this.size);
public set = jest.fn((value: string | number) => { }); public override set = jest.fn((value: string | number) => { });
public serialize = jest.fn((dataView: DataView, offset: number): void => { }); public serialize = jest.fn((dataView: DataView, offset: number): void => { });
} }
@ -29,7 +29,7 @@ describe('Types', () => {
public size = 0; public size = 0;
public getSize = jest.fn(() => this.size); public override getSize = jest.fn(() => this.size);
public serialize(dataView: DataView, offset: number): void { public serialize(dataView: DataView, offset: number): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
@ -40,7 +40,7 @@ describe('Types', () => {
it('should return size of its original field value', () => { it('should return size of its original field value', () => {
const mockOriginalFieldValue = new MockOriginalFieldValue(); const mockOriginalFieldValue = new MockOriginalFieldValue();
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue(); const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue( const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
mockOriginalFieldValue, mockOriginalFieldValue,
mockArrayBufferFieldValue, mockArrayBufferFieldValue,
); );
@ -60,7 +60,7 @@ describe('Types', () => {
it('should return size of its `arrayBufferField`', async () => { it('should return size of its `arrayBufferField`', async () => {
const mockOriginalFieldValue = new MockOriginalFieldValue(); const mockOriginalFieldValue = new MockOriginalFieldValue();
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue(); const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue( const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
mockOriginalFieldValue, mockOriginalFieldValue,
mockArrayBufferFieldValue, mockArrayBufferFieldValue,
); );
@ -82,7 +82,7 @@ describe('Types', () => {
it('should return size of its `arrayBufferField` as string', async () => { it('should return size of its `arrayBufferField` as string', async () => {
const mockOriginalFieldValue = new MockOriginalFieldValue(); const mockOriginalFieldValue = new MockOriginalFieldValue();
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue(); const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue( const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
mockOriginalFieldValue, mockOriginalFieldValue,
mockArrayBufferFieldValue, mockArrayBufferFieldValue,
); );
@ -106,7 +106,7 @@ describe('Types', () => {
it('should does nothing', async () => { it('should does nothing', async () => {
const mockOriginalFieldValue = new MockOriginalFieldValue(); const mockOriginalFieldValue = new MockOriginalFieldValue();
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue(); const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue( const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
mockOriginalFieldValue, mockOriginalFieldValue,
mockArrayBufferFieldValue, mockArrayBufferFieldValue,
); );
@ -124,7 +124,7 @@ describe('Types', () => {
it('should call `serialize` of its `originalField`', async () => { it('should call `serialize` of its `originalField`', async () => {
const mockOriginalFieldValue = new MockOriginalFieldValue(); const mockOriginalFieldValue = new MockOriginalFieldValue();
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue(); const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue( const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
mockOriginalFieldValue, mockOriginalFieldValue,
mockArrayBufferFieldValue, mockArrayBufferFieldValue,
); );
@ -155,7 +155,7 @@ describe('Types', () => {
it('should stringify its length if `originalField` is a string', async () => { it('should stringify its length if `originalField` is a string', async () => {
const mockOriginalFieldValue = new MockOriginalFieldValue(); const mockOriginalFieldValue = new MockOriginalFieldValue();
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue(); const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue( const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
mockOriginalFieldValue, mockOriginalFieldValue,
mockArrayBufferFieldValue, mockArrayBufferFieldValue,
); );
@ -186,7 +186,7 @@ describe('Types', () => {
it('should stringify its length in specified base if `originalField` is a string', async () => { it('should stringify its length in specified base if `originalField` is a string', async () => {
const mockOriginalFieldValue = new MockOriginalFieldValue(); const mockOriginalFieldValue = new MockOriginalFieldValue();
const mockArrayBufferFieldValue = new MockArrayBufferFieldValue(); const mockArrayBufferFieldValue = new MockArrayBufferFieldValue();
const lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue( const lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
mockOriginalFieldValue, mockOriginalFieldValue,
mockArrayBufferFieldValue, mockArrayBufferFieldValue,
); );
@ -228,14 +228,14 @@ describe('Types', () => {
const originalLengthFieldValue = new MockOriginalFieldValue(); const originalLengthFieldValue = new MockOriginalFieldValue();
struct.set(lengthField, originalLengthFieldValue); struct.set(lengthField, originalLengthFieldValue);
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition( const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, Uint8ArrayBufferFieldSubType.Instance,
{ lengthField }, { lengthField },
); );
const value = new ArrayBuffer(0); const value = new Uint8Array(0);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
struct, struct,
@ -257,14 +257,14 @@ describe('Types', () => {
const originalLengthFieldValue = new MockOriginalFieldValue(); const originalLengthFieldValue = new MockOriginalFieldValue();
struct.set(lengthField, originalLengthFieldValue); struct.set(lengthField, originalLengthFieldValue);
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition( const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, Uint8ArrayBufferFieldSubType.Instance,
{ lengthField }, { lengthField },
); );
const value = new ArrayBuffer(100); const value = new Uint8Array(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
struct, struct,
@ -276,7 +276,7 @@ describe('Types', () => {
expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions); expect(arrayBufferFieldValue).toHaveProperty('options', StructDefaultOptions);
expect(arrayBufferFieldValue).toHaveProperty('struct', struct); expect(arrayBufferFieldValue).toHaveProperty('struct', struct);
expect(arrayBufferFieldValue).toHaveProperty('value', value); expect(arrayBufferFieldValue).toHaveProperty('value', value);
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value); expect(arrayBufferFieldValue).toHaveProperty('array', value);
expect(arrayBufferFieldValue).toHaveProperty('length', 100); expect(arrayBufferFieldValue).toHaveProperty('length', 100);
}); });
@ -287,14 +287,14 @@ describe('Types', () => {
const originalLengthFieldValue = new MockOriginalFieldValue(); const originalLengthFieldValue = new MockOriginalFieldValue();
struct.set(lengthField, originalLengthFieldValue); struct.set(lengthField, originalLengthFieldValue);
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition( const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, Uint8ArrayBufferFieldSubType.Instance,
{ lengthField }, { lengthField },
); );
const value = new ArrayBuffer(0); const value = new Uint8Array(0);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
struct, struct,
@ -307,18 +307,18 @@ describe('Types', () => {
}); });
describe('#getSize', () => { describe('#getSize', () => {
class MockArrayBufferFieldType extends ArrayBufferLikeFieldType<ArrayBuffer> { class MockArrayBufferFieldType extends BufferFieldSubType<Uint8Array> {
public toArrayBuffer = jest.fn((value: ArrayBuffer): ArrayBuffer => { public override toBuffer = jest.fn((value: Uint8Array): Uint8Array => {
return value; return value;
}); });
public fromArrayBuffer = jest.fn((arrayBuffer: ArrayBuffer): ArrayBuffer => { public override toValue = jest.fn((arrayBuffer: Uint8Array): Uint8Array => {
return arrayBuffer; return arrayBuffer;
}); });
public size = 0; public size = 0;
public getSize = jest.fn((value: ArrayBuffer): number => { public override getSize = jest.fn((value: Uint8Array): number => {
return this.size; return this.size;
}); });
} }
@ -331,14 +331,14 @@ describe('Types', () => {
struct.set(lengthField, originalLengthFieldValue); struct.set(lengthField, originalLengthFieldValue);
const arrayBufferFieldType = new MockArrayBufferFieldType(); const arrayBufferFieldType = new MockArrayBufferFieldType();
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition( const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
arrayBufferFieldType, arrayBufferFieldType,
{ lengthField }, { lengthField },
); );
const value = new ArrayBuffer(100); const value = new Uint8Array(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
struct, struct,
@ -347,8 +347,8 @@ describe('Types', () => {
); );
expect(arrayBufferFieldValue.getSize()).toBe(100); expect(arrayBufferFieldValue.getSize()).toBe(100);
expect(arrayBufferFieldType.fromArrayBuffer).toBeCalledTimes(0); expect(arrayBufferFieldType.toValue).toBeCalledTimes(0);
expect(arrayBufferFieldType.toArrayBuffer).toBeCalledTimes(0); expect(arrayBufferFieldType.toBuffer).toBeCalledTimes(0);
expect(arrayBufferFieldType.getSize).toBeCalledTimes(0); expect(arrayBufferFieldType.getSize).toBeCalledTimes(0);
}); });
@ -360,14 +360,14 @@ describe('Types', () => {
struct.set(lengthField, originalLengthFieldValue); struct.set(lengthField, originalLengthFieldValue);
const arrayBufferFieldType = new MockArrayBufferFieldType(); const arrayBufferFieldType = new MockArrayBufferFieldType();
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition( const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
arrayBufferFieldType, arrayBufferFieldType,
{ lengthField }, { lengthField },
); );
const value = new ArrayBuffer(100); const value = new Uint8Array(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
struct, struct,
@ -376,8 +376,8 @@ describe('Types', () => {
arrayBufferFieldType.size = 100; arrayBufferFieldType.size = 100;
expect(arrayBufferFieldValue.getSize()).toBe(100); expect(arrayBufferFieldValue.getSize()).toBe(100);
expect(arrayBufferFieldType.fromArrayBuffer).toBeCalledTimes(0); expect(arrayBufferFieldType.toValue).toBeCalledTimes(0);
expect(arrayBufferFieldType.toArrayBuffer).toBeCalledTimes(0); expect(arrayBufferFieldType.toBuffer).toBeCalledTimes(0);
expect(arrayBufferFieldType.getSize).toBeCalledTimes(1); expect(arrayBufferFieldType.getSize).toBeCalledTimes(1);
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined); expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', undefined);
expect(arrayBufferFieldValue).toHaveProperty('length', 100); expect(arrayBufferFieldValue).toHaveProperty('length', 100);
@ -391,14 +391,14 @@ describe('Types', () => {
struct.set(lengthField, originalLengthFieldValue); struct.set(lengthField, originalLengthFieldValue);
const arrayBufferFieldType = new MockArrayBufferFieldType(); const arrayBufferFieldType = new MockArrayBufferFieldType();
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition( const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
arrayBufferFieldType, arrayBufferFieldType,
{ lengthField }, { lengthField },
); );
const value = new ArrayBuffer(100); const value = new Uint8Array(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
struct, struct,
@ -407,8 +407,8 @@ describe('Types', () => {
arrayBufferFieldType.size = -1; arrayBufferFieldType.size = -1;
expect(arrayBufferFieldValue.getSize()).toBe(100); expect(arrayBufferFieldValue.getSize()).toBe(100);
expect(arrayBufferFieldType.fromArrayBuffer).toBeCalledTimes(0); expect(arrayBufferFieldType.toValue).toBeCalledTimes(0);
expect(arrayBufferFieldType.toArrayBuffer).toBeCalledTimes(1); expect(arrayBufferFieldType.toBuffer).toBeCalledTimes(1);
expect(arrayBufferFieldType.getSize).toBeCalledTimes(1); expect(arrayBufferFieldType.getSize).toBeCalledTimes(1);
expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value); expect(arrayBufferFieldValue).toHaveProperty('arrayBuffer', value);
expect(arrayBufferFieldValue).toHaveProperty('length', 100); expect(arrayBufferFieldValue).toHaveProperty('length', 100);
@ -423,14 +423,14 @@ describe('Types', () => {
const originalLengthFieldValue = new MockOriginalFieldValue(); const originalLengthFieldValue = new MockOriginalFieldValue();
struct.set(lengthField, originalLengthFieldValue); struct.set(lengthField, originalLengthFieldValue);
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition( const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, Uint8ArrayBufferFieldSubType.Instance,
{ lengthField }, { lengthField },
); );
const value = new ArrayBuffer(100); const value = new Uint8Array(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
struct, struct,
@ -451,14 +451,14 @@ describe('Types', () => {
const originalLengthFieldValue = new MockOriginalFieldValue(); const originalLengthFieldValue = new MockOriginalFieldValue();
struct.set(lengthField, originalLengthFieldValue); struct.set(lengthField, originalLengthFieldValue);
const arrayBufferFieldDefinition = new VariableLengthArrayBufferLikeFieldDefinition( const arrayBufferFieldDefinition = new VariableLengthBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, Uint8ArrayBufferFieldSubType.Instance,
{ lengthField }, { lengthField },
); );
const value = new ArrayBuffer(100); const value = new Uint8Array(100);
const arrayBufferFieldValue = new VariableLengthArrayBufferLikeStructFieldValue( const arrayBufferFieldValue = new VariableLengthBufferLikeStructFieldValue(
arrayBufferFieldDefinition, arrayBufferFieldDefinition,
StructDefaultOptions, StructDefaultOptions,
struct, struct,
@ -476,8 +476,8 @@ describe('Types', () => {
describe('VariableLengthArrayBufferLikeFieldDefinition', () => { describe('VariableLengthArrayBufferLikeFieldDefinition', () => {
describe('#getSize', () => { describe('#getSize', () => {
it('should always return `0`', () => { it('should always return `0`', () => {
const definition = new VariableLengthArrayBufferLikeFieldDefinition( const definition = new VariableLengthBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, Uint8ArrayBufferFieldSubType.Instance,
{ lengthField: 'foo' }, { lengthField: 'foo' },
); );
expect(definition.getSize()).toBe(0); expect(definition.getSize()).toBe(0);
@ -492,8 +492,8 @@ describe('Types', () => {
const originalLengthFieldValue = new MockOriginalFieldValue(); const originalLengthFieldValue = new MockOriginalFieldValue();
struct.set(lengthField, originalLengthFieldValue); struct.set(lengthField, originalLengthFieldValue);
const definition = new VariableLengthArrayBufferLikeFieldDefinition( const definition = new VariableLengthBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, Uint8ArrayBufferFieldSubType.Instance,
{ lengthField }, { lengthField },
); );
@ -514,8 +514,8 @@ describe('Types', () => {
const originalLengthFieldValue = new MockOriginalFieldValue(); const originalLengthFieldValue = new MockOriginalFieldValue();
struct.set(lengthField, originalLengthFieldValue); struct.set(lengthField, originalLengthFieldValue);
const definition = new VariableLengthArrayBufferLikeFieldDefinition( const definition = new VariableLengthBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, Uint8ArrayBufferFieldSubType.Instance,
{ lengthField }, { lengthField },
); );
@ -537,8 +537,8 @@ describe('Types', () => {
struct.set(lengthField, originalLengthFieldValue); struct.set(lengthField, originalLengthFieldValue);
const base = 8; const base = 8;
const definition = new VariableLengthArrayBufferLikeFieldDefinition( const definition = new VariableLengthBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, Uint8ArrayBufferFieldSubType.Instance,
{ lengthField, lengthFieldBase: base }, { lengthField, lengthFieldBase: base },
); );
@ -561,12 +561,12 @@ describe('Types', () => {
const originalLengthFieldValue = new MockOriginalFieldValue(); const originalLengthFieldValue = new MockOriginalFieldValue();
struct.set(lengthField, originalLengthFieldValue); struct.set(lengthField, originalLengthFieldValue);
const definition = new VariableLengthArrayBufferLikeFieldDefinition( const definition = new VariableLengthBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, Uint8ArrayBufferFieldSubType.Instance,
{ lengthField }, { lengthField },
); );
const value = new ArrayBuffer(100); const value = new Uint8Array(100);
const arrayBufferFieldValue = definition.create( const arrayBufferFieldValue = definition.create(
StructDefaultOptions, StructDefaultOptions,
struct, struct,
@ -588,12 +588,12 @@ describe('Types', () => {
const originalLengthFieldValue = new MockOriginalFieldValue(); const originalLengthFieldValue = new MockOriginalFieldValue();
struct.set(lengthField, originalLengthFieldValue); struct.set(lengthField, originalLengthFieldValue);
const definition = new VariableLengthArrayBufferLikeFieldDefinition( const definition = new VariableLengthBufferLikeFieldDefinition(
ArrayBufferFieldType.instance, Uint8ArrayBufferFieldSubType.Instance,
{ lengthField }, { lengthField },
); );
const value = new ArrayBuffer(100); const value = new Uint8Array(100);
const arrayBufferFieldValue = definition.create( const arrayBufferFieldValue = definition.create(
StructDefaultOptions, StructDefaultOptions,
struct, struct,

View file

@ -1,10 +1,10 @@
import { StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from '../basic'; import { StructFieldDefinition, StructFieldValue, StructOptions, StructValue } from '../../basic';
import type { KeysOfType } from '../utils'; import type { KeysOfType } from '../../utils';
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType, ArrayBufferLikeFieldValue } from './array-buffer'; import { BufferFieldSubType, BufferLikeFieldDefinition, BufferLikeFieldValue } from './base';
export type LengthField<TFields> = KeysOfType<TFields, number | string>; export type LengthField<TFields> = KeysOfType<TFields, number | string>;
export interface VariableLengthArrayBufferLikeFieldOptions< export interface VariableLengthBufferLikeFieldOptions<
TFields = object, TFields = object,
TLengthField extends LengthField<TFields> = any, TLengthField extends LengthField<TFields> = any,
> { > {
@ -13,10 +13,10 @@ export interface VariableLengthArrayBufferLikeFieldOptions<
lengthFieldBase?: number; lengthFieldBase?: number;
} }
export class VariableLengthArrayBufferLikeFieldDefinition< export class VariableLengthBufferLikeFieldDefinition<
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType, TType extends BufferFieldSubType = BufferFieldSubType,
TOptions extends VariableLengthArrayBufferLikeFieldOptions = VariableLengthArrayBufferLikeFieldOptions TOptions extends VariableLengthBufferLikeFieldOptions = VariableLengthBufferLikeFieldOptions
> extends ArrayBufferLikeFieldDefinition< > extends BufferLikeFieldDefinition<
TType, TType,
TOptions, TOptions,
TOptions['lengthField'] TOptions['lengthField']
@ -37,43 +37,43 @@ export class VariableLengthArrayBufferLikeFieldDefinition<
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
struct: StructValue, struct: StructValue,
value: TType['TTypeScriptType'], value: TType['TTypeScriptType'],
arrayBuffer?: ArrayBuffer array?: Uint8Array
): VariableLengthArrayBufferLikeStructFieldValue<this> { ): VariableLengthBufferLikeStructFieldValue<this> {
return new VariableLengthArrayBufferLikeStructFieldValue( return new VariableLengthBufferLikeStructFieldValue(
this, this,
options, options,
struct, struct,
value, value,
arrayBuffer, array,
); );
} }
} }
export class VariableLengthArrayBufferLikeStructFieldValue< export class VariableLengthBufferLikeStructFieldValue<
TDefinition extends VariableLengthArrayBufferLikeFieldDefinition = VariableLengthArrayBufferLikeFieldDefinition, TDefinition extends VariableLengthBufferLikeFieldDefinition = VariableLengthBufferLikeFieldDefinition,
> extends ArrayBufferLikeFieldValue<TDefinition> { > extends BufferLikeFieldValue<TDefinition> {
protected length: number | undefined; protected length: number | undefined;
protected lengthFieldValue: VariableLengthArrayBufferLikeFieldLengthValue; protected lengthFieldValue: VariableLengthBufferLikeFieldLengthValue;
public constructor( public constructor(
definition: TDefinition, definition: TDefinition,
options: Readonly<StructOptions>, options: Readonly<StructOptions>,
struct: StructValue, struct: StructValue,
value: TDefinition['TValue'], value: TDefinition['TValue'],
arrayBuffer?: ArrayBuffer, array?: Uint8Array,
) { ) {
super(definition, options, struct, value, arrayBuffer); super(definition, options, struct, value, array);
if (arrayBuffer) { if (array) {
this.length = arrayBuffer.byteLength; this.length = array.byteLength;
} }
// Patch the associated length field. // Patch the associated length field.
const lengthField = this.definition.options.lengthField; const lengthField = this.definition.options.lengthField;
const originalValue = struct.get(lengthField); const originalValue = struct.get(lengthField);
this.lengthFieldValue = new VariableLengthArrayBufferLikeFieldLengthValue( this.lengthFieldValue = new VariableLengthBufferLikeFieldLengthValue(
originalValue, originalValue,
this, this,
); );
@ -84,8 +84,8 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
if (this.length === undefined) { if (this.length === undefined) {
this.length = this.definition.type.getSize(this.value); this.length = this.definition.type.getSize(this.value);
if (this.length === -1) { if (this.length === -1) {
this.arrayBuffer = this.definition.type.toArrayBuffer(this.value); this.array = this.definition.type.toBuffer(this.value);
this.length = this.arrayBuffer.byteLength; this.length = this.array.byteLength;
} }
} }
@ -94,24 +94,24 @@ export class VariableLengthArrayBufferLikeStructFieldValue<
public override set(value: unknown) { public override set(value: unknown) {
super.set(value); super.set(value);
this.arrayBuffer = undefined; this.array = undefined;
this.length = undefined; this.length = undefined;
} }
} }
// Not using `VariableLengthArrayBufferLikeStructFieldValue` directly makes writing tests much easier... // Not using `VariableLengthBufferLikeStructFieldValue` directly makes writing tests much easier...
type VariableLengthArrayBufferLikeFieldValueLike = type VariableLengthBufferLikeFieldValueLike =
StructFieldValue<StructFieldDefinition<VariableLengthArrayBufferLikeFieldOptions, any, any>>; StructFieldValue<StructFieldDefinition<VariableLengthBufferLikeFieldOptions, any, any>>;
export class VariableLengthArrayBufferLikeFieldLengthValue export class VariableLengthBufferLikeFieldLengthValue
extends StructFieldValue { extends StructFieldValue {
protected originalField: StructFieldValue; protected originalField: StructFieldValue;
protected arrayBufferField: VariableLengthArrayBufferLikeFieldValueLike; protected arrayBufferField: VariableLengthBufferLikeFieldValueLike;
public constructor( public constructor(
originalField: StructFieldValue, originalField: StructFieldValue,
arrayBufferField: VariableLengthArrayBufferLikeFieldValueLike, arrayBufferField: VariableLengthBufferLikeFieldValueLike,
) { ) {
super(originalField.definition, originalField.options, originalField.struct, 0); super(originalField.definition, originalField.options, originalField.struct, 0);
this.originalField = originalField; this.originalField = originalField;

View file

@ -1,17 +0,0 @@
import { ArrayBufferLikeFieldDefinition, ArrayBufferLikeFieldType } from './array-buffer';
export interface FixedLengthArrayBufferLikeFieldOptions {
length: number;
}
export class FixedLengthArrayBufferLikeFieldDefinition<
TType extends ArrayBufferLikeFieldType = ArrayBufferLikeFieldType,
TOptions extends FixedLengthArrayBufferLikeFieldOptions = FixedLengthArrayBufferLikeFieldOptions,
> extends ArrayBufferLikeFieldDefinition<
TType,
TOptions
> {
public getSize(): number {
return this.options.length;
}
};

View file

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