mittens-engine 0.7.0

A Vulkan and OpenXR scene engine with ECS, reactive signals, and Meow Meow scripting
use crate::engine::ecs::component::{KeyframeComponent, MusicNoteComponent};
use crate::engine::ecs::{ComponentId, IntentValue, RxWorld, SignalEmitter, World};
use crate::scripting::world_evaluator::{RuntimeClosureExecMode, eval_runtime_closure};

#[derive(Debug, Default)]
pub(crate) struct AnimationKeyframeEvaluator;

impl AnimationKeyframeEvaluator {
    pub(crate) fn evaluate_audio_due_keyframe(
        &self,
        world: &mut World,
        rx: &mut RxWorld,
        kf_id: ComponentId,
        kf_global_beat: f64,
    ) {
        let runtime_closure = world
            .get_component_by_id_as::<KeyframeComponent>(kf_id)
            .and_then(|kf| kf.callback.clone());

        if let Some(runtime_closure) = runtime_closure {
            if let Err(error) = eval_runtime_closure(
                &runtime_closure,
                None,
                Some(world),
                Some(rx),
                Some(kf_id),
                RuntimeClosureExecMode::KeyframeAudioOnly {
                    beat_context: kf_global_beat,
                },
            ) {
                eprintln!(
                    "[AnimationSystem] keyframe runtime closure audio lookahead failed for {kf_id:?}: {error}"
                );
            }
        }

        fire_music_note_children(world, rx, kf_id, Some(kf_global_beat));
    }

    pub(crate) fn evaluate_visual_due_keyframe(
        &self,
        world: &mut World,
        rx: &mut RxWorld,
        kf_id: ComponentId,
        beat_now: f64,
        audio_already_scheduled_this_cycle: bool,
    ) {
        let runtime_closure = world
            .get_component_by_id_as::<KeyframeComponent>(kf_id)
            .and_then(|kf| kf.callback.clone());

        if let Some(runtime_closure) = runtime_closure {
            if let Err(error) = eval_runtime_closure(
                &runtime_closure,
                None,
                Some(world),
                Some(rx),
                Some(kf_id),
                RuntimeClosureExecMode::KeyframeVisualOnly,
            ) {
                eprintln!(
                    "[AnimationSystem] keyframe runtime closure failed for {kf_id:?}: {error}"
                );
            }
        }

        if !audio_already_scheduled_this_cycle {
            fire_music_note_children(world, rx, kf_id, Some(beat_now));
        }
    }
}

fn fire_music_note_children(
    world: &mut World,
    rx: &mut RxWorld,
    kf_id: ComponentId,
    beat_context: Option<f64>,
) {
    let note_ids: Vec<ComponentId> = world
        .children_of(kf_id)
        .iter()
        .copied()
        .filter(|&cid| {
            world
                .get_component_by_id_as::<MusicNoteComponent>(cid)
                .is_some()
        })
        .collect();

    for note_cid in note_ids {
        let note = match world.get_component_by_id_as::<MusicNoteComponent>(note_cid) {
            Some(mn) => mn.note,
            None => continue,
        };
        rx.push_intent_now(
            note_cid,
            IntentValue::AudioSchedulePlay {
                component_id: note_cid,
                beat_offset: 0.0,
                beat_context,
                note: Some(note),
                gain: None,
                rate: None,
                duration: None,
            },
        );
    }
}