Coveralls logob
Coveralls logo
  • Home
  • Features
  • Pricing
  • Docs
  • Sign In

uber / deck.gl / 13720

17 Sep 2019 - 4:53 coverage increased (+2.8%) to 82.714%
13720

Pull #3588

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
Try to fix examples
Pull Request #3588: Add JSON Tile3D examples to Playground

3390 of 4602 branches covered (73.66%)

Branch coverage included in aggregate %.

0 of 2 new or added lines in 1 file covered. (0.0%)

479 existing lines in 86 files now uncovered.

7161 of 8154 relevant lines covered (87.82%)

4294.85 hits per line

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

83.99
/modules/core/src/lib/layer.js
1
// Copyright (c) 2015 - 2017 Uber Technologies, Inc.
2
//
3
// Permission is hereby granted, free of charge, to any person obtaining a copy
4
// of this software and associated documentation files (the "Software"), to deal
5
// in the Software without restriction, including without limitation the rights
6
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
// copies of the Software, and to permit persons to whom the Software is
8
// furnished to do so, subject to the following conditions:
9
//
10
// The above copyright notice and this permission notice shall be included in
11
// all copies or substantial portions of the Software.
12
//
13
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
// THE SOFTWARE.
20

21
/* eslint-disable react/no-direct-mutation-state */
22
import {COORDINATE_SYSTEM} from './constants';
23
import AttributeManager from './attribute-manager';
24
import {removeLayerInSeer} from './seer-integration';
25
import UniformTransitionManager from './uniform-transition-manager';
26
import {diffProps, validateProps} from '../lifecycle/props';
27
import {count} from '../utils/count';
28
import log from '../utils/log';
29
import GL from '@luma.gl/constants';
30
import {withParameters} from '@luma.gl/core';
31
import assert from '../utils/assert';
32
import {mergeShaders} from '../utils/shader';
33
import {projectPosition, getWorldPosition} from '../shaderlib/project/project-functions';
34

35
import Component from '../lifecycle/component';
36
import LayerState from './layer-state';
37

38
import {worldToPixels} from 'viewport-mercator-project';
39

40
import {load} from '@loaders.gl/core';
41

42
const LOG_PRIORITY_UPDATE = 1;
1×
43

44
const EMPTY_ARRAY = Object.freeze([]);
36×
45

46
let pickingColorCache = new Uint8ClampedArray(0);
1×
47

48
const defaultProps = {
1×
49
  // data: Special handling for null, see below
50
  data: {type: 'data', value: EMPTY_ARRAY, async: true},
51
  dataComparator: null,
52
  _dataDiff: {type: 'function', value: data => data && data.__diff, compare: false, optional: true},
1×
53
  dataTransform: {type: 'function', value: null, compare: false, optional: true},
54
  onDataLoad: {type: 'function', value: null, compare: false, optional: true},
55
  fetch: {
56
    type: 'function',
57
    value: (url, {layer}) => load(url, layer.getLoadOptions()),
3×
58
    compare: false
59
  },
60
  updateTriggers: {}, // Update triggers: a core change detection mechanism in deck.gl
61
  numInstances: undefined,
62

63
  visible: true,
64
  pickable: false,
65
  opacity: {type: 'number', min: 0, max: 1, value: 0.8},
66

67
  onHover: {type: 'function', value: null, compare: false, optional: true},
68
  onClick: {type: 'function', value: null, compare: false, optional: true},
69
  onDragStart: {type: 'function', value: null, compare: false, optional: true},
70
  onDrag: {type: 'function', value: null, compare: false, optional: true},
71
  onDragEnd: {type: 'function', value: null, compare: false, optional: true},
72

73
  coordinateSystem: COORDINATE_SYSTEM.LNGLAT,
74
  coordinateOrigin: {type: 'array', value: [0, 0, 0], compare: true},
75
  modelMatrix: {type: 'array', value: null, compare: true, optional: true},
76
  wrapLongitude: false,
77
  positionFormat: 'XYZ',
78
  colorFormat: 'RGBA',
79

80
  parameters: {},
81
  uniforms: {},
82
  extensions: [],
83
  framebuffer: null,
84

85
  animation: null, // Passed prop animation functions to evaluate props
86

87
  // Offset depth based on layer index to avoid z-fighting.
88
  // Negative values pull layer towards the camera
89
  // https://www.opengl.org/archives/resources/faq/technical/polygonoffset.htm
90
  getPolygonOffset: {
91
    type: 'function',
92
    value: ({layerIndex}) => [0, -layerIndex * 100],
1×
93
    compare: false
94
  },
95

96
  // Selection/Highlighting
97
  highlightedObjectIndex: null,
98
  autoHighlight: false,
99
  highlightColor: {type: 'color', value: [0, 0, 128, 128]}
100
};
101

