Skip to content

Commit

Permalink
Remove 64-bit atomics.
Browse files Browse the repository at this point in the history
  • Loading branch information
SamiPerttu committed Jul 3, 2024
1 parent e16287b commit 8b7fd21
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 30 deletions.
8 changes: 4 additions & 4 deletions src/net.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use super::combinator::*;
use super::math::*;
use super::realnet::*;
use super::setting::*;
use super::shared::IdGenerator;
use super::signal::*;
use super::*;
use core::sync::atomic::{AtomicU64, Ordering};
use hashbrown::HashMap;
use thingbuf::mpsc::{channel, Receiver, Sender};
extern crate alloc;
Expand All @@ -24,14 +24,14 @@ pub type PortIndex = usize;
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
pub struct NodeId(u64);

/// This atomic supplies globally unique IDs.
static GLOBAL_NODE_ID: AtomicU64 = AtomicU64::new(0);
/// This atomic supplies globally unique node IDs.
static GLOBAL_NODE_ID: IdGenerator = IdGenerator::new();

impl NodeId {
/// Create a new, globally unique node ID.
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
NodeId(GLOBAL_NODE_ID.fetch_add(1, Ordering::Relaxed))
NodeId(GLOBAL_NODE_ID.get_id())
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/sequencer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use super::audiounit::*;
use super::buffer::*;
use super::math::*;
use super::realseq::*;
use super::shared::IdGenerator;
use super::signal::*;
use super::*;
use core::cmp::{Eq, Ord, Ordering};
Expand All @@ -12,7 +13,6 @@ use alloc::boxed::Box;
use alloc::collections::BinaryHeap;
use alloc::vec;
use alloc::vec::Vec;
use core::sync::atomic::AtomicU64;
use hashbrown::HashMap;
use thingbuf::mpsc::{channel, Receiver, Sender};

Expand All @@ -21,13 +21,13 @@ use thingbuf::mpsc::{channel, Receiver, Sender};
pub struct EventId(u64);

/// This atomic supplies globally unique IDs.
static GLOBAL_EVENT_ID: AtomicU64 = AtomicU64::new(0);
static GLOBAL_EVENT_ID: IdGenerator = IdGenerator::new();

impl EventId {
/// Create a new, globally unique event ID.
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
EventId(GLOBAL_EVENT_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed))
EventId(GLOBAL_EVENT_ID.get_id())
}
}

Expand Down
90 changes: 67 additions & 23 deletions src/shared.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! Shared atomic controls.
//! Shared atomic controls and other atomic stuff.
use super::audionode::*;
use super::buffer::*;
use super::combinator::*;
use super::signal::*;
use super::*;
use core::sync::atomic::{AtomicU32, AtomicU64};
use core::sync::atomic::{AtomicU32, Ordering};

use numeric_array::typenum::*;
extern crate alloc;
use alloc::sync::Arc;
Expand All @@ -29,35 +30,16 @@ impl Atomic for f32 {

#[inline]
fn store(stored: &Self::Storage, t: Self) {
stored.store(t.to_bits(), core::sync::atomic::Ordering::Relaxed);
stored.store(t.to_bits(), Ordering::Relaxed);
}

#[inline]
fn get_stored(stored: &Self::Storage) -> Self {
let u = stored.load(core::sync::atomic::Ordering::Relaxed);
let u = stored.load(Ordering::Relaxed);
f32::from_bits(u)
}
}

impl Atomic for f64 {
type Storage = AtomicU64;

fn storage(t: Self) -> Self::Storage {
AtomicU64::from(t.to_bits())
}

#[inline]
fn store(stored: &Self::Storage, t: Self) {
stored.store(t.to_bits(), core::sync::atomic::Ordering::Relaxed);
}

#[inline]
fn get_stored(stored: &Self::Storage) -> Self {
let u = stored.load(core::sync::atomic::Ordering::Relaxed);
f64::from_bits(u)
}
}

/// A shared float variable that can be accessed from multiple threads.
#[derive(Default, Clone)]
pub struct Shared {
Expand Down Expand Up @@ -378,3 +360,65 @@ impl<T: Float> AudioNode for AtomicSynth<T> {
super::signal::Routing::Generator(0.0).route(input, self.outputs())
}
}

/// This thing generates unique 64-bit IDs using 32-bit atomics.
#[derive(Default)]
pub struct IdGenerator {
low: AtomicU32,
high: AtomicU32,
}

/// When the low word of an `IdGenerator` enters the danger zone,
/// we attempt to rewind it and increase the high word.
const DANGER: u32 = 0xff000000;

impl IdGenerator {
pub const fn new() -> Self {
Self {
low: AtomicU32::new(0),
high: AtomicU32::new(0),
}
}

/// Generate a unique 64-bit ID.
pub fn get_id(&self) -> u64 {
// TODO. I have no idea whether the atomic orderings are correct in the following.
let low = self
.low
.fetch_update(Ordering::Release, Ordering::Acquire, |x| {
Some(x.wrapping_add(1))
})
.unwrap();
if low < DANGER {
let high = self.high.load(Ordering::Acquire);
let low_again = self.low.load(Ordering::Acquire);
if low_again < low || low_again >= DANGER {
// The low word has spilled over, so we cannot trust the high word value. Try again.
self.get_id()
} else {
low as u64 | ((high as u64) << 32)
}
} else {
// We are in the danger zone. Our goal is to wind back the low word to the beginning while manipulating
// the high word to stay unique.
let high = self
.high
.fetch_update(Ordering::Release, Ordering::Acquire, |x| {
Some(x.wrapping_add(1))
})
.unwrap();
let _ = self
.low
.fetch_update(Ordering::Release, Ordering::Acquire, |x| {
if x < DANGER {
// Another thread has already rewound the low word.
None
} else {
// Try to rewind.
Some(0)
}
});
low as u64 | ((high as u64) << 32)
}
}
}
1 change: 1 addition & 0 deletions tests/test_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ fn test_basic() {
let id0 = add_net.push(Box::new(add((2.0, 3.0))));
let idd = add_net.push(Box::new(zero()));
let id1 = add_net.push(Box::new(multipass::<U2>()));
assert!(id0 != idd && id0 != id1 && idd != id1);
add_net.remove(idd);
add_net.pipe_input(id0);
add_net.pipe(id0, id1);
Expand Down

0 comments on commit 8b7fd21

Please sign in to comment.