• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

iTowns / itowns / 5691913681

pending completion
5691913681

push

github

mgermerie
chore: update three to r154

3933 of 5864 branches covered (67.07%)

Branch coverage included in aggregate %.

7778 of 9456 relevant lines covered (82.25%)

1619.74 hits per line

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

66.27
/src/Core/MainLoop.js
1
import { EventDispatcher } from 'three';
1,051!
2

3
export const RENDERING_PAUSED = 0;
1✔
4
export const RENDERING_SCHEDULED = 1;
1✔
5

6
/**
1✔
7
 * MainLoop's update events list that are fired using
8
 * {@link View#execFrameRequesters}.
9
 *
10
 * @property UPDATE_START {string} fired at the start of the update
11
 * @property BEFORE_CAMERA_UPDATE {string} fired before the camera update
12
 * @property AFTER_CAMERA_UPDATE {string} fired after the camera update
13
 * @property BEFORE_LAYER_UPDATE {string} fired before the layer update
14
 * @property AFTER_LAYER_UPDATE {string} fired after the layer update
15
 * @property BEFORE_RENDER {string} fired before the render
16
 * @property AFTER_RENDER {string} fired after the render
17
 * @property UPDATE_END {string} fired at the end of the update
18
 */
19

20
export const MAIN_LOOP_EVENTS = {
1✔
21
    UPDATE_START: 'update_start',
22
    BEFORE_CAMERA_UPDATE: 'before_camera_update',
23
    AFTER_CAMERA_UPDATE: 'after_camera_update',
24
    BEFORE_LAYER_UPDATE: 'before_layer_update',
25
    AFTER_LAYER_UPDATE: 'after_layer_update',
26
    BEFORE_RENDER: 'before_render',
27
    AFTER_RENDER: 'after_render',
28
    UPDATE_END: 'update_end',
29
};
1✔
30

31
function updateElements(context, geometryLayer, elements) {
32
    if (!elements) {
6!
33
        return;
×
34
    }
6✔
35
    for (const element of elements) {
6✔
36
        // update element
37
        // TODO find a way to notify attachedLayers when geometryLayer deletes some elements
38
        // and then update Debug.js:addGeometryLayerDebugFeatures
39
        const newElementsToUpdate = geometryLayer.update(context, geometryLayer, element);
4✔
40

41
        const sub = geometryLayer.getObjectToUpdateForAttachedLayers(element);
4✔
42

43
        if (sub) {
4!
44
            if (sub.element) {
×
45
                if (__DEBUG__) {
46
                    if (!(sub.element.isObject3D)) {
47
                        throw new Error(`
48
                            Invalid object for attached layer to update.
49
                            Must be a THREE.Object and have a THREE.Material`);
50
                    }
51
                }
52
                // update attached layers
×
53
                for (const attachedLayer of geometryLayer.attachedLayers) {
×
54
                    if (attachedLayer.ready) {
×
55
                        attachedLayer.update(context, attachedLayer, sub.element, sub.parent);
×
56
                        attachedLayer.cache.flush();
×
57
                    }
58
                }
×
59
            } else if (sub.elements) {
×
60
                for (let i = 0; i < sub.elements.length; i++) {
×
61
                    if (!(sub.elements[i].isObject3D)) {
×
62
                        throw new Error(`
×
63
                            Invalid object for attached layer to update.
64
                            Must be a THREE.Object and have a THREE.Material`);
65
                    }
66
                    // update attached layers
×
67
                    for (const attachedLayer of geometryLayer.attachedLayers) {
×
68
                        if (attachedLayer.ready) {
×
69
                            attachedLayer.update(context, attachedLayer, sub.elements[i], sub.parent);
×
70
                            attachedLayer.cache.flush();
×
71
                        }
72
                    }
×
73
                }
74
            }
75
        }
76
        updateElements(context, geometryLayer, newElementsToUpdate);
4✔
77
    }
6✔
78
}
79

80
function filterChangeSources(updateSources, geometryLayer) {
81
    let fullUpdate = false;
2✔
82
    const filtered = new Set();
2✔
83
    updateSources.forEach((src) => {
2✔
84
        if (src === geometryLayer || src.isCamera) {
2!
85
            geometryLayer.info.clear();
2✔
86
            fullUpdate = true;
2✔
87
        } else if (src.layer === geometryLayer) {
×
88
            filtered.add(src);
×
89
        }
90
    });
91
    return fullUpdate ? new Set([geometryLayer]) : filtered;
2!
92
}
6✔
93

94

