Skip to main content

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