use std::collections::{BTreeMap, BTreeSet};
use std::sync::Arc;
use sim_kernel::{
CapabilityName, Cx, Error, Export, Lib, LibManifest, LibSource, LoaderRegistry, Result, Symbol,
};
use crate::{AudioRouter, AudioSite};
pub const AUDIO_PROVIDER_ENTRY_V1: &str = "sim_audio_provider_v1";
pub const AUDIO_PROVIDER_ABI_VERSION: u32 = 1;
pub fn native_audio_provider_capability() -> CapabilityName {
CapabilityName::new("audio.provider.native")
}
pub trait AudioProviderRegistrar {
fn register_site(&mut self, site: Arc<dyn AudioSite>);
fn host_abi_version(&self) -> u32;
}
pub type AudioProviderEntry = fn(&mut dyn AudioProviderRegistrar) -> Result<()>;
#[derive(Clone, Default)]
pub struct AudioProviderProofEntries {
entries: BTreeMap<Symbol, AudioProviderEntry>,
}
impl AudioProviderProofEntries {
pub fn new() -> Self {
Self::default()
}
pub fn with_proof_entry(mut self, provider: Symbol, entry: AudioProviderEntry) -> Self {
self.insert(provider, entry);
self
}
pub fn insert(&mut self, provider: Symbol, entry: AudioProviderEntry) {
self.entries.insert(provider, entry);
}
fn entry(&self, provider: &Symbol) -> Result<AudioProviderEntry> {
self.entries.get(provider).copied().ok_or_else(|| {
Error::HostError(format!(
"audio provider {} has no proof entry for {}",
provider, AUDIO_PROVIDER_ENTRY_V1
))
})
}
}
pub struct AudioProviderHost<'a> {
cx: &'a mut Cx,
loaders: &'a LoaderRegistry,
proof_entries: AudioProviderProofEntries,
}
impl<'a> AudioProviderHost<'a> {
pub fn new(cx: &'a mut Cx, loaders: &'a LoaderRegistry) -> Self {
Self {
cx,
loaders,
proof_entries: AudioProviderProofEntries::new(),
}
}
pub fn with_proof_entry(mut self, provider: Symbol, entry: AudioProviderEntry) -> Self {
self.proof_entries.insert(provider, entry);
self
}
pub fn with_entry(self, provider: Symbol, entry: AudioProviderEntry) -> Self {
self.with_proof_entry(provider, entry)
}
pub fn load_into(&mut self, source: LibSource, router: &mut AudioRouter) -> Result<()> {
self.cx.require(&native_audio_provider_capability())?;
let lib = self.loaders.load_lib(self.cx, source)?;
register_provider_lib(lib.as_ref(), &self.proof_entries, router)
}
}
fn register_provider_lib(
lib: &dyn Lib,
proof_entries: &AudioProviderProofEntries,
router: &mut AudioRouter,
) -> Result<()> {
let manifest = lib.manifest();
let site_exports = AudioProviderSiteExports::from_manifest(&manifest)?;
let entry = proof_entries.entry(&manifest.id)?;
let mut registrar =
RouterAudioProviderRegistrar::for_provider(router, manifest.id, site_exports.symbols);
entry(&mut registrar)?;
registrar.finish()
}
struct AudioProviderSiteExports {
symbols: BTreeSet<Symbol>,
}
impl AudioProviderSiteExports {
fn from_manifest(manifest: &LibManifest) -> Result<Self> {
let mut symbols = BTreeSet::new();
for export in &manifest.exports {
if let Export::Site { symbol, .. } = export {
if symbol.namespace.as_deref() != Some("audio/site") {
return Err(Error::HostError(format!(
"audio provider {} declared invalid site export {}; expected audio/site",
manifest.id, symbol
)));
}
symbols.insert(symbol.clone());
}
}
if symbols.is_empty() {
return Err(Error::HostError(format!(
"audio provider {} declared no audio/site exports",
manifest.id
)));
}
Ok(Self { symbols })
}
}
pub struct RouterAudioProviderRegistrar<'a> {
router: &'a mut AudioRouter,
owner: Symbol,
allowed_sites: Option<BTreeSet<Symbol>>,
registration_error: Option<Error>,
}
impl<'a> RouterAudioProviderRegistrar<'a> {
pub fn new(router: &'a mut AudioRouter) -> Self {
Self {
router,
owner: Symbol::qualified("audio/provider", "proof"),
allowed_sites: None,
registration_error: None,
}
}
pub fn for_provider(
router: &'a mut AudioRouter,
owner: Symbol,
allowed_sites: BTreeSet<Symbol>,
) -> Self {
Self {
router,
owner,
allowed_sites: Some(allowed_sites),
registration_error: None,
}
}
pub fn finish(self) -> Result<()> {
match self.registration_error {
Some(err) => Err(err),
None => Ok(()),
}
}
fn record_error(&mut self, error: Error) {
if self.registration_error.is_none() {
self.registration_error = Some(error);
}
}
}
impl AudioProviderRegistrar for RouterAudioProviderRegistrar<'_> {
fn register_site(&mut self, site: Arc<dyn AudioSite>) {
if self.registration_error.is_some() {
return;
}
let site_symbol = site.key().0.clone();
if let Some(allowed_sites) = &self.allowed_sites
&& !allowed_sites.contains(&site_symbol)
{
self.record_error(Error::HostError(format!(
"audio provider {} registered undeclared site {}",
self.owner, site_symbol
)));
return;
}
if let Err(err) = self.router.register_owned(self.owner.clone(), site) {
self.record_error(err);
}
}
fn host_abi_version(&self) -> u32 {
AUDIO_PROVIDER_ABI_VERSION
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::{
AudioDeviceCard, AudioProviderRegistrar, AudioRouter, AudioSiteKey, FakeBackend,
ModeledAudioSite, RouterAudioProviderRegistrar,
};
#[test]
fn provider_seam_registers_site_into_router() {
let key = AudioSiteKey::new("audio/native/jack-spike");
let card = AudioDeviceCard::modeled(key.clone(), "JACK Provider Spike");
let site = Arc::new(ModeledAudioSite::new(card, Arc::new(FakeBackend::new())));
let mut router = AudioRouter::new();
{
let mut registrar = RouterAudioProviderRegistrar::new(&mut router);
assert_eq!(registrar.host_abi_version(), 1);
registrar.register_site(site);
registrar.finish().unwrap();
}
assert!(router.site(&key).is_some());
}
}