#[cfg(doc)]
use super::runner::SimulationRunner;
use crate::audio_settings::AudioSettings;
use crate::geometry::Scene;
use crate::ray_tracing::RayTracer;
use crate::simulation::{SimulationInputs, Simulator, Source};
use arc_swap::ArcSwap;
use object_pool::{Pool, ReusableOwned};
use std::collections::HashSet;
use std::sync::{
Arc, Condvar, Mutex,
atomic::{AtomicBool, Ordering},
};
mod direct;
pub use direct::*;
mod reflections;
pub use reflections::*;
mod reflections_reverb;
pub use reflections_reverb::*;
mod pathing;
pub use pathing::*;
pub struct Simulation<SourceId, T, D, R, P, RE>
where
T: RayTracer,
{
pub simulator: Simulator<T, D, R, P, RE>,
pub sources_pool: SourcesPool<SourceId, D, R, P, RE>,
pub sources: SharedSources<SourceId, D, R, P, RE>,
pub simulator_commit_needed: Arc<AtomicBool>,
pub pending_scene_commits: Arc<ArcSwap<HashSet<Scene<T>>>>,
pub shutdowns: Vec<Arc<AtomicBool>>,
pub paused: Vec<Arc<(Mutex<bool>, Condvar)>>,
}
impl<T, D, R, P, RE> Simulation<(), T, D, R, P, RE>
where
T: RayTracer,
{
pub fn new<SourceId>(
simulator: Simulator<T, D, R, P, RE>,
) -> Simulation<SourceId, T, D, R, P, RE> {
let sources_pool = Arc::new(Pool::new(4, Default::default));
let sources = Arc::new(ArcSwap::new(Arc::new(
sources_pool.pull_owned(Default::default),
)));
let simulator_commit_needed = Arc::new(AtomicBool::new(false));
let pending_scene_commits = Arc::new(ArcSwap::new(Arc::new(HashSet::new())));
let shutdowns = vec![];
let paused = vec![];
Simulation {
simulator,
sources_pool,
sources,
simulator_commit_needed,
pending_scene_commits,
shutdowns,
paused,
}
}
}
impl<SourceId, T, D, R, P, RE> Simulation<SourceId, T, D, R, P, RE>
where
T: RayTracer,
{
pub fn simulator(&self) -> &Simulator<T, D, R, P, RE> {
&self.simulator
}
pub const fn audio_settings(&self) -> AudioSettings {
self.simulator.audio_settings()
}
pub fn sources_pool(&self) -> &SourcesPool<SourceId, D, R, P, RE> {
&self.sources_pool
}
pub fn update_sources<F>(&self, f: F)
where
F: FnOnce(&mut Vec<(SourceId, SourceWithInputs<D, R, P, RE>)>),
{
let mut sources = self.sources_pool.pull_owned(Vec::default);
sources.clear();
f(&mut sources);
self.sources.store(Arc::new(sources));
}
pub fn request_simulator_commit(&mut self) {
self.simulator_commit_needed.store(true, Ordering::Relaxed);
}
pub fn request_scene_commits(&self, scene_commits: &[Scene<T>]) {
self.pending_scene_commits.rcu(|pending_scene_commits| {
#[allow(clippy::mutable_key_type)]
let mut new_pending_scene_commits =
HashSet::with_capacity(pending_scene_commits.len() + scene_commits.len());
new_pending_scene_commits.extend(pending_scene_commits.iter().cloned());
new_pending_scene_commits.extend(scene_commits.iter().cloned());
new_pending_scene_commits
});
}
pub fn shutdown(&self) {
for shutdown in &self.shutdowns {
shutdown.store(true, Ordering::Relaxed);
}
self.resume();
}
pub fn pause(&self) {
for thread in &self.paused {
*thread.0.lock().unwrap() = true;
}
}
pub fn resume(&self) {
for thread in &self.paused {
*thread.0.lock().unwrap() = false;
thread.1.notify_one();
}
}
}
impl<SourceId, T, D, R, P, RE> Drop for Simulation<SourceId, T, D, R, P, RE>
where
T: RayTracer,
{
fn drop(&mut self) {
self.shutdown();
}
}
pub(crate) struct SimulationControls {
handle: Option<std::thread::JoinHandle<()>>,
paused: Arc<(Mutex<bool>, Condvar)>,
shutdown: Arc<AtomicBool>,
}
impl SimulationControls {
pub(crate) fn new(
handle: std::thread::JoinHandle<()>,
paused: Arc<(Mutex<bool>, Condvar)>,
shutdown: Arc<AtomicBool>,
) -> Self {
Self {
handle: Some(handle),
paused,
shutdown,
}
}
pub(crate) fn pause(&self) {
*self.paused.0.lock().unwrap() = true;
}
pub(crate) fn resume(&self) {
*self.paused.0.lock().unwrap() = false;
self.paused.1.notify_one();
}
pub(crate) fn shutdown(&self) {
self.shutdown.store(true, Ordering::Relaxed);
*self.paused.0.lock().unwrap() = false;
self.paused.1.notify_one();
}
pub(crate) fn join(&mut self) -> std::thread::Result<()> {
self.handle
.take()
.map_or(Ok(()), std::thread::JoinHandle::join)
}
}
impl Drop for SimulationControls {
fn drop(&mut self) {
self.shutdown();
let _ = self.join();
}
}
#[derive(Clone, Debug)]
pub struct SourceWithInputs<D, R, P, RE> {
pub source: Source<D, R, P, RE>,
pub simulation_inputs: SimulationInputs<D, R, P>,
}
pub type SourcesPool<SourceId, D, R, P, RE> =
Arc<Pool<Vec<(SourceId, SourceWithInputs<D, R, P, RE>)>>>;
pub type SharedSources<SourceId, D, R, P, RE> =
Arc<ArcSwap<ReusableOwned<Vec<(SourceId, SourceWithInputs<D, R, P, RE>)>>>>;
pub struct SharedSimulationOutput<T: 'static + Send + Sync>(
pub(crate) Arc<ArcSwap<ReusableOwned<T>>>,
);
impl<T: 'static + Send + Sync> SharedSimulationOutput<T> {
pub fn load(&self) -> arc_swap::Guard<Arc<ReusableOwned<T>>> {
self.0.load()
}
}
impl<T: 'static + Send + Sync> Clone for SharedSimulationOutput<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
#[test]
fn test_new() {
let context = Context::default();
let audio_settings = AudioSettings::default();
let simulation_settings = SimulationSettings::new(&audio_settings)
.with_direct(DirectSimulationSettings {
max_num_occlusion_samples: 4,
})
.with_reflections(ConvolutionSettings {
max_num_rays: 128,
num_diffuse_samples: 8,
max_duration: 0.5,
max_num_sources: 4,
num_threads: 1,
max_order: 1,
});
let simulator = Simulator::try_new(&context, &simulation_settings).unwrap();
let _simulation = Simulation::new::<()>(simulator);
}
#[test]
fn test_update_clears_buffer_between_calls() {
let context = Context::default();
let audio_settings = AudioSettings::default();
let simulation_settings = SimulationSettings::new(&audio_settings)
.with_direct(DirectSimulationSettings {
max_num_occlusion_samples: 4,
})
.with_reflections(ConvolutionSettings {
max_num_rays: 128,
num_diffuse_samples: 8,
max_duration: 0.5,
max_num_sources: 4,
num_threads: 1,
max_order: 1,
});
let simulator = Simulator::try_new(&context, &simulation_settings).unwrap();
let simulator_clone = simulator.clone();
let simulation = Simulation::new::<()>(simulator);
let source =
Source::<Direct, Reflections, (), Convolution>::try_new(&simulator_clone).unwrap();
simulation.update_sources(|sources| {
sources.push((
(),
SourceWithInputs {
source: source.clone(),
simulation_inputs: SimulationInputs {
source: CoordinateSystem::default(),
parameters: SimulationParameters::new()
.with_direct(DirectSimulationParameters::new())
.with_reflections(ConvolutionParameters {
baked_data_identifier: None,
}),
},
},
));
assert_eq!(sources.len(), 1);
});
simulation.update_sources(|sources| {
assert!(
sources.is_empty(),
"update should receive an empty buffer each call"
);
});
}
#[test]
fn test_shutdown() {
let context = Context::default();
let audio_settings = AudioSettings::default();
let simulation_settings = SimulationSettings::new(&audio_settings)
.with_direct(DirectSimulationSettings {
max_num_occlusion_samples: 4,
})
.with_reflections(ConvolutionSettings {
max_num_rays: 128,
num_diffuse_samples: 8,
max_duration: 0.5,
max_num_sources: 4,
num_threads: 1,
max_order: 1,
});
let simulator = Simulator::try_new(&context, &simulation_settings).unwrap();
let mut simulation = Simulation::new::<()>(simulator);
let mut direct_simulation = simulation.spawn_direct(|error| {
eprintln!("{error}");
});
simulation.shutdown();
direct_simulation
.join()
.expect("simulation thread panicked");
}
}