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

visgl / luma.gl / 28704379903

04 Jul 2026 11:12AM UTC coverage: 72.054% (-0.1%) from 72.186%
28704379903

Pull #2733

github

web-flow
Merge e6af62a6e into 30d8c7ace
Pull Request #2733: [codex] Declare physical GPU data struct formats inline

11275 of 17606 branches covered (64.04%)

Branch coverage included in aggregate %.

22 of 79 new or added lines in 3 files covered. (27.85%)

10 existing lines in 3 files now uncovered.

21723 of 28190 relevant lines covered (77.06%)

5621.96 hits per line

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

79.52
/modules/tables/src/table/gpu-record-batch.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import type {BufferLayout, VertexFormat} from '@luma.gl/core';
6
import type {GPUData} from './gpu-data';
7
import type {GPUField, GPUSchema, GPUTypeMap} from './gpu-schema';
8
import {isGPUTableIndexColumnName} from './gpu-schema';
9
import {
10
  getGPUVectorElementFormat,
11
  isValueListGPUVectorFormat,
12
  isVertexListGPUVectorFormat
13
} from './gpu-vector-format';
14

15
/** Batch-local GPU data keyed by selected table column name. */
16
export type GPUDataMap<T extends GPUTypeMap = GPUTypeMap> = {
17
  [Name in keyof T & string]: GPUData<T[Name]>;
18
};
19

20
/** Generic source-row identity for a GPU record batch. */
21
export type GPURecordBatchSourceInfo = {
22
  /** Zero-based source batch index in the producing stream/table. */
23
  sourceBatchIndex: number;
24
  /** Zero-based source row index assigned to the first logical source row in this batch. */
25
  sourceRowIndexOffset: number;
26
  /** Number of logical source rows represented by this batch. */
27
  sourceRowCount: number;
28
};
29

30
/** Props for constructing one immutable GPU record batch from GPU data chunks. */
31
export type GPURecordBatchFromDataProps<T extends GPUTypeMap = GPUTypeMap> = {
32
  /** One batch-local GPU data chunk keyed by selected column name. */
33
  gpuData: GPUDataMap<T> | Record<string, GPUData>;
34
  /** Optional precomputed batch buffer layouts. */
35
  bufferLayout?: BufferLayout[];
36
  /** Optional selected schema fields. Defaults to fields synthesized from data names and formats. */
37
  fields?: GPUField[];
38
  /** Explicit row count for intentionally data-less batches. */
39
  numRows?: number;
40
  /** Optional batch-level schema metadata. */
41
  metadata?: Map<string, string>;
42
  /** Optional source-row identity retained for picking and row-level diagnostics. */
43
  sourceInfo?: GPURecordBatchSourceInfo;
44
  /** Number of null rows in the generated GPU record batch. */
45
  nullCount?: number;
46
};
47

48
/** Generic record-batch construction props. */
49
export type GPURecordBatchProps<T extends GPUTypeMap = GPUTypeMap> = GPURecordBatchFromDataProps<T>;
50

51
/** Immutable GPU memory and schema metadata for one selected record batch. */
52
export class GPURecordBatch<T extends GPUTypeMap = GPUTypeMap> {
53
  /** GPU-facing schema for the selected columns. */
54
  schema: GPUSchema<T>;
55
  /** Number of logical rows represented by the batch. */
56
  numRows: number;
57
  /** Number of selected GPU columns in {@link schema}. */
58
  numCols: number;
59
  /** Number of null rows retained in batch metadata. */
60
  nullCount: number;
61
  /** Buffer layout derived by the producing adapter. */
62
  readonly bufferLayout: BufferLayout[] = [];
124✔
63
  /** One batch-local GPU data chunk keyed by shader/table column name. */
64
  readonly gpuData: Record<string, GPUData> = {};
124✔
65
  /** Optional source-row identity retained from the producing table/stream. */
66
  readonly sourceInfo?: GPURecordBatchSourceInfo;
67

68
  /** Creates one immutable GPU record batch from named GPU data chunks. */
69
  constructor({
70
    gpuData,
71
    bufferLayout,
72
    fields,
73
    numRows,
74
    metadata,
75
    sourceInfo,
76
    nullCount = 0
19✔
77
  }: GPURecordBatchFromDataProps<T>) {
78
    this.numRows = getGPUDataCollectionRowCount(gpuData, numRows);
124✔
79
    this.nullCount = nullCount;
124✔
80
    Object.assign(this.gpuData, gpuData);
124✔
81
    this.bufferLayout.push(...getGPUDataCollectionBufferLayout(gpuData, bufferLayout));
124✔
82
    const resolvedFields = getGPUDataCollectionFields(gpuData, fields, this.bufferLayout);
124✔
83
    this.schema = {
124✔
84
      fields: resolvedFields,
85
      metadata: metadata ?? new Map()
168✔
86
    };
87
    this.numCols = resolvedFields.length;
124✔
88
    this.sourceInfo = sourceInfo ? {...sourceInfo} : undefined;
124✔
89
  }
90

91
  /** Destroys every owned GPU data chunk retained by this batch. */
92
  destroy(): void {
93
    for (const data of Object.values(this.gpuData)) {
91✔
94
      data.destroy();
135✔
95
    }
96
  }
97
}
98

