World, cross-Pane communication

main
Shad Amethyst 2 years ago
parent a816da6062
commit 7175e65570
Signed by: amethyst
GPG Key ID: D970C8DD1D6DEE36

@ -361,6 +361,12 @@ impl<'a> UpdateContext<'a> {
}
}
/// Sends a signal to another [`Pane`] in the world.
/// If the tile at `coordinates = (pane, x, y)` does not exist, then the signal will be lost.
pub fn send_outbound(&mut self, coordinates: (String, usize, usize), signal: Signal) {
self.commit.send_outbound(coordinates, signal);
}
/// Stores the current signal back in the current tile, guaranteeing that it will stay there for
/// this update cycle. See [`take_signal`](UpdateContext::take_signal) for more information.
///
@ -398,6 +404,11 @@ impl<'a> UpdateContext<'a> {
_ => {}
}
}
/// Executes an arbitrary function after all the updates.
pub fn callback<F: for<'c> FnOnce(&'c mut Pane) + 'static>(&mut self, callback: F) {
self.commit.set_callback(Box::new(callback));
}
}
pub struct SendError(pub Signal);
@ -416,12 +427,16 @@ impl std::fmt::Display for SendError {
}
}
pub type UpdateCommitCallback = Box<dyn for<'c> FnOnce(&'c mut Pane) + 'static>;
/// Temporarily holds a list of actions to be made on a given Pane, which should be [applied](UpdateCommit::apply)
/// after every tile was updated.
pub(crate) struct UpdateCommit {
states: Vec<(usize, usize, State)>,
signals: Vec<(usize, usize, Option<Signal>)>,
updates: Vec<(usize, usize)>,
callbacks: Vec<UpdateCommitCallback>,
outbound_signals: Vec<((String, usize, usize), Signal)>,
self_signal: Option<Signal>,
}
@ -432,6 +447,8 @@ impl UpdateCommit {
states: Vec::new(),
signals: Vec::new(),
updates: Vec::new(),
callbacks: Vec::new(),
outbound_signals: Vec::new(),
self_signal: None,
}
@ -449,7 +466,15 @@ impl UpdateCommit {
self.self_signal = signal;
}
pub(crate) fn apply(self, pane: &mut Pane) {
fn set_callback(&mut self, callback: UpdateCommitCallback) {
self.callbacks.push(callback);
}
fn send_outbound(&mut self, coordinates: (String, usize, usize), signal: Signal) {
self.outbound_signals.push((coordinates, signal));
}
pub(crate) fn apply(self, pane: &mut Pane) -> PaneResult {
for (x, y) in self.updates {
if let Some(tile) = pane.get_mut((x, y)) {
tile.updated = false;
@ -476,6 +501,14 @@ impl UpdateCommit {
pane.signals.push((x, y));
}
}
for callback in self.callbacks {
(callback)(pane);
}
PaneResult {
outbound_signals: self.outbound_signals,
}
}
/// Applies transformations on a FullTile before the end of the update phase

@ -18,6 +18,10 @@ use signal::*;
pub mod pane;
use pane::*;
pub mod world;
#[allow(unused_imports)]
use world::*;
pub mod utils;
use utils::*;
@ -30,13 +34,9 @@ use context::*;
pub mod text;
use text::*;
pub struct World {
panes: Vec<Pane>,
}
pub mod prelude {
pub use crate::pane::Pane;
pub use crate::World;
pub use crate::world::World;
pub use crate::text::{TextChar, TextSurface};

@ -351,7 +351,7 @@ impl Pane {
/// // The signal is now at (1, 1)
/// assert!(pane.get((1, 1)).unwrap().signal().is_some());
/// ```
pub fn step(&mut self) {
pub fn step(&mut self) -> PaneResult {
let mut commit = UpdateCommit::new();
for position in std::mem::replace(&mut self.signals, Vec::new()) {
@ -366,7 +366,7 @@ impl Pane {
}
}
commit.apply(self);
commit.apply(self)
}
/// Returns an iterator over the tiles and their coordinates
@ -390,6 +390,12 @@ impl Pane {
}
}
/// Stores the results of a [`Pane`]'s update step.
pub struct PaneResult {
/// Signals to be sent to other panes.
pub outbound_signals: Vec<((String, usize, usize), Signal)>,
}
#[cfg(test)]
mod test {
use super::*;

@ -1,6 +1,6 @@
#[macro_export]
macro_rules! test_tile_setup {
( $width:expr, $height:expr, [ $( $x:expr ),* ] ) => {{
( $width:expr, $height:expr, [ $( $x:expr ),* $(,)? ] ) => {{
assert!($width > 0);
assert!($height > 0);
let mut pane = crate::pane::Pane::empty($width, $height).unwrap();

@ -0,0 +1,55 @@
use super::*;
use std::collections::HashMap;
pub struct World {
panes: HashMap<String, Pane>,
}
impl World {
pub fn new() -> Self {
Self {
panes: HashMap::new(),
}
}
pub fn step(&mut self) {
let mut outbound_signals = Vec::new();
for (_, pane) in self.panes.iter_mut() {
let mut res = pane.step();
outbound_signals.append(&mut res.outbound_signals);
}
for ((name, x, y), signal) in outbound_signals {
if let Some(pane) = self.get_pane_mut(&name) {
let _ = pane.set_signal((x, y), signal); // Errors are ignored
}
}
}
pub fn set_pane(&mut self, name: String, pane: Pane) {
self.panes.insert(name, pane);
}
pub fn get_pane<'b>(&'b self, name: &str) -> Option<&'b Pane> {
self.panes.get(name)
}
pub fn get_pane_mut<'b>(&'b mut self, name: &str) -> Option<&'b mut Pane> {
self.panes.get_mut(name)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_world_set_pane() {
let mut world = World::new();
world.set_pane(String::from("main"), Pane::empty(10, 10).unwrap());
assert!(world.get_pane("main").is_some());
}
}

@ -0,0 +1,143 @@
//! Transmission tiles: allow for inter-Pane communication
use crate::prelude::*;
/// Instantly sends any incomming signals to `coordinates`
#[derive(Clone, Debug)]
pub struct Portal {
pub coordinates: (String, usize, usize),
}
impl Portal {
pub fn new(name: String, x: usize, y: usize) -> Self {
Self {
coordinates: (name, x, y)
}
}
}
impl Tile for Portal {
fn update<'b>(&'b mut self, mut context: UpdateContext<'b>) {
if let Some(signal) = context.take_signal() {
context.send_outbound(self.coordinates.clone(), signal);
}
if context.state() != State::Idle {
context.next_state();
}
}
fn draw(&self, x: usize, y: usize, state: State, surface: &mut TextSurface) {
surface.set(x, y, TextChar::from_state('P', state));
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_portal_transmit_same_pane() {
use crate::{Diode, Wire};
use Direction::*;
use Orientation::*;
let mut main_pane = test_tile_setup!(
3,
3,
[
Diode::new(Right), Portal::new(String::from("main"), 2, 2), (),
(), (), (),
(), (), Wire::new(Any)
]
);
test_set_signal!(main_pane, (0, 0), Right);
let mut world = World::new();
world.set_pane(String::from("main"), main_pane);
world.step();
assert_signal!(world.get_pane("main").unwrap(), (1, 0));
world.step();
assert_no_signal!(world.get_pane("main").unwrap(), (1, 0));
assert_signal!(world.get_pane("main").unwrap(), (2, 2));
world.step();
assert_no_signal!(world.get_pane("main").unwrap(), (0, 0));
assert_no_signal!(world.get_pane("main").unwrap(), (1, 0));
assert_no_signal!(world.get_pane("main").unwrap(), (2, 2));
}
#[test]
fn test_portal_transmit_other_pane() {
use crate::{Diode, Wire};
use Direction::*;
use Orientation::*;
let mut main_pane = test_tile_setup!(
2,
1,
[
Diode::new(Right), Portal::new(String::from("sub"), 0, 0),
]
);
test_set_signal!(main_pane, (0, 0), Right);
let sub_pane = test_tile_setup!(
1,
1,
[
Wire::new(Any),
]
);
let mut world = World::new();
world.set_pane(String::from("main"), main_pane);
world.set_pane(String::from("sub"), sub_pane);
world.step();
assert_signal!(world.get_pane("main").unwrap(), (1, 0));
world.step();
assert_no_signal!(world.get_pane("main").unwrap(), (1, 0));
assert_signal!(world.get_pane("sub").unwrap(), (0, 0));
world.step();
assert_no_signal!(world.get_pane("main").unwrap(), (0, 0));
assert_no_signal!(world.get_pane("main").unwrap(), (1, 0));
assert_no_signal!(world.get_pane("sub").unwrap(), (0, 0));
}
#[test]
fn test_portal_transmit_self() {
use Direction::*;
let mut main_pane = test_tile_setup!(
1,
1,
[
Portal::new(String::from("main"), 0, 0),
]
);
test_set_signal!(main_pane, (0, 0), Right);
let mut world = World::new();
world.set_pane(String::from("main"), main_pane);
for _ in 0..5 {
world.step();
assert_signal!(world.get_pane("main").unwrap(), (0, 0));
}
}
}
Loading…
Cancel
Save