Skip to main content

sim_lib_cookbook/
runtime.rs

1//! The cookbook lib: registers the `cookbook:` ops over a shared recipe store.
2//!
3//! A server or CLI builds a [`RecipeStore`] (by registering each loaded lib's
4//! embedded book and loading the user overlay), then installs this lib to
5//! expose the recipes as runtime operations.
6
7use std::sync::{Arc, Mutex};
8
9use sim_cookbook::RecipeStore;
10use sim_kernel::{
11    AbiVersion, CORE_TABLE_CLASS_ID, ClassRef, Cx, Export, Lib, LibManifest, LibTarget, Linker,
12    LoadCx, Object, ObjectCompat, Result, Symbol, Value, Version,
13};
14
15use crate::cli::{cookbook_cli_exports, register_cookbook_cli};
16use crate::ops::{CookbookOp, OpKind};
17
18/// The lib id: `sim:cookbook`.
19pub fn manifest_name() -> Symbol {
20    Symbol::qualified("sim", "cookbook")
21}
22
23/// The registered value holding the shared cookbook store handle.
24pub fn store_symbol() -> Symbol {
25    Symbol::qualified("cookbook", "store")
26}
27
28/// A runtime value that exposes the shared recipe store to projection layers.
29#[sim_citizen_derive::non_citizen(
30    reason = "live cookbook store handle; reconstruct from embedded recipe descriptors",
31    kind = "handle",
32    descriptor = "cookbook/Recipe"
33)]
34#[derive(Clone)]
35pub struct CookbookStoreHandle {
36    store: Arc<Mutex<RecipeStore>>,
37}
38
39impl CookbookStoreHandle {
40    /// Build a handle over the store used by the `cookbook:` ops.
41    pub fn new(store: Arc<Mutex<RecipeStore>>) -> Self {
42        Self { store }
43    }
44
45    /// Return the shared store handle.
46    pub fn store(&self) -> Arc<Mutex<RecipeStore>> {
47        self.store.clone()
48    }
49}
50
51impl Object for CookbookStoreHandle {
52    fn display(&self, _cx: &mut Cx) -> Result<String> {
53        Ok("#<cookbook-store>".to_owned())
54    }
55
56    fn as_any(&self) -> &dyn std::any::Any {
57        self
58    }
59}
60
61impl ObjectCompat for CookbookStoreHandle {
62    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
63        cx.factory()
64            .class_stub(CORE_TABLE_CLASS_ID, Symbol::qualified("core", "Table"))
65    }
66
67    fn as_table(&self, cx: &mut Cx) -> Result<Value> {
68        let count = self.store.lock().expect("recipe store lock").len();
69        let kind = cx
70            .factory()
71            .symbol(Symbol::qualified("cookbook", "store"))?;
72        let recipes = cx.factory().string(count.to_string())?;
73        cx.factory().table(vec![
74            (Symbol::new("kind"), kind),
75            (Symbol::new("recipes"), recipes),
76        ])
77    }
78}
79
80/// The cookbook lib, holding the shared recipe store the ops read.
81pub struct CookbookLib {
82    store: Arc<Mutex<RecipeStore>>,
83}
84
85impl CookbookLib {
86    /// Build the lib over an already-populated recipe store.
87    pub fn new(store: RecipeStore) -> Self {
88        Self {
89            store: Arc::new(Mutex::new(store)),
90        }
91    }
92
93    /// The shared store handle (for the server to keep populating, if needed).
94    pub fn store(&self) -> Arc<Mutex<RecipeStore>> {
95        self.store.clone()
96    }
97}
98
99impl Lib for CookbookLib {
100    fn manifest(&self) -> LibManifest {
101        LibManifest {
102            id: manifest_name(),
103            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
104            abi: AbiVersion { major: 0, minor: 1 },
105            target: LibTarget::HostRegistered,
106            requires: Vec::new(),
107            capabilities: Vec::new(),
108            exports: {
109                let mut exports = op_exports();
110                exports.extend(cookbook_cli_exports());
111                exports
112            },
113        }
114    }
115
116    fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
117        for kind in OpKind::ALL {
118            let op = CookbookOp::new(self.store.clone(), kind);
119            linker.function_value(kind.symbol(), cx.factory().opaque(Arc::new(op))?)?;
120        }
121        let store = CookbookStoreHandle::new(self.store.clone());
122        linker.value(store_symbol(), cx.factory().opaque(Arc::new(store))?)?;
123        register_cookbook_cli(cx, linker)?;
124        Ok(())
125    }
126}
127
128/// The `cookbook:*` function exports.
129pub fn op_exports() -> Vec<Export> {
130    let mut exports = OpKind::ALL
131        .into_iter()
132        .map(|kind| Export::Function {
133            symbol: kind.symbol(),
134            function_id: None,
135        })
136        .collect::<Vec<_>>();
137    exports.push(Export::Value {
138        symbol: store_symbol(),
139    });
140    exports
141}
142
143/// Install the cookbook lib over `store` (idempotent).
144pub fn install_cookbook_lib(cx: &mut Cx, store: RecipeStore) -> Result<()> {
145    sim_lib_core::install_once(cx, &CookbookLib::new(store))?;
146    Ok(())
147}