| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- import assert from 'node:assert/strict'
- import { createRegionLoadingController } from '../../src/modules/cockpit/services/regionLoading.js'
- // Replay the load boundary seen in the low-spec exports: coarse is ready,
- // close candidates remain visible, but the camera never stops moving.
- let clock = 0, pose = 'initial', visible = false
- const started = [], pending = new Map()
- const controller = createRegionLoadingController({
- regions: [1, 2].map(id => ({ id, contentCount: 1, decodedGeometryBytes: 1024 })),
- load: region => { started.push(region.id); return new Promise(resolve => pending.set(region.id, resolve)) },
- add() {}, remove() {}, requestRender() {},
- distance: region => visible ? region.id * 10 : Infinity,
- canStart: () => true, readCameraKey: () => pose, now: () => clock,
- setTimer: () => 1, clearTimer() {},
- })
- try {
- controller.refresh()
- visible = true
- for (let i = 1; i <= 8; i++) {
- clock = i * 180
- pose = 'moving-' + i
- controller.onRender()
- }
- assert.equal(controller.frontierSnapshot().moving, true)
- const whileMoving = started.slice(), movingCount = controller.snapshot().length
- clock += 1000
- controller.refresh()
- console.log(JSON.stringify({ whileMoving, afterSettling: started }))
- assert.deepEqual(started, [1, 2], 'Same candidates start under the unchanged stationary concurrency limit')
- assert.deepEqual(whileMoving, [1], 'Continuous movement must admit one nearest region, not starve all fine loading')
- assert.equal(movingCount, 1, 'Movement keeps at most one initial load in flight')
- console.log('PASS moving admission: visible near candidate progresses through one bounded load slot')
- } finally {
- controller.detach()
- for (const resolve of pending.values()) resolve({ show: false, tilesLoaded: false })
- await Promise.resolve()
- }
- // Boundary cases use the same public controller and explicit asynchronous loads.
- const flush = async () => { await Promise.resolve(); await Promise.resolve() }
- async function check(options, scenario) {
- const { count = 3, ...controllerOptions } = options
- let clock = 0, pose = 0
- const state = { visible: false, coarseReady: true }
- const started = [], added = [], removed = [], models = new Map(), waits = new Map()
- const controller = createRegionLoadingController({
- regions: Array.from({ length: count }, (_, i) => ({ id: i + 1, contentCount: 1, decodedGeometryBytes: 1024 })),
- distance: r => state.visible ? r.id * 10 : Infinity,
- readCameraKey: () => String(pose), now: () => clock,
- canStart: () => state.coarseReady,
- coverageReady: e => e.instance.covered === true,
- load: r => { started.push(r.id); return new Promise((resolve, reject) => waits.set(r.id, { resolve, reject })) },
- add: m => added.push(m), remove: m => removed.push(m), requestRender() {},
- setTimer: () => 1, clearTimer() {},
- ...controllerOptions,
- })
- controller.refresh()
- const h = {
- controller, state, started, added, removed,
- async step(ms = 180, moving = true) { clock += ms; if (moving) pose++; controller.onRender(); await flush() },
- async resolveLoad(id, ready = false) {
- const wait = waits.get(id)
- assert.ok(wait, 'Only pending loads can resolve')
- const m = { show: false, tilesLoaded: ready, covered: ready }
- models.set(id, m); wait.resolve(m); waits.delete(id); await flush(); return m
- },
- markReady(id) { const m = models.get(id); m.tilesLoaded = m.covered = true },
- async fail(id) { waits.get(id).reject(new Error('fixture load failed')); waits.delete(id); await flush() },
- }
- try { await scenario(h) } finally {
- controller.detach()
- for (const wait of waits.values()) wait.resolve({ show: false, tilesLoaded: false, covered: false })
- await flush()
- }
- }
- await check({}, async h => {
- h.state.visible = true
- await h.step()
- assert.deepEqual(h.started, [1], 'Nearest candidate starts first during movement')
- await h.resolveLoad(1) // fromUrl resolved, but content is not ready yet.
- await h.step()
- assert.deepEqual(h.started, [1], 'Streaming keeps the single moving slot occupied')
- assert.equal(h.controller.detailReady(1), false, 'Incomplete coverage keeps coarse fallback')
- h.markReady(1)
- await h.step()
- assert.deepEqual(h.started, [1, 2], 'Arrival releases the slot without waiting for camera settle')
- })
- await check({}, async h => {
- h.state.visible = true
- await h.step(300, false)
- assert.deepEqual(h.started, [1, 2], 'Stationary phase retains configured parallelism')
- await h.step()
- assert.equal(h.removed.length, 0, 'Moving does not cancel the two existing initial loads')
- await h.resolveLoad(1, true); await h.step()
- assert.deepEqual(h.started, [1, 2], 'Do not add a new load while one existing slot remains')
- await h.resolveLoad(2, true); await h.step()
- assert.deepEqual(h.started, [1, 2, 3], 'Resume bounded moving admission after existing work completes')
- })
- for (const budget of [{ maxContents: 1 }, { maxDecodedBytes: 1024 }]) {
- await check({ budget }, async h => {
- h.state.visible = true
- await h.step(); await h.resolveLoad(1, true); await h.step()
- await h.step(1000, false)
- assert.deepEqual(h.started, [1], 'Neither movement nor settling bypasses the selection budget')
- })
- }
- await check({ maxConcurrentLoads: 1 }, async h => {
- h.state.visible = true
- await h.step(300, false)
- assert.deepEqual(h.started, [1], 'Explicit single-slot configuration still applies when stationary')
- await h.resolveLoad(1, true); await h.step(300, false)
- assert.deepEqual(h.started, [1, 2])
- })
- await check({}, async h => {
- h.state.visible = true; h.state.coarseReady = false
- await h.step(); await h.step()
- assert.deepEqual(h.started, [], 'Coarse readiness gate remains mandatory')
- h.state.coarseReady = true
- await h.step(); assert.deepEqual(h.started, [1])
- h.controller.setPipeline(true)
- const m = await h.resolveLoad(1, true)
- await h.step()
- assert.equal(h.added.length, 0, 'Late completion cannot attach in pipeline mode')
- assert.deepEqual(h.removed, [m], 'Rejected late model is released exactly once')
- assert.deepEqual(h.started, [1], 'Pipeline mode does not admit new ground regions')
- })
- await check({ count: 1 }, async h => {
- h.state.visible = true; await h.step(); await h.fail(1)
- await h.step(); assert.deepEqual(h.started, [1], 'Failure cooldown prevents repeated starts')
- await h.step(15001)
- assert.deepEqual(h.started, [1, 1], 'Retry can progress during movement after cooldown')
- })
- await check({ count: 1 }, async h => {
- h.state.visible = true; await h.step()
- h.state.visible = false; await h.step()
- const m = await h.resolveLoad(1, true)
- assert.equal(h.added.length, 0, 'Withdrawn candidate cannot attach after async completion')
- assert.deepEqual(h.removed, [m])
- h.state.visible = true; await h.step()
- assert.deepEqual(h.started, [1, 1], 'Rejected completion releases its slot for a new candidate')
- })
- console.log('PASS moving admission boundaries: progress, streaming cap, static transition, budgets, coarse gate, cancellation, retry')
|