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

visgl / luma.gl / 26033117099

18 May 2026 12:20PM UTC coverage: 74.769% (-0.1%) from 74.887%
26033117099

push

github

web-flow
feat(arrow) Streaming ArrowTextLayer (#2620)

6882 of 10396 branches covered (66.2%)

Branch coverage included in aggregate %.

194 of 250 new or added lines in 9 files covered. (77.6%)

13 existing lines in 7 files now uncovered.

15038 of 18921 relevant lines covered (79.48%)

914.25 hits per line

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

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

5
import {
6
  Buffer,
7
  Device,
8
  type BufferAttributeLayout,
9
  type BufferLayout,
10
  type BufferProps
11
} from '@luma.gl/core';
12
import {DynamicBuffer, type DynamicBufferProps} from '@luma.gl/engine';
13
import * as arrow from 'apache-arrow';
14
import {isInstanceArrowType, type AttributeArrowType} from './arrow-types';
15
import {
16
  GPUData,
17
  getArrowDataBufferSource,
18
  getArrowUtf8DataBufferSource,
19
  getArrowTypeByteStride,
20
  getArrowTypeStride,
21
  getArrowVectorBufferSource,
22
  readArrowGPUVectorAsync,
23
  validateArrowGPUDataDirectUpload
24
} from './arrow-gpu-data';
25

26
export {
27
  GPUData,
28
  readArrowGPUVectorAsync,
29
  type GPUDataFromBufferProps
30
} from './arrow-gpu-data';
31

32
/** Buffer creation props forwarded when uploading Arrow vector memory to the GPU. */
33
export type GPUVectorBufferProps = Omit<BufferProps, 'byteLength' | 'data'>;
34
/** Dynamic buffer props forwarded when creating appendable Arrow GPU vectors. */
35
export type GPUVectorDynamicBufferProps = Omit<DynamicBufferProps, 'byteLength' | 'data'>;
36

37
type AppendableArrowType = AttributeArrowType | arrow.Utf8;
38

39
/** @deprecated Use {@link GPUVectorBufferProps}. */
40
export type GPUVectorProps = GPUVectorBufferProps;
41

42
/** Constructor props that upload an Arrow vector into a new GPU buffer. */
43
export type GPUVectorFromArrowProps<T extends arrow.DataType = AttributeArrowType> = {
44
  /** Optional discriminator for Arrow-vector upload construction. Inferred from `vector` when omitted. */
45
  type?: 'arrow';
46
  /** Name used when this vector is added to an {@link GPUTable}. */
47
  name: string;
48
  /** Device that creates the GPU buffer. */
49
  device: Device;
50
  /** Arrow vector whose value memory is uploaded. */
51
  vector: arrow.Vector<T>;
52
  /** Buffer creation props forwarded to the GPU buffer. */
53
  bufferProps?: GPUVectorBufferProps;
54
};
55

56
/** Constructor props that wrap an existing typed GPU buffer. */
57
export type GPUVectorFromBufferProps<T extends AttributeArrowType = AttributeArrowType> = {
58
  /** Discriminator for existing-buffer construction. */
59
  type: 'buffer';
60
  /** Name used when this vector is added to an {@link GPUTable}. */
61
  name: string;
62
  /** Existing GPU buffer. */
63
  buffer: Buffer;
64
  /** Arrow type that describes the values in the buffer. */
65
  arrowType: T;
66
  /** Number of logical rows in the buffer. */
67
  length: number;
68
  /** Byte offset of the first logical row. */
69
  byteOffset?: number;
70
  /** Bytes between adjacent logical rows. Defaults to the byte width of `arrowType`. */
71
  byteStride?: number;
72
  /**
73
   * Whether this vector should destroy the buffer.
74
   *
75
   * Defaults to `false` for wrapped buffers because ownership remains with the caller unless
76
   * explicitly transferred or opted in.
77
   */
78
  ownsBuffer?: boolean;
79
};
80

81
/** Constructor props that wrap one interleaved GPU buffer as opaque Arrow binary rows. */
82
export type GPUVectorFromInterleavedProps = {
83
  /** Discriminator for interleaved-buffer construction. */
84
  type: 'interleaved';
85
  /** Name used when this vector is added to an {@link GPUTable}. */
86
  name: string;
87
  /** Existing interleaved GPU buffer. */
88
  buffer: Buffer;
89
  /** Number of logical rows in the buffer. */
90
  length: number;
91
  /** Byte offset of the first logical row. */
92
  byteOffset?: number;
93
  /** Bytes between adjacent logical rows. */
94
  byteStride: number;
95
  /** Attribute views stored in each interleaved row. */
96
  attributes: BufferAttributeLayout[];
97
  /**
98
   * Whether this vector should destroy the buffer.
99
   *
100
   * Defaults to `false` for wrapped buffers because ownership remains with the caller unless
101
   * explicitly transferred or opted in.
102
   */
103
  ownsBuffer?: boolean;
104
};
105

106
/** Constructor props that expose pre-existing Arrow GPU data chunks as one logical vector. */
107
export type GPUVectorFromDataProps<T extends arrow.DataType = AttributeArrowType> = {
108
  /** Discriminator for chunk-backed construction. */
109
  type: 'data';
110
  /** Name used when this vector is added to an {@link GPUTable}. */
111
  name: string;
112
  /** Arrow type that describes every chunk in `data`. */
113
  arrowType: T;
114
  /** Existing GPU data chunks to expose through this vector. */
115
  data: GPUData<T>[];
116
  /** Bytes between adjacent logical rows. Defaults to the first chunk stride when available. */
117
  byteStride?: number;
118
  /** Optional buffer layout retained for interleaved chunk collections. */
119
  bufferLayout?: BufferLayout;
120
};
121

122
/** Constructor props for an appendable DynamicBuffer-backed Arrow vector. */
123
export type GPUVectorFromAppendableProps<T extends AppendableArrowType = AppendableArrowType> = {
124
  /** Discriminator for appendable DynamicBuffer-backed construction. */
125
  type: 'appendable';
126
  /** Name used when this vector is added to an {@link GPUTable}. */
127
  name: string;
128
  /** Device that creates the DynamicBuffer. */
129
  device: Device;
130
  /** Arrow type that describes appended Arrow data. */
131
  arrowType: T;
132
  /** Initial row capacity. Defaults to `0`. */
133
  initialCapacityRows?: number;
134
  /** Capacity growth multiplier. Defaults to `1.5`. */
135
  capacityGrowthFactor?: number;
136
  /** DynamicBuffer construction props. */
137
  bufferProps?: GPUVectorDynamicBufferProps;
138
};
139

140
/** Discriminated constructor props for {@link GPUVector}. */
141
export type GPUVectorCreateProps<T extends arrow.DataType = AttributeArrowType> =
142
  | GPUVectorFromArrowProps<T>
143
  | (T extends AttributeArrowType ? GPUVectorFromBufferProps<T> : never)
144
  | GPUVectorFromInterleavedProps
145
  | GPUVectorFromDataProps<T>
146
  | (T extends AppendableArrowType ? GPUVectorFromAppendableProps<T> : never);
147

148
/**
149
 * GPU memory and Arrow type metadata derived from one Arrow vector.
150
 *
151
 * The Arrow vector is a construction input only. GPUVector does not retain
152
 * the source vector; it keeps a GPU buffer plus the type, length, and stride that
153
 * describe the uploaded memory.
154
 *
155
 * Ownership is tracked separately from the buffer reference. Vectors constructed
156
 * from Arrow data allocate and own their buffers. Vectors wrapping an existing
157
 * buffer default to non-owning unless `ownsBuffer` is supplied. In-place
158
 * operations can use {@link transferBufferOwnership} to consume one logical
159
 * vector and return another view that becomes responsible for destroying the
160
 * shared buffer.
161
 */
162
export class GPUVector<T extends arrow.DataType = AttributeArrowType> {
163
  /** Name used when this vector is added to an {@link GPUTable}. */
164
  readonly name: string;
165
  /** Arrow type that describes the uploaded vector memory. */
166
  readonly type: T;
167
  /** Number of logical Arrow vector rows uploaded into the GPU buffer. */
168
  length: number;
169
  /** Number of scalar values per logical vector row. */
170
  readonly stride: number;
171
  /** Byte offset of the first logical row in {@link buffer}. */
172
  readonly byteOffset: number;
173
  /** Bytes between adjacent logical rows in {@link buffer}. */
174
  readonly byteStride: number;
175
  /** Optional GPU buffer layout described by this vector. */
176
  readonly bufferLayout?: BufferLayout;
177
  /** GPU data chunk views preserving the source Arrow vector's chunk boundaries. */
178
  readonly data: GPUData<T>[] = [];
211✔
179
  /** Single concrete GPU buffer when this vector is directly bindable as one buffer. */
180
  private _buffer?: Buffer | DynamicBuffer;
181
  /** Whether this vector is responsible for destroying {@link buffer}. */
182
  private _ownsBuffer: boolean;
183
  /** Whether this vector owns chunk-local GPU buffers rather than one aggregate buffer. */
184
  private _ownsDataChunks = false;
211✔
185
  /** Detached batch-local vectors whose buffers are now owned by this aggregate view. */
186
  private readonly _ownedVectors: GPUVector[] = [];
211✔
187
  /** Capacity growth multiplier for appendable DynamicBuffer-backed vectors. */
188
  private readonly capacityGrowthFactor?: number;
189
  /** Number of occupied bytes written into appendable DynamicBuffer-backed vectors. */
190
  private appendableByteLength = 0;
211✔
191

192
  /** Creates a GPU representation from an Arrow vector without retaining the source vector. */
193
  constructor(device: Device, vector: arrow.Vector<T>, props?: GPUVectorBufferProps);
194
  /** Creates a GPU representation using discriminated construction props. */
195
  constructor(props: GPUVectorCreateProps<T>);
196
  constructor(
197
    deviceOrProps: Device | GPUVectorCreateProps<any>,
198
    vector?: arrow.Vector<T>,
199
    props: GPUVectorBufferProps = {}
211✔
200
  ) {
201
    const constructionProps =
202
      deviceOrProps instanceof Device
211✔
203
        ? ({
204
            type: 'arrow',
205
            name: 'vector',
206
            device: deviceOrProps,
207
            vector: vector!,
208
            bufferProps: props
209
          } satisfies GPUVectorFromArrowProps<T>)
210
        : normalizeGPUVectorCreateProps(deviceOrProps);
211

212
    switch (constructionProps.type) {
211✔
213
      case 'arrow': {
214
        const {name, device, vector: arrowVector, bufferProps = {}} = constructionProps;
159✔
215
        this.name = name;
159✔
216
        this.type = arrowVector.type;
159✔
217
        this.length = arrowVector.length;
159✔
218
        if (arrow.DataType.isUtf8(arrowVector.type)) {
159✔
219
          this.stride = 1;
20✔
220
          this.byteOffset = 0;
20✔
221
          this.byteStride = 1;
20✔
222
          for (const arrowData of arrowVector.data) {
20✔
223
            this.data.push(new GPUData(device, arrowData as arrow.Data<T>, bufferProps));
22✔
224
          }
225
          this._ownsBuffer = false;
20✔
226
          this._ownsDataChunks = true;
20✔
227
          return;
20✔
228
        }
229
        if (!isInstanceArrowType(arrowVector.type)) {
139!
230
          throw new Error(`GPUVector does not support Arrow type ${arrowVector.type}`);
×
231
        }
232
        this.stride = getArrowVectorStride(arrowVector as arrow.Vector<AttributeArrowType>);
139✔
233
        this.byteOffset = 0;
139✔
234
        this.byteStride = getArrowTypeByteStride(arrowVector.type);
139✔
235
        this._buffer = device.createBuffer({
139✔
236
          usage: Buffer.VERTEX | Buffer.STORAGE | Buffer.COPY_DST | Buffer.COPY_SRC,
237
          ...bufferProps,
238
          data: getArrowVectorBufferSource(arrowVector as any)
239
        });
240
        const dataBuffer = createArrowGPUDataBuffer(this._buffer);
139✔
241
        let byteOffset = 0;
139✔
242
        for (const arrowData of arrowVector.data) {
139✔
243
          const gpuData = new GPUData<T>({
140✔
244
            buffer: dataBuffer,
245
            arrowType: arrowData.type as T,
246
            length: arrowData.length,
247
            byteOffset,
248
            byteStride: this.byteStride,
249
            ownsBuffer: false,
250
            sourceData: arrowData as arrow.Data<T>
251
          });
252
          this.data.push(gpuData);
140✔
253
          byteOffset += arrowData.length * this.byteStride;
140✔
254
        }
255
        this._ownsBuffer = true;
139✔
256
        return;
139✔
257
      }
258

259
      case 'buffer': {
260
        const {
261
          name,
262
          buffer,
263
          arrowType,
264
          length,
265
          byteOffset = 0,
14✔
266
          byteStride = getArrowTypeByteStride(arrowType),
14✔
267
          ownsBuffer = false
14✔
268
        } = constructionProps;
14✔
269
        this.name = name;
14✔
270
        this._buffer = buffer;
14✔
271
        this.type = arrowType;
14✔
272
        this.length = length;
14✔
273
        this.stride = getArrowTypeStride(arrowType);
14✔
274
        this.byteOffset = byteOffset;
14✔
275
        this.byteStride = byteStride;
14✔
276
        const dataBuffer = createArrowGPUDataBuffer(buffer);
14✔
277
        this.data.push(
14✔
278
          new GPUData({
279
            buffer: dataBuffer,
280
            arrowType,
281
            length,
282
            byteOffset,
283
            byteStride,
284
            ownsBuffer: false
285
          }) as GPUData<T>
286
        );
287
        this._ownsBuffer = ownsBuffer;
14✔
288
        return;
14✔
289
      }
290

291
      case 'interleaved': {
292
        const {
293
          name,
294
          buffer,
295
          length,
296
          byteOffset = 0,
3✔
297
          byteStride,
298
          attributes,
299
          ownsBuffer = false
3✔
300
        } = constructionProps;
3✔
301
        this.name = name;
3✔
302
        this._buffer = buffer;
3✔
303
        this.type = new arrow.Binary() as T;
3✔
304
        this.length = length;
3✔
305
        this.stride = byteStride;
3✔
306
        this.byteOffset = byteOffset;
3✔
307
        this.byteStride = byteStride;
3✔
308
        this.bufferLayout = {name, byteStride, attributes};
3✔
309
        const dataBuffer = createArrowGPUDataBuffer(buffer);
3✔
310
        this.data.push(
3✔
311
          new GPUData({
312
            buffer: dataBuffer,
313
            arrowType: this.type,
314
            length,
315
            byteOffset,
316
            byteStride,
317
            ownsBuffer: false
318
          })
319
        );
320
        this._ownsBuffer = ownsBuffer;
3✔
321
        return;
3✔
322
      }
323

324
      case 'data': {
325
        const {name, arrowType, data, byteStride, bufferLayout} = constructionProps;
27✔
326
        if (data.some(chunk => !arrow.util.compareTypes(chunk.type, arrowType))) {
27!
327
          throw new Error('GPUVector data chunks must share the declared Arrow type');
×
328
        }
329

330
        this.name = name;
27✔
331
        this.type = arrowType;
27✔
332
        this.length = data.reduce((length, chunk) => length + chunk.length, 0);
27✔
333
        this.stride = data[0]?.stride ?? getArrowTypeStride(arrowType);
27!
334
        this.byteOffset = data.length === 1 ? data[0].byteOffset : 0;
27!
335
        this.byteStride = byteStride ?? data[0]?.byteStride ?? getArrowTypeByteStride(arrowType);
27!
336
        this.bufferLayout = bufferLayout;
27✔
337
        this.data.push(...data);
27✔
338
        this._buffer = data.length === 1 ? data[0].buffer : undefined;
27!
339
        this._ownsBuffer = false;
27✔
340
        return;
27✔
341
      }
342

343
      case 'appendable': {
344
        const {
345
          name,
346
          device,
347
          arrowType,
348
          initialCapacityRows = 0,
8✔
349
          capacityGrowthFactor = 1.5,
8✔
350
          bufferProps
351
        } = constructionProps;
8✔
352
        if (!isInstanceArrowType(arrowType) && !arrow.DataType.isUtf8(arrowType)) {
8!
UNCOV
353
          throw new Error(`GPUVector does not support Arrow type ${arrowType}`);
×
354
        }
355
        this.name = name;
8✔
356
        this.type = arrowType as unknown as T;
8✔
357
        this.length = 0;
8✔
358
        this.stride = arrow.DataType.isUtf8(arrowType) ? 1 : getArrowTypeStride(arrowType);
8✔
359
        this.byteOffset = 0;
8✔
360
        this.byteStride = arrow.DataType.isUtf8(arrowType) ? 1 : getArrowTypeByteStride(arrowType);
8✔
361
        this._buffer = new DynamicBuffer(device, {
8✔
362
          usage: Buffer.VERTEX | Buffer.STORAGE | Buffer.COPY_DST | Buffer.COPY_SRC,
363
          ...bufferProps,
364
          id: bufferProps?.id ?? `${name}-appendable-arrow-vector`,
16✔
365
          byteLength: Math.max(1, initialCapacityRows * this.byteStride)
366
        });
367
        this.capacityGrowthFactor = capacityGrowthFactor;
8✔
368
        this._ownsBuffer = true;
8✔
369
        return;
8✔
370
      }
371
    }
372
  }
373

374
  /**
375
   * Directly bindable GPU buffer when this vector has one concrete backing buffer.
376
   *
377
   * Aggregate table vectors may span multiple batch-owned buffers. Those vectors
378
   * intentionally require callers to use {@link data} or batch-local vectors.
379
   */
380
  get buffer(): Buffer | DynamicBuffer {
381
    if (!this._buffer) {
202✔
382
      throw new Error('GPUVector.buffer is unavailable for multi-buffer vectors; use data[]');
2✔
383
    }
384
    return this._buffer;
200✔
385
  }
386

387
  /**
388
   * Whether this vector is responsible for destroying {@link buffer}.
389
   *
390
   * `destroy()` only releases the buffer when this is `true`. This value can
391
   * change when ownership is transferred to another same-buffer view.
392
   */
393
  get ownsBuffer(): boolean {
394
    return this._ownsBuffer || this._ownedVectors.some(vector => vector.ownsBuffer);
5✔
395
  }
396

397
  /**
398
   * Adds one already-materialized GPU data chunk to this logical vector.
399
   *
400
   * This preserves ownership on the supplied {@link GPUData}; the vector
401
   * only aggregates metadata and never adopts or destroys that buffer through
402
   * this method.
403
   */
404
  addData(data: GPUData<T>): this {
405
    if (!arrow.util.compareTypes(data.type, this.type)) {
31!
406
      throw new Error('GPUVector.addData() requires matching Arrow data types');
×
407
    }
408
    if (data.byteStride !== this.byteStride) {
31!
409
      throw new Error('GPUVector.addData() requires matching byteStride');
×
410
    }
411

412
    this.data.push(data);
31✔
413
    this.length += data.length;
31✔
414
    if (this.data.length > 1) {
31!
415
      this._buffer = undefined;
31✔
416
    }
417
    return this;
31✔
418
  }
419

420
  /** Number of rows the appendable backing DynamicBuffer can hold without reallocating. */
421
  get capacityRows(): number | undefined {
422
    if (arrow.DataType.isUtf8(this.type)) {
1!
NEW
423
      return undefined;
×
424
    }
425
    return this._buffer instanceof DynamicBuffer
1!
426
      ? Math.floor(this._buffer.byteLength / this.byteStride)
427
      : undefined;
428
  }
429

430
  /** Appends one Arrow Data chunk into this vector's trailing DynamicBuffer-backed data storage. */
431
  addToLastData(data: arrow.Data<T & AppendableArrowType>): this {
432
    if (!(this._buffer instanceof DynamicBuffer)) {
14!
433
      throw new Error('GPUVector.addToLastData() requires appendable vector storage');
×
434
    }
435
    if (!arrow.util.compareTypes(data.type, this.type)) {
14!
436
      throw new Error('GPUVector.addToLastData() requires matching Arrow data types');
×
437
    }
438
    validateArrowGPUDataDirectUpload(this.name, data);
14✔
439

440
    const isUtf8Data = arrow.DataType.isUtf8(data.type);
14✔
441
    const uploadData = isUtf8Data
14✔
442
      ? getArrowUtf8DataBufferSource(data as arrow.Data<arrow.Utf8>)
443
      : getArrowDataBufferSource(data as arrow.Data<AttributeArrowType>);
444
    const byteOffset = isUtf8Data
14✔
445
      ? alignAppendableByteLength(this.appendableByteLength)
446
      : this.appendableByteLength;
447
    const writeData = isUtf8Data
14✔
448
      ? padAppendableUtf8UploadData(uploadData as Uint8Array)
449
      : uploadData;
450
    const requiredRows = this.length + data.length;
14✔
451
    const requiredByteLength = byteOffset + writeData.byteLength;
14✔
452
    this.ensureAppendableByteCapacity(requiredByteLength);
14✔
453
    if (writeData.byteLength > 0) {
14!
454
      this._buffer.write(writeData, byteOffset);
14✔
455
    }
456
    this.data.push(
14✔
457
      new GPUData({
458
        buffer: this._buffer,
459
        arrowType: data.type,
460
        length: data.length,
461
        byteOffset,
462
        byteStride: this.byteStride,
463
        ownsBuffer: false,
464
        sourceData: data as arrow.Data<T>
465
      }) as GPUData<T>
466
    );
467
    this.length = requiredRows;
14✔
468
    this.appendableByteLength = requiredByteLength;
14✔
469
    return this;
14✔
470
  }
471

472
  /** @deprecated Use {@link addToLastData}. */
473
  addToLastBatch(data: arrow.Data<T & AppendableArrowType>): this {
474
    return this.addToLastData(data);
×
475
  }
476

477
  /** Appends every Arrow Data chunk from one Arrow vector into appendable batch storage. */
478
  addVectorToLastBatch(vector: arrow.Vector<T & AppendableArrowType>): this {
UNCOV
479
    if (!arrow.util.compareTypes(vector.type, this.type)) {
×
480
      throw new Error('GPUVector.addVectorToLastBatch() requires matching Arrow data types');
×
481
    }
UNCOV
482
    for (const data of vector.data) {
×
NEW
483
      this.addToLastData(data as arrow.Data<T & AppendableArrowType>);
×
484
    }
UNCOV
485
    return this;
×
486
  }
487

488
  /** Clears appendable logical rows while retaining the DynamicBuffer allocation. */
489
  resetLastBatch(): this {
490
    if (!(this._buffer instanceof DynamicBuffer)) {
3!
491
      throw new Error('GPUVector.resetLastBatch() requires appendable vector storage');
×
492
    }
493
    this.length = 0;
3✔
494
    this.appendableByteLength = 0;
3✔
495
    this.data.length = 0;
3✔
496
    return this;
3✔
497
  }
498

499
  /** @internal Retains detached batch-local vector ownership under this aggregate vector. */
500
  retainOwnedVectors(vectors: GPUVector[]): this {
501
    this._ownedVectors.push(...vectors);
1✔
502
    return this;
1✔
503
  }
504

505
  /**
506
   * Transfers buffer ownership to another vector that views the same GPU buffer.
507
   *
508
   * This is intended for in-place operations that consume one logical vector and
509
   * return a new logical interpretation of the same bytes. After transfer,
510
   * destroying this vector will not destroy the buffer; destroying `target` will
511
   * destroy it if this vector previously owned it.
512
   */
513
  transferBufferOwnership(target: GPUVector): void {
514
    if (!this._buffer || !target._buffer || target._buffer !== this._buffer) {
1!
515
      throw new Error('GPUVector ownership can only be transferred to the same buffer');
×
516
    }
517
    target._ownsBuffer = this._ownsBuffer;
1✔
518
    this._ownsBuffer = false;
1✔
519
  }
520

521
  /** Reads the GPU buffer contents back into a single non-null Arrow vector. */
522
  async readAsync(): Promise<arrow.Vector<T>> {
523
    if (this.bufferLayout) {
7✔
524
      throw new Error('GPUVector.readAsync() does not support interleaved vectors');
1✔
525
    }
526

527
    if (arrow.DataType.isUtf8(this.type)) {
6✔
528
      const data = await Promise.all(this.data.map(chunk => chunk.readAsync()));
2✔
529
      return new arrow.Vector(data) as arrow.Vector<T>;
1✔
530
    }
531

532
    if (!this._buffer) {
5!
533
      const data = await Promise.all(this.data.map(chunk => chunk.readAsync()));
×
534
      return new arrow.Vector(data) as arrow.Vector<T>;
×
535
    }
536

537
    return readArrowGPUVectorAsync({
5✔
538
      type: this.type as unknown as AttributeArrowType,
539
      buffer: this._buffer,
540
      length: this.length,
541
      byteOffset: this.byteOffset,
542
      byteStride: this.byteStride
543
    }) as unknown as Promise<arrow.Vector<T>>;
544
  }
545

546
  destroy(): void {
547
    if (this._ownsBuffer && this._buffer) {
188✔
548
      this._buffer.destroy();
161✔
549
      this._ownsBuffer = false;
161✔
550
    }
551
    for (const vector of this._ownedVectors.splice(0)) {
188✔
552
      vector.destroy();
2✔
553
    }
554
    if (this._ownsDataChunks) {
188✔
555
      for (const data of this.data) {
20✔
556
        data.destroy();
22✔
557
      }
558
      this._ownsDataChunks = false;
20✔
559
    }
560
  }
561

562
  private ensureAppendableByteCapacity(requiredByteLength: number): void {
563
    if (!(this._buffer instanceof DynamicBuffer)) {
14!
564
      throw new Error('GPUVector append capacity requires DynamicBuffer storage');
×
565
    }
566
    const capacityByteLength = this._buffer.byteLength;
14✔
567
    if (requiredByteLength <= capacityByteLength) {
14!
UNCOV
568
      return;
×
569
    }
570
    const grownByteLength = Math.ceil(
14✔
571
      Math.max(capacityByteLength, 1) * (this.capacityGrowthFactor ?? 1.5)
14!
572
    );
573
    const nextCapacityByteLength = Math.max(requiredByteLength, grownByteLength);
14✔
574
    this._buffer.resize({
14✔
575
      byteLength: nextCapacityByteLength,
576
      preserveData: this.appendableByteLength > 0,
577
      copyByteLength: this.appendableByteLength
578
    });
579
  }
580
}
581

582
function alignAppendableByteLength(byteLength: number): number {
583
  return Math.ceil(byteLength / 4) * 4;
6✔
584
}
585

586
function padAppendableUtf8UploadData(uploadData: Uint8Array): Uint8Array {
587
  const paddedByteLength = alignAppendableByteLength(uploadData.byteLength);
3✔
588
  if (paddedByteLength === uploadData.byteLength) {
3!
NEW
589
    return uploadData;
×
590
  }
591
  const paddedUploadData = new Uint8Array(paddedByteLength);
3✔
592
  paddedUploadData.set(uploadData);
3✔
593
  return paddedUploadData;
3✔
594
}
595

596
type NormalizedGPUVectorCreateProps<T extends arrow.DataType = AttributeArrowType> =
597
  | (GPUVectorFromArrowProps<T> & {type: 'arrow'})
598
  | (T extends AttributeArrowType ? GPUVectorFromBufferProps<T> : never)
599
  | GPUVectorFromInterleavedProps
600
  | GPUVectorFromDataProps<T>
601
  | (T extends AppendableArrowType ? GPUVectorFromAppendableProps<T> : never);
602

603
function normalizeGPUVectorCreateProps<T extends arrow.DataType>(
604
  props: GPUVectorCreateProps<T>
605
): NormalizedGPUVectorCreateProps<T> {
606
  if ('vector' in props && props.type === undefined) {
128✔
607
    return {...props, type: 'arrow'};
2✔
608
  }
609
  return props as NormalizedGPUVectorCreateProps<T>;
126✔
610
}
611

612
function getArrowVectorStride(vector: arrow.Vector<AttributeArrowType>): number {
613
  return getArrowTypeStride(vector.type);
139✔
614
}
615

616
function createArrowGPUDataBuffer(buffer: Buffer | DynamicBuffer): DynamicBuffer {
617
  return buffer instanceof DynamicBuffer
156!
618
    ? buffer
619
    : new DynamicBuffer(buffer.device, {
620
        buffer,
621
        ownsBuffer: false
622
      });
623
}
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