use core::ffi::c_void;
use core::sync::atomic::{AtomicU32, Ordering};
use crate::abi::{
ModHandle, PointFn, PointHandle, PointLink, PointSpec, PointVerdict, Status, Str,
};
use crate::points::{Decision, LinkPlace, Point, Verdict, order};
use super::Installed;
use super::table::{put, view, with};
#[derive(Clone)]
struct Declared {
name: String,
input: Vec<(String, u64)>,
output: Vec<(String, u64)>,
mode: crate::abi::PointMode,
refuse: bool,
input_size: u32,
output_size: u32,
}
impl Declared {
fn of<P: Point>() -> Self {
let fields = |layout: &[crate::abi::LayoutField]| {
layout
.iter()
.filter_map(|f| unsafe { f.name.as_str() }.map(|n| (n.to_owned(), f.hash)))
.collect()
};
Self {
name: P::NAME.to_owned(),
input: fields(P::LAYOUT),
output: fields(P::OUTPUT),
mode: P::MODE.to_abi(),
refuse: P::REFUSE,
input_size: u32::try_from(size_of::<P>()).unwrap_or(u32::MAX),
output_size: u32::try_from(size_of::<P::Output>()).unwrap_or(u32::MAX),
}
}
fn serves(&self, link: &Self) -> bool {
self.name == link.name
&& crate::services::compare(&view(&link.input), &view(&self.input)).is_ok()
&& self.output == link.output
&& (self.mode, self.refuse) == (link.mode, link.refuse)
&& link.input_size <= self.input_size
&& link.output_size == self.output_size
}
}
#[derive(Clone, Copy)]
struct Link {
callback: PointFn,
user: usize,
on: bool,
}
struct Defined {
declared: Declared,
live: &'static AtomicU32,
links: Vec<Link>,
}
struct Attached {
declared: Declared,
id: String,
priority: i32,
before: Vec<String>,
after: Vec<String>,
link: Link,
}
#[derive(Default)]
pub(crate) struct FakePoints {
defined: Vec<Defined>,
attached: Vec<Attached>,
}
fn declared(spec: *const PointSpec) -> Option<Declared> {
let spec = unsafe { spec.as_ref() }?;
let name = unsafe { spec.name.as_str() }?.to_owned();
let input = unsafe { crate::services::read_layout(&spec.input) }?;
let output = unsafe { crate::services::read_layout(&spec.output) }?;
Some(Declared {
name,
input,
output,
mode: spec.mode,
refuse: spec.flags.contains(crate::abi::PointFlags::REFUSE),
input_size: spec.input.size,
output_size: spec.output.size,
})
}
fn ids(raw: *const Str, count: u32) -> Vec<String> {
if raw.is_null() || count == 0 {
return Vec::new();
}
let ids = unsafe { core::slice::from_raw_parts(raw, count as usize) };
ids.iter()
.filter_map(|id| unsafe { id.as_str() }.map(str::to_owned))
.collect()
}
pub(crate) extern "system" fn define_point(
_me: ModHandle,
spec: *const PointSpec,
out: *mut PointHandle,
) -> Status {
let Some(declared) = declared(spec) else {
return Status::BAD_ARGS;
};
let id = with(None, |s| {
let points = &mut s.points;
if points
.defined
.iter()
.any(|d| d.declared.name == declared.name)
{
return None;
}
let live: &'static AtomicU32 = Box::leak(Box::new(AtomicU32::new(0)));
points.defined.push(Defined {
declared,
live,
links: Vec::new(),
});
u32::try_from(points.defined.len() - 1)
.ok()
.map(|id| (id, live))
});
match id {
Some((id, live)) => put(
out,
PointHandle {
header: crate::abi::Header::of::<PointHandle>(),
id,
live,
},
),
None => Status::REFUSED,
}
}
pub(crate) extern "system" fn attach_point(
_me: ModHandle,
link: *const PointLink,
callback: PointFn,
user: *mut c_void,
) -> Status {
let Some(link) = (unsafe { link.as_ref() }) else {
return Status::BAD_ARGS;
};
let Some(declared) = declared(link.point) else {
return Status::BAD_ARGS;
};
let attached = Attached {
declared,
id: String::new(),
priority: link.priority,
before: ids(link.before, link.before_count),
after: ids(link.after, link.after_count),
link: Link {
callback,
user: user.expose_provenance(),
on: true,
},
};
with(Status::MISSING, |s| {
let attached_already = s
.points
.attached
.iter()
.any(|a| a.declared.name == attached.declared.name);
if attached_already {
return Status::REFUSED;
}
s.points.attached.push(attached);
Status::OK
})
}
fn run(
links: &[Link],
changes: bool,
refuse: bool,
input: *const c_void,
value: *mut c_void,
) -> (PointVerdict, Vec<usize>) {
let mut verdict = PointVerdict::PASS;
let mut panicked = Vec::new();
for (i, link) in links.iter().enumerate().filter(|(_, l)| l.on) {
let answer = unsafe {
(link.callback)(
input,
value,
core::ptr::with_exposed_provenance_mut(link.user),
)
};
match answer {
PointVerdict::SET if changes => verdict = PointVerdict::SET,
PointVerdict::REFUSE if changes && refuse => return (PointVerdict::REFUSE, panicked),
PointVerdict::PANICKED => panicked.push(i),
_ => {}
}
}
(verdict, panicked)
}
pub(crate) extern "system" fn run_point(
_me: ModHandle,
point: u32,
input: *const c_void,
value: *mut c_void,
verdict: *mut PointVerdict,
) -> Status {
let Some((links, changes, refuse)) = with(None, |s| {
s.points.defined.get(point as usize).map(|d| {
(
d.links.clone(),
d.declared.mode != crate::abi::PointMode::OBSERVE,
d.declared.refuse,
)
})
}) else {
return Status::BAD_ARGS;
};
let (answer, panicked) = run(&links, changes, refuse, input, value);
if !panicked.is_empty() {
with((), |s| {
if let Some(defined) = s.points.defined.get_mut(point as usize) {
for &i in &panicked {
if let Some(link) = defined.links.get_mut(i) {
link.on = false;
defined.live.fetch_sub(1, Ordering::Relaxed);
}
}
}
});
}
put(verdict, answer)
}
#[expect(
clippy::unused_self,
reason = "the fake's state is process-wide; `self` proves it is installed"
)]
impl Installed {
#[must_use = "false means the mod defines no such point"]
pub fn extend_point<P: Point, F>(&self, link: F) -> bool
where
F: Fn(&P, &P::Output) -> Verdict<P::Output> + Send + Sync + 'static,
{
let wanted = Declared::of::<P>();
let user = Box::into_raw(Box::new(link));
let attached = with(false, |s| {
let Some(defined) = s
.points
.defined
.iter_mut()
.find(|d| d.declared.serves(&wanted))
else {
return false;
};
defined.links.push(Link {
callback: crate::points::run_link::<P, F>,
user: user.expose_provenance(),
on: true,
});
defined.live.fetch_add(1, Ordering::Relaxed);
true
});
if !attached {
drop(unsafe { Box::from_raw(user) });
}
attached
}
#[must_use]
pub fn defined_points(&self) -> Vec<String> {
with(Vec::new(), |s| {
s.points
.defined
.iter()
.map(|d| d.declared.name.clone())
.collect()
})
}
#[must_use]
pub fn attached_points(&self) -> Vec<String> {
with(Vec::new(), |s| {
s.points
.attached
.iter()
.map(|a| a.declared.name.clone())
.collect()
})
}
#[must_use]
pub fn ask_point<P: Point>(&self, input: &P, answer: P::Output) -> Decision<P::Output> {
let owner = Declared::of::<P>();
let (links, positions) = with((Vec::new(), Vec::new()), |s| {
let serving: Vec<(usize, &Attached)> = s
.points
.attached
.iter()
.enumerate()
.filter(|(_, a)| owner.serves(&a.declared))
.collect();
let before: Vec<Vec<&str>> = serving
.iter()
.map(|(_, a)| a.before.iter().map(String::as_str).collect())
.collect();
let after: Vec<Vec<&str>> = serving
.iter()
.map(|(_, a)| a.after.iter().map(String::as_str).collect())
.collect();
let places: Vec<LinkPlace<'_>> = serving
.iter()
.zip(before.iter().zip(&after))
.map(|((_, a), (before, after))| LinkPlace {
id: &a.id,
priority: a.priority,
before,
after,
})
.collect();
let (running, _) = order(&places);
let links: Vec<Link> = running.iter().map(|&i| serving[i].1.link).collect();
let positions: Vec<usize> = running.iter().map(|&i| serving[i].0).collect();
(links, positions)
});
let mut value = answer;
let (verdict, panicked) = run(
&links,
owner.mode != crate::abi::PointMode::OBSERVE,
owner.refuse,
core::ptr::from_ref(input).cast(),
(&raw mut value).cast(),
);
with((), |s| {
for i in panicked {
if let Some(attached) = positions.get(i).and_then(|&p| s.points.attached.get_mut(p))
{
attached.link.on = false;
}
}
});
match verdict {
PointVerdict::SET => Decision::Changed(value),
PointVerdict::REFUSE => Decision::Refused,
_ => Decision::Kept(answer),
}
}
}