sim-lib-stream-host 0.1.0-rc.1

Host-device stream backend substrate for STREAM 6.
Documentation
use std::sync::{
    Arc,
    atomic::{AtomicUsize, Ordering},
};

use sim_kernel::{
    AbiVersion, Cx, DefaultFactory, EagerPolicy, Lib, LibLoader, LibManifest, LibSource, LibTarget,
    Linker, LoadCx, LoaderRegistry, Result, Symbol, Version,
};
use sim_lib_stream_core::{BufferPolicy, StreamMedia};

use crate::{
    AudioDeviceCard, AudioPlacementRequest, AudioProviderHost, AudioProviderRegistrar, AudioRouter,
    AudioSiteKey, DeviceCatalog, FakeBackend, HostDirection, HostStreamConfigRequest,
    ModeledAudioSite, fake_backend_symbol, native_audio_provider_capability,
};

fn modeled_provider_symbol() -> Symbol {
    Symbol::qualified("audio/provider", "modeled-fixture")
}

fn modeled_provider_entry(registrar: &mut dyn AudioProviderRegistrar) -> Result<()> {
    let key = AudioSiteKey::new("audio/provider-modeled/stereo-0");
    let card = AudioDeviceCard::modeled(key, "Modeled Provider Stereo 0");
    registrar.register_site(Arc::new(ModeledAudioSite::new(
        card,
        Arc::new(FakeBackend::new()),
    )));
    Ok(())
}

#[derive(Clone)]
struct ModeledProviderLoader {
    loads: Arc<AtomicUsize>,
}

impl ModeledProviderLoader {
    fn new(loads: Arc<AtomicUsize>) -> Self {
        Self { loads }
    }
}

impl LibLoader for ModeledProviderLoader {
    fn can_load(&self, source: &LibSource) -> bool {
        matches!(source, LibSource::Symbol(symbol) if symbol == &modeled_provider_symbol())
    }

    fn load(&self, _cx: &mut Cx, source: LibSource) -> Result<Box<dyn Lib>> {
        assert!(self.can_load(&source));
        self.loads.fetch_add(1, Ordering::SeqCst);
        Ok(Box::new(ModeledProviderLib))
    }
}

struct ModeledProviderLib;

impl Lib for ModeledProviderLib {
    fn manifest(&self) -> LibManifest {
        LibManifest {
            id: modeled_provider_symbol(),
            version: Version("0.1.0".to_owned()),
            abi: AbiVersion { major: 0, minor: 1 },
            target: LibTarget::HostRegistered,
            requires: Vec::new(),
            capabilities: Vec::new(),
            exports: Vec::new(),
        }
    }

    fn load(&self, _cx: &mut LoadCx, _linker: &mut Linker) -> Result<()> {
        Ok(())
    }
}

fn test_cx() -> Cx {
    Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory))
}

#[test]
fn modeled_provider_load_denied_without_capability() {
    let loads = Arc::new(AtomicUsize::new(0));
    let loaders = LoaderRegistry::new().with_loader(ModeledProviderLoader::new(Arc::clone(&loads)));
    let mut cx = test_cx();
    let mut router = AudioRouter::new();
    let mut host = AudioProviderHost::new(&mut cx, &loaders)
        .with_entry(modeled_provider_symbol(), modeled_provider_entry);

    let err = host
        .load_into(LibSource::Symbol(modeled_provider_symbol()), &mut router)
        .unwrap_err();

    assert!(err.to_string().contains("audio.provider.native"));
    assert_eq!(loads.load(Ordering::SeqCst), 0);
    assert!(router.site_keys().next().is_none());
}

#[test]
fn modeled_provider_registers_site_and_catalog_discovers_it() {
    let loads = Arc::new(AtomicUsize::new(0));
    let loaders = LoaderRegistry::new().with_loader(ModeledProviderLoader::new(Arc::clone(&loads)));
    let mut cx = test_cx();
    cx.grant(native_audio_provider_capability());
    let mut router = AudioRouter::new();
    let mut host = AudioProviderHost::new(&mut cx, &loaders)
        .with_entry(modeled_provider_symbol(), modeled_provider_entry);

    host.load_into(LibSource::Symbol(modeled_provider_symbol()), &mut router)
        .unwrap();

    assert_eq!(loads.load(Ordering::SeqCst), 1);
    let key = AudioSiteKey::new("audio/provider-modeled/stereo-0");
    assert!(router.site(&key).is_some());

    let mut catalog = DeviceCatalog::default_modeled();
    catalog.register_provider_sites(&router);
    let records = catalog.enumerate_audio().unwrap();
    assert!(
        records
            .iter()
            .any(|record| record.id.to_string().contains("provider-modeled"))
    );
}

#[test]
fn missing_provider_entry_degrades_to_modeled_site() {
    let loads = Arc::new(AtomicUsize::new(0));
    let loaders = LoaderRegistry::new().with_loader(ModeledProviderLoader::new(Arc::clone(&loads)));
    let mut cx = test_cx();
    cx.grant(native_audio_provider_capability());
    let modeled = AudioSiteKey::new("audio/modeled/stereo-0");
    let mut router = AudioRouter::new();
    router.register(Arc::new(ModeledAudioSite::new(
        AudioDeviceCard::modeled(modeled.clone(), "Modeled Stereo 0"),
        Arc::new(FakeBackend::new()),
    )));
    let mut host = AudioProviderHost::new(&mut cx, &loaders);

    let err = host
        .load_into(LibSource::Symbol(modeled_provider_symbol()), &mut router)
        .unwrap_err();

    assert_eq!(loads.load(Ordering::SeqCst), 1);
    assert!(err.to_string().contains("sim_audio_provider_v1"));
    let requested = AudioSiteKey::new("audio/provider-modeled/stereo-0");
    let resolved = router.resolve_or_modeled(&requested, &modeled).unwrap();
    assert_eq!(resolved, modeled);
    let opened = router
        .open_placement(AudioPlacementRequest {
            site_key: resolved,
            stream_request: HostStreamConfigRequest::new(
                fake_backend_symbol(),
                Symbol::new("fake/pcm"),
                StreamMedia::Pcm,
                HostDirection::Output,
                BufferPolicy::bounded(8).unwrap(),
            ),
        })
        .unwrap();
    assert_eq!(opened.config().media(), StreamMedia::Pcm);
    opened.close().unwrap();
}