Skip to content

Commit 27552b0

Browse files
committed
Experiment: store nodes in contiguous Vec instead of HashMap
1 parent 94fad63 commit 27552b0

File tree

3 files changed

+106
-50
lines changed

3 files changed

+106
-50
lines changed

src/context/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub(crate) struct AudioNodeId(pub u64);
4343
///
4444
/// Store these in your `AudioProcessor` to get access to `AudioParam` values.
4545
#[derive(Debug)]
46-
pub struct AudioParamId(u64);
46+
pub struct AudioParamId(pub(crate) u64);
4747

4848
// bit contrived, but for type safety only the context mod can access the inner u64
4949
impl From<&AudioParamId> for AudioNodeId {

src/render/graph.rs

Lines changed: 95 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use std::cell::RefCell;
44
use std::panic::{self, AssertUnwindSafe};
55

66
use crate::context::AudioNodeId;
7-
use rustc_hash::FxHashMap;
87
use smallvec::{smallvec, SmallVec};
98

109
use super::{Alloc, AudioParamValues, AudioProcessor, AudioRenderQuantum};
@@ -76,7 +75,7 @@ impl Node {
7675
/// The audio graph
7776
pub(crate) struct Graph {
7877
/// Processing Nodes
79-
nodes: FxHashMap<AudioNodeId, RefCell<Node>>,
78+
nodes: Vec<Option<RefCell<Node>>>,
8079
/// Allocator for audio buffers
8180
alloc: Alloc,
8281

@@ -95,7 +94,7 @@ pub(crate) struct Graph {
9594
impl Graph {
9695
pub fn new() -> Self {
9796
Graph {
98-
nodes: FxHashMap::default(),
97+
nodes: Vec::with_capacity(64),
9998
ordered: vec![],
10099
marked: vec![],
101100
marked_temp: vec![],
@@ -126,25 +125,28 @@ impl Graph {
126125
let inputs = vec![AudioRenderQuantum::from(self.alloc.silence()); number_of_inputs];
127126
let outputs = vec![AudioRenderQuantum::from(self.alloc.silence()); number_of_outputs];
128127

129-
self.nodes.insert(
130-
index,
131-
RefCell::new(Node {
132-
processor,
133-
inputs,
134-
outputs,
135-
channel_config,
136-
outgoing_edges: smallvec![],
137-
free_when_finished: false,
138-
has_inputs_connected: false,
139-
cycle_breaker: false,
140-
}),
141-
);
128+
let index = index.0 as usize;
129+
if index >= self.nodes.len() {
130+
self.nodes.resize_with(index + 1, || None);
131+
}
132+
self.nodes[index] = Some(RefCell::new(Node {
133+
processor,
134+
inputs,
135+
outputs,
136+
channel_config,
137+
outgoing_edges: smallvec![],
138+
free_when_finished: false,
139+
has_inputs_connected: false,
140+
cycle_breaker: false,
141+
}));
142142
}
143143

144144
pub fn add_edge(&mut self, source: (AudioNodeId, usize), dest: (AudioNodeId, usize)) {
145145
self.nodes
146-
.get_mut(&source.0)
146+
.get_mut(source.0 .0 as usize)
147147
.unwrap_or_else(|| panic!("cannot connect {:?} to {:?}", source, dest))
148+
.as_mut()
149+
.unwrap()
148150
.get_mut()
149151
.outgoing_edges
150152
.push(OutgoingEdge {
@@ -158,8 +160,10 @@ impl Graph {
158160

159161
pub fn remove_edge(&mut self, source: AudioNodeId, dest: AudioNodeId) {
160162
self.nodes
161-
.get_mut(&source)
163+
.get_mut(source.0 as usize)
162164
.unwrap_or_else(|| panic!("cannot remove the edge from {:?} to {:?}", source, dest))
165+
.as_mut()
166+
.unwrap()
163167
.get_mut()
164168
.outgoing_edges
165169
.retain(|edge| edge.other_id != dest);
@@ -169,13 +173,15 @@ impl Graph {
169173

170174
pub fn remove_edges_from(&mut self, source: AudioNodeId) {
171175
self.nodes
172-
.get_mut(&source)
176+
.get_mut(source.0 as usize)
173177
.unwrap_or_else(|| panic!("cannot remove edges from {:?}", source))
178+
.as_mut()
179+
.unwrap()
174180
.get_mut()
175181
.outgoing_edges
176182
.clear();
177183

178-
self.nodes.values_mut().for_each(|node| {
184+
self.nodes.iter_mut().flatten().for_each(|node| {
179185
node.get_mut()
180186
.outgoing_edges
181187
.retain(|edge| edge.other_id != source);
@@ -188,18 +194,26 @@ impl Graph {
188194
// Issue #92, a race condition can occur for AudioParams. They may have already been
189195
// removed from the audio graph if the node they feed into was dropped.
190196
// Therefore, do not assume this node still exists:
191-
if let Some(node) = self.nodes.get_mut(&index) {
197+
if let Some(node) = self.nodes.get_mut(index.0 as usize).unwrap().as_mut() {
192198
node.get_mut().free_when_finished = true;
193199
}
194200
}
195201

196202
pub fn mark_cycle_breaker(&mut self, index: AudioNodeId) {
197-
self.nodes.get_mut(&index).unwrap().get_mut().cycle_breaker = true;
203+
self.nodes
204+
.get_mut(index.0 as usize)
205+
.unwrap()
206+
.as_mut()
207+
.unwrap()
208+
.get_mut()
209+
.cycle_breaker = true;
198210
}
199211

200212
pub fn route_message(&mut self, index: AudioNodeId, msg: &mut dyn Any) {
201213
self.nodes
202-
.get_mut(&index)
214+
.get_mut(index.0 as usize)
215+
.unwrap()
216+
.as_mut()
203217
.unwrap()
204218
.get_mut()
205219
.processor
@@ -223,10 +237,15 @@ impl Graph {
223237
// If this node is in the cycle detection list, it is part of a cycle!
224238
if let Some(pos) = marked_temp.iter().position(|&m| m == node_id) {
225239
// check if we can find some node that can break the cycle
226-
let cycle_breaker_node = marked_temp
227-
.iter()
228-
.skip(pos)
229-
.find(|node_id| self.nodes.get(node_id).unwrap().borrow().cycle_breaker);
240+
let cycle_breaker_node = marked_temp.iter().skip(pos).find(|node_id| {
241+
self.nodes
242+
.get(node_id.0 as usize)
243+
.unwrap()
244+
.as_ref()
245+
.unwrap()
246+
.borrow()
247+
.cycle_breaker
248+
});
230249

231250
match cycle_breaker_node {
232251
Some(&node_id) => {
@@ -257,7 +276,9 @@ impl Graph {
257276
// Visit outgoing nodes, and call `visit` on them recursively
258277
for edge in self
259278
.nodes
260-
.get(&node_id)
279+
.get(node_id.0 as usize)
280+
.unwrap()
281+
.as_ref()
261282
.unwrap()
262283
.borrow()
263284
.outgoing_edges
@@ -324,7 +345,13 @@ impl Graph {
324345
// since the audio graph could contain legs detached from the destination and those should
325346
// still be rendered.
326347
let mut cycle_breaker_applied = false;
327-
for &node_id in self.nodes.keys() {
348+
for node_id in self
349+
.nodes
350+
.iter()
351+
.enumerate()
352+
.filter(|(_, v)| v.is_some())
353+
.map(|(i, _)| AudioNodeId(i as u64))
354+
{
328355
cycle_breaker_applied = self.visit(
329356
node_id,
330357
&mut marked,
@@ -343,7 +370,7 @@ impl Graph {
343370
// clear the outgoing edges of the nodes that have been recognized as cycle breaker
344371
let nodes = &mut self.nodes;
345372
cycle_breakers.iter().for_each(|node_id| {
346-
let node = nodes.get_mut(node_id).unwrap();
373+
let node = nodes.get_mut(node_id.0 as usize).unwrap().as_mut().unwrap();
347374
node.get_mut().outgoing_edges.clear();
348375
});
349376

@@ -385,7 +412,12 @@ impl Graph {
385412
// process every node, in topological sorted order
386413
self.ordered.iter().for_each(|index| {
387414
// acquire a mutable borrow of the current processing node
388-
let mut node = nodes.get(index).unwrap().borrow_mut();
415+
let mut node = nodes
416+
.get(index.0 as usize)
417+
.unwrap()
418+
.as_ref()
419+
.unwrap()
420+
.borrow_mut();
389421

390422
// make sure all input buffers have the correct number of channels, this might not be
391423
// the case if the node has no inputs connected or the channel count has just changed
@@ -396,7 +428,7 @@ impl Graph {
396428
.for_each(|i| i.mix(count, interpretation));
397429

398430
// let the current node process (catch any panics that may occur)
399-
let params = AudioParamValues::from(&*nodes);
431+
let params = AudioParamValues::from(nodes.as_slice());
400432
scope.node_id.set(*index);
401433
let (success, tail_time) = {
402434
// We are abusing AssertUnwindSafe here, we cannot guarantee it upholds.
@@ -420,7 +452,12 @@ impl Graph {
420452
// audio params are connected to the 'hidden' usize::MAX output, ignore them here
421453
.filter(|edge| edge.other_index != usize::MAX)
422454
.for_each(|edge| {
423-
let mut output_node = nodes.get(&edge.other_id).unwrap().borrow_mut();
455+
let mut output_node = nodes
456+
.get(edge.other_id.0 as usize)
457+
.unwrap()
458+
.as_ref()
459+
.unwrap()
460+
.borrow_mut();
424461
output_node.has_inputs_connected = true;
425462
let signal = &node.outputs[edge.self_index];
426463
let channel_config = &output_node.channel_config.clone();
@@ -446,33 +483,44 @@ impl Graph {
446483
// Check if we can decommission this node (end of life)
447484
if can_free {
448485
// Node is dropped, remove it from the node list
449-
nodes.remove(index);
486+
nodes[index.0 as usize] = None;
450487

451488
// And remove it from the ordering after we have processed all nodes
452489
nodes_dropped = true;
453490

454491
// Nodes are only dropped when they do not have incoming connections.
455492
// But they may have AudioParams feeding into them, these can de dropped too.
456-
nodes.retain(|id, n| {
493+
for (id, node) in nodes.iter_mut().enumerate() {
494+
if node.is_none() {
495+
continue;
496+
}
457497
// Check if this node was connected to the dropped node. In that case, it is
458498
// either an AudioParam (which can be dropped), or the AudioListener that feeds
459499
// into a PannerNode (which can be disconnected).
460-
let outgoing_edges = &mut n.borrow_mut().outgoing_edges;
461-
let prev_len = outgoing_edges.len();
462-
outgoing_edges.retain(|e| e.other_id != *index);
463-
let was_connected = outgoing_edges.len() != prev_len;
464-
465-
let special = id.0 < 2; // never drop Listener and Destination node
466-
special || !was_connected
467-
});
500+
let was_connected = {
501+
let outgoing_edges =
502+
&mut node.as_mut().unwrap().borrow_mut().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+
let special = id < 2; // never drop Listener and Destination node
509+
510+
if special || !was_connected {
511+
// retain
512+
} else {
513+
*node = None;
514+
}
515+
}
468516
}
469517
});
470518

471519
// If there were any nodes decommissioned, remove from graph order
472520
if nodes_dropped {
473521
let mut i = 0;
474522
while i < self.ordered.len() {
475-
if !nodes.contains_key(&self.ordered[i]) {
523+
if nodes[self.ordered[i].0 as usize].is_none() {
476524
self.ordered.remove(i);
477525
} else {
478526
i += 1;
@@ -483,7 +531,9 @@ impl Graph {
483531
// Return the output buffer of destination node
484532
&self
485533
.nodes
486-
.get_mut(&AudioNodeId(0))
534+
.get_mut(0)
535+
.unwrap()
536+
.as_mut()
487537
.unwrap()
488538
.get_mut()
489539
.outputs[0]

src/render/processor.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use crate::{Event, RENDER_QUANTUM_SIZE};
66
use super::{graph::Node, AudioRenderQuantum};
77

88
use crossbeam_channel::Sender;
9-
use rustc_hash::FxHashMap;
109
use std::cell::{Cell, RefCell};
1110

1211
use std::any::Any;
@@ -140,11 +139,11 @@ impl Deref for DerefAudioRenderQuantumChannel<'_> {
140139
///
141140
/// Provided to implementations of [`AudioProcessor`] in the render thread
142141
pub struct AudioParamValues<'a> {
143-
nodes: &'a FxHashMap<AudioNodeId, RefCell<Node>>,
142+
nodes: &'a [Option<RefCell<Node>>],
144143
}
145144

146145
impl<'a> AudioParamValues<'a> {
147-
pub(crate) fn from(nodes: &'a FxHashMap<AudioNodeId, RefCell<Node>>) -> Self {
146+
pub(crate) fn from(nodes: &'a [Option<RefCell<Node>>]) -> Self {
148147
Self { nodes }
149148
}
150149

@@ -155,7 +154,14 @@ impl<'a> AudioParamValues<'a> {
155154
/// provide a slice of length equal to the render quantum size (default: 128)
156155
#[allow(clippy::missing_panics_doc)]
157156
pub fn get(&self, index: &AudioParamId) -> impl Deref<Target = [f32]> + '_ {
158-
DerefAudioRenderQuantumChannel(self.nodes.get(&index.into()).unwrap().borrow())
157+
DerefAudioRenderQuantumChannel(
158+
self.nodes
159+
.get(index.0 as usize)
160+
.unwrap()
161+
.as_ref()
162+
.unwrap()
163+
.borrow(),
164+
)
159165
}
160166

161167
pub(crate) fn listener_params(&self) -> [impl Deref<Target = [f32]> + '_; 9] {

0 commit comments

Comments
 (0)