99
function getGPUDataCollectionRowCount<T extends GPUTypeMap>(
100
  gpuData: GPURecordBatchFromDataProps<T>['gpuData'],
101
  explicitNumRows?: number
102
): number {
103
  const firstData = Object.values(gpuData)[0];
124✔
104
  if (!firstData) {
124✔
105
    return explicitNumRows ?? 0;
10!
106
  }
107

108
  const numRows = explicitNumRows ?? firstData.length;
114✔
109
  const mismatchedData = Object.values(gpuData).find(data => data.length !== numRows);
195✔
110
  if (mismatchedData) {
114!
UNCOV
111
    throw new Error('GPURecordBatch requires matching GPUData row counts');
×
112
  }
113
  return numRows;
114✔
114
}
115

116
function getGPUDataCollectionBufferLayout<T extends GPUTypeMap>(
117
  gpuData: GPURecordBatchFromDataProps<T>['gpuData'],
118
  explicitBufferLayout?: BufferLayout[]
119
): BufferLayout[] {
120
  if (explicitBufferLayout) {
124✔
121
    for (const layout of explicitBufferLayout) {
111✔
122
      if (isGPUTableIndexColumnName(layout.name)) {
150!
UNCOV
123
        throw new Error(
×
124
          `GPURecordBatch buffer layout cannot include reserved index column "${layout.name}"`
125
        );
126
      }
127
      if (!gpuData[layout.name]) {
150!
UNCOV
128
        throw new Error(`GPURecordBatch buffer layout references missing GPUData "${layout.name}"`);
×
129
      }
130
    }
131
    return explicitBufferLayout;
111✔
132
  }
133

134
  return Object.entries(gpuData).flatMap(([name, data]) =>
13✔
135
    isGPUTableIndexColumnName(name) ? [] : synthesizeGPUDataBufferLayout(name, data)
16✔
136
  );
137
}
138

139
function synthesizeGPUDataBufferLayout(name: string, data: GPUData): BufferLayout[] {
140
  if (!data.format) {
12!
UNCOV
141
    throw new Error(
×
142
      `GPURecordBatch cannot synthesize a buffer layout for GPUData "${name}" without a format`
143
    );
144
  }
145
  if (isVertexListGPUVectorFormat(data.format)) {
12!
UNCOV
146
    throw new Error(
×
147
      `GPURecordBatch cannot synthesize a generic buffer layout for vertex-list GPUData "${name}"`
148
    );
149
  }
150
  if (isValueListGPUVectorFormat(data.format)) {
12!
UNCOV
151
    throw new Error(
×
152
      `GPURecordBatch cannot synthesize a generic buffer layout for value-list GPUData "${name}"`
153
    );
154
  }
155
  return [
12✔
156
    {
157
      name,
158
      byteStride: data.byteStride,
159
      format: getGPUVectorElementFormat(data.format) as VertexFormat
160
    }
161
  ];
162
}
163

164
function getGPUDataCollectionFields<T extends GPUTypeMap>(
165
  gpuData: GPURecordBatchFromDataProps<T>['gpuData'],
166
  explicitFields: GPUField[] | undefined,
167
  bufferLayout: BufferLayout[]
168
): GPUField[] {
169
  if (explicitFields) {
124✔
170
    for (const field of explicitFields) {
96✔
171
      if (!gpuData[field.name]) {
138!
UNCOV
172
        throw new Error(`GPURecordBatch schema references missing GPUData "${field.name}"`);
×
173
      }
174
    }
175
    return explicitFields;
96✔
176
  }
177

178
  const layoutNames = new Set(bufferLayout.map(layout => layout.name));
43✔
179
  return Object.entries(gpuData).map(([name, data]) => {
28✔
180
    if (!data.format) {
57✔
181
      if (layoutNames.has(name)) {
5!
182
        return {
5✔
183
          name,
184
          nullable: false,
185
          metadata: new Map()
186
        };
187
      }
UNCOV
188
      throw new Error(
×
189
        `GPURecordBatch cannot synthesize a schema field for GPUData "${name}" without a format`
190
      );
191
    }
192
    return {
52✔
193
      name,
194
      format: data.format,
195
      nullable: false,
196
      metadata: new Map()
197
    };
198
  });
199
}
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