1use core::any::Any;
2use core::fmt::Debug;
3use core::hash::Hash;
4
5#[cfg(not(feature = "std"))]
6use alloc::string::ToString;
7#[cfg(not(feature = "std"))]
8use bevy_platform::prelude::{Box, Vec};
9
10use bevy_platform::collections::HashMap;
11use firewheel_core::StreamInfo;
12use firewheel_core::channel_config::{ChannelConfig, ChannelCount};
13use firewheel_core::event::NodeEvent;
14use firewheel_core::node::{ConstructProcessorContext, NodeError, UpdateContext};
15use smallvec::SmallVec;
16use thunderdome::Arena;
17
18use crate::FirewheelConfig;
19use crate::error::{AddEdgeError, CompileGraphError, RemoveNodeError};
20use crate::graph::dummy_node::{DummyNode, DummyNodeConfig};
21use crate::processor::profiling::ProfilerHeapData;
22use firewheel_core::node::{
23 AudioNode, AudioNodeInfo, AudioNodeInfoInner, Constructor, DynAudioNode, NodeID,
24};
25
26pub(crate) use self::compiler::{
27 CompiledSchedule, NodeHeapData, ProcessNodeInfo, ScheduleHeapData,
28};
29
30pub use self::compiler::{Edge, EdgeID, NodeEntry, PortIdx};
31
32mod compiler;
33mod dummy_node;
34
35#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
36struct EdgeHash {
37 pub src_node: NodeID,
38 pub dst_node: NodeID,
39 pub src_port: PortIdx,
40 pub dst_port: PortIdx,
41}
42
43pub(crate) struct AudioGraph {
45 nodes: Arena<NodeEntry>,
46 edges: Arena<Edge>,
47 existing_edges: HashMap<EdgeHash, EdgeID>,
48
49 graph_in_id: NodeID,
50 graph_out_id: NodeID,
51 graph_channel_config: ChannelConfig,
52 needs_compile: bool,
53
54 nodes_to_remove_from_schedule: Vec<NodeID>,
55 active_nodes_to_remove: HashMap<NodeID, NodeEntry>,
56 nodes_to_call_update_method: Vec<NodeID>,
57
58 prev_node_arena_capacity: usize,
59 prev_buffer_capacity: usize,
60
61 modify_guard_stack: Vec<ModifyGraphGuard>,
62}
63
64impl AudioGraph {
65 pub fn new(config: &FirewheelConfig) -> Self {
66 let mut nodes = Arena::with_capacity(config.initial_node_capacity as usize);
67
68 let graph_in_config = DummyNodeConfig {
69 channel_config: ChannelConfig {
70 num_inputs: ChannelCount::ZERO,
71 num_outputs: config.num_graph_inputs,
72 },
73 };
74 let graph_out_config = DummyNodeConfig {
75 channel_config: ChannelConfig {
76 num_inputs: config.num_graph_outputs,
77 num_outputs: ChannelCount::ZERO,
78 },
79 };
80
81 let graph_in_id = NodeID(
82 nodes.insert(NodeEntry::new(
83 AudioNodeInfo::new()
84 .debug_name("graph_in")
85 .channel_config(graph_in_config.channel_config)
86 .into(),
87 Box::new(Constructor::new(DummyNode, Some(graph_in_config))),
88 )),
89 );
90 nodes[graph_in_id.0].id = graph_in_id;
91
92 let graph_out_id = NodeID(
93 nodes.insert(NodeEntry::new(
94 AudioNodeInfo::new()
95 .debug_name("graph_out")
96 .channel_config(graph_out_config.channel_config)
97 .into(),
98 Box::new(Constructor::new(DummyNode, Some(graph_out_config))),
99 )),
100 );
101 nodes[graph_out_id.0].id = graph_out_id;
102
103 Self {
104 nodes,
105 edges: Arena::with_capacity(config.initial_edge_capacity as usize),
106 existing_edges: HashMap::with_capacity(config.initial_edge_capacity as usize),
107 graph_in_id,
108 graph_out_id,
109 graph_channel_config: ChannelConfig {
110 num_inputs: config.num_graph_inputs,
111 num_outputs: config.num_graph_outputs,
112 },
113 needs_compile: true,
114 nodes_to_remove_from_schedule: Vec::with_capacity(
115 config.initial_node_capacity as usize,
116 ),
117 active_nodes_to_remove: HashMap::with_capacity(config.initial_node_capacity as usize),
118 nodes_to_call_update_method: Vec::new(),
119 prev_node_arena_capacity: 0,
120 prev_buffer_capacity: 0,
121 modify_guard_stack: Vec::new(),
122 }
123 }
124
125 pub fn begin_modify_guard(&mut self) {
126 self.modify_guard_stack.push(ModifyGraphGuard {
127 prev_needs_compile: self.needs_compile,
128 prev_graph_channel_config: self.graph_channel_config,
129 ..Default::default()
130 });
131 }
132
133 pub fn end_modify_guard(&mut self, restore_prev_state: bool) {
134 let Some(mut guard) = self.modify_guard_stack.pop() else {
135 return;
136 };
137
138 if !restore_prev_state {
139 for node_entry in guard.removed_nodes.drain(..) {
140 self.nodes_to_remove_from_schedule.push(node_entry.id);
141 self.active_nodes_to_remove
142 .insert(node_entry.id, node_entry);
143 }
144
145 return;
146 }
147
148 for edge_id in guard.new_edges.drain(..) {
149 let _ = self.disconnect_by_edge_id(edge_id, true);
150 }
151
152 for node_id in guard.new_nodes.drain(..) {
153 assert!(self.remove_node(node_id, true).unwrap().is_empty());
154 }
155
156 if guard.prev_graph_channel_config != self.graph_channel_config {
157 assert!(
158 self.set_graph_channel_config(guard.prev_graph_channel_config, true)
159 .is_empty()
160 );
161 }
162
163 for node_entry in guard.removed_nodes.drain(..) {
164 if node_entry.info.call_update_method {
165 self.nodes_to_call_update_method.push(node_entry.id);
166 }
167
168 assert!(self.nodes.insert_at(node_entry.id.0, node_entry).is_none());
169 }
170
171 for edge in guard.removed_edges.drain(..) {
172 self.connect(
173 edge.src_node,
174 edge.dst_node,
175 &[(edge.src_port, edge.dst_port)],
176 false,
177 true,
178 )
179 .unwrap();
180 }
181
182 self.needs_compile = guard.prev_needs_compile;
183 }
184
185 pub fn graph_in_node(&self) -> NodeID {
187 self.graph_in_id
188 }
189
190 pub fn graph_out_node(&self) -> NodeID {
192 self.graph_out_id
193 }
194
195 pub fn add_node<T: AudioNode + 'static>(
197 &mut self,
198 node: T,
199 config: Option<T::Configuration>,
200 ) -> Result<NodeID, NodeError> {
201 let constructor = Constructor::new(node, config);
202 let info: AudioNodeInfoInner = constructor.info()?.into();
203 let call_update_method = info.call_update_method;
204
205 let new_id = NodeID(
206 self.nodes
207 .insert(NodeEntry::new(info, Box::new(constructor))),
208 );
209 self.nodes[new_id.0].id = new_id;
210
211 if call_update_method {
212 self.nodes_to_call_update_method.push(new_id);
213 }
214
215 self.needs_compile = true;
216
217 if let Some(guard) = self.modify_guard_stack.last_mut() {
218 guard.new_nodes.push(new_id);
219 }
220
221 Ok(new_id)
222 }
223
224 pub fn add_dyn_node<T: DynAudioNode + 'static>(
226 &mut self,
227 node: T,
228 ) -> Result<NodeID, NodeError> {
229 let info: AudioNodeInfoInner = node.info()?.into();
230 let call_update_method = info.call_update_method;
231
232 let new_id = NodeID(self.nodes.insert(NodeEntry::new(info, Box::new(node))));
233 self.nodes[new_id.0].id = new_id;
234
235 if call_update_method {
236 self.nodes_to_call_update_method.push(new_id);
237 }
238
239 self.needs_compile = true;
240
241 if let Some(guard) = self.modify_guard_stack.last_mut() {
242 guard.new_nodes.push(new_id);
243 }
244
245 Ok(new_id)
246 }
247
248 pub fn remove_node(
259 &mut self,
260 node_id: NodeID,
261 is_restoring_graph_state: bool,
262 ) -> Result<SmallVec<[Edge; 4]>, RemoveNodeError> {
263 if node_id == self.graph_in_id {
264 return Err(RemoveNodeError::CannotRemoveGraphInNode);
265 }
266 if node_id == self.graph_out_id {
267 return Err(RemoveNodeError::CannotRemoveGraphOutNode);
268 }
269
270 let mut removed_edges = SmallVec::new();
271
272 let Some(node_entry) = self.nodes.remove(node_id.0) else {
273 return Ok(removed_edges);
274 };
275
276 for port_idx in 0..node_entry.info.channel_config.num_inputs.get() {
277 removed_edges.append(&mut self.remove_edges_with_input_port(
278 node_id,
279 port_idx,
280 is_restoring_graph_state,
281 ));
282 }
283 for port_idx in 0..node_entry.info.channel_config.num_outputs.get() {
284 removed_edges.append(&mut self.remove_edges_with_output_port(
285 node_id,
286 port_idx,
287 is_restoring_graph_state,
288 ));
289 }
290
291 self.needs_compile = true;
292
293 if !is_restoring_graph_state && let Some(guard) = self.modify_guard_stack.last_mut() {
294 guard.removed_nodes.push(node_entry);
295 } else {
296 self.nodes_to_remove_from_schedule.push(node_id);
297 self.active_nodes_to_remove.insert(node_id, node_entry);
298 }
299
300 Ok(removed_edges)
301 }
302
303 pub fn node_info(&self, id: NodeID) -> Option<&NodeEntry> {
305 self.nodes.get(id.0)
306 }
307
308 pub fn contains_node(&self, id: NodeID) -> bool {
310 self.nodes.contains(id.0)
311 }
312
313 pub fn node_state<T: 'static>(&self, id: NodeID) -> Option<&T> {
315 self.node_state_dyn(id).and_then(|s| s.downcast_ref())
316 }
317
318 pub fn node_state_dyn(&self, id: NodeID) -> Option<&dyn Any> {
320 self.nodes
321 .get(id.0)
322 .and_then(|node_entry| node_entry.info.custom_state.as_ref().map(|s| s.as_ref()))
323 }
324
325 pub fn node_state_mut<T: 'static>(&mut self, id: NodeID) -> Option<&mut T> {
327 self.node_state_dyn_mut(id).and_then(|s| s.downcast_mut())
328 }
329
330 pub fn node_state_dyn_mut(&mut self, id: NodeID) -> Option<&mut dyn Any> {
332 self.nodes
333 .get_mut(id.0)
334 .and_then(|node_entry| node_entry.info.custom_state.as_mut().map(|s| s.as_mut()))
335 }
336
337 pub fn nodes(&self) -> impl Iterator<Item = &NodeEntry> {
339 self.nodes.iter().map(|(_, n)| n)
340 }
341
342 pub fn edges(&self) -> impl Iterator<Item = &Edge> {
344 self.edges.iter().map(|(_, e)| e)
345 }
346
347 pub fn set_graph_channel_config(
351 &mut self,
352 channel_config: ChannelConfig,
353 is_restoring_graph_state: bool,
354 ) -> SmallVec<[Edge; 4]> {
355 let mut removed_edges = SmallVec::new();
356
357 let graph_in_node = self.nodes.get_mut(self.graph_in_id.0).unwrap();
358 if channel_config.num_inputs != graph_in_node.info.channel_config.num_outputs {
359 let old_num_inputs = graph_in_node.info.channel_config.num_outputs;
360 graph_in_node.info.channel_config.num_outputs = channel_config.num_inputs;
361
362 if channel_config.num_inputs < old_num_inputs {
363 for port_idx in channel_config.num_inputs.get()..old_num_inputs.get() {
364 removed_edges.append(&mut self.remove_edges_with_output_port(
365 self.graph_in_id,
366 port_idx,
367 is_restoring_graph_state,
368 ));
369 }
370 }
371
372 self.needs_compile = true;
373 }
374
375 let graph_out_node = self.nodes.get_mut(self.graph_in_id.0).unwrap();
376
377 if channel_config.num_outputs != graph_out_node.info.channel_config.num_inputs {
378 let old_num_outputs = graph_out_node.info.channel_config.num_inputs;
379 graph_out_node.info.channel_config.num_inputs = channel_config.num_outputs;
380
381 if channel_config.num_outputs < old_num_outputs {
382 for port_idx in channel_config.num_outputs.get()..old_num_outputs.get() {
383 removed_edges.append(&mut self.remove_edges_with_input_port(
384 self.graph_out_id,
385 port_idx,
386 is_restoring_graph_state,
387 ));
388 }
389 }
390
391 self.needs_compile = true;
392 }
393
394 self.graph_channel_config = channel_config;
395
396 removed_edges
397 }
398
399 pub fn connect(
417 &mut self,
418 src_node: NodeID,
419 dst_node: NodeID,
420 ports_src_dst: &[(PortIdx, PortIdx)],
421 check_for_cycles: bool,
422 is_restoring_graph_state: bool,
423 ) -> Result<SmallVec<[EdgeID; 4]>, AddEdgeError> {
424 let src_node_entry = self
425 .nodes
426 .get(src_node.0)
427 .ok_or(AddEdgeError::SrcNodeNotFound(src_node))?;
428 let dst_node_entry = self
429 .nodes
430 .get(dst_node.0)
431 .ok_or(AddEdgeError::DstNodeNotFound(dst_node))?;
432
433 if src_node.0 == dst_node.0 {
434 return Err(AddEdgeError::CycleDetected);
435 }
436
437 for (src_port, dst_port) in ports_src_dst.iter().copied() {
438 if src_port >= src_node_entry.info.channel_config.num_outputs.get() {
439 return Err(AddEdgeError::OutPortOutOfRange {
440 node: src_node,
441 port_idx: src_port,
442 num_out_ports: src_node_entry.info.channel_config.num_outputs,
443 });
444 }
445 if dst_port >= dst_node_entry.info.channel_config.num_inputs.get() {
446 return Err(AddEdgeError::InPortOutOfRange {
447 node: dst_node,
448 port_idx: dst_port,
449 num_in_ports: dst_node_entry.info.channel_config.num_inputs,
450 });
451 }
452 }
453
454 let mut edge_ids = SmallVec::new();
455
456 for (src_port, dst_port) in ports_src_dst.iter().copied() {
457 if let Some(id) = self.existing_edges.get(&EdgeHash {
458 src_node,
459 src_port,
460 dst_node,
461 dst_port,
462 }) {
463 edge_ids.push(*id);
465 continue;
466 }
467
468 let new_edge_id = EdgeID(self.edges.insert(Edge {
469 id: EdgeID(thunderdome::Index::DANGLING),
470 src_node,
471 src_port,
472 dst_node,
473 dst_port,
474 }));
475 self.edges[new_edge_id.0].id = new_edge_id;
476 self.existing_edges.insert(
477 EdgeHash {
478 src_node,
479 src_port,
480 dst_node,
481 dst_port,
482 },
483 new_edge_id,
484 );
485
486 edge_ids.push(new_edge_id);
487 }
488
489 if check_for_cycles && self.cycle_detected() {
490 for edge_id in edge_ids {
491 self.disconnect_by_edge_id(edge_id, true);
492 }
493
494 return Err(AddEdgeError::CycleDetected);
495 } else if !is_restoring_graph_state && let Some(guard) = self.modify_guard_stack.last_mut()
496 {
497 guard.new_edges.extend_from_slice(&edge_ids);
498 }
499
500 self.needs_compile = true;
501
502 Ok(edge_ids)
503 }
504
505 pub fn disconnect(
515 &mut self,
516 src_node: NodeID,
517 dst_node: NodeID,
518 ports_src_dst: &[(PortIdx, PortIdx)],
519 ) -> SmallVec<[Edge; 4]> {
520 let mut removed_edges = SmallVec::new();
521
522 for (src_port, dst_port) in ports_src_dst.iter().copied() {
523 if let Some(edge_id) = self.existing_edges.remove(&EdgeHash {
524 src_node,
525 src_port,
526 dst_node,
527 dst_port,
528 }) {
529 self.disconnect_by_edge_id(edge_id, false);
530 removed_edges.push(Edge {
531 id: edge_id,
532 src_node,
533 dst_node,
534 src_port,
535 dst_port,
536 });
537 }
538 }
539
540 removed_edges
541 }
542
543 pub fn disconnect_all_between(
548 &mut self,
549 src_node: NodeID,
550 dst_node: NodeID,
551 ) -> SmallVec<[Edge; 4]> {
552 let mut removed_edges = SmallVec::new();
553
554 if !self.nodes.contains(src_node.0) || !self.nodes.contains(dst_node.0) {
555 return removed_edges;
556 };
557
558 for (_, edge) in self.edges.iter() {
559 if edge.src_node == src_node && edge.dst_node == dst_node {
560 removed_edges.push(*edge);
561 }
562 }
563
564 for &edge in removed_edges.iter() {
565 let _ = self.disconnect_by_edge_id(edge.id, false);
566 }
567
568 removed_edges
569 }
570
571 pub fn disconnect_by_edge_id(
575 &mut self,
576 edge_id: EdgeID,
577 is_restoring_graph_state: bool,
578 ) -> Option<Edge> {
579 if let Some(edge) = self.edges.remove(edge_id.0) {
580 self.existing_edges.remove(&EdgeHash {
581 src_node: edge.src_node,
582 src_port: edge.src_port,
583 dst_node: edge.dst_node,
584 dst_port: edge.dst_port,
585 });
586
587 self.needs_compile = true;
588
589 if !is_restoring_graph_state && let Some(guard) = self.modify_guard_stack.last_mut() {
590 guard.removed_edges.push(edge);
591 }
592
593 Some(edge)
594 } else {
595 None
596 }
597 }
598
599 pub fn edge(&self, edge_id: EdgeID) -> Option<&Edge> {
601 self.edges.get(edge_id.0)
602 }
603
604 fn remove_edges_with_input_port(
605 &mut self,
606 node_id: NodeID,
607 port_idx: PortIdx,
608 is_restoring_graph_state: bool,
609 ) -> SmallVec<[Edge; 4]> {
610 let mut edges_to_remove: SmallVec<[Edge; 4]> = SmallVec::new();
611
612 for (_, edge) in self.edges.iter() {
614 if edge.dst_node == node_id && edge.dst_port == port_idx {
615 edges_to_remove.push(*edge);
616 }
617 }
618
619 for edge in edges_to_remove.iter() {
620 self.disconnect_by_edge_id(edge.id, is_restoring_graph_state);
621 }
622
623 edges_to_remove
624 }
625
626 fn remove_edges_with_output_port(
627 &mut self,
628 node_id: NodeID,
629 port_idx: PortIdx,
630 is_restoring_graph_state: bool,
631 ) -> SmallVec<[Edge; 4]> {
632 let mut edges_to_remove: SmallVec<[Edge; 4]> = SmallVec::new();
633
634 for (_, edge) in self.edges.iter() {
636 if edge.src_node == node_id && edge.src_port == port_idx {
637 edges_to_remove.push(*edge);
638 }
639 }
640
641 for edge in edges_to_remove.iter() {
642 self.disconnect_by_edge_id(edge.id, is_restoring_graph_state);
643 }
644
645 edges_to_remove
646 }
647
648 pub fn cycle_detected(&mut self) -> bool {
649 compiler::cycle_detected(
650 &mut self.nodes,
651 &mut self.edges,
652 self.graph_in_id,
653 self.graph_out_id,
654 )
655 }
656
657 pub(crate) fn needs_compile(&self) -> bool {
658 self.needs_compile
659 }
660
661 pub(crate) fn on_schedule_send_failed(&mut self, failed_schedule: Box<ScheduleHeapData>) {
662 self.needs_compile = true;
663 self.prev_buffer_capacity = 0;
664
665 for node in failed_schedule.new_node_processors.iter() {
666 if let Some(node_entry) = &mut self.nodes.get_mut(node.id.0) {
667 node_entry.processor_constructed = false;
668 }
669 }
670 }
671
672 pub(crate) fn deactivate(&mut self) {
673 self.needs_compile = true;
674 self.prev_buffer_capacity = 0;
675 }
676
677 pub(crate) fn compile(
678 &mut self,
679 stream_info: &StreamInfo,
680 ) -> Result<Box<ScheduleHeapData>, CompileGraphError> {
681 let schedule = self.compile_internal(stream_info.max_block_frames.get() as usize)?;
682
683 let buffer_capacity = schedule.buffer_capacity();
684
685 let mut new_node_processors = Vec::new();
686 for (_, entry) in self.nodes.iter_mut() {
687 if !entry.processor_constructed {
688 entry.processor_constructed = true;
689
690 let cx = ConstructProcessorContext::new(
691 entry.id,
692 stream_info,
693 &mut entry.info.custom_state,
694 );
695
696 new_node_processors.push(NodeHeapData {
697 id: entry.id,
698 processor: entry
699 .dyn_node
700 .construct_processor(cx)
701 .map_err(|node_error| {
702 CompileGraphError::ProcessorConstructionFailed(node_error.to_string())
703 })?,
704 is_pre_process: entry.info.channel_config.is_empty(),
705 in_place_buffers: entry.info.in_place_buffers,
706 });
707 }
708 }
709
710 let mut nodes_to_remove = Vec::new();
711 core::mem::swap(
712 &mut self.nodes_to_remove_from_schedule,
713 &mut nodes_to_remove,
714 );
715
716 let (new_arena, new_profiler_heap_data) =
717 if self.nodes.capacity() > self.prev_node_arena_capacity {
718 (
719 Some(Arena::with_capacity(self.nodes.capacity())),
720 Some(ProfilerHeapData::new(self.nodes.capacity(), true)),
721 )
722 } else {
723 (None, None)
724 };
725 self.prev_node_arena_capacity = self.nodes.capacity();
726
727 let schedule_data = Box::new(ScheduleHeapData::new(
728 schedule,
729 nodes_to_remove,
730 new_node_processors,
731 new_arena,
732 new_profiler_heap_data,
733 ));
734
735 self.needs_compile = false;
736 self.prev_buffer_capacity = buffer_capacity;
737
738 #[cfg(feature = "tracing")]
739 tracing::debug!("compiled new audio graph: {:?}", &schedule_data);
740
741 #[cfg(all(feature = "log", not(feature = "tracing")))]
742 log::debug!("compiled new audio graph: {:?}", &schedule_data);
743
744 Ok(schedule_data)
745 }
746
747 fn compile_internal(
748 &mut self,
749 max_block_frames: usize,
750 ) -> Result<CompiledSchedule, CompileGraphError> {
751 assert!(max_block_frames > 0);
752
753 compiler::compile(
754 &mut self.nodes,
755 &mut self.edges,
756 self.graph_in_id,
757 self.graph_out_id,
758 max_block_frames,
759 self.prev_buffer_capacity,
760 )
761 }
762
763 pub(crate) fn update(
764 &mut self,
765 stream_info: Option<&StreamInfo>,
766 event_queue: &mut Vec<NodeEvent>,
767 ) {
768 let mut cull_list = false;
769 for node_id in self.nodes_to_call_update_method.iter() {
770 if let Some(node_entry) = self.nodes.get_mut(node_id.0) {
771 node_entry.dyn_node.update(UpdateContext::new(
772 *node_id,
773 stream_info,
774 &mut node_entry.info.custom_state,
775 event_queue,
776 ));
777 } else {
778 cull_list = true;
779 }
780 }
781
782 if cull_list {
783 self.nodes_to_call_update_method
784 .retain(|node_id| self.nodes.contains(node_id.0));
785 }
786 }
787
788 pub(crate) fn drop_old_schedule_data(&mut self, mut data: Box<ScheduleHeapData>) {
789 for n in data.removed_nodes.drain(..) {
790 let id = n.id;
791
792 drop(n);
795 firewheel_core::collector::GlobalRtGc::collect();
796
797 let _ = self.active_nodes_to_remove.remove(&id);
798 }
799 }
800}
801
802#[derive(Default)]
803struct ModifyGraphGuard {
804 prev_needs_compile: bool,
805 prev_graph_channel_config: ChannelConfig,
806 new_nodes: Vec<NodeID>,
807 removed_nodes: Vec<NodeEntry>,
808 new_edges: Vec<EdgeID>,
809 removed_edges: Vec<Edge>,
810}