95
class MainLoop extends EventDispatcher {
2✔
96
    #needsRedraw = false;
39✔
97
    #updateLoopRestarted = true;
39✔
98
    #lastTimestamp = 0;
99
    constructor(scheduler, engine) {
39✔
100
        super();
117✔
101
        this.renderingState = RENDERING_PAUSED;
39✔
102
        this.scheduler = scheduler;
39✔
103
        this.gfxEngine = engine; // TODO: remove me
39✔
104
    }
1✔
105

106
    scheduleViewUpdate(view, forceRedraw) {
179✔
107
        this.#needsRedraw |= forceRedraw;
179✔
108

109
        if (this.renderingState !== RENDERING_SCHEDULED) {
179✔
110
            this.renderingState = RENDERING_SCHEDULED;
34✔
111

112
            if (__DEBUG__) {
113
                document.title += ' ⌛';
114
            }
115

116
            requestAnimationFrame((timestamp) => { this.#step(view, timestamp); });
34✔
117
        }
118
    }
1✔
119

120
    #update(view, updateSources, dt) {
121
        const context = {
2✔
122
            camera: view.camera,
123
            engine: this.gfxEngine,
124
            scheduler: this.scheduler,
125
            view,
126
        };
127

128
        // replace layer with their parent where needed
129
        updateSources.forEach((src) => {
2✔
130
            const layer = src.layer || src;
2✔
131
            if (layer.isLayer && layer.parent) {
2!
132
                updateSources.add(layer.parent);
×
133
            }
134
        });
2✔
135

136
        for (const geometryLayer of view.getLayers((x, y) => !y)) {
2✔
137
            context.geometryLayer = geometryLayer;
2✔
138
            if (geometryLayer.ready && geometryLayer.visible && !geometryLayer.frozen) {
2!
139
                view.execFrameRequesters(MAIN_LOOP_EVENTS.BEFORE_LAYER_UPDATE, dt, this.#updateLoopRestarted, geometryLayer);
2✔
140

141
                // Filter updateSources that are relevant for the geometryLayer
142
                const srcs = filterChangeSources(updateSources, geometryLayer);
2✔
143
                if (srcs.size > 0) {
2!
144
                    // pre update attached layer
2✔
145
                    for (const attachedLayer of geometryLayer.attachedLayers) {
2✔
146
                        if (attachedLayer.ready && attachedLayer.preUpdate) {
×
147
                            attachedLayer.preUpdate(context, srcs);
×
148
                        }
149
                    }
150
                    // `preUpdate` returns an array of elements to update
2✔
151
                    const elementsToUpdate = geometryLayer.preUpdate(context, srcs);
2✔
152
                    // `update` is called in `updateElements`.
153
                    updateElements(context, geometryLayer, elementsToUpdate);
2✔
154
                    // `postUpdate` is called when this geom layer update process is finished
155
                    geometryLayer.postUpdate(context, geometryLayer, updateSources);
2✔
156
                }
157

158
                // Clear the cache of expired resources
159
                geometryLayer.cache.flush();
2✔
160

161
                view.execFrameRequesters(MAIN_LOOP_EVENTS.AFTER_LAYER_UPDATE, dt, this.#updateLoopRestarted, geometryLayer);
2✔
162
            }
163
        }
2✔
164
    }
165

166
    #step(view, timestamp) {
167
        const dt = timestamp - this.#lastTimestamp;
2✔
168
        view._executeFrameRequestersRemovals();
2✔
169

170
        view.execFrameRequesters(MAIN_LOOP_EVENTS.UPDATE_START, dt, this.#updateLoopRestarted);
2✔
171

172
        const willRedraw = this.#needsRedraw;
2✔
173
        this.#lastTimestamp = timestamp;
2✔
174

175
        // Reset internal state before calling _update (so future calls to View.notifyChange()
176
        // can properly change it)
177
        this.#needsRedraw = false;
2✔
178
        this.renderingState = RENDERING_PAUSED;
2✔
179
        const updateSources = new Set(view._changeSources);
2✔
180
        view._changeSources.clear();
2✔
181

182
        // update camera
183
        const dim = this.gfxEngine.getWindowSize();
2✔
184

185
        view.execFrameRequesters(MAIN_LOOP_EVENTS.BEFORE_CAMERA_UPDATE, dt, this.#updateLoopRestarted);
2✔
186
        view.camera.update(dim.x, dim.y);
2✔
187
        view.execFrameRequesters(MAIN_LOOP_EVENTS.AFTER_CAMERA_UPDATE, dt, this.#updateLoopRestarted);
2✔
188

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

199
        // update data-structure
200
        this.#update(view, updateSources, dt);
2✔
201

202
        if (this.scheduler.commandsWaitingExecutionCount() == 0) {
2!
203
            this.dispatchEvent({ type: 'command-queue-empty' });
2✔
204
        }
205

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

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

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

222
        view.camera.camera3D.matrixAutoUpdate = oldAutoUpdate;
2✔
223

224
        view.execFrameRequesters(MAIN_LOOP_EVENTS.UPDATE_END, dt, this.#updateLoopRestarted);
2✔
225
    }
226

227
    #renderView(view, dt) {
228
        view.execFrameRequesters(MAIN_LOOP_EVENTS.BEFORE_RENDER, dt, this.#updateLoopRestarted);
1✔
229

230
        if (view.render) {
1!
231
            view.render();
×
232
        } else {
233
            // use default rendering method
234
            this.gfxEngine.renderView(view);
1✔
235
        }
236

237
        view.execFrameRequesters(MAIN_LOOP_EVENTS.AFTER_RENDER, dt, this.#updateLoopRestarted);
1✔
238
    }
239
}
240

241
export default MainLoop;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc