wyrd/runtime_impl/
outbox.rs1use crate::foundation::{Signal, SignalDomain, ONE, ZERO};
8
9use crate::runtime_impl::bind::Runtime;
10use crate::runtime_impl::error::HandleError;
11use crate::runtime_impl::handles::{CmdId, HostPathId, SenseId};
12
13#[derive(Copy, Clone, Debug)]
15pub struct SignalOutSample {
16 pub path: HostPathId,
18 pub value: Signal,
20}
21
22#[derive(Copy, Clone, Debug)]
25pub struct Emit {
26 pub cmd: CmdId,
28 pub payload: Signal,
30}
31
32pub struct Outbox<'a> {
38 pub(crate) signals: &'a [SignalOutSample],
39 pub(crate) emits: &'a [Emit],
40 pub(crate) dropped_emits: usize,
41}
42
43impl Outbox<'_> {
44 pub fn signals(&self) -> &[SignalOutSample] {
46 self.signals
47 }
48
49 pub fn emits(&self) -> &[Emit] {
51 self.emits
52 }
53
54 pub fn dropped_emits(&self) -> usize {
59 self.dropped_emits
60 }
61}
62
63pub struct PortWriter<'a> {
65 pub(crate) rt: &'a mut Runtime,
66}
67
68impl PortWriter<'_> {
69 #[inline]
75 pub fn set_sense(&mut self, id: SenseId, value: Signal) -> Result<(), HandleError> {
76 if id.owner != self.rt.owner {
77 return Err(HandleError::ForeignRuntime { handle: "sense" });
78 }
79 let i = usize::from(id.index);
80 let domain = match self.rt.knots.get(i).map(|k| &k.kind) {
81 Some(crate::foundation::KnotKind::SignalIn { domain }) => *domain,
82 _ => return Err(HandleError::InvalidSense { sense: id }),
83 };
84 if !domain_value_is_valid(domain, value) {
85 return Err(HandleError::DomainValue { sense: id, domain });
86 }
87 self.rt.sense_values[i] = value;
88 Ok(())
89 }
90}
91
92#[inline]
93pub(crate) fn domain_value_is_valid(domain: SignalDomain, value: Signal) -> bool {
94 match domain {
95 SignalDomain::Bool => value == ZERO || value == ONE,
96 SignalDomain::Level => {
97 #[cfg(feature = "signal-f32")]
98 {
99 value.is_finite()
100 }
101 #[cfg(feature = "signal-i32")]
102 {
103 true
104 }
105 }
106 SignalDomain::Count => {
107 #[cfg(feature = "signal-f32")]
108 {
109 value.is_finite()
110 && value >= i32::MIN as f32
111 && value < 2_147_483_648.0
112 && value == (value as i32) as f32
113 }
114 #[cfg(feature = "signal-i32")]
115 {
116 true
117 }
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::authoring::Weave;
126 use crate::foundation::{KnotKind, ONE};
127
128 use crate::runtime_impl::bind::{BindOpts, Runtime};
129
130 #[test]
131 fn domain_values_are_checked_at_the_host_boundary() {
132 assert!(domain_value_is_valid(SignalDomain::Bool, ZERO));
133 assert!(domain_value_is_valid(SignalDomain::Bool, ONE));
134 assert!(!domain_value_is_valid(
135 SignalDomain::Bool,
136 crate::foundation::from_count(2)
137 ));
138
139 #[cfg(feature = "signal-f32")]
140 {
141 assert!(domain_value_is_valid(SignalDomain::Level, 0.25));
142 assert!(!domain_value_is_valid(SignalDomain::Level, f32::NAN));
143 assert!(domain_value_is_valid(SignalDomain::Count, 42.0));
144 assert!(!domain_value_is_valid(SignalDomain::Count, 1.5));
145 assert!(!domain_value_is_valid(
146 SignalDomain::Count,
147 -2_147_483_904.0,
148 ));
149 assert!(!domain_value_is_valid(SignalDomain::Count, 2_147_483_648.0));
150 }
151
152 #[cfg(feature = "signal-i32")]
153 {
154 assert!(domain_value_is_valid(SignalDomain::Level, i32::MAX));
155 assert!(domain_value_is_valid(SignalDomain::Count, i32::MIN));
156 }
157 }
158
159 #[test]
160 fn set_sense_oob_returns_error_without_mutation() {
161 let mut b = Weave::builder("x").unwrap();
162 let _k_c = b
163 .knot("c", KnotKind::constant(ONE, SignalDomain::Bool))
164 .unwrap();
165 let weave = b.build().unwrap();
166 let mut rt = Runtime::bind(weave.clone(), BindOpts::default()).unwrap();
167 let mut other_builder = Weave::builder("other").unwrap();
168 let _sense = other_builder
169 .knot("sense", KnotKind::signal_in(SignalDomain::Bool))
170 .unwrap();
171 let other = Runtime::bind(other_builder.build().unwrap(), BindOpts::default()).unwrap();
172 let invalid = other.sense_id("sense").unwrap();
173 assert_eq!(
174 rt.port_writer().set_sense(invalid, ONE),
175 Err(HandleError::ForeignRuntime { handle: "sense" })
176 );
177 assert!(rt.outbox().signals().is_empty());
178 assert!(rt.outbox().emits().is_empty());
179 }
180
181 #[test]
182 fn set_sense_rejects_same_runtime_non_sense_and_domain_values() {
183 let mut b = Weave::builder("domain-errors").unwrap();
184 let _constant = b
185 .knot("constant", KnotKind::constant(ONE, SignalDomain::Bool))
186 .unwrap();
187 let _bool_sense = b
188 .knot("bool", KnotKind::signal_in(SignalDomain::Bool))
189 .unwrap();
190 let _count_sense = b
191 .knot("count", KnotKind::signal_in(SignalDomain::Count))
192 .unwrap();
193 let mut rt = Runtime::bind(b.build().unwrap(), BindOpts::default()).unwrap();
194
195 let constant_as_sense = SenseId::new(rt.owner, 0);
197 assert_eq!(
198 rt.port_writer().set_sense(constant_as_sense, ONE),
199 Err(HandleError::InvalidSense {
200 sense: constant_as_sense
201 })
202 );
203
204 let bool_id = SenseId::new(rt.owner, 1);
205 assert_eq!(
206 rt.port_writer()
207 .set_sense(bool_id, crate::foundation::from_count(2)),
208 Err(HandleError::DomainValue {
209 sense: bool_id,
210 domain: SignalDomain::Bool,
211 })
212 );
213
214 #[cfg(feature = "signal-f32")]
215 {
216 let count_id = SenseId::new(rt.owner, 2);
217 assert_eq!(
218 rt.port_writer().set_sense(count_id, 1.5),
219 Err(HandleError::DomainValue {
220 sense: count_id,
221 domain: SignalDomain::Count,
222 })
223 );
224 }
225
226 #[cfg(feature = "signal-i32")]
227 {
228 let count_id = SenseId::new(rt.owner, 2);
229 rt.port_writer().set_sense(count_id, 1).unwrap();
230 }
231 }
232}