use crate::edge::NodeMap;
use crate::error::SeedlingError;
use crate::pool::sample_effects::EffectOf;
use crate::{SeedlingSystems, prelude::AudioContext};
use bevy::ecs::component::{ComponentId, HookContext, Mutable};
use bevy::ecs::world::DeferredWorld;
use bevy::prelude::*;
use firewheel::{
diff::{Diff, Patch},
event::{NodeEvent, NodeEventType},
node::{AudioNode, NodeID},
};
pub mod follower;
pub mod label;
use label::NodeLabels;
#[derive(Component)]
pub(crate) struct Baseline<T>(pub(crate) T);
#[derive(Component, Clone, Copy)]
pub(crate) struct EffectId(pub(crate) ComponentId);
#[derive(Component, Default)]
pub struct Events(Vec<NodeEventType>);
impl core::fmt::Debug for Events {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_list()
.entries((0..self.0.len()).map(|_| ()))
.finish()
}
}
impl Events {
pub fn push(&mut self, event: NodeEventType) {
self.0.push(event);
}
pub fn push_custom<T: Send + Sync + 'static>(&mut self, value: T) {
self.0.push(NodeEventType::Custom(Box::new(value)));
}
}
fn apply_patch<T: Patch>(value: &mut T, event: &NodeEventType) -> Result {
let NodeEventType::Param { data, path } = event else {
return Ok(());
};
let patch = T::patch(data, path).map_err(|e| SeedlingError::PatchError {
ty: core::any::type_name::<T>(),
error: e,
})?;
value.apply(patch);
Ok(())
}
fn generate_param_events<T: Diff + Patch + Component + Clone>(
mut nodes: Query<(&T, &mut Baseline<T>, &mut Events), (Changed<T>, Without<EffectOf>)>,
) -> Result {
for (params, mut baseline, mut events) in nodes.iter_mut() {
let starting_len = events.0.len();
params.diff(&baseline.0, Default::default(), &mut events.0);
for event in &events.0[starting_len..] {
apply_patch(&mut baseline.0, event)?;
}
}
Ok(())
}
fn acquire_id<T>(
q: Query<
(Entity, &T, Option<&T::Configuration>, Option<&NodeLabels>),
(Without<FirewheelNode>, Without<EffectOf>),
>,
mut context: ResMut<AudioContext>,
mut commands: Commands,
mut node_map: ResMut<NodeMap>,
) where
T: AudioNode<Configuration: Component + Clone> + Component + Clone,
{
if q.iter().len() == 0 {
return;
}
context.with(|context| {
for (entity, container, config, labels) in q.iter() {
let node = context.add_node(container.clone(), config.cloned());
for label in labels.iter().flat_map(|l| l.iter()) {
node_map.insert(*label, entity);
}
commands.entity(entity).insert(FirewheelNode(node));
}
});
}
pub trait RegisterNode {
fn register_node<T>(&mut self) -> &mut Self
where
T: AudioNode<Configuration: Component + Clone>
+ Diff
+ Patch
+ Component<Mutability = Mutable>
+ Clone;
fn register_simple_node<T>(&mut self) -> &mut Self
where
T: AudioNode<Configuration: Component + Clone> + Component + Clone;
}
impl RegisterNode for App {
fn register_node<T>(&mut self) -> &mut Self
where
T: AudioNode<Configuration: Component + Clone>
+ Diff
+ Patch
+ Component<Mutability = Mutable>
+ Clone,
{
let world = self.world_mut();
world.register_component_hooks::<T>().on_insert(
|mut world: DeferredWorld, context: HookContext| {
let value = world.get::<T>(context.entity).unwrap().clone();
world
.commands()
.entity(context.entity)
.insert((Baseline(value), EffectId(context.component_id)));
},
);
world.register_required_components::<T, Events>();
world.register_required_components::<T, T::Configuration>();
self.add_systems(
Last,
(
acquire_id::<T>.in_set(SeedlingSystems::Acquire),
(follower::param_follower::<T>, generate_param_events::<T>)
.chain()
.in_set(SeedlingSystems::Queue),
),
)
}
fn register_simple_node<T>(&mut self) -> &mut Self
where
T: AudioNode<Configuration: Component + Clone> + Component + Clone,
{
let world = self.world_mut();
world.register_required_components::<T, Events>();
world.register_required_components::<T, T::Configuration>();
self.add_systems(Last, acquire_id::<T>.in_set(SeedlingSystems::Acquire))
}
}
#[derive(Debug, Clone, Copy, Component)]
#[component(on_remove = on_remove_node, immutable)]
pub struct FirewheelNode(pub NodeID);
fn on_remove_node(mut world: DeferredWorld, context: HookContext) {
let Some(node) = world.get::<FirewheelNode>(context.entity).copied() else {
return;
};
let mut removals = world.resource_mut::<PendingRemovals>();
removals.push(node.0);
}
#[derive(Debug, Default, Resource)]
pub(crate) struct PendingRemovals(Vec<NodeID>);
impl PendingRemovals {
pub fn push(&mut self, node: NodeID) {
self.0.push(node);
}
}
pub(crate) fn flush_events(
mut nodes: Query<(&FirewheelNode, &mut Events)>,
mut removals: ResMut<PendingRemovals>,
mut context: ResMut<AudioContext>,
) {
context.with(|context| {
for node in removals.0.drain(..) {
if context.remove_node(node).is_err() {
error!("attempted to remove non-existent or invalid node from audio graph");
}
}
for (node, mut events) in nodes.iter_mut() {
for event in events.0.drain(..) {
context.queue_event(NodeEvent {
node_id: node.0,
event,
});
}
}
if let Err(e) = context.update() {
error!("graph error: {:?}", e);
}
});
}