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

uber / deck.gl / 13779

18 Sep 2019 - 0:00 coverage decreased (-2.9%) to 79.902%
13779

Pull #3623

travis-ci-com

9181eb84f9c35729a3bad740fb7f9d93?size=18&default=identiconweb-flow
beta.2
Pull Request #3623: Bump dependency versions

3405 of 4619 branches covered (73.72%)

Branch coverage included in aggregate %.

7031 of 8442 relevant lines covered (83.29%)

5687.45 hits per line

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

83.47
/modules/core/src/lib/layer-manager.js
1
// Copyright (c) 2015 - 2017 Uber Technologies, Inc.
22×
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
import assert from '../utils/assert';
22
import {Timeline} from '@luma.gl/addons';
23
import seer from 'seer';
24
import Layer from './layer';
25
import {LIFECYCLE} from '../lifecycle/constants';
26
import log from '../utils/log';
27
import {flatten} from '../utils/flatten';
28
import {Stats} from 'probe.gl';
29

30
import Viewport from '../viewports/viewport';
31
import {createProgramManager} from '../shaderlib';
32

33
import {
34
  setPropOverrides,
35
  layerEditListener,
36
  seerInitListener,
37
  initLayerInSeer,
38
  updateLayerInSeer
39
} from './seer-integration';
40

41
const LOG_PRIORITY_LIFECYCLE = 2;
1×
42
const LOG_PRIORITY_LIFECYCLE_MINOR = 4;
1×
43

44
// CONTEXT IS EXPOSED TO LAYERS
45
const INITIAL_CONTEXT = Object.seal({
1×
46
  layerManager: null,
47
  deck: null,
48
  gl: null,
49

50
  // General resources
51
  stats: null, // for tracking lifecycle performance
52

53
  // GL Resources
54
  shaderCache: null,
55
  pickingFBO: null, // Screen-size framebuffer that layers can reuse
56

57
  mousePosition: null,
58

59
  userData: {} // Place for any custom app `context`
60
});
61

62
const layerName = layer => (layer instanceof Layer ? `${layer}` : !layer ? 'null' : 'invalid');
Branches [[0, 1], [1, 0], [1, 1]] missed. 1,451×
63

