Skip to content

Commit

Permalink
refactor(MainLoop): use class for MainLoop.
Browse files Browse the repository at this point in the history
  • Loading branch information
gchoqueux committed Mar 15, 2022
1 parent 164b6ee commit 25a48fd
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 120 deletions.
236 changes: 119 additions & 117 deletions src/Core/MainLoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,6 @@ export const MAIN_LOOP_EVENTS = {
UPDATE_END: 'update_end',
};

function MainLoop(scheduler, engine) {
this.renderingState = RENDERING_PAUSED;
this.needsRedraw = false;
this.scheduler = scheduler;
this.gfxEngine = engine; // TODO: remove me
this._updateLoopRestarted = true;
}

MainLoop.prototype = Object.create(EventDispatcher.prototype);
MainLoop.prototype.constructor = MainLoop;

MainLoop.prototype.scheduleViewUpdate = function scheduleViewUpdate(view, forceRedraw) {
this.needsRedraw |= forceRedraw;

if (this.renderingState !== RENDERING_SCHEDULED) {
this.renderingState = RENDERING_SCHEDULED;

if (__DEBUG__) {
document.title += ' ⌛';
}

requestAnimationFrame((timestamp) => { this._step(view, timestamp); });
}
};

function updateElements(context, geometryLayer, elements) {
if (!elements) {
return;
Expand Down Expand Up @@ -116,124 +91,151 @@ function filterChangeSources(updateSources, geometryLayer) {
return fullUpdate ? new Set([geometryLayer]) : filtered;
}

MainLoop.prototype._update = function _update(view, updateSources, dt) {
const context = {
camera: view.camera,
engine: this.gfxEngine,
scheduler: this.scheduler,
view,
};

// replace layer with their parent where needed
updateSources.forEach((src) => {
const layer = src.layer || src;
if (layer.isLayer && layer.parent) {
updateSources.add(layer.parent);
class MainLoop extends EventDispatcher {
#needsRedraw = false;
#updateLoopRestarted = true;
#lastTimestamp = 0;
constructor(scheduler, engine) {
super();
this.renderingState = RENDERING_PAUSED;
this.scheduler = scheduler;
this.gfxEngine = engine; // TODO: remove me
}

scheduleViewUpdate(view, forceRedraw) {
this.#needsRedraw |= forceRedraw;

if (this.renderingState !== RENDERING_SCHEDULED) {
this.renderingState = RENDERING_SCHEDULED;

if (__DEBUG__) {
document.title += ' ⌛';
}

requestAnimationFrame((timestamp) => { this.#step(view, timestamp); });
}
});
}

#update(view, updateSources, dt) {
const context = {
camera: view.camera,
engine: this.gfxEngine,
scheduler: this.scheduler,
view,
};

// replace layer with their parent where needed
updateSources.forEach((src) => {
const layer = src.layer || src;
if (layer.isLayer && layer.parent) {
updateSources.add(layer.parent);
}
});

for (const geometryLayer of view.getLayers((x, y) => !y)) {
context.geometryLayer = geometryLayer;
if (geometryLayer.ready && geometryLayer.visible && !geometryLayer.frozen) {
view.execFrameRequesters(MAIN_LOOP_EVENTS.BEFORE_LAYER_UPDATE, dt, this._updateLoopRestarted, geometryLayer);
for (const geometryLayer of view.getLayers((x, y) => !y)) {
context.geometryLayer = geometryLayer;
if (geometryLayer.ready && geometryLayer.visible && !geometryLayer.frozen) {
view.execFrameRequesters(MAIN_LOOP_EVENTS.BEFORE_LAYER_UPDATE, dt, this.#updateLoopRestarted, geometryLayer);

// Filter updateSources that are relevant for the geometryLayer
const srcs = filterChangeSources(updateSources, geometryLayer);
if (srcs.size > 0) {
// pre update attached layer
for (const attachedLayer of geometryLayer.attachedLayers) {
if (attachedLayer.ready && attachedLayer.preUpdate) {
attachedLayer.preUpdate(context, srcs);
// Filter updateSources that are relevant for the geometryLayer
const srcs = filterChangeSources(updateSources, geometryLayer);
if (srcs.size > 0) {
// pre update attached layer
for (const attachedLayer of geometryLayer.attachedLayers) {
if (attachedLayer.ready && attachedLayer.preUpdate) {
attachedLayer.preUpdate(context, srcs);
}
}
// `preUpdate` returns an array of elements to update
const elementsToUpdate = geometryLayer.preUpdate(context, srcs);
// `update` is called in `updateElements`.
updateElements(context, geometryLayer, elementsToUpdate);
// `postUpdate` is called when this geom layer update process is finished
geometryLayer.postUpdate(context, geometryLayer, updateSources);
}
// `preUpdate` returns an array of elements to update
const elementsToUpdate = geometryLayer.preUpdate(context, srcs);
// `update` is called in `updateElements`.
updateElements(context, geometryLayer, elementsToUpdate);
// `postUpdate` is called when this geom layer update process is finished
geometryLayer.postUpdate(context, geometryLayer, updateSources);
}

// Clear the cache of expired resources
geometryLayer.cache.flush();
// Clear the cache of expired resources
geometryLayer.cache.flush();

view.execFrameRequesters(MAIN_LOOP_EVENTS.AFTER_LAYER_UPDATE, dt, this._updateLoopRestarted, geometryLayer);
view.execFrameRequesters(MAIN_LOOP_EVENTS.AFTER_LAYER_UPDATE, dt, this.#updateLoopRestarted, geometryLayer);
}
}
}
};

MainLoop.prototype._step = function _step(view, timestamp) {
const dt = timestamp - this._lastTimestamp;
view._executeFrameRequestersRemovals();
#step(view, timestamp) {
const dt = timestamp - this.#lastTimestamp;
view._executeFrameRequestersRemovals();

view.execFrameRequesters(MAIN_LOOP_EVENTS.UPDATE_START, dt, this._updateLoopRestarted);
view.execFrameRequesters(MAIN_LOOP_EVENTS.UPDATE_START, dt, this.#updateLoopRestarted);

const willRedraw = this.needsRedraw;
this._lastTimestamp = timestamp;
const willRedraw = this.#needsRedraw;
this.#lastTimestamp = timestamp;

// Reset internal state before calling _update (so future calls to View.notifyChange()
// can properly change it)
this.needsRedraw = false;
this.renderingState = RENDERING_PAUSED;
const updateSources = new Set(view._changeSources);
view._changeSources.clear();
// Reset internal state before calling _update (so future calls to View.notifyChange()
// can properly change it)
this.#needsRedraw = false;
this.renderingState = RENDERING_PAUSED;
const updateSources = new Set(view._changeSources);
view._changeSources.clear();

// update camera
const dim = this.gfxEngine.getWindowSize();
// update camera
const dim = this.gfxEngine.getWindowSize();

view.execFrameRequesters(MAIN_LOOP_EVENTS.BEFORE_CAMERA_UPDATE, dt, this._updateLoopRestarted);
view.camera.update(dim.x, dim.y);
view.execFrameRequesters(MAIN_LOOP_EVENTS.AFTER_CAMERA_UPDATE, dt, this._updateLoopRestarted);
view.execFrameRequesters(MAIN_LOOP_EVENTS.BEFORE_CAMERA_UPDATE, dt, this.#updateLoopRestarted);
view.camera.update(dim.x, dim.y);
view.execFrameRequesters(MAIN_LOOP_EVENTS.AFTER_CAMERA_UPDATE, dt, this.#updateLoopRestarted);

// Disable camera's matrix auto update to make sure the camera's
// world matrix is never updated mid-update.
// Otherwise inconsistencies can appear because object visibility
// testing and object drawing could be performed using different
// camera matrixWorld.
// Note: this is required at least because WEBGLRenderer calls
// camera.updateMatrixWorld()
const oldAutoUpdate = view.camera.camera3D.matrixAutoUpdate;
view.camera.camera3D.matrixAutoUpdate = false;
// Disable camera's matrix auto update to make sure the camera's
// world matrix is never updated mid-update.
// Otherwise inconsistencies can appear because object visibility
// testing and object drawing could be performed using different
// camera matrixWorld.
// Note: this is required at least because WEBGLRenderer calls
// camera.updateMatrixWorld()
const oldAutoUpdate = view.camera.camera3D.matrixAutoUpdate;
view.camera.camera3D.matrixAutoUpdate = false;

// update data-structure
this._update(view, updateSources, dt);
// update data-structure
this.#update(view, updateSources, dt);

if (this.scheduler.commandsWaitingExecutionCount() == 0) {
this.dispatchEvent({ type: 'command-queue-empty' });
}
if (this.scheduler.commandsWaitingExecutionCount() == 0) {
this.dispatchEvent({ type: 'command-queue-empty' });
}

// Redraw *only* if needed.
// (redraws only happen when this.needsRedraw is true, which in turn only happens when
// view.notifyChange() is called with redraw=true)
// As such there's no continuous update-loop, instead we use a ad-hoc update/render
// mechanism.
if (willRedraw) {
this._renderView(view, dt);
}
// Redraw *only* if needed.
// (redraws only happen when this.#needsRedraw is true, which in turn only happens when
// view.notifyChange() is called with redraw=true)
// As such there's no continuous update-loop, instead we use a ad-hoc update/render
// mechanism.
if (willRedraw) {
this.#renderView(view, dt);
}

// next time, we'll consider that we've just started the loop if we are still PAUSED now
this._updateLoopRestarted = this.renderingState === RENDERING_PAUSED;
// next time, we'll consider that we've just started the loop if we are still PAUSED now
this.#updateLoopRestarted = this.renderingState === RENDERING_PAUSED;

if (__DEBUG__) {
document.title = document.title.substr(0, document.title.length - 2);
}
if (__DEBUG__) {
document.title = document.title.substr(0, document.title.length - 2);
}

view.camera.camera3D.matrixAutoUpdate = oldAutoUpdate;
view.camera.camera3D.matrixAutoUpdate = oldAutoUpdate;

view.execFrameRequesters(MAIN_LOOP_EVENTS.UPDATE_END, dt, this._updateLoopRestarted);
};
view.execFrameRequesters(MAIN_LOOP_EVENTS.UPDATE_END, dt, this.#updateLoopRestarted);
}

MainLoop.prototype._renderView = function _renderView(view, dt) {
view.execFrameRequesters(MAIN_LOOP_EVENTS.BEFORE_RENDER, dt, this._updateLoopRestarted);
#renderView(view, dt) {
view.execFrameRequesters(MAIN_LOOP_EVENTS.BEFORE_RENDER, dt, this.#updateLoopRestarted);

if (view.render) {
view.render();
} else {
// use default rendering method
this.gfxEngine.renderView(view);
}
if (view.render) {
view.render();
} else {
// use default rendering method
this.gfxEngine.renderView(view);
}

view.execFrameRequesters(MAIN_LOOP_EVENTS.AFTER_RENDER, dt, this._updateLoopRestarted);
};
view.execFrameRequesters(MAIN_LOOP_EVENTS.AFTER_RENDER, dt, this.#updateLoopRestarted);
}
}

export default MainLoop;
20 changes: 17 additions & 3 deletions test/unit/view.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as THREE from 'three';
import { getMaxColorSamplerUnitsCount } from 'Renderer/LayeredMaterial';
import { MAIN_LOOP_EVENTS } from 'Core/MainLoop';
import assert from 'assert';
import View from 'Core/View';
import ColorLayer from 'Layer/ColorLayer';
Expand Down Expand Up @@ -74,11 +75,24 @@ describe('Viewer', function () {
});

it('should update sources viewer and notify change', () => {
global.requestAnimationFrame = step => step(0);
let wasRedraw = false;

viewer.addFrameRequester(
MAIN_LOOP_EVENTS.BEFORE_RENDER,
() => {
wasRedraw = true;
},
);
viewer.addLayer(globelayer);
viewer.notifyChange(globelayer, true);
assert.equal(viewer.mainLoop.needsRedraw, 1);
viewer.mainLoop._step(viewer, 0);
assert.equal(viewer.mainLoop.needsRedraw, false);
assert.equal(wasRedraw, true);

wasRedraw = false;
viewer.notifyChange(globelayer, false);
assert.equal(wasRedraw, false);

global.requestAnimationFrame = () => {};
});

it('ColorLayersOrdering', (done) => {
Expand Down

0 comments on commit 25a48fd

Please sign in to comment.