• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / luma.gl / 26365832078

24 May 2026 03:53PM UTC coverage: 74.573% (+0.06%) from 74.51%
26365832078

push

github

web-flow
chore: Simplify arrow examples (#2636)

8463 of 12834 branches covered (65.94%)

Branch coverage included in aggregate %.

281 of 357 new or added lines in 3 files covered. (78.71%)

733 existing lines in 26 files now uncovered.

17709 of 22262 relevant lines covered (79.55%)

4350.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.5
/modules/arrow/src/arrow/arrow-gpu-data.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import {Buffer, Device, type BigTypedArray} from '@luma.gl/core';
6
import {DynamicBuffer, type DynamicBufferProps} from '@luma.gl/engine';
7
import {
8
  BufferType,
9
  Data,
10
  DataType,
11
  FixedSizeList,
12
  Precision,
13
  Utf8,
14
  Vector,
15
  makeData,
16
  makeVector
17
} from 'apache-arrow';
18
import {
19
  isVariableLengthAttributeArrowType,
20
  type AttributeArrowType,
21
  type NumericArrowType,
22
  type VariableLengthAttributeArrowType
23
} from './arrow-types';
24

25
type GPUDataBufferProps = Omit<DynamicBufferProps, 'byteLength' | 'data' | 'buffer' | 'ownsBuffer'>;
26

27
/** Compact CPU metadata required to reconstruct variable-width Arrow chunks after GPU readback. */
28
export type GPUDataReadbackMetadata =
29
  | {
30
      /** Metadata variant for Arrow UTF-8 value bytes. */
31
      kind: 'utf8';
32
      /** Chunk-local Arrow value offsets normalized to the copied GPU byte range. */
33
      valueOffsets: Int32Array;
34
      /** Number of nullable rows in the source Arrow chunk. */
35
      nullCount: number;
36
      /** Optional copied null bitmap required for Arrow reconstruction. */
37
      nullBitmap?: Uint8Array;
38
      /** Number of value bytes to read back from the GPU buffer. */
39
      valueByteLength: number;
40
    }
41
  | {
42
      /** Metadata variant for nested variable-length numeric attribute payloads. */
43
      kind: 'variable-length-attribute';
44
      /** Chunk-local Arrow list offsets normalized to the copied GPU value range. */
45
      valueOffsets: Int32Array;
46
      /** Number of nullable rows in the source Arrow chunk. */
47
      nullCount: number;
48
      /** Optional copied null bitmap required for Arrow reconstruction. */
49
      nullBitmap?: Uint8Array;
50
      /** Number of flattened numeric value bytes to read back from the GPU buffer. */
51
      valueByteLength: number;
52
    };
53

54
/** Constructor props that wrap one existing typed GPU data buffer. */
55
export type GPUDataFromBufferProps<T extends DataType = AttributeArrowType> = {
56
  /** Stable dynamic GPU buffer wrapper for this data range. */
57
  buffer: DynamicBuffer;
58
  /** Arrow type that describes the values in the data chunk. */
59
  arrowType: T;
60
  /** Number of logical rows in the data chunk. */
61
  length: number;
62
  /** Byte offset of the first logical row. */
63
  byteOffset?: number;
64
  /** Bytes between adjacent logical rows. Defaults to the byte width of `arrowType`. */
65
  byteStride?: number;
66
  /** Whether this data view should destroy the buffer. */
67
  ownsBuffer?: boolean;
68
  /** Optional compact metadata for reconstructing variable-width Arrow chunks after GPU readback. */
69
  readbackMetadata?: GPUDataReadbackMetadata;
70
};
71

72
type GPUVectorReadableBuffer = Pick<Buffer, 'readAsync'>;
73

74
type GPUVectorReadProps<T extends AttributeArrowType> = {
75
  type: T;
76
  buffer: GPUVectorReadableBuffer;
77
  length: number;
78
  byteOffset: number;
79
  byteStride: number;
80
};
81

82
type NumericTypedArrayConstructor = {
83
  readonly BYTES_PER_ELEMENT: number;
84
  new (buffer: ArrayBufferLike, byteOffset?: number, length?: number): BigTypedArray;
85
};
86

87
const makeNumericData = makeData as <T extends NumericArrowType>(props: {
18✔
88
  type: T;
89
  length: number;
90
  data: T['TArray'];
91
}) => Data<T>;
92

93
const makeFixedSizeListData = makeData as <T extends NumericArrowType>(props: {
18✔
94
  type: FixedSizeList<T>;
95
  length: number;
96
  nullCount: number;
97
  nullBitmap: null;
98
  child: Data<T>;
99
}) => Data<FixedSizeList<T>>;
100

101
/**
102
 * GPU memory and Arrow type metadata for one Arrow Data chunk.
103
 *
104
 * GPUData can own a dedicated buffer when constructed from Arrow Data, or
105
 * describe a byte-range view into a shared static or dynamic GPU buffer.
106
 */
107
export class GPUData<T extends DataType = AttributeArrowType> {
108
  /** GPU buffer containing the Arrow data chunk's attribute-compatible value memory. */
109
  readonly buffer: DynamicBuffer;
110
  /** Arrow type that describes the uploaded data chunk. */
111
  readonly type: T;
112
  /** Number of logical Arrow rows in this chunk. */
113
  readonly length: number;
114
  /** Number of scalar values per logical row. */
115
  readonly stride: number;
116
  /** Byte offset of the first logical row in {@link buffer}. */
117
  readonly byteOffset: number;
118
  /** Bytes between adjacent logical rows in {@link buffer}. */
119
  readonly byteStride: number;
120
  /** Optional compact metadata for reconstructing variable-width Arrow chunks after GPU readback. */
121
  readonly readbackMetadata?: GPUDataReadbackMetadata;
122
  /** Whether this data view is responsible for destroying {@link buffer}. */
123
  private _ownsBuffer: boolean;
124

125
  /** Creates a GPU representation from one Arrow Data chunk. */
126
  constructor(device: Device, data: Data<T>, props?: GPUDataBufferProps);
127
  /** Creates a data view over an existing GPU buffer. */
128
  constructor(props: GPUDataFromBufferProps<T>);
129
  constructor(
130
    deviceOrProps: Device | GPUDataFromBufferProps<any>,
131
    data?: Data<T>,
132
    props: GPUDataBufferProps = {}
36✔
133
  ) {
134
    if (deviceOrProps instanceof Device) {
36!
UNCOV
135
      const arrowData = data!;
×
UNCOV
136
      this.type = arrowData.type as T;
×
UNCOV
137
      this.length = arrowData.length;
×
138
      this.readbackMetadata = getArrowGPUDataReadbackMetadata(arrowData);
×
139
      if (DataType.isUtf8(arrowData.type)) {
×
UNCOV
140
        this.stride = 1;
×
141
        this.byteOffset = 0;
×
142
        this.byteStride = 1;
×
143
        this.buffer = new DynamicBuffer(deviceOrProps, {
×
144
          usage: Buffer.VERTEX | Buffer.STORAGE | Buffer.COPY_DST | Buffer.COPY_SRC,
145
          ...props,
146
          data: getArrowUtf8DataBufferSource(arrowData as Data<Utf8>)
147
        });
UNCOV
148
        this._ownsBuffer = true;
×
UNCOV
149
        return;
×
150
      }
UNCOV
151
      if (isVariableLengthAttributeArrowType(arrowData.type)) {
×
152
        this.stride = getArrowVariableLengthAttributeElementStride(arrowData.type);
×
153
        this.byteOffset = 0;
×
UNCOV
154
        this.byteStride = getArrowVariableLengthAttributeElementByteStride(arrowData.type);
×
155
        this.buffer = new DynamicBuffer(deviceOrProps, {
×
156
          usage: Buffer.VERTEX | Buffer.STORAGE | Buffer.COPY_DST | Buffer.COPY_SRC,
157
          ...props,
158
          data: getArrowVariableLengthAttributeDataBufferSource(
159
            arrowData as unknown as Data<VariableLengthAttributeArrowType>
160
          )
161
        });
UNCOV
162
        this._ownsBuffer = true;
×
163
        return;
×
164
      }
UNCOV
165
      this.stride = getArrowTypeStride(arrowData.type);
×
UNCOV
166
      this.byteOffset = 0;
×
UNCOV
167
      this.byteStride = getArrowTypeByteStride(arrowData.type);
×
UNCOV
168
      this.buffer = new DynamicBuffer(deviceOrProps, {
×
169
        usage: Buffer.VERTEX | Buffer.STORAGE | Buffer.COPY_DST | Buffer.COPY_SRC,
170
        ...props,
171
        data: getArrowDataBufferSource(arrowData as any)
172
      });
UNCOV
173
      this._ownsBuffer = true;
×
UNCOV
174
      return;
×
175
    }
176

177
    const {
178
      buffer,
179
      arrowType,
180
      length,
181
      byteOffset = 0,
36✔
182
      byteStride = getArrowTypeByteStride(arrowType),
36✔
183
      ownsBuffer = false,
36✔
184
      readbackMetadata
185
    } = deviceOrProps;
36✔
186
    this.buffer = buffer;
36✔
187
    this.type = arrowType as T;
36✔
188
    this.length = length;
36✔
189
    this.stride = getArrowTypeStride(arrowType);
36✔
190
    this.byteOffset = byteOffset;
36✔
191
    this.byteStride = byteStride;
36✔
192
    this.readbackMetadata = readbackMetadata;
36✔
193
    this._ownsBuffer = ownsBuffer;
36✔
194
  }
195

196
  /** Whether this GPU data range is responsible for destroying its backing `DynamicBuffer`. */
197
  get ownsBuffer(): boolean {
UNCOV
198
    return this._ownsBuffer;
×
199
  }
200

201
  /** Reads this GPU chunk back into a single non-null Arrow Data chunk. */
202
  async readAsync(): Promise<Data<T>> {
203
    if (DataType.isUtf8(this.type)) {
36✔
204
      const metadata = this.readbackMetadata;
4✔
205
      if (metadata?.kind !== 'utf8') {
4!
UNCOV
206
        throw new Error('GPUData.readAsync() requires UTF-8 readback metadata');
×
207
      }
208
      const bytes =
209
        metadata.valueByteLength === 0
4!
210
          ? new Uint8Array(0)
211
          : await this.buffer.readAsync(this.byteOffset, metadata.valueByteLength);
212
      return makeData({
4✔
213
        type: new Utf8(),
214
        length: this.length,
215
        nullCount: metadata.nullCount,
216
        nullBitmap: metadata.nullBitmap,
217
        valueOffsets: metadata.valueOffsets,
218
        data: bytes
219
      }) as Data<T>;
220
    }
221
    if (isVariableLengthAttributeArrowType(this.type)) {
32✔
222
      const metadata = this.readbackMetadata;
29✔
223
      if (metadata?.kind !== 'variable-length-attribute') {
29!
UNCOV
224
        throw new Error('GPUData.readAsync() requires variable-length attribute readback metadata');
×
225
      }
226
      const bytes =
227
        metadata.valueByteLength === 0
29!
228
          ? new Uint8Array(0)
229
          : await this.buffer.readAsync(this.byteOffset, metadata.valueByteLength);
230
      return makeArrowVariableLengthAttributeDataFromPackedBytes(
29✔
231
        this.type,
232
        this.length,
233
        metadata,
234
        bytes
235
      ) as unknown as Data<T>;
236
    }
237
    const vector = await readArrowGPUVectorAsync({
3✔
238
      type: this.type as unknown as AttributeArrowType,
239
      buffer: this.buffer,
240
      length: this.length,
241
      byteOffset: this.byteOffset,
242
      byteStride: this.byteStride
243
    });
244
    return vector.data[0] as unknown as Data<T>;
3✔
245
  }
246

247
  /** Releases the backing buffer when this range owns it. */
248
  destroy(): void {
UNCOV
249
    if (this._ownsBuffer) {
×
UNCOV
250
      this.buffer.destroy();
×
UNCOV
251
      this._ownsBuffer = false;
×
252
    }
253
  }
254
}
255

256
/** Returns the uploadable typed-array view for one Arrow Data chunk. */
257
export function getArrowDataBufferSource<T extends NumericArrowType>(data: Data<T>): T['TArray'];
258
export function getArrowDataBufferSource<T extends NumericArrowType>(
259
  data: Data<FixedSizeList<T>>
260
): T['TArray'];
261
export function getArrowDataBufferSource<T extends AttributeArrowType>(
262
  data: Data<T>
263
): NumericArrowType['TArray'];
264
export function getArrowDataBufferSource(data: Data): NumericArrowType['TArray'] {
265
  const {values, startElement, elementCount} = getArrowDataValueRange(data);
462✔
266
  if (values.length < elementCount) {
462!
267
    throw new Error('Arrow data values are shorter than the logical upload length');
×
268
  }
269
  if (values.length === elementCount) {
462✔
270
    return values;
430✔
271
  }
272

273
  const endElement = startElement + elementCount;
32✔
274
  if (endElement > values.length) {
32!
UNCOV
275
    throw new Error('Arrow data values are shorter than the logical upload length');
×
276
  }
277
  return values.subarray(startElement, endElement) as NumericArrowType['TArray'];
32✔
278
}
279

280
/** Return the UTF-8 value bytes referenced by one Arrow Utf8 data chunk. */
281
export function getArrowUtf8DataBufferSource(data: Data<Utf8>): Uint8Array {
282
  const valueOffsets = data.valueOffsets as Int32Array | undefined;
62✔
283
  const values = data.values as Uint8Array | undefined;
62✔
284
  if (!valueOffsets || !values) {
62!
UNCOV
285
    return new Uint8Array(0);
×
286
  }
287
  const firstValueOffset = valueOffsets[0] ?? 0;
62!
288
  const lastValueOffset = valueOffsets[data.length] ?? firstValueOffset;
62!
289
  return values.subarray(firstValueOffset, lastValueOffset);
62✔
290
}
291

292
/** Return flattened numeric values referenced by one variable-length nested attribute chunk. */
293
export function getArrowVariableLengthAttributeDataBufferSource(
294
  data: Data<VariableLengthAttributeArrowType>
295
): NumericArrowType['TArray'] {
296
  const valueOffsets = data.valueOffsets as Int32Array | undefined;
90✔
297
  const elementData = data.children[0] as Data | undefined;
90✔
298
  if (!valueOffsets || !elementData) {
90!
UNCOV
299
    return new Float32Array(0);
×
300
  }
301

302
  const firstElementOffset = valueOffsets[0] ?? 0;
90!
303
  const lastElementOffset = valueOffsets[data.length] ?? firstElementOffset;
90!
304

305
  if (DataType.isFixedSizeList(elementData.type)) {
90✔
306
    const numericData = elementData.children[0] as Data<NumericArrowType> | undefined;
84✔
307
    const values = numericData?.values as NumericArrowType['TArray'] | undefined;
84✔
308
    if (!numericData || !values) {
84!
UNCOV
309
      return new Float32Array(0);
×
310
    }
311

312
    const elementStride = elementData.type.listSize;
84✔
313
    const numericValueOffset = numericData.offset ?? 0;
84!
314
    const firstValueOffset = numericValueOffset + firstElementOffset * elementStride;
84✔
315
    const lastValueOffset = numericValueOffset + lastElementOffset * elementStride;
84✔
316
    return values.subarray(firstValueOffset, lastValueOffset) as NumericArrowType['TArray'];
84✔
317
  }
318

319
  const values = elementData.values as NumericArrowType['TArray'] | undefined;
6✔
320
  if (!values) {
6!
UNCOV
321
    return new Float32Array(0);
×
322
  }
323
  const numericValueOffset = elementData.offset ?? 0;
6!
324
  return values.subarray(
90✔
325
    numericValueOffset + firstElementOffset,
326
    numericValueOffset + lastElementOffset
327
  ) as NumericArrowType['TArray'];
328
}
329

330
/** Copy compact variable-width reconstruction metadata without retaining Arrow value buffers. */
331
export function getArrowGPUDataReadbackMetadata(data: Data): GPUDataReadbackMetadata | undefined {
332
  if (DataType.isUtf8(data.type)) {
100✔
333
    const values = getArrowUtf8DataBufferSource(data as Data<Utf8>);
28✔
334
    return {
28✔
335
      kind: 'utf8',
336
      valueOffsets: copyNormalizedArrowValueOffsets(data),
337
      nullCount: data.nullCount,
338
      nullBitmap: copyNormalizedArrowNullBitmap(data),
339
      valueByteLength: values.byteLength
340
    };
341
  }
342

343
  if (isVariableLengthAttributeArrowType(data.type)) {
72✔
344
    const values = getArrowVariableLengthAttributeDataBufferSource(
36✔
345
      data as Data<VariableLengthAttributeArrowType>
346
    );
347
    return {
36✔
348
      kind: 'variable-length-attribute',
349
      valueOffsets: copyNormalizedArrowValueOffsets(data),
350
      nullCount: data.nullCount,
351
      nullBitmap: copyNormalizedArrowNullBitmap(data),
352
      valueByteLength: values.byteLength
353
    };
354
  }
355

356
  return undefined;
36✔
357
}
358

359
/** Returns a typed array that can be passed directly to `device.createBuffer()`. */
360
export function getArrowVectorBufferSource<T extends NumericArrowType>(
361
  vector: Vector<T>
362
): T['TArray'];
363
export function getArrowVectorBufferSource<T extends NumericArrowType>(
364
  vector: Vector<FixedSizeList<T>>
365
): T['TArray'];
366
export function getArrowVectorBufferSource<T extends AttributeArrowType>(
367
  vector: Vector<T>
368
): NumericArrowType['TArray'];
369
export function getArrowVectorBufferSource(vector: Vector): NumericArrowType['TArray'] {
370
  const dataSources = vector.data.map(data => getArrowDataBufferSource(data));
427✔
371
  if (dataSources.length === 0) {
424!
UNCOV
372
    throw new Error('Arrow vector has no data');
×
373
  }
374
  if (dataSources.length === 1) {
424✔
375
    return dataSources[0];
422✔
376
  }
377

378
  const totalLength = dataSources.reduce((length, dataSource) => length + dataSource.length, 0);
5✔
379
  const values = createTypedArrayLike(dataSources[0], totalLength);
2✔
380
  let targetOffset = 0;
2✔
381
  for (const dataSource of dataSources) {
2✔
382
    values.set(dataSource as never, targetOffset);
5✔
383
    targetOffset += dataSource.length;
5✔
384
  }
385
  return values;
2✔
386
}
387

388
/** Number of scalar values in one logical Arrow row. */
389
export function getArrowTypeStride(type: DataType): number {
390
  if (isVariableLengthAttributeArrowType(type)) {
724✔
391
    return getArrowVariableLengthAttributeElementStride(type);
96✔
392
  }
393
  return DataType.isFixedSizeList(type) ? type.listSize : 1;
628✔
394
}
395

396
/** Number of uploaded bytes in one logical Arrow row. */
397
export function getArrowTypeByteStride(type: DataType): number {
398
  if (isVariableLengthAttributeArrowType(type)) {
731✔
399
    return getArrowVariableLengthAttributeElementByteStride(type);
67✔
400
  }
401
  if (DataType.isFixedSizeList(type)) {
664✔
402
    return type.listSize * getArrowTypeByteStride(type.children[0].type);
248✔
403
  }
404
  if (DataType.isInt(type)) {
416✔
405
    return type.bitWidth / 8;
144✔
406
  }
407
  if (DataType.isFloat(type)) {
272!
408
    switch (type.precision) {
272!
409
      case Precision.HALF:
UNCOV
410
        return 2;
×
411
      case Precision.SINGLE:
412
        return 4;
272✔
413
      case Precision.DOUBLE:
UNCOV
414
        return 8;
×
415
    }
416
  }
UNCOV
417
  throw new Error(`Cannot determine byte stride for Arrow type ${type}`);
×
418
}
419

420
/** Reject nullable Arrow chunks that cannot be uploaded directly into GPU attribute buffers. */
421
export function validateArrowGPUDataDirectUpload(
422
  name: string,
423
  data: Data<AttributeArrowType | Utf8 | VariableLengthAttributeArrowType>
424
): void {
425
  if (data.nullCount > 0) {
60✔
426
    throw new Error(`GPUVector "${name}" does not support nullable data`);
1✔
427
  }
428
  if (DataType.isFixedSizeList(data.type) && (data.children[0]?.nullCount ?? 0) > 0) {
59!
UNCOV
429
    throw new Error(`GPUVector "${name}" does not support nullable child data`);
×
430
  }
431
  if (isVariableLengthAttributeArrowType(data.type)) {
59✔
432
    const elementData = data.children[0];
35✔
433
    const nestedNumericData = DataType.isFixedSizeList(elementData?.type)
35✔
434
      ? elementData.children[0]
435
      : undefined;
436
    if ((elementData?.nullCount ?? 0) > 0 || (nestedNumericData?.nullCount ?? 0) > 0) {
35!
UNCOV
437
      throw new Error(`GPUVector "${name}" does not support nullable nested list data`);
×
438
    }
439
  }
440
}
441

442
/** Read GPU bytes and reconstruct one non-null Arrow vector with the supplied Arrow type. */
443
export async function readArrowGPUVectorAsync<T extends AttributeArrowType>(
444
  props: GPUVectorReadProps<T>
445
): Promise<Vector<T>> {
446
  const {buffer, type, length, byteOffset, byteStride} = props;
24✔
447
  const rowByteWidth = getArrowTypeByteStride(type);
24✔
448
  if (byteStride < rowByteWidth) {
24!
UNCOV
449
    throw new Error(
×
450
      `GPUVector.readAsync() byteStride ${byteStride} is smaller than row byte width ${rowByteWidth}`
451
    );
452
  }
453

454
  const readByteLength = length === 0 ? 0 : (length - 1) * byteStride + rowByteWidth;
24!
455
  const bytes =
456
    readByteLength === 0 ? new Uint8Array(0) : await buffer.readAsync(byteOffset, readByteLength);
24!
457
  const packedBytes =
458
    byteStride === rowByteWidth
24✔
459
      ? bytes
460
      : compactStridedRows(bytes, length, byteStride, rowByteWidth);
461

462
  return makeArrowVectorFromPackedBytes(type, length, packedBytes);
24✔
463
}
464

465
function getArrowDataValueRange(data: Data): {
466
  values: NumericArrowType['TArray'];
467
  startElement: number;
468
  elementCount: number;
469
} {
470
  if (DataType.isFixedSizeList(data.type)) {
462✔
471
    const childData = data.children[0];
314✔
472
    const values = childData?.values;
314✔
473
    if (!values) {
314!
UNCOV
474
      throw new Error('Arrow FixedSizeList data has no child values');
×
475
    }
476
    const elementCount = data.length * data.type.listSize;
314✔
477
    const startElement = (childData.offset ?? 0) + data.offset * data.type.listSize;
314!
478
    return {values: values as NumericArrowType['TArray'], startElement, elementCount};
314✔
479
  }
480

481
  const values = data.values;
148✔
482
  if (!values) {
148!
UNCOV
483
    throw new Error('Arrow data has no values');
×
484
  }
485
  return {
148✔
486
    values: values as NumericArrowType['TArray'],
487
    startElement: data.offset,
488
    elementCount: data.length
489
  };
490
}
491

492
function createTypedArrayLike(
493
  source: NumericArrowType['TArray'],
494
  length: number
495
): NumericArrowType['TArray'] {
496
  const TypedArrayConstructor = source.constructor as {
2✔
497
    new (length: number): NumericArrowType['TArray'];
498
  };
499
  return new TypedArrayConstructor(length);
2✔
500
}
501

502
function compactStridedRows(
503
  bytes: Uint8Array,
504
  length: number,
505
  byteStride: number,
506
  rowByteWidth: number
507
): Uint8Array {
508
  const packedBytes = new Uint8Array(length * rowByteWidth);
1✔
509
  for (let rowIndex = 0; rowIndex < length; rowIndex++) {
1✔
510
    const sourceOffset = rowIndex * byteStride;
2✔
511
    const targetOffset = rowIndex * rowByteWidth;
2✔
512
    packedBytes.set(bytes.subarray(sourceOffset, sourceOffset + rowByteWidth), targetOffset);
2✔
513
  }
514
  return packedBytes;
1✔
515
}
516

517
function makeArrowVectorFromPackedBytes<T extends AttributeArrowType>(
518
  type: T,
519
  length: number,
520
  bytes: Uint8Array
521
): Vector<T> {
522
  if (DataType.isFixedSizeList(type)) {
24✔
523
    const childType = type.children[0].type as NumericArrowType;
4✔
524
    const values = makeNumericTypedArray(childType, bytes, length * type.listSize);
4✔
525
    const childData = makeNumericData({
4✔
526
      type: childType,
527
      length: length * type.listSize,
528
      data: values as NumericArrowType['TArray']
529
    });
530
    const listData = makeFixedSizeListData({
4✔
531
      type: type as FixedSizeList<NumericArrowType>,
532
      length,
533
      nullCount: 0,
534
      nullBitmap: null,
535
      child: childData
536
    });
537
    return makeVector(listData) as Vector<T>;
4✔
538
  }
539

540
  const numericType = type as NumericArrowType;
20✔
541
  const values = makeNumericTypedArray(numericType, bytes, length);
20✔
542
  const data = makeNumericData({
20✔
543
    type: numericType,
544
    length,
545
    data: values as NumericArrowType['TArray']
546
  });
547
  return makeVector(data) as Vector<T>;
20✔
548
}
549

550
function makeArrowVariableLengthAttributeDataFromPackedBytes(
551
  type: VariableLengthAttributeArrowType,
552
  length: number,
553
  metadata: Extract<GPUDataReadbackMetadata, {kind: 'variable-length-attribute'}>,
554
  bytes: Uint8Array
555
): Data<VariableLengthAttributeArrowType> {
556
  const elementType = type.children[0].type;
29✔
557
  const elementData = DataType.isFixedSizeList(elementType)
29✔
558
    ? makeArrowFixedSizeListElementDataFromPackedBytes(elementType, bytes)
559
    : makeArrowNumericElementDataFromPackedBytes(elementType as NumericArrowType, bytes);
560

561
  return new Data<VariableLengthAttributeArrowType>(
29✔
562
    type,
563
    0,
564
    length,
565
    metadata.nullCount,
566
    {
567
      [BufferType.OFFSET]: metadata.valueOffsets,
568
      ...(metadata.nullBitmap ? {[BufferType.VALIDITY]: metadata.nullBitmap} : {})
29!
569
    },
570
    [elementData]
571
  );
572
}
573

574
function copyNormalizedArrowValueOffsets(data: Data): Int32Array {
575
  const valueOffsets = data.valueOffsets as Int32Array | undefined;
64✔
576
  const copiedOffsets = new Int32Array(data.length + 1);
64✔
577
  if (!valueOffsets) {
64!
UNCOV
578
    return copiedOffsets;
×
579
  }
580

581
  const firstValueOffset = valueOffsets[0] ?? 0;
64!
582
  for (let offsetIndex = 0; offsetIndex <= data.length; offsetIndex++) {
64✔
583
    copiedOffsets[offsetIndex] = Math.max(
173✔
584
      0,
585
      (valueOffsets[offsetIndex] ?? firstValueOffset) - firstValueOffset
173!
586
    );
587
  }
588
  return copiedOffsets;
64✔
589
}
590

591
function copyNormalizedArrowNullBitmap(data: Data): Uint8Array | undefined {
592
  if (data.nullCount === 0 || !data.nullBitmap) {
64✔
593
    return undefined;
61✔
594
  }
595

596
  const copiedBitmap = new Uint8Array(Math.ceil(data.length / 8));
3✔
597
  const sourceBitmap = data.nullBitmap as Uint8Array;
3✔
598
  const sourceRowOffset = data.offset ?? 0;
3!
599
  for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
64✔
600
    const sourceBitIndex = sourceRowOffset + rowIndex;
5✔
601
    const sourceByte = sourceBitmap[sourceBitIndex >> 3] ?? 0;
5!
602
    if ((sourceByte & (1 << (sourceBitIndex & 7))) !== 0) {
5✔
603
      copiedBitmap[rowIndex >> 3] |= 1 << (rowIndex & 7);
2✔
604
    }
605
  }
606
  return copiedBitmap;
3✔
607
}
608

