use std::collections::BTreeMap;
use sim_kernel::{Error, Export, Result, Symbol};
use sim_lib_stream_core::{
BridgeLatency, ClockDomain, DomainBridgeDescriptor, DomainBridgeKind, LatencyClass,
RateContract,
};
use crate::{EdgeId, NodeId};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SiteId(Symbol);
impl SiteId {
pub fn new(name: impl Into<String>) -> Self {
Self(Symbol::new(name.into()))
}
pub fn from_symbol(symbol: Symbol) -> Self {
Self(symbol)
}
pub fn as_symbol(&self) -> &Symbol {
&self.0
}
}
impl From<&str> for SiteId {
fn from(value: &str) -> Self {
Self::new(value)
}
}
impl From<String> for SiteId {
fn from(value: String) -> Self {
Self::new(value)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SiteProfile {
id: SiteId,
export: Export,
latency_classes: Vec<LatencyClass>,
clock_domains: Vec<ClockDomain>,
audio_clock: bool,
stream_ports: bool,
}
impl SiteProfile {
pub fn new(
id: impl Into<SiteId>,
latency_classes: Vec<LatencyClass>,
audio_clock: bool,
) -> Self {
let id = id.into();
let export = Export::Site {
symbol: id.as_symbol().clone(),
runtime_id: None,
};
Self {
id,
export,
clock_domains: default_clock_domains(audio_clock),
latency_classes,
audio_clock,
stream_ports: true,
}
}
pub fn from_site_export(
export: Export,
latency_classes: Vec<LatencyClass>,
audio_clock: bool,
) -> Result<Self> {
let Export::Site { symbol, .. } = &export else {
return Err(Error::Eval(
"topology placement site requires a kernel site export".to_owned(),
));
};
Ok(Self {
id: SiteId::from_symbol(symbol.clone()),
export,
clock_domains: default_clock_domains(audio_clock),
latency_classes,
audio_clock,
stream_ports: true,
})
}
pub fn audio_clock(id: impl Into<SiteId>) -> Self {
Self::new(
id,
vec![
LatencyClass::SampleExact,
LatencyClass::BlockLocal,
LatencyClass::Interactive,
LatencyClass::OfflineRender,
],
true,
)
}
pub fn local_worker(id: impl Into<SiteId>) -> Self {
Self::new(
id,
vec![
LatencyClass::BlockLocal,
LatencyClass::Interactive,
LatencyClass::BufferedPreview,
LatencyClass::OfflineRender,
],
false,
)
}
pub fn buffered_remote(id: impl Into<SiteId>) -> Self {
Self::new(
id,
vec![
LatencyClass::BufferedPreview,
LatencyClass::CollabBarDelay,
LatencyClass::RemoteCollaboration,
LatencyClass::OfflineRender,
],
false,
)
}
pub fn id(&self) -> &SiteId {
&self.id
}
pub fn site_export(&self) -> &Export {
&self.export
}
pub fn supports_latency_class(&self, latency_class: LatencyClass) -> bool {
self.latency_classes.contains(&latency_class)
}
pub fn with_clock_domains(mut self, clock_domains: Vec<ClockDomain>) -> Self {
self.clock_domains = clock_domains;
self
}
pub fn supports_clock_domain(&self, clock_domain: ClockDomain) -> bool {
self.clock_domains.contains(&clock_domain)
}
pub fn with_stream_ports(mut self, stream_ports: bool) -> Self {
self.stream_ports = stream_ports;
self
}
pub fn supports_stream_ports(&self) -> bool {
self.stream_ports
}
pub fn is_audio_clock(&self) -> bool {
self.audio_clock
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PlacementNodeProfile {
rate_contract: RateContract,
realtime_pin: bool,
latency: BridgeLatency,
}
impl PlacementNodeProfile {
pub fn new(rate_contract: RateContract, realtime_pin: bool) -> Self {
Self {
rate_contract,
realtime_pin,
latency: BridgeLatency::zero(),
}
}
pub fn sample_exact(nominal_rate_hz: Option<u32>, realtime_pin: bool) -> Self {
Self::new(RateContract::sample_exact(nominal_rate_hz), realtime_pin)
}
pub fn block_local() -> Self {
Self::new(RateContract::block_local(), false)
}
pub fn control() -> Self {
Self::new(RateContract::control(), false)
}
pub fn with_latency(mut self, latency: BridgeLatency) -> Self {
self.latency = latency;
self
}
pub fn rate_contract(&self) -> RateContract {
self.rate_contract
}
pub fn realtime_pin(&self) -> bool {
self.realtime_pin
}
pub fn latency(&self) -> BridgeLatency {
self.latency
}
}
impl Default for PlacementNodeProfile {
fn default() -> Self {
Self::block_local()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SiteMap {
default_site: SiteId,
sites: BTreeMap<SiteId, SiteProfile>,
assignments: BTreeMap<NodeId, SiteId>,
node_profiles: BTreeMap<NodeId, PlacementNodeProfile>,
}
impl SiteMap {
pub fn new(default_site: SiteProfile) -> Self {
let default_site_id = default_site.id().clone();
let mut sites = BTreeMap::new();
sites.insert(default_site_id.clone(), default_site);
Self {
default_site: default_site_id,
sites,
assignments: BTreeMap::new(),
node_profiles: BTreeMap::new(),
}
}
pub fn with_site(mut self, site: SiteProfile) -> Self {
self.sites.insert(site.id().clone(), site);
self
}
pub fn assign_node(mut self, node: impl Into<NodeId>, site: impl Into<SiteId>) -> Self {
self.assignments.insert(node.into(), site.into());
self
}
pub fn with_node_profile(
mut self,
node: impl Into<NodeId>,
profile: PlacementNodeProfile,
) -> Self {
self.node_profiles.insert(node.into(), profile);
self
}
pub fn site_for(&self, node: &NodeId) -> &SiteId {
self.assignments.get(node).unwrap_or(&self.default_site)
}
pub fn profile_for(&self, node: &NodeId) -> PlacementNodeProfile {
self.node_profiles.get(node).cloned().unwrap_or_default()
}
pub fn site_profile(&self, site: &SiteId) -> Option<&SiteProfile> {
self.sites.get(site)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PlacementReport {
pub placed: Vec<PlacedNode>,
pub bridges: Vec<DomainBridge>,
pub latency: Vec<PortLatency>,
pub refusals: Vec<PlacementRefusal>,
}
impl PlacementReport {
pub fn is_accepted(&self) -> bool {
self.refusals.is_empty()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PlacedNode {
pub node: NodeId,
pub site: SiteId,
pub clock_domain: ClockDomain,
pub latency_class: LatencyClass,
pub realtime_pin: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DomainBridge {
pub edge: EdgeId,
pub from: NodeId,
pub to: NodeId,
pub from_site: SiteId,
pub to_site: SiteId,
pub descriptor: DomainBridgeDescriptor,
}
impl DomainBridge {
pub fn kind(&self) -> DomainBridgeKind {
self.descriptor.kind()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PortLatency {
pub node: NodeId,
pub site: SiteId,
pub latency: BridgeLatency,
pub latency_class: LatencyClass,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PlacementRefusal {
pub node: NodeId,
pub site: SiteId,
pub reason: PlacementRefusalReason,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PlacementRefusalReason {
UnknownSite,
RealtimePinViolation,
UnsupportedLatencyClass,
UnsupportedClockDomain {
domain: ClockDomain,
},
UnsupportedStreamPorts,
IncomparableClockDomain {
from: ClockDomain,
to: ClockDomain,
},
}
fn default_clock_domains(audio_clock: bool) -> Vec<ClockDomain> {
let mut domains = vec![
ClockDomain::Block,
ClockDomain::Control,
ClockDomain::MidiTick,
ClockDomain::Wall,
ClockDomain::Job,
];
if audio_clock {
domains.insert(0, ClockDomain::Sample);
}
domains
}