#[cfg(not(target_arch = "wasm32"))]
use std::sync::{
Arc, Mutex, PoisonError,
atomic::{AtomicBool, Ordering},
mpsc::{self, Receiver, Sender},
};
#[cfg(not(target_arch = "wasm32"))]
const LANE_FINISH_WAIT: web_time::Duration = web_time::Duration::from_secs(5);
#[cfg(not(target_arch = "wasm32"))]
const LANES: usize = 2;
#[cfg(not(target_arch = "wasm32"))]
type Job = Box<dyn FnOnce() + Send + 'static>;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) trait CompilerSend: Send {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Send + ?Sized> CompilerSend for T {}
#[cfg(target_arch = "wasm32")]
pub(crate) trait CompilerSend {}
#[cfg(target_arch = "wasm32")]
impl<T: ?Sized> CompilerSend for T {}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) trait CompilerSync: Sync {}
#[cfg(not(target_arch = "wasm32"))]
impl<T: Sync + ?Sized> CompilerSync for T {}
#[cfg(target_arch = "wasm32")]
pub(crate) trait CompilerSync {}
#[cfg(target_arch = "wasm32")]
impl<T: ?Sized> CompilerSync for T {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum CompileLane {
Demanded,
WarmUp,
}
#[derive(Clone, Default)]
pub(crate) struct PipelineCompiler {
#[cfg(not(target_arch = "wasm32"))]
workers: Option<Arc<Workers>>,
}
#[cfg(not(target_arch = "wasm32"))]
struct Workers {
demanded: Option<Sender<Job>>,
warm_up: Option<Sender<Job>>,
stopped: Arc<AtomicBool>,
finished: Mutex<Receiver<()>>,
}
#[cfg(not(target_arch = "wasm32"))]
impl Drop for Workers {
fn drop(&mut self) {
self.stopped.store(true, Ordering::Release);
self.demanded = None;
self.warm_up = None;
let finished = self
.finished
.get_mut()
.unwrap_or_else(PoisonError::into_inner);
let deadline = web_time::Instant::now() + LANE_FINISH_WAIT;
for _ in 0..LANES {
let left = deadline.saturating_duration_since(web_time::Instant::now());
if finished.recv_timeout(left).is_err() {
log::warn!(
"[gpu-pipeline] a compile outlived its renderer by {LANE_FINISH_WAIT:?}"
);
break;
}
}
}
}
#[cfg(not(target_arch = "wasm32"))]
fn spawn_lane(
name: &str,
stopped: &Arc<AtomicBool>,
finished: &Sender<()>,
) -> std::io::Result<Sender<Job>> {
let (jobs, queued) = mpsc::channel::<Job>();
let stopped = Arc::clone(stopped);
let finished = finished.clone();
std::thread::Builder::new()
.name(name.into())
.spawn(move || {
crate::render::mark_thread_off_frame();
while let Ok(job) = queued.recv() {
if stopped.load(Ordering::Acquire) {
break;
}
job();
}
let _ = finished.send(());
})?;
Ok(jobs)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum PipelineCompilation {
Background,
Inline,
}
impl PipelineCompiler {
pub(crate) fn for_compilation(compilation: PipelineCompilation) -> Self {
match compilation {
PipelineCompilation::Background => Self::spawn(),
PipelineCompilation::Inline => Self::inactive(),
}
}
pub(crate) fn inactive() -> Self {
Self::default()
}
pub(crate) fn spawn() -> Self {
#[cfg(not(target_arch = "wasm32"))]
{
static BACKGROUND_PIPELINES: crate::debug_toggles::DebugToggle =
crate::debug_toggles::DebugToggle::new("CRANPOSE_BACKGROUND_PIPELINES");
if BACKGROUND_PIPELINES.equals("0") {
return Self::inactive();
}
let stopped = Arc::new(AtomicBool::new(false));
let (finished_tx, finished) = mpsc::channel();
let lanes =
spawn_lane("cranpose-pipelines", &stopped, &finished_tx).and_then(|demanded| {
spawn_lane("cranpose-warm-up", &stopped, &finished_tx)
.map(|warm_up| (demanded, warm_up))
});
match lanes {
Ok((demanded, warm_up)) => Self {
workers: Some(Arc::new(Workers {
demanded: Some(demanded),
warm_up: Some(warm_up),
stopped,
finished: Mutex::new(finished),
})),
},
Err(error) => {
log::error!("[gpu-pipeline] background compiler failed to spawn: {error}");
Self::inactive()
}
}
}
#[cfg(target_arch = "wasm32")]
{
Self::inactive()
}
}
pub(crate) fn is_active(&self) -> bool {
#[cfg(not(target_arch = "wasm32"))]
{
self.workers.is_some()
}
#[cfg(target_arch = "wasm32")]
{
false
}
}
pub(crate) fn enqueue(&self, lane: CompileLane, job: impl FnOnce() + CompilerSend + 'static) {
#[cfg(not(target_arch = "wasm32"))]
if let Some(workers) = self.workers.as_ref() {
let jobs = match lane {
CompileLane::Demanded => workers.demanded.as_ref(),
CompileLane::WarmUp => workers.warm_up.as_ref(),
};
if jobs.is_none_or(|jobs| jobs.send(Box::new(job)).is_err()) {
log::error!("[gpu-pipeline] background compiler stopped unexpectedly");
}
}
#[cfg(target_arch = "wasm32")]
drop((lane, job));
}
}
#[cfg(all(test, not(target_arch = "wasm32")))]
#[path = "tests/pipeline_compiler_tests.rs"]
mod tests;