Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:

- name: Check no_std build
run: |
cargo build --locked --no-default-features --features "libm, scheduled_events, musical_transport, all_nodes_no_std, pool, node_profiling, glam-29, glam-30, glam-31"
cargo build --locked --no-default-features --features "libm, scheduled_events, musical_transport, all_nodes_no_std, node_profiling, glam-29, glam-30, glam-31"

# Check formatting.
format:
Expand Down
16 changes: 3 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ std = [
"firewheel-core/std",
"firewheel-graph/std",
"firewheel-nodes/std",
"firewheel-pool?/std",
]
# Enable this if "std" is disabled.
libm = ["firewheel-core/libm", "firewheel-nodes/libm"]
Expand All @@ -53,7 +52,6 @@ scheduled_events = [
"firewheel-core/scheduled_events",
"firewheel-graph/scheduled_events",
"firewheel-nodes/scheduled_events",
"firewheel-pool?/scheduled_events",
]
# Enables the musical transport feature
musical_transport = [
Expand All @@ -76,9 +74,6 @@ rtaudio = ["std", "firewheel-rtaudio"]
symphonium = ["dep:firewheel-symphonium"]
# Enables performance profiling for each individual node.
node_profiling = ["firewheel-graph/node_profiling"]
# Enables the `AudioNodePool` helper type for constructing a pool of
# audio node chains that can dynamically be assigned work.
pool = ["dep:firewheel-pool"]
# Enables all built-in factory nodes
all_nodes = ["firewheel-nodes/all_nodes"]
# Enables all built-in factory nodes which are no_std compatible
Expand All @@ -88,11 +83,10 @@ beep_test_node = ["firewheel-nodes/beep_test"]
# Enables the peak meter node
peak_meter_node = ["firewheel-nodes/peak_meter"]
# Enables the sampler node
sampler_node = ["firewheel-nodes/sampler", "firewheel-pool?/sampler"]
sampler_node = ["firewheel-nodes/sampler"]
# Enables the basic 3D spatial positioning node
spatial_basic_node = [
"firewheel-nodes/spatial_basic",
"firewheel-pool?/spatial_basic",
]
# Enables the triple buffer node for sending raw audio data from the
# audio graph to another thread. Useful for cases where you only care
Expand Down Expand Up @@ -171,15 +165,13 @@ members = [
"crates/firewheel-graph",
"crates/firewheel-nodes",
"crates/firewheel-macros",
"crates/firewheel-pool",
"crates/firewheel-rtaudio",
"crates/firewheel-symphonium",
"examples/beep_test",
"examples/cpal_input",
"examples/custom_nodes",
"examples/play_sample",
"examples/rtaudio_beep_test",
"examples/sampler_pool",
"examples/sampler_test",
"examples/spatial_basic",
"examples/stream_nodes",
Expand All @@ -197,7 +189,6 @@ ringbuf = { version = "0.4", default-features = false, features = [
"alloc",
] }
triple_buffer = "9"
triple_buf_64 = { version = "0.1.1", features = ["portable-atomic"] }
thiserror = { version = "2", default-features = false }
smallvec = "1"
arrayvec = { version = "0.7", default-features = false }
Expand Down Expand Up @@ -227,11 +218,10 @@ eframe = { version = "0.33.3", default-features = false, features = [
] }

[dependencies]
firewheel-core = { path = "crates/firewheel-core", version = "0.10.0", default-features = false }
firewheel-graph = { path = "crates/firewheel-graph", version = "0.10.0", default-features = false }
firewheel-core = { path = "crates/firewheel-core", version = "0.10.1", default-features = false }
firewheel-graph = { path = "crates/firewheel-graph", version = "0.10.2", default-features = false }
firewheel-cpal = { path = "crates/firewheel-cpal", version = "0.10.0", default-features = false, optional = true }
firewheel-nodes = { path = "crates/firewheel-nodes", version = "0.10.0", default-features = false }
firewheel-pool = { path = "crates/firewheel-pool", version = "0.10.0", default-features = false, optional = true }
firewheel-symphonium = { path = "crates/firewheel-symphonium", version = "0.10.0", default-features = false, optional = true }
firewheel-rtaudio = { path = "crates/firewheel-rtaudio", version = "0.10.0", default-features = false, optional = true }
thunderdome = { workspace = true, optional = true }
Expand Down
24 changes: 12 additions & 12 deletions crates/firewheel-core/src/diff/leaf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use super::{Diff, EventQueue, Patch, PatchError, PathBuilder};
use crate::{
clock::{DurationSamples, DurationSeconds, InstantSamples, InstantSeconds},
collector::ArcGc,
diff::{Notify, RealtimeClone},
diff::{Notify, RealtimeClone, notify::NotifyID},
dsp::volume::Volume,
event::{NodeEventType, ParamData},
vector::{Vec2, Vec3},
Expand Down Expand Up @@ -221,7 +221,7 @@ impl<T: Send + Sync + RealtimeClone + PartialEq + 'static> Patch for Option<T> {
impl Diff for Notify<()> {
fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
if self != baseline {
event_queue.push_param(ParamData::U64(self.id()), path);
event_queue.push_param(ParamData::U64(self.id().0), path);
}
}
}
Expand All @@ -231,7 +231,7 @@ impl Patch for Notify<()> {

fn patch(data: &ParamData, _: &[u32]) -> Result<Self::Patch, PatchError> {
match data {
ParamData::U64(counter) => Ok(Notify::from_raw((), *counter)),
ParamData::U64(id) => Ok(Notify::from_raw((), NotifyID(*id))),
_ => Err(PatchError::InvalidData),
}
}
Expand All @@ -245,8 +245,8 @@ impl Diff for Notify<bool> {
fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
if self != baseline {
let mut bytes: [u8; 20] = [0; 20];
bytes[0..8].copy_from_slice(&self.id().to_ne_bytes());
bytes[8] = (**self) as u8;
bytes[0..size_of::<u64>()].copy_from_slice(&self.id().0.to_ne_bytes());
bytes[size_of::<u64>()] = if **self { 1 } else { 0 };

event_queue.push_param(ParamData::CustomBytes(bytes), path);
}
Expand All @@ -259,12 +259,12 @@ impl Patch for Notify<bool> {
fn patch(data: &ParamData, _path: &[u32]) -> Result<Self::Patch, PatchError> {
match data {
ParamData::CustomBytes(bytes) => {
let (counter_bytes, rest_bytes) = bytes.split_at(size_of::<u64>());
let counter = u64::from_ne_bytes(counter_bytes.try_into().unwrap());
let (id_bytes, rest_bytes) = bytes.split_at(size_of::<u64>());
let id = u64::from_ne_bytes(id_bytes.try_into().unwrap());

let value = rest_bytes[0] != 0;

Ok(Notify::from_raw(value, counter))
Ok(Notify::from_raw(value, NotifyID(id)))
}
_ => Err(PatchError::InvalidData),
}
Expand All @@ -281,7 +281,7 @@ macro_rules! trivial_notify {
fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
if self != baseline {
let mut bytes: [u8; 20] = [0; 20];
bytes[0..8].copy_from_slice(&self.id().to_ne_bytes());
bytes[0..8].copy_from_slice(&self.id().0.to_ne_bytes());
let value_bytes = self.to_ne_bytes();
bytes[8..8 + value_bytes.len()].copy_from_slice(&value_bytes);

Expand All @@ -296,13 +296,13 @@ macro_rules! trivial_notify {
fn patch(data: &ParamData, _path: &[u32]) -> Result<Self::Patch, PatchError> {
match data {
ParamData::CustomBytes(bytes) => {
let (counter_bytes, rest_bytes) = bytes.split_at(size_of::<u64>());
let counter = u64::from_ne_bytes(counter_bytes.try_into().unwrap());
let (id_bytes, rest_bytes) = bytes.split_at(size_of::<u64>());
let id = u64::from_ne_bytes(id_bytes.try_into().unwrap());

let (value_bytes, _) = rest_bytes.split_at(size_of::<$ty>());
let value = <$ty>::from_ne_bytes(value_bytes.try_into().unwrap());

Ok(Notify::from_raw(value, counter))
Ok(Notify::from_raw(value, NotifyID(id)))
}
_ => Err(PatchError::InvalidData),
}
Expand Down
2 changes: 1 addition & 1 deletion crates/firewheel-core/src/diff/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ mod memo;
mod notify;

pub use memo::Memo;
pub use notify::Notify;
pub use notify::{Notify, NotifyID};

/// Derive macros for diffing and patching.
pub use firewheel_macros::{Diff, Patch, RealtimeClone};
Expand Down
74 changes: 46 additions & 28 deletions crates/firewheel-core/src/diff/notify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@ use crate::{
// Increment a realtime-safe atomic counter.
//
// This is gauranteed to never return zero.
fn increment_counter() -> u64 {
fn increment_counter() -> NotifyID {
portable_atomic::cfg_has_atomic_64! {
use portable_atomic::AtomicU64;

static NOTIFY_COUNTER: AtomicU64 = AtomicU64::new(1);

portable_atomic::cfg_has_atomic_cas! {
NOTIFY_COUNTER.fetch_add(1, Ordering::Relaxed)
NotifyID(NOTIFY_COUNTER.fetch_add(1, Ordering::Relaxed))
}

portable_atomic::cfg_no_atomic_cas! {
let val = NOTIFY_COUNTER.load(Ordering::Relaxed) + 1;
NOTIFY_COUNTER.store(val, Ordering::Relaxed);
val
NotifyID(val)
}
}

Expand All @@ -44,19 +44,37 @@ fn increment_counter() -> u64 {
NOTIFY_COUNTER_0.store((val & (u32::MAX as u64)) as u32, Ordering::Relaxed);
NOTIFY_COUNTER_1.store((val >> 32) as u32, Ordering::Relaxed);

val
NotifyID(val)
}

portable_atomic::cfg_no_atomic_32! {
use portable_atomic::AtomicU64;

// Just accept the locking behavior for these esoteric platforms.
static NOTIFY_COUNTER: AtomicU64 = AtomicU64::new(1);
NOTIFY_COUNTER.fetch_add(1, Ordering::Relaxed)
NotifyID(NOTIFY_COUNTER.fetch_add(1, Ordering::Relaxed))
}
}
}

/// An identifier representing the "generation" of a [`Notify`] parameter.
///
/// Whenever a `Notify` parameter is mutated, it will be assigned a new [`NotifyID`].
/// For all practical purposes, the ID can be considered unique among all [`Notify`]
/// instances.
///
/// Valid (non-dangling) [`NotifyID`]s are guaranteed to never be 0, so it can be
/// used as a sentinel value.
#[repr(transparent)]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(opaque))]
pub struct NotifyID(pub u64);

impl NotifyID {
pub const DANGLING: Self = Self(0);
}

/// A lightweight wrapper that guarantees an event
/// will be generated every time the inner value is accessed mutably,
/// even if the value doesn't change.
Expand All @@ -70,7 +88,7 @@ fn increment_counter() -> u64 {
#[derive(Debug, Clone)]
pub struct Notify<T> {
value: T,
counter: u64,
id: NotifyID,
}

impl<T> Notify<T> {
Expand All @@ -92,25 +110,25 @@ impl<T> Notify<T> {
pub fn new(value: T) -> Self {
Self {
value,
counter: increment_counter(),
id: increment_counter(),
}
}

pub(crate) fn from_raw(value: T, counter: u64) -> Self {
Self { value, counter }
pub(crate) fn from_raw(value: T, id: NotifyID) -> Self {
Self { value, id }
}

/// Get this instance's unique ID.
/// An identifier representing the "generation" of this [`Notify`] parameter.
///
/// After each mutable dereference, this ID will be replaced
/// with a new, unique value. For all practical purposes,
/// the ID can be considered unique among all [`Notify`] instances.
/// Whenever this parameter is mutated, it will be assigned a new [`NotifyID`].
/// For all practical purposes, the ID can be considered unique among all [`Notify`]
/// instances.
///
/// [`Notify`] IDs are guaranteed to never be 0, so it can be
/// Valid (non-dangling) [`NotifyID`]s are guaranteed to never be 0, so it can be
/// used as a sentinel value.
#[inline(always)]
pub fn id(&self) -> u64 {
self.counter
pub fn id(&self) -> NotifyID {
self.id
}

/// Get mutable access to the inner value without updating the ID.
Expand All @@ -120,7 +138,7 @@ impl<T> Notify<T> {

/// Manually update the internal ID without modifying the internals.
pub fn notify(&mut self) {
self.counter = increment_counter();
self.id = increment_counter();
}
}

Expand All @@ -132,7 +150,7 @@ impl<T> AsRef<T> for Notify<T> {

impl<T> AsMut<T> for Notify<T> {
fn as_mut(&mut self) -> &mut T {
self.counter += 1;
self.id = increment_counter();

&mut self.value
}
Expand All @@ -154,22 +172,30 @@ impl<T> core::ops::Deref for Notify<T> {

impl<T> core::ops::DerefMut for Notify<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.counter += 1;
self.id = increment_counter();

&mut self.value
}
}

impl<T: Copy> Copy for Notify<T> {}

impl<T> PartialEq for Notify<T> {
fn eq(&self, other: &Self) -> bool {
// under normal usage, it is not possible that the inner value
// can change without incrementing the counter
self.id == other.id
}
}

impl<T: RealtimeClone + Send + Sync + 'static> Diff for Notify<T> {
fn diff<E: super::EventQueue>(
&self,
baseline: &Self,
path: super::PathBuilder,
event_queue: &mut E,
) {
if self.counter != baseline.counter {
if self.id != baseline.id {
event_queue.push_param(ParamData::any(self.clone()), path);
}
}
Expand All @@ -189,14 +215,6 @@ impl<T: RealtimeClone + Send + Sync + 'static> Patch for Notify<T> {
}
}

impl<T> PartialEq for Notify<T> {
fn eq(&self, other: &Self) -> bool {
// under normal usage, it is not possible that the inner value
// can change without incrementing the counter
self.counter == other.counter
}
}

#[cfg(test)]
mod test {
use crate::diff::PathBuilder;
Expand Down
11 changes: 5 additions & 6 deletions crates/firewheel-core/src/log.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use bevy_platform::sync::Arc;
use core::sync::atomic::{AtomicBool, Ordering};
use ringbuf::traits::{Consumer, Observer, Producer, Split};

#[cfg(not(feature = "std"))]
use bevy_platform::prelude::String;

use crate::collector::ArcGc;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand Down Expand Up @@ -58,7 +57,7 @@ pub fn realtime_logger(config: RealtimeLoggerConfig) -> (RealtimeLogger, Realtim
error_prod_1.try_push(slot).unwrap();
}

let shared_state = ArcGc::new(SharedState {
let shared_state = Arc::new(SharedState {
message_too_long_occurred: AtomicBool::new(false),
not_enough_slots_occurred: AtomicBool::new(false),
});
Expand All @@ -71,7 +70,7 @@ pub fn realtime_logger(config: RealtimeLoggerConfig) -> (RealtimeLogger, Realtim
debug_cons: debug_cons_1,
error_prod: error_prod_2,
error_cons: error_cons_1,
shared_state: ArcGc::clone(&shared_state),
shared_state: Arc::clone(&shared_state),
max_msg_length: config.max_message_length,
},
RealtimeLoggerMainThread {
Expand Down Expand Up @@ -101,7 +100,7 @@ pub struct RealtimeLogger {
error_prod: ringbuf::HeapProd<String>,
error_cons: ringbuf::HeapCons<String>,

shared_state: ArcGc<SharedState>,
shared_state: Arc<SharedState>,

max_msg_length: usize,
}
Expand Down Expand Up @@ -246,7 +245,7 @@ pub struct RealtimeLoggerMainThread {
error_prod: ringbuf::HeapProd<String>,
error_cons: ringbuf::HeapCons<String>,

shared_state: ArcGc<SharedState>,
shared_state: Arc<SharedState>,
}

impl RealtimeLoggerMainThread {
Expand Down
Loading
Loading