use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use crate::frame::{ConnectorId, EndpointId, RouteId, SessionId, SourceId, StemId, StreamId};
use crate::graph::NodeConfig;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SessionTimelineOrigin {
monotonic_timestamp_ns: u64,
}
impl SessionTimelineOrigin {
pub const fn from_monotonic_timestamp_ns(monotonic_timestamp_ns: u64) -> Self {
Self {
monotonic_timestamp_ns,
}
}
pub const fn monotonic_timestamp_ns(self) -> u64 {
self.monotonic_timestamp_ns
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EndpointInputOrigin {
Stem(StemId),
Signal,
Source {
source_id: SourceId,
stream_id: StreamId,
audio_stem_id: Option<StemId>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EndpointRouteContext {
route_id: RouteId,
origin: EndpointInputOrigin,
}
impl EndpointRouteContext {
pub const fn from_stem(route_id: RouteId, stem_id: StemId) -> Self {
Self {
route_id,
origin: EndpointInputOrigin::Stem(stem_id),
}
}
pub const fn from_source(
route_id: RouteId,
source_id: SourceId,
stream_id: StreamId,
audio_stem_id: Option<StemId>,
) -> Self {
Self {
route_id,
origin: EndpointInputOrigin::Source {
source_id,
stream_id,
audio_stem_id,
},
}
}
pub const fn signal(route_id: RouteId) -> Self {
Self {
route_id,
origin: EndpointInputOrigin::Signal,
}
}
pub const fn route_id(self) -> RouteId {
self.route_id
}
pub const fn origin(self) -> EndpointInputOrigin {
self.origin
}
pub const fn audio_stem_id(self) -> Option<StemId> {
match self.origin {
EndpointInputOrigin::Stem(stem_id) => Some(stem_id),
EndpointInputOrigin::Signal => None,
EndpointInputOrigin::Source { audio_stem_id, .. } => audio_stem_id,
}
}
}
#[derive(Debug, Clone)]
pub struct EndpointPrepareContext {
session_id: SessionId,
endpoint_id: EndpointId,
connector_id: Option<ConnectorId>,
route_context: EndpointRouteContext,
session_timeline_origin: SessionTimelineOrigin,
node_configuration: NodeConfig,
}
impl EndpointPrepareContext {
pub fn new(
session_id: SessionId,
endpoint_id: EndpointId,
route_context: EndpointRouteContext,
session_timeline_origin: SessionTimelineOrigin,
node_configuration: NodeConfig,
) -> Self {
Self {
session_id,
endpoint_id,
connector_id: None,
route_context,
session_timeline_origin,
node_configuration,
}
}
pub const fn session_id(&self) -> SessionId {
self.session_id
}
pub const fn endpoint_id(&self) -> EndpointId {
self.endpoint_id
}
pub(crate) const fn with_connector_id(mut self, connector_id: Option<ConnectorId>) -> Self {
self.connector_id = connector_id;
self
}
pub const fn connector_id(&self) -> Option<ConnectorId> {
self.connector_id
}
pub const fn route_context(&self) -> EndpointRouteContext {
self.route_context
}
pub const fn session_timeline_origin(&self) -> SessionTimelineOrigin {
self.session_timeline_origin
}
pub const fn node_configuration(&self) -> &NodeConfig {
&self.node_configuration
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EndpointFailureStage {
Prepare,
CancelPreparation,
Start,
RequestStop,
JoinFinalize,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("endpoint {stage:?} failed: {message}")]
pub struct EndpointFailure {
stage: EndpointFailureStage,
message: String,
}
impl EndpointFailure {
pub fn new(stage: EndpointFailureStage, message: impl Into<String>) -> Self {
Self {
stage,
message: message.into(),
}
}
pub const fn stage(&self) -> EndpointFailureStage {
self.stage
}
pub fn message(&self) -> &str {
&self.message
}
pub(crate) fn owned_heap_bytes(&self) -> usize {
self.message.capacity()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct EndpointDriverObservations {
pub frames_received_total: u64,
pub frames_delivered_total: u64,
pub frames_dropped_total: u64,
pub discontinuities_total: u64,
pub failures_total: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EndpointCancellationOutcome {
pub observations: EndpointDriverObservations,
pub result: Result<(), EndpointFailure>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EndpointDriverFinalization {
pub observations: EndpointDriverObservations,
pub result: Result<(), EndpointFailure>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EndpointFinalizationOutcome {
pub observations: EndpointDriverObservations,
pub request_stop_result: Result<(), EndpointFailure>,
pub join_finalize_result: Result<(), EndpointFailure>,
}
impl EndpointFinalizationOutcome {
pub fn is_success(&self) -> bool {
self.request_stop_result.is_ok() && self.join_finalize_result.is_ok()
}
}
pub trait PreparedEndpointDriver: Send {
fn start(
self: Box<Self>,
start_gate: Arc<EndpointStartGate>,
) -> Result<Box<dyn RunningEndpointDriver>, EndpointFailure>;
fn cancel_preparation(self: Box<Self>) -> EndpointCancellationOutcome;
}
pub trait RunningEndpointDriver: Send {
fn observations(&self) -> EndpointDriverObservations;
fn request_stop(&mut self) -> Result<(), EndpointFailure>;
fn join_and_finalize(self: Box<Self>) -> EndpointDriverFinalization;
}
pub struct EndpointStartGate {
open: AtomicBool,
}
impl EndpointStartGate {
pub fn is_open(&self) -> bool {
self.open.load(Ordering::Acquire)
}
}
pub struct EndpointStartGateController {
gate: Arc<EndpointStartGate>,
}
impl EndpointStartGateController {
pub fn open(&self) -> bool {
!self.gate.open.swap(true, Ordering::AcqRel)
}
}
pub fn endpoint_start_gate() -> (EndpointStartGateController, Arc<EndpointStartGate>) {
let gate = Arc::new(EndpointStartGate {
open: AtomicBool::new(false),
});
(
EndpointStartGateController {
gate: Arc::clone(&gate),
},
gate,
)
}
pub struct PreparedEndpoint {
pub(crate) driver: Box<dyn PreparedEndpointDriver>,
}
impl PreparedEndpoint {
pub fn cancel_preparation(self) -> EndpointCancellationOutcome {
self.driver.cancel_preparation()
}
pub fn start(
self,
start_gate: Arc<EndpointStartGate>,
) -> Result<RunningEndpoint, EndpointStartFailure> {
if start_gate.is_open() {
return Err(EndpointStartFailure {
cause: EndpointStartFailureCause::GateAlreadyOpen,
prepared: Some(self),
});
}
match self.driver.start(start_gate) {
Ok(driver) => Ok(RunningEndpoint {
driver,
request_stop_result: None,
}),
Err(failure) => Err(EndpointStartFailure {
cause: EndpointStartFailureCause::Driver(failure),
prepared: None,
}),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EndpointStartFailureCause {
GateAlreadyOpen,
Driver(EndpointFailure),
}
pub struct EndpointStartFailure {
cause: EndpointStartFailureCause,
prepared: Option<PreparedEndpoint>,
}
impl EndpointStartFailure {
pub const fn cause(&self) -> &EndpointStartFailureCause {
&self.cause
}
pub fn into_prepared(self) -> Option<PreparedEndpoint> {
self.prepared
}
}
impl fmt::Debug for EndpointStartFailure {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("EndpointStartFailure")
.field("cause", &self.cause)
.field("prepared_recoverable", &self.prepared.is_some())
.finish()
}
}
impl fmt::Display for EndpointStartFailure {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.cause {
EndpointStartFailureCause::GateAlreadyOpen => {
formatter.write_str("endpoint start gate was already open")
}
EndpointStartFailureCause::Driver(failure) => failure.fmt(formatter),
}
}
}
impl std::error::Error for EndpointStartFailure {}
pub struct RunningEndpoint {
driver: Box<dyn RunningEndpointDriver>,
request_stop_result: Option<Result<(), EndpointFailure>>,
}
impl RunningEndpoint {
pub fn observations(&self) -> EndpointDriverObservations {
self.driver.observations()
}
pub fn request_stop(&mut self) -> &Result<(), EndpointFailure> {
self.request_stop_result
.get_or_insert_with(|| self.driver.request_stop())
}
pub fn join_and_finalize(self) -> EndpointFinalizationOutcome {
let Self {
mut driver,
request_stop_result,
} = self;
let request_stop_result = request_stop_result.unwrap_or_else(|| driver.request_stop());
let finalization = driver.join_and_finalize();
EndpointFinalizationOutcome {
observations: finalization.observations,
request_stop_result,
join_finalize_result: finalization.result,
}
}
}
#[cfg(test)]
mod tests;