@@ -89,19 +89,26 @@ impl Node {
8989 return false ;
9090 }
9191
92- // Drop when the node does not have any inputs and outputs
93- if !self . has_inputs_connected && self . outgoing_edges . is_empty ( ) {
94- return true ;
92+ // When the nodes has no incoming connections:
93+ if !self . has_inputs_connected {
94+ // Drop when the processor reports it won't yield output.
95+ if !tail_time {
96+ return true ;
97+ }
98+
99+ // Drop when the node does not have any inputs and outputs
100+ if self . outgoing_edges . is_empty ( ) {
101+ return true ;
102+ }
95103 }
96104
97- // Drop when the node does not have any inputs connected,
98- // and if the processor reports it won't yield output.
99- if !self . has_inputs_connected && !tail_time {
105+ // Node has no control handle and does have inputs connected.
106+ // Drop when the processor when it has no outputs connected and does not have side effects
107+ if !self . processor . has_side_effects ( ) && self . outgoing_edges . is_empty ( ) {
100108 return true ;
101109 }
102110
103111 // Otherwise, do not drop the node.
104- // (Even if it has no outputs connected, it may have side effects)
105112 false
106113 }
107114
@@ -193,8 +200,8 @@ impl Graph {
193200 }
194201
195202 pub fn add_edge ( & mut self , source : ( AudioNodeId , usize ) , dest : ( AudioNodeId , usize ) ) {
196- self . nodes [ source . 0 ]
197- . get_mut ( )
203+ self . nodes
204+ . get_unchecked_mut ( source . 0 )
198205 . outgoing_edges
199206 . push ( OutgoingEdge {
200207 self_index : source. 1 ,
@@ -206,8 +213,8 @@ impl Graph {
206213 }
207214
208215 pub fn remove_edge ( & mut self , source : AudioNodeId , dest : AudioNodeId ) {
209- self . nodes [ source ]
210- . get_mut ( )
216+ self . nodes
217+ . get_unchecked_mut ( source )
211218 . outgoing_edges
212219 . retain ( |edge| edge. other_id != dest) ;
213220
@@ -216,7 +223,7 @@ impl Graph {
216223
217224 pub fn remove_edges_from ( & mut self , source : AudioNodeId ) {
218225 // Remove outgoing edges
219- self . nodes [ source ] . get_mut ( ) . outgoing_edges . clear ( ) ;
226+ self . nodes . get_unchecked_mut ( source ) . outgoing_edges . clear ( ) ;
220227
221228 // Remove incoming edges - we need to traverse all nodes
222229 self . nodes . values_mut ( ) . for_each ( |node| {
@@ -241,21 +248,27 @@ impl Graph {
241248 }
242249
243250 pub fn mark_cycle_breaker ( & mut self , index : AudioNodeId ) {
244- self . nodes [ index ] . get_mut ( ) . cycle_breaker = true ;
251+ self . nodes . get_unchecked_mut ( index ) . cycle_breaker = true ;
245252 }
246253
247254 pub fn set_channel_count ( & mut self , index : AudioNodeId , v : usize ) {
248- self . nodes [ index ] . get_mut ( ) . channel_config . count = v;
255+ self . nodes . get_unchecked_mut ( index ) . channel_config . count = v;
249256 }
250257 pub fn set_channel_count_mode ( & mut self , index : AudioNodeId , v : ChannelCountMode ) {
251- self . nodes [ index] . get_mut ( ) . channel_config . count_mode = v;
258+ self . nodes
259+ . get_unchecked_mut ( index)
260+ . channel_config
261+ . count_mode = v;
252262 }
253263 pub fn set_channel_interpretation ( & mut self , index : AudioNodeId , v : ChannelInterpretation ) {
254- self . nodes [ index] . get_mut ( ) . channel_config . interpretation = v;
264+ self . nodes
265+ . get_unchecked_mut ( index)
266+ . channel_config
267+ . interpretation = v;
255268 }
256269
257270 pub fn route_message ( & mut self , index : AudioNodeId , msg : & mut dyn Any ) {
258- self . nodes [ index ] . get_mut ( ) . processor . onmessage ( msg) ;
271+ self . nodes . get_unchecked_mut ( index ) . processor . onmessage ( msg) ;
259272 }
260273
261274 /// Helper function for `order_nodes` - traverse node and outgoing edges
@@ -278,7 +291,7 @@ impl Graph {
278291 let cycle_breaker_node = marked_temp
279292 . iter ( )
280293 . skip ( pos)
281- . find ( |& & node_id| self . nodes [ node_id] . borrow ( ) . cycle_breaker ) ;
294+ . find ( |& & node_id| self . nodes . get_unchecked ( node_id) . borrow ( ) . cycle_breaker ) ;
282295
283296 match cycle_breaker_node {
284297 Some ( & node_id) => {
@@ -307,7 +320,13 @@ impl Graph {
307320 marked_temp. push ( node_id) ;
308321
309322 // Visit outgoing nodes, and call `visit` on them recursively
310- for edge in self . nodes [ node_id] . borrow ( ) . outgoing_edges . iter ( ) {
323+ for edge in self
324+ . nodes
325+ . get_unchecked ( node_id)
326+ . borrow ( )
327+ . outgoing_edges
328+ . iter ( )
329+ {
311330 let cycle_breaker_applied = self . visit (
312331 edge. other_id ,
313332 marked,
@@ -386,9 +405,11 @@ impl Graph {
386405
387406 if cycle_breaker_applied {
388407 // clear the outgoing edges of the nodes that have been recognized as cycle breaker
389- let nodes = & mut self . nodes ;
390408 cycle_breakers. iter ( ) . for_each ( |node_id| {
391- nodes[ * node_id] . get_mut ( ) . outgoing_edges . clear ( ) ;
409+ self . nodes
410+ . get_unchecked_mut ( * node_id)
411+ . outgoing_edges
412+ . clear ( ) ;
392413 } ) ;
393414
394415 continue ;
@@ -423,16 +444,13 @@ impl Graph {
423444 // keep track of end-of-lifecyle nodes
424445 let mut nodes_dropped = false ;
425446
426- // for borrow-checker reasons, move mutable borrow of nodes out of self
427- let nodes = & mut self . nodes ;
428-
429447 // process every node, in topological sorted order
430448 self . ordered . iter ( ) . for_each ( |index| {
431449 // acquire a mutable borrow of the current processing node
432- let mut node = nodes[ * index] . borrow_mut ( ) ;
450+ let mut node = self . nodes . get_unchecked ( * index) . borrow_mut ( ) ;
433451
434452 // let the current node process (catch any panics that may occur)
435- let params = AudioParamValues :: from ( nodes) ;
453+ let params = AudioParamValues :: from ( & self . nodes ) ;
436454 scope. node_id . set ( * index) ;
437455 let ( success, tail_time) = {
438456 // We are abusing AssertUnwindSafe here, we cannot guarantee it upholds.
@@ -456,7 +474,7 @@ impl Graph {
456474 // audio params are connected to the 'hidden' usize::MAX output, ignore them here
457475 . filter ( |edge| edge. other_index != usize:: MAX )
458476 . for_each ( |edge| {
459- let mut output_node = nodes[ edge. other_id ] . borrow_mut ( ) ;
477+ let mut output_node = self . nodes . get_unchecked ( edge. other_id ) . borrow_mut ( ) ;
460478 output_node. has_inputs_connected = true ;
461479 let signal = & node. outputs [ edge. self_index ] ;
462480 let channel_config = & output_node. channel_config . clone ( ) ;
@@ -482,7 +500,7 @@ impl Graph {
482500 // Check if we can decommission this node (end of life)
483501 if can_free {
484502 // Node is dropped, remove it from the node list
485- let mut node = nodes. remove ( * index) . into_inner ( ) ;
503+ let mut node = self . nodes . remove ( * index) . into_inner ( ) ;
486504 self . reclaim_id_channel
487505 . push ( node. reclaim_id . take ( ) . unwrap ( ) ) ;
488506 drop ( node) ;
@@ -492,39 +510,22 @@ impl Graph {
492510
493511 // Nodes are only dropped when they do not have incoming connections.
494512 // But they may have AudioParams feeding into them, these can de dropped too.
495- nodes. retain ( |id, node| {
496- let node = node. get_mut ( ) ; // unwrap the RefCell
497-
513+ self . nodes . values_mut ( ) . for_each ( |node| {
498514 // Check if this node was connected to the dropped node. In that case, it is
499- // either an AudioParam (which can be dropped), or the AudioListener that feeds
500- // into a PannerNode (which can be disconnected).
501- let was_connected = {
502- let outgoing_edges = & mut node. outgoing_edges ;
503- let prev_len = outgoing_edges. len ( ) ;
504- outgoing_edges. retain ( |e| e. other_id != * index) ;
505- outgoing_edges. len ( ) != prev_len
506- } ;
507-
508- // Retain when
509- // - special node (destination = id 0, listener = id 1), or
510- // - not connected to this dropped node, or
511- // - if the control thread still has a handle to it.
512- let retain = id. 0 < 2 || !was_connected || !node. control_handle_dropped ;
513-
514- if !retain {
515- self . reclaim_id_channel
516- . push ( node. reclaim_id . take ( ) . unwrap ( ) ) ;
517- }
518- retain
519- } )
515+ // either an AudioParam or the AudioListener that feeds into a PannerNode.
516+ // These should be disconnected
517+ node. get_mut ( )
518+ . outgoing_edges
519+ . retain ( |e| e. other_id != * index) ;
520+ } ) ;
520521 }
521522 } ) ;
522523
523524 // If there were any nodes decommissioned, remove from graph order
524525 if nodes_dropped {
525526 let mut i = 0 ;
526527 while i < self . ordered . len ( ) {
527- if nodes. get ( self . ordered [ i] ) . is_none ( ) {
528+ if ! self . nodes . contains ( self . ordered [ i] ) {
528529 self . ordered . remove ( i) ;
529530 } else {
530531 i += 1 ;
@@ -533,7 +534,7 @@ impl Graph {
533534 }
534535
535536 // Return the output buffer of destination node
536- & self . nodes [ AudioNodeId ( 0 ) ] . get_mut ( ) . outputs [ 0 ]
537+ & self . nodes . get_unchecked_mut ( AudioNodeId ( 0 ) ) . outputs [ 0 ]
537538 }
538539}
539540
@@ -735,7 +736,10 @@ mod tests {
735736 // dropped and the AudioNodeId(3) should be reclaimed
736737 add_node ( & mut graph, 2 , node. clone ( ) ) ;
737738 // Mark the node as 'detached from the control thread', so it is allowed to drop
738- graph. nodes [ AudioNodeId ( 2 ) ] . get_mut ( ) . control_handle_dropped = true ;
739+ graph
740+ . nodes
741+ . get_unchecked_mut ( AudioNodeId ( 2 ) )
742+ . control_handle_dropped = true ;
739743
740744 // Connect the regular node to the AudioDestinationNode
741745 add_edge ( & mut graph, 2 , 0 ) ;
@@ -777,7 +781,10 @@ mod tests {
777781 // dropped and the AudioNodeId(3) should be reclaimed
778782 add_node ( & mut graph, 2 , node. clone ( ) ) ;
779783 // Mark the node as 'detached from the control thread', so it is allowed to drop
780- graph. nodes [ AudioNodeId ( 2 ) ] . get_mut ( ) . control_handle_dropped = true ;
784+ graph
785+ . nodes
786+ . get_unchecked_mut ( AudioNodeId ( 2 ) )
787+ . control_handle_dropped = true ;
781788
782789 // Connect the regular node to the AudioDestinationNode
783790 add_edge ( & mut graph, 2 , 0 ) ;
@@ -786,7 +793,10 @@ mod tests {
786793 let param = Box :: new ( TestNode { tail_time : true } ) ; // audio params have tail time true
787794 add_node ( & mut graph, 3 , param) ;
788795 // Mark the node as 'detached from the control thread', so it is allowed to drop
789- graph. nodes [ AudioNodeId ( 3 ) ] . get_mut ( ) . control_handle_dropped = true ;
796+ graph
797+ . nodes
798+ . get_unchecked_mut ( AudioNodeId ( 3 ) )
799+ . control_handle_dropped = true ;
790800
791801 // Connect the audioparam to the regular node
792802 add_audioparam ( & mut graph, 3 , 2 ) ;
@@ -799,7 +809,10 @@ mod tests {
799809 node_id : std:: cell:: Cell :: new ( AudioNodeId ( 0 ) ) ,
800810 event_sender : None ,
801811 } ;
802- graph. render ( & scope) ;
812+
813+ // render twice
814+ graph. render ( & scope) ; // node is dropped
815+ graph. render ( & scope) ; // param is dropped
803816
804817 // First the regular node should be dropped, then the audioparam
805818 assert_eq ! ( node_id_consumer. pop( ) . unwrap( ) . 0 , 2 ) ;
@@ -809,6 +822,78 @@ mod tests {
809822 assert ! ( node_id_consumer. pop( ) . is_none( ) ) ;
810823 }
811824
825+ #[ test]
826+ fn test_audio_param_with_signal_lifecycle ( ) {
827+ let ( node_id_producer, mut node_id_consumer) = llq:: Queue :: new ( ) . split ( ) ;
828+ let mut graph = Graph :: new ( node_id_producer) ;
829+
830+ let node = Box :: new ( TestNode { tail_time : false } ) ;
831+
832+ // Destination Node is always node id 0, and should never drop
833+ add_node ( & mut graph, 0 , node. clone ( ) ) ;
834+
835+ // AudioListener Node is always node id 1, and should never drop
836+ add_node ( & mut graph, 1 , node. clone ( ) ) ;
837+
838+ // Add a regular node at id 3, it has tail time false so after rendering it should be
839+ // dropped and the AudioNodeId(3) should be reclaimed
840+ add_node ( & mut graph, 2 , node. clone ( ) ) ;
841+ // Mark the node as 'detached from the control thread', so it is allowed to drop
842+ graph
843+ . nodes
844+ . get_unchecked_mut ( AudioNodeId ( 2 ) )
845+ . control_handle_dropped = true ;
846+
847+ // Connect the regular node to the AudioDestinationNode
848+ add_edge ( & mut graph, 2 , 0 ) ;
849+
850+ // Add an AudioParam at id 4, it should be dropped alongside the regular node
851+ let param = Box :: new ( TestNode { tail_time : true } ) ; // audio params have tail time true
852+ add_node ( & mut graph, 3 , param) ;
853+ // Mark the node as 'detached from the control thread', so it is allowed to drop
854+ graph
855+ . nodes
856+ . get_unchecked_mut ( AudioNodeId ( 3 ) )
857+ . control_handle_dropped = true ;
858+
859+ // Connect the audioparam to the regular node
860+ add_audioparam ( & mut graph, 3 , 2 ) ;
861+
862+ // Add a source node to feed into the AudioParam
863+ let signal = Box :: new ( TestNode { tail_time : true } ) ;
864+ add_node ( & mut graph, 4 , signal) ;
865+ add_edge ( & mut graph, 4 , 3 ) ;
866+ // Mark the node as 'detached from the control thread', so it is allowed to drop
867+ graph
868+ . nodes
869+ . get_unchecked_mut ( AudioNodeId ( 4 ) )
870+ . control_handle_dropped = true ;
871+
872+ // Render a single quantum
873+ let scope = AudioWorkletGlobalScope {
874+ current_frame : 0 ,
875+ current_time : 0. ,
876+ sample_rate : 48000. ,
877+ node_id : std:: cell:: Cell :: new ( AudioNodeId ( 0 ) ) ,
878+ event_sender : None ,
879+ } ;
880+
881+ // render twice
882+ graph. render ( & scope) ; // node is dropped
883+ graph. render ( & scope) ; // param is dropped
884+
885+ // First the regular node should be dropped, then the audioparam
886+ assert_eq ! ( node_id_consumer. pop( ) . unwrap( ) . 0 , 2 ) ;
887+ assert_eq ! ( node_id_consumer. pop( ) . unwrap( ) . 0 , 3 ) ;
888+
889+ // No other dropped nodes
890+ assert ! ( node_id_consumer. pop( ) . is_none( ) ) ;
891+
892+ // Render again
893+ graph. render ( & scope) ; // param signal source is dropped
894+ assert_eq ! ( node_id_consumer. pop( ) . unwrap( ) . 0 , 4 ) ;
895+ }
896+
812897 #[ test]
813898 fn test_release_orphaned_source_nodes ( ) {
814899 let ( node_id_producer, mut node_id_consumer) = llq:: Queue :: new ( ) . split ( ) ;
@@ -828,7 +913,10 @@ mod tests {
828913 add_node ( & mut graph, 2 , node) ;
829914
830915 // Mark the node as 'detached from the control thread', so it is allowed to drop
831- graph. nodes [ AudioNodeId ( 2 ) ] . get_mut ( ) . control_handle_dropped = true ;
916+ graph
917+ . nodes
918+ . get_unchecked_mut ( AudioNodeId ( 2 ) )
919+ . control_handle_dropped = true ;
832920
833921 // Render a single quantum
834922 let scope = AudioWorkletGlobalScope {
0 commit comments