Skip to main content

firewheel_graph/
graph.rs

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
43/// The audio graph interface.
44pub(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    /// The ID of the graph input node
186    pub fn graph_in_node(&self) -> NodeID {
187        self.graph_in_id
188    }
189
190    /// The ID of the graph output node
191    pub fn graph_out_node(&self) -> NodeID {
192        self.graph_out_id
193    }
194
195    /// Add a node to the audio graph.
196    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    /// Add a node to the audio graph which implements the type-erased [`DynAudioNode`] trait.
225    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    /// Remove the given node from the audio graph.
249    ///
250    /// This will automatically remove all edges from the graph that
251    /// were connected to this node.
252    ///
253    /// On success, this returns a list of all edges that were removed
254    /// from the graph as a result of removing this node.
255    ///
256    /// This will return an error if the ID is of the graph input or graph
257    /// output node.
258    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    /// Get information about a node in the graph.
304    pub fn node_info(&self, id: NodeID) -> Option<&NodeEntry> {
305        self.nodes.get(id.0)
306    }
307
308    /// Returns `true` if the node exists in the graph.
309    pub fn contains_node(&self, id: NodeID) -> bool {
310        self.nodes.contains(id.0)
311    }
312
313    /// Get an immutable reference to the custom state of a node.
314    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    /// Get a type-erased, immutable reference to the custom state of a node.
319    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    /// Get a mutable reference to the custom state of a node.
326    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    /// Get a type-erased, mutable reference to the custom state of a node.
331    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    /// Get a list of all the existing nodes in the graph.
338    pub fn nodes(&self) -> impl Iterator<Item = &NodeEntry> {
339        self.nodes.iter().map(|(_, n)| n)
340    }
341
342    /// Get a list of all the existing edges in the graph.
343    pub fn edges(&self) -> impl Iterator<Item = &Edge> {
344        self.edges.iter().map(|(_, e)| e)
345    }
346
347    /// Set the number of input and output channels to and from the audio graph.
348    ///
349    /// Returns the list of edges that were removed.
350    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    /// Add connections (edges) between two nodes to the graph.
400    ///
401    /// * `src_node` - The ID of the source node.
402    /// * `dst_node` - The ID of the destination node.
403    /// * `ports_src_dst` - The port indices for each connection to make,
404    ///   where the first value in a tuple is the output port on `src_node`,
405    ///   and the second value in that tuple is the input port on `dst_node`.
406    /// * `check_for_cycles` - If `true`, then this will run a check to
407    ///   see if adding these edges will create a cycle in the graph, and
408    ///   return an error if it does. Note, checking for cycles can be quite
409    ///   expensive, so avoid enabling this when calling this method many times
410    ///   in a row.
411    ///
412    /// If successful, then this returns a list of edge IDs in order.
413    ///
414    /// If this returns an error, then the audio graph has not been
415    /// modified.
416    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                // The caller gave us more than one of the same edge.
464                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    /// Remove connections (edges) between two nodes from the graph.
506    ///
507    /// * `src_node` - The ID of the source node.
508    /// * `dst_node` - The ID of the destination node.
509    /// * `ports_src_dst` - The port indices for each connection to make,
510    ///   where the first value in a tuple is the output port on `src_node`,
511    ///   and the second value in that tuple is the input port on `dst_node`.
512    ///
513    /// Returns the list of edges that were successfully removed.
514    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    /// Remove all connections (edges) between two nodes in the graph.
544    ///
545    /// * `src_node` - The ID of the source node.
546    /// * `dst_node` - The ID of the destination node.
547    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    /// Remove a connection (edge) via the edge's unique ID.
572    ///
573    /// If the edge did not exist in this graph, then `None` will be returned.
574    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    /// Get information about the given [Edge]
600    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        // Remove all existing edges which have this port.
613        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        // Remove all existing edges which have this port.
635        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            // Make sure all node processors are dropped before node states in
793            // order to be compatible with CLAP plugin hosting.
794            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}