use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use crate::core::error::{Error, ErrorCode, Result};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Strictness {
#[default]
Fallback,
Strict,
}
pub trait Declared: Copy + Eq + 'static {
type Caps;
fn name(self) -> &'static str;
fn lacks(self, caps: &Self::Caps) -> Option<String>;
fn fallback(self) -> Option<Self>;
const FALLBACK_CODE: ErrorCode;
const REFUSED_CODE: ErrorCode;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Resolution<K> {
pub requested: K,
pub running: K,
pub warning: Option<Error>,
}
impl<K: Declared> Resolution<K> {
#[must_use]
pub fn label(&self) -> String {
if self.running == self.requested {
String::from(self.running.name())
} else {
format!(
"{} (fallback from {})",
self.running.name(),
self.requested.name()
)
}
}
}
pub fn resolve<K: Declared>(
requested: K,
strictness: Strictness,
caps: &K::Caps,
) -> Result<Resolution<K>> {
let mut running = requested;
let mut why: Vec<String> = Vec::new();
while let Some(reason) = running.lacks(caps) {
if strictness == Strictness::Strict {
return Err(Error::new(
K::REFUSED_CODE,
alloc::vec![String::from(requested.name()), reason],
));
}
why.push(format!("{}: {reason}", running.name()));
match running.fallback() {
Some(next) => running = next,
None => break,
}
}
let warning = (running != requested).then(|| {
Error::new(
K::FALLBACK_CODE,
alloc::vec![
String::from(requested.name()),
why.join("; "),
String::from(running.name()),
],
)
});
Ok(Resolution {
requested,
running,
warning,
})
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ExecutorKind {
Naive,
GpuReactor,
IoUring,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ExecutorCaps {
pub word_wait: core::result::Result<(), String>,
pub io_uring: core::result::Result<(), String>,
}
impl ExecutorKind {
pub const ALL: [ExecutorKind; 3] = [Self::Naive, Self::GpuReactor, Self::IoUring];
pub fn parse(text: &str) -> Result<Self> {
Self::ALL
.into_iter()
.find(|k| k.name() == text)
.ok_or_else(|| {
let names: Vec<&str> = Self::ALL.iter().map(|k| k.name()).collect();
crate::error!(InvalidExecutor, text, names.join(", "))
})
}
}
impl Declared for ExecutorKind {
type Caps = ExecutorCaps;
fn name(self) -> &'static str {
match self {
Self::Naive => "naive",
Self::GpuReactor => "gpu-reactor",
Self::IoUring => "io-uring",
}
}
fn lacks(self, caps: &ExecutorCaps) -> Option<String> {
match self {
Self::Naive => None,
Self::GpuReactor => caps.word_wait.as_ref().err().cloned(),
Self::IoUring => caps
.io_uring
.as_ref()
.err()
.or(caps.word_wait.as_ref().err())
.cloned(),
}
}
fn fallback(self) -> Option<Self> {
match self {
Self::IoUring => Some(Self::GpuReactor),
Self::GpuReactor => Some(Self::Naive),
Self::Naive => None,
}
}
const FALLBACK_CODE: ErrorCode = ErrorCode::ExecutorFallback;
const REFUSED_CODE: ErrorCode = ErrorCode::ExecutorRefused;
}
#[cfg(test)]
#[path = "declared_tests.rs"]
mod tests;