khora_core/lane/bus.rs
1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! `LaneBus` — typed read-only bus of [`Flow`] outputs, scoped to one tick.
16//!
17//! `Flow`s publish typed `View`s into the bus during the Substrate Pass; lanes
18//! consume them during the CLAD descent. Outside of lane execution the bus
19//! does not exist (it is constructed by the scheduler at frame start and
20//! dropped at frame end).
21//!
22//! # Visibility contract
23//!
24//! - [`LaneBus::publish`] is `pub(crate)` (or used through the scheduler / a
25//! `Flow` runner inside `khora-core`/`khora-control`). Lanes cannot publish.
26//! - [`LaneBus::get`] is the only method exposed to lane code.
27//!
28//! [`Flow`]: ../../../khora_data/flow/index.html
29
30use std::any::{Any, TypeId};
31use std::collections::HashMap;
32
33/// Read-only typed bus carrying [`Flow`] outputs to lanes for one tick.
34///
35/// Lanes access it through [`LaneContext::bus`](super::LaneContext::bus)
36/// and read views via [`LaneBus::get`]. The bus itself is constructed by
37/// the scheduler at the start of each frame and dropped at the end — its
38/// lifetime is strictly tick-scoped.
39///
40/// [`Flow`]: ../../../khora_data/flow/index.html
41pub struct LaneBus {
42 views: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
43}
44
45impl LaneBus {
46 /// Creates an empty bus.
47 pub fn new() -> Self {
48 Self {
49 views: HashMap::new(),
50 }
51 }
52
53 /// Publishes a typed view into the bus. Replaces any existing entry of
54 /// the same type. Used by `Flow` execution; not callable from lane code.
55 pub fn publish<V: Any + Send + Sync>(&mut self, view: V) {
56 self.views.insert(TypeId::of::<V>(), Box::new(view));
57 }
58
59 /// Returns a shared reference to a view by type, or `None` if no `Flow`
60 /// has published one this tick.
61 pub fn get<V: Any + Send + Sync>(&self) -> Option<&V> {
62 self.views
63 .get(&TypeId::of::<V>())
64 .and_then(|b| b.downcast_ref::<V>())
65 }
66
67 /// Reports whether a view of the given type is present.
68 pub fn contains<V: Any + Send + Sync>(&self) -> bool {
69 self.views.contains_key(&TypeId::of::<V>())
70 }
71
72 /// Number of views currently published.
73 pub fn len(&self) -> usize {
74 self.views.len()
75 }
76
77 /// Whether no views are published.
78 pub fn is_empty(&self) -> bool {
79 self.views.is_empty()
80 }
81
82 /// Clears all published views. Called by the scheduler between ticks
83 /// when a bus instance is reused rather than reallocated.
84 pub fn clear(&mut self) {
85 self.views.clear();
86 }
87}
88
89impl Default for LaneBus {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95impl std::fmt::Debug for LaneBus {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 f.debug_struct("LaneBus")
98 .field("views", &self.views.len())
99 .finish()
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[derive(Debug, PartialEq)]
108 struct TestView {
109 value: u32,
110 }
111
112 #[test]
113 fn publish_then_get_returns_same_value() {
114 let mut bus = LaneBus::new();
115 bus.publish(TestView { value: 42 });
116 assert_eq!(bus.get::<TestView>(), Some(&TestView { value: 42 }));
117 }
118
119 #[test]
120 fn get_missing_returns_none() {
121 let bus = LaneBus::new();
122 assert!(bus.get::<TestView>().is_none());
123 }
124
125 #[test]
126 fn publish_replaces_previous_entry() {
127 let mut bus = LaneBus::new();
128 bus.publish(TestView { value: 1 });
129 bus.publish(TestView { value: 2 });
130 assert_eq!(bus.get::<TestView>(), Some(&TestView { value: 2 }));
131 }
132
133 #[test]
134 fn contains_reports_presence() {
135 let mut bus = LaneBus::new();
136 assert!(!bus.contains::<TestView>());
137 bus.publish(TestView { value: 0 });
138 assert!(bus.contains::<TestView>());
139 }
140
141 #[test]
142 fn clear_removes_all_views() {
143 let mut bus = LaneBus::new();
144 bus.publish(TestView { value: 1 });
145 bus.publish(0u8);
146 bus.clear();
147 assert!(bus.is_empty());
148 }
149}