1use std::collections::{BTreeMap, BTreeSet};
9use std::time::Instant;
10
11use super::error::ConsoleError;
12use super::records::{
13 Connection, Device, DeviceBox, DeviceKind, Line, LineKind, LineLife, LineState, Link, Mount,
14 Station, StationActivity, StationKind, StationState, TopologyDocument,
15};
16use super::wall::WallBase;
17use crate::server::connection::is_console_subject;
18
19#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum TopologyChange {
22 Service {
24 subject: String,
26 present: bool,
28 },
29 Channel {
31 name: String,
33 life: LineLife,
35 state: LineState,
37 },
38 ConnectionOpened {
40 id: u64,
42 mount: Mount,
44 participant: Option<String>,
46 },
47 ConnectionClosed {
49 id: u64,
51 },
52 Worker {
55 connection: u64,
57 identity: String,
59 subjects: Vec<String>,
61 activities: Vec<StationActivity>,
63 },
64 Flow {
66 connection: u64,
68 line: String,
70 role: FlowRole,
72 present: bool,
74 },
75 Peer {
77 name: String,
79 present: bool,
81 },
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
88pub enum FlowRole {
89 Publishes,
91 Subscribes,
93}
94
95#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct ThisDevice {
98 pub id: String,
100 pub liminal: String,
102 pub started: Instant,
104 pub depot: bool,
107}
108
109#[derive(Clone, Debug)]
111pub struct TopologyModel {
112 this: ThisDevice,
113 rev: u64,
114 stations: BTreeSet<String>,
115 lines: BTreeMap<String, (LineLife, LineState)>,
116 connections: BTreeMap<u64, (Mount, Option<String>)>,
117 peers: BTreeSet<String>,
118 workers: BTreeMap<u64, WorkerStation>,
120 flows: BTreeMap<String, BTreeSet<(FlowRole, u64)>>,
122}
123
124#[derive(Clone, Debug, PartialEq, Eq)]
126struct WorkerStation {
127 identity: String,
128 subjects: Vec<String>,
129 activities: Vec<StationActivity>,
130}
131
132impl TopologyModel {
133 #[must_use]
136 pub fn new(
137 this: ThisDevice,
138 stations: impl IntoIterator<Item = String>,
139 lines: impl IntoIterator<Item = (String, LineLife, LineState)>,
140 connections: impl IntoIterator<Item = (u64, Mount, Option<String>)>,
141 peers: impl IntoIterator<Item = String>,
142 ) -> Self {
143 Self {
144 this,
145 rev: 1,
146 stations: stations.into_iter().collect(),
147 lines: lines
148 .into_iter()
149 .filter(|(name, _, _)| !is_console_subject(name))
150 .map(|(name, life, state)| (name, (life, state)))
151 .collect(),
152 connections: connections
153 .into_iter()
154 .map(|(id, mount, participant)| (id, (mount, participant)))
155 .collect(),
156 peers: peers.into_iter().collect(),
157 workers: BTreeMap::new(),
158 flows: BTreeMap::new(),
159 }
160 }
161
162 #[must_use]
164 pub const fn depot(&self) -> bool {
165 self.this.depot
166 }
167
168 #[must_use]
170 pub const fn rev(&self) -> u64 {
171 self.rev
172 }
173
174 pub fn apply(&mut self, change: TopologyChange) -> bool {
179 let changed = match change {
180 TopologyChange::Service { subject, present } => {
181 if present {
182 self.stations.insert(subject)
183 } else {
184 self.stations.remove(&subject)
185 }
186 }
187 TopologyChange::Channel { name, life, state } => {
188 !is_console_subject(&name)
189 && self.lines.insert(name, (life, state)) != Some((life, state))
190 }
191 TopologyChange::ConnectionOpened {
192 id,
193 mount,
194 participant,
195 } => {
196 let entry = (mount, participant);
197 if self.connections.get(&id) == Some(&entry) {
198 false
199 } else {
200 self.connections.insert(id, entry);
201 true
202 }
203 }
204 TopologyChange::ConnectionClosed { id } => {
205 let closed = self.connections.remove(&id).is_some();
206 let worker = self.workers.remove(&id).is_some();
207 for users in self.flows.values_mut() {
208 users.retain(|(_, connection)| *connection != id);
209 }
210 self.flows.retain(|_, users| !users.is_empty());
211 closed || worker
212 }
213 TopologyChange::Worker {
214 connection,
215 identity,
216 subjects,
217 activities,
218 } => {
219 let worker = WorkerStation {
220 identity,
221 subjects,
222 activities,
223 };
224 self.workers.insert(connection, worker.clone()) != Some(worker)
225 }
226 TopologyChange::Flow {
227 connection,
228 line,
229 role,
230 present,
231 } => {
232 if is_console_subject(&line) {
233 false
234 } else {
235 let moved = if present {
236 self.flows
237 .entry(line)
238 .or_default()
239 .insert((role, connection))
240 } else if let Some(users) = self.flows.get_mut(&line) {
241 let removed = users.remove(&(role, connection));
242 if users.is_empty() {
243 self.flows.remove(&line);
244 }
245 removed
246 } else {
247 false
248 };
249 moved && self.workers.contains_key(&connection)
250 }
251 }
252 TopologyChange::Peer { name, present } => {
253 if present {
254 self.peers.insert(name)
255 } else {
256 self.peers.remove(&name)
257 }
258 }
259 };
260 if changed {
261 self.rev = self.rev.saturating_add(1);
262 }
263 changed
264 }
265
266 fn stops(&self, line: &str, line_life: LineLife) -> Vec<String> {
272 let mut stops: Vec<String> = Vec::new();
273 let depot = (self.this.depot && line_life == LineLife::Kept).then(|| self.depot_id());
274 let mut own = depot.into_iter().chain([self.this.id.clone()]);
276 let users = self.flows.get(line).into_iter().flatten();
277 for (role, connection) in users {
278 if *role == FlowRole::Subscribes {
279 stops.extend(own.by_ref());
280 }
281 if let Some(identity) = self.workers.get(connection).map(|worker| &worker.identity)
282 && self.reserved(identity).is_none()
283 && !stops.contains(identity)
284 {
285 stops.push(identity.clone());
286 }
287 }
288 stops.extend(own);
289 stops
290 }
291
292 #[must_use]
297 pub fn reserved(&self, id: &str) -> Option<&'static str> {
298 if id == self.this.id {
299 Some("exchange")
300 } else if self.this.depot
301 && id
302 .strip_prefix(self.this.id.as_str())
303 .is_some_and(|rest| rest == ".depot")
304 {
305 Some("depot")
306 } else {
307 None
308 }
309 }
310
311 fn depot_id(&self) -> String {
313 format!("{}.depot", self.this.id)
314 }
315
316 pub fn incarnation_started(&mut self, started: Instant) -> bool {
319 let moved = self.this.started != started;
320 self.this.started = started;
321 moved
322 }
323
324 #[cfg(test)]
326 pub(crate) fn participants(&self) -> Vec<Option<String>> {
327 self.connections
328 .values()
329 .map(|(_, participant)| participant.clone())
330 .collect()
331 }
332
333 #[cfg(test)]
335 pub(crate) fn connection_count(&self) -> usize {
336 self.connections.len()
337 }
338
339 #[must_use]
347 pub fn document(&self, wall: &WallBase) -> (TopologyDocument, Option<ConsoleError>) {
348 let this = &self.this.id;
349 let (started, fault) = match wall.unix_ms_of(self.this.started) {
350 Ok(started) => (Some(started), None),
351 Err(error) => (None, Some(error)),
352 };
353 let mut devices = vec![Device {
354 id: this.clone(),
355 name: this.clone(),
356 kind: DeviceKind::Device,
357 liminal: Some(self.this.liminal.clone()),
358 started,
359 started_fault: fault.as_ref().map(ToString::to_string),
360 zone: Some(DeviceBox::THIS),
361 }];
362 devices.extend(self.peers.iter().map(|peer| Device {
363 id: peer.clone(),
364 name: peer.clone(),
365 kind: DeviceKind::Device,
366 liminal: None,
367 started: None,
368 started_fault: None,
369 zone: None,
370 }));
371 let depot = self.this.depot.then(|| self.depot_id());
375 let mut stations: BTreeMap<&String, (StationKind, Option<&WorkerStation>)> = self
376 .workers
377 .values()
378 .filter(|worker| self.reserved(&worker.identity).is_none())
379 .map(|worker| (&worker.identity, (StationKind::Worker, Some(worker))))
380 .collect();
381 stations.extend(
382 self.stations
383 .iter()
384 .filter(|subject| self.reserved(subject).is_none())
385 .map(|subject| (subject, (StationKind::Service, None))),
386 );
387 stations.extend(depot.iter().map(|id| (id, (StationKind::Depot, None))));
388 stations.insert(this, (StationKind::Exchange, None));
389 let document = TopologyDocument::new(
390 self.rev,
391 this.clone(),
392 devices,
393 stations
394 .into_iter()
395 .map(|(subject, (kind, worker))| Station {
396 id: subject.clone(),
397 name: subject.clone(),
398 device: this.clone(),
399 kind,
400 state: StationState::Live,
401 subjects: worker.map(|worker| worker.subjects.clone()),
402 activities: worker.map(|worker| worker.activities.clone()),
403 })
404 .collect(),
405 self.lines
406 .iter()
407 .map(|(name, (life, state))| Line {
408 id: name.clone(),
409 kind: LineKind::Stream,
410 life: *life,
411 state: *state,
412 stops: self.stops(name, *life),
413 })
414 .collect(),
415 self.peers
416 .iter()
417 .map(|peer| Link::to_peer(this, peer))
418 .collect(),
419 self.connections
420 .iter()
421 .map(|(id, (mount, participant))| Connection {
422 id: id.to_string(),
423 mount: *mount,
424 participant: participant.clone(),
425 })
426 .collect(),
427 );
428 (document, fault)
429 }
430}