@@ -89,19 +89,26 @@ impl Node {
89
89
return false ;
90
90
}
91
91
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
+ }
95
103
}
96
104
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 ( ) {
100
108
return true ;
101
109
}
102
110
103
111
// Otherwise, do not drop the node.
104
- // (Even if it has no outputs connected, it may have side effects)
105
112
false
106
113
}
107
114
@@ -193,8 +200,8 @@ impl Graph {
193
200
}
194
201
195
202
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 )
198
205
. outgoing_edges
199
206
. push ( OutgoingEdge {
200
207
self_index : source. 1 ,
@@ -206,8 +213,8 @@ impl Graph {
206
213
}
207
214
208
215
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 )
211
218
. outgoing_edges
212
219
. retain ( |edge| edge. other_id != dest) ;
213
220
@@ -216,7 +223,7 @@ impl Graph {
216
223
217
224
pub fn remove_edges_from ( & mut self , source : AudioNodeId ) {
218
225
// Remove outgoing edges
219
- self . nodes [ source ] . get_mut ( ) . outgoing_edges . clear ( ) ;
226
+ self . nodes . get_unchecked_mut ( source ) . outgoing_edges . clear ( ) ;
220
227
221
228
// Remove incoming edges - we need to traverse all nodes
222
229
self . nodes . values_mut ( ) . for_each ( |node| {
@@ -241,21 +248,27 @@ impl Graph {
241
248
}
242
249
243
250
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 ;
245
252
}
246
253
247
254
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;
249
256
}
250
257
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;
252
262
}
253
263
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;
255
268
}
256
269
257
270
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) ;
259
272
}
260
273
261
274
/// Helper function for `order_nodes` - traverse node and outgoing edges
@@ -278,7 +291,7 @@ impl Graph {
278
291
let cycle_breaker_node = marked_temp
279
292
. iter ( )
280
293
. 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 ) ;
282
295
283
296
match cycle_breaker_node {
284
297
Some ( & node_id) => {
@@ -307,7 +320,13 @@ impl Graph {
307
320
marked_temp. push ( node_id) ;
308
321
309
322
// 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
+ {
311
330
let cycle_breaker_applied = self . visit (
312
331
edge. other_id ,
313
332
marked,
@@ -386,9 +405,11 @@ impl Graph {
386
405
387
406
if cycle_breaker_applied {
388
407
// clear the outgoing edges of the nodes that have been recognized as cycle breaker
389
- let nodes = & mut self . nodes ;
390
408
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 ( ) ;
392
413
} ) ;
393
414
394
415
continue ;
@@ -423,16 +444,13 @@ impl Graph {
423
444
// keep track of end-of-lifecyle nodes
424
445
let mut nodes_dropped = false ;
425
446
426
- // for borrow-checker reasons, move mutable borrow of nodes out of self
427
- let nodes = & mut self . nodes ;
428
-
429
447
// process every node, in topological sorted order
430
448
self . ordered . iter ( ) . for_each ( |index| {
431
449
// 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 ( ) ;
433
451
434
452
// let the current node process (catch any panics that may occur)
435
- let params = AudioParamValues :: from ( nodes) ;
453
+ let params = AudioParamValues :: from ( & self . nodes ) ;
436
454
scope. node_id . set ( * index) ;
437
455
let ( success, tail_time) = {
438
456
// We are abusing AssertUnwindSafe here, we cannot guarantee it upholds.
@@ -456,7 +474,7 @@ impl Graph {
456
474
// audio params are connected to the 'hidden' usize::MAX output, ignore them here
457
475
. filter ( |edge| edge. other_index != usize:: MAX )
458
476
. 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 ( ) ;
460
478
output_node. has_inputs_connected = true ;
461
479
let signal = & node. outputs [ edge. self_index ] ;
462
480
let channel_config = & output_node. channel_config . clone ( ) ;
@@ -482,7 +500,7 @@ impl Graph {
482
500
// Check if we can decommission this node (end of life)
483
501
if can_free {
484
502
// 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 ( ) ;
486
504
self . reclaim_id_channel
487
505
. push ( node. reclaim_id . take ( ) . unwrap ( ) ) ;
488
506
drop ( node) ;
@@ -492,39 +510,22 @@ impl Graph {
492
510
493
511
// Nodes are only dropped when they do not have incoming connections.
494
512
// 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| {
498
514
// 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
+ } ) ;
520
521
}
521
522
} ) ;
522
523
523
524
// If there were any nodes decommissioned, remove from graph order
524
525
if nodes_dropped {
525
526
let mut i = 0 ;
526
527
while i < self . ordered . len ( ) {
527
- if nodes. get ( self . ordered [ i] ) . is_none ( ) {
528
+ if ! self . nodes . contains ( self . ordered [ i] ) {
528
529
self . ordered . remove ( i) ;
529
530
} else {
530
531
i += 1 ;
@@ -533,7 +534,7 @@ impl Graph {
533
534
}
534
535
535
536
// 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 ]
537
538
}
538
539
}
539
540
@@ -735,7 +736,10 @@ mod tests {
735
736
// dropped and the AudioNodeId(3) should be reclaimed
736
737
add_node ( & mut graph, 2 , node. clone ( ) ) ;
737
738
// 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 ;
739
743
740
744
// Connect the regular node to the AudioDestinationNode
741
745
add_edge ( & mut graph, 2 , 0 ) ;
@@ -777,7 +781,10 @@ mod tests {
777
781
// dropped and the AudioNodeId(3) should be reclaimed
778
782
add_node ( & mut graph, 2 , node. clone ( ) ) ;
779
783
// 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 ;
781
788
782
789
// Connect the regular node to the AudioDestinationNode
783
790
add_edge ( & mut graph, 2 , 0 ) ;
@@ -786,7 +793,10 @@ mod tests {
786
793
let param = Box :: new ( TestNode { tail_time : true } ) ; // audio params have tail time true
787
794
add_node ( & mut graph, 3 , param) ;
788
795
// 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 ;
790
800
791
801
// Connect the audioparam to the regular node
792
802
add_audioparam ( & mut graph, 3 , 2 ) ;
@@ -799,7 +809,10 @@ mod tests {
799
809
node_id : std:: cell:: Cell :: new ( AudioNodeId ( 0 ) ) ,
800
810
event_sender : None ,
801
811
} ;
802
- graph. render ( & scope) ;
812
+
813
+ // render twice
814
+ graph. render ( & scope) ; // node is dropped
815
+ graph. render ( & scope) ; // param is dropped
803
816
804
817
// First the regular node should be dropped, then the audioparam
805
818
assert_eq ! ( node_id_consumer. pop( ) . unwrap( ) . 0 , 2 ) ;
@@ -809,6 +822,78 @@ mod tests {
809
822
assert ! ( node_id_consumer. pop( ) . is_none( ) ) ;
810
823
}
811
824
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
+
812
897
#[ test]
813
898
fn test_release_orphaned_source_nodes ( ) {
814
899
let ( node_id_producer, mut node_id_consumer) = llq:: Queue :: new ( ) . split ( ) ;
@@ -828,7 +913,10 @@ mod tests {
828
913
add_node ( & mut graph, 2 , node) ;
829
914
830
915
// 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 ;
832
920
833
921
// Render a single quantum
834
922
let scope = AudioWorkletGlobalScope {
0 commit comments