use mandible_extract::{FillResult, ResolvedTool, Runner};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::mpsc::{Receiver, Sender};
use std::sync::{mpsc, Arc};
const MAX_WARMED_NODES: usize = 4096;
pub struct WarmedNode {
pub path: Vec<String>,
pub result: FillResult,
}
pub struct Warmer {
pool: rayon::ThreadPool,
cancelled: Arc<AtomicBool>,
submitted: AtomicUsize,
tx: Sender<WarmedNode>,
rx: Receiver<WarmedNode>,
}
impl Warmer {
pub fn new() -> Warmer {
let threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
.saturating_mul(4)
.clamp(4, 32);
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(threads)
.thread_name(|i| format!("mandible-warm-{i}"))
.build()
.expect("failed to build the background warming thread pool");
let (tx, rx) = mpsc::channel();
Warmer {
pool,
cancelled: Arc::new(AtomicBool::new(false)),
submitted: AtomicUsize::new(0),
tx,
rx,
}
}
pub fn submit(
&self,
runner: Arc<Runner>,
tool: ResolvedTool,
path: Vec<String>,
existing: mandible_core::CommandNode,
) -> bool {
if self.cancelled.load(Ordering::Relaxed) {
return false;
}
if self.submitted.fetch_add(1, Ordering::Relaxed) >= MAX_WARMED_NODES {
return false;
}
let cancelled = Arc::clone(&self.cancelled);
let tx = self.tx.clone();
self.pool.spawn(move || {
if cancelled.load(Ordering::Relaxed) {
return;
}
let result = runner.fill_node(&tool, &path, existing);
if cancelled.load(Ordering::Relaxed) {
return;
}
let _ = tx.send(WarmedNode { path, result });
});
true
}
pub fn warm_children(
&self,
runner: &Arc<Runner>,
tool: &ResolvedTool,
node: &mandible_core::CommandNode,
path: &[String],
) -> Vec<Vec<String>> {
let mut queued = Vec::new();
for child in &node.subcommands {
if child.children_filled {
continue;
}
let mut child_path = path.to_vec();
child_path.push(child.name.clone());
if !self.submit(
Arc::clone(runner),
tool.clone(),
child_path.clone(),
child.clone(),
) {
break;
}
queued.push(child_path);
}
queued
}
pub fn drain(&self) -> Vec<WarmedNode> {
self.rx.try_iter().collect()
}
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::Relaxed);
}
}
impl Default for Warmer {
fn default() -> Self {
Self::new()
}
}