embedded_wg_display/runtime/
mod.rs1mod platform;
14
15mod host_api;
16
17pub mod http_sync;
18
19use common::models::WidgetInstallationData;
20use wasmtime::component::{Component, HasSelf, Linker};
21use wasmtime::{Config, Engine, Precompiled, Result, Store};
22
23use alloc::string::{String, ToString};
24
25use crate::runtime::widget::widget::types::Datetime;
26
27use crate::globals;
28
29use hashbrown::HashMap;
30
31use defmt::warn;
32
33wasmtime::component::bindgen!({ path: "src/runtime/host_api/wit" });
35
36pub struct WidgetState {}
39
40impl WidgetState {
41 fn new() -> Self {
42 Self {}
43 }
44}
45
46pub struct Runtime {
47 engine: Engine,
48 linker: Linker<WidgetState>,
49 last_run: HashMap<String, Datetime>,
50}
51
52impl Runtime {
53 pub fn new() -> Self {
55 defmt::info!("Initializing Wasmtime runtime");
56
57 let mut config = Config::new();
58 config.wasm_component_model(true);
59
60 config.wasm_bulk_memory(true);
62 config.wasm_simd(false);
63 config.wasm_relaxed_simd(false);
64 config.wasm_multi_memory(false);
65 config.gc_support(false);
66
67 config.signals_based_traps(false);
68 config.wasm_multi_value(false);
70 config.wasm_tail_call(false);
72
73 config.memory_reservation(0);
74 config.memory_guard_size(0);
76 config.memory_init_cow(false);
77 config.concurrency_support(false);
78
79 let engine = Engine::new(&config).expect("Failed to create Wasmtime engine");
80
81 let mut linker = Linker::<WidgetState>::new(&engine);
82 Widget::add_to_linker::<WidgetState, HasSelf<WidgetState>>(
84 &mut linker,
85 |state: &mut WidgetState| state,
86 )
87 .expect("Could not link host API");
88
89 defmt::info!("Wasmtime runtime initialized successfully");
90
91 Self {
92 engine,
93 linker,
94 last_run: HashMap::new(),
95 }
96 }
97
98 unsafe fn load_module(&self, bytes: &[u8]) -> Result<Component> {
100 defmt::debug!("Loading precompiled module ({} bytes)", bytes.len());
101
102 match Engine::detect_precompiled(bytes) {
103 Some(Precompiled::Component) => {}
104 Some(Precompiled::Module) => {
105 defmt::error!("Precompiled blob is a core module, but runtime expects a component");
106 return Err(wasmtime::Error::msg("expected precompiled component"));
107 }
108 None => {
109 defmt::error!("Input bytes are not recognized as a Wasmtime precompiled artifact");
110 return Err(wasmtime::Error::msg("invalid precompiled artifact"));
111 }
112 }
113
114 let component = match unsafe { Component::deserialize(&self.engine, bytes) } {
116 Ok(component) => component,
117 Err(err) => {
118 defmt::error!(
119 "Failed to deserialize component: {:?}",
120 defmt::Debug2Format(&err)
121 );
122 return Err(err);
123 }
124 };
125
126 defmt::info!("Module loaded successfully");
127 Ok(component)
128 }
129
130 fn instantiate(
133 &mut self,
134 component: &Component,
135 store: &mut Store<WidgetState>,
136 ) -> Result<Widget> {
137 defmt::debug!("Instantiating component");
138
139 let widget = match Widget::instantiate(store, component, &self.linker) {
140 Ok(widget) => widget,
141 Err(err) => {
142 defmt::error!(
143 "Failed to instantiate component: {:?}",
144 defmt::Debug2Format(&err)
145 );
146 return Err(err);
147 }
148 };
149
150 defmt::info!("Component instantiated successfully");
151 Ok(widget)
152 }
153
154 fn run(
162 &mut self,
163 widget: &Widget,
164 config: String,
165 store: &mut Store<WidgetState>,
166 name: String,
167 ) -> wasmtime::Result<Option<WidgetResult>> {
168 defmt::info!("Running widget with config: {}", config.as_str());
169 let last_invocation =
170 *self
171 .last_run
172 .get(name.as_str())
173 .unwrap_or(&globals::now().unwrap_or(Datetime {
174 seconds: 0,
175 nanoseconds: 0,
176 }));
177
178 let context = WidgetContext {
179 last_invocation,
180 config,
181 };
182
183 let result = match widget.call_run(store, &context) {
184 Ok(result) => result,
185 Err(err) => {
186 defmt::error!("Failed to run widget: {:?}", defmt::Debug2Format(&err));
187 return Err(err);
188 }
189 };
190
191 self.last_run.insert(
192 name,
193 globals::now().unwrap_or(Datetime {
194 seconds: 0,
195 nanoseconds: 0,
196 }),
197 );
198
199 defmt::info!("Widget ran successfully result: {}", result.data.as_str());
200 Ok(Some(result))
201 }
202
203 fn get_widget_name(
205 &mut self,
206 widget: &Widget,
207 store: &mut Store<WidgetState>,
208 ) -> wasmtime::Result<String> {
209 widget.call_get_name(store)
210 }
211
212 fn get_config_schema(
214 &mut self,
215 widget: &Widget,
216 store: &mut Store<WidgetState>,
217 ) -> wasmtime::Result<String> {
218 widget.call_get_config_schema(store)
219 }
220
221 fn get_widget_version(
223 &mut self,
224 widget: &Widget,
225 store: &mut Store<WidgetState>,
226 ) -> wasmtime::Result<String> {
227 widget.call_get_version(store)
228 }
229
230 fn get_run_update_cycle_seconds(
232 &mut self,
233 widget: &Widget,
234 store: &mut Store<WidgetState>,
235 ) -> wasmtime::Result<u32> {
236 widget.call_get_run_update_cycle_seconds(store)
237 }
238
239 pub async unsafe fn run_widget(
241 &mut self,
242 widget_name: String,
243 config: String,
244 ) -> wasmtime::Result<Option<WidgetResult>> {
245 let mut store = Store::new(&self.engine, WidgetState::new());
246
247 let wasm_bytes = match globals::with_storage(|s| s.wasm_read(&widget_name)).await {
248 Ok(bytes) => bytes,
249 Err(err) => {
250 warn!(
251 "Could not read widget '{}': {:?}",
252 widget_name.as_str(),
253 defmt::Debug2Format(&err)
254 );
255 return Err(wasmtime::Error::msg("Widget binary missing"));
256 }
257 };
258
259 let component = unsafe { self.load_module(&wasm_bytes)? };
260 let instance = self.instantiate(&component, &mut store)?;
261 self.run(&instance, config, &mut store, widget_name)
262 }
263
264 pub async unsafe fn get_widget_metadata(
268 &mut self,
269 bytes: &[u8],
270 ) -> wasmtime::Result<WidgetInstallationData> {
271 let mut store = Store::new(&self.engine, WidgetState::new());
272 let component = unsafe { self.load_module(bytes)? };
273 let instance = self.instantiate(&component, &mut store)?;
274 let name = self.get_widget_name(&instance, &mut store)?;
275 let json_config_schema = self.get_config_schema(&instance, &mut store)?;
276 let version = self.get_widget_version(&instance, &mut store)?;
277 let update_cycle_seconds = self.get_run_update_cycle_seconds(&instance, &mut store)?;
278
279 Ok(WidgetInstallationData {
280 name,
281 description: String::new(), version,
283 json_config: "{}".to_string(),
284 json_config_schema,
285 update_cycle_seconds,
286 })
287 }
288}