makepad/libs/drumkit_phys/tests/rt_safety.rs
Admin f2b02531b7 drumkit: a sample-based kit for the score preview; the physical model parked as drumkit_phys
The kit that plays drum scores is now a sample player over the Salamander Drumkit (velocity layers with round-robins, derived toms, a synthesised clap), loaded off the audio thread and swapped in atomically. The physically modelled kit stays as makepad-drumkit-phys for a later pass.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 02:52:12 +02:00

66 lines
2.3 KiB
Rust

// Proof that DrumKit::process and DrumKit::trigger never allocate: a
// counting global allocator wraps the system allocator for this test binary;
// after construction and warm-up, four seconds of dense playing (every
// voice, voice stealing, every block size) must leave the counter untouched.
//
// (Locks/IO: the render path calls no std sync or IO APIs at all. Panics:
// every slice access in the render path is bounds-checked against
// preallocated fixed sizes; sound.rs exercises the polyphony and sample-rate
// edges.)
use makepad_drumkit_phys::{DrumKit, DrumVoice};
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
static ALLOCS: AtomicUsize = AtomicUsize::new(0);
struct CountingAlloc;
unsafe impl GlobalAlloc for CountingAlloc {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOCS.fetch_add(1, Ordering::Relaxed);
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
ALLOCS.fetch_add(1, Ordering::Relaxed);
unsafe { System.realloc(ptr, layout, new_size) }
}
}
#[global_allocator]
static A: CountingAlloc = CountingAlloc;
#[test]
fn trigger_and_process_never_allocate() {
let mut kit = DrumKit::new(48000.0);
let mut out = vec![[0.0f32; 2]; 512];
// warm-up: every voice once
for v in DrumVoice::ALL {
kit.trigger(v, 0.9);
kit.process(&mut out);
}
let before = ALLOCS.load(Ordering::Relaxed);
let mut i = 0usize;
for block in 0..375u32 {
// 375 x 512 = 4 s; a new hit every second block, stealing after 16
if block % 2 == 0 {
kit.trigger(DrumVoice::ALL[i % DrumVoice::ALL.len()], 0.3 + 0.7 * ((i % 7) as f32 / 6.0));
i += 1;
}
let n = match block % 4 {
0 => 512,
1 => 64,
2 => 1,
_ => 300,
};
out[..n].iter_mut().for_each(|f| *f = [0.0; 2]);
kit.process(&mut out[..n]);
}
kit.all_off();
kit.process(&mut out);
let after = ALLOCS.load(Ordering::Relaxed);
assert_eq!(before, after, "the render path allocated {} times", after - before);
}