@@ -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 AudioNodeIdIssuer {
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 AudioNodeIdIssuer {
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 issue ( & 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,10 +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 ,
52
- /// receiver for decommissioned AudioNodeIds, which can be reused
53
- node_id_consumer : Mutex < llq:: Consumer < AudioNodeId > > ,
78
+ /// issuer for new AudioNodeIds
79
+ audio_node_id_issuer : AudioNodeIdIssuer ,
54
80
/// destination node's current channel count
55
81
destination_channel_config : ChannelConfig ,
56
82
/// message channel from control to render thread
@@ -85,13 +111,8 @@ impl BaseAudioContext for ConcreteBaseAudioContext {
85
111
& self ,
86
112
f : F ,
87
113
) -> T {
88
- // create unique identifier for this node
89
- let id = if let Some ( available_id) = self . inner . node_id_consumer . lock ( ) . unwrap ( ) . pop ( ) {
90
- llq:: Node :: into_inner ( available_id)
91
- } else {
92
- AudioNodeId ( self . inner . node_id_inc . fetch_add ( 1 , Ordering :: Relaxed ) )
93
- } ;
94
-
114
+ // create a unique id for this node
115
+ let id = self . inner . audio_node_id_issuer . issue ( ) ;
95
116
let registration = AudioContextRegistration {
96
117
id,
97
118
context : self . clone ( ) ,
@@ -141,13 +162,14 @@ impl ConcreteBaseAudioContext {
141
162
Some ( ( send, recv) ) => ( Some ( send) , Some ( recv) ) ,
142
163
} ;
143
164
165
+ let audio_node_id_issuer = AudioNodeIdIssuer :: new ( node_id_consumer) ;
166
+
144
167
let base_inner = ConcreteBaseAudioContextInner {
145
168
sample_rate,
146
169
max_channel_count,
147
170
render_channel : RwLock :: new ( render_channel) ,
148
171
queued_messages : Mutex :: new ( Vec :: new ( ) ) ,
149
- node_id_inc : AtomicU64 :: new ( 0 ) ,
150
- node_id_consumer : Mutex :: new ( node_id_consumer) ,
172
+ audio_node_id_issuer,
151
173
destination_channel_config : ChannelConfigOptions :: default ( ) . into ( ) ,
152
174
frames_played,
153
175
queued_audio_listener_msgs : Mutex :: new ( Vec :: new ( ) ) ,
@@ -211,7 +233,10 @@ impl ConcreteBaseAudioContext {
211
233
212
234
// Validate if the hardcoded node IDs line up
213
235
debug_assert_eq ! (
214
- base. inner. node_id_inc. load( Ordering :: Relaxed ) ,
236
+ base. inner
237
+ . audio_node_id_issuer
238
+ . id_inc
239
+ . load( Ordering :: Relaxed ) ,
215
240
LISTENER_PARAM_IDS . end,
216
241
) ;
217
242
@@ -429,3 +454,19 @@ impl ConcreteBaseAudioContext {
429
454
self . inner . event_loop . clear_handler ( event) ;
430
455
}
431
456
}
457
+
458
+ #[ cfg( test) ]
459
+ mod tests {
460
+ use super :: * ;
461
+
462
+ #[ test]
463
+ fn test_issue_node_id ( ) {
464
+ let ( mut id_producer, id_consumer) = llq:: Queue :: new ( ) . split ( ) ;
465
+ let issuer = AudioNodeIdIssuer :: new ( id_consumer) ;
466
+ assert_eq ! ( issuer. issue( ) . 0 , 0 ) ; // newly assigned
467
+ assert_eq ! ( issuer. issue( ) . 0 , 1 ) ; // newly assigned
468
+ id_producer. push ( llq:: Node :: new ( AudioNodeId ( 0 ) ) ) ;
469
+ assert_eq ! ( issuer. issue( ) . 0 , 0 ) ; // reused
470
+ assert_eq ! ( issuer. issue( ) . 0 , 2 ) ; // newly assigned
471
+ }
472
+ }
0 commit comments