use crate::foundation::{Signal, SignalDomain, ONE, ZERO};
use crate::runtime_impl::bind::Runtime;
use crate::runtime_impl::error::HandleError;
use crate::runtime_impl::handles::{CmdId, HostPathId, SenseId};
#[derive(Copy, Clone, Debug)]
pub struct SignalOutSample {
pub path: HostPathId,
pub value: Signal,
}
#[derive(Copy, Clone, Debug)]
pub struct Emit {
pub cmd: CmdId,
pub payload: Signal,
}
pub struct Outbox<'a> {
pub(crate) signals: &'a [SignalOutSample],
pub(crate) emits: &'a [Emit],
pub(crate) dropped_emits: usize,
}
impl Outbox<'_> {
pub fn signals(&self) -> &[SignalOutSample] {
self.signals
}
pub fn emits(&self) -> &[Emit] {
self.emits
}
pub fn dropped_emits(&self) -> usize {
self.dropped_emits
}
}
pub struct PortWriter<'a> {
pub(crate) rt: &'a mut Runtime,
}
impl PortWriter<'_> {
#[inline]
pub fn set_sense(&mut self, id: SenseId, value: Signal) -> Result<(), HandleError> {
if id.owner != self.rt.owner {
return Err(HandleError::ForeignRuntime { handle: "sense" });
}
let i = usize::from(id.index);
let domain = match self.rt.knots.get(i).map(|k| &k.kind) {
Some(crate::foundation::KnotKind::SignalIn { domain }) => *domain,
_ => return Err(HandleError::InvalidSense { sense: id }),
};
if !domain_value_is_valid(domain, value) {
return Err(HandleError::DomainValue { sense: id, domain });
}
self.rt.sense_values[i] = value;
Ok(())
}
}
#[inline]
pub(crate) fn domain_value_is_valid(domain: SignalDomain, value: Signal) -> bool {
match domain {
SignalDomain::Bool => value == ZERO || value == ONE,
SignalDomain::Level => {
#[cfg(feature = "signal-f32")]
{
value.is_finite()
}
#[cfg(feature = "signal-i32")]
{
true
}
}
SignalDomain::Count => {
#[cfg(feature = "signal-f32")]
{
value.is_finite()
&& value >= i32::MIN as f32
&& value < 2_147_483_648.0
&& value == (value as i32) as f32
}
#[cfg(feature = "signal-i32")]
{
true
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::authoring::Weave;
use crate::foundation::{KnotKind, ONE};
use crate::runtime_impl::bind::{BindOpts, Runtime};
#[test]
fn domain_values_are_checked_at_the_host_boundary() {
assert!(domain_value_is_valid(SignalDomain::Bool, ZERO));
assert!(domain_value_is_valid(SignalDomain::Bool, ONE));
assert!(!domain_value_is_valid(
SignalDomain::Bool,
crate::foundation::from_count(2)
));
#[cfg(feature = "signal-f32")]
{
assert!(domain_value_is_valid(SignalDomain::Level, 0.25));
assert!(!domain_value_is_valid(SignalDomain::Level, f32::NAN));
assert!(domain_value_is_valid(SignalDomain::Count, 42.0));
assert!(!domain_value_is_valid(SignalDomain::Count, 1.5));
assert!(!domain_value_is_valid(
SignalDomain::Count,
-2_147_483_904.0,
));
assert!(!domain_value_is_valid(SignalDomain::Count, 2_147_483_648.0));
}
#[cfg(feature = "signal-i32")]
{
assert!(domain_value_is_valid(SignalDomain::Level, i32::MAX));
assert!(domain_value_is_valid(SignalDomain::Count, i32::MIN));
}
}
#[test]
fn set_sense_oob_returns_error_without_mutation() {
let mut b = Weave::builder("x").unwrap();
let _k_c = b
.knot("c", KnotKind::constant(ONE, SignalDomain::Bool))
.unwrap();
let weave = b.build().unwrap();
let mut rt = Runtime::bind(weave.clone(), BindOpts::default()).unwrap();
let mut other_builder = Weave::builder("other").unwrap();
let _sense = other_builder
.knot("sense", KnotKind::signal_in(SignalDomain::Bool))
.unwrap();
let other = Runtime::bind(other_builder.build().unwrap(), BindOpts::default()).unwrap();
let invalid = other.sense_id("sense").unwrap();
assert_eq!(
rt.port_writer().set_sense(invalid, ONE),
Err(HandleError::ForeignRuntime { handle: "sense" })
);
assert!(rt.outbox().signals().is_empty());
assert!(rt.outbox().emits().is_empty());
}
#[test]
fn set_sense_rejects_same_runtime_non_sense_and_domain_values() {
let mut b = Weave::builder("domain-errors").unwrap();
let _constant = b
.knot("constant", KnotKind::constant(ONE, SignalDomain::Bool))
.unwrap();
let _bool_sense = b
.knot("bool", KnotKind::signal_in(SignalDomain::Bool))
.unwrap();
let _count_sense = b
.knot("count", KnotKind::signal_in(SignalDomain::Count))
.unwrap();
let mut rt = Runtime::bind(b.build().unwrap(), BindOpts::default()).unwrap();
let constant_as_sense = SenseId::new(rt.owner, 0);
assert_eq!(
rt.port_writer().set_sense(constant_as_sense, ONE),
Err(HandleError::InvalidSense {
sense: constant_as_sense
})
);
let bool_id = SenseId::new(rt.owner, 1);
assert_eq!(
rt.port_writer()
.set_sense(bool_id, crate::foundation::from_count(2)),
Err(HandleError::DomainValue {
sense: bool_id,
domain: SignalDomain::Bool,
})
);
#[cfg(feature = "signal-f32")]
{
let count_id = SenseId::new(rt.owner, 2);
assert_eq!(
rt.port_writer().set_sense(count_id, 1.5),
Err(HandleError::DomainValue {
sense: count_id,
domain: SignalDomain::Count,
})
);
}
#[cfg(feature = "signal-i32")]
{
let count_id = SenseId::new(rt.owner, 2);
rt.port_writer().set_sense(count_id, 1).unwrap();
}
}
}