mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-08 04:14:02 +00:00
Give a viewer with no renderer an effects port, as cameras get
The renderer bridge builds and keeps an effects port over a viewer no renderer is bound to, with a factory the Cesium effects module provides, so visual settings on a consumer's own viewer draw their styles, bloom and sharpen as before and give the scene's bloom back when destroyed. The application shell loads the factory; a test checks every export that reaches the effects lookup loads it.
This commit is contained in:
1 parent
d0ef3a902b
commit
910a691494
8 files changed
+333
-64
No files matched your search
@@ -1000,6 +1000,7 @@
|
||||
"src/styles/surveillance.js",
|
||||
"src/styles/thermal.js",
|
||||
"src/ui/applicationShell.js",
|
||||
"src/render/cesium/effects.js",
|
||||
"src/data/followed.js",
|
||||
"src/render/cesium/camera.js",
|
||||
"src/render/cesium/credits.js",
|
||||
|
||||
Vendored
+68
-9
@@ -22,7 +22,8 @@
|
||||
* in its own shader, not the frame.
|
||||
*/
|
||||
import * as Cesium from 'cesium';
|
||||
import { listenerList } from './common.js';
|
||||
import { provideUnboundEffects } from '../rendererOf.js';
|
||||
import { listenerList, uncaught } from './common.js';
|
||||
|
||||
/** Below this a style, bloom or sharpen draws nothing, so it runs no pass. */
|
||||
const OFF = 0.001;
|
||||
@@ -135,6 +136,9 @@ export function createCesiumEffects(
|
||||
let disposed = false;
|
||||
const started = performance.now();
|
||||
|
||||
// Disposed, or the scene went first and took its stages with it.
|
||||
const gone = () => disposed || scene.isDestroyed?.() === true;
|
||||
|
||||
const drawing = (record) =>
|
||||
(record.id === current || fading.has(record.id)) && record.intensity > OFF;
|
||||
|
||||
@@ -197,7 +201,7 @@ export function createCesiumEffects(
|
||||
}
|
||||
|
||||
function changed(reason) {
|
||||
if (disposed) return;
|
||||
if (gone()) return;
|
||||
apply();
|
||||
frame.requestRender(reason);
|
||||
listeners.fire(port.diagnostics());
|
||||
@@ -217,7 +221,7 @@ export function createCesiumEffects(
|
||||
: [],
|
||||
defineStyle(definition) {
|
||||
if (
|
||||
disposed ||
|
||||
gone() ||
|
||||
!capabilities.has('styles') ||
|
||||
typeof definition?.shader?.glsl !== 'string'
|
||||
)
|
||||
@@ -260,7 +264,7 @@ export function createCesiumEffects(
|
||||
return () => {
|
||||
if (removed) return;
|
||||
removed = true;
|
||||
if (disposed || styles.get(id) !== record) return;
|
||||
if (gone() || styles.get(id) !== record) return;
|
||||
styles.delete(id);
|
||||
stages.remove(record.stage);
|
||||
fading.delete(id);
|
||||
@@ -269,7 +273,7 @@ export function createCesiumEffects(
|
||||
};
|
||||
},
|
||||
setStyle(id, params) {
|
||||
if (disposed || !capabilities.has('styles')) return;
|
||||
if (gone() || !capabilities.has('styles')) return;
|
||||
const next = id === null || id === undefined ? null : String(id);
|
||||
if (next !== null && !styles.has(next)) return;
|
||||
if (next !== current) {
|
||||
@@ -282,19 +286,19 @@ export function createCesiumEffects(
|
||||
changed('effects-style');
|
||||
},
|
||||
setStyleIntensity(id, intensity) {
|
||||
if (disposed || !known(id)) return;
|
||||
if (gone() || !known(id)) return;
|
||||
const record = styles.get(id);
|
||||
record.intensity = clamp01(intensity);
|
||||
if (record.intensity <= OFF) fading.delete(id);
|
||||
changed('effects-style');
|
||||
},
|
||||
setStyleParams(id, params) {
|
||||
if (disposed || !known(id)) return;
|
||||
if (gone() || !known(id)) return;
|
||||
assignParams(styles.get(id), params);
|
||||
changed('effects-style');
|
||||
},
|
||||
bloom({ enabled, strength } = {}) {
|
||||
if (disposed || !capabilities.has('bloom')) return;
|
||||
if (gone() || !capabilities.has('bloom')) return;
|
||||
const bloom = scene.postProcessStages.bloom;
|
||||
if (!bloomTaken) {
|
||||
bloomTaken = {
|
||||
@@ -311,7 +315,7 @@ export function createCesiumEffects(
|
||||
changed('effects-bloom');
|
||||
},
|
||||
sharpen({ enabled, amount } = {}) {
|
||||
if (disposed || !capabilities.has('sharpen')) return;
|
||||
if (gone() || !capabilities.has('sharpen')) return;
|
||||
sharpenEnabled = Boolean(enabled);
|
||||
if (amount !== undefined) sharpenAmount = clamp01(amount);
|
||||
if (!sharpenStage) makeSharpen();
|
||||
@@ -352,6 +356,13 @@ export function createCesiumEffects(
|
||||
disposed = true;
|
||||
stopClock();
|
||||
listeners.clear();
|
||||
// A destroyed scene took its stages with it.
|
||||
if (scene.isDestroyed?.()) {
|
||||
styles.clear();
|
||||
sharpenStage = null;
|
||||
bloomTaken = null;
|
||||
return;
|
||||
}
|
||||
for (const record of styles.values()) stages.remove(record.stage);
|
||||
styles.clear();
|
||||
if (sharpenStage) stages.remove(sharpenStage);
|
||||
@@ -365,3 +376,51 @@ export function createCesiumEffects(
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The frame port's members the effects port uses, straight over a scene no
|
||||
* renderer drives: a hold asks for the next frame after each one drawn.
|
||||
* @param {object} scene
|
||||
*/
|
||||
function sceneFrame(scene) {
|
||||
return {
|
||||
on: (phase, cb) =>
|
||||
scene[phase].addEventListener(() => cb({ now: performance.now() })),
|
||||
requestRender: () => scene.requestRender(),
|
||||
holdContinuous() {
|
||||
const off = scene.postRender.addEventListener(() =>
|
||||
scene.requestRender(),
|
||||
);
|
||||
scene.requestRender();
|
||||
let released = false;
|
||||
return () => {
|
||||
if (released) return;
|
||||
released = true;
|
||||
off();
|
||||
if (!scene.isDestroyed?.()) scene.requestRender();
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// A Cesium viewer of a consumer's own, with no renderer bound to it, gets an
|
||||
// effects port over its scene when code first asks `findEffects` for one.
|
||||
provideUnboundEffects((viewer) => {
|
||||
const scene = viewer.scene;
|
||||
// By shape, not `instanceof`: a consumer's viewer may come from another
|
||||
// copy of Cesium.
|
||||
if (
|
||||
typeof scene?.postProcessStages?.add !== 'function' ||
|
||||
typeof scene.postProcessStages.remove !== 'function' ||
|
||||
!scene.postProcessStages.bloom ||
|
||||
typeof scene.preRender?.addEventListener !== 'function' ||
|
||||
typeof scene.postRender?.addEventListener !== 'function' ||
|
||||
typeof scene.requestRender !== 'function'
|
||||
)
|
||||
return null;
|
||||
return createCesiumEffects(scene, {
|
||||
capabilities: new Set(['styles', 'bloom', 'sharpen']),
|
||||
frame: sceneFrame(scene),
|
||||
report: uncaught,
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { bloomUniforms, createCesiumEffects } from './effects.js';
|
||||
import { findEffects, releaseUnboundEffects } from '../rendererOf.js';
|
||||
|
||||
const ALL = new Set(['styles', 'bloom', 'sharpen']);
|
||||
|
||||
@@ -300,3 +301,90 @@ test('dispose removes every stage and releases the clock', () => {
|
||||
f.effects.setStyle('anim');
|
||||
assert.equal(f.stages.length, 0);
|
||||
});
|
||||
|
||||
test('a viewer with no renderer gets an effects port over its scene', () => {
|
||||
const events = () => {
|
||||
const cbs = new Set();
|
||||
return {
|
||||
cbs,
|
||||
addEventListener(cb) {
|
||||
cbs.add(cb);
|
||||
return () => cbs.delete(cb);
|
||||
},
|
||||
raise: () => [...cbs].forEach((cb) => cb()),
|
||||
};
|
||||
};
|
||||
const stages = [];
|
||||
let requests = 0;
|
||||
const scene = {
|
||||
postProcessStages: {
|
||||
bloom: { enabled: false, uniforms: {} },
|
||||
add: (stage) => stages.push(stage),
|
||||
remove: (stage) => stages.splice(stages.indexOf(stage), 1),
|
||||
},
|
||||
preRender: events(),
|
||||
postRender: events(),
|
||||
requestRender: () => (requests += 1),
|
||||
};
|
||||
const viewer = { scene };
|
||||
const effects = findEffects(viewer);
|
||||
assert.ok(effects, 'built');
|
||||
assert.equal(findEffects(viewer), effects, 'kept');
|
||||
assert.equal(findEffects({ scene: {} }), null, 'not a Cesium scene');
|
||||
effects.bloom({ enabled: true, strength: 1 });
|
||||
assert.equal(scene.postProcessStages.bloom.enabled, true);
|
||||
// An animated style draws every frame while it shows.
|
||||
effects.defineStyle(style('anim', { animated: true }));
|
||||
effects.setStyle('anim');
|
||||
assert.equal(scene.postRender.cbs.size, 1, 'held');
|
||||
const before = requests;
|
||||
scene.postRender.raise();
|
||||
assert.equal(requests, before + 1, 'the next frame is asked for');
|
||||
effects.setStyleIntensity('anim', 0);
|
||||
assert.equal(scene.postRender.cbs.size, 0, 'released');
|
||||
assert.equal(scene.preRender.cbs.size, 0);
|
||||
releaseUnboundEffects(viewer, {});
|
||||
assert.ok(stages.length > 0, 'another port is not let go of');
|
||||
releaseUnboundEffects(viewer, effects);
|
||||
assert.equal(stages.length, 0, 'let go: its passes are gone');
|
||||
assert.equal(
|
||||
scene.postProcessStages.bloom.enabled,
|
||||
false,
|
||||
'bloom given back',
|
||||
);
|
||||
assert.notEqual(findEffects(viewer), effects, 'a later lookup builds anew');
|
||||
});
|
||||
|
||||
test('a destroyed scene is let go of without touching it', () => {
|
||||
let destroyed = false;
|
||||
const scene = {
|
||||
isDestroyed: () => destroyed,
|
||||
postProcessStages: {
|
||||
bloom: { enabled: false, uniforms: {} },
|
||||
add() {},
|
||||
remove() {
|
||||
if (destroyed) throw new Error('destroyed');
|
||||
},
|
||||
},
|
||||
preRender: { addEventListener: () => () => {} },
|
||||
postRender: { addEventListener: () => () => {} },
|
||||
requestRender() {
|
||||
if (destroyed) throw new Error('destroyed');
|
||||
},
|
||||
};
|
||||
const viewer = { scene, isDestroyed: () => destroyed };
|
||||
const effects = findEffects(viewer);
|
||||
effects.defineStyle(style('anim', { animated: true }));
|
||||
effects.setStyle('anim');
|
||||
effects.bloom({ enabled: true, strength: 1 });
|
||||
effects.sharpen({ enabled: true, amount: 1 });
|
||||
const off = effects.defineStyle(style('plain'));
|
||||
destroyed = true;
|
||||
// Core's own teardown after the scene went: nothing throws.
|
||||
off();
|
||||
effects.bloom({ enabled: false });
|
||||
effects.sharpen({ enabled: false });
|
||||
effects.setStyle(null);
|
||||
releaseUnboundEffects(viewer, effects);
|
||||
assert.equal(findEffects(viewer), null);
|
||||
});
|
||||
+98
-45
@@ -9,15 +9,59 @@
|
||||
const renderers = new WeakMap();
|
||||
|
||||
/**
|
||||
* Camera ports built for scene objects no renderer is bound to (a viewer of
|
||||
* a consumer's own, say), each `{port, dispose}` and kept for as long as its
|
||||
* scene object lives, or until a renderer is bound to it.
|
||||
* Ports built for scene objects no renderer is bound to (a viewer of a
|
||||
* consumer's own, say), per port: each `{port, dispose}` kept for as long
|
||||
* as its scene object lives, or until a renderer is bound to it. The
|
||||
* renderer adapter provides the factory each is built with as it loads.
|
||||
*/
|
||||
const unboundCameras = new WeakMap();
|
||||
let buildUnboundCamera = null;
|
||||
/** Credits ports built likewise, each `{port, dispose, release}`. */
|
||||
const unboundCredits = new WeakMap();
|
||||
let buildUnboundCredits = null;
|
||||
function unboundPorts(member) {
|
||||
const built = new WeakMap();
|
||||
let build = null;
|
||||
return {
|
||||
provide(factory) {
|
||||
build = factory;
|
||||
},
|
||||
drop(host, port) {
|
||||
const entry = built.get(host);
|
||||
if (!entry || (port !== undefined && entry.port !== port)) return;
|
||||
built.delete(host);
|
||||
entry.dispose();
|
||||
},
|
||||
/**
|
||||
* A renderer bound to `host` supersedes its built port: released when
|
||||
* it can be (it stops working for new callers but keeps what it did),
|
||||
* else disposed.
|
||||
*/
|
||||
supersede(host) {
|
||||
const entry = built.get(host);
|
||||
if (!entry) return;
|
||||
built.delete(host);
|
||||
if (entry.release) entry.release();
|
||||
else entry.dispose();
|
||||
},
|
||||
find(host) {
|
||||
const port = rendererOf(host)?.[member];
|
||||
if (port) return port;
|
||||
if (!host || typeof host !== 'object') return null;
|
||||
if (host.isDestroyed?.()) {
|
||||
// Gone with its scene: let the built port go with it.
|
||||
this.drop(host);
|
||||
return null;
|
||||
}
|
||||
let entry = built.get(host);
|
||||
if (!entry && build) {
|
||||
entry = build(host);
|
||||
if (entry) built.set(host, entry);
|
||||
}
|
||||
return entry?.port ?? null;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const unboundCameras = unboundPorts('camera');
|
||||
const unboundEffects = unboundPorts('effects');
|
||||
/** Credits ports are `{port, dispose, release}`: see `bindRenderer`. */
|
||||
const unboundCredits = unboundPorts('credits');
|
||||
|
||||
/**
|
||||
* Provide the factory `cameraOf` builds a camera port with for a scene
|
||||
@@ -26,7 +70,7 @@ let buildUnboundCredits = null;
|
||||
* @param {(host: object) => ({port: object, dispose: () => void} | null)} build
|
||||
*/
|
||||
export function provideUnboundCamera(build) {
|
||||
buildUnboundCamera = build;
|
||||
unboundCameras.provide(build);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,27 +81,45 @@ export function provideUnboundCamera(build) {
|
||||
* release: () => void} | null)} build
|
||||
*/
|
||||
export function provideUnboundCredits(build) {
|
||||
buildUnboundCredits = build;
|
||||
unboundCredits.provide(build);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a renderer to its scene object. A camera `cameraOf` built for it
|
||||
* while it had no renderer is disposed: its listeners hear nothing more,
|
||||
* and `cameraOf` answers with the renderer's camera from then on.
|
||||
* Provide the factory `findEffects` builds an effects port with for a
|
||||
* scene object no renderer is bound to, as `provideUnboundCamera` does.
|
||||
* @param {(host: object) => ({port: object, dispose: () => void} | null)} build
|
||||
*/
|
||||
export function provideUnboundEffects(build) {
|
||||
unboundEffects.provide(build);
|
||||
}
|
||||
|
||||
/**
|
||||
* Let go of `port`, if it is the effects port `findEffects` built for
|
||||
* `host`: its passes go and the scene's own settings come back. A
|
||||
* renderer's own port, or one built later, is left alone.
|
||||
* @param {object|null|undefined} host
|
||||
* @param {object|null|undefined} port
|
||||
*/
|
||||
export function releaseUnboundEffects(host, port) {
|
||||
if (host && typeof host === 'object' && port) unboundEffects.drop(host, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a renderer to its scene object. A port `cameraOf` or `findEffects`
|
||||
* built for it while it had no renderer is disposed: its listeners hear
|
||||
* nothing more, and they answer with the renderer's ports from then on.
|
||||
* A credits port `findCredits` built is released, not disposed: the
|
||||
* credits added through it stay shown until their own Off.
|
||||
* @param {object} host The scene object callers hold.
|
||||
* @param {object} renderer
|
||||
* @returns {() => void} Unbinds, if the binding is still this one.
|
||||
*/
|
||||
export function bindRenderer(host, renderer) {
|
||||
if (renderers.has(host)) throw new Error('This scene already has a renderer');
|
||||
// The renderer's camera supersedes one built while there was none.
|
||||
unboundCameras.get(host)?.dispose();
|
||||
unboundCameras.delete(host);
|
||||
// Its credits port does too for credits added from now on; one built
|
||||
// before is released, not disposed: the credits added through it stay
|
||||
// shown until their own Off.
|
||||
unboundCredits.get(host)?.release?.();
|
||||
unboundCredits.delete(host);
|
||||
// The renderer's ports supersede ones built while there was none.
|
||||
unboundCameras.drop(host);
|
||||
unboundEffects.drop(host);
|
||||
unboundCredits.supersede(host);
|
||||
renderers.set(host, renderer);
|
||||
return () => {
|
||||
if (renderers.get(host) === renderer) renderers.delete(host);
|
||||
@@ -79,10 +141,20 @@ export const rendererOf = (host) => (host && renderers.get(host)) || null;
|
||||
* has no renderer and no camera can be built for it, or is destroyed.
|
||||
*/
|
||||
export function findCamera(host) {
|
||||
return (
|
||||
rendererOf(host)?.camera ??
|
||||
findUnbound(host, unboundCameras, buildUnboundCamera)
|
||||
);
|
||||
return unboundCameras.find(host);
|
||||
}
|
||||
|
||||
/**
|
||||
* The effects port of the renderer bound to `host`; with no renderer bound,
|
||||
* one built over `host` by the provided factory and kept with it while
|
||||
* `host` lives.
|
||||
* @param {object|null|undefined} host
|
||||
* @returns {import('./contract/effects').EffectsPort | null} Null when
|
||||
* `host` has no renderer and no effects can be built for it, or is
|
||||
* destroyed.
|
||||
*/
|
||||
export function findEffects(host) {
|
||||
return unboundEffects.find(host);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,26 +165,7 @@ export function findCamera(host) {
|
||||
* @returns {import('./contract/credits').CreditsPort | null}
|
||||
*/
|
||||
export function findCredits(host) {
|
||||
return (
|
||||
rendererOf(host)?.credits ??
|
||||
findUnbound(host, unboundCredits, buildUnboundCredits)
|
||||
);
|
||||
}
|
||||
|
||||
function findUnbound(host, built, build) {
|
||||
if (!host || typeof host !== 'object') return null;
|
||||
if (host.isDestroyed?.()) {
|
||||
// Gone with its scene: let the built port go with it.
|
||||
built.get(host)?.dispose();
|
||||
built.delete(host);
|
||||
return null;
|
||||
}
|
||||
let part = built.get(host);
|
||||
if (!part && build) {
|
||||
part = build(host);
|
||||
if (part) built.set(host, part);
|
||||
}
|
||||
return part?.port ?? null;
|
||||
return unboundCredits.find(host);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,8 +5,10 @@ import {
|
||||
cameraOf,
|
||||
findCamera,
|
||||
findCredits,
|
||||
findEffects,
|
||||
provideUnboundCamera,
|
||||
provideUnboundCredits,
|
||||
provideUnboundEffects,
|
||||
rendererOf,
|
||||
} from './rendererOf.js';
|
||||
|
||||
@@ -130,3 +132,36 @@ test('with no renderer bound, findCredits builds one port per scene and keeps it
|
||||
assert.equal(findCredits(gone), null);
|
||||
assert.equal(built.find((entry) => entry.port === port).disposed, 1);
|
||||
});
|
||||
|
||||
test('with no renderer bound, findEffects builds one effects port per scene until a renderer is bound', (t) => {
|
||||
t.after(() => provideUnboundEffects(null));
|
||||
const built = [];
|
||||
provideUnboundEffects((host) => {
|
||||
if (!host.scene) return null;
|
||||
const entry = { port: { id: built.length }, disposed: 0 };
|
||||
entry.dispose = () => (entry.disposed += 1);
|
||||
built.push(entry);
|
||||
return entry;
|
||||
});
|
||||
const viewer = { scene: {} };
|
||||
assert.equal(findEffects(viewer), built[0].port);
|
||||
assert.equal(findEffects(viewer), built[0].port, 'kept, not rebuilt');
|
||||
assert.equal(findEffects({}), null, 'nothing to build over');
|
||||
assert.equal(findEffects(null), null);
|
||||
const effects = { styles() {} };
|
||||
const unbind = bindRenderer(viewer, { effects });
|
||||
assert.equal(built[0].disposed, 1, 'the renderer supersedes the built port');
|
||||
assert.equal(findEffects(viewer), effects);
|
||||
unbind();
|
||||
assert.equal(
|
||||
findEffects(viewer),
|
||||
built[1].port,
|
||||
'unbound again: a fresh one',
|
||||
);
|
||||
let destroyed = false;
|
||||
const gone = { scene: {}, isDestroyed: () => destroyed };
|
||||
findEffects(gone);
|
||||
destroyed = true;
|
||||
assert.equal(findEffects(gone), null);
|
||||
assert.equal(built[2].disposed, 1, 'let go with its scene');
|
||||
});
|
||||
@@ -5,6 +5,8 @@
|
||||
* provides as `render/cesium/camera.js` loads, so every such entry must load
|
||||
* that module too. It loads `render/cesium/credits.js`, whose factory
|
||||
* builds `findCredits`' port for such a viewer the same way.
|
||||
* Likewise an entry that reaches a caller of `findEffects` must load
|
||||
* `render/cesium/effects.js`, which provides the effects factory.
|
||||
*
|
||||
* The graph is every static import, re-export and literal dynamic import of
|
||||
* the entry's module and those it reaches, read by the parsers the Cesium
|
||||
@@ -24,26 +26,26 @@ const root = path.resolve(
|
||||
);
|
||||
const BRIDGE = 'src/render/rendererOf.js';
|
||||
const FACTORY = 'src/render/cesium/camera.js';
|
||||
const EFFECTS_FACTORY = 'src/render/cesium/effects.js';
|
||||
|
||||
/**
|
||||
* Entries that take their Cesium code injected: the module named here is
|
||||
* GEV's Cesium side for them, and must load the factory. Given another, the
|
||||
* entry's flight finds no camera (`findCamera`) and does nothing.
|
||||
* GEV's Cesium side for them, and must load the factories. Given another, the
|
||||
* entry's flight finds no camera (`findCamera`) and does nothing. Visual
|
||||
* settings are given their viewer by the application shell, which loads
|
||||
* both factories; without it, their styles keep records and draw nothing.
|
||||
*/
|
||||
const INJECTED = {
|
||||
'./layers/recent-imagery': 'src/layers/recentImagery/rendering.js',
|
||||
'./ui/visual-settings': 'src/ui/applicationShell.js',
|
||||
};
|
||||
|
||||
/**
|
||||
* Modules whose use of the bridge holds up without the factory: with no
|
||||
* port they read Cesium's `trackedEntity` instead, which is all an unbound
|
||||
* viewer can follow by; visual settings ask for the renderer's effects
|
||||
* only, never a camera, and keep their own records without one.
|
||||
* viewer can follow by.
|
||||
*/
|
||||
const FALLS_BACK = new Set([
|
||||
'src/data/followed.js',
|
||||
'src/ui/visualSettings.js',
|
||||
]);
|
||||
const FALLS_BACK = new Set(['src/data/followed.js']);
|
||||
|
||||
const pkg = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8'));
|
||||
|
||||
@@ -163,3 +165,29 @@ test('every public entry that reaches the renderer bridge loads the unbound came
|
||||
`These entries can reach cameraOf without ${FACTORY}; import it where the entry first reaches the bridge.`,
|
||||
);
|
||||
});
|
||||
|
||||
test('every public entry that reaches a findEffects caller loads the unbound effects factory', () => {
|
||||
const missing = [];
|
||||
let reaching = 0;
|
||||
const callsFindEffects = (file) =>
|
||||
!file.startsWith('src/render/') &&
|
||||
/\.(m?js|vue)$/.test(file) &&
|
||||
readFileSync(path.join(root, file), 'utf8').includes('findEffects(');
|
||||
for (const [key, declaration] of Object.entries(pkg.exports)) {
|
||||
const target = browserTarget(declaration);
|
||||
if (typeof target !== 'string' || !target.startsWith('./src/')) continue;
|
||||
const entry = path.normalize(target.slice(2));
|
||||
if (![...reachable(entry)].some(callsFindEffects)) continue;
|
||||
reaching += 1;
|
||||
const carrier = INJECTED[key];
|
||||
const loads = (file) =>
|
||||
reachable(file, { eager: true }).has(EFFECTS_FACTORY);
|
||||
if (!loads(entry) && !(carrier && loads(carrier))) missing.push(key);
|
||||
}
|
||||
assert.ok(reaching > 0, 'no entry reaches findEffects: the walk is broken');
|
||||
assert.deepEqual(
|
||||
missing,
|
||||
[],
|
||||
`These entries can reach findEffects without ${EFFECTS_FACTORY}; import it where the entry first reaches the lookup.`,
|
||||
);
|
||||
});
|
||||
@@ -32,6 +32,9 @@ import {
|
||||
|
||||
import * as Cesium from 'cesium';
|
||||
import { cameraOf } from '../render/cesium/camera.js';
|
||||
// Loads the effects factory, so visual settings on a viewer with no renderer
|
||||
// bound still get an effects port.
|
||||
import '../render/cesium/effects.js';
|
||||
|
||||
import { aircraftTrackingTarget } from '../cockpitTracking.js';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useDisplayStore } from './stores/display.js';
|
||||
import { applicationPinia } from './vueRuntime.js';
|
||||
import { onLocaleChange, t } from '../i18n/index.js';
|
||||
import { UiLifetime } from './uiLifetime.js';
|
||||
import { rendererOf } from '../render/rendererOf.js';
|
||||
import { findEffects, releaseUnboundEffects } from '../render/rendererOf.js';
|
||||
import {
|
||||
VisualEffects,
|
||||
STYLES,
|
||||
@@ -196,7 +196,7 @@ export class VisualSettings {
|
||||
setStyleParameter: (name, value) => this.setStyleParameter(name, value),
|
||||
});
|
||||
this._visualEffects = new VisualEffects({
|
||||
effects: rendererOf(viewer)?.effects ?? null,
|
||||
effects: findEffects(viewer),
|
||||
viewer,
|
||||
requestRender: services.governorRequestRender,
|
||||
holdRender: services.holdContinuousRender,
|
||||
@@ -1621,5 +1621,7 @@ export class VisualSettings {
|
||||
this.stop();
|
||||
this.releaseIrBoost();
|
||||
this._visualEffects.destroy();
|
||||
// On a viewer of a consumer's own, its bloom comes back as it was.
|
||||
releaseUnboundEffects(this.viewer, this._visualEffects.effects);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user