Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ bevy_reflect = [
"firewheel-core/bevy_reflect",
"firewheel-graph/bevy_reflect",
]
# Enables the wasm-bindgen feature for the CPAL backend
wasm-bindgen = ["firewheel-cpal/wasm-bindgen"]
# Enables wasm-bindgen for dependencies
wasm-bindgen = ["firewheel-cpal/wasm-bindgen", "firewheel-graph/wasm-bindgen"]
# Enables `glam::Vec2` and `glam::Vec3` parameter derives for glam 0.29.
glam-29 = ["firewheel-core/glam-29"]
# Enables `glam::Vec2` and `glam::Vec3` parameter derives for glam 0.30.
Expand Down
4 changes: 4 additions & 0 deletions crates/firewheel-graph/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ node_profiling = []
# For an explanation on why denormal numbers are a problem, see:
# https://mu.krj.st/denormal/
unsafe_flush_denormals_to_zero = []
# Provides wasm-bindgen, allowing for non-panicking `Instant::now`
# calls in audio worklet contexts.
wasm-bindgen = ["dep:wasm-bindgen"]

[dependencies]
firewheel-core = { path = "../firewheel-core", version = "0.10.0", default-features = false }
Expand All @@ -78,6 +81,7 @@ num-traits.workspace = true
audioadapter.workspace = true
serde = { workspace = true, optional = true }
bevy_reflect = { workspace = true, optional = true }
wasm-bindgen = { version = "0.2", default-features = false, optional = true }

[dev-dependencies]
audioadapter-buffers = { workspace = true, features = ["alloc"] }
1 change: 1 addition & 0 deletions crates/firewheel-graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod context;
pub mod error;
pub mod graph;
pub mod processor;
mod time;

#[cfg(feature = "unsafe_flush_denormals_to_zero")]
mod ftz;
Expand Down
24 changes: 15 additions & 9 deletions crates/firewheel-graph/src/processor/profiling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,10 @@ impl ProfilerTx {
}

pub fn begin_new_bookkeeping_part(&mut self) {
if self.is_profiling_bookkeeping {
self.bookkeeping_start_instant = Instant::now();
if self.is_profiling_bookkeeping
&& let Some(now) = crate::time::now()
{
self.bookkeeping_start_instant = now;
}
}

Expand All @@ -199,16 +201,18 @@ impl ProfilerTx {
pub fn begin_node_profiling(&mut self) {
self.node_schedule_index = 0;

if self.is_profiling_nodes {
self.node_profile_start_instant = Instant::now();
if self.is_profiling_nodes
&& let Some(now) = crate::time::now()
{
self.node_profile_start_instant = now;
}
}

#[cfg(feature = "node_profiling")]
pub fn node_completed(&mut self) {
if self.is_profiling_nodes {
let new_profile_instant = Instant::now();

if self.is_profiling_nodes
&& let Some(new_profile_instant) = crate::time::now()
{
let node_cpu_usage = new_profile_instant
.duration_since(self.node_profile_start_instant)
.as_secs_f64()
Expand All @@ -221,15 +225,17 @@ impl ProfilerTx {
}

pub fn process_loop_completed(&mut self) {
let Some(now) = crate::time::now() else {
return;
};

#[cfg(feature = "node_profiling")]
if self.is_profiling_nodes {
for (node, &sum) in self.nodes.iter_mut().zip(self.node_cpu_sums.iter()) {
node.cpu_usage = node.cpu_usage.max(sum);
}
}

let now = Instant::now();

let overall_cpu_usage = now.duration_since(self.proc_start_instant).as_secs_f64()
* self.total_cpu_seconds_recip;
self.overall_cpu_usage = self.overall_cpu_usage.max(overall_cpu_usage);
Expand Down
57 changes: 57 additions & 0 deletions crates/firewheel-graph/src/time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use bevy_platform::time::Instant;

/// Return [`Instant::now`] in contexts where it's available.
///
/// In a Wasm-with-JS context, [`Instant::now`] will panic in
/// an audio worklet. Rather than panicking, this function returns `None`.
pub fn now() -> Option<Instant> {
#[cfg(all(
feature = "wasm-bindgen",
target_family = "wasm",
target_feature = "atomics"
))]
return is_not_worklet().then(|| bevy_platform::time::Instant::now());

#[cfg(not(all(
feature = "wasm-bindgen",
target_arch = "wasm32",
target_feature = "atomics"
)))]
return Some(bevy_platform::time::Instant::now());
}

#[cfg(all(
feature = "wasm-bindgen",
target_family = "wasm",
target_feature = "atomics"
))]
#[wasm_bindgen::prelude::wasm_bindgen(inline_js = "
export function is_audio_worklet() {
return typeof sampleRate !== 'undefined';
}
")]
extern "C" {
fn is_audio_worklet() -> bool;
}

/// Determines if this execution context is an audio worklet.
#[cfg(all(
feature = "wasm-bindgen",
target_family = "wasm",
target_feature = "atomics"
))]
fn is_not_worklet() -> bool {
#[cfg(feature = "std")]
{
// A thread local allows us to limit calls into JS to once per
// execution context.
thread_local! {
static IS_NOT_WORKLET: bool = !is_audio_worklet();
}

return IS_NOT_WORKLET.with(|w| *w);
}

#[cfg(not(feature = "std"))]
return !is_audio_worklet();
}
Loading