//! Arithmetic operations: add, subtract, etc. use crate::prelude::*; use crate::tile::prelude::*; use serde_with::{serde_as, DefaultOnNull}; use veccell::VecRef; macro_rules! macro_try { ( $instructions:block ) => { (|| { $instructions })() } } #[serde_as] #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Store { #[serde_as(deserialize_as = "DefaultOnNull")] signal: Signal, } impl Store { pub fn signal(&self) -> &Signal { &self.signal } pub fn is_empty(&self) -> bool { self.signal.is_stack_empty() } } impl Tile for Store { fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { if let Some(signal) = context.take_signal() { let position = context.position(); // NOTE: we *could* write the signal immediately, // but by delaying the write we can read from a `Store` without being order-dependent context.callback(move |pane| { if let Some(mut this) = pane.borrow_mut_as::(position) { this.signal = signal; } }); } if context.state() != State::Idle { context.next_state(); } } fn draw_simple(&self, ctx: DrawContext) -> TextChar { if self.is_empty() { TextChar::from_state('\u{25cb}', ctx.state) // WHITE CIRCLE } else { TextChar::from_state('\u{25c9}', ctx.state) // FISHEYE } } fn schema(&self) -> TileSchema { TileSchema::map() .add("signal", TileSchema::value("Stored signal", "Signal")) } } /// When a signal is received, reads a signal from a [`Store`] (at the reader's tail), /// then outputs that signal (at the reader's head). /// /// # Example /// /// The following circuit can receive a value in `B`, then store it indefinitely. /// Sending a signal in `A` will output the stored signal from `B` in `OUT`. /// /// ```text /// o = Store /// ▸ = Reader(Right) /// /// (A) ---+ /// | /// (B) --o▸-- (OUT) /// ``` #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Reader(Direction); impl Reader { pub fn new(direction: Direction) -> Self { Self(direction) } } impl Tile for Reader { fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { if let Some(_signal) = context.take_signal() { let _: Option<()> = macro_try!({ let store_position = context.offset(self.0.opposite().into_offset())?; let store = context.get(store_position).and_then(get_store)?; let signal = store.signal.clone(); drop(store); let target_position = context.offset(self.0.into_offset())?; let _ = context.send(target_position, self.0, signal); Some(()) }); } if context.state() != State::Idle { context.next_state(); } } fn draw_simple(&self, ctx: DrawContext) -> TextChar { match self.0 { Direction::Down => TextChar::from_state('\u{25bd}', ctx.state), // WHITE DOWN-POINTING TRIANGLE Direction::Left => TextChar::from_state('\u{25c1}', ctx.state), // WHITE LEFT-POINTING TRIANGLE Direction::Right => TextChar::from_state('\u{25b7}', ctx.state), // WHITE RIGHT-POINTING TRIANGLE Direction::Up => TextChar::from_state('\u{25b3}', ctx.state), // WHITE UP-POINTING TRIANGLE } } fn schema(&self) -> TileSchema { TileSchema::value("Direction", "Direction") } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct StorageCount; impl Tile for StorageCount { fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { if let Some(mut signal) = context.take_signal() { let mut count = 0; for dir in Orientation::Any.into_directions() { if let Some(store_position) = context.offset(dir.into_offset()) { if let Some(store) = context.get(store_position).and_then(get_store) { count += (!store.is_empty()) as u8; } } } signal.push(Value::Number(count.into())); if let Some(target_position) = context.offset(signal.direction().into_offset()) { let _ = context.send(target_position, signal.direction(), signal); } } if context.state() != State::Idle { context.next_state(); } } fn draw_simple(&self, ctx: DrawContext) -> TextChar { TextChar::from_state('\u{2058}', ctx.state) // FOUR DOT PUNCTUATION } } #[serde_as] #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Stacker { #[serde_as(deserialize_as = "DefaultOnNull")] signal: Signal, /// May only be Horizontal or Vertical orientation: Orientation, } impl Stacker { pub fn new(orientation: Orientation) -> Self { Self { signal: Signal::empty(Direction::default()), orientation, } } pub fn signal(&self) -> &Signal { &self.signal } pub fn is_empty(&self) -> bool { self.signal.is_stack_empty() } } impl Default for Stacker { fn default() -> Self { Stacker::new(Orientation::Horizontal) } } impl Tile for Stacker { fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { if let Some(mut signal) = context.take_signal() { if self.orientation.contains(signal.direction()) { // Stack the stored signal on top of the incomming signal signal.append(&self.signal); if let Some(target_position) = context.offset(signal.direction().into_offset()) { let _ = context.send(target_position, signal.direction(), signal); } if context.state() != State::Idle { context.next_state(); } } else { // Store the signal inside of ourselves self.signal = signal; context.set_state(State::Idle); } } else { if context.state() != State::Idle { context.next_state(); } } } fn draw_simple(&self, ctx: DrawContext) -> TextChar { match self.orientation { Orientation::Vertical => TextChar::from_state('\u{03a6}', ctx.state), // GREEK CAPITAL LETTER PHI Orientation::Horizontal => TextChar::from_state('\u{a74a}', ctx.state), // LATIN CAPITAL LETTER O WITH LONG STROKE _ => TextChar::from_state('+', ctx.state), } } fn schema(&self) -> TileSchema { TileSchema::map() .add("signal", TileSchema::value("Signal to stack", "Signal")) .add("orientation", TileSchema::value("Orientation", "Orientation|NotAny")) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Debug { value: Value, } impl Default for Debug { fn default() -> Self { Self { value: Value::Number(0.0) } } } impl Tile for Debug { fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) { if let Some(mut signal) = context.take_signal() { if let Some(value) = signal.pop() { self.value = value; } } if context.state() != State::Idle { context.next_state(); } } fn draw(&self, surface: &mut TextSurface, ctx: DrawContext<'_>) { use palette::Srgb; let text = format!("\"{}", self.value); let y = ctx.surface_coords.1; for (index, c) in text.chars().enumerate() { let x = ctx.surface_coords.0 + index as i32; if let (Ok(x), Ok(y)) = (x.try_into(), y.try_into()) { let textchar = if index == 0 { TextChar::from_state(c, ctx.state) } else { TextChar::new(c, Srgb::new(100, 100, 100), None) }; surface.set(x, y, textchar); } } } fn schema(&self) -> TileSchema { TileSchema::map() .add("value", TileSchema::value("Stored value", "Value")) } } /// Tries to convert a [`FullTile`] to a [`Store`] fn get_store<'a>(full: VecRef<'a, FullTile>) -> Option> { VecRef::try_map(full, |tile| { let tile = tile.get().ok_or(())?; let store = tile.try_into().map_err(|_| ())?; Ok::<&Store, ()>(store) }).ok() }