use super::{DEFAULT_CONNECTION, EdgeTarget, NodeMap, PendingEdge};
use crate::{context::AudioContext, node::FirewheelNode};
use bevy_ecs::prelude::*;
use bevy_log::prelude::*;
#[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 {
self.disconnect_with(target, DEFAULT_CONNECTION)
}
#[cfg_attr(debug_assertions, track_caller)]
fn disconnect_with(self, target: impl Into<EdgeTarget>, ports: &[(u32, u32)]) -> Self;
}
impl Disconnect for EntityCommands<'_> {
fn disconnect_with(mut self, target: impl Into<EdgeTarget>, ports: &[(u32, u32)]) -> Self {
let target = target.into();
let ports = ports.to_vec();
#[cfg(debug_assertions)]
let location = Location::caller();
self.entry::<PendingDisconnections>()
.or_default()
.and_modify(|mut pending| {
pending.push(PendingEdge::new_with_location(
target,
Some(ports),
#[cfg(debug_assertions)]
location,
));
});
self
}
}
pub(crate) fn process_disconnections(
mut disconnections: Query<(&mut PendingDisconnections, &FirewheelNode)>,
targets: Query<&FirewheelNode>,
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 ports = disconnections.ports.as_deref().unwrap_or(DEFAULT_CONNECTION);
let target_entity = match disconnections.target {
EdgeTarget::Entity(entity) => entity,
EdgeTarget::Label(label) => {
let Some(entity) = node_map.get(&label) else {
#[cfg(debug_assertions)]
{
let location = disconnections.origin;
error_once!("failed to disconnect from node label `{label:?}` at {location}: no associated Firewheel node found");
}
#[cfg(not(debug_assertions))]
error_once!("failed to disconnect from node label `{label:?}`: no associated Firewheel node found");
return true;
};
*entity
}
EdgeTarget::Node(dest_node) => {
context.disconnect(source_node.0, dest_node, ports);
return false;
}
};
let target = match targets.get(target_entity) {
Ok(t) => t,
Err(_) => {
#[cfg(debug_assertions)]
{
let location = disconnections.origin;
error_once!("failed to disconnect from entity `{target_entity:?}` at {location}: no Firewheel node found");
}
#[cfg(not(debug_assertions))]
error_once!("failed to disconnect from entity `{target_entity:?}`: no Firewheel node found");
return false;
}
};
context.disconnect(source_node.0, target.0, 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));
});
},
);
}
}