Spaces:
Running
Running
File size: 2,850 Bytes
4635d45 102a1b6 4635d45 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | // Runtime performance monitor for the live canvas. It measures the time spent
// in our own frame work (not time while a tab is backgrounded) and uses
// hysteresis, so one GC pause never makes quality visibly flicker.
export class RenderPerformance {
constructor({ frameBudgetMs, degradeAfterMs, recoverAfterMs }, preference = "auto") {
this.frameBudgetMs = frameBudgetMs;
this.degradeAfterMs = degradeAfterMs;
this.recoverAfterMs = recoverAfterMs;
this.mode = "full";
this.workMs = 0;
this.slowSince = null;
this.nextProbeAt = null;
this.probing = false;
this.setPreference(preference);
}
setPreference(preference) {
this.preference = ["auto", "full", "economy"].includes(preference) ? preference : "auto";
this.slowSince = null;
this.nextProbeAt = null;
this.probing = false;
// Returning to auto starts a fresh high-quality measurement window. Keeping
// an earlier forced economy mode here would leave it with no scheduled probe.
this.mode = this.preference === "economy" ? "economy" : "full";
}
// In economy mode, periodically allow exactly one full-quality frame. The
// result tells us whether CPU headroom really returned; simply measuring the
// cheap sprite path would otherwise make a slow device oscillate forever.
useEconomy(now) {
if (this.preference === "economy") return true;
if (this.preference === "full") return false;
if (this.mode !== "economy") return false;
if (this.nextProbeAt !== null && now >= this.nextProbeAt) {
this.probing = true;
return false;
}
return true;
}
record(now, workMs) {
this.workMs = this.workMs ? this.workMs * 0.85 + workMs * 0.15 : workMs;
if (this.preference !== "auto") return;
// A probe must be judged on its own full-quality frame. The smoothed value
// includes cheap economy frames and would incorrectly promote a slow device.
const slow = (this.probing ? workMs : this.workMs) > this.frameBudgetMs;
if (this.mode === "full") {
this.slowSince = slow ? (this.slowSince ?? now) : null;
if (this.slowSince !== null && now - this.slowSince >= this.degradeAfterMs) {
this.mode = "economy";
this.slowSince = null;
this.nextProbeAt = now + this.recoverAfterMs;
}
} else if (this.probing) {
this.probing = false;
if (!slow) {
this.mode = "full";
this.nextProbeAt = null;
} else {
this.nextProbeAt = now + this.recoverAfterMs;
}
}
}
get economical() { return this.mode === "economy"; }
label() {
const policy = this.preference === "full" ? "always high quality"
: this.preference === "economy" ? "always power saving"
: this.economical ? "auto power saving" : "auto";
return `${this.workMs.toFixed(0)}ms · ${policy}`;
}
}
|