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) } } } const flush = async () => { await Promise.resolve(); await Promise.resolve() } let clock = 0, front = 0 const loads = [], removed = [], models = new Map() const controller = createRegionLoadingController({ regions: [0, 1].map(id => ({ id, contentCount: 1, decodedGeometryBytes: 1024 })), budget: { maxContents: 2, maxDecodedBytes: 2048 }, distance: r => r.id === front ? 10 : Infinity, readCameraKey: () => String(front), now: () => clock, load: async r => { loads.push(r.id); const m = { id: r.id, show: false, tilesLoaded: true, covered: true, tileVisible: event() }; models.set(r.id, m); return m }, add() {}, remove: m => removed.push(m.id), coverageReady: e => e.instance.covered, canStart: () => true, requestRender() {}, setTimer: () => 1, clearTimer() {}, }) try { controller.refresh(); await flush(); controller.onRender() const a = models.get(0) a.tileVisible.raise({ content: { trianglesLength: 1 } }); controller.onRender() assert.equal(controller.detailReady(0), true, 'Initial A coverage was rendered') clock = 200; front = 1; controller.refresh(); await flush(); controller.onRender() clock = 216; controller.onRender() // next frame has an unused show slot clock = 400; front = 0; controller.refresh(); await flush() assert.deepEqual(loads, [0, 1], 'A -> B -> A within the existing budget must reuse ready A, not reload it') assert.equal(models.get(0), a, 'Reuse the existing model instance') assert.equal(a.show, false, 'Cached readiness must not show before a fresh renderer pass') assert.equal(controller.snapshot().find(e => e.id === 0).awaitingRender, true) a.tilesLoaded = a.covered = false // renderer discovers an uncovered part of the new view controller.onRender() assert.equal(a.show, false, 'Fresh view readiness overrides the cached ready flag') assert.equal(controller.detailReady(0), false, 'Reused model must render again before coarse yields') a.tilesLoaded = a.covered = true; clock = 416; controller.onRender() assert.equal(controller.detailReady(0), false) a.tileVisible.raise({ content: { trianglesLength: 1 } }); clock = 432; controller.onRender() assert.equal(controller.detailReady(0), true) assert.deepEqual(removed, [], 'No eviction needed while both models fit the original budget') console.log('PASS bounded residency: ready A -> B -> A reuses A and re-proves visible coverage') } finally { controller.detach() } async function scene(options, run) { const costs = options.costs || [1, 1, 1] const budget = options.budget || { maxContents: 2, maxDecodedBytes: 2048 } let time = 0, pose = 0, visible = [0], parentReady = true, startAllowed = true, renderRequested = false const loaded = [], added = [], disposed = [], byId = new Map(), waiting = new Map() const control = createRegionLoadingController({ regions: costs.map((cost, id) => ({ id, contentCount: cost, decodedGeometryBytes: cost * 1024 })), budget, distance: r => visible.includes(r.id) ? r.id + 1 : Infinity, readCameraKey: () => String(pose), now: () => time, canStart: () => parentReady && startAllowed, canRetain: () => parentReady, coverageReady: e => e.instance.covered, load: r => { loaded.push(r.id) const m = { id: r.id, show: false, covered: options.ready !== false, tilesLoaded: options.ready !== false, tileVisible: event(), tileFailed: event() } byId.set(r.id, m) return (options.deferred || options.deferredIds?.includes(r.id)) ? new Promise((resolve, reject) => waiting.set(r.id, { resolve: () => resolve(m), reject })) : Promise.resolve(m) }, add: m => added.push(m.id), remove: m => disposed.push(m.id), requestRender: () => { renderRequested = true }, setTimer: () => 1, clearTimer() {}, }) const h = { control, loaded, added, disposed, byId, async view(ids, elapsed = 200, moving = true) { time += elapsed; visible = ids; if (moving) pose++ control.refresh(); await flush(); control.onRender(); await flush() }, async resolve(id) { waiting.get(id).resolve(); waiting.delete(id); await flush() }, async reject(id) { waiting.get(id).reject(new Error('load rejected')); waiting.delete(id); await flush() }, parent(value) { parentReady = value; control.refresh() }, allowStarts(value) { startAllowed = value; control.refresh() }, renderPending: () => renderRequested, clearRenderRequest() { renderRequested = false }, checkBudget() { const rows = control.snapshot() assert.ok(rows.reduce((n, e) => n + e.contents, 0) <= (budget.maxContents ?? 1200)) assert.ok(rows.reduce((n, e) => n + e.decodedGeometryEstimate, 0) <= (budget.maxDecodedBytes ?? 256 * 1024 * 1024)) if (budget.maxRegions !== undefined) assert.ok(rows.length <= Math.ceil(budget.maxRegions)) }, } control.refresh(); await flush(); control.onRender(); await flush() try { await run(h) } finally { control.detach() for (const wait of waiting.values()) wait.resolve() await flush() } } await scene({}, async h => { await h.view([1]) const a = h.byId.get(0) assert.equal(a.show, false) assert.equal(a.preloadWhenHidden, false, 'Parked ready model does not preload or render') assert.equal(h.control.detailReady(0), false, 'Cached region cannot mask coarse') assert.equal(h.control.snapshot().find(e => e.id === 0).cached, true) assert.equal(h.control.frontierSnapshot().cachedRegions, 1) h.clearRenderRequest() await h.view([0]) assert.equal(h.renderPending(), true, 'Reactivation wakes requestRenderMode') assert.equal(a.preloadWhenHidden, true) assert.deepEqual(h.loaded, [0, 1]) h.checkBudget() }) await scene({}, async h => { await h.view([1]); await h.view([0]); await h.view([2]) assert.deepEqual(h.disposed, [1], 'Least recently wanted hidden cache yields first') assert.equal(h.control.snapshot().find(e => e.id === 0).cached, true) await h.view([0]) assert.deepEqual(h.loaded, [0, 1, 2], 'Most recently reused cache survives pressure') h.checkBudget() }) await scene({}, async h => { await h.view([1]); await h.view([0]); await h.view([0, 2]) assert.deepEqual(h.disposed, [1], 'Cache yields instead of evicting the active near target') assert.equal(h.byId.get(0).show, true) h.checkBudget() }) for (const budget of [{ maxContents: 1 }, { maxDecodedBytes: 1024 }, { maxRegions: 1 }]) { await scene({ budget }, async h => { await h.view([1]) assert.deepEqual(h.disposed, [0], 'Content, bytes and optional count limits include cached models') assert.deepEqual(h.control.snapshot().map(e => e.id), [1]) h.checkBudget() }) } await scene({ deferred: true, budget: { maxContents: 1, maxDecodedBytes: 1024 } }, async h => { await h.view([1], 1000, false) assert.deepEqual(h.loaded, [0], 'Withdrawn unresolved initialization still reserves its budget') h.checkBudget() await h.resolve(0) assert.deepEqual(h.added, [], 'Late withdrawn initialization never attaches') await h.view([1], 1000, false) assert.deepEqual(h.loaded, [0, 1], 'Released initialization reservation permits the new target') h.checkBudget() }) await scene({ ready: false }, async h => { await h.view([1]) assert.deepEqual(h.disposed, [0], 'Incomplete initial stream is not retained') assert.equal(h.control.frontierSnapshot().cachedRegions, 0) }) await scene({ costs: [0, 0, 0] }, async h => { await h.view([1]) assert.deepEqual(h.disposed, [0], 'Unknown zero-cost models cannot accumulate unbounded caches') }) await scene({}, async h => { await h.view([1]) h.byId.get(0).tileFailed.raise({ message: 'late cached failure' }) h.control.refresh() assert.deepEqual(h.disposed, [0], 'Failure removes a cached instance') assert.equal(h.control.frontierSnapshot().cachedRegions, 0) }) await scene({}, async h => { await h.view([1]); h.parent(false) assert.deepEqual(h.disposed, [0], 'Unavailable parent releases hidden caches') assert.equal(h.control.frontierSnapshot().cachedRegions, 0) }) await scene({}, async h => { await h.view([1]); await h.view([], 1000, false) assert.equal(h.control.snapshot().length, 0, 'Settled scene without demand releases residency') }) await scene({}, async h => { await h.view([1]); h.control.setPipeline(true) assert.equal(h.control.snapshot().length, 0) assert.deepEqual(h.disposed.slice().sort(), [0, 1], 'Pipeline releases active and cached models') h.control.detach() assert.equal(h.disposed.length, 2, 'Dispose does not release a model twice') }) await scene({ costs: [1, 1, 3], budget: { maxContents: 3, maxDecodedBytes: 3072 }, deferredIds: [1] }, async h => { await h.view([1]) // ready A is cached; B is still initializing. await h.view([2], 1000, false) assert.deepEqual(h.loaded, [0, 1], 'Outstanding B prevents a full-budget C from starting') assert.deepEqual(h.disposed.slice(), [], 'Do not evict A when even clearing caches cannot yet admit C') await h.resolve(1) await h.view([2], 1000, false) assert.deepEqual(h.loaded, [0, 1, 2], 'C starts after the obsolete reservation resolves') assert.deepEqual(h.disposed.slice().sort(), [0, 1]) h.checkBudget() }) await scene({ deferred: true }, async h => { await h.view([0, 1, 2], 1000, false) assert.deepEqual(h.loaded, [0, 1], 'Two unresolved initializations fill the shared budget') h.checkBudget() await h.view([2], 1000, false) assert.deepEqual(h.loaded, [0, 1]) await h.resolve(0) await h.view([2], 1000, false) assert.deepEqual(h.loaded, [0, 1, 2], 'Only released reservation is replaced') h.checkBudget() }) await scene({ deferredIds: [1] }, async h => { await h.view([1]); await h.reject(1); await h.view([2]) assert.deepEqual(h.loaded, [0, 1, 2], 'Rejected load releases capacity for the next target') assert.equal(h.control.snapshot().find(e => e.id === 0).cached, true, 'Unrelated valid cache survives rejection') h.checkBudget() }) await scene({ deferredIds: [1] }, async h => { await h.view([1]); h.control.detach(); await h.resolve(1) assert.deepEqual(h.added, [0], 'Disposed controller never attaches a late model') assert.deepEqual(h.disposed.slice().sort(), [0, 1], 'Cache and late model are each released once') assert.equal(h.control.snapshot().length, 0) }) await scene({}, async h => { await h.view([1]) const a = h.byId.get(0) assert.equal(h.control.snapshot().find(e => e.id === 0).cached, true) h.allowStarts(false) // parent is alive/visible, but temporarily not ready for new loads. assert.deepEqual(h.disposed.slice(), [], 'A closed start gate must not invalidate a ready cache') await h.view([0]) assert.deepEqual(h.loaded, [0, 1], 'Valid cached A can return while new starts are paused') assert.equal(h.byId.get(0), a) await h.view([2]) assert.deepEqual(h.loaded, [0, 1], 'The independent start gate still blocks a new region') h.parent(false) assert.equal(h.control.snapshot().length, 0, 'A genuinely invalid parent releases caches') }) console.log('PASS residency boundaries: hidden state, wakeup, LRU, budgets, pending reservation, failure and release')