102 lines
3.2 KiB
JavaScript
102 lines
3.2 KiB
JavaScript
// A Linux-boot-style log of the client's map setup, shown while a game loads.
|
|
// The client regenerates the whole world from the seed and uploads it to the
|
|
// GPU, which can take several seconds; this shows each step and how long it
|
|
// took. It records nothing unless a log has been started, so importing modules
|
|
// (and the test suite) pay no cost.
|
|
|
|
const now = () =>
|
|
(typeof performance !== "undefined" ? performance.now() : Date.now());
|
|
|
|
export class LoadingLog {
|
|
constructor() {
|
|
this.$root = null;
|
|
this.$log = null;
|
|
this.active = false;
|
|
this.startedAt = 0;
|
|
this.lastAt = 0;
|
|
}
|
|
|
|
bind($root) {
|
|
this.$root = $root;
|
|
this.$log = $root && $root.length ? $root.find(".loading-log") : null;
|
|
return this;
|
|
}
|
|
|
|
// Clears the log and shows the overlay. Safe to call when the overlay is not
|
|
// in the page: everything then no-ops.
|
|
begin() {
|
|
if (!this.$root || !this.$root.length) return;
|
|
this.active = true;
|
|
this.startedAt = now();
|
|
this.lastAt = this.startedAt;
|
|
this.$log.empty();
|
|
this.$root.removeClass("hidden").removeClass("done");
|
|
this.step("Booting the game client");
|
|
}
|
|
|
|
// Records a step, timed from the previous step/measure.
|
|
step(label) {
|
|
if (!this.active) return;
|
|
const time = now();
|
|
this._line(time - this.lastAt, label);
|
|
this.lastAt = time;
|
|
}
|
|
|
|
// Runs `fn`, logs how long it took under `label` and returns its result.
|
|
measure(label, fn) {
|
|
if (!this.active) return fn();
|
|
const start = now();
|
|
try {
|
|
return fn();
|
|
} finally {
|
|
const time = now();
|
|
this._line(time - start, label);
|
|
this.lastAt = time;
|
|
}
|
|
}
|
|
|
|
// `onHidden` runs once the overlay has actually left the screen, so callers
|
|
// can show something (the capability report) without it landing behind the
|
|
// fading loading panel. It fires at once when the log was never bound or was
|
|
// already finished.
|
|
finish(onHidden) {
|
|
if (!this.active) {
|
|
if (onHidden) onHidden();
|
|
return;
|
|
}
|
|
this.step("Ready");
|
|
this._line(now() - this.startedAt, "Total map setup", true);
|
|
this.active = false;
|
|
const notify = () => { if (onHidden) onHidden(); };
|
|
if (this.$root) {
|
|
const $root = this.$root;
|
|
$root.addClass("done");
|
|
// Fade out after a beat so the last lines can be read. Hiding is deferred
|
|
// through requestAnimationFrame, which the test harness stubs out, so a
|
|
// finished log never touches the DOM after the test window is closed.
|
|
const hide = () => { $root.addClass("hidden"); notify(); };
|
|
if (typeof requestAnimationFrame === "function") {
|
|
setTimeout(() => requestAnimationFrame(hide), 2500);
|
|
} else {
|
|
hide();
|
|
}
|
|
} else {
|
|
notify();
|
|
}
|
|
}
|
|
|
|
_line(milliseconds, label, total = false) {
|
|
if (!this.$log || !this.$log.length) return;
|
|
const value = milliseconds < 1000
|
|
? `${milliseconds.toFixed(1)} ms`
|
|
: `${(milliseconds / 1000).toFixed(2)} s`;
|
|
const line = document.createElement("div");
|
|
line.className = total ? "loading-line loading-total" : "loading-line";
|
|
line.textContent = `[ ${value.padStart(7, " ")} ] ${label}`;
|
|
this.$log[0].appendChild(line);
|
|
this.$log[0].scrollTop = this.$log[0].scrollHeight;
|
|
}
|
|
}
|
|
|
|
export const bootLog = new LoadingLog();
|