Skip to content

Commit 6cfc2d7

Browse files
authored
Merge pull request #379 from orottier/feature/store-nodes-in-vec-not-hashmap
Experiment: store nodes in contiguous Vec instead of HashMap
2 parents 94fad63 + 402e21f commit 6cfc2d7

File tree

12 files changed

+401
-108
lines changed

12 files changed

+401
-108
lines changed

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ no_denormals = "0.1.2"
2929
num-complex = "0.4"
3030
realfft = "3.3"
3131
rubato = "0.14"
32-
rustc-hash = "1.1"
3332
smallvec = "1.11"
3433
symphonia = { version = "0.5", default-features = false }
3534
vecmath = "1.0"

src/context/concrete_base.rs

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,34 @@ use crossbeam_channel::{Receiver, SendError, Sender};
1717
use std::sync::atomic::{AtomicU64, AtomicU8, Ordering};
1818
use 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+
}

src/context/offline.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ impl OfflineAudioContext {
4343
// unbounded is fine because it does not need to be realtime safe
4444
let (sender, receiver) = crossbeam_channel::unbounded();
4545

46-
let graph = crate::render::graph::Graph::new();
46+
let (node_id_producer, node_id_consumer) = llq::Queue::new().split();
47+
let graph = crate::render::graph::Graph::new(node_id_producer);
4748
let message = crate::message::ControlMessage::Startup { graph };
4849
sender.send(message).unwrap();
4950

@@ -67,6 +68,7 @@ impl OfflineAudioContext {
6768
sender,
6869
None,
6970
true,
71+
node_id_consumer,
7072
);
7173

7274
Self {

src/context/online.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ impl AudioContext {
173173
event_recv,
174174
} = control_thread_init;
175175

176-
let graph = Graph::new();
176+
let (node_id_producer, node_id_consumer) = llq::Queue::new().split();
177+
let graph = Graph::new(node_id_producer);
177178
let message = ControlMessage::Startup { graph };
178179
ctrl_msg_send.send(message).unwrap();
179180

@@ -184,6 +185,7 @@ impl AudioContext {
184185
ctrl_msg_send,
185186
Some((event_send, event_recv)),
186187
false,
188+
node_id_consumer,
187189
);
188190
base.set_state(AudioContextState::Running);
189191

src/media_devices/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
//!
55
//! <https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices>
66
7-
use rustc_hash::FxHasher;
7+
use std::collections::hash_map::DefaultHasher;
88
use std::hash::{Hash, Hasher};
99

1010
use crate::context::{AudioContextLatencyCategory, AudioContextOptions};
@@ -54,7 +54,7 @@ impl DeviceId {
5454
index,
5555
};
5656

57-
let mut hasher = FxHasher::default();
57+
let mut hasher = DefaultHasher::new();
5858
device_info.hash(&mut hasher);
5959
format!("{}", hasher.finish())
6060
}

src/message.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub(crate) enum ControlMessage {
1414
/// Register a new node in the audio graph
1515
RegisterNode {
1616
id: AudioNodeId,
17+
reclaim_id: llq::Node<AudioNodeId>,
1718
node: Box<dyn AudioProcessor>,
1819
inputs: usize,
1920
outputs: usize,

0 commit comments

Comments
 (0)