1use std::collections::BTreeMap;
2
3use sim_kernel::{Error, Export, Result, Symbol};
4use sim_lib_stream_core::{
5 BridgeLatency, ClockDomain, DomainBridgeDescriptor, DomainBridgeKind, LatencyClass,
6 RateContract,
7};
8
9use crate::{EdgeId, NodeId};
10
11#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct SiteId(Symbol);
14
15impl SiteId {
16 pub fn new(name: impl Into<String>) -> Self {
18 Self(Symbol::new(name.into()))
19 }
20
21 pub fn from_symbol(symbol: Symbol) -> Self {
23 Self(symbol)
24 }
25
26 pub fn as_symbol(&self) -> &Symbol {
28 &self.0
29 }
30}
31
32impl From<&str> for SiteId {
33 fn from(value: &str) -> Self {
34 Self::new(value)
35 }
36}
37
38impl From<String> for SiteId {
39 fn from(value: String) -> Self {
40 Self::new(value)
41 }
42}
43
44#[derive(Clone, Debug, PartialEq, Eq)]
47pub struct SiteProfile {
48 id: SiteId,
49 export: Export,
50 latency_classes: Vec<LatencyClass>,
51 clock_domains: Vec<ClockDomain>,
52 audio_clock: bool,
53 stream_ports: bool,
54}
55
56impl SiteProfile {
57 pub fn new(
59 id: impl Into<SiteId>,
60 latency_classes: Vec<LatencyClass>,
61 audio_clock: bool,
62 ) -> Self {
63 let id = id.into();
64 let export = Export::Site {
65 symbol: id.as_symbol().clone(),
66 runtime_id: None,
67 };
68 Self {
69 id,
70 export,
71 clock_domains: default_clock_domains(audio_clock),
72 latency_classes,
73 audio_clock,
74 stream_ports: true,
75 }
76 }
77
78 pub fn from_site_export(
81 export: Export,
82 latency_classes: Vec<LatencyClass>,
83 audio_clock: bool,
84 ) -> Result<Self> {
85 let Export::Site { symbol, .. } = &export else {
86 return Err(Error::Eval(
87 "topology placement site requires a kernel site export".to_owned(),
88 ));
89 };
90 Ok(Self {
91 id: SiteId::from_symbol(symbol.clone()),
92 export,
93 clock_domains: default_clock_domains(audio_clock),
94 latency_classes,
95 audio_clock,
96 stream_ports: true,
97 })
98 }
99
100 pub fn audio_clock(id: impl Into<SiteId>) -> Self {
103 Self::new(
104 id,
105 vec![
106 LatencyClass::SampleExact,
107 LatencyClass::BlockLocal,
108 LatencyClass::Interactive,
109 LatencyClass::OfflineRender,
110 ],
111 true,
112 )
113 }
114
115 pub fn local_worker(id: impl Into<SiteId>) -> Self {
117 Self::new(
118 id,
119 vec![
120 LatencyClass::BlockLocal,
121 LatencyClass::Interactive,
122 LatencyClass::BufferedPreview,
123 LatencyClass::OfflineRender,
124 ],
125 false,
126 )
127 }
128
129 pub fn buffered_remote(id: impl Into<SiteId>) -> Self {
132 Self::new(
133 id,
134 vec![
135 LatencyClass::BufferedPreview,
136 LatencyClass::CollabBarDelay,
137 LatencyClass::RemoteCollaboration,
138 LatencyClass::OfflineRender,
139 ],
140 false,
141 )
142 }
143
144 pub fn id(&self) -> &SiteId {
146 &self.id
147 }
148
149 pub fn site_export(&self) -> &Export {
151 &self.export
152 }
153
154 pub fn supports_latency_class(&self, latency_class: LatencyClass) -> bool {
156 self.latency_classes.contains(&latency_class)
157 }
158
159 pub fn with_clock_domains(mut self, clock_domains: Vec<ClockDomain>) -> Self {
161 self.clock_domains = clock_domains;
162 self
163 }
164
165 pub fn supports_clock_domain(&self, clock_domain: ClockDomain) -> bool {
167 self.clock_domains.contains(&clock_domain)
168 }
169
170 pub fn with_stream_ports(mut self, stream_ports: bool) -> Self {
172 self.stream_ports = stream_ports;
173 self
174 }
175
176 pub fn supports_stream_ports(&self) -> bool {
178 self.stream_ports
179 }
180
181 pub fn is_audio_clock(&self) -> bool {
183 self.audio_clock
184 }
185}
186
187#[derive(Clone, Debug, PartialEq, Eq)]
190pub struct PlacementNodeProfile {
191 rate_contract: RateContract,
192 realtime_pin: bool,
193 latency: BridgeLatency,
194}
195
196impl PlacementNodeProfile {
197 pub fn new(rate_contract: RateContract, realtime_pin: bool) -> Self {
199 Self {
200 rate_contract,
201 realtime_pin,
202 latency: BridgeLatency::zero(),
203 }
204 }
205
206 pub fn sample_exact(nominal_rate_hz: Option<u32>, realtime_pin: bool) -> Self {
208 Self::new(RateContract::sample_exact(nominal_rate_hz), realtime_pin)
209 }
210
211 pub fn block_local() -> Self {
213 Self::new(RateContract::block_local(), false)
214 }
215
216 pub fn control() -> Self {
218 Self::new(RateContract::control(), false)
219 }
220
221 pub fn with_latency(mut self, latency: BridgeLatency) -> Self {
223 self.latency = latency;
224 self
225 }
226
227 pub fn rate_contract(&self) -> RateContract {
229 self.rate_contract
230 }
231
232 pub fn realtime_pin(&self) -> bool {
234 self.realtime_pin
235 }
236
237 pub fn latency(&self) -> BridgeLatency {
239 self.latency
240 }
241}
242
243impl Default for PlacementNodeProfile {
244 fn default() -> Self {
245 Self::block_local()
246 }
247}
248
249#[derive(Clone, Debug, PartialEq, Eq)]
252pub struct SiteMap {
253 default_site: SiteId,
254 sites: BTreeMap<SiteId, SiteProfile>,
255 assignments: BTreeMap<NodeId, SiteId>,
256 node_profiles: BTreeMap<NodeId, PlacementNodeProfile>,
257}
258
259impl SiteMap {
260 pub fn new(default_site: SiteProfile) -> Self {
262 let default_site_id = default_site.id().clone();
263 let mut sites = BTreeMap::new();
264 sites.insert(default_site_id.clone(), default_site);
265 Self {
266 default_site: default_site_id,
267 sites,
268 assignments: BTreeMap::new(),
269 node_profiles: BTreeMap::new(),
270 }
271 }
272
273 pub fn with_site(mut self, site: SiteProfile) -> Self {
275 self.sites.insert(site.id().clone(), site);
276 self
277 }
278
279 pub fn assign_node(mut self, node: impl Into<NodeId>, site: impl Into<SiteId>) -> Self {
281 self.assignments.insert(node.into(), site.into());
282 self
283 }
284
285 pub fn with_node_profile(
287 mut self,
288 node: impl Into<NodeId>,
289 profile: PlacementNodeProfile,
290 ) -> Self {
291 self.node_profiles.insert(node.into(), profile);
292 self
293 }
294
295 pub fn site_for(&self, node: &NodeId) -> &SiteId {
297 self.assignments.get(node).unwrap_or(&self.default_site)
298 }
299
300 pub fn profile_for(&self, node: &NodeId) -> PlacementNodeProfile {
302 self.node_profiles.get(node).cloned().unwrap_or_default()
303 }
304
305 pub fn site_profile(&self, site: &SiteId) -> Option<&SiteProfile> {
307 self.sites.get(site)
308 }
309}
310
311#[derive(Clone, Debug, PartialEq, Eq)]
314pub struct PlacementReport {
315 pub placed: Vec<PlacedNode>,
317 pub bridges: Vec<DomainBridge>,
319 pub latency: Vec<PortLatency>,
321 pub refusals: Vec<PlacementRefusal>,
323}
324
325impl PlacementReport {
326 pub fn is_accepted(&self) -> bool {
328 self.refusals.is_empty()
329 }
330}
331
332#[derive(Clone, Debug, PartialEq, Eq)]
334pub struct PlacedNode {
335 pub node: NodeId,
337 pub site: SiteId,
339 pub clock_domain: ClockDomain,
341 pub latency_class: LatencyClass,
343 pub realtime_pin: bool,
345}
346
347#[derive(Clone, Debug, PartialEq, Eq)]
349pub struct DomainBridge {
350 pub edge: EdgeId,
352 pub from: NodeId,
354 pub to: NodeId,
356 pub from_site: SiteId,
358 pub to_site: SiteId,
360 pub descriptor: DomainBridgeDescriptor,
362}
363
364impl DomainBridge {
365 pub fn kind(&self) -> DomainBridgeKind {
367 self.descriptor.kind()
368 }
369}
370
371#[derive(Clone, Debug, PartialEq, Eq)]
373pub struct PortLatency {
374 pub node: NodeId,
376 pub site: SiteId,
378 pub latency: BridgeLatency,
380 pub latency_class: LatencyClass,
382}
383
384#[derive(Clone, Debug, PartialEq, Eq)]
386pub struct PlacementRefusal {
387 pub node: NodeId,
389 pub site: SiteId,
391 pub reason: PlacementRefusalReason,
393}
394
395#[derive(Clone, Debug, PartialEq, Eq)]
397pub enum PlacementRefusalReason {
398 UnknownSite,
400 RealtimePinViolation,
402 UnsupportedLatencyClass,
404 UnsupportedClockDomain {
406 domain: ClockDomain,
408 },
409 UnsupportedStreamPorts,
411 IncomparableClockDomain {
413 from: ClockDomain,
415 to: ClockDomain,
417 },
418}
419
420fn default_clock_domains(audio_clock: bool) -> Vec<ClockDomain> {
421 let mut domains = vec![
422 ClockDomain::Block,
423 ClockDomain::Control,
424 ClockDomain::MidiTick,
425 ClockDomain::Wall,
426 ClockDomain::Job,
427 ];
428 if audio_clock {
429 domains.insert(0, ClockDomain::Sample);
430 }
431 domains
432}