mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
feat(globe): engine-agnostic maps checks and maps.current
The contract suite gains maps checks (opt-in with mapSources: two imagery sources and a photoreal one the engine can show): setPhotorealTiles(null) brings back the last base imagery, the last imagery or photoreal call decides what shows, and a released photoreal source no longer shows or counts. They observe the new maps.current(), the source objects on screen as passed. The fake engine follows the same semantics; qa-globe-contract runs the checks on the app engine with the startup tileset and reapplies the app's map stack afterwards. The contract documents map sources as engine-neutral descriptions whose prebuilt members only the engine kind that built them uses.
This commit is contained in:
1 parent
12c9651a67
commit
ab0acb0e09
6 files changed
+160
-14
No files matched your search
@@ -95,6 +95,9 @@ export function createCesiumMaps(
|
||||
// decides what the globe shows. Terrain has its own.
|
||||
const generations = { display: 0, terrain: 0 };
|
||||
let baseSource = null;
|
||||
// The source objects on screen, as passed (maps.current()).
|
||||
let currentBase = null;
|
||||
let currentTiles = null;
|
||||
let creditOwner = null;
|
||||
let destroyed = false;
|
||||
|
||||
@@ -130,6 +133,7 @@ export function createCesiumMaps(
|
||||
}
|
||||
|
||||
function showBase(source, provider) {
|
||||
currentBase = source;
|
||||
mapScene.showBaseImagery(provider);
|
||||
showCredit('imagery', imageryCredit(source));
|
||||
}
|
||||
@@ -156,6 +160,7 @@ export function createCesiumMaps(
|
||||
if (source == null) {
|
||||
mapScene.showTileset(null, tilesets());
|
||||
shownTileset = null;
|
||||
currentTiles = null;
|
||||
// The globe comes back with the last base imagery and its credit;
|
||||
// the photoreal credit goes.
|
||||
if (baseSource) {
|
||||
@@ -195,6 +200,8 @@ export function createCesiumMaps(
|
||||
// As MapSourceController activates a 3D stack: no base layer, the
|
||||
// source's credit, one tileset shown and the globe hidden.
|
||||
mapScene.removeBaseImagery();
|
||||
currentBase = null;
|
||||
currentTiles = source;
|
||||
showCredit('photoreal', source.credit);
|
||||
shownTileset = tileset;
|
||||
mapScene.showTileset(tileset, tilesets());
|
||||
@@ -205,9 +212,11 @@ export function createCesiumMaps(
|
||||
const tileset = source?.tileset;
|
||||
if (!tileset || !known.delete(tileset)) return;
|
||||
if (shownTileset === tileset) shownTileset = null;
|
||||
if (currentTiles?.tileset === tileset) currentTiles = null;
|
||||
if (added.delete(tileset)) viewer.scene.primitives.remove(tileset);
|
||||
},
|
||||
photorealActive: () => tilesets().some((tileset) => tileset.show === true),
|
||||
current: () => ({ baseImagery: currentBase, photoreal: currentTiles }),
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -378,3 +378,21 @@ test('releasePhotorealTiles leaves a tileset the engine did not add in the scene
|
||||
maps.releasePhotorealTiles({ tileset: startup });
|
||||
assert.equal(viewer.primitiveList.includes(startup), true);
|
||||
});
|
||||
|
||||
test('current names the sources on screen as passed', async () => {
|
||||
const { maps } = setup();
|
||||
assert.deepEqual(maps.current(), { baseImagery: null, photoreal: null });
|
||||
const esri = esriSource();
|
||||
const tiles = { tileset: tileset(), credit: 'Google' };
|
||||
await maps.setBaseImagery(esri);
|
||||
assert.deepEqual(maps.current(), { baseImagery: esri, photoreal: null });
|
||||
await maps.setPhotorealTiles(tiles);
|
||||
assert.equal(maps.current().photoreal, tiles);
|
||||
assert.equal(maps.current().baseImagery, null);
|
||||
await maps.setPhotorealTiles(null);
|
||||
assert.equal(maps.current().baseImagery, esri);
|
||||
assert.equal(maps.current().photoreal, null);
|
||||
await maps.setPhotorealTiles(tiles);
|
||||
maps.releasePhotorealTiles(tiles);
|
||||
assert.equal(maps.current().photoreal, null, 'a released source is gone');
|
||||
});
|
||||
+33
-11
@@ -161,6 +161,37 @@
|
||||
* a surface-pick check reads), else undefined.
|
||||
* @property {any} [object] The engine's own pick result, for engine-specific code.
|
||||
*
|
||||
* @typedef {object} GlobeMaps
|
||||
* Map sources are engine-neutral descriptions, as maps/registry.js sources
|
||||
* carry them; each engine builds its own providers from them:
|
||||
* - imagery `{ kind: 'osm'|'esri-imagery'|'ion', style?, accessToken?,
|
||||
* credit?, descriptor? }`;
|
||||
* - photoreal tiles `{ kind: 'photoreal', googleApiKey?, cesiumToken?,
|
||||
* credit?, descriptor? }`;
|
||||
* - terrain `{ kind: 'ellipsoid'|'keyless'|'world', accessToken?, id? }`.
|
||||
* Optional prebuilt members (`provider`, `tileset`, terrain `create`, an
|
||||
* `imagery()` factory) are engine objects their caller built and owns: only
|
||||
* the engine kind that built them uses them (the Cesium engine for the
|
||||
* registry's Cesium factories), any other ignores them and builds from the
|
||||
* description. The engine neither caches nor disposes them.
|
||||
* Each setter settles once the source is on screen and rejects when it
|
||||
* cannot be built (the current source then stays). Base imagery and
|
||||
* photoreal tiles share one switch order: the call made last decides what
|
||||
* shows, and a superseded call settles without showing anything.
|
||||
* @property {(source: object) => Promise<void>} setBaseImagery
|
||||
* @property {(source: object) => Promise<void>} setTerrain
|
||||
* @property {(source: object | null) => Promise<void>} setPhotorealTiles
|
||||
* Tiles replace the base imagery and hide the globe; null hides every
|
||||
* tileset and shows the globe again with the last base imagery and its
|
||||
* credit (no credit when none was set).
|
||||
* @property {(source: object) => void} releasePhotorealTiles Forgets a
|
||||
* photoreal source shown before (it no longer counts for photorealActive)
|
||||
* and takes engine-added tiles out of the scene; its caller disposes them.
|
||||
* @property {() => boolean} photorealActive
|
||||
* @property {() => { baseImagery: object | null, photoreal: object | null }} current
|
||||
* The source objects on screen, as passed: the base imagery layer on the
|
||||
* globe and the photoreal tiles shown (null for none).
|
||||
*
|
||||
* @typedef {object} GlobeEngine
|
||||
* @property {'cesium'|'navara'} kind
|
||||
* @property {{ container: object, canvas: object, size: () => { width: number, height: number } }} host
|
||||
@@ -174,17 +205,7 @@
|
||||
* As in Cesium, a listener with a modifier fires only while that key is
|
||||
* held and one without fires only while no modifier is held.
|
||||
* @property {{ addStatic: (html: string, options?: { onScreen?: boolean }) => () => void }} credits
|
||||
* @property {{ setBaseImagery: (source: object) => Promise<void>, setTerrain: (source: object) => Promise<void>, setPhotorealTiles: (source: object | null) => Promise<void>, releasePhotorealTiles: (source: object) => void, photorealActive: () => boolean }} maps
|
||||
* Sources are maps/registry.js descriptions; the engine builds its own
|
||||
* providers. Each setter settles once the source is on screen and rejects
|
||||
* when it cannot be built (the current source then stays). A source may
|
||||
* carry a ready engine `provider` its caller built and owns (the engine
|
||||
* neither caches nor disposes it). Base imagery and photoreal tiles share
|
||||
* one switch order: the one asked for last wins, a superseded call settles
|
||||
* without showing anything. setPhotorealTiles(null) shows the globe again
|
||||
* with the last base imagery and its credit (no credit when none was set).
|
||||
* releasePhotorealTiles forgets a tileset source shown before and takes it
|
||||
* out of the scene when the engine put it there; its caller disposes it.
|
||||
* @property {GlobeMaps} maps
|
||||
* @property {() => void} destroy
|
||||
*/
|
||||
|
||||
@@ -364,6 +385,7 @@ const SHAPE = {
|
||||
setPhotorealTiles: isFunction,
|
||||
releasePhotorealTiles: isFunction,
|
||||
photorealActive: isFunction,
|
||||
current: isFunction,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -191,6 +191,7 @@ test('the contract carries what the shell infrastructure needs', () => {
|
||||
'camera.livePosition',
|
||||
'camera.height',
|
||||
'maps.releasePhotorealTiles',
|
||||
'maps.current',
|
||||
])
|
||||
assert.ok(GLOBE_ENGINE_MEMBERS.includes(member), member);
|
||||
const engine = createFakeEngine();
|
||||
@@ -209,8 +210,13 @@ test('the fake engine passes the engine contract suite', async (t) => {
|
||||
const engine = createFakeEngine();
|
||||
const checks = createContractSuite(engine, {
|
||||
waitFrames: async (count) => engine.fake.frames(count),
|
||||
mapSources: {
|
||||
imagery: [{ kind: 'osm' }, { kind: 'esri-imagery' }],
|
||||
photoreal: { kind: 'photoreal' },
|
||||
},
|
||||
});
|
||||
assert.ok(checks.length >= 15);
|
||||
assert.ok(checks.some(({ name }) => name.startsWith('maps:')));
|
||||
assert.equal(new Set(checks.map((check) => check.name)).size, checks.length);
|
||||
for (const check of checks) await t.test(check.name, () => check.run());
|
||||
});
|
||||
|
||||
@@ -69,12 +69,15 @@ const along = (origin, direction, distance) =>
|
||||
|
||||
/**
|
||||
* @param {import('../contract.js').GlobeEngine} engine
|
||||
* @param {{ waitFrames: (count: number) => Promise<void>, maxWaitFrames?: number }} options
|
||||
* @param {{ waitFrames: (count: number) => Promise<void>, maxWaitFrames?: number,
|
||||
* mapSources?: { imagery: object[], photoreal: object } }} options
|
||||
* `mapSources` (two imagery sources and a photoreal one the engine can
|
||||
* show) adds the maps checks; they leave photoreal tiles hidden.
|
||||
* @returns {{ name: string, run: () => Promise<void> }[]}
|
||||
*/
|
||||
export function createContractSuite(
|
||||
engine,
|
||||
{ waitFrames, maxWaitFrames = 240 },
|
||||
{ waitFrames, maxWaitFrames = 240, mapSources = null },
|
||||
) {
|
||||
if (typeof waitFrames !== 'function')
|
||||
throw new TypeError('createContractSuite needs a waitFrames(count) helper');
|
||||
@@ -1378,9 +1381,82 @@ export function createContractSuite(
|
||||
},
|
||||
],
|
||||
];
|
||||
if (mapSources?.photoreal && mapSources.imagery?.length >= 2)
|
||||
checks.push(...mapChecks(engine.maps, mapSources));
|
||||
return checks.map(([name, run]) => ({ name, run }));
|
||||
}
|
||||
|
||||
// The maps switch order, observed through maps.current().
|
||||
function mapChecks(maps, { imagery: [first, second], photoreal }) {
|
||||
const shows = (baseImagery, tiles, label) => {
|
||||
const current = maps.current();
|
||||
ensure(
|
||||
current.baseImagery === baseImagery,
|
||||
`${label}: base imagery is not the expected source`,
|
||||
);
|
||||
ensure(
|
||||
current.photoreal === tiles,
|
||||
`${label}: photoreal tiles are not the expected source`,
|
||||
);
|
||||
ensure(
|
||||
maps.photorealActive() === (tiles !== null),
|
||||
`${label}: photorealActive is ${maps.photorealActive()}`,
|
||||
);
|
||||
};
|
||||
return [
|
||||
[
|
||||
'maps: setPhotorealTiles(null) brings back the last base imagery',
|
||||
async () => {
|
||||
await maps.setBaseImagery(first);
|
||||
shows(first, null, 'after setBaseImagery');
|
||||
await maps.setPhotorealTiles(photoreal);
|
||||
shows(null, photoreal, 'tiles replace the base imagery');
|
||||
await maps.setPhotorealTiles(null);
|
||||
shows(first, null, 'null');
|
||||
},
|
||||
],
|
||||
[
|
||||
'maps: the last imagery or photoreal call decides what shows',
|
||||
async () => {
|
||||
await maps.setBaseImagery(first);
|
||||
await Promise.all([
|
||||
maps.setBaseImagery(second),
|
||||
maps.setPhotorealTiles(photoreal),
|
||||
]);
|
||||
shows(null, photoreal, 'tiles last');
|
||||
await maps.setPhotorealTiles(null);
|
||||
const restored = maps.current().baseImagery;
|
||||
// The second source was superseded before or after it showed.
|
||||
ensure(
|
||||
restored === first || restored === second,
|
||||
'null: the last base imagery shown is not back',
|
||||
);
|
||||
await Promise.all([
|
||||
maps.setPhotorealTiles(photoreal),
|
||||
maps.setBaseImagery(second),
|
||||
]);
|
||||
ensure(
|
||||
maps.current().baseImagery === second,
|
||||
'imagery last: the second source is not on the globe',
|
||||
);
|
||||
await maps.setPhotorealTiles(null);
|
||||
shows(second, null, 'null after imagery');
|
||||
},
|
||||
],
|
||||
[
|
||||
'maps.releasePhotorealTiles: a released source no longer shows or counts',
|
||||
async () => {
|
||||
await maps.setPhotorealTiles(photoreal);
|
||||
await maps.setPhotorealTiles(null);
|
||||
maps.releasePhotorealTiles(photoreal);
|
||||
maps.releasePhotorealTiles(photoreal);
|
||||
ensure(!maps.photorealActive(), 'photorealActive after release');
|
||||
ensure(maps.current().photoreal === null, 'current after release');
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/** Runs every check in order; never throws for a failing check. */
|
||||
export async function runContractSuite(engine, options) {
|
||||
const results = [];
|
||||
|
||||
@@ -241,6 +241,7 @@ export function createFakeEngine(options = {}) {
|
||||
};
|
||||
const inputs = new Map(INPUT_TYPES.map((type) => [type, new Set()]));
|
||||
const livePosition = cartesian3.create();
|
||||
let lastBaseImagery = null;
|
||||
const scripts = new Map();
|
||||
let transform = [...matrix4.IDENTITY];
|
||||
let percentageChanged = 0.5;
|
||||
@@ -819,10 +820,13 @@ export function createFakeEngine(options = {}) {
|
||||
};
|
||||
},
|
||||
},
|
||||
// The contract's switch order: a call shows its source when made (the
|
||||
// fake builds nothing), so the call made last decides what shows.
|
||||
maps: {
|
||||
async setBaseImagery(source) {
|
||||
record('maps.setBaseImagery', [source]);
|
||||
fake.maps.imagery = source;
|
||||
lastBaseImagery = source;
|
||||
},
|
||||
async setTerrain(source) {
|
||||
record('maps.setTerrain', [source]);
|
||||
@@ -830,12 +834,23 @@ export function createFakeEngine(options = {}) {
|
||||
},
|
||||
async setPhotorealTiles(source) {
|
||||
record('maps.setPhotorealTiles', [source]);
|
||||
fake.maps.photoreal = source ?? null;
|
||||
if (source == null) {
|
||||
fake.maps.photoreal = null;
|
||||
fake.maps.imagery = lastBaseImagery;
|
||||
return;
|
||||
}
|
||||
fake.maps.photoreal = source;
|
||||
fake.maps.imagery = null;
|
||||
},
|
||||
releasePhotorealTiles(source) {
|
||||
record('maps.releasePhotorealTiles', [source]);
|
||||
if (fake.maps.photoreal === source) fake.maps.photoreal = null;
|
||||
},
|
||||
photorealActive: () => fake.maps.photoreal != null,
|
||||
current: () => ({
|
||||
baseImagery: fake.maps.imagery ?? null,
|
||||
photoreal: fake.maps.photoreal,
|
||||
}),
|
||||
},
|
||||
destroy() {
|
||||
record('destroy', []);
|
||||
|
||||
Reference in new issue
Block a user