Skip to main content

yog_api/
interop.rs

1//! Mods' exported functions on the bus (`docs/design/bus.md`, stage 4):
2//! `#[yog_export] fn name(..)` of mod `id` is a service at `id:name`; an
3//! `import!` (or a mod's generated `*_exports` crate) calls it. The
4//! arguments cross as a map of the parameters' names, the answer as its
5//! own Yog Data — so a Java mod can call it too, and the bus can describe it.
6
7use yog_abi::{YogBytes, YogHandlerFn, YogReply, YogStatus, status};
8use yog_data::Schema;
9use yog_event::Message;
10
11use crate::registry::guard;
12use crate::service::call_at;
13
14/// A `#[yog_export]` function, as its `ctor` registers it.
15#[doc(hidden)]
16pub struct YogExportEntry {
17    pub name: &'static str,
18    pub handler: YogHandlerFn,
19    /// Its doc comment.
20    pub doc: &'static str,
21    /// Its arguments' and its answer's shapes.
22    pub shapes: fn() -> (yog_data::Shape, yog_data::Shape),
23    /// The interfaces it implements (`#[yog_export(implements = "yog:energy/storage")]`).
24    pub implements: &'static [&'static str],
25}
26
27#[doc(hidden)]
28pub fn __yog_export_registry() -> &'static yog_sync::Mutex<Vec<YogExportEntry>> {
29    static REG: yog_sync::Mutex<Vec<YogExportEntry>> = yog_sync::Mutex::new(Vec::new());
30    &REG
31}
32
33/// Where mod `mod_id`'s export `name` is provided: `mod_id:name`.
34pub fn export_address(mod_id: &str, name: &str) -> String {
35    yog_ids::join(mod_id, name)
36}
37
38/// What an exported function's provider does: its arguments `A` read from
39/// `payload`, `run` called, its answer `R` written to `reply`. A panic in
40/// the mod is a failed call, not a crash.
41///
42/// # Safety
43/// `payload` and `reply` are what the bus hands a handler (valid for the call).
44#[doc(hidden)]
45pub unsafe fn __yog_serve<A: Schema, R: Schema>(
46    payload: YogBytes,
47    reply: *const YogReply,
48    run: impl FnOnce(A) -> R,
49) -> YogStatus {
50    let Some(args) = A::from_bytes(unsafe { payload.as_slice() }) else {
51        return status::BAD_PAYLOAD;
52    };
53    let Some(answer) = guard(None, || Some(run(args))) else {
54        return status::FAILED;
55    };
56    unsafe { YogReply::send(reply, &answer.to_bytes()) };
57    status::OK
58}
59
60/// Calls mod `mod_id`'s export `name` with `args`: its answer, `None` when
61/// it isn't there (the mod isn't loaded) or failed.
62pub fn call_export<A: Schema, R: Schema>(mod_id: &str, name: &str, args: &A) -> Option<R> {
63    R::from_bytes(&call_at(
64        yog_event::resolve(&export_address(mod_id, name)),
65        &args.to_bytes(),
66    )?)
67}
68
69impl crate::Registry {
70    /// Provides `mod_id`'s export `entry` (what `export_mod!` does for each).
71    #[doc(hidden)]
72    pub fn provide_export(&mut self, mod_id: &str, entry: &YogExportEntry) {
73        let address = export_address(mod_id, entry.name);
74        if self.provide_raw(&address, entry.handler) {
75            let (args, answer) = (entry.shapes)();
76            let interfaces = entry.implements.iter().map(|i| (*i).to_owned()).collect();
77            self.declare(&address, entry.doc, &args, &answer, interfaces);
78        }
79    }
80}