use super::{EdgeTarget, NodeMap, PendingEdge};
use crate::{
context::AudioContext,
node::{FirewheelNode, FirewheelNodeInfo},
};
use bevy_ecs::prelude::*;
use core::ops::Deref;
#[cfg(debug_assertions)]
use core::panic::Location;
#[derive(Debug, Default, Component)]
pub struct PendingDisconnections(Vec<PendingEdge>);
impl PendingDisconnections {
pub fn push(&mut self, disconnection: PendingEdge) {
self.0.push(disconnection)
}
}
pub trait Disconnect: Sized {
#[cfg_attr(debug_assertions, track_caller)]
fn disconnect(self, target: impl Into<EdgeTarget>) -> Self;
#[cfg_attr(debug_assertions, track_caller)]
fn disconnect_with(self, target: impl Into<EdgeTarget>, ports: &[(u32, u32)]) -> Self;
}
#[cfg_attr(debug_assertions, track_caller)]
fn disconnect_with_commands(
target: EdgeTarget,
ports: Option<Vec<(u32, u32)>>,
commands: &mut EntityCommands,
) {
#[cfg(debug_assertions)]
let location = Location::caller();
commands
.entry::<PendingDisconnections>()
.or_default()
.and_modify(|mut pending| {
pending.push(PendingEdge::new_with_location(
target,
ports,
#[cfg(debug_assertions)]
location,
));
});
}
impl Disconnect for EntityCommands<'_> {
fn disconnect(mut self, target: impl Into<EdgeTarget>) -> Self {
let target = target.into();
disconnect_with_commands(target, None, &mut self);
self
}
fn disconnect_with(mut self, target: impl Into<EdgeTarget>, ports: &[(u32, u32)]) -> Self {
let target = target.into();
let ports = ports.to_vec();
disconnect_with_commands(target, Some(ports), &mut self);
self
}
}
pub(crate) fn process_disconnections(
mut disconnections: Query<(&mut PendingDisconnections, &FirewheelNode)>,
targets: Query<(&FirewheelNode, &FirewheelNodeInfo)>,
node_map: Res<NodeMap>,
mut context: ResMut<AudioContext>,
) {
let disconnections = disconnections
.iter_mut()
.filter(|(pending, _)| !pending.0.is_empty())
.collect::<Vec<_>>();
if disconnections.is_empty() {
return;
}
context.with(|context| {
for (mut pending, source_node) in disconnections.into_iter() {
pending.0.retain(|disconnections| {
let Some((target_node, _target_info)) =
super::fetch_target(disconnections, &node_map, &targets, (*context).deref())
else {
return false;
};
let existing_connections;
let ports = match disconnections.ports.as_deref() {
Some(ports) => ports,
None => {
existing_connections = context
.edges()
.into_iter()
.filter(|e| e.src_node == source_node.0 && e.dst_node == target_node)
.map(|e| (e.src_port, e.dst_port))
.collect::<Vec<_>>();
existing_connections.as_slice()
}
};
context.disconnect(source_node.0, target_node, ports);
false
});
}
});
}
#[cfg(test)]
mod test {
use crate::{
context::AudioContext,
edge::{AudioGraphOutput, Connect},
prelude::MainBus,
test::{prepare_app, run},
};
use super::*;
use firewheel::nodes::volume::VolumeNode;
#[derive(Component)]
struct One;
#[derive(Component)]
struct Two;
#[test]
fn test_disconnect() {
let mut app = prepare_app(|mut commands: Commands| {
commands
.spawn((VolumeNode::default(), One))
.chain_node((VolumeNode::default(), Two));
commands
.spawn((VolumeNode::default(), MainBus))
.connect(AudioGraphOutput);
});
run(
&mut app,
|mut context: ResMut<AudioContext>,
one: Single<&FirewheelNode, With<One>>,
two: Single<&FirewheelNode, With<Two>>,
main: Single<&FirewheelNode, With<MainBus>>| {
let one = one.into_inner();
let two = two.into_inner();
let main = main.into_inner();
context.with(|context| {
assert_eq!(context.nodes().len(), 5);
let outgoing_edges_one: Vec<_> = context
.edges()
.into_iter()
.filter(|e| e.src_node == one.0)
.collect();
let outgoing_edges_two: Vec<_> = context
.edges()
.into_iter()
.filter(|e| e.src_node == two.0)
.collect();
assert_eq!(outgoing_edges_one.len(), 2);
assert_eq!(outgoing_edges_two.len(), 2);
assert!(outgoing_edges_one.iter().all(|e| e.dst_node == two.0));
assert!(outgoing_edges_two.iter().all(|e| e.dst_node == main.0));
});
},
);
run(
&mut app,
|one: Single<Entity, With<One>>,
two: Single<Entity, With<Two>>,
mut commands: Commands| {
let one = one.into_inner();
let two = two.into_inner();
commands.entity(one).disconnect(two);
},
);
app.update();
run(
&mut app,
|mut context: ResMut<AudioContext>,
one: Single<&FirewheelNode, With<One>>,
two: Single<&FirewheelNode, With<Two>>,
main: Single<&FirewheelNode, With<MainBus>>| {
let one = one.into_inner();
let two = two.into_inner();
let main = main.into_inner();
context.with(|context| {
assert_eq!(context.nodes().len(), 5);
let outgoing_edges_one: Vec<_> = context
.edges()
.into_iter()
.filter(|e| e.src_node == one.0)
.collect();
let outgoing_edges_two: Vec<_> = context
.edges()
.into_iter()
.filter(|e| e.src_node == two.0)
.collect();
assert_eq!(outgoing_edges_one.len(), 0);
assert_eq!(outgoing_edges_two.len(), 2);
assert!(outgoing_edges_one.iter().all(|e| e.dst_node == two.0));
assert!(outgoing_edges_two.iter().all(|e| e.dst_node == main.0));
});
},
);
}
}