1use std::{collections::BTreeMap, sync::Arc};
2
3use sim_kernel::{
4 CapabilityName, CatalogSource, Cx, DefaultFactory, Error as KernelError, GrantSeat, Lib,
5 LibBootDependency, LibId, LibLoader, LibManifest, LibSource as KernelLibSource,
6 LibSourceSpec as KernelLibSourceSpec, LoadedLib, LoaderRegistry, NoopEvalPolicy, Symbol,
7};
8use sim_lib_stream_host::native_audio_provider_capability;
9
10use crate::{
11 CliBoot, CliError, CratesIoResolver, CratesIoSpec, LibSourceSpec, LoadReceipt, LoadReceiptRole,
12 codec_boot::{boot_codec_name, codec_lib_symbol, explicit_codec_source_index},
13 crates_io::fallback_spec_for_symbol,
14 source::symbol_from_text,
15};
16
17pub struct LoadSession {
19 cx: Cx,
20 seat: GrantSeat,
24 loaders: LoaderRegistry,
25 hosts: HostLibRegistry,
26 crates_io: CratesIoResolver,
27 catalog_sources: BTreeMap<Symbol, LibSourceSpec>,
28 default_verb_sources: BTreeMap<String, Vec<LibSourceSpec>>,
29 receipts: Vec<LoadReceipt>,
30}
31
32impl LoadSession {
33 pub fn new() -> Self {
35 let mut loaders = LoaderRegistry::new();
36 loaders.add_loader(HostSourceLoader);
37 let (cx, seat) = Cx::new_seated(Arc::new(NoopEvalPolicy), Arc::new(DefaultFactory));
38 Self {
39 cx,
40 seat,
41 loaders,
42 hosts: HostLibRegistry::default(),
43 crates_io: CratesIoResolver::default(),
44 catalog_sources: BTreeMap::new(),
45 default_verb_sources: BTreeMap::new(),
46 receipts: Vec::new(),
47 }
48 }
49
50 pub fn add_loader(&mut self, loader: impl LibLoader + 'static) {
52 self.loaders.add_loader(loader);
53 }
54
55 pub fn add_catalog_source(&mut self, symbol: impl AsRef<str>, source: CatalogSource) {
57 let symbol = symbol_from_text(symbol.as_ref());
58 self.catalog_sources
59 .insert(symbol.clone(), catalog_source_spec(source.clone()));
60 self.loaders.add_source(symbol, source);
61 }
62
63 pub fn with_catalog_source(mut self, symbol: impl AsRef<str>, source: CatalogSource) -> Self {
65 self.add_catalog_source(symbol, source);
66 self
67 }
68
69 pub fn with_loader(mut self, loader: impl LibLoader + 'static) -> Self {
71 self.add_loader(loader);
72 self
73 }
74
75 pub fn add_host_factory(
77 &mut self,
78 name: impl Into<String>,
79 factory: impl Fn() -> Box<dyn Lib> + Send + Sync + 'static,
80 ) {
81 self.hosts.add(name, factory);
82 }
83
84 pub fn with_host_factory(
86 mut self,
87 name: impl Into<String>,
88 factory: impl Fn() -> Box<dyn Lib> + Send + Sync + 'static,
89 ) -> Self {
90 self.add_host_factory(name, factory);
91 self
92 }
93
94 pub fn with_crates_io_resolver(mut self, resolver: CratesIoResolver) -> Self {
96 self.crates_io = resolver;
97 self
98 }
99
100 pub fn with_context(mut self, configure: impl FnOnce(&mut Cx)) -> Self {
105 configure(&mut self.cx);
106 self
107 }
108
109 pub fn with_capability(mut self, capability: CapabilityName) -> Self {
116 self.seat.grant(&mut self.cx, capability);
117 self
118 }
119
120 pub fn add_default_verb_sources(
122 &mut self,
123 verb: impl Into<String>,
124 sources: Vec<LibSourceSpec>,
125 ) {
126 self.default_verb_sources.insert(verb.into(), sources);
127 }
128
129 pub fn with_default_verb_sources(
132 mut self,
133 verb: impl Into<String>,
134 sources: Vec<LibSourceSpec>,
135 ) -> Self {
136 self.add_default_verb_sources(verb, sources);
137 self
138 }
139
140 pub fn cx(&self) -> &Cx {
142 &self.cx
143 }
144
145 pub(crate) fn cx_mut(&mut self) -> &mut Cx {
146 &mut self.cx
147 }
148
149 pub(crate) fn crates_io(&self) -> &CratesIoResolver {
150 &self.crates_io
151 }
152
153 pub(crate) fn hosts(&self) -> &HostLibRegistry {
154 &self.hosts
155 }
156
157 pub(crate) fn catalog_sources(&self) -> &BTreeMap<Symbol, LibSourceSpec> {
158 &self.catalog_sources
159 }
160
161 pub(crate) fn resolve_data_source(&self, source: KernelLibSourceSpec) -> KernelLibSourceSpec {
162 self.loaders.resolve_source_spec(&source)
163 }
164
165 pub(crate) fn inspect_data_source_manifest(
166 &mut self,
167 source: KernelLibSourceSpec,
168 ) -> Result<LibManifest, CliError> {
169 self.loaders
170 .inspect_manifest(&mut self.cx, source.into())
171 .map_err(|err| CliError::new(format!("inspect source: {err}")))
172 }
173
174 pub fn receipts(&self) -> &[LoadReceipt] {
176 &self.receipts
177 }
178
179 pub fn load_boot(&mut self, boot: &CliBoot) -> Result<&[LoadReceipt], CliError> {
181 let boot = self.boot_with_default_verb_sources(boot);
182 self.load_native_audio_provider(&boot);
183 let codec_name = boot_codec_name(&boot);
184 let codec_symbol = codec_lib_symbol(codec_name);
185 let codec_index = explicit_codec_source_index(&boot, &codec_symbol);
186 match codec_index {
187 Some(index) => {
188 self.load_boot_codec_source(codec_name, &codec_symbol, &boot.loads[index])?;
189 }
190 None => {
191 self.load_boot_codec_source(
192 codec_name,
193 &codec_symbol,
194 &LibSourceSpec::Symbol(codec_symbol.clone()),
195 )?;
196 }
197 }
198 for (index, source) in boot.loads.iter().enumerate() {
199 if Some(index) != codec_index {
200 self.load_source(source)?;
201 }
202 }
203 Ok(&self.receipts)
204 }
205
206 fn load_native_audio_provider(&mut self, boot: &CliBoot) {
207 let Some(source) = boot.native_audio_provider.as_deref() else {
208 return;
209 };
210 self.seat
211 .grant(&mut self.cx, native_audio_provider_capability());
212 if self
213 .load_source_with_role(source, LoadReceiptRole::Library)
214 .is_err()
215 {
216 }
219 }
220
221 fn boot_with_default_verb_sources(&self, boot: &CliBoot) -> CliBoot {
222 if !boot.loads.is_empty() {
223 return boot.clone();
224 }
225 let Some(verb) = boot
226 .payload
227 .args
228 .first()
229 .map(|arg| arg.to_string_lossy().into_owned())
230 else {
231 return boot.clone();
232 };
233 let Some(sources) = self.default_verb_sources.get(&verb) else {
234 return boot.clone();
235 };
236 let mut boot = boot.clone();
237 boot.loads.clone_from(sources);
238 boot
239 }
240
241 pub fn load_source(&mut self, source: &LibSourceSpec) -> Result<LoadReceipt, CliError> {
243 self.load_source_with_role(source, LoadReceiptRole::Library)
244 }
245
246 fn load_boot_codec_source(
247 &mut self,
248 codec_name: &str,
249 codec_symbol: &str,
250 source: &LibSourceSpec,
251 ) -> Result<LoadReceipt, CliError> {
252 let role = LoadReceiptRole::boot_codec(codec_name, codec_symbol);
253 match self.load_source_with_role(source, role.clone()) {
254 Ok(receipt) => Ok(receipt),
255 Err(_) if self.hosts.contains(codec_symbol) => {
256 self.load_source_with_role(&LibSourceSpec::Host(codec_symbol.to_owned()), role)
257 }
258 Err(err) => Err(no_codec_error(codec_name, err)),
259 }
260 }
261
262 fn load_source_with_role(
263 &mut self,
264 source: &LibSourceSpec,
265 role: LoadReceiptRole,
266 ) -> Result<LoadReceipt, CliError> {
267 if let LibSourceSpec::Host(name) = source {
268 return self.load_host_source(source, name, role);
269 }
270 if let LibSourceSpec::CratesIo(spec) = source {
271 return self.load_crates_io_source(source, spec, role);
272 }
273
274 let data_source = source
275 .to_kernel_data_source()
276 .expect("non-host sources have data forms");
277 ensure_loadable_path(&data_source, source)?;
278 let fallback = match source {
279 LibSourceSpec::Symbol(symbol) => fallback_spec_for_symbol(symbol),
280 _ => None,
281 };
282 match self.load_data_source(source, data_source, role.clone()) {
283 Ok(receipt) => Ok(receipt),
284 Err(err) => match fallback {
285 Some(spec) => {
286 self.load_crates_io_source(&LibSourceSpec::CratesIo(spec.clone()), &spec, role)
287 }
288 None => Err(err),
289 },
290 }
291 }
292
293 fn load_data_source(
294 &mut self,
295 source: &LibSourceSpec,
296 data_source: KernelLibSourceSpec,
297 role: LoadReceiptRole,
298 ) -> Result<LoadReceipt, CliError> {
299 let receipt = self
300 .loaders
301 .load_and_register_with_receipt(&mut self.cx, data_source)
302 .map_err(|err| load_error(source, err))?;
303 let receipt = LoadReceipt {
304 lib_id: receipt.lib_id,
305 role,
306 requested_source: LibSourceSpec::from_kernel_data_source(receipt.requested_source),
307 resolved_source: LibSourceSpec::from_kernel_data_source(receipt.resolved_source),
308 manifest: receipt.manifest,
309 dependencies: receipt.dependencies,
310 exports: receipt.exports,
311 };
312 self.receipts.push(receipt.clone());
313 Ok(receipt)
314 }
315
316 fn load_crates_io_source(
317 &mut self,
318 source: &LibSourceSpec,
319 spec: &CratesIoSpec,
320 role: LoadReceiptRole,
321 ) -> Result<LoadReceipt, CliError> {
322 let resolved = self.crates_io.resolve(spec)?;
323 let data_source = KernelLibSourceSpec::Path(resolved.artifact);
324 ensure_loadable_path(&data_source, source)?;
325 let receipt = self
326 .loaders
327 .load_and_register_with_receipt(&mut self.cx, data_source)
328 .map_err(|err| load_error(source, err))?;
329 let receipt = LoadReceipt {
330 lib_id: receipt.lib_id,
331 role,
332 requested_source: source.clone(),
333 resolved_source: LibSourceSpec::from_kernel_data_source(receipt.resolved_source),
334 manifest: receipt.manifest,
335 dependencies: receipt.dependencies,
336 exports: receipt.exports,
337 };
338 self.receipts.push(receipt.clone());
339 Ok(receipt)
340 }
341
342 pub fn unload_receipt(&mut self, receipt: &LoadReceipt) -> Result<Vec<LibId>, CliError> {
344 self.cx.unload_lib(receipt.lib_id).map_err(|err| {
345 CliError::new(format!("unload failed for {}: {err}", receipt.manifest.id))
346 })
347 }
348
349 fn load_host_source(
350 &mut self,
351 source: &LibSourceSpec,
352 name: &str,
353 role: LoadReceiptRole,
354 ) -> Result<LoadReceipt, CliError> {
355 let lib = self.hosts.instantiate(name)?;
356 let lib_id = self
357 .loaders
358 .load_and_register(&mut self.cx, KernelLibSource::Host(lib))
359 .map_err(|err| load_error(source, err))?;
360 let loaded = self
361 .cx
362 .registry()
363 .libs()
364 .iter()
365 .find(|loaded| loaded.id == lib_id)
366 .cloned()
367 .ok_or_else(|| CliError::new(format!("loaded lib id {lib_id:?} is not registered")))?;
368 let receipt = host_receipt(source.clone(), role, loaded, self.cx.registry().libs());
369 self.receipts.push(receipt.clone());
370 Ok(receipt)
371 }
372}
373
374fn catalog_source_spec(source: CatalogSource) -> LibSourceSpec {
375 match source {
376 CatalogSource::Path(path) => LibSourceSpec::Path(path),
377 CatalogSource::Url(url) => LibSourceSpec::Url(url),
378 CatalogSource::Bytes(bytes) => LibSourceSpec::Bytes(bytes),
379 }
380}
381
382impl Default for LoadSession {
383 fn default() -> Self {
384 Self::new()
385 }
386}
387
388#[derive(Default)]
389pub(crate) struct HostLibRegistry {
390 factories: BTreeMap<String, Box<dyn Fn() -> Box<dyn Lib> + Send + Sync>>,
391}
392
393impl HostLibRegistry {
394 fn add(
395 &mut self,
396 name: impl Into<String>,
397 factory: impl Fn() -> Box<dyn Lib> + Send + Sync + 'static,
398 ) {
399 self.factories.insert(name.into(), Box::new(factory));
400 }
401
402 pub(crate) fn instantiate(&self, name: &str) -> Result<Box<dyn Lib>, CliError> {
403 self.factories
404 .get(name)
405 .map(|factory| factory())
406 .ok_or_else(|| CliError::new(format!("unknown host library: {name}")))
407 }
408
409 fn contains(&self, name: &str) -> bool {
410 self.factories.contains_key(name)
411 }
412}
413
414struct HostSourceLoader;
415
416impl LibLoader for HostSourceLoader {
417 fn can_load(&self, source: &KernelLibSource) -> bool {
418 matches!(source, KernelLibSource::Host(_))
419 }
420
421 fn load(&self, _cx: &mut Cx, source: KernelLibSource) -> sim_kernel::Result<Box<dyn Lib>> {
422 match source {
423 KernelLibSource::Host(lib) => Ok(lib),
424 _ => Err(KernelError::Lib(
425 "host loader received a non-host source".to_owned(),
426 )),
427 }
428 }
429}
430
431fn host_receipt(
432 source: LibSourceSpec,
433 role: LoadReceiptRole,
434 loaded: LoadedLib,
435 loaded_libs: &[LoadedLib],
436) -> LoadReceipt {
437 let dependencies = loaded
438 .manifest
439 .requires
440 .iter()
441 .filter_map(|dependency| {
442 let loaded = loaded_libs
443 .iter()
444 .find(|candidate| candidate.manifest.id == dependency.id)?;
445 Some(LibBootDependency {
446 lib_id: loaded.id,
447 symbol: loaded.manifest.id.clone(),
448 })
449 })
450 .collect();
451 LoadReceipt {
452 lib_id: loaded.id,
453 role,
454 requested_source: source.clone(),
455 resolved_source: source,
456 manifest: loaded.manifest,
457 dependencies,
458 exports: loaded.exports,
459 }
460}
461
462fn ensure_loadable_path(
463 data_source: &KernelLibSourceSpec,
464 source: &LibSourceSpec,
465) -> Result<(), CliError> {
466 if let KernelLibSourceSpec::Path(path) = data_source
467 && !path.exists()
468 {
469 return Err(CliError::new(format!(
470 "path source not found for {source}: {}",
471 path.display()
472 )));
473 }
474 Ok(())
475}
476
477fn load_error(source: &LibSourceSpec, err: KernelError) -> CliError {
478 CliError::new(format!("load failed for {source}: {err}"))
479}
480
481fn no_codec_error(codec_name: &str, err: CliError) -> CliError {
482 CliError::new(format!(
483 "no codec '{codec_name}' available; provide one with --load ({err})"
484 ))
485}