#[cfg(feature = "unstable")]
mod described;
use core::ffi::c_void;
use core::marker::PhantomData;
use core::sync::atomic::{AtomicU32, Ordering};
use crate::abi::{LayoutField, PointFlags, PointMode, PointVerdict, Status, Str};
use crate::load::{hand_over, ok};
use crate::{Host, Load, Off};
#[cfg(feature = "unstable")]
pub use described::{DescribedField, DescribedPoint, DescribedVerdict};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Mode {
Observe,
Advise,
Override,
}
impl Mode {
#[must_use]
pub const fn changes(self) -> bool {
!matches!(self, Self::Observe)
}
#[must_use]
pub const fn to_abi(self) -> PointMode {
match self {
Self::Observe => PointMode::OBSERVE,
Self::Advise => PointMode::ADVISE,
Self::Override => PointMode::OVERRIDE,
}
}
}
pub unsafe trait Point: Copy + 'static {
const NAME: &'static str;
const MODE: Mode;
const REFUSE: bool;
const LAYOUT: &'static [LayoutField];
const OUTPUT: &'static [LayoutField];
const SCHEMA: &'static str;
type Output: Copy + 'static;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Verdict<O> {
Pass,
Set(O),
Refuse,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Decision<O> {
Kept(O),
Changed(O),
Refused,
}
impl<O: Copy> Decision<O> {
#[must_use]
pub const fn value_or(self, refused: O) -> O {
match self {
Self::Kept(value) | Self::Changed(value) => value,
Self::Refused => refused,
}
}
#[must_use]
pub const fn is_refused(self) -> bool {
matches!(self, Self::Refused)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Order {
before: Vec<&'static str>,
after: Vec<&'static str>,
priority: i32,
}
impl Order {
#[must_use]
pub const fn new() -> Self {
Self {
before: Vec::new(),
after: Vec::new(),
priority: 0,
}
}
#[must_use]
pub fn before(mut self, id: &'static str) -> Self {
self.before.push(id);
self
}
#[must_use]
pub fn after(mut self, id: &'static str) -> Self {
self.after.push(id);
self
}
#[must_use]
pub const fn priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
#[must_use]
pub const fn priority_value(&self) -> i32 {
self.priority
}
}
pub struct OwnedPoint<P> {
host: Host,
id: u32,
live: &'static AtomicU32,
_point: PhantomData<fn(&P)>,
}
impl<P> Clone for OwnedPoint<P> {
fn clone(&self) -> Self {
*self
}
}
impl<P> Copy for OwnedPoint<P> {}
impl<P> core::fmt::Debug for OwnedPoint<P> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("OwnedPoint")
.field("id", &self.id)
.field("live", &self.live.load(Ordering::Relaxed))
.finish_non_exhaustive()
}
}
impl<P: Point> OwnedPoint<P> {
#[inline]
pub fn decide(&self, input: &P, answer: P::Output) -> Decision<P::Output> {
if self.live.load(Ordering::Relaxed) == 0 {
return Decision::Kept(answer);
}
self.run(input, answer)
}
#[must_use]
pub fn is_extended(&self) -> bool {
self.live.load(Ordering::Relaxed) != 0
}
#[inline(never)]
fn run(&self, input: &P, answer: P::Output) -> Decision<P::Output> {
let mut value = answer;
let mut verdict = PointVerdict::PASS;
let status = (self.host.api().run_point)(
self.host.handle(),
self.id,
core::ptr::from_ref(input).cast(),
(&raw mut value).cast(),
&raw mut verdict,
);
if !status.is_ok() {
return Decision::Kept(answer);
}
match verdict {
PointVerdict::SET => Decision::Changed(value),
PointVerdict::REFUSE => Decision::Refused,
_ => Decision::Kept(answer),
}
}
}
static NONE_LIVE: AtomicU32 = AtomicU32::new(0);
pub(crate) fn spec<P: Point>() -> crate::abi::PointSpec {
crate::abi::PointSpec {
header: crate::abi::Header::of::<crate::abi::PointSpec>(),
name: Str::new(P::NAME),
mode: P::MODE.to_abi(),
flags: if P::REFUSE {
PointFlags::REFUSE
} else {
PointFlags::NONE
},
input: crate::services::layout(P::LAYOUT, size_of::<P>()),
output: crate::services::layout(P::OUTPUT, size_of::<P::Output>()),
}
}
impl Load<'_> {
pub fn define_point<P: Point>(&mut self) -> Result<OwnedPoint<P>, Off> {
let spec = spec::<P>();
let mut handle = crate::abi::PointHandle::EMPTY;
let status = (self.host().api().define_point)(
self.host().handle(),
&raw const spec,
&raw mut handle,
);
let live = unsafe { handle.live.as_ref() }.unwrap_or(&NONE_LIVE);
ok(
status,
OwnedPoint {
host: self.host(),
id: handle.id,
live,
_point: PhantomData,
},
|| {
if status == Status::REFUSED {
format!(
"point {}: named after another mod or defined twice",
P::NAME
)
} else {
format!("point {}", P::NAME)
}
},
)
}
pub fn extend_point<P: Point>(
&mut self,
order: &Order,
link: impl Fn(&P, &P::Output) -> Verdict<P::Output> + Send + Sync + 'static,
) -> Result<(), Off> {
attach(self.host(), order, link)
}
pub fn observe_point<P: Point>(
&mut self,
order: &Order,
link: impl Fn(&P, &P::Output) + Send + Sync + 'static,
) -> Result<(), Off> {
attach(self.host(), order, move |input: &P, value: &P::Output| {
link(input, value);
Verdict::Pass
})
}
}
fn attach<P: Point, F: Fn(&P, &P::Output) -> Verdict<P::Output> + Send + Sync + 'static>(
host: Host,
order: &Order,
link: F,
) -> Result<(), Off> {
let spec = spec::<P>();
let before: Vec<Str> = order.before.iter().map(|id| Str::new(id)).collect();
let after: Vec<Str> = order.after.iter().map(|id| Str::new(id)).collect();
let count = |ids: &[Str]| u32::try_from(ids.len()).unwrap_or(0);
let raw = crate::abi::PointLink {
header: crate::abi::Header::of::<crate::abi::PointLink>(),
point: &raw const spec,
before: before.as_ptr(),
after: after.as_ptr(),
before_count: count(&before),
after_count: count(&after),
priority: order.priority,
};
hand_over(
link,
|user| (host.api().attach_point)(host.handle(), &raw const raw, run_link::<P, F>, user),
&format!("point {}", P::NAME),
)
}
pub(crate) unsafe extern "system" fn run_link<
P: Point,
F: Fn(&P, &P::Output) -> Verdict<P::Output>,
>(
input: *const c_void,
value: *mut c_void,
user: *mut c_void,
) -> PointVerdict {
let link = unsafe { &*user.cast::<F>() };
let Some(input) = (unsafe { input.cast::<P>().as_ref() }) else {
return PointVerdict::PASS;
};
let Some(value) = (unsafe { value.cast::<P::Output>().as_mut() }) else {
return PointVerdict::PASS;
};
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| link(input, value))) {
Ok(Verdict::Set(new)) if P::MODE.changes() => {
*value = new;
PointVerdict::SET
}
Ok(Verdict::Refuse) if P::REFUSE && P::MODE.changes() => PointVerdict::REFUSE,
Ok(_) => PointVerdict::PASS,
Err(_) => PointVerdict::PANICKED,
}
}
#[doc(hidden)]
#[must_use]
pub fn order(links: &[LinkPlace<'_>]) -> (Vec<usize>, Vec<usize>) {
let n = links.len();
let index_of = |id: &str| links.iter().position(|l| l.id == id);
let mut edges: Vec<Vec<usize>> = vec![Vec::new(); n];
for (i, link) in links.iter().enumerate() {
for &later in link.before {
if let Some(j) = index_of(later).filter(|&j| j != i) {
edges[i].push(j);
}
}
for &earlier in link.after {
if let Some(j) = index_of(earlier).filter(|&j| j != i) {
edges[j].push(i);
}
}
}
let key = |i: usize| (links[i].priority, links[i].id, i);
let mut dropped = Vec::new();
let mut placed = vec![false; n];
let mut running = Vec::with_capacity(n);
while running.len() < n {
let mut waiting = vec![0usize; n];
for (a, after) in edges.iter().enumerate() {
if placed[a] {
continue;
}
for &b in after {
waiting[b] += 1;
}
}
let free = (0..n)
.filter(|&i| !placed[i] && waiting[i] == 0)
.min_by_key(|&i| key(i));
if let Some(next) = free {
placed[next] = true;
running.push(next);
continue;
}
let reach = reachable(&edges, &placed);
let Some(next) = (0..n)
.filter(|&i| {
!placed[i]
&& reach[i][i]
&& (0..n).all(|q| placed[q] || !reach[q][i] || reach[i][q])
})
.min_by_key(|&i| key(i))
else {
break;
};
for (a, after) in edges.iter_mut().enumerate() {
if !placed[a] {
after.retain(|&b| b != next);
}
}
dropped.push(next);
placed[next] = true;
running.push(next);
}
(running, dropped)
}
fn reachable(edges: &[Vec<usize>], placed: &[bool]) -> Vec<Vec<bool>> {
let n = edges.len();
let mut reach = vec![vec![false; n]; n];
for (start, row) in reach.iter_mut().enumerate() {
if placed[start] {
continue;
}
let mut stack = vec![start];
while let Some(a) = stack.pop() {
for &b in &edges[a] {
if !placed[b] && !row[b] {
row[b] = true;
stack.push(b);
}
}
}
}
reach
}
#[doc(hidden)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LinkPlace<'a> {
pub id: &'a str,
pub priority: i32,
pub before: &'a [&'a str],
pub after: &'a [&'a str],
}
#[cfg(test)]
mod tests {
use super::{LinkPlace, order};
fn place<'a>(id: &'a str, priority: i32, after: &'a [&'a str]) -> LinkPlace<'a> {
LinkPlace {
id,
priority,
before: &[],
after,
}
}
#[test]
fn a_cycle_drops_only_its_own_constraints() {
let links = [
place("c", 0, &["d"]),
place("d", 0, &["c"]),
place("x", -5, &["c"]),
];
let (running, dropped) = order(&links);
assert_eq!(running, [0, 2, 1]);
assert_eq!(dropped, [0]);
}
#[test]
fn a_cycle_behind_another_waits_for_it() {
let links = [
place("a", 5, &["b"]),
place("b", 5, &["a"]),
place("c", -5, &["d", "b"]),
place("d", -5, &["c"]),
];
let (running, dropped) = order(&links);
assert_eq!(running, [0, 1, 2, 3]);
assert_eq!(dropped, [0, 2]);
}
}