| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- import assert from 'node:assert/strict'
- import { createRegionLoadingController } from '../../src/modules/cockpit/services/regionLoading.js'
- function event() {
- const listeners = new Set()
- return {
- addEventListener(fn) { listeners.add(fn); return () => listeners.delete(fn) },
- raise(value) { for (const fn of listeners) fn(value) },
- }
- }
- async function harness({ count = 2, budget = 1 } = {}) {
- let clock = 0, timer = null, renderRequested = false
- const models = new Map(), removed = []
- const regions = Array.from({ length: count }, (_, index) => ({
- id: count - index, contentCount: 1, decodedGeometryBytes: 1024,
- }))
- const controller = createRegionLoadingController({
- regions, showBudgetPerFrame: budget, maxConcurrentLoads: count,
- load: async region => {
- const model = { show: false, tilesLoaded: false, tileVisible: event() }
- models.set(region.id, model)
- return model
- },
- add() {}, remove(model) { removed.push(model) }, requestRender() { renderRequested = true },
- distance: region => region.id * 10, readCameraKey: () => 'fixed', now: () => clock,
- setTimer(callback) { timer = callback; return 1 }, clearTimer() { timer = null },
- })
- controller.refresh()
- await Promise.resolve()
- for (const model of models.values()) model.tilesLoaded = true
- return {
- controller, models, removed,
- shown: () => [...models].filter(([, model]) => model.show).map(([id]) => id).sort((a, b) => a - b),
- renderPending: () => renderRequested,
- frame(at) { clock = at; renderRequested = false; controller.onRender() },
- timer(at) { clock = at; const callback = timer; timer = null; callback?.() },
- }
- }
- // 199ms postRender + 200ms refresh occur before the next 216ms render.
- // A scheduling callback is not a rendered frame, even after a long idle gap.
- for (const budget of [1, 2]) {
- const h = await harness({ count: budget * 2, budget })
- try {
- const nearest = Array.from({ length: budget }, (_, index) => index + 1)
- h.frame(199)
- assert.deepEqual(h.shown(), nearest, 'First-show slots go to nearest ready regions')
- assert.equal(h.renderPending(), true, 'First show requests the next render to advance deferred work')
- h.timer(200)
- assert.deepEqual(h.shown(), nearest, 'Timer must share the current frame show budget')
- h.timer(400)
- h.controller.refresh()
- assert.deepEqual(h.shown(), nearest, 'Elapsed time and explicit refresh do not grant frame slots')
- for (let id = budget + 1; id <= budget * 2; id++) {
- assert.equal(h.controller.detailReady(id), false, 'Deferred regions retain coarse fallback')
- }
- // A new frame, not wall-clock advancement, grants the next budget.
- h.frame(400)
- assert.equal(h.shown().length, budget * 2, 'Ready work progresses on the next frame')
- } finally { h.controller.detach() }
- }
- // A newly shown model must actually render before its coarse fallback yields.
- {
- const h = await harness()
- try {
- h.frame(199)
- h.timer(200)
- assert.equal(h.controller.detailReady(1), false)
- h.models.get(1).tileVisible.raise({ content: { url: 'near', trianglesLength: 1 } })
- h.frame(216)
- assert.equal(h.controller.detailReady(1), true)
- assert.equal(h.controller.detailReady(2), false)
- h.models.get(2).tileVisible.raise({ content: { url: 'far', trianglesLength: 1 } })
- h.frame(233)
- assert.equal(h.controller.detailReady(2), true)
- } finally { h.controller.detach() }
- }
- // Exhausted show slots must not throttle pipeline release or resurrect disposed work.
- {
- const h = await harness()
- h.frame(199)
- h.controller.setPipeline(true)
- assert.equal(h.removed.length, 2, 'Pipeline change releases every loaded model immediately')
- assert.equal(h.controller.detailReady(1), false)
- assert.equal(h.controller.detailReady(2), false)
- h.controller.detach()
- h.timer(400)
- h.frame(416)
- h.controller.refresh()
- assert.equal(h.models.size, 2, 'Disposed controller does not start another model')
- assert.equal(h.removed.length, 2, 'Each model is removed exactly once')
- }
- console.log('PASS region frame budget: timer/render interleaving, nearest-first, progress, coarse handoff, disposal')
|