1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! High-level helpers for running simulations following best practices.
//!
//! A [multi-threaded simulation architecture](crate::simulation#multi-threading-architecture) is recommended to get the best out of Steam Audio.
//! However, getting the configuration right is error-prone, and mistakes can lead to poor
//! game performance.
//!
//! This module provides a toolbox to compose simulation pipelines that adhere to best practices,
//! using different levels of abstraction:
//! - [`Simulation`]: the recommended starting point.
//! It can spawn concurrent simulation threads that all share the same lock-free [`Source`] buffers.
//! The game thread publishes a new buffer of sources each frame, and the audio thread retrieves
//! simulation outputs without ever blocking.
//! - [`SimulationRunner`] for advanced use cases where you need to drive a custom [`SimulationStep`] on a dedicated thread.
//!
//! For full control, you can bypass the `wiring` module entirely and use the core AudioNimbus API
//! directly (see the [`Simulator`]).
//!
//! This module is gated behind the `wiring` feature (enabled by default).
//!
//! ## Example
//!
//! The example below wires up direct (occlusion/attenuation) and reflections simulation
//! for a small scene with two sources.
//! It mirrors a typical game-loop structure [split into separate threads](crate::simulation#multi-threading-architecture):
//! - Game thread: Calls [`Simulation::update_sources`] each frame to publish the latest sources.
//! - Simulation threads: Spawned by [`Simulation::spawn_direct`] and similar methods; runs continuously,
//! picking up the latest sources on each iteration.
//! - Audio thread: Reads the most recent simulation outputs available and applies the
//! corresponding effects.
//!
//! ```
//! # use audionimbus::wiring::*;
//! # use audionimbus::*;
//! # let context = Context::default();
//! # let audio_settings = AudioSettings::default();
//! let simulation_settings = SimulationSettings::new(&audio_settings)
//! .with_direct(DirectSimulationSettings {
//! max_num_occlusion_samples: 32,
//! })
//! .with_reflections(ConvolutionSettings {
//! max_num_rays: 4096,
//! num_diffuse_samples: 32,
//! max_duration: 2.0,
//! max_num_sources: 8,
//! num_threads: 2,
//! max_order: 2,
//! });
//!
//! let mut simulator = Simulator::try_new(&context, &simulation_settings)?;
//!
//! // Build and commit a scene so the simulator has geometry to trace rays against.
//! let mut scene = Scene::try_new(&context)?;
//! // ... add static meshes to the scene ...
//! scene.commit();
//! simulator.set_scene(&scene);
//! simulator.commit();
//!
//! // Create two sources.
//! let source_a = Source::<Direct, Reflections, (), Convolution>::try_new(&simulator)?;
//! let source_b = Source::<Direct, Reflections, (), Convolution>::try_new(&simulator)?;
//! simulator.add_source(&source_a);
//! simulator.add_source(&source_b);
//!
//! // Sources are identified by a u32 `SourceId` here, but any `Clone + Send + Sync` type works.
//! let mut simulation = Simulation::new::<u32>(simulator);
//!
//! // Commit is required after adding sources so the simulator picks them up.
//! simulation.request_simulator_commit();
//!
//! // Spawn one thread per simulation type.
//! // Each thread shares the same list of sources that the game thread writes every frame.
//! let on_error = |error| {
//! eprintln!("{error}");
//! };
//! let mut direct_simulation = simulation.spawn_direct(on_error);
//! let mut reflections_simulation = simulation.spawn_reflections(on_error);
//!
//! // Game thread loop (runs every frame)
//! // Publish the world-state snapshot for this frame.
//! // Simulation threads will pick it up on their next iteration.
//! // In a real game this would live inside the frame/tick function.
//! # let listener_transform = CoordinateSystem::default();
//! # let source_a_transform = CoordinateSystem::default();
//! # let source_b_transform = CoordinateSystem::default();
//! simulation.update_sources(|sources| {
//! for (id, source, transform) in [
//! (0, &source_a, source_a_transform),
//! (1, &source_b, source_b_transform),
//! ] {
//! sources.push((
//! id,
//! SourceWithInputs {
//! source: source.clone(),
//! simulation_inputs: SimulationInputs {
//! source: transform,
//! parameters: SimulationParameters::new()
//! .with_direct(DirectSimulationParameters::new())
//! .with_reflections(ConvolutionParameters {
//! baked_data_identifier: None,
//! }),
//! },
//! },
//! ));
//! }
//! });
//!
//! // Audio thread
//! // `load` is lock-free and safe to call from the audio thread at any time.
//! let direct_outputs = direct_simulation.output().load();
//! let reflections_outputs = reflections_simulation.output().load();
//! for (id, params) in direct_outputs.iter() {
//! // Apply direct effect for source `id`.
//! }
//! for (id, params) in reflections_outputs.sources.iter() {
//! // Apply reflections effect for source `id`.
//! }
//!
//! // Teardown
//! simulation.shutdown();
//! direct_simulation.join().expect("direct simulation thread panicked");
//! reflections_simulation.join().expect("reflections simulation thread panicked");
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! ## Pausing and resuming simulation threads
//!
//! Simulation is compute-intensive.
//! If no sources are active (e.g. during a loading screen), you can pause all spawned threads
//! without tearing them down:
//!
//! ```
//! # use audionimbus::wiring::*;
//! # use audionimbus::*;
//! # let context = Context::default();
//! # let audio_settings = AudioSettings::default();
//! # let simulation_settings = SimulationSettings::new(&audio_settings)
//! # .with_direct(DirectSimulationSettings {
//! # max_num_occlusion_samples: 32,
//! # });
//! # let simulator = Simulator::try_new(&context, &simulation_settings)?;
//! # let mut simulation = Simulation::new::<()>(simulator);
//! # let direct_simulator = simulation.spawn_direct(|error| {
//! # eprintln!("{error}");
//! # });
//! // Pause all simulation threads.
//! simulation.pause();
//!
//! // ... do other work ...
//!
//! // Resume when gameplay is about to start again.
//! simulation.resume();
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! Individual simulations also expose their own [`pause`](DirectSimulation::pause) /
//! [`resume`](DirectSimulation::resume) methods for finer control.
use crate;
use SimulationRunner;
use Simulation;
use SimulationStep;
pub use *;
pub use *;
pub use *;