Skip to content

Commit

Permalink
feature: allow geometry layer to implement partial update
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
peppsac committed May 18, 2017
1 parent 2166f0d commit 3f340b9
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 70 deletions.
5 changes: 2 additions & 3 deletions src/Core/MainLoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ function MainLoop(scheduler, engine) {
this.needsRedraw = false;
this.scheduler = scheduler;
this.gfxEngine = engine; // TODO: remove me

this._viewsToUpdate = new Set();
}

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

for (const geometryLayer of view.getLayers((x, y) => !y)) {
context.geometryLayer = geometryLayer;
const elementsToUpdate = geometryLayer.preUpdate(context, geometryLayer);
const elementsToUpdate = geometryLayer.preUpdate(context, geometryLayer, view._changeSources);
updateElements(context, geometryLayer, elementsToUpdate);
}
};
Expand All @@ -81,6 +79,7 @@ MainLoop.prototype._step = function _step(view) {
document.title = document.title.substr(0, document.title.length - 2);
}
this.renderingState = RENDERING_PAUSED;
view._changeSources.clear();
};

/**
Expand Down
47 changes: 45 additions & 2 deletions src/Core/Prefab/GlobeView.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,58 @@ function GlobeView(viewerDiv, coordCarto) {
};
const SSE_SUBDIVISION_THRESHOLD = 1.0;

function _commonAncestorLookup(a, b) {
if (!a || !b) {
return undefined;
}
if (a.level == b.level) {
if (a.id == b.id) {
return a;
} else if (a.level != 0) {
return _commonAncestorLookup(a.parent, b.parent);
} else {
return undefined;
}
} else if (a.level < b.level) {
return _commonAncestorLookup(a, b.parent);
} else {
return _commonAncestorLookup(a.parent, b);
}
}

const wgs84TileLayer = new GeometryLayer('globe');
const initLayer = initTiledGeometryLayer(globeSchemeTileWMTS(globeSchemeTile1));
wgs84TileLayer.preUpdate = (context, layer) => {
wgs84TileLayer.preUpdate = (context, layer, changeSources) => {
this._latestUpdateStartingLevel = 0;
if (layer.level0Nodes === undefined) {
initLayer(context, layer);
}
preGlobeUpdate(context);
return layer.level0Nodes;
if (changeSources.has(undefined) || changeSources.size == 0) {
return layer.level0Nodes;
}
let commonAncestor;
for (const source of changeSources.values()) {
if (!commonAncestor) {
commonAncestor = source;
} else {
commonAncestor = _commonAncestorLookup(commonAncestor, source);
if (!commonAncestor) {
return layer.level0Nodes;
}
}
if (commonAncestor.material == null) {
commonAncestor = undefined;
}
}
if (commonAncestor) {
this._latestUpdateStartingLevel = commonAncestor.level;
return [commonAncestor];
} else {
return [];
}
};

wgs84TileLayer.update =
processTiledGeometryNode(
globeCulling,
Expand Down
48 changes: 46 additions & 2 deletions src/Core/Prefab/PlanarView.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,56 @@ function PlanarView(viewerDiv, boundingbox) {
const tileLayer = new GeometryLayer('planar');
const initLayer = initTiledGeometryLayer(planarSchemeTile(boundingbox));

tileLayer.preUpdate = (context, layer) => {
function _commonAncestorLookup(a, b) {
if (!a || !b) {
return undefined;
}
if (a.level == b.level) {
if (a.id == b.id) {
return a;
} else if (a.level != 0) {
return _commonAncestorLookup(a.parent, b.parent);
} else {
return undefined;
}
} else if (a.level < b.level) {
return _commonAncestorLookup(a, b.parent);
} else {
return _commonAncestorLookup(a.parent, b);
}
}

tileLayer.preUpdate = (context, layer, changeSources) => {
this._latestUpdateStartingLevel = 0;
if (layer.level0Nodes === undefined) {
initLayer(context, layer);
}
return layer.level0Nodes;

if (changeSources.has(undefined) || changeSources.size == 0) {
return layer.level0Nodes;
}
let commonAncestor;
for (const source of changeSources.values()) {
if (!commonAncestor) {
commonAncestor = source;
} else {
commonAncestor = _commonAncestorLookup(commonAncestor, source);
if (!commonAncestor) {
return layer.level0Nodes;
}
}
if (commonAncestor.material == null) {
commonAncestor = undefined;
}
}
if (commonAncestor) {
this._latestUpdateStartingLevel = commonAncestor.level;
return [commonAncestor];
} else {
return [];
}
};

tileLayer.update =
processTiledGeometryNode(
planarCulling,
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Scheduler/Scheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Scheduler.prototype.runCommand = function runCommand(command, queue, executingCo
// We allow the view to delay the update/repaint up to 100ms
// to reduce CPU load (no need to perform an update on completion if we
// know there's another one ending soon)
command.view.notifyChange(100, 'redraw' in command ? command.redraw : true);
command.view.notifyChange(100, 'redraw' in command ? command.redraw : true, command.requester);

// try to execute next command
if (queue.counters.executing < this.maxCommandsPerHost) {
Expand Down
10 changes: 8 additions & 2 deletions src/Core/View.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ function View(crs, viewerDiv, mainLoop) {
}, false);

this.onAfterRender = () => {};

this._changeSources = new Set();
}

function _preprocessLayer(view, layer, provider) {
Expand Down Expand Up @@ -108,10 +110,14 @@ View.prototype.addLayer = function addLayer(layer, parentLayer) {
* non-interactive events (e.g: texture loaded)
* needsRedraw param indicates if notified change requires a full scene redraw.
*/
View.prototype.notifyChange = function notifyChange(delay, needsRedraw) {
View.prototype.notifyChange = function notifyChange(delay, needsRedraw, changeSource) {
if (delay) {
window.setTimeout(() => { this.mainLoop.scheduleViewUpdate(this, needsRedraw); }, delay);
window.setTimeout(() => {
this._changeSources.add(changeSource);
this.mainLoop.scheduleViewUpdate(this, needsRedraw);
}, delay);
} else {
this._changeSources.add(changeSource);
this.mainLoop.scheduleViewUpdate(this, needsRedraw);
}
};
Expand Down
95 changes: 35 additions & 60 deletions utils/debug/Debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ function Debug(view, viewerDiv) {
chartDiv.appendChild(rightChart);

// line graph for nb elements
const nbObjectsCanvas = document.createElement('canvas');
nbObjectsCanvas.heigth = '20rem';
nbObjectsCanvas.width = '50vw';
nbObjectsCanvas.id = 'nb-objects';
leftChart.appendChild(nbObjectsCanvas);
const viewChartCanvas = document.createElement('canvas');
viewChartCanvas.heigth = '20rem';
viewChartCanvas.width = '50vw';
viewChartCanvas.id = 'nb-objects';
leftChart.appendChild(viewChartCanvas);

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

const timestamp = Date.now();
const nbObjectsDataset = { label: 'Number of objects in Scene', data: [{ x: 0, y: 0 }] };
const nbVisibleDataset = { label: 'Number of visible objects in Scene', data: [{ x: 0, y: 0 }], borderColor: 'rgba(75,192,192,1)' };
const nbDisplayedDataset = { label: 'Number of displayed objects in Scene', data: [{ x: 0, y: 0 }], borderColor: 'rgba(153, 102, 255, 1)' };
const nbObjectsChartLabel = ['0s'];
const viewLevelStartDataset = { label: 'Update 1st level', data: [{ x: 0, y: 0 }] };
const viewUpdateDurationDataset = { label: 'Update duration (ms)', data: [{ x: 0, y: 0 }], borderColor: 'rgba(75,192,192,1)' };
const viewInfoChartLabel = ['0s'];
const nbObjectsChart = new Chart('nb-objects', {
type: 'line',
data: {
labels: nbObjectsChartLabel,
datasets: [nbObjectsDataset, nbVisibleDataset, nbDisplayedDataset],
labels: viewInfoChartLabel,
datasets: [viewLevelStartDataset, viewUpdateDurationDataset],
},
options: {
animation: { duration: 10 },
Expand Down Expand Up @@ -99,20 +98,7 @@ function Debug(view, viewerDiv) {
},
});

function debugChartUpdate() {
function countElem(node) {
if (!node) {
return 0;
}
let count = 1; // this node
if (node.children) {
for (const child of node.children) {
count += countElem(child);
}
}
return count;
}

function debugChartUpdate(updateStartLevel, updateDuration) {
function countVisible(node, stats) {
if (!node || !node.visible) {
return;
Expand All @@ -137,54 +123,40 @@ function Debug(view, viewerDiv) {
// update bar graph
const stats = {};
countVisible(view.mainLoop.gfxEngine.scene3D, stats);
let totalVisible = 0;
let totalDisplayed = 0;
nbVisibleLabels.length = 0;
nbVisibleData.length = 0;
for (const level in stats) {
if ({}.hasOwnProperty.call(stats, level)) {
nbVisibleLabels[level - 1] = `${level}`;
nbVisibleData[level - 1] = stats[level][0];
nbDisplayedData[level - 1] = stats[level][1];
totalVisible += stats[level][0];
totalDisplayed += stats[level][1];
}
}


// update line graph
const newCount = countElem(view.mainLoop.gfxEngine.scene3D);

// test if we values didn't change
if (nbObjectsDataset.data.length > 1) {
const last = nbObjectsDataset.data.length - 1;
if (nbObjectsDataset.data[last].y === newCount &&
nbVisibleDataset.data[last].y === totalVisible &&
nbDisplayedDataset.data[last].y === totalDisplayed) {
// nothing change: drop the last point, to keep more interesting (changing)
// data displayed
nbObjectsDataset.data.pop();
nbVisibleDataset.data.pop();
nbDisplayedDataset.data.pop();
nbObjectsChartLabel.pop();
}
}

// update time
const limit = 25;
const limit = 60;
const timeInS = Math.floor((Date.now() - timestamp) / 1000);
nbObjectsChartLabel.push(`${timeInS}s`);
if (nbObjectsChartLabel.length > limit) {
nbObjectsChartLabel.shift();
const lbl = `${timeInS}s`;
const identical = (viewInfoChartLabel.lastValidCompareIndex > 0 && viewInfoChartLabel[viewInfoChartLabel.lastValidCompareIndex] == lbl);
if (identical) {
viewInfoChartLabel.push('');
} else {
viewInfoChartLabel.push(lbl);
viewInfoChartLabel.lastValidCompareIndex = viewInfoChartLabel.length - 1;
}

nbObjectsDataset.data.push({ x: timeInS, y: newCount });
nbVisibleDataset.data.push({ x: timeInS, y: totalVisible });
nbDisplayedDataset.data.push({ x: timeInS, y: totalDisplayed });
if (nbObjectsDataset.data.length > limit) {
nbObjectsDataset.data.shift();
nbVisibleDataset.data.shift();
nbDisplayedDataset.data.shift();
if (viewInfoChartLabel.length > limit) {
viewInfoChartLabel.shift();
viewInfoChartLabel.lastValidCompareIndex--;
}

viewLevelStartDataset.data.push({ x: timeInS, y: updateStartLevel });
viewUpdateDurationDataset.data.push({ x: timeInS, y: updateDuration });
if (viewLevelStartDataset.data.length > limit) {
viewLevelStartDataset.data.shift();
viewUpdateDurationDataset.data.shift();
}

if (chartDiv.style.display != 'none') {
Expand Down Expand Up @@ -280,11 +252,14 @@ function Debug(view, viewerDiv) {
// hook that to scene.update
const ml = view.mainLoop;
const oldUpdate = Object.getPrototypeOf(ml)._update.bind(ml);
ml._update = function debugUpdate(view) {
ml._update = function debugUpdate(view, ...args) {
// regular itowns update
oldUpdate(view);
const before = Date.now();
oldUpdate(view, ...args);
const duration = Date.now() - before;
// debug graphs update
debugChartUpdate();
debugChartUpdate(view._latestUpdateStartingLevel, duration);

// obb layer update
for (const gLayer of view._layers) {
const obbLayerAlreadyAdded =
Expand Down

0 comments on commit 3f340b9

Please sign in to comment.