64
export default class LayerManager {
65
  // eslint-disable-next-line
66
  constructor(gl, {deck, stats, viewport = null, timeline = null} = {}) {
67
    // Currently deck.gl expects the DeckGL.layers array to be different
68
    // whenever React rerenders. If the same layers array is used, the
69
    // LayerManager's diffing algorithm will generate a fatal error and
70
    // break the rendering.
71

72
    // `this.lastRenderedLayers` stores the UNFILTERED layers sent
73
    // down to LayerManager, so that `layers` reference can be compared.
74
    // If it's the same across two React render calls, the diffing logic
75
    // will be skipped.
76
    this.lastRenderedLayers = [];
83×
77
    this.layers = [];
83×
78

79
    this.context = Object.assign({}, INITIAL_CONTEXT, {
83×
80
      layerManager: this,
81
      deck,
82
      gl,
83
      // Enabling luma.gl Program caching using private API (_cachePrograms)
84
      programManager: gl && createProgramManager(gl),
85
      stats: stats || new Stats({id: 'deck.gl'}),
86
      // Make sure context.viewport is not empty on the first layer initialization
87
      viewport: viewport || new Viewport({id: 'DEFAULT-INITIAL-VIEWPORT'}), // Current viewport, exposed to layers for project* function
88
      timeline: timeline || new Timeline()
89
    });
90

91
    this._needsRedraw = 'Initial render';
83×
92
    this._needsUpdate = false;
83×
93
    this._debug = false;
83×
94

95
    this.activateViewport = this.activateViewport.bind(this);
83×
96

97
    // Seer integration
98
    this._initSeer = this._initSeer.bind(this);
83×
99
    this._editSeer = this._editSeer.bind(this);
83×
100

101
    Object.seal(this);
83×
102

103
    seerInitListener(this._initSeer);
83×
104
    layerEditListener(this._editSeer);
83×
105
  }
106

107
  // Method to call when the layer manager is not needed anymore.
108
  // Currently used in the <DeckGL> componentWillUnmount lifecycle to unbind Seer listeners.
109
  finalize() {
110
    // Finalize all layers
111
    for (const layer of this.layers) {
13×
112
      this._finalizeLayer(layer);
12×
113
    }
114

115
    seer.removeListener(this._initSeer);
13×
116
    seer.removeListener(this._editSeer);
13×
117
  }
118

119
  // Check if a redraw is needed
120
  needsRedraw(opts = {clearRedrawFlags: false}) {
Branches [[9, 0]] missed.
121
    return this._checkIfNeedsRedraw(opts);
14×
122
  }
123

124
  // Check if a deep update of all layers is needed
125
  needsUpdate() {
126
    return this._needsUpdate;
16×
127
  }
128

129
  // Layers will be redrawn (in next animation frame)
130
  setNeedsRedraw(reason) {
131
    this._needsRedraw = this._needsRedraw || reason;
106×
132
  }
133

134
  // Layers will be updated deeply (in next animation frame)
135
  // Potentially regenerating attributes and sub layers
136
  setNeedsUpdate(reason) {
137
    this._needsUpdate = this._needsUpdate || reason;
297×
138
  }
139

140
  // Gets an (optionally) filtered list of layers
141
  getLayers({layerIds = null} = {}) {
142
    // Filtering by layerId compares beginning of strings, so that sublayers will be included
143
    // Dependes on the convention of adding suffixes to the parent's layer name
144
    return layerIds
612×
145
      ? this.layers.filter(layer => layerIds.find(layerId => layer.id.indexOf(layerId) === 0))
8×
146
      : this.layers;
147
  }
148

149
  // Set props needed for layer rendering and picking.
150
  setProps(props) {
151
    if ('debug' in props) {
Branches [[15, 1]] missed. 10×
152
      this._debug = props.debug;
10×
153
    }
154

155
    // A way for apps to add data to context that can be accessed in layers
156
    if ('userData' in props) {
10×
157
      this.context.userData = props.userData;
2×
158
    }
159

160
    // TODO - For now we set layers before viewports to preserve changeFlags
161
    if ('layers' in props) {
Branches [[17, 1]] missed. 10×
162
      this.setLayers(props.layers);
10×
163
    }
164
  }
165

166
  // Supply a new layer list, initiating sublayer generation and layer matching
167
  setLayers(newLayers, forceUpdate = false) {
168
    // TODO - something is generating state updates that cause rerender of the same
169
    if (!forceUpdate && newLayers === this.lastRenderedLayers) {
728×
170
      log.log(3, 'Ignoring layer update due to layer array not changed')();
1×
171
      return this;
1×
172
    }
173
    this.lastRenderedLayers = newLayers;
727×
174

175
    newLayers = flatten(newLayers, {filter: Boolean});
727×
176

177
    for (const layer of newLayers) {
727×
178
      layer.context = this.context;
681×
179
    }
180

181
    const {error, generatedLayers} = this._updateLayers({
727×
182
      oldLayers: this.layers,
183
      newLayers
184
    });
185

186
    this.layers = generatedLayers;
727×
187

188
    // Throw first error found, if any
189
    if (error) {
Branches [[21, 0]] missed. 727×
UNCOV
190
      throw error;
!
191
    }
192
    return this;
727×
193
  }
194

195
  // Update layers from last cycle if `setNeedsUpdate()` has been called
196
  updateLayers() {
197
    // NOTE: For now, even if only some layer has changed, we update all layers
198
    // to ensure that layer id maps etc remain consistent even if different
199
    // sublayers are rendered
200
    const reason = this.needsUpdate();
16×
201
    if (reason) {
16×
202
      this.setNeedsRedraw(`updating layers: ${reason}`);
3×
203
      // Force a full update
204
      const forceUpdate = true;
3×
205
      this.setLayers(this.lastRenderedLayers, forceUpdate);
3×
206
    }
207
  }
208

209
  //
210
  // PRIVATE METHODS
211
  //
212

213
  _checkIfNeedsRedraw(opts) {
214
    let redraw = this._needsRedraw;
14×
215
    if (opts.clearRedrawFlags) {
Branches [[23, 1]] missed. 14×
216
      this._needsRedraw = false;
14×
217
    }
218

219
    // This layers list doesn't include sublayers, relying on composite layers
220
    for (const layer of this.layers) {
14×
221
      // Call every layer to clear their flags
222
      const layerNeedsRedraw = layer.getNeedsRedraw(opts);
14×
223
      redraw = redraw || layerNeedsRedraw;
14×
224
    }
225

226
    return redraw;
14×
227
  }
228

229
  // Make a viewport "current" in layer context, updating viewportChanged flags
230
  activateViewport(viewport) {
231
    const oldViewport = this.context.viewport;
612×
232
    const viewportChanged = !oldViewport || !viewport.equals(oldViewport);
612×
233

234
    if (viewportChanged) {
612×
235
      log.log(4, 'Viewport changed', viewport)();
1×
236

237
      this.context.viewport = viewport;
1×
238

239
      // Update layers states
240
      // Let screen space layers update their state based on viewport
241
      for (const layer of this.layers) {
1×
242
        layer.setChangeFlags({viewportChanged: 'Viewport changed'});
3×
243
        this._updateLayer(layer);
3×
244
      }
245
    }
246

247
    assert(this.context.viewport, 'LayerManager: viewport not set');
612×
248

249
    return this;
612×
250
  }
251

252
  // Match all layers, checking for caught errors
253
  // To avoid having an exception in one layer disrupt other layers
254
  // TODO - mark layers with exceptions as bad and remove from rendering cycle?
255
  _updateLayers({oldLayers, newLayers}) {
256
    // Create old layer map
257
    const oldLayerMap = {};
727×
258
    for (const oldLayer of oldLayers) {
727×
259
      if (oldLayerMap[oldLayer.id]) {
Branches [[27, 0]] missed. 1,262×
UNCOV
260
        log.warn(`Multiple old layers with same id ${layerName(oldLayer)}`)();
!
261
      } else {
262
        oldLayerMap[oldLayer.id] = oldLayer;
1,262×
263
      }
264
    }
265

266
    // Allocate array for generated layers
267
    const generatedLayers = [];
727×
268

269
    // Match sublayers
270
    const error = this._updateSublayersRecursively({
727×
271
      newLayers,
272
      oldLayerMap,
273
      generatedLayers
274
    });
275

276
    // Finalize unmatched layers
277
    const error2 = this._finalizeOldLayers(oldLayerMap);
727×
278

279
    this._needsUpdate = generatedLayers.some(layer => layer.hasUniformTransition());
1,312×
280

281
    const firstError = error || error2;
727×
282
    return {error: firstError, generatedLayers};
727×
283
  }
284

285
  /* eslint-disable complexity,max-statements */
286
  // Note: adds generated layers to `generatedLayers` array parameter
287
  _updateSublayersRecursively({newLayers, oldLayerMap, generatedLayers}) {
288
    let error = null;
1,226×
289

290
    for (const newLayer of newLayers) {
1,226×
291
      newLayer.context = this.context;
1,312×
292

293
      // Given a new coming layer, find its matching old layer (if any)
294
      const oldLayer = oldLayerMap[newLayer.id];
1,312×
295
      if (oldLayer === null) {
Branches [[29, 0]] missed. 1,312×
296
        // null, rather than undefined, means this id was originally there
UNCOV
297
        log.warn(`Multiple new layers with same id ${layerName(newLayer)}`)();
!
298
      }
299
      // Remove the old layer from candidates, as it has been matched with this layer
300
      oldLayerMap[newLayer.id] = null;
1,312×
301

302
      let sublayers = null;
1,312×
303

304
      // We must not generate exceptions until after layer matching is complete
305
      try {
1,312×
306
        if (this._debug && oldLayer !== newLayer) {
Branches [[30, 0], [31, 1]] missed. 1,312×
UNCOV
307
          newLayer.validateProps();
!
308
        }
309

310
        if (!oldLayer) {
1,312×
311
          const err = this._initializeLayer(newLayer);
141×
312
          error = error || err;
141×
313
          initLayerInSeer(newLayer); // Initializes layer in seer chrome extension (if connected)
141×
314
        } else {
315
          this._transferLayerState(oldLayer, newLayer);
1,171×
316
          const err = this._updateLayer(newLayer);
1,171×
317
          error = error || err;
1,171×
318
          updateLayerInSeer(newLayer); // Updates layer in seer chrome extension (if connected)
1,171×
319
        }
320
        generatedLayers.push(newLayer);
1,312×
321

322
        // Call layer lifecycle method: render sublayers
323
        sublayers = newLayer.isComposite && newLayer.getSubLayers();
1,312×
324
        // End layer lifecycle method: render sublayers
325
      } catch (err) {
UNCOV
326
        log.warn(`error during matching of ${layerName(newLayer)}`, err)();
!
UNCOV
327
        error = error || err; // Record first exception
Branches [[36, 0], [36, 1]] missed. !
328
      }
329

330
      if (sublayers) {
1,312×
331
        const err = this._updateSublayersRecursively({
499×
332
          newLayers: sublayers,
333
          oldLayerMap,
334
          generatedLayers
335
        });
336
        error = error || err;
499×
337
      }
338
    }
339

340
    return error;
1,226×
341
  }
342
  /* eslint-enable complexity,max-statements */
343

344
  // Finalize any old layers that were not matched
345
  _finalizeOldLayers(oldLayerMap) {
346
    let error = null;
727×
347
    for (const layerId in oldLayerMap) {
727×
348
      const layer = oldLayerMap[layerId];
1,403×
349
      if (layer) {
1,403×
350
        error = error || this._finalizeLayer(layer);
91×
351
      }
352
    }
353
    return error;
727×
354
  }
355

356
  // EXCEPTION SAFE LAYER ACCESS
357

358
  // Initializes a single layer, calling layer methods
359
  _initializeLayer(layer) {
360
    log.log(LOG_PRIORITY_LIFECYCLE, `initializing ${layerName(layer)}`)();
141×
361

362
    let error = null;
141×
363
    try {
141×
364
      layer._initialize();
141×
365
      layer.lifecycle = LIFECYCLE.INITIALIZED;
141×
366
    } catch (err) {
367
      log.warn(`error while initializing ${layerName(layer)}\n`, err)();
!
UNCOV
368
      error = error || err;
Branches [[41, 0], [41, 1]] missed. !
369
      // TODO - what should the lifecycle state be here? LIFECYCLE.INITIALIZATION_FAILED?
370
    }
371

372
    // Set back pointer (used in picking)
373
    layer.internalState.layer = layer;
141×
374

375
    // Save layer on model for picking purposes
376
    // store on model.userData rather than directly on model
377
    for (const model of layer.getModels()) {
141×
378
      model.userData.layer = layer;
82×
379
    }
380

381
    return error;
141×
382
  }
383

384
  _transferLayerState(oldLayer, newLayer) {
385
    newLayer._transferState(oldLayer);
1,171×
386
    newLayer.lifecycle = LIFECYCLE.MATCHED;
1,171×
387

388
    if (newLayer !== oldLayer) {
1,171×
389
      log.log(
1,104×
390
        LOG_PRIORITY_LIFECYCLE_MINOR,
391
        `matched ${layerName(newLayer)}`,
392
        oldLayer,
393
        '->',
394
        newLayer
395
      )();
396
      oldLayer.lifecycle = LIFECYCLE.AWAITING_GC;
1,104×
397
    } else {
398
      log.log(LOG_PRIORITY_LIFECYCLE_MINOR, `Matching layer is unchanged ${newLayer.id}`)();
67×
399
    }
400
  }
401

402
  // Updates a single layer, cleaning all flags
403
  _updateLayer(layer) {
404
    log.log(
1,174×
405
      LOG_PRIORITY_LIFECYCLE_MINOR,
406
      `updating ${layer} because: ${layer.printChangeFlags()}`
407
    )();
408
    let error = null;
1,174×
409
    try {
1,174×
410
      layer._update();
1,174×
411
    } catch (err) {
UNCOV
412
      log.warn(`error during update of ${layerName(layer)}`, err)();
!
413
      // Save first error
UNCOV
414
      error = err;
!
415
    }
416
    return error;
1,174×
417
  }
418

419
  // Finalizes a single layer
420
  _finalizeLayer(layer) {
421
    assert(layer.lifecycle !== LIFECYCLE.AWAITING_FINALIZATION);
103×
422
    layer.lifecycle = LIFECYCLE.AWAITING_FINALIZATION;
103×
423
    let error = null;
103×
424
    this.setNeedsRedraw(`finalized ${layerName(layer)}`);
103×
425
    try {
103×
426
      layer._finalize();
103×
427
    } catch (err) {
UNCOV
428
      log.warn(`error during finalization of ${layerName(layer)}`, err)();
!
UNCOV
429
      error = err;
!
430
    }
431
    layer.lifecycle = LIFECYCLE.FINALIZED;
103×
432
    log.log(LOG_PRIORITY_LIFECYCLE, `finalizing ${layerName(layer)}`)();
103×
433
    return error;
103×
434
  }
435

436
  // SEER INTEGRATION
437

438
  /**
439
   * Called upon Seer initialization, manually sends layers data.
440
   */
441
  _initSeer() {
UNCOV
442
    this.layers.forEach(layer => {
!
UNCOV
443
      initLayerInSeer(layer);
!
UNCOV
444
      updateLayerInSeer(layer);
!
445
    });
446
  }
447

448
  /**
449
   * On Seer property edition, set override and update layers.
450
   */
451
  _editSeer(payload) {
UNCOV
452
    if (payload.type !== 'edit' || payload.valuePath[0] !== 'props') {
Branches [[43, 0], [43, 1], [44, 0], [44, 1]] missed. !
UNCOV
453
      return;
!
454
    }
455

UNCOV
456
    setPropOverrides(payload.itemKey, payload.valuePath.slice(1), payload.value);
!
UNCOV
457
    this.updateLayers();
!
458
  }
459
}
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