Skip to content

Commit 3f340b9

Browse files
committed
feature: allow geometry layer to implement partial update
Before this commit there was no link between an update starting and the cause of this update being fired. Now the source of notifyChange() calls are stored, and the layer.preUpdate() method receive them as an argument. This way preUpdate can choose a better starting point from the update. As an example I've modified GlobeView/PlanarView to use this feature: updates now start at the common ancestor (if any) of all the notifyChanges() sources.
1 parent 2166f0d commit 3f340b9

6 files changed

Lines changed: 137 additions & 70 deletions

File tree

src/Core/MainLoop.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ function MainLoop(scheduler, engine) {
88
this.needsRedraw = false;
99
this.scheduler = scheduler;
1010
this.gfxEngine = engine; // TODO: remove me
11-
12-
this._viewsToUpdate = new Set();
1311
}
1412

1513
MainLoop.prototype = Object.create(EventDispatcher.prototype);
@@ -54,7 +52,7 @@ MainLoop.prototype._update = function _update(view) {
5452

5553
for (const geometryLayer of view.getLayers((x, y) => !y)) {
5654
context.geometryLayer = geometryLayer;
57-
const elementsToUpdate = geometryLayer.preUpdate(context, geometryLayer);
55+
const elementsToUpdate = geometryLayer.preUpdate(context, geometryLayer, view._changeSources);
5856
updateElements(context, geometryLayer, elementsToUpdate);
5957
}
6058
};
@@ -81,6 +79,7 @@ MainLoop.prototype._step = function _step(view) {
8179
document.title = document.title.substr(0, document.title.length - 2);
8280
}
8381
this.renderingState = RENDERING_PAUSED;
82+
view._changeSources.clear();
8483
};
8584

