use core::sync::atomic::{AtomicPtr, Ordering};
use std::ffi::c_void;
use std::sync::{Arc, Mutex};
use crate::dispatch::DeviceState;
use crate::driver::{AnyDriver, DriverInfo};
use crate::objects::ObjectMap;
use crate::raw::abi::{AudioServerPlugInDriverInterface, AudioServerPlugInHostRef};
use crate::raw::clock::DeviceClock;
use crate::raw::ring::DeviceRing;
use crate::realtime::Refcount;
pub struct DriverRuntime {
driver: Arc<dyn AnyDriver>,
objects: ObjectMap,
info: DriverInfo,
state: Mutex<DeviceState>,
host: AtomicPtr<c_void>,
clock: DeviceClock,
ring: DeviceRing,
}
impl DriverRuntime {
#[must_use]
pub fn new(driver: Arc<dyn AnyDriver>) -> Self {
let spec = driver.device();
let objects = ObjectMap::new(spec);
let info = driver.info();
let state = Mutex::new(DeviceState::from_spec(objects.spec()));
let capacity_frames = (spec.sample_rate() as usize).max(1);
let input_channels = spec.input().map_or(0, |stream| stream.channels() as usize);
let output_channels = spec.output().map_or(0, |stream| stream.channels() as usize);
let channels = input_channels.max(output_channels).max(1);
Self {
driver,
objects,
info,
state,
host: AtomicPtr::new(core::ptr::null_mut()),
clock: DeviceClock::new(),
ring: DeviceRing::new(capacity_frames, channels),
}
}
#[inline]
#[must_use]
pub fn driver(&self) -> &Arc<dyn AnyDriver> {
&self.driver
}
#[inline]
#[must_use]
pub fn objects(&self) -> &ObjectMap {
&self.objects
}
#[inline]
#[must_use]
pub fn info(&self) -> &DriverInfo {
&self.info
}
pub fn state(&self) -> std::sync::MutexGuard<'_, DeviceState> {
self.state
.lock()
.unwrap_or_else(|poison| poison.into_inner())
}
pub fn set_host(&self, host: AudioServerPlugInHostRef) {
self.host.store(host.cast(), Ordering::Release);
}
#[must_use]
pub fn host(&self) -> AudioServerPlugInHostRef {
self.host.load(Ordering::Acquire).cast()
}
#[inline]
#[must_use]
pub fn clock(&self) -> &DeviceClock {
&self.clock
}
#[inline]
#[must_use]
pub fn ring(&self) -> &DeviceRing {
&self.ring
}
}
#[repr(C)]
pub struct DriverObject {
vtable: *const AudioServerPlugInDriverInterface,
refcount: Refcount,
runtime: DriverRuntime,
}
unsafe impl Send for DriverObject {}
unsafe impl Sync for DriverObject {}
impl DriverObject {
#[must_use]
pub fn new(vtable: &'static AudioServerPlugInDriverInterface, runtime: DriverRuntime) -> Self {
Self {
vtable,
refcount: Refcount::new(),
runtime,
}
}
#[inline]
#[must_use]
pub fn runtime(&self) -> &DriverRuntime {
&self.runtime
}
#[inline]
#[must_use]
pub fn refcount(&self) -> &Refcount {
&self.refcount
}
#[inline]
#[must_use]
pub unsafe fn from_ref<'a>(
driver: crate::raw::abi::AudioServerPlugInDriverRef,
) -> Option<&'a DriverObject> {
unsafe { driver.cast::<DriverObject>().as_ref() }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::driver::{Driver, DriverInstance};
use crate::raw::abi::AudioServerPlugInDriverRef;
use crate::realtime::State;
use crate::{DeviceSpec, IoBuffer, RealtimeContext, StreamFormat, StreamSpec};
struct Loopback;
impl Driver for Loopback {
const NAME: &'static str = "tympan-aspl runtime fixture";
const MANUFACTURER: &'static str = "tympan-aspl";
const VERSION: &'static str = "0.0.0";
fn new() -> Self {
Self
}
fn device(&self) -> DeviceSpec {
let format = StreamFormat::float32(48_000.0, 2);
DeviceSpec::new(
"com.tympan.test.runtime",
"Runtime Fixture",
Self::MANUFACTURER,
)
.with_input(StreamSpec::input(format))
.with_output(StreamSpec::output(format))
}
fn process_io(&mut self, _rt: &RealtimeContext, _buffer: &mut IoBuffer<'_>) {}
}
fn runtime() -> DriverRuntime {
DriverRuntime::new(Arc::new(DriverInstance::<Loopback>::new()))
}
fn vtable() -> &'static AudioServerPlugInDriverInterface {
crate::raw::vtable::driver_interface()
}
#[test]
fn runtime_materialises_the_object_tree_and_identity() {
let rt = runtime();
assert_eq!(rt.info().name, "tympan-aspl runtime fixture");
assert!(rt.objects().spec().is_loopback());
assert_eq!(rt.state().sample_rate, 48_000.0);
assert!(!rt.state().running);
assert_eq!(rt.driver().state(), State::Uninitialized);
}
#[test]
fn host_pointer_round_trips() {
let rt = runtime();
assert!(rt.host().is_null());
let fake_host = 0xDEAD_BEEF_usize as *mut c_void;
rt.set_host(fake_host);
assert_eq!(rt.host(), fake_host);
}
#[test]
fn state_lock_is_mutable() {
let rt = runtime();
rt.state().sample_rate = 96_000.0;
assert_eq!(rt.state().sample_rate, 96_000.0);
}
#[test]
fn driver_object_starts_with_refcount_one() {
let object = DriverObject::new(vtable(), runtime());
assert_eq!(object.refcount().count(), 1);
}
#[test]
fn from_ref_recovers_the_object() {
let object = Box::new(DriverObject::new(vtable(), runtime()));
let raw: *mut DriverObject = Box::into_raw(object);
let driver_ref = raw.cast::<*const AudioServerPlugInDriverInterface>();
let recovered = unsafe { DriverObject::from_ref(driver_ref) }.unwrap();
assert_eq!(recovered.refcount().count(), 1);
assert_eq!(
recovered.runtime().info().name,
"tympan-aspl runtime fixture"
);
drop(unsafe { Box::from_raw(raw) });
}
#[test]
fn from_ref_rejects_null() {
let recovered =
unsafe { DriverObject::from_ref(core::ptr::null_mut() as AudioServerPlugInDriverRef) };
assert!(recovered.is_none());
}
#[test]
fn driver_object_is_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<DriverObject>();
assert_send_sync::<DriverRuntime>();
}
}