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::loadable::{LifecycleAction, LoadableLibList};
17use crate::ops::{CookbookLifecycleOp, CookbookOp, OpKind};
18
19/// The lib id: `sim:cookbook`.
20pub fn manifest_name() -> Symbol {
21    Symbol::qualified("sim", "cookbook")
22}
23
24/// The registered value holding the shared cookbook store handle.
25pub fn store_symbol() -> Symbol {
26    Symbol::qualified("cookbook", "store")
27}
28
29/// A runtime value that exposes the shared recipe store to projection layers.
30#[sim_citizen_derive::non_citizen(
31    reason = "live cookbook store handle; reconstruct from embedded recipe descriptors",
32    kind = "handle",
33    descriptor = "cookbook/Recipe"
34)]
35#[derive(Clone)]
36pub struct CookbookStoreHandle {
37    store: Arc<Mutex<RecipeStore>>,
38}
39
40impl CookbookStoreHandle {
41    /// Build a handle over the store used by the `cookbook:` ops.
42    pub fn new(store: Arc<Mutex<RecipeStore>>) -> Self {
43        Self { store }
44    }
45
46    /// Return the shared store handle.
47    pub fn store(&self) -> Arc<Mutex<RecipeStore>> {
48        self.store.clone()
49    }
50}
51
52impl Object for CookbookStoreHandle {
53    fn display(&self, _cx: &mut Cx) -> Result<String> {
54        Ok("#<cookbook-store>".to_owned())
55    }
56
57    fn as_any(&self) -> &dyn std::any::Any {
58        self
59    }
60}
61
62impl ObjectCompat for CookbookStoreHandle {
63    fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
64        cx.factory()
65            .class_stub(CORE_TABLE_CLASS_ID, Symbol::qualified("core", "Table"))
66    }
67
68    fn as_table(&self, cx: &mut Cx) -> Result<Value> {
69        let count = self.store.lock().expect("recipe store lock").len();
70        let kind = cx
71            .factory()
72            .symbol(Symbol::qualified("cookbook", "store"))?;
73        let recipes = cx.factory().string(count.to_string())?;
74        cx.factory().table(vec![
75            (Symbol::new("kind"), kind),
76            (Symbol::new("recipes"), recipes),
77        ])
78    }
79}
80
81/// The cookbook lib, holding the shared recipe store the ops read.
82pub struct CookbookLib {
83    store: Arc<Mutex<RecipeStore>>,
84    loadable_libs: Arc<LoadableLibList>,
85}
86
87impl CookbookLib {
88    /// Build the lib over an already-populated recipe store.
89    pub fn new(store: RecipeStore) -> Self {
90        Self {
91            store: Arc::new(Mutex::new(store)),
92            loadable_libs: Arc::new(LoadableLibList::new(Vec::new())),
93        }
94    }
95
96    /// Build the lib over `store` with a host-owned loadable-lib directory.
97    pub fn with_loadable_libs(store: RecipeStore, loadable_libs: LoadableLibList) -> Self {
98        Self {
99            store: Arc::new(Mutex::new(store)),
100            loadable_libs: Arc::new(loadable_libs),
101        }
102    }
103
104    /// The shared store handle (for the server to keep populating, if needed).
105    pub fn store(&self) -> Arc<Mutex<RecipeStore>> {
106        self.store.clone()
107    }
108}
109
110impl Lib for CookbookLib {
111    fn manifest(&self) -> LibManifest {
112        LibManifest {
113            id: manifest_name(),
114            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
115            abi: AbiVersion { major: 0, minor: 1 },
116            target: LibTarget::HostRegistered,
117            requires: Vec::new(),
118            capabilities: Vec::new(),
119            exports: {
120                let mut exports = op_exports();
121                exports.extend(cookbook_cli_exports());
122                exports
123            },
124        }
125    }
126
127    fn load(&self, cx: &mut LoadCx, linker: &mut Linker<'_>) -> Result<()> {
128        for kind in OpKind::ALL {
129            let op = CookbookOp::new(self.store.clone(), kind);
130            linker.function_value(kind.symbol(), cx.factory().opaque(Arc::new(op))?)?;
131        }
132        for action in [LifecycleAction::Load, LifecycleAction::Unload] {
133            let op = CookbookLifecycleOp::new(self.loadable_libs.clone(), action);
134            linker.function_value(
135                CookbookLifecycleOp::symbol(action),
136                cx.factory().opaque(Arc::new(op))?,
137            )?;
138        }
139        let store = CookbookStoreHandle::new(self.store.clone());
140        linker.value(store_symbol(), cx.factory().opaque(Arc::new(store))?)?;
141        register_cookbook_cli(cx, linker)?;
142        Ok(())
143    }
144}
145
146/// The `cookbook:*` function exports.
147pub fn op_exports() -> Vec<Export> {
148    let mut exports = OpKind::ALL
149        .into_iter()
150        .map(|kind| Export::Function {
151            symbol: kind.symbol(),
152            function_id: None,
153        })
154        .collect::<Vec<_>>();
155    exports.extend(
156        [LifecycleAction::Load, LifecycleAction::Unload]
157            .into_iter()
158            .map(|action| Export::Function {
159                symbol: CookbookLifecycleOp::symbol(action),
160                function_id: None,
161            }),
162    );
163    exports.push(Export::Value {
164        symbol: store_symbol(),
165    });
166    exports
167}
168
169/// Install the cookbook lib over `store` (idempotent).
170pub fn install_cookbook_lib(cx: &mut Cx, store: RecipeStore) -> Result<()> {
171    sim_lib_core::install_once(cx, &CookbookLib::new(store))?;
172    Ok(())
173}
174
175/// Install the cookbook lib with lifecycle ops over `loadable_libs`.
176pub fn install_cookbook_lib_with_loadable_libs(
177    cx: &mut Cx,
178    store: RecipeStore,
179    loadable_libs: LoadableLibList,
180) -> Result<()> {
181    sim_lib_core::install_once(cx, &CookbookLib::with_loadable_libs(store, loadable_libs))?;
182    Ok(())
183}