use std::borrow::Cow;
use crate::chip::Placed;
use crate::link::{LARGEST_FRAME, Wire, WireError, checked_width};
use crate::machine::Board;
use crate::time::VirtualNanos;
pub trait Edge {
type Signals;
type Outputs;
type Conditions;
const FIELDS: &'static [Field];
const OUTPUT_FIELDS: &'static [Field];
const CONDITIONS: &'static [Field] = &[];
const REST: Self::Signals;
const CHIPS: &'static [&'static str] = &[];
fn place(&mut self, chip: usize) -> Option<&mut dyn Placed> {
let _ = chip;
None
}
fn sense(&mut self, signals: &Self::Signals);
fn condition(&mut self, conditions: &Self::Conditions);
fn draw(&mut self, _seed: u64, _index: u64) {}
fn drive(&mut self) -> Self::Outputs;
}
pub trait Coupled: Board {
fn fields(&self) -> &'static [Field];
fn output_fields(&self) -> &'static [Field];
fn rest_wire(&self) -> Vec<u8>;
fn sense_wire(&mut self, bytes: &[u8]) -> Result<(), WireError>;
fn conditions(&self) -> &'static [Field];
fn condition_wire(&mut self, bytes: &[u8]) -> Result<(), WireError>;
fn draw(&mut self, seed: u64, index: u64);
fn drive_wire(&mut self) -> Vec<u8>;
fn chips(&self) -> &'static [&'static str];
fn place(&mut self, chip: usize) -> Option<&mut dyn Placed>;
fn check(&mut self) -> Result<(), Mismatch>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Declaration {
Fields,
OutputFields,
Conditions,
}
impl Declaration {
#[must_use]
pub fn carrier(self) -> &'static str {
match self {
Self::Fields => "Signals",
Self::OutputFields => "Outputs",
Self::Conditions => "Conditions",
}
}
}
impl std::fmt::Display for Declaration {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(match self {
Self::Fields => "FIELDS",
Self::OutputFields => "OUTPUT_FIELDS",
Self::Conditions => "CONDITIONS",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum Mismatch {
#[error("{machine}'s {declaration} give {field} {width} bytes, and a field takes 1, 2, 4 or 8")]
Width {
machine: &'static str,
declaration: Declaration,
field: Cow<'static, str>,
width: u8,
},
#[error(
"{machine}'s {declaration} add up to {declared} bytes, and its {} type takes {carried} on the wire",
.declaration.carrier()
)]
Total {
machine: &'static str,
declaration: Declaration,
declared: usize,
carried: usize,
},
#[error(
"{machine}'s {} type doesn't read zero bytes back: {why}",
.declaration.carrier()
)]
Unread {
machine: &'static str,
declaration: Declaration,
why: WireError,
},
#[error("{machine}'s CHIPS name {chip}, and its `place` gives none for it")]
Unplaced {
machine: &'static str,
chip: &'static str,
},
}
fn declared(fields: &[Field]) -> usize {
fields.iter().map(|field| usize::from(field.width)).sum()
}
fn carried<T: Wire>() -> Result<usize, WireError> {
let zeros = vec![0; LARGEST_FRAME];
let mut rest = zeros.as_slice();
T::decode(&mut rest)?;
Ok(zeros.len() - rest.len())
}
impl<B: Edge + Board> Coupled for B
where
B::Signals: Wire,
B::Outputs: Wire,
B::Conditions: Wire,
{
fn fields(&self) -> &'static [Field] {
B::FIELDS
}
fn output_fields(&self) -> &'static [Field] {
B::OUTPUT_FIELDS
}
fn rest_wire(&self) -> Vec<u8> {
let mut bytes = Vec::new();
B::REST.encode(&mut bytes);
bytes
}
fn sense_wire(&mut self, bytes: &[u8]) -> Result<(), WireError> {
let signals = B::Signals::decode(&mut &*bytes)?;
self.sense(&signals);
Ok(())
}
fn conditions(&self) -> &'static [Field] {
B::CONDITIONS
}
fn condition_wire(&mut self, bytes: &[u8]) -> Result<(), WireError> {
let conditions = B::Conditions::decode(&mut &*bytes)?;
self.condition(&conditions);
Ok(())
}
fn draw(&mut self, seed: u64, index: u64) {
Edge::draw(self, seed, index);
}
fn drive_wire(&mut self) -> Vec<u8> {
let mut bytes = Vec::new();
self.drive().encode(&mut bytes);
bytes
}
fn chips(&self) -> &'static [&'static str] {
B::CHIPS
}
fn place(&mut self, chip: usize) -> Option<&mut dyn Placed> {
Edge::place(self, chip)
}
fn check(&mut self) -> Result<(), Mismatch> {
let machine = self.name();
let declarations = [
(Declaration::Fields, B::FIELDS),
(Declaration::OutputFields, B::OUTPUT_FIELDS),
(Declaration::Conditions, B::CONDITIONS),
];
for (declaration, fields) in declarations {
if let Some(field) = fields
.iter()
.find(|field| checked_width(field.width).is_err())
{
return Err(Mismatch::Width {
machine,
declaration,
field: field.name.clone(),
width: field.width,
});
}
}
let unread = |declaration| {
move |why| Mismatch::Unread {
machine,
declaration,
why,
}
};
let carried = [
self.rest_wire().len(),
carried::<B::Outputs>().map_err(unread(Declaration::OutputFields))?,
carried::<B::Conditions>().map_err(unread(Declaration::Conditions))?,
];
for ((declaration, fields), carried) in declarations.into_iter().zip(carried) {
let declared = declared(fields);
if declared != carried {
return Err(Mismatch::Total {
machine,
declaration,
declared,
carried,
});
}
}
match (0..B::CHIPS.len()).find(|&chip| Edge::place(self, chip).is_none()) {
Some(chip) => Err(Mismatch::Unplaced {
machine,
chip: B::CHIPS[chip],
}),
None => Ok(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Field {
pub name: Cow<'static, str>,
pub unit: Cow<'static, str>,
pub width: u8,
pub signed: bool,
}
impl Field {
#[must_use]
pub const fn new(name: &'static str, unit: &'static str, width: u8, signed: bool) -> Self {
Self {
name: Cow::Borrowed(name),
unit: Cow::Borrowed(unit),
width,
signed,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Pulse {
pub width: VirtualNanos,
pub period: VirtualNanos,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Condition<const N: usize> {
pub stuck: bool,
#[serde(with = "crate::snapshot::array")]
pub bias: [i32; N],
#[serde(with = "crate::snapshot::array")]
pub noise: [u32; N],
}
impl<const N: usize> Default for Condition<N> {
fn default() -> Self {
Self {
stuck: false,
bias: [0; N],
noise: [0; N],
}
}
}
crate::wire!(Condition<const N: usize> {
stuck with flag,
bias,
noise
});
pub(crate) fn flag(bytes: &mut &[u8]) -> Result<bool, WireError> {
Ok(u8::decode(bytes)? != 0)
}
pub trait Reading: Copy + Default + Into<i64> + TryFrom<i64> {
const MIN: Self;
const MAX: Self;
}
macro_rules! reading {
($($int:ty),*) => {$(
impl Reading for $int {
const MIN: Self = <$int>::MIN;
const MAX: Self = <$int>::MAX;
}
)*};
}
reading!(i8, i16, i32, i64, u8, u16, u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(bound(
serialize = "T: serde::Serialize",
deserialize = "T: serde::Deserialize<'de>"
))]
pub struct Sensed<T, const N: usize> {
#[serde(with = "crate::snapshot::array")]
held: [T; N],
#[serde(with = "crate::snapshot::array")]
drawn: [i64; N],
}
impl<T: Reading, const N: usize> Default for Sensed<T, N> {
fn default() -> Self {
Self {
held: [T::default(); N],
drawn: [0; N],
}
}
}
impl<T: Reading, const N: usize> Sensed<T, N> {
pub fn hand(&mut self, value: [T; N], condition: &Condition<N>) -> [T; N] {
if !condition.stuck {
self.held = std::array::from_fn(|axis| {
let sum = value[axis]
.into()
.saturating_add(i64::from(condition.bias[axis]))
.saturating_add(self.drawn[axis]);
T::try_from(sum).unwrap_or(if sum < 0 { T::MIN } else { T::MAX })
});
}
self.held
}
pub fn draw(
&mut self,
condition: &Condition<N>,
names: &[&str; N],
seed: u64,
index: u64,
) -> bool {
let drawn = std::array::from_fn(|axis| match condition.noise[axis] {
0 => 0,
deviation => normal(seed, names[axis], index, deviation),
});
std::mem::replace(&mut self.drawn, drawn) != drawn
}
}
const GOLDEN_GAMMA: u64 = 0x9E37_79B9_7F4A_7C15;
const FNV_OFFSET_BASIS: u64 = 0xCBF2_9CE4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01B3;
const DRAWS_PER_NORMAL: u64 = 6;
const UNIFORM_BITS: u32 = 32;
const LOW_HALF: u64 = 0xFFFF_FFFF;
const IRWIN_HALL_CENTER: i128 = 6 << UNIFORM_BITS;
const fn mixed(z: u64) -> u64 {
let z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
let z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[must_use]
pub fn draw(seed: u64, name: &str, index: u64) -> u64 {
let hash = name.bytes().fold(FNV_OFFSET_BASIS, |hash, byte| {
(hash ^ u64::from(byte)).wrapping_mul(FNV_PRIME)
});
let start = mixed(seed ^ hash);
mixed(start.wrapping_add(index.wrapping_add(1).wrapping_mul(GOLDEN_GAMMA)))
}
#[must_use]
pub fn normal(seed: u64, name: &str, index: u64, deviation: u32) -> i64 {
let first = index.wrapping_mul(DRAWS_PER_NORMAL);
let sum: i128 = (0..DRAWS_PER_NORMAL)
.map(|offset| draw(seed, name, first.wrapping_add(offset)))
.map(|drawn| i128::from(drawn >> UNIFORM_BITS) + i128::from(drawn & LOW_HALF))
.sum();
let scaled = (sum - IRWIN_HALL_CENTER) * i128::from(deviation);
let rounded = (scaled + (1 << (UNIFORM_BITS - 1))) >> UNIFORM_BITS;
i64::try_from(rounded).expect("six deviations of a u32 fit an i64")
}
#[cfg(test)]
mod tests {
use std::marker::PhantomData;
use super::*;
#[derive(Debug)]
struct Wrong<O, const N: usize>(PhantomData<O>);
impl<O, const N: usize> crate::snapshot::Store for Wrong<O, N> {}
impl<O: Wire + Default + Send + std::fmt::Debug, const N: usize> Board for Wrong<O, N> {
fn name(&self) -> &'static str {
"wrong"
}
fn targets(&mut self) -> Vec<crate::machine::Target<'_>> {
Vec::new()
}
fn target_names(&self) -> Vec<&'static str> {
Vec::new()
}
fn mcu(&mut self, _target: usize) -> Option<&mut dyn crate::mcu::Mcu> {
None
}
}
impl<O: Wire + Default + Send + std::fmt::Debug, const N: usize> Edge for Wrong<O, N> {
type Signals = ();
type Outputs = O;
type Conditions = ();
const FIELDS: &'static [Field] = &[];
const OUTPUT_FIELDS: &'static [Field] = &[Field::new("lamp", "flag", 1, false)];
const REST: () = ();
const CHIPS: &'static [&'static str] = &["gauge"; N];
fn sense(&mut self, _signals: &()) {}
fn condition(&mut self, _conditions: &()) {}
fn drive(&mut self) -> O {
O::default()
}
}
#[derive(Debug, Default)]
struct Lit;
impl Wire for Lit {
fn encode(&self, out: &mut Vec<u8>) {
out.push(1);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, WireError> {
match u8::decode(bytes)? {
1 => Ok(Self),
value => Err(WireError::Invalid {
what: "lit flag",
value: u64::from(value),
}),
}
}
}
#[test]
fn a_board_that_names_a_chip_it_has_no_place_for_is_refused() {
assert_eq!(Wrong::<u8, 0>(PhantomData).check(), Ok(()), "no chips");
assert_eq!(
Wrong::<u8, 1>(PhantomData).check().unwrap_err().to_string(),
"wrong's CHIPS name gauge, and its `place` gives none for it"
);
}
#[test]
fn outputs_that_do_not_read_zeros_back_are_refused_with_why() {
assert_eq!(
Wrong::<Lit, 0>(PhantomData)
.check()
.unwrap_err()
.to_string(),
"wrong's Outputs type doesn't read zero bytes back: 0 is no lit flag"
);
}
#[test]
fn a_reading_carries_its_bias_until_it_sticks_and_then_holds() {
let mut sensed = Sensed::default();
let biased = Condition {
bias: [0, 5, -5],
..Condition::default()
};
assert_eq!(sensed.hand([1, 2, 3], &biased), [1, 7, -2]);
let stuck = Condition {
stuck: true,
bias: [100; 3],
..Condition::default()
};
assert_eq!(
sensed.hand([40, 50, 60], &stuck),
[1, 7, -2],
"stuck at what it had, and deaf to a new bias"
);
assert_eq!(
sensed.hand([40, 50, 60], &Condition::default()),
[40, 50, 60]
);
}
#[test]
fn a_bias_past_the_end_of_its_type_saturates_rather_than_wraps() {
let condition = Condition {
bias: [1, -1],
..Condition::default()
};
assert_eq!(
Sensed::default().hand([i32::MAX, i32::MIN], &condition),
[i32::MAX, i32::MIN]
);
assert_eq!(
Sensed::default().hand([u32::MAX, 0], &condition),
[u32::MAX, 0]
);
assert_eq!(
Sensed::default().hand([3_000_000_000_u32], &Condition::default()),
[3_000_000_000],
"a value past an i32 crosses whole"
);
assert_eq!(
Sensed::default().hand([u8::MAX, 0], &condition),
[u8::MAX, 0]
);
assert_eq!(
Sensed::default().hand([i16::MAX, i16::MIN], &condition),
[i16::MAX, i16::MIN]
);
}
#[test]
fn a_draw_is_splitmix64_from_the_seed_and_the_names_hash_and_never_moves() {
assert_eq!(
mixed(GOLDEN_GAMMA),
0xE220_A839_7B1D_CDAF,
"SplitMix64's first draw from a state of nothing"
);
assert_eq!(draw(0, "", 0), 0xE587_D3DF_F9E9_2ED0);
assert_eq!(draw(0, "a", 0), 0x538A_21EC_5E49_5B1A);
assert_eq!(draw(1, "chip.rate.noise.x", 0), 0xC2A3_85B3_2629_CC2D);
assert_eq!(draw(1, "chip.rate.noise.x", 1), 0x1149_FEDC_A1A2_5114);
assert_eq!(draw(42, "line.noise_errors", 7), 0xD0B3_686D_9D99_13E4);
assert_eq!(
draw(u64::MAX, "chip.rate.noise.y", u64::MAX),
0x2515_FCB1_4983_6BE6,
"every sum and product wraps"
);
}
#[test]
fn a_normal_is_irwin_and_halls_in_integers_and_never_moves() {
assert_eq!(normal(0, "chip.rate.noise.x", 0, 1000), -251);
assert_eq!(normal(0, "chip.rate.noise.x", 1, 1000), -653);
assert_eq!(
normal(7, "chip.pressure.noise", 3, u32::MAX),
-2_298_779_098
);
assert_eq!(normal(7, "chip.pressure.noise", 3, 0), 0);
}
#[test]
#[cfg_attr(miri, ignore = "a million normals is beyond Miri")]
fn a_million_normals_have_the_deviation_asked_for_about_no_mean_and_stop_at_six() {
const DRAWS: i32 = 1_000_000;
let deviation = 1_000_000;
let (mut sum, mut squares, mut widest) = (0_i128, 0_i128, 0_i64);
for index in 0..u64::try_from(DRAWS).unwrap() {
let drawn = normal(5, "chip.rate.noise.z", index, deviation);
sum += i128::from(drawn);
squares += i128::from(drawn) * i128::from(drawn);
widest = widest.max(drawn.abs());
}
let mean = sum / i128::from(DRAWS);
let variance = squares / i128::from(DRAWS) - mean * mean;
assert!(mean.abs() < 5_000, "a mean of {mean}");
let spread = i128::from(deviation) * i128::from(deviation);
assert!(
(variance - spread).abs() < spread / 100,
"a variance of {variance}"
);
assert!(widest <= 6 * i64::from(deviation), "{widest}");
}
#[test]
fn noise_rides_on_the_bias_holds_for_the_step_and_a_stuck_chip_hears_none() {
let names = ["chip.rate.noise.x", "chip.rate.noise.y"];
let noisy = Condition {
bias: [10, 0],
noise: [1000, 0],
..Condition::default()
};
let mut sensed = Sensed::default();
assert!(sensed.draw(&noisy, &names, 0, 0), "the first draw moves it");
assert_eq!(sensed.hand([100, 100], &noisy), [100 + 10 - 251, 100]);
assert_eq!(
sensed.hand([200, 200], &noisy),
[200 + 10 - 251, 200],
"the same noise until the next step"
);
assert!(sensed.draw(&noisy, &names, 0, 1));
assert_eq!(sensed.hand([100, 100], &noisy), [100 + 10 - 653, 100]);
let stuck = Condition {
stuck: true,
..noisy
};
assert!(sensed.draw(&stuck, &names, 0, 0));
assert_eq!(
sensed.hand([0, 0], &stuck),
[100 + 10 - 653, 100],
"stuck at what it had"
);
let quiet = Condition::default();
assert!(sensed.draw(&quiet, &names, 0, 2));
assert!(
!sensed.draw(&quiet, &names, 0, 3),
"no noise, and none to take away"
);
assert_eq!(sensed.hand([7, 8], &quiet), [7, 8]);
}
#[test]
fn a_condition_crosses_as_the_flag_then_each_bias_then_each_noise() {
let condition = Condition {
stuck: true,
bias: [-1, 2],
noise: [3, 4],
};
let mut bytes = Vec::new();
condition.encode(&mut bytes);
assert_eq!(
bytes,
[
vec![1],
(-1_i32).to_le_bytes().to_vec(),
2_i32.to_le_bytes().to_vec(),
3_u32.to_le_bytes().to_vec(),
4_u32.to_le_bytes().to_vec()
]
.concat()
);
assert_eq!(Condition::<2>::decode(&mut &bytes[..]).unwrap(), condition);
}
#[test]
fn any_flag_byte_but_zero_sets_it() {
assert!(!flag(&mut &[0][..]).unwrap());
assert!(flag(&mut &[1][..]).unwrap());
assert!(
flag(&mut &[2][..]).unwrap(),
"the field holds it, so the board takes it"
);
assert!(flag(&mut &[][..]).is_err());
}
}