use crucible_core::{Player, Queues, Recorder, Ring, Transport}; use std::sync::Arc; use std::{ alloc::{GlobalAlloc, Layout, System}, cell::Cell, }; struct CheckedAllocator; thread_local! { static CHECK:Cell=const {Cell::new(false)}; static OPERATIONS:Cell=const {Cell::new(0)}; } fn record() { if CHECK.try_with(Cell::get).unwrap_or(false) { let _ = OPERATIONS.try_with(|n| n.set(n.get() + 1)); } } unsafe impl GlobalAlloc for CheckedAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { record(); unsafe { System.alloc(layout) } } unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) { record(); unsafe { System.dealloc(pointer, layout) } } unsafe fn realloc(&self, pointer: *mut u8, layout: Layout, size: usize) -> *mut u8 { record(); unsafe { System.realloc(pointer, layout, size) } } } #[global_allocator] static ALLOCATOR: CheckedAllocator = CheckedAllocator; #[test] fn recording_transport_and_audition_do_not_allocate_or_free() { let ring = Ring::new(48000., 4.); let queues = Queues::default(); let mut recorder = Recorder::new(); let mut player = Player::default(); let clips: Vec> = (0..4).map(|i| Arc::from(vec![0.1; 2000 + i])).collect(); CHECK.with(|v| v.set(true)); for block in 0..2000u32 { if block % 300 == 0 { // Sending only moves the Arc; freeing happens on drain, outside the check. queues.play(clips[(block / 300) as usize % 4].clone()); } recorder.block( &ring, Some(Transport { playing: block % 700 < 600, tempo: if block < 1000 { 140. } else { 150. }, beats: block as f64 * 0.1, numerator: 4, denominator: 4, }), ); player.block(&queues); for i in 0..256 { let a = player.next(&queues); recorder.push(&ring, [i as f32 * 1e-3 + a[0], a[1]]); } recorder.publish(&ring); } CHECK.with(|v| v.set(false)); assert_eq!(OPERATIONS.with(Cell::get), 0); queues.drain(); }