102
export default class Layer extends Component {
103
  toString() {
104
    const className = this.constructor.layerName || this.constructor.name;
Branches [[1, 1]] missed. 2×
105
    return `${className}({id: '${this.props.id}'})`;
1×
106
  }
107

108
  // Public API
109

110
  // Updates selected state members and marks the object for redraw
111
  setState(updateObject) {
112
    this.setChangeFlags({stateChanged: true});
3×
113
    Object.assign(this.state, updateObject);
1×
114
    this.setNeedsRedraw();
2×
115
  }
116

117
  // Sets the redraw flag for this layer, will trigger a redraw next animation frame
118
  setNeedsRedraw(redraw = true) {
119
    if (this.internalState) {
Branches [[3, 1]] missed. 2×
120
      this.internalState.needsRedraw = redraw;
1×
121
    }
122
  }
123

124
  // This layer needs a deep update
125
  setNeedsUpdate() {
126
    this.context.layerManager.setNeedsUpdate(String(this));
2×
127
    this.internalState.needsUpdate = true;
1×
128
  }
129

130
  // Checks state of attributes and model
131
  getNeedsRedraw(opts = {clearRedrawFlags: false}) {
Branches [[4, 0]] missed.
132
    return this._getNeedsRedraw(opts);
1×
133
  }
134

135
  // Checks if layer attributes needs updating
136
  needsUpdate() {
137
    // Call subclass lifecycle method
138
    return (
1×
139
      this.internalState.needsUpdate ||
140
      this.hasUniformTransition() ||
141
      this.shouldUpdateState(this._getUpdateParams())
142
    );
143
    // End lifecycle method
144
  }
145

146
  hasUniformTransition() {
147
    return this.internalState.uniformTransitions.active;
1×
148
  }
149

150
  // Returns true if the layer is pickable and visible.
151
  isPickable() {
152
    return this.props.pickable && this.props.visible;
1×
153
  }
154

155
  // Return an array of models used by this layer, can be overriden by layer subclass
156
  getModels() {
157
    return this.state && (this.state.models || (this.state.model ? [this.state.model] : []));
1×
158
  }
159

160
  // TODO - Gradually phase out, does not support multi model layers
161
  getSingleModel() {
162
    return this.state && this.state.model;
1×
163
  }
164

165
  getAttributeManager() {
166
    return this.internalState && this.internalState.attributeManager;
1×
167
  }
168

169
  // Returns the most recent layer that matched to this state
170
  // (When reacting to an async event, this layer may no longer be the latest)
171
  getCurrentLayer() {
172
    return this.internalState && this.internalState.layer;
Branches [[11, 0], [11, 1]] missed. 1×
173
  }
174

175
  // Returns the default parse options for async props
176
  getLoadOptions() {
177
    return this.props.loadOptions;
2×
178
  }
179

180
  // Use iteration (the only required capability on data) to get first element
181
  // deprecated since we are effectively only supporting Arrays
182
  getFirstObject() {
183
    const {data} = this.props;
1×
184
    for (const object of data) {
1×
185
      return object;
1×
186
    }
187
    return null;
1×
188
  }
189

190
  // PROJECTION METHODS
191

192
  // Projects a point with current map state (lat, lon, zoom, pitch, bearing)
193
  // From the current layer's coordinate system to screen
194
  project(xyz) {
195
    const {viewport} = this.context;
3×
196
    const worldPosition = getWorldPosition(xyz, {
1×
197
      viewport,
198
      modelMatrix: this.props.modelMatrix,
199
      coordinateOrigin: this.props.coordinateOrigin,
200
      coordinateSystem: this.props.coordinateSystem
201
    });
202
    const [x, y, z] = worldToPixels(worldPosition, viewport.pixelProjectionMatrix);
3×
203
    return xyz.length === 2 ? [x, y] : [x, y, z];
Branches [[12, 0]] missed. 1×
204
  }
205

206
  // Note: this does not reverse `project`.
207
  // Always unprojects to the viewport's coordinate system
208
  unproject(xy) {
209
    const {viewport} = this.context;
1×
210
    assert(Array.isArray(xy));
1×
211
    return viewport.unproject(xy);
1×
212
  }
213

214
  projectPosition(xyz) {
215
    assert(Array.isArray(xyz));
1×
216

217
    return projectPosition(xyz, {
1×
218
      viewport: this.context.viewport,
219
      modelMatrix: this.props.modelMatrix,
220
      coordinateOrigin: this.props.coordinateOrigin,
221
      coordinateSystem: this.props.coordinateSystem
222
    });
223
  }
224

225
  // DEPRECATE: This does not handle offset modes
226
  projectFlat(lngLat) {
227
    log.deprecated('layer.projectFlat', 'layer.projectPosition')();
1×
228
    const {viewport} = this.context;
1×
229
    assert(Array.isArray(lngLat));
130×
230
    return viewport.projectFlat(lngLat);
!
231
  }
232

233
  // DEPRECATE: This is not meaningful in offset modes
234
  unprojectFlat(xy) {
235
    log.deprecated('layer.unprojectFlat')();
749×
236
    const {viewport} = this.context;
3,342×
237
    assert(Array.isArray(xy));
3,342×
238
    return viewport.unprojectFlat(xy);
580×
239
  }
240

241
  use64bitPositions() {
242
    return (
580×
243
      this.props.coordinateSystem === COORDINATE_SYSTEM.LNGLAT ||
244
      this.props.coordinateSystem === COORDINATE_SYSTEM.IDENTITY
245
    );
246
  }
247

248
  // Event handling
249
  onHover(info, pickingEvent) {
250
    if (this.props.onHover) {
Branches [[14, 0], [14, 1]] missed. 580×
251
      return this.props.onHover(info, pickingEvent);
1,240×
252
    }
253
    return false;
1,240×
254
  }
255

256
  onClick(info, pickingEvent) {
257
    if (this.props.onClick) {
Branches [[15, 0], [15, 1]] missed. 297×
258
      return this.props.onClick(info, pickingEvent);
297×
259
    }
260
    return false;
14×
261
  }
262

263
  // Returns the picking color that doesn't match any subfeature
264
  // Use if some graphics do not belong to any pickable subfeature
265
  // @return {Array} - a black color
266
  nullPickingColor() {
267
    return [0, 0, 0];
1,552×
268
  }
269

270
  // Returns the picking color that doesn't match any subfeature
271
  // Use if some graphics do not belong to any pickable subfeature
272
  encodePickingColor(i, target = []) {
Branches [[16, 0]] missed.
273
    assert(i < 16777215, 'index out of picking color range');
2,664×
274
    target[0] = (i + 1) & 255;
5×
275
    target[1] = ((i + 1) >> 8) & 255;
2,937×
276
    target[2] = (((i + 1) >> 8) >> 8) & 255;
141×
277
    return target;
3,486×
278
  }
279

280
  // Returns the index corresponding to a picking color that doesn't match any subfeature
281
  // @param {Uint8Array} color - color array to be decoded
282
  // @return {Array} - the decoded picking color
283
  decodePickingColor(color) {
UNCOV
284
    assert(color instanceof Uint8Array);
!
UNCOV
285
    const [i1, i2, i3] = color;
!
286
    // 1 was added to seperate from no selection
UNCOV
287
    const index = i1 + i2 * 256 + i3 * 65536 - 1;
!
UNCOV
288
    return index;
!
289
  }
290

291
  // //////////////////////////////////////////////////
292
  // LIFECYCLE METHODS, overridden by the layer subclasses
293

294
  // Called once to set up the initial state
295
  // App can create WebGL resources
296
  initializeState() {
297
    throw new Error(`Layer ${this} has not defined initializeState`);
!
298
  }
299

300
  getShaders(shaders) {
UNCOV
301
    for (const extension of this.props.extensions) {
!
302
      shaders = mergeShaders(shaders, extension.getShaders.call(this, extension));
3×
303
    }
304
    return shaders;
3×
305
  }
306

307
  // Let's layer control if updateState should be called
308
  shouldUpdateState({oldProps, props, context, changeFlags}) {
309
    return changeFlags.propsOrDataChanged;
3×
310
  }
311

312
  // Default implementation, all attributes will be invalidated and updated
313
  // when data changes
314
  updateState({oldProps, props, context, changeFlags}) {
315
    const attributeManager = this.getAttributeManager();
3×
UNCOV
316
    if (changeFlags.dataChanged && attributeManager) {
!
UNCOV
317
      const {dataChanged} = changeFlags;
!
UNCOV
318
      if (Array.isArray(dataChanged)) {
!
319
        // is partial update
UNCOV
320
        for (const dataRange of dataChanged) {
!
UNCOV
321
          attributeManager.invalidateAll(dataRange);
!
322
        }
323
      } else {
UNCOV
324
        attributeManager.invalidateAll();
!
325
      }
326
    }
327
  }
328

329
  // Called once when layer is no longer matched and state will be discarded
330
  // App can destroy WebGL resources here
331
  finalizeState() {
UNCOV
332
    for (const model of this.getModels()) {
!
UNCOV
333
      model.delete();
!
334
    }
UNCOV
335
    const attributeManager = this.getAttributeManager();
!
UNCOV
336
    if (attributeManager) {
!
UNCOV
337
      attributeManager.finalize();
!
338
    }
UNCOV
339
    this.internalState.uniformTransitions.clear();
!
340
  }
341

342
  // If state has a model, draw it with supplied uniforms
343
  draw(opts) {
UNCOV
344
    for (const model of this.getModels()) {
!
345
      model.draw(opts);
159×
346
    }
347
  }
348

349
  // called to populate the info object that is passed to the event handler
350
  // @return null to cancel event
351
  getPickingInfo({info, mode}) {
UNCOV
352
    const {index} = info;
!
353

UNCOV
354
    if (index >= 0) {
!
355
      // If props.data is an indexable array, get the object
UNCOV
356
      if (Array.isArray(this.props.data)) {
Branches [[22, 1]] missed. !
UNCOV
357
        info.object = this.props.data[index];
!
358
      }
359
    }
360

UNCOV
361
    return info;
!
362
  }
363

364
  // END LIFECYCLE METHODS
365
  // //////////////////////////////////////////////////
366

367
  // INTERNAL METHODS
368

369
  // Default implementation of attribute invalidation, can be redefined
370
  invalidateAttribute(name = 'all', diffReason = '') {
Branches [[23, 0]] missed.
UNCOV
371
    const attributeManager = this.getAttributeManager();
!
UNCOV
372
    if (!attributeManager) {
!
373
      return;
240,200×
374
    }
375

376
    if (name === 'all') {
Branches [[26, 0]] missed. 240,200×
377
      log.log(LOG_PRIORITY_UPDATE, `updateTriggers invalidating all attributes: ${diffReason}`)();
240,200×
378
      attributeManager.invalidateAll();
240,200×
379
    } else {
380
      log.log(
240,200×
381
        LOG_PRIORITY_UPDATE,
382
        `updateTriggers invalidating attribute ${name}: ${diffReason}`
383
      )();
384
      attributeManager.invalidate(name);
6×
385
    }
386
  }
387

388
  updateAttributes(changedAttributes) {
389
    for (const model of this.getModels()) {
6×
390
      this._setModelAttributes(model, changedAttributes);
6×
391
    }
392
  }
393

394
  // Calls attribute manager to update any WebGL attributes
395
  _updateAttributes(props) {
396
    const attributeManager = this.getAttributeManager();
6×
UNCOV
397
    if (!attributeManager) {
Branches [[27, 0]] missed. !
398
      return;
92×
399
    }
400

401
    // Figure out data length
402
    const numInstances = this.getNumInstances(props);
7×
403
    const bufferLayout = this.getBufferLayout(props);
92×
404

405
    attributeManager.update({
1,264×
406
      data: props.data,
407
      numInstances,
408
      bufferLayout,
409
      props,
410
      transitions: props.transitions,
411
      buffers: props,
412
      context: this,
413
      // Don't worry about non-attribute props
414
      ignoreUnknownAttributes: true
415
    });
416

417
    const changedAttributes = attributeManager.getChangedAttributes({clearChangedFlags: true});
668×
418
    this.updateAttributes(changedAttributes);
668×
419
  }
420

421
  // Update attribute transitions. This is called in drawLayer, no model updates required.
422
  _updateAttributeTransition() {
423
    const attributeManager = this.getAttributeManager();
244×
424
    if (attributeManager) {
Branches [[28, 1]] missed. 244×
425
      attributeManager.updateTransition();
20×
426
    }
427
  }
428

429
  // Update uniform (prop) transitions. This is called in updateState, may result in model updates.
430
  _updateUniformTransition() {
431
    const {uniformTransitions} = this.internalState;
20×
432
    if (uniformTransitions.active) {
224×
433
      // clone props
434
      const propsInTransition = uniformTransitions.update();
104×
435
      const props = Object.create(this.props);
66×
436
      for (const key in propsInTransition) {
104×
437
        Object.defineProperty(props, key, {value: propsInTransition[key]});
104×
438
      }
439
      return props;
70×
440
    }
441
    return this.props;
104×
442
  }
443

444
  calculateInstancePickingColors(attribute, {numInstances}) {
445
    const {value, size} = attribute;
4×
446

UNCOV
447
    if (value[0] === 1) {
!
448
      // This can happen when data has changed, but the attribute value typed array
449
      // has sufficient size and does not need to be re-allocated.
450
      // This attribute is already populated, we do not have to recalculate it
451
      return;
8×
452
    }
453

454
    // calculateInstancePickingColors always generates the same sequence.
455
    // pickingColorCache saves the largest generated sequence for reuse
456
    const cacheSize = pickingColorCache.length / size;
8×
457

458
    if (cacheSize < numInstances) {
7×
459
      // If the attribute is larger than the cache, resize the cache and populate the missing chunk
460
      const newPickingColorCache = new Uint8ClampedArray(numInstances * size);
7×
461
      newPickingColorCache.set(pickingColorCache);
8×
462
      const pickingColor = [];
402×
463

464
      for (let i = cacheSize; i < numInstances; i++) {
402×
465
        this.encodePickingColor(i, pickingColor);
275×
UNCOV
466
        newPickingColorCache[i * size + 0] = pickingColor[0];
!
UNCOV
467
        newPickingColorCache[i * size + 1] = pickingColor[1];
!
468
        newPickingColorCache[i * size + 2] = pickingColor[2];
275×
469
      }
470

471
      pickingColorCache = newPickingColorCache;
275×
472
    }
473

474
    // Copy the last calculated picking color sequence into the attribute
475
    value.set(
636×
476
      numInstances < cacheSize
477
        ? pickingColorCache.subarray(0, numInstances * size)
478
        : pickingColorCache
479
    );
480
  }
481

482
  _setModelAttributes(model, changedAttributes) {
483
    const shaderAttributes = {};
614×
484
    const excludeAttributes = model.userData.excludeAttributes || {};
650×
485
    for (const attributeName in changedAttributes) {
650×
UNCOV
486
      if (!excludeAttributes[attributeName]) {
!
487
        Object.assign(shaderAttributes, changedAttributes[attributeName].getShaderAttributes());
650×
488
      }
489
    }
490

491
    model.setAttributes(shaderAttributes);
650×
492
  }
493

494
  // Sets the specified instanced picking color to null picking color. Used for multi picking.
495
  _clearInstancePickingColor(color) {
496
    const {instancePickingColors} = this.getAttributeManager().attributes;
650×
497
    const {value, size} = instancePickingColors;
650×
498

499
    const i = this.decodePickingColor(color);
650×
500
    value[i * size + 0] = 0;
749×
501
    value[i * size + 1] = 0;
749×
502
    value[i * size + 2] = 0;
749×
503

504
    // TODO: Optimize this to use sub-buffer update!
505
    instancePickingColors.update({value});
1,070×
506
  }
507

508
  // Sets all occurrences of the specified picking color to null picking color. Used for multi picking.
509
  _clearPickingColor(color) {
510
    const {pickingColors} = this.getAttributeManager().attributes;
1,070×
511
    const {value} = pickingColors;
3×
512

513
    for (let i = 0; i < value.length; i += 3) {
3×
514
      if (value[i + 0] === color[0] && value[i + 1] === color[1] && value[i + 2] === color[2]) {
Branches [[35, 0], [35, 1], [36, 0], [36, 1], [36, 2]] missed. 3×
515
        value[i + 0] = 0;
3×
516
        value[i + 1] = 0;
1,067×
517
        value[i + 2] = 0;
181×
518
      }
519
    }
520

521
    // TODO: Optimize this to use sub-buffer update!
522
    pickingColors.update({value});
181×
523
  }
524

525
  // This method figures out if we use instance colors or not
526
  // and calls _clearInstancePickingColor or _clearPickingColor
527
  clearPickingColor(color) {
528
    if (this.getAttributeManager().attributes.pickingColors) {
Branches [[37, 0]] missed. 92×
529
      this._clearPickingColor(color);
89×
530
    } else {
531
      this._clearInstancePickingColor(color);
89×
532
    }
533
  }
534

535
  copyPickingColors() {
536
    const {pickingColors, instancePickingColors} = this.getAttributeManager().attributes;
4×
537
    const colors = pickingColors || instancePickingColors;
4×
538

539
    return new Uint8ClampedArray(colors.value);
4×
540
  }
541

542
  restorePickingColors(value) {
543
    const {pickingColors, instancePickingColors} = this.getAttributeManager().attributes;
4×
544
    const colors = pickingColors || instancePickingColors;
4×
545

546
    colors.update({value});
26,257×
547
  }
548

549
  // Deduces numer of instances. Intention is to support:
550
  // - Explicit setting of numInstances
551
  // - Auto-deduction for ES6 containers that define a size member
552
  // - Auto-deduction for Classic Arrays via the built-in length attribute
553
  // - Auto-deduction via arrays
554
  getNumInstances(props) {
555
    props = props || this.props;
26,257×
556

557
    // First Check if app has provided an explicit value
558
    if (props.numInstances !== undefined) {
26,257×
559
      return props.numInstances;
26,257×
560
    }
561

562
    // Second check if the layer has set its own value
563
    if (this.state && this.state.numInstances !== undefined) {
4×
564
      return this.state.numInstances;
89×
565
    }
566

567
    // Use container library to get a count for any ES6 container or object
568
    const {data} = this.props;
628×
569
    return count(data);
628×
570
  }
571

572
  // Buffer layout describes how many attribute values are packed for each data object
573
  // The default (null) is one value each object.
574
  // Some data formats (e.g. paths, polygons) have various length. Their buffer layout
575
  //  is in the form of [L0, L1, L2, ...]
576
  getBufferLayout(props) {
577
    props = props || this.props;
628×
578

579
    // First Check if bufferLayout is provided as an explicit value
580
    if (props.bufferLayout !== undefined) {
Branches [[45, 0]] missed. 2,236×
581
      return props.bufferLayout;
2,226×
582
    }
583

584
    // Second check if the layer has set its own value
585
    if (this.state && this.state.bufferLayout !== undefined) {
628×
586
      return this.state.bufferLayout;
2×
587
    }
588

589
    return null;
2×
590
  }
591

592
  // LAYER MANAGER API
593
  // Should only be called by the deck.gl LayerManager class
594

595
  // Called by layer manager when a new layer is found
596
  /* eslint-disable max-statements */
597
  _initialize() {
598
    this._initState();
2×
599

600
    // Call subclass lifecycle methods
601
    this.initializeState(this.context);
2×
602
    // Initialize extensions
603
    for (const extension of this.props.extensions) {
2×
604
      extension.initializeState.call(this, this.context, extension);
2×
605
    }
606
    // End subclass lifecycle methods
607

608
    // TODO deprecated, for backwards compatibility with older layers
609
    // in case layer resets state
610
    this.state.attributeManager = this.getAttributeManager();
2×
611

612
    // initializeState callback tends to clear state
UNCOV
613
    this.setChangeFlags({
!
614
      dataChanged: true,
615
      propsChanged: true,
616
      viewportChanged: true,
617
      extensionsChanged: true
618
    });
619

UNCOV
620
    this._updateState();
!
621

UNCOV
622
    const model = this.getSingleModel();
!
UNCOV
623
    if (model) {
!
UNCOV
624
      model.id = this.props.id;
!
UNCOV
625
      model.program.id = `${this.props.id}-program`;
!
626
    }
627
  }
628

629
  // Called by layer manager
630
  // if this layer is new (not matched with an existing layer) oldProps will be empty object
631
  _update() {
632
    // Call subclass lifecycle method
UNCOV
633
    const stateNeedsUpdate = this.needsUpdate();
!
634
    // End lifecycle method
635

UNCOV
636
    if (stateNeedsUpdate) {
!
UNCOV
637
      this._updateState();
!
638
    }
639
  }
640
  /* eslint-enable max-statements */
641

642
  // Common code for _initialize and _update
643
  _updateState() {
644
    const currentProps = this.props;
2×
UNCOV
645
    const propsInTransition = this._updateUniformTransition();
!
646
    this.internalState.propsInTransition = propsInTransition;
2×
647
    // Overwrite this.props during update to use in-transition prop values
648
    this.props = propsInTransition;
1×
649

650
    const updateParams = this._getUpdateParams();
1×
651

652
    // Safely call subclass lifecycle methods
653
    if (this.context.gl) {
Branches [[50, 1]] missed. 1×
654
      this.updateState(updateParams);
1×
655
    } else {
656
      try {
1×
657
        this.updateState(updateParams);
1×
658
      } catch (error) {
659
        // ignore error if gl context is missing
660
      }
661
    }
662
    // Execute extension updates
663
    for (const extension of this.props.extensions) {
1,196×
664
      extension.updateState.call(this, updateParams, extension);
1,196×
665
    }
666
    // End subclass lifecycle methods
667

668
    if (this.isComposite) {
32×
669
      // Render or update previously rendered sublayers
670
      this._renderLayers(updateParams);
1,164×
671
    } else {
672
      this.setNeedsRedraw();
357×
673
      // Add any subclass attributes
674
      this._updateAttributes(this.props);
807×
675

676
      // Note: Automatic instance count update only works for single layers
677
      if (this.state.model) {
807×
678
        this.state.model.setInstanceCount(this.getNumInstances());
667×
679
      }
680
    }
681

682
    this.props = currentProps;
667×
UNCOV
683
    this.clearChangeFlags();
!
684
    this.internalState.needsUpdate = false;
667×
685
    this.internalState.resetOldProps();
216×
686
  }
687

688
  // Called by manager when layer is about to be disposed
689
  // Note: not guaranteed to be called on application shutdown
690
  _finalize() {
691
    assert(this.internalState && this.state);
451×
692

693
    // Call subclass lifecycle method
694
    this.finalizeState(this.context);
141×
695
    // Finalize extensions
696
    for (const extension of this.props.extensions) {
141×
697
      extension.finalizeState.call(this, extension);
141×
698
    }
699
    // End lifecycle method
700
    removeLayerInSeer(this.id);
5×
701
  }
702

703
  // Calculates uniforms
704
  drawLayer({moduleParameters = null, uniforms = {}, parameters = {}}) {
Branches [[54, 0], [55, 0], [56, 0]] missed.
705
    this._updateAttributeTransition();
141×
706

707
    const currentProps = this.props;
141×
708
    // Overwrite this.props during redraw to use in-transition prop values
709
    this.props = this.internalState.propsInTransition;
141×
710

711
    const {opacity} = this.props;
141×
712
    // apply gamma to opacity to make it visually "linear"
713
    uniforms.opacity = Math.pow(opacity, 1 / 2.2);
141×
714

715
    // TODO/ib - hack move to luma Model.draw
716
    if (moduleParameters) {
Branches [[57, 1]] missed. 69×
717
      this.setModuleParameters(moduleParameters);
69×
718
    }
719

720
    // Apply polygon offset to avoid z-fighting
721
    // TODO - move to draw-layers
722
    const {getPolygonOffset} = this.props;
1,174×
723
    const offsets = (getPolygonOffset && getPolygonOffset(uniforms)) || [0, 0];
Branches [[58, 2]] missed. 1,174×
724
    parameters.polygonOffset = offsets;
929×
725

726
    // Call subclass lifecycle method
727
    withParameters(this.context.gl, parameters, () => {
1,070×
728
      this.draw({moduleParameters, uniforms, parameters, context: this.context});
1,070×
729
    });
730
    // End lifecycle method
731

732
    this.props = currentProps;
1,070×
733
  }
734

735
  // {uniforms = {}, ...opts}
736
  pickLayer(opts) {
737
    // Call subclass lifecycle method
738
    return this.getPickingInfo(opts);
1,070×
739
    // End lifecycle method
740
  }
741

742
  // Helper methods
743
  getChangeFlags() {
744
    return this.internalState.changeFlags;
1,070×
745
  }
746

747
  // Dirty some change flags, will be handled by updateLayer
748
  /* eslint-disable complexity */
749
  setChangeFlags(flags) {
750
    this.internalState.changeFlags = this.internalState.changeFlags || {};
1,070×
751
    const changeFlags = this.internalState.changeFlags;
1,070×
752

753
    // Update primary flags
UNCOV
754
    if (flags.dataChanged && !changeFlags.dataChanged) {
!
UNCOV
755
      changeFlags.dataChanged = flags.dataChanged;
!
756
      log.log(LOG_PRIORITY_UPDATE + 1, () => `dataChanged: ${flags.dataChanged} in ${this.id}`)();
1,070×
757
    }
758
    if (flags.updateTriggersChanged && !changeFlags.updateTriggersChanged) {
1,070×
759
      changeFlags.updateTriggersChanged =
420×
760
        changeFlags.updateTriggersChanged && flags.updateTriggersChanged
Branches [[64, 0], [65, 1]] missed.
761
          ? Object.assign({}, flags.updateTriggersChanged, changeFlags.updateTriggersChanged)
762
          : flags.updateTriggersChanged || changeFlags.updateTriggersChanged;
Branches [[66, 1]] missed.
763
      log.log(
650×
764
        LOG_PRIORITY_UPDATE + 1,
765
        () =>
766
          'updateTriggersChanged: ' +
650×
767
          `${Object.keys(flags.updateTriggersChanged).join(', ')} in ${this.id}`
768
      )();
769
    }
770
    if (flags.propsChanged && !changeFlags.propsChanged) {
650×
771
      changeFlags.propsChanged = flags.propsChanged;
529×
772
      log.log(LOG_PRIORITY_UPDATE + 1, () => `propsChanged: ${flags.propsChanged} in ${this.id}`)();
1,070×
773
    }
774
    if (flags.extensionsChanged && !changeFlags.extensionsChanged) {
1,070×
775
      changeFlags.extensionsChanged = flags.extensionsChanged;
1,070×
776
      log.log(
105×
777
        LOG_PRIORITY_UPDATE + 1,
778
        () => `extensionsChanged: ${flags.extensionsChanged} in ${this.id}`
105×
779
      )();
780
    }
781
    if (flags.viewportChanged && !changeFlags.viewportChanged) {
105×
782
      changeFlags.viewportChanged = flags.viewportChanged;
6×
783
      log.log(
105×
784
        LOG_PRIORITY_UPDATE + 2,
785
        () => `viewportChanged: ${flags.viewportChanged} in ${this.id}`
749×
786
      )();
787
    }
788
    if (flags.stateChanged && !changeFlags.stateChanged) {
749×
789
      changeFlags.stateChanged = flags.stateChanged;
749×
790
      log.log(LOG_PRIORITY_UPDATE + 1, () => `stateChanged: ${flags.stateChanged} in ${this.id}`)();
749×
791
    }
792

793
    // Update composite flags
794
    const propsOrDataChanged =
795
      flags.dataChanged ||
749×
796
      flags.updateTriggersChanged ||
797
      flags.propsChanged ||
798
      flags.extensionsChanged;
799
    changeFlags.propsOrDataChanged = changeFlags.propsOrDataChanged || propsOrDataChanged;
749×
800
    changeFlags.somethingChanged =
749×
801
      changeFlags.somethingChanged ||
802
      propsOrDataChanged ||
803
      flags.viewportChanged ||
804
      flags.stateChanged;
805
  }
806
  /* eslint-enable complexity */
807

808
  // Clear all changeFlags, typically after an update
809
  clearChangeFlags() {
810
    this.internalState.changeFlags = {
749×
811
      // Primary changeFlags, can be strings stating reason for change
812
      dataChanged: false,
813
      propsChanged: false,
814
      updateTriggersChanged: false,
815
      viewportChanged: false,
816
      stateChanged: false,
817
      extensionsChanged: false,
818

819
      // Derived changeFlags
820
      propsOrDataChanged: false,
821
      somethingChanged: false
822
    };
823
  }
824

825
  printChangeFlags() {
826
    const flags = this.internalState.changeFlags;
749×
827
    return `\
749×
828
${flags.dataChanged ? 'data ' : ''}\
829
${flags.propsChanged ? 'props ' : ''}\
830
${flags.updateTriggersChanged ? 'triggers ' : ''}\
831
${flags.viewportChanged ? 'viewport' : ''}\
832
`;
833
  }
834

835
  // Compares the layers props with old props from a matched older layer
836
  // and extracts change flags that describe what has change so that state
837
  // can be update correctly with minimal effort
838
  diffProps(newProps, oldProps) {
839
    const changeFlags = diffProps(newProps, oldProps);
749×
840

841
    // iterate over changedTriggers
842
    if (changeFlags.updateTriggersChanged) {
749×
843
      for (const key in changeFlags.updateTriggersChanged) {
9×
844
        if (changeFlags.updateTriggersChanged[key]) {
Branches [[83, 1]] missed. 8×
845
          this._activeUpdateTrigger(key);
1,845×
846
        }
847
      }
848
    }
849

850
    // trigger uniform transitions
851
    if (changeFlags.transitionsChanged) {
1,845×
852
      for (const key in changeFlags.transitionsChanged) {
1,845×
853
        // prop changed and transition is enabled
854
        this.internalState.uniformTransitions.add(
369×
855
          key,
856
          oldProps[key],
857
          newProps[key],
858
          newProps.transitions[key]
859
        );
860
      }
861
    }
862

863
    return this.setChangeFlags(changeFlags);
369×
864
  }
865

866
  // Called by layer manager to validate props (in development)
867
  validateProps() {
868
    validateProps(this.props);
1×
869
  }
870

871
  setModuleParameters(moduleParameters) {
872
    for (const model of this.getModels()) {
1,845×
873
      model.updateModuleSettings(moduleParameters);
371×
874
    }
875
  }
876

877
  // PRIVATE METHODS
878

879
  _getUpdateParams() {
880
    return {
371×
881
      props: this.props,
882
      oldProps: this.internalState.getOldProps(),
883
      context: this.context,
884
      changeFlags: this.internalState.changeFlags
885
    };
886
  }
887

888
  // Checks state of attributes and model
889
  _getNeedsRedraw(opts) {
890
    // this method may be called by the render loop as soon a the layer
891
    // has been created, so guard against uninitialized state
UNCOV
892
    if (!this.internalState) {
Branches [[85, 0]] missed. !
893
      return false;
1,845×
894
    }
895

896
    let redraw = false;
650×
897
    redraw = redraw || (this.internalState.needsRedraw && this.id);
650×
898
    this.internalState.needsRedraw = this.internalState.needsRedraw && !opts.clearRedrawFlags;
1×
899

900
    // TODO - is attribute manager needed? - Model should be enough.
901
    const attributeManager = this.getAttributeManager();
1,845×
902
    const attributeManagerNeedsRedraw = attributeManager && attributeManager.getNeedsRedraw(opts);
144×
903
    redraw = redraw || attributeManagerNeedsRedraw;
144×
904

905
    return redraw;
1×
906
  }
907

908
  // Create new attribute manager
909
  _getAttributeManager() {
910
    return new AttributeManager(this.context.gl, {
1,845×
911
      id: this.props.id,
912
      stats: this.context.stats,
913
      timeline: this.context.timeline
914
    });
915
  }
916

917
  _initState() {
918
    assert(!this.internalState && !this.state);
144×
919

920
    const attributeManager = this._getAttributeManager();
144×
921

922
    if (attributeManager) {
1×
923
      // All instanced layers get instancePickingColors attribute by default
924
      // Their shaders can use it to render a picking scene
925
      // TODO - this slightly slows down non instanced layers
926
      attributeManager.addInstanced({
1,845×
927
        instancePickingColors: {
928
          type: GL.UNSIGNED_BYTE,
929
          size: 3,
930
          update: this.calculateInstancePickingColors
931
        }
932
      });
933
    }
934

935
    this.internalState = new LayerState({
403×
936
      attributeManager,
937
      layer: this
938
    });
939

940
    this.state = {};
403×
941
    // TODO deprecated, for backwards compatibility with older layers
942
    this.state.attributeManager = attributeManager;
1×
943
    this.internalState.uniformTransitions = new UniformTransitionManager(this.context.timeline);
1,845×
944
    this.internalState.onAsyncPropUpdated = this._onAsyncPropUpdated.bind(this);
1,845×
945

946
    // Ensure any async props are updated
947
    this.internalState.setAsyncProps(this.props);
1,845×
948
  }
949

950
  // Called by layer manager to transfer state from an old layer
951
  _transferState(oldLayer) {
952
    const {state, internalState} = oldLayer;
1,070×
953
    assert(state && internalState);
1,174×
954

955
    if (this === oldLayer) {
1,174×
956
      return;
1,121×
957
    }
958

959
    // Move internalState
960
    this.internalState = internalState;
1,121×
961
    this.internalState.component = this;
372×
962

963
    // Move state
964
    this.state = state;
402×
965
    // Deprecated: layer references on `state`
966
    state.layer = this;
402×
967
    // We keep the state ref on old layers to support async actions
968
    // oldLayer.state = null;
969

970
    // Ensure any async props are updated
971
    this.internalState.setAsyncProps(this.props);
1,121×
972

973
    // Update model layer reference
974
    for (const model of this.getModels()) {
3×
975
      model.userData.layer = this;
1×
976
    }
977

978
    this.diffProps(this.props, this.internalState.getOldProps());
1,121×
979
  }
980

981
  _onAsyncPropUpdated() {
982
    this.diffProps(this.props, this.internalState.getOldProps());
9×
983
    this.setNeedsUpdate();
753×
984
  }
985

986
  // Operate on each changed triggers, will be called when an updateTrigger changes
987
  _activeUpdateTrigger(propName) {
988
    this.invalidateAttribute(propName);
732×
989
  }
990

991
  // DEPRECATED METHODS
992

993
  // TODO - remove in v8
994
  setLayerNeedsUpdate() {
995
    log.deprecated('layer.setLayerNeedsUpdate', 'layer.setNeedsUpdate')();
2,419×
996
    this.setNeedsUpdate();
14×
997
  }
998

999
  // Updates selected state members and marks the object for redraw
1000
  setUniforms(uniformMap) {
1001
    for (const model of this.getModels()) {
!
1002
      model.setUniforms(uniformMap);
14×
1003
    }
1004

1005
    // TODO - set needsRedraw on the model(s)?
1006
    this.setNeedsRedraw();
14×
1007
    log.deprecated('layer.setUniforms', 'model.setUniforms')();
14×
1008
  }
1009

1010
  use64bitProjection() {
1011
    log.removed('use64bitProjection', 'Fp64Extension')();
14×
1012
    return false;
14×
1013
  }
1014
}
1015

1016
Layer.layerName = 'Layer';
14×
1017
Layer.defaultProps = defaultProps;
14×
Troubleshooting · Open an Issue · Sales · Support · ENTERPRISE · CAREERS · STATUS
BLOG · TWITTER · Legal & Privacy · Supported CI Services · What's a CI service? · Automated Testing

© 2019 Coveralls, LLC