@@ -45,6 +45,9 @@ const BOUNDING_BOX_ALGORITHM_ID = 205;
4545const BOUNDING_BOX_OUTLINE_ID = 206 ;
4646const BOUNDING_BOX_AXES_ID = 207 ;
4747
48+ /** Three actor node ids for the edge fan-out. Hand-written literals. */
49+ const FAN_OUT_ACTOR_IDS = [ 11 , 12 , 13 ] ;
50+
4851/** What the wasm camera reports for `GetParallelProjection()`. */
4952const PARALLEL = 1 ;
5053const PERSPECTIVE = 0 ;
@@ -102,21 +105,23 @@ function makeFakeWasmObjects() {
102105/**
103106 * A scene-graph stand-in for `attachSceneGraph`.
104107 *
105- * `EdgesWidget` fans out through the graph node's own
106- * `setEdgeVisibilityAsync`; `BoundingBoxWidget` enumerates
107- * `descendantActorNodesOrSelfArray` when recomputing bounds. Neither is the
108- * subject here -- what is pinned is the *send* that follows the local write.
108+ * `EdgesWidget` enumerates `descendantActorNodesOrSelfArray` and calls the
109+ * renderer's own per-actor `setEdgeVisibilityAsync` for each node's `id`;
110+ * `BoundingBoxWidget` enumerates the same array when recomputing bounds.
111+ * Neither is the subject of the send tests -- what those pin is the *send*
112+ * that follows the local write, and they pass an empty actor list so the
113+ * fan-out reaches nothing.
109114 */
110- function makeSceneGraphDouble ( ) {
115+ function makeSceneGraphDouble ( actorIds : number [ ] = [ ] ) {
111116 return {
112- setEdgeVisibilityAsync : jest . fn ( async ( ) => undefined ) ,
113- descendantActorNodesOrSelfArray : [ ] ,
117+ descendantActorNodesOrSelfArray : actorIds . map ( ( id ) => ( { id } ) ) ,
114118 } ;
115119}
116120
117121async function makeRenderer (
118122 sender : TrameTriggerSender | null ,
119- parallelProjection : number = PERSPECTIVE
123+ parallelProjection : number = PERSPECTIVE ,
124+ actorIds : number [ ] = [ ]
120125) {
121126 const objects = makeFakeWasmObjects ( ) ;
122127 const camera = {
@@ -145,7 +150,7 @@ async function makeRenderer(
145150 makeAnnotation ( ) ,
146151 sender
147152 ) ;
148- const sceneGraph = makeSceneGraphDouble ( ) ;
153+ const sceneGraph = makeSceneGraphDouble ( actorIds ) ;
149154 renderer . attachSceneGraph ( sceneGraph as unknown as VisorSceneNodeExtended ) ;
150155 return { renderer, camera, sceneGraph, ...objects } ;
151156}
@@ -241,6 +246,32 @@ describe('WasmRenderer widget sends: no sender, and a failing sender', () => {
241246 } ) ;
242247} ) ;
243248
249+ describe ( 'WasmRenderer edge fan-out reaches every actor on every call' , ( ) => {
250+ test ( 'setEdgeVisibilityGlobalAsync issues one per-actor call per actor on every call, not only when the value changes' , async ( ) => {
251+ // The per-node cache that D1 deleted lived on the scene-graph node
252+ // and had no invalidation, so a second toggle to the same value was
253+ // a no-op and three of the six pairs below were absent. This is an
254+ // id set with its value, not a call count: a count would not say
255+ // which actors were reached.
256+ const { renderer } = await makeRenderer ( makeSender ( ) , PERSPECTIVE , FAN_OUT_ACTOR_IDS ) ;
257+ const perActor = jest . spyOn ( renderer , 'setEdgeVisibilityAsync' ) ;
258+
259+ await renderer . setEdgeVisibilityGlobalAsync ( true ) ;
260+ await renderer . setEdgeVisibilityGlobalAsync ( true ) ;
261+
262+ expect ( perActor . mock . calls ) . toEqual ( [
263+ [ 11 , true ] ,
264+ [ 12 , true ] ,
265+ [ 13 , true ] ,
266+ [ 11 , true ] ,
267+ [ 12 , true ] ,
268+ [ 13 , true ] ,
269+ ] ) ;
270+
271+ perActor . mockRestore ( ) ;
272+ } ) ;
273+ } ) ;
274+
244275describe ( 'WasmRenderer.createAsync seeds the orthographic flag from the wasm camera' , ( ) => {
245276 test ( 'isOrthographicEnabled is true when the wasm camera reports parallel, before any setter runs' , async ( ) => {
246277 const { renderer } = await makeRenderer ( makeSender ( ) , PARALLEL ) ;
0 commit comments