@@ -17,6 +17,34 @@ use crossbeam_channel::{Receiver, SendError, Sender};
1717use std:: sync:: atomic:: { AtomicU64 , AtomicU8 , Ordering } ;
1818use std:: sync:: { Arc , Mutex , RwLock , RwLockWriteGuard } ;
1919
20+ /// This struct assigns new [`AudioNodeId`]s for [`AudioNode`]s
21+ ///
22+ /// It reuses the ids of decommissioned nodes to prevent unbounded growth of the audio graphs node
23+ /// list (which is stored in a Vec indexed by the AudioNodeId).
24+ struct AudioNodeIdProvider {
25+ /// incrementing id
26+ id_inc : AtomicU64 ,
27+ /// receiver for decommissioned AudioNodeIds, which can be reused
28+ id_consumer : Mutex < llq:: Consumer < AudioNodeId > > ,
29+ }
30+
31+ impl AudioNodeIdProvider {
32+ fn new ( id_consumer : llq:: Consumer < AudioNodeId > ) -> Self {
33+ Self {
34+ id_inc : AtomicU64 :: new ( 0 ) ,
35+ id_consumer : Mutex :: new ( id_consumer) ,
36+ }
37+ }
38+
39+ fn get ( & self ) -> AudioNodeId {
40+ if let Some ( available_id) = self . id_consumer . lock ( ) . unwrap ( ) . pop ( ) {
41+ llq:: Node :: into_inner ( available_id)
42+ } else {
43+ AudioNodeId ( self . id_inc . fetch_add ( 1 , Ordering :: Relaxed ) )
44+ }
45+ }
46+ }
47+
2048/// The struct that corresponds to the Javascript `BaseAudioContext` object.
2149///
2250/// This object is returned from the `base()` method on
@@ -47,8 +75,8 @@ struct ConcreteBaseAudioContextInner {
4775 sample_rate : f32 ,
4876 /// max number of speaker output channels
4977 max_channel_count : usize ,
50- /// incrementing id to assign to audio nodes
51- node_id_inc : AtomicU64 ,
78+ /// provider for new AudioNodeIds
79+ audio_node_id_provider : AudioNodeIdProvider ,
5280 /// destination node's current channel count
5381 destination_channel_config : ChannelConfig ,
5482 /// message channel from control to render thread
@@ -83,9 +111,8 @@ impl BaseAudioContext for ConcreteBaseAudioContext {
83111 & self ,
84112 f : F ,
85113 ) -> T {
86- // create unique identifier for this node
87- let id = self . inner . node_id_inc . fetch_add ( 1 , Ordering :: SeqCst ) ;
88- let id = AudioNodeId ( id) ;
114+ // create a unique id for this node
115+ let id = self . inner . audio_node_id_provider . get ( ) ;
89116 let registration = AudioContextRegistration {
90117 id,
91118 context : self . clone ( ) ,
@@ -97,6 +124,7 @@ impl BaseAudioContext for ConcreteBaseAudioContext {
97124 // pass the renderer to the audio graph
98125 let message = ControlMessage :: RegisterNode {
99126 id,
127+ reclaim_id : llq:: Node :: new ( id) ,
100128 node : render,
101129 inputs : node. number_of_inputs ( ) ,
102130 outputs : node. number_of_outputs ( ) ,
@@ -126,19 +154,22 @@ impl ConcreteBaseAudioContext {
126154 render_channel : Sender < ControlMessage > ,
127155 event_channel : Option < ( Sender < EventDispatch > , Receiver < EventDispatch > ) > ,
128156 offline : bool ,
157+ node_id_consumer : llq:: Consumer < AudioNodeId > ,
129158 ) -> Self {
130159 let event_loop = EventLoop :: new ( ) ;
131160 let ( event_send, event_recv) = match event_channel {
132161 None => ( None , None ) ,
133162 Some ( ( send, recv) ) => ( Some ( send) , Some ( recv) ) ,
134163 } ;
135164
165+ let audio_node_id_provider = AudioNodeIdProvider :: new ( node_id_consumer) ;
166+
136167 let base_inner = ConcreteBaseAudioContextInner {
137168 sample_rate,
138169 max_channel_count,
139170 render_channel : RwLock :: new ( render_channel) ,
140171 queued_messages : Mutex :: new ( Vec :: new ( ) ) ,
141- node_id_inc : AtomicU64 :: new ( 0 ) ,
172+ audio_node_id_provider ,
142173 destination_channel_config : ChannelConfigOptions :: default ( ) . into ( ) ,
143174 frames_played,
144175 queued_audio_listener_msgs : Mutex :: new ( Vec :: new ( ) ) ,
@@ -202,7 +233,10 @@ impl ConcreteBaseAudioContext {
202233
203234 // Validate if the hardcoded node IDs line up
204235 debug_assert_eq ! (
205- base. inner. node_id_inc. load( Ordering :: Relaxed ) ,
236+ base. inner
237+ . audio_node_id_provider
238+ . id_inc
239+ . load( Ordering :: Relaxed ) ,
206240 LISTENER_PARAM_IDS . end,
207241 ) ;
208242
@@ -420,3 +454,19 @@ impl ConcreteBaseAudioContext {
420454 self . inner . event_loop . clear_handler ( event) ;
421455 }
422456}
457+
458+ #[ cfg( test) ]
459+ mod tests {
460+ use super :: * ;
461+
462+ #[ test]
463+ fn test_provide_node_id ( ) {
464+ let ( mut id_producer, id_consumer) = llq:: Queue :: new ( ) . split ( ) ;
465+ let provider = AudioNodeIdProvider :: new ( id_consumer) ;
466+ assert_eq ! ( provider. get( ) . 0 , 0 ) ; // newly assigned
467+ assert_eq ! ( provider. get( ) . 0 , 1 ) ; // newly assigned
468+ id_producer. push ( llq:: Node :: new ( AudioNodeId ( 0 ) ) ) ;
469+ assert_eq ! ( provider. get( ) . 0 , 0 ) ; // reused
470+ assert_eq ! ( provider. get( ) . 0 , 2 ) ; // newly assigned
471+ }
472+ }
0 commit comments