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