use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};
use crossbeam_channel::{Receiver, unbounded};
use crate::backend::GpuBackend;
use crate::model::GpuSample;
#[derive(Debug)]
pub enum SamplerEvent {
Samples(Vec<GpuSample>),
Error(String),
}
pub struct SamplerHandle {
stop: Arc<AtomicBool>,
join: Option<JoinHandle<()>>,
}
impl SamplerHandle {
pub fn stop(mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(join) = self.join.take() {
let _ = join.join();
}
}
}
impl Drop for SamplerHandle {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
}
}
pub fn spawn_sampler(
mut backend: Box<dyn GpuBackend>,
interval: Duration,
) -> (Receiver<SamplerEvent>, SamplerHandle) {
let (tx, rx) = unbounded();
let stop = Arc::new(AtomicBool::new(false));
let thread_stop = Arc::clone(&stop);
let join = thread::Builder::new()
.name("gpu-histop-sampler".to_owned())
.spawn(move || {
while !thread_stop.load(Ordering::Relaxed) {
let started = Instant::now();
let event = match backend.sample() {
Ok(samples) => SamplerEvent::Samples(samples),
Err(error) => SamplerEvent::Error(error.to_string()),
};
if tx.send(event).is_err() {
break;
}
sleep_until_next_tick(interval, started, &thread_stop);
}
})
.expect("failed to spawn sampler thread");
(
rx,
SamplerHandle {
stop,
join: Some(join),
},
)
}
fn sleep_until_next_tick(interval: Duration, started: Instant, stop: &AtomicBool) {
let elapsed = started.elapsed();
let mut remaining = interval.saturating_sub(elapsed);
while !remaining.is_zero() && !stop.load(Ordering::Relaxed) {
let nap = remaining.min(Duration::from_millis(20));
thread::sleep(nap);
remaining = remaining.saturating_sub(nap);
}
}