UNPKG

14.6 kBPlain TextView Raw
1/*
2 * Generates the following:
3 * - data/array_types.js, which consists of:
4 * - StructArrayLayout_* subclasses, one for each underlying memory layout we need
5 * - Named exports mapping each conceptual array type (e.g., CircleLayoutArray) to its corresponding StructArrayLayout class
6 * - Particular, named StructArray subclasses, when fancy struct accessors are needed (e.g. CollisionBoxArray)
7 */
8
9'use strict'; // eslint-disable-line strict
10
11import * as fs from 'fs';
12import * as util from '../src/util/util';
13import {createLayout, viewTypes} from '../src/util/struct_array';
14import type {ViewType, StructArrayLayout} from '../src/util/struct_array';
15
16import posAttributes from '../src/data/pos_attributes';
17import rasterBoundsAttributes from '../src/data/raster_bounds_attributes';
18import circleAttributes from '../src/data/bucket/circle_attributes';
19import fillAttributes from '../src/data/bucket/fill_attributes';
20import fillExtrusionAttributes from '../src/data/bucket/fill_extrusion_attributes';
21import lineAttributes from '../src/data/bucket/line_attributes';
22import lineAttributesExt from '../src/data/bucket/line_attributes_ext';
23import patternAttributes from '../src/data/bucket/pattern_attributes';
24// symbol layer specific arrays
25import {
26 symbolLayoutAttributes,
27 dynamicLayoutAttributes,
28 placementOpacityAttributes,
29 collisionBox,
30 collisionBoxLayout,
31 collisionCircleLayout,
32 collisionVertexAttributes,
33 quadTriangle,
34 placement,
35 symbolInstance,
36 glyphOffset,
37 lineVertex
38} from '../src/data/bucket/symbol_attributes';
39
40const typeAbbreviations = {
41 'Int8': 'b',
42 'Uint8': 'ub',
43 'Int16': 'i',
44 'Uint16': 'ui',
45 'Int32': 'l',
46 'Uint32': 'ul',
47 'Float32': 'f'
48};
49
50const arraysWithStructAccessors = [];
51const arrayTypeEntries = new Set();
52const layoutCache = {};
53
54function normalizeMembers(members, usedTypes) {
55 return members.map((member) => {
56 if (usedTypes && !usedTypes.has(member.type)) {
57 usedTypes.add(member.type);
58 }
59
60 return util.extend(member, {
61 size: sizeOf(member.type),
62 view: member.type.toLowerCase()
63 });
64 });
65}
66
67// - If necessary, write the StructArrayLayout_* class for the given layout
68// - If `includeStructAccessors`, write the fancy subclass
69// - Add an entry for `name` in the array type registry
70function createStructArrayType(name: string, layout: StructArrayLayout, includeStructAccessors: boolean = false) {
71 const hasAnchorPoint = layout.members.some(m => m.name === 'anchorPointX');
72
73 // create the underlying StructArrayLayout class exists
74 const layoutClass = createStructArrayLayoutType(layout);
75 const arrayClass = `${camelize(name)}Array`;
76
77 if (includeStructAccessors) {
78 const usedTypes = new Set(['Uint8']);
79 const members = normalizeMembers(layout.members, usedTypes);
80 arraysWithStructAccessors.push({
81 arrayClass,
82 members,
83 size: layout.size,
84 usedTypes,
85 hasAnchorPoint,
86 layoutClass,
87 includeStructAccessors
88 });
89 } else {
90 arrayTypeEntries.add(`export class ${arrayClass} extends ${layoutClass} {}`);
91 }
92}
93
94function createStructArrayLayoutType({members, size, alignment}) {
95 const usedTypes = new Set(['Uint8']);
96 members = normalizeMembers(members, usedTypes);
97
98 // combine consecutive 'members' with same underlying type, summing their
99 // component counts
100 if (!alignment || alignment === 1) members = members.reduce((memo, member) => {
101 if (memo.length > 0 && memo[memo.length - 1].type === member.type) {
102 const last = memo[memo.length - 1];
103 return memo.slice(0, -1).concat(util.extend({}, last, {
104 components: last.components + member.components,
105 }));
106 }
107 return memo.concat(member);
108 }, []);
109
110 const key = `${members.map(m => `${m.components}${typeAbbreviations[m.type]}`).join('')}${size}`;
111 const className = `StructArrayLayout${key}`;
112 // Layout alignment to 4 bytes boundaries can be an issue on some set of graphics cards. Particularly AMD.
113 if (size % 4 !== 0) { console.warn(`Warning: The layout ${className} is not aligned to 4-bytes boundaries.`); }
114 if (!layoutCache[key]) {
115 layoutCache[key] = {
116 className,
117 members,
118 size,
119 usedTypes
120 };
121 }
122
123 return className;
124}
125
126function sizeOf(type: ViewType): number {
127 return viewTypes[type].BYTES_PER_ELEMENT;
128}
129
130function camelize (str) {
131 return str.replace(/(?:^|[-_])(.)/g, (_, x) => {
132 return /^[0-9]$/.test(x) ? _ : x.toUpperCase();
133 });
134}
135
136createStructArrayType('pos', posAttributes);
137createStructArrayType('raster_bounds', rasterBoundsAttributes);
138
139// layout vertex arrays
140const layoutAttributes = {
141 circle: circleAttributes,
142 fill: fillAttributes,
143 'fill-extrusion': fillExtrusionAttributes,
144 heatmap: circleAttributes,
145 line: lineAttributes,
146 lineExt: lineAttributesExt,
147 pattern: patternAttributes
148};
149for (const name in layoutAttributes) {
150 createStructArrayType(`${name.replace(/-/g, '_')}_layout`, layoutAttributes[name]);
151}
152
153createStructArrayType('symbol_layout', symbolLayoutAttributes);
154createStructArrayType('symbol_dynamic_layout', dynamicLayoutAttributes);
155createStructArrayType('symbol_opacity', placementOpacityAttributes);
156createStructArrayType('collision_box', collisionBox, true);
157createStructArrayType('collision_box_layout', collisionBoxLayout);
158createStructArrayType('collision_circle_layout', collisionCircleLayout);
159createStructArrayType('collision_vertex', collisionVertexAttributes);
160createStructArrayType('quad_triangle', quadTriangle);
161createStructArrayType('placed_symbol', placement, true);
162createStructArrayType('symbol_instance', symbolInstance, true);
163createStructArrayType('glyph_offset', glyphOffset, true);
164createStructArrayType('symbol_line_vertex', lineVertex, true);
165
166// feature index array
167createStructArrayType('feature_index', createLayout([
168 // the index of the feature in the original vectortile
169 {type: 'Uint32', name: 'featureIndex'},
170 // the source layer the feature appears in
171 {type: 'Uint16', name: 'sourceLayerIndex'},
172 // the bucket the feature appears in
173 {type: 'Uint16', name: 'bucketIndex'}
174]), true);
175
176// triangle index array
177createStructArrayType('triangle_index', createLayout([
178 {type: 'Uint16', name: 'vertices', components: 3}
179]));
180
181// line index array
182createStructArrayType('line_index', createLayout([
183 {type: 'Uint16', name: 'vertices', components: 2}
184]));
185
186// line strip index array
187createStructArrayType('line_strip_index', createLayout([
188 {type: 'Uint16', name: 'vertices', components: 1}
189]));
190
191// paint vertex arrays
192
193// used by SourceBinder for float properties
194createStructArrayLayoutType(createLayout([{
195 name: 'dummy name (unused for StructArrayLayout)',
196 type: 'Float32',
197 components: 1
198}], 4));
199
200// used by SourceBinder for color properties and CompositeBinder for float properties
201createStructArrayLayoutType(createLayout([{
202 name: 'dummy name (unused for StructArrayLayout)',
203 type: 'Float32',
204 components: 2
205}], 4));
206
207// used by CompositeBinder for color properties
208createStructArrayLayoutType(createLayout([{
209 name: 'dummy name (unused for StructArrayLayout)',
210 type: 'Float32',
211 components: 4
212}], 4));
213
214const layouts = Object.keys(layoutCache).map(k => layoutCache[k]);
215
216function emitStructArrayLayout(locals) {
217 const output = [];
218 const {
219 className,
220 members,
221 size,
222 usedTypes
223 } = locals;
224 const structArrayLayoutClass = className;
225
226 output.push(
227 `/**
228 * Implementation of the StructArray layout:`);
229
230 for (const member of members) {
231 output.push(
232 ` * [${member.offset}]: ${member.type}[${member.components}]`);
233 }
234
235 output.push(
236 ` *
237 * @private
238 */
239class ${structArrayLayoutClass} extends StructArray {`);
240
241 for (const type of usedTypes) {
242 output.push(
243 ` ${type.toLowerCase()}: ${type}Array;`);
244 }
245
246 output.push(`
247 _refreshViews() {`);
248
249 for (const type of usedTypes) {
250 output.push(
251 ` this.${type.toLowerCase()} = new ${type}Array(this.arrayBuffer);`);
252 }
253
254 output.push(
255 ' }');
256
257 // prep for emplaceBack: collect type sizes and count the number of arguments
258 // we'll need
259 const bytesPerElement = size;
260 const usedTypeSizes = [];
261 const argNames = [];
262 const argNamesTyped = [];
263
264 for (const member of members) {
265 if (usedTypeSizes.indexOf(member.size) < 0) {
266 usedTypeSizes.push(member.size);
267 }
268 for (let c = 0; c < member.components; c++) {
269 // arguments v0, v1, v2, ... are, in order, the components of
270 // member 0, then the components of member 1, etc.
271 const name = `v${argNames.length}`;
272 argNames.push(name);
273 argNamesTyped.push(`${name}: number`);
274 }
275 }
276
277 output.push(
278 `
279 public emplaceBack(${argNamesTyped.join(', ')}) {
280 const i = this.length;
281 this.resize(i + 1);
282 return this.emplace(i, ${argNames.join(', ')});
283 }
284
285 public emplace(i: number, ${argNamesTyped.join(', ')}) {`);
286
287 for (const size of usedTypeSizes) {
288 output.push(
289 ` const o${size.toFixed(0)} = i * ${(bytesPerElement / size).toFixed(0)};`);
290 }
291
292 let argIndex = 0;
293 for (const member of members) {
294 for (let c = 0; c < member.components; c++) {
295 // The index for `member` component `c` into the appropriate type array is:
296 // this.{TYPE}[o{SIZE} + MEMBER_OFFSET + {c}] = v{X}
297 // where MEMBER_OFFSET = ROUND(member.offset / size) is the per-element
298 // offset of this member into the array
299 const index = `o${member.size.toFixed(0)} + ${(member.offset / member.size + c).toFixed(0)}`;
300
301 output.push(
302 ` this.${member.view}[${index}] = v${argIndex++};`);
303 }
304 }
305
306 output.push(
307 ` return i;
308 }
309}
310
311${structArrayLayoutClass}.prototype.bytesPerElement = ${size};
312register('${structArrayLayoutClass}', ${structArrayLayoutClass});
313`);
314
315 return output.join('\n');
316}
317
318function emitStructArray(locals) {
319 const output = [];
320 const {
321 arrayClass,
322 members,
323 size,
324 hasAnchorPoint,
325 layoutClass,
326 includeStructAccessors
327 } = locals;
328
329 const structTypeClass = arrayClass.replace('Array', 'Struct');
330 const structArrayClass = arrayClass;
331 const structArrayLayoutClass = layoutClass;
332
333 // collect components
334 const components = [];
335 for (const member of members) {
336 for (let c = 0; c < member.components; c++) {
337 let name = member.name;
338 if (member.components > 1) {
339 name += c;
340 }
341 components.push({name, member, component: c});
342 }
343 }
344
345 // exceptions for which we generate accessors on the array rather than a separate struct for performance
346 const useComponentGetters = structArrayClass === 'GlyphOffsetArray' || structArrayClass === 'SymbolLineVertexArray';
347
348 if (includeStructAccessors && !useComponentGetters) {
349 output.push(
350 `class ${structTypeClass} extends Struct {
351 _structArray: ${structArrayClass};`);
352
353 for (const {name, member, component} of components) {
354 const elementOffset = `this._pos${member.size.toFixed(0)}`;
355 const componentOffset = (member.offset / member.size + component).toFixed(0);
356 const index = `${elementOffset} + ${componentOffset}`;
357 const componentAccess = `this._structArray.${member.view}[${index}]`;
358
359 output.push(
360 ` get ${name}() { return ${componentAccess}; }`);
361
362 // generate setters for properties that are updated during runtime symbol placement; others are read-only
363 if (name === 'crossTileID' || name === 'placedOrientation' || name === 'hidden') {
364 output.push(
365 ` set ${name}(x: number) { ${componentAccess} = x; }`);
366 }
367 }
368
369 // Special case used for the CollisionBoxArray type
370 if (hasAnchorPoint) {
371 output.push(
372 ' get anchorPoint() { return new Point(this.anchorPointX, this.anchorPointY); }');
373 }
374
375 output.push(
376 `}
377
378${structTypeClass}.prototype.size = ${size};
379
380export type ${structTypeClass.replace('Struct', '')} = ${structTypeClass};
381`);
382 } // end 'if (includeStructAccessors)'
383
384 output.push(
385 `/**
386 * @private
387 */
388export class ${structArrayClass} extends ${structArrayLayoutClass} {`);
389
390 if (useComponentGetters) {
391 for (const member of members) {
392 for (let c = 0; c < member.components; c++) {
393 if (!includeStructAccessors) continue;
394 let name = `get${member.name}`;
395 if (member.components > 1) {
396 name += c;
397 }
398 const componentOffset = (member.offset / member.size + c).toFixed(0);
399 const componentStride = size / member.size;
400 output.push(
401 ` ${name}(index: number) { return this.${member.view}[index * ${componentStride} + ${componentOffset}]; }`);
402 }
403 }
404 } else if (includeStructAccessors) { // get(i)
405 output.push(
406 ` /**
407 * Return the ${structTypeClass} at the given location in the array.
408 * @param {number} index The index of the element.
409 * @private
410 */
411 get(index: number): ${structTypeClass} {
412 assert(!this.isTransferred);
413 return new ${structTypeClass}(this, index);
414 }`);
415 }
416 output.push(
417 `}
418
419register('${structArrayClass}', ${structArrayClass});
420`);
421
422 return output.join('\n');
423}
424
425fs.writeFileSync('src/data/array_types.g.ts',
426 `// This file is generated. Edit build/generate-struct-arrays.ts, then run \`npm run codegen\`.
427
428import assert from 'assert';
429import {Struct, StructArray} from '../util/struct_array';
430import {register} from '../util/web_worker_transfer';
431import Point from '@mapbox/point-geometry';
432
433${layouts.map(emitStructArrayLayout).join('\n')}
434${arraysWithStructAccessors.map(emitStructArray).join('\n')}
435${[...arrayTypeEntries].join('\n')}
436export {
437 ${layouts.map(layout => layout.className).join(',\n ')}
438};
439`);
440
\No newline at end of file