609
function makeArrowFixedSizeListElementDataFromPackedBytes(
610
  type: FixedSizeList<NumericArrowType>,
611
  bytes: Uint8Array
612
): Data<FixedSizeList<NumericArrowType>> {
613
  const numericType = type.children[0].type as NumericArrowType;
26✔
614
  const values = makeNumericTypedArray(
26✔
615
    numericType,
616
    bytes,
617
    bytes.byteLength / getArrowTypeByteStride(numericType)
618
  ) as NumericArrowType['TArray'];
619
  const numericData = makeNumericData({
26✔
620
    type: numericType,
621
    length: values.length,
622
    data: values
623
  });
624
  return makeFixedSizeListData({
26✔
625
    type,
626
    length: type.listSize === 0 ? 0 : values.length / type.listSize,
26!
627
    nullCount: 0,
628
    nullBitmap: null,
629
    child: numericData
630
  });
631
}
632

633
function makeArrowNumericElementDataFromPackedBytes(
634
  type: NumericArrowType,
635
  bytes: Uint8Array
636
): Data<NumericArrowType> {
637
  const values = makeNumericTypedArray(
3✔
638
    type,
639
    bytes,
640
    bytes.byteLength / getArrowTypeByteStride(type)
641
  ) as NumericArrowType['TArray'];
642
  return makeNumericData({
3✔
643
    type,
644
    length: values.length,
645
    data: values
646
  });
647
}
648

