nightshade 0.8.0

A cross-platform data-oriented game engine.
Documentation
use std::collections::HashMap;

use freecs::Entity;
use kira::{
    AudioManager, AudioManagerSettings, DefaultBackend, Mix,
    effect::reverb::ReverbBuilder,
    listener::ListenerHandle,
    sound::static_sound::{StaticSoundData, StaticSoundHandle},
    track::{SendTrackBuilder, SendTrackHandle, SpatialTrackHandle},
};

#[derive(Default)]
pub struct AudioEngine {
    pub manager: Option<AudioManager<DefaultBackend>>,
    pub listener: Option<ListenerHandle>,
    pub reverb_send: Option<SendTrackHandle>,
    pub initialized: bool,
    pub sound_cache: HashMap<String, StaticSoundData>,
    pub sound_handles: HashMap<Entity, StaticSoundHandle>,
    pub spatial_tracks: HashMap<Entity, SpatialTrackHandle>,
}

impl AudioEngine {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn initialize(&mut self) -> Result<(), Box<dyn std::error::Error>> {
        if self.initialized {
            return Ok(());
        }

        match AudioManager::<DefaultBackend>::new(AudioManagerSettings::default()) {
            Ok(mut manager) => {
                let reverb_send = manager
                    .add_send_track(
                        SendTrackBuilder::new()
                            .with_effect(ReverbBuilder::new().mix(Mix::WET).damping(0.5)),
                    )
                    .map_err(|e| format!("Failed to create reverb send track: {}", e))?;

                self.reverb_send = Some(reverb_send);
                self.manager = Some(manager);
                self.initialized = true;
                tracing::info!("Audio engine initialized successfully with reverb");
                Ok(())
            }
            Err(error) => {
                tracing::error!("Failed to initialize audio engine: {}", error);
                Err(Box::new(error))
            }
        }
    }

    pub fn is_initialized(&self) -> bool {
        self.initialized && self.manager.is_some()
    }

    pub fn load_sound(&mut self, name: impl Into<String>, data: StaticSoundData) {
        self.sound_cache.insert(name.into(), data);
    }

    pub fn get_sound(&self, name: &str) -> Option<&StaticSoundData> {
        self.sound_cache.get(name)
    }

    pub fn has_handle(&self, entity: Entity) -> bool {
        self.sound_handles.contains_key(&entity) || self.spatial_tracks.contains_key(&entity)
    }

    pub fn stop_sound(&mut self, entity: Entity) {
        use kira::Tween;
        if let Some(handle) = self.sound_handles.get_mut(&entity) {
            handle.stop(Tween::default());
        }
        self.sound_handles.remove(&entity);
        self.spatial_tracks.remove(&entity);
    }

    pub fn cleanup_entity(&mut self, entity: Entity) {
        self.stop_sound(entity);
    }
}