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
64 changes: 60 additions & 4 deletions crates/byard-core/benches/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use std::hint::black_box;
use std::time::Instant;

use byard_core::evaluator::EvaluatorTick;
use byard_core::evaluator::{Signal, ViewArena};
use byard_core::frame::TargetId;

Expand Down Expand Up @@ -76,7 +77,14 @@ fn bench_with_setup<S, F, T>(
fn main() {
println!("\n=== Evaluator benchmarks ===\n");

// ── ViewArena allocation ─────────────────────────────────────────────
bench_arena();
bench_signal();
bench_tick();

println!();
}

fn bench_arena() {
bench(
"arena: alloc u64 (trivially-droppable)",
1_000_000,
Expand Down Expand Up @@ -116,7 +124,9 @@ fn main() {
drop(black_box(arena));
},
);
}

fn bench_signal() {
let arena = ViewArena::new();
let signal = Signal::new_in(&arena, 0_u64);

Expand Down Expand Up @@ -148,9 +158,9 @@ fn main() {
});

bench(
"signal: 100 signals × 10 subs × 100 writes",
"signal: 100 signals x 10 subs x 100 writes",
1_000,
100 * 10 + 100 * 100, // subscribes + writes
100 * 10 + 100 * 100,
|| {
let arena = ViewArena::new();
let mut signals = Vec::with_capacity(100);
Expand All @@ -171,6 +181,52 @@ fn main() {
}
},
);
}

println!();
fn bench_tick() {
bench_with_setup(
"tick: collect_dirty (10 signals, no writes)",
100_000,
1,
|| {
let arena: &'static ViewArena = Box::leak(Box::new(ViewArena::new()));
let mut tick = EvaluatorTick::new();
for i in 0..10_u32 {
let signal = Signal::new_in(arena, i);
signal.subscribe(TargetId::new(i, 0, 0));
tick.register(signal);
}
tick.collect_dirty();
tick
},
|mut tick| {
black_box(tick.collect_dirty());
},
);

bench_with_setup(
"tick: collect_dirty (10 signals, all dirty)",
100_000,
1,
|| {
let arena: &'static ViewArena = Box::leak(Box::new(ViewArena::new()));
let mut tick = EvaluatorTick::new();
let signals: Vec<_> = (0..10_u32)
.map(|i| {
let s = Signal::new_in(arena, i);
s.subscribe(TargetId::new(i, 0, 0));
tick.register(s);
s
})
.collect();
tick.collect_dirty();
for s in &signals {
s.write(|v| *v = v.wrapping_add(1));
}
tick
},
|mut tick| {
black_box(tick.collect_dirty());
},
);
}
5 changes: 3 additions & 2 deletions crates/byard-core/src/evaluator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@

pub mod arena;
pub mod signal;

pub use signal::Signal;
pub mod tick;

pub use arena::ViewArena;
pub use signal::Signal;
pub use tick::EvaluatorTick;
35 changes: 35 additions & 0 deletions crates/byard-core/src/evaluator/signal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,29 @@ pub(crate) struct SignalSlot<T> {
borrow_state: Cell<isize>,
}

impl<T> SignalSlot<T> {
/// Returns a reference to the atomic version counter.
///
/// Used by [`EvaluatorTick`](crate::evaluator::EvaluatorTick) to poll
/// versions without going through `Signal`'s borrow-guarded API.
/// Safe because `AtomicU64` is itself thread-safe.
pub(crate) fn dirty_version_ref(&self) -> &AtomicU64 {
&self.dirty_version
}

/// Returns a shared reference to the subscriber list.
///
/// # Safety
///
/// Caller must guarantee that no exclusive borrow of `dirty_targets`
/// is active. This is upheld by the Logic-thread-only invariant when
/// called from `EvaluatorTick::collect_dirty`.
pub(crate) unsafe fn subscribers_ref(&self) -> &[TargetId] {
// SAFETY: caller upholds the contract above.
unsafe { (*self.dirty_targets.get()).as_slice() }
}
}

/// Marker value for `borrow_state` indicating an exclusive borrow.
const BORROW_MUT_SENTINEL: isize = -1;

Expand Down Expand Up @@ -279,6 +302,18 @@ impl<'a, T: 'static> Signal<'a, T> {
.dirty_version
.load(Ordering::Acquire)
}

/// Returns the raw address of this signal's backing slot.
///
/// Used internally by [`EvaluatorTick`](crate::evaluator::EvaluatorTick) to
/// detect duplicate registrations in debug builds. Two `Signal` handles
/// that compare equal by this pointer refer to the same underlying slot.
///
/// The returned pointer is opaque — it must not be dereferenced.
#[must_use]
pub(crate) fn slot_ptr(self) -> *const () {
self.slot.as_ptr().cast()
}
}

#[cfg(test)]
Expand Down
Loading
Loading