feedback7 R22-HUD: the cached-atlas refreshes of a crowded battle take their turns from this frame's update order, not random phases — each frame refreshes ⌊n/k⌋ or ⌈n/k⌉ of n units (step 1 of GitHub #327, PR #334 by @Cloudnyco); golden: unchanged
Applied unchanged after review (Grok static review: MERGE; the diff read in full: no network, no dependency). UnitView._updateImpostor refreshed a unit when (frame + phase) % interval === 0 with a random phase per unit, so the refreshes of a frame were binomial: reproduced on the parent with 7 pen figures at interval 3 — 5 1 1 5 1 1 … and 2 4 1 2 4 1 … refreshes per frame instead of 2 / 3. Now render/app.js hands every impostor unit the next slot of the frame (ctx.impostorSlot, a counter reset in frameBody), the unit refreshes when (frame + slot) % interval === 0, and a unit whose slot keeps moving with the frame (the units before it culled on and off in step) is still refreshed after 2 intervals (imp.last); a context without slots keeps the random phase. Each unit's rate and the total work are unchanged; no sim or pixel-as-state change. Step 2 of #327 (a time budget by priority) is not part of this and still needs a decision. - Tests: test/render/unitview.test.js 'impostor refresh slots' (3: slots in update order — fails on the parent, the starvation cap, the no-slots context); unit view / render suites unchanged. - Docs: design/client.md. Golden: unchanged (client only).
This commit is contained in:
1 parent
3484178e2e
commit
bc11c0d61a
4 files changed
+86
-3
No files matched your search
@@ -316,6 +316,7 @@ export async function createFieldView(host, options = {}) {
|
||||
renderer: app.renderer,
|
||||
frameNo: () => frameNo,
|
||||
impostorInterval: () => impInterval,
|
||||
impostorSlot: () => impSlot++,
|
||||
clipAllowed: () => clipAllowed,
|
||||
viewport: () => vp,
|
||||
loadLevel: () => loadLevel,
|
||||
@@ -1565,6 +1566,10 @@ export async function createFieldView(host, options = {}) {
|
||||
let cpuMs = 0;
|
||||
let frameNo = 0;
|
||||
let impInterval = 0;
|
||||
// Each frame the impostor units take slots 0, 1, 2 … in update order (units.js _updateImpostor): a unit refreshes when
|
||||
// (frame + slot) % interval === 0, so a frame refreshes ⌊n/k⌋ or ⌈n/k⌉ of n units. Random per-unit phases left the
|
||||
// busiest frame 20–66% above that (16–48 units, intervals 2–6) and set the frame-time peaks of a crowded battle.
|
||||
let impSlot = 0;
|
||||
let vp = { width: s0.width, height: s0.height }; // viewport (CSS px) of this frame: unit culling
|
||||
let culledCount = 0;
|
||||
// Adaptive load level 0–3: a device that cannot hold the frame rate with the current work switches crowds to
|
||||
@@ -1605,6 +1610,7 @@ export async function createFieldView(host, options = {}) {
|
||||
}
|
||||
function frameBody(now) {
|
||||
frameNo++;
|
||||
impSlot = 0;
|
||||
if (frameNo % 30 === 1) {
|
||||
impInterval = pickImpostorInterval(); clipAllowed = pickClipping();
|
||||
culledCount = 0;
|
||||
|
||||
@@ -1457,16 +1457,22 @@ export class UnitView {
|
||||
if (this.actor.spine.parent) this.actor.spine.parent.removeChild(this.actor.spine);
|
||||
if (atlas) atlas.park(this.actor.spine);
|
||||
this.body.addChild(sprite);
|
||||
this.imp = { sprite, slot: null, rt: null, sc: 0, acc: 0, phase: (Math.random() * 64) | 0, dirty: true };
|
||||
this.imp = { sprite, slot: null, rt: null, sc: 0, acc: 0, phase: (Math.random() * 64) | 0, dirty: true, last: 0 };
|
||||
}
|
||||
const imp = this.imp;
|
||||
imp.acc += animDt;
|
||||
const frame = this.ctx.frameNo ? this.ctx.frameNo() : 0;
|
||||
const due = imp.dirty || interval <= 1 || (frame + imp.phase) % interval === 0 || Math.abs(sc - imp.sc) > imp.sc * 0.12;
|
||||
// the view's slot in this frame's update order (app.js impostorSlot) spreads the refreshes evenly over the interval;
|
||||
// a context without slots keeps the random phase. A unit whose slot keeps moving with the frame (the units before it
|
||||
// culled on and off in step) is still refreshed after 2 intervals at the latest.
|
||||
const turn = this.ctx.impostorSlot ? this.ctx.impostorSlot() : imp.phase;
|
||||
const due = imp.dirty || interval <= 1 || (frame + turn) % interval === 0 || frame - imp.last >= 2 * interval
|
||||
|| Math.abs(sc - imp.sc) > imp.sc * 0.12;
|
||||
if (due) {
|
||||
this.actor.update(imp.acc);
|
||||
imp.acc = 0;
|
||||
imp.dirty = false;
|
||||
imp.last = frame;
|
||||
this._renderImpostor(sc, atlas);
|
||||
}
|
||||
const k = imp.sc > 0 ? sc / imp.sc : 1;
|
||||
|
||||
Reference in new issue
Block a user