Skip to main content

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}