@@ -17,6 +17,34 @@ use crossbeam_channel::{Receiver, SendError, Sender};
17
17
use std:: sync:: atomic:: { AtomicU64 , AtomicU8 , Ordering } ;
18
18
use std:: sync:: { Arc , Mutex , RwLock , RwLockWriteGuard } ;
19
19
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
+
20
48
/// The struct that corresponds to the Javascript `BaseAudioContext` object.
21
49
///
22
50
/// This object is returned from the `base()` method on
@@ -47,8 +75,8 @@ struct ConcreteBaseAudioContextInner {
47
75
sample_rate : f32 ,
48
76
/// max number of speaker output channels
49
77
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 ,
52
80
/// destination node's current channel count
53
81
destination_channel_config : ChannelConfig ,
54
82
/// message channel from control to render thread
@@ -83,9 +111,8 @@ impl BaseAudioContext for ConcreteBaseAudioContext {
83
111
& self ,
84
112
f : F ,
85
113
) -> 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 ( ) ;
89
116
let registration = AudioContextRegistration {
90
117
id,
91
118
context : self . clone ( ) ,
@@ -97,6 +124,7 @@ impl BaseAudioContext for ConcreteBaseAudioContext {
97
124
// pass the renderer to the audio graph
98
125
let message = ControlMessage :: RegisterNode {
99
126
id,
127
+ reclaim_id : llq:: Node :: new ( id) ,
100
128
node : render,
101
129
inputs : node. number_of_inputs ( ) ,
102
130
outputs : node. number_of_outputs ( ) ,
@@ -126,19 +154,22 @@ impl ConcreteBaseAudioContext {
126
154
render_channel : Sender < ControlMessage > ,
127
155
event_channel : Option < ( Sender < EventDispatch > , Receiver < EventDispatch > ) > ,
128
156
offline : bool ,
157
+ node_id_consumer : llq:: Consumer < AudioNodeId > ,
129
158
) -> Self {
130
159
let event_loop = EventLoop :: new ( ) ;
131
160
let ( event_send, event_recv) = match event_channel {
132
161
None => ( None , None ) ,
133
162
Some ( ( send, recv) ) => ( Some ( send) , Some ( recv) ) ,
134
163
} ;
135
164
165
+ let audio_node_id_provider = AudioNodeIdProvider :: new ( node_id_consumer) ;
166
+
136
167
let base_inner = ConcreteBaseAudioContextInner {
137
168
sample_rate,
138
169
max_channel_count,
139
170
render_channel : RwLock :: new ( render_channel) ,
140
171
queued_messages : Mutex :: new ( Vec :: new ( ) ) ,
141
- node_id_inc : AtomicU64 :: new ( 0 ) ,
172
+ audio_node_id_provider ,
142
173
destination_channel_config : ChannelConfigOptions :: default ( ) . into ( ) ,
143
174
frames_played,
144
175
queued_audio_listener_msgs : Mutex :: new ( Vec :: new ( ) ) ,
@@ -202,7 +233,10 @@ impl ConcreteBaseAudioContext {
202
233
203
234
// Validate if the hardcoded node IDs line up
204
235
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 ) ,
206
240
LISTENER_PARAM_IDS . end,
207
241
) ;
208
242
@@ -420,3 +454,19 @@ impl ConcreteBaseAudioContext {
420
454
self . inner . event_loop . clear_handler ( event) ;
421
455
}
422
456
}
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