8685
/**

src/Core/Prefab/GlobeView.js

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,58 @@ function GlobeView(viewerDiv, coordCarto) {
5858
};
5959
const SSE_SUBDIVISION_THRESHOLD = 1.0;
6060

61+
function _commonAncestorLookup(a, b) {
62+
if (!a || !b) {
63+
return undefined;
64+
}
65+
if (a.level == b.level) {
66+
if (a.id == b.id) {
67+
return a;
68+
} else if (a.level != 0) {
69+
return _commonAncestorLookup(a.parent, b.parent);
70+
} else {
71+
return undefined;
72+
}
73+
} else if (a.level < b.level) {
74+
return _commonAncestorLookup(a, b.parent);
75+
} else {
76+
return _commonAncestorLookup(a.parent, b);
77+
}
78+
}
79+
6180
const wgs84TileLayer = new GeometryLayer('globe');
6281
const initLayer = initTiledGeometryLayer(globeSchemeTileWMTS(globeSchemeTile1));
63-
wgs84TileLayer.preUpdate = (context, layer) => {
82+
wgs84TileLayer.preUpdate = (context, layer, changeSources) => {
83+
this._latestUpdateStartingLevel = 0;
6484
if (layer.level0Nodes === undefined) {
6585
initLayer(context, layer);
6686
}
6787
preGlobeUpdate(context);
68-
return layer.level0Nodes;
88+
if (changeSources.has(undefined) || changeSources.size == 0) {
89+
return layer.level0Nodes;
90+
}
91+
let commonAncestor;
92+
for (const source of changeSources.values()) {
93+
if (!commonAncestor) {
94+
commonAncestor = source;
95+
} else {
96+
commonAncestor = _commonAncestorLookup(commonAncestor, source);
97+
if (!commonAncestor) {
98+
return layer.level0Nodes;
99+
}
100+
}
101+
if (commonAncestor.material == null) {
102+
commonAncestor = undefined;
103+
}
104+
}
105+
if (commonAncestor) {
106+
this._latestUpdateStartingLevel = commonAncestor.level;
107+
return [commonAncestor];
108+
} else {
109+
return [];
110+
}
69111
};
112+
70113
wgs84TileLayer.update =
71114
processTiledGeometryNode(
72115
globeCulling,

src/Core/Prefab/PlanarView.js

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,56 @@ function PlanarView(viewerDiv, boundingbox) {
5656
const tileLayer = new GeometryLayer('planar');
5757
const initLayer = initTiledGeometryLayer(planarSchemeTile(boundingbox));
5858

59-
tileLayer.preUpdate = (context, layer) => {
59+
function _commonAncestorLookup(a, b) {
60+
if (!a || !b) {
61+
return undefined;
62+
}
63+
if (a.level == b.level) {
64+
if (a.id == b.id) {
65+
return a;
66+
} else if (a.level != 0) {
67+
return _commonAncestorLookup(a.parent, b.parent);
68+
} else {
69+
return undefined;
70+
}
71+
} else if (a.level < b.level) {
72+
return _commonAncestorLookup(a, b.parent);
73+
} else {
74+
return _commonAncestorLookup(a.parent, b);
75+
}
76+
}
77+
78+
tileLayer.preUpdate = (context, layer, changeSources) => {
79+
this._latestUpdateStartingLevel = 0;
6080
if (layer.level0Nodes === undefined) {
6181
initLayer(context, layer);
6282
}
63-
return layer.level0Nodes;
83+
84+
if (changeSources.has(undefined) || changeSources.size == 0) {
85+
return layer.level0Nodes;
86+
}
87+
let commonAncestor;
88+
for (const source of changeSources.values()) {
89+
if (!commonAncestor) {
90+
commonAncestor = source;
91+
} else {
92+
commonAncestor = _commonAncestorLookup(commonAncestor, source);
93+
if (!commonAncestor) {
94+
return layer.level0Nodes;
95+
}
96+
}
97+
if (commonAncestor.material == null) {
98+
commonAncestor = undefined;
99+
}
100+
}
101+
if (commonAncestor) {
102+
this._latestUpdateStartingLevel = commonAncestor.level;
103+
return [commonAncestor];
104+
} else {
105+
return [];
106+
}
64107
};
108+
65109
tileLayer.update =
66110
processTiledGeometryNode(
67111
planarCulling,

src/Core/Scheduler/Scheduler.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ Scheduler.prototype.runCommand = function runCommand(command, queue, executingCo
9898
// We allow the view to delay the update/repaint up to 100ms
9999
// to reduce CPU load (no need to perform an update on completion if we
100100
// know there's another one ending soon)
101-
command.view.notifyChange(100, 'redraw' in command ? command.redraw : true);
101+
command.view.notifyChange(100, 'redraw' in command ? command.redraw : true, command.requester);
102102

103103
// try to execute next command
104104
if (queue.counters.executing < this.maxCommandsPerHost) {

src/Core/View.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ function View(crs, viewerDiv, mainLoop) {
5151
}, false);
5252

5353
this.onAfterRender = () => {};
54+
55+
this._changeSources = new Set();
5456
}
5557

5658
function _preprocessLayer(view, layer, provider) {
@@ -108,10 +110,14 @@ View.prototype.addLayer = function addLayer(layer, parentLayer) {
108110
* non-interactive events (e.g: texture loaded)
109111
* needsRedraw param indicates if notified change requires a full scene redraw.
110112
*/
111-
View.prototype.notifyChange = function notifyChange(delay, needsRedraw) {
113+
View.prototype.notifyChange = function notifyChange(delay, needsRedraw, changeSource) {
112114
if (delay) {
113-
window.setTimeout(() => { this.mainLoop.scheduleViewUpdate(this, needsRedraw); }, delay);
115+
window.setTimeout(() => {
116+
this._changeSources.add(changeSource);
117+
this.mainLoop.scheduleViewUpdate(this, needsRedraw);
118+
}, delay);
114119
} else {
120+
this._changeSources.add(changeSource);
115121
this.mainLoop.scheduleViewUpdate(this, needsRedraw);
116122
}
117123
};

utils/debug/Debug.js

Lines changed: 35 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ function Debug(view, viewerDiv) {
3131
chartDiv.appendChild(rightChart);
3232

3333
// line graph for nb elements
34-
const nbObjectsCanvas = document.createElement('canvas');
35-
nbObjectsCanvas.heigth = '20rem';
36-
nbObjectsCanvas.width = '50vw';
37-
nbObjectsCanvas.id = 'nb-objects';
38-
leftChart.appendChild(nbObjectsCanvas);
34+
const viewChartCanvas = document.createElement('canvas');
35+
viewChartCanvas.heigth = '20rem';
36+
viewChartCanvas.width = '50vw';
37+
viewChartCanvas.id = 'nb-objects';
38+
leftChart.appendChild(viewChartCanvas);
3939

4040
// bar graph for nb visible elements
4141
const nbVisibleCanvas = document.createElement('canvas');
@@ -45,15 +45,14 @@ function Debug(view, viewerDiv) {
4545
rightChart.appendChild(nbVisibleCanvas);
4646

4747
const timestamp = Date.now();
48-
const nbObjectsDataset = { label: 'Number of objects in Scene', data: [{ x: 0, y: 0 }] };
49-
const nbVisibleDataset = { label: 'Number of visible objects in Scene', data: [{ x: 0, y: 0 }], borderColor: 'rgba(75,192,192,1)' };
50-
const nbDisplayedDataset = { label: 'Number of displayed objects in Scene', data: [{ x: 0, y: 0 }], borderColor: 'rgba(153, 102, 255, 1)' };
51-
const nbObjectsChartLabel = ['0s'];
48+
const viewLevelStartDataset = { label: 'Update 1st level', data: [{ x: 0, y: 0 }] };
49+
const viewUpdateDurationDataset = { label: 'Update duration (ms)', data: [{ x: 0, y: 0 }], borderColor: 'rgba(75,192,192,1)' };
50+
const viewInfoChartLabel = ['0s'];
5251
const nbObjectsChart = new Chart('nb-objects', {
5352
type: 'line',
5453
data: {
55-
labels: nbObjectsChartLabel,
56-
datasets: [nbObjectsDataset, nbVisibleDataset, nbDisplayedDataset],
54+
labels: viewInfoChartLabel,
55+
datasets: [viewLevelStartDataset, viewUpdateDurationDataset],
5756
},
5857
options: {
5958
animation: { duration: 10 },
@@ -99,20 +98,7 @@ function Debug(view, viewerDiv) {
9998
},
10099
});
101100

102-
function debugChartUpdate() {
103-
function countElem(node) {
104-
if (!node) {
105-
return 0;
106-
}
107-
let count = 1; // this node
108-
if (node.children) {
109-
for (const child of node.children) {
110-
count += countElem(child);
111-
}
112-
}
113-
return count;
114-
}
115-
101+
function debugChartUpdate(updateStartLevel, updateDuration) {
116102
function countVisible(node, stats) {
117103
if (!node || !node.visible) {
118104
return;
@@ -137,54 +123,40 @@ function Debug(view, viewerDiv) {
137123
// update bar graph
138124
const stats = {};
139125
countVisible(view.mainLoop.gfxEngine.scene3D, stats);
140-
let totalVisible = 0;
141-
let totalDisplayed = 0;
142126
nbVisibleLabels.length = 0;
143127
nbVisibleData.length = 0;
144128
for (const level in stats) {
145129
if ({}.hasOwnProperty.call(stats, level)) {
146130
nbVisibleLabels[level - 1] = `${level}`;
147131
nbVisibleData[level - 1] = stats[level][0];
148132
nbDisplayedData[level - 1] = stats[level][1];
149-
totalVisible += stats[level][0];
150-
totalDisplayed += stats[level][1];
151133
}
152134
}
153135

154136

155137
// update line graph
156-
const newCount = countElem(view.mainLoop.gfxEngine.scene3D);
157-
158-
// test if we values didn't change
159-
if (nbObjectsDataset.data.length > 1) {
160-
const last = nbObjectsDataset.data.length - 1;
161-
if (nbObjectsDataset.data[last].y === newCount &&
162-
nbVisibleDataset.data[last].y === totalVisible &&
163-
nbDisplayedDataset.data[last].y === totalDisplayed) {
164-
// nothing change: drop the last point, to keep more interesting (changing)
165-
// data displayed
166-
nbObjectsDataset.data.pop();
167-
nbVisibleDataset.data.pop();
168-
nbDisplayedDataset.data.pop();
169-
nbObjectsChartLabel.pop();
170-
}
171-
}
172-
173138
// update time
174-
const limit = 25;
139+
const limit = 60;
175140
const timeInS = Math.floor((Date.now() - timestamp) / 1000);
176-
nbObjectsChartLabel.push(`${timeInS}s`);
177-
if (nbObjectsChartLabel.length > limit) {
178-
nbObjectsChartLabel.shift();
141+
const lbl = `${timeInS}s`;
142+
const identical = (viewInfoChartLabel.lastValidCompareIndex > 0 && viewInfoChartLabel[viewInfoChartLabel.lastValidCompareIndex] == lbl);
143+
if (identical) {
144+
viewInfoChartLabel.push('');
145+
} else {
146+
viewInfoChartLabel.push(lbl);
147+
viewInfoChartLabel.lastValidCompareIndex = viewInfoChartLabel.length - 1;
179148
}
180149

181-
nbObjectsDataset.data.push({ x: timeInS, y: newCount });
182-
nbVisibleDataset.data.push({ x: timeInS, y: totalVisible });
183-
nbDisplayedDataset.data.push({ x: timeInS, y: totalDisplayed });
184-
if (nbObjectsDataset.data.length > limit) {
185-
nbObjectsDataset.data.shift();
186-
nbVisibleDataset.data.shift();
187-
nbDisplayedDataset.data.shift();
150+
if (viewInfoChartLabel.length > limit) {
151+
viewInfoChartLabel.shift();
152+
viewInfoChartLabel.lastValidCompareIndex--;
153+
}
154+
155+
viewLevelStartDataset.data.push({ x: timeInS, y: updateStartLevel });
156+
viewUpdateDurationDataset.data.push({ x: timeInS, y: updateDuration });
157+
if (viewLevelStartDataset.data.length > limit) {
158+
viewLevelStartDataset.data.shift();
159+
viewUpdateDurationDataset.data.shift();
188160
}
189161

190162
if (chartDiv.style.display != 'none') {
@@ -280,11 +252,14 @@ function Debug(view, viewerDiv) {
280252
// hook that to scene.update
281253
const ml = view.mainLoop;
282254
const oldUpdate = Object.getPrototypeOf(ml)._update.bind(ml);
283-
ml._update = function debugUpdate(view) {
255+
ml._update = function debugUpdate(view, ...args) {
284256
// regular itowns update
285-
oldUpdate(view);
257+
const before = Date.now();
258+
oldUpdate(view, ...args);
259+
const duration = Date.now() - before;
286260
// debug graphs update
287-
debugChartUpdate();
261+
debugChartUpdate(view._latestUpdateStartingLevel, duration);
262+
288263
// obb layer update
289264
for (const gLayer of view._layers) {
290265
const obbLayerAlreadyAdded =

0 commit comments

Comments
 (0)