649
function getArrowVariableLengthAttributeElementStride(
650
  type: VariableLengthAttributeArrowType
651
): number {
652
  return getArrowTypeStride(type.children[0].type);
96✔
653
}
654

655
function getArrowVariableLengthAttributeElementByteStride(
656
  type: VariableLengthAttributeArrowType
657
): number {
658
  return getArrowTypeByteStride(type.children[0].type);
67✔
659
}
660

661
function makeNumericTypedArray(
662
  type: NumericArrowType,
663
  bytes: Uint8Array,
664
  length: number
665
): BigTypedArray {
666
  if (DataType.isInt(type)) {
53✔
667
    if (type.isSigned) {
8✔
668
      switch (type.bitWidth) {
2!
669
        case 8:
670
          return makeTypedArrayView(Int8Array, bytes, length);
×
671
        case 16:
672
          return makeTypedArrayView(Int16Array, bytes, length);
1✔
673
        case 32:
674
          return makeTypedArrayView(Int32Array, bytes, length);
1✔
675
        case 64:
676
          return makeTypedArrayView(BigInt64Array, bytes, length);
×
677
      }
678
    }
679

680
    switch (type.bitWidth) {
6!
681
      case 8:
682
        return makeTypedArrayView(Uint8Array, bytes, length);
×
683
      case 16:
UNCOV
684
        return makeTypedArrayView(Uint16Array, bytes, length);
×
685
      case 32:
UNCOV
686
        return makeTypedArrayView(Uint32Array, bytes, length);
×
687
      case 64:
688
        return makeTypedArrayView(BigUint64Array, bytes, length);
6✔
689
    }
690
  }
691

692
  if (DataType.isFloat(type)) {
45!
693
    switch (type.precision) {
45!
694
      case Precision.HALF:
UNCOV
695
        return makeTypedArrayView(Uint16Array, bytes, length);
×
696
      case Precision.SINGLE:
697
        return makeTypedArrayView(Float32Array, bytes, length);
45✔
698
      case Precision.DOUBLE:
UNCOV
699
        return makeTypedArrayView(Float64Array, bytes, length);
×
700
    }
701
  }
702

UNCOV
703
  throw new Error(`GPUVector.readAsync() does not support Arrow type ${type}`);
×
704
}
705

706
function makeTypedArrayView<T extends BigTypedArray>(
707
  TypedArrayConstructor: NumericTypedArrayConstructor,
708
  bytes: Uint8Array,
709
  length: number
710
): T {
711
  const byteLength = length * TypedArrayConstructor.BYTES_PER_ELEMENT;
53✔
712
  if (bytes.byteOffset % TypedArrayConstructor.BYTES_PER_ELEMENT === 0) {
53!
713
    return new TypedArrayConstructor(bytes.buffer, bytes.byteOffset, length) as T;
53✔
714
  }
715

UNCOV
716
  const alignedBytes = new Uint8Array(byteLength);
×
UNCOV
717
  alignedBytes.set(bytes.subarray(0, byteLength));
×
UNCOV
718
  return new TypedArrayConstructor(alignedBytes.buffer, 0, length) as T;
×
719
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc