diff --git a/src/layers/awareness/rangeRing.test.mjs b/src/layers/awareness/rangeRing.test.mjs index 17caa3bbc..db2758f56 100644 --- a/src/layers/awareness/rangeRing.test.mjs +++ b/src/layers/awareness/rangeRing.test.mjs @@ -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; diff --git a/src/layers/awareness/rendering.js b/src/layers/awareness/rendering.js index 2611dcbfb..969ffe5d5 100644 --- a/src/layers/awareness/rendering.js +++ b/src/layers/awareness/rendering.js @@ -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), );