fix(awareness): give the pulse material a named type so Cesium caches it once

Cesium keeps every material type in a global cache for the page's
lifetime. The pulse disc's fabric had no type, so each new animated
visual (a subject switch, a reselect, reduced motion turned off) added a
GUID-named entry that was never removed. Name the type, and give each
material built from the cached template its own uniform objects back.
This commit is contained in:
Sameh Khamis committed 2026-11-03 10:39:06 -08:00
1 parent a851199f5b
commit fe3d17df02
2 files changed
+43 -1

No files matched your search

+30
View File
@@ -774,6 +774,36 @@ test('a gradient disc behind the pulses fades inward and follows the subject', (
assert.equal(scene.preRender.numberOfListeners, 0);
});
test("switching subjects keeps one pulse material in Cesium's global cache", (t) => {
const ring = ringRendering(t);
const { scene, rendering } = ring;
frameLoop(t, ring);
// Cesium keeps every new material type for the page's lifetime, so an
// unnamed fabric (a fresh GUID type each time) would grow it per subject.
const cached = () =>
Object.keys(Cesium.Material._materialCache._materials).length;
rendering.renderVisual({ ...flight(3), id: 'subject-0' });
const first = pulsePrimitives(scene).disc.appearance.material;
const size = cached();
let last = first;
for (let index = 1; index <= 6; index++) {
rendering.renderVisual({ ...flight(3 + index), id: `subject-${index}` });
last = pulsePrimitives(scene).disc.appearance.material;
}
assert.equal(cached(), size, 'subject switches add no cache entries');
rendering.clearVisual();
rendering.renderVisual(flight(3));
assert.equal(cached(), size, 'nor does a reselect');
// A material built from the cached template still owns its uniform values.
const again = pulsePrimitives(scene).disc.appearance.material;
assert.notEqual(again, last);
assert.notEqual(again.uniforms.radii, last.uniforms.radii);
assert.notEqual(again.uniforms.alphas, first.uniforms.alphas);
assert.ok(again.uniforms.radii instanceof Cesium.Cartesian4);
assert.ok(again.uniforms.lineColor instanceof Cesium.Color);
assert.equal(again.uniforms.lineWidth, AWARENESS_RANGE_PULSE.width);
});
test('a resting pulse disc is not rebuilt as the subject moves, and catches up on wake', (t) => {
const ring = ringRendering(t);
const { scene, rendering } = ring;
+13 -1
View File
@@ -176,6 +176,9 @@ export function awarenessRingFlash(nowMs) {
/** Most pulses the pulse shader draws; its uniforms are vec4s. */
export const AWARENESS_PULSE_GRADIENT_MAX = 4;
/** The pulse disc's Cesium material type, one cached template per page. */
const AWARENESS_PULSE_MATERIAL_TYPE = 'GevAwarenessPulseGradient';
// Ellipse texture coordinates put the centre at (0.5, 0.5) and the rim at
// distance 0.5, so `r` is the fraction of the range. Each pulse is a line
// `lineWidth` CSS pixels wide whose outer `haloWidth` is a dark halo, half
@@ -804,10 +807,19 @@ export function createRendering({
haloWidth: pulse.outlineWidth,
};
uniforms.color.alpha = pulse.gradientOpacity;
// A named type: Cesium caches every material type for the page's
// lifetime, so an unnamed fabric (a fresh GUID each time) would leave
// one entry per new visual. Built from the cached template, the
// uniforms come back as plain copies; restore this visual's own.
const material = new Cesium.Material({
fabric: { uniforms, source: PULSE_GRADIENT_SOURCE },
fabric: {
type: AWARENESS_PULSE_MATERIAL_TYPE,
uniforms,
source: PULSE_GRADIENT_SOURCE,
},
translucent: true,
});
Object.assign(material.uniforms, uniforms);
gradient = scene.primitives.add(
createPulseGradient(groundCenter, material),
);