From 13796a87e81538b5322953be53b505d7d4159e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nurzhan=20Sak=C3=A9n?= Date: Sun, 7 Jan 2024 20:11:38 +0400 Subject: [PATCH] Clean up the dining philosophers example --- README.md | 5 +- examples/README.md | 9 + examples/dining_philosophers.rs | 325 +++++++++++++++----------------- src/lib.rs | 2 +- src/net.rs | 6 +- src/net/place.rs | 5 +- src/net/trans.rs | 7 +- src/token.rs | 6 +- 8 files changed, 175 insertions(+), 190 deletions(-) create mode 100644 examples/README.md diff --git a/README.md b/README.md index 4c1cc2a..cde42ef 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,4 @@ how I can improve it with time and usage. ## Examples -Check out [this example](examples/simple.rs) demonstrating a simple Petri net in action, as well as [the tests here](src/net.rs) for more Petri net instances. - -### More examples: -- [Dining philosophers](examples/dining_philosophers.rs) (demonstrates competing for shared resources, and a deadlock situation) +Examples can be found in the [`examples`](examples) directory. \ No newline at end of file diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..10a51c0 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,9 @@ +## Examples + +--- + +Check out [this example](simple.rs) demonstrating a simple Petri net in action, as well as [the tests here](../src/net.rs) for more Petri net instances. + +### [Dining philosophers](dining_philosophers.rs) + +An interactive demo illustrating competing for shared resources, and a deadlock situation. diff --git a/examples/dining_philosophers.rs b/examples/dining_philosophers.rs index 126adba..684e0c0 100644 --- a/examples/dining_philosophers.rs +++ b/examples/dining_philosophers.rs @@ -9,50 +9,53 @@ use petnat::{NetId, PetriNet, PetriNetPlugin, Place, Token, Trans, W}; enum DiningPhils {} -enum ForkFree {} +const LEFT: bool = true; +const RIGHT: bool = false; + +enum ForkClean {} enum ForkTaken {} -enum ForkUsed {} +enum ForkDirty {} enum Eating {} enum Thinking {} enum Take {} -enum Return {} +enum Wash {} enum Eat {} enum Finish {} impl NetId for DiningPhils {} -impl Place for ForkFree {} +impl Place for ForkClean {} impl Place for ForkTaken {} -impl Place for ForkUsed {} +impl Place for ForkDirty {} impl Place for Eating {} impl Place for Thinking {} impl Trans for Take {} -impl Trans for Return {} +impl Trans for Wash {} impl Trans for Eat {} impl Trans for Finish {} fn add_philosopher(net: PetriNet) -> PetriNet { - net.add_place::>() - .add_place::>() - .add_place::>() - .add_place::>() + net.add_place::>() + .add_place::>() + .add_place::>() + .add_place::>() .add_place::>() .add_place::>() - .add_trans::, (ForkFree, W<1>), (ForkTaken, W<1>)>() - .add_trans::, (ForkFree, W<1>), (ForkTaken, W<1>)>() - .add_trans::, (ForkUsed, W<1>), (ForkFree, W<1>)>() - .add_trans::, (ForkUsed, W<1>), (ForkFree, W<1>)>() + .add_trans::, (ForkClean, W<1>), (ForkTaken, W<1>)>() + .add_trans::, (ForkClean, W<1>), (ForkTaken, W<1>)>() + .add_trans::, (ForkDirty, W<1>), (ForkClean, W<1>)>() + .add_trans::, (ForkDirty, W<1>), (ForkClean, W<1>)>() .add_trans::, ( (Thinking, W<1>), - (ForkTaken, W<1>), - (ForkTaken, W<1>), + (ForkTaken, W<1>), + (ForkTaken, W<1>), ), (Eating, W<1>)>() .add_trans::, (Eating, W<1>), ( (Thinking, W<1>), - (ForkUsed, W<1>), - (ForkUsed, W<1>), + (ForkDirty, W<1>), + (ForkDirty, W<1>), )>() } @@ -62,7 +65,7 @@ fn main() { primary_window: Some(Window { resolution: WindowResolution::new(640.0, 640.0), title: "Dining Philosophers".to_string(), - resizable: true, + resizable: LEFT, ..default() }), ..default() @@ -71,8 +74,8 @@ fn main() { .insert_resource(ClearColor(Color::BLACK)) .add_plugins(PetriNetPlugin:: { build: |net| { - net.add_place::>() - .add_place::>() + net.add_place::>() + .add_place::>() .compose(add_philosopher::<0>) .compose(add_philosopher::<1>) }, @@ -87,7 +90,7 @@ fn main() { (init_forks, init_philosopher::<0>, init_philosopher::<1>), ) .chain(), - (draw_manual, draw_net), + (show_manual, show_net), ), ) .add_systems( @@ -104,16 +107,17 @@ fn main() { .add_systems( Update, ( - take_fork::.run_if(input_just_pressed(KeyCode::Q)), - take_fork::.run_if(input_just_pressed(KeyCode::E)), - return_fork::.run_if(input_just_pressed(KeyCode::A)), - return_fork::.run_if(input_just_pressed(KeyCode::D)), + // In this setup, all transitions are fired manually via player input. + take_fork::.run_if(input_just_pressed(KeyCode::Q)), + take_fork::.run_if(input_just_pressed(KeyCode::E)), + wash_fork::.run_if(input_just_pressed(KeyCode::A)), + wash_fork::.run_if(input_just_pressed(KeyCode::D)), eat::<0>.run_if(input_just_pressed(KeyCode::W)), finish::<0>.run_if(input_just_pressed(KeyCode::S)), - take_fork::.run_if(input_just_pressed(KeyCode::U)), - take_fork::.run_if(input_just_pressed(KeyCode::O)), - return_fork::.run_if(input_just_pressed(KeyCode::J)), - return_fork::.run_if(input_just_pressed(KeyCode::L)), + take_fork::.run_if(input_just_pressed(KeyCode::U)), + take_fork::.run_if(input_just_pressed(KeyCode::O)), + wash_fork::.run_if(input_just_pressed(KeyCode::J)), + wash_fork::.run_if(input_just_pressed(KeyCode::L)), eat::<1>.run_if(input_just_pressed(KeyCode::I)), finish::<1>.run_if(input_just_pressed(KeyCode::K)), ) @@ -122,24 +126,18 @@ fn main() { .add_systems( PostUpdate, ( - |tokens: Query<(), Changed>>| { - if tokens.get_single().is_ok() { - info!("=== STATE ==="); - } - }, - show_free_fork::, - show_free_fork::, - show_taken_fork::, - show_taken_fork::, - show_taken_fork::, - show_taken_fork::, + show_clean_fork::, + show_clean_fork::, + show_taken_fork::, + show_taken_fork::, + show_taken_fork::, + show_taken_fork::, + show_dirty_fork::, + show_dirty_fork::, + show_dirty_fork::, + show_dirty_fork::, show_philosopher::<0>, show_philosopher::<1>, - |tokens: Query<(), Changed>>| { - if tokens.get_single().is_ok() { - info!("============="); - } - }, ) .chain(), ) @@ -154,8 +152,9 @@ fn spawn_terminal(mut commands: Commands) { } fn spawn_token(mut commands: Commands, net: Res>) { - // A token can represent the current state of some game object, - // or some sort of resource + // A token can represent the current state of some game object, or some sort of resource. + // In this example, the token represents the state of the dinner, capturing both the state + // of the forks, and the state of the philosophers. let token = net.spawn_token(); commands.spawn(token); info!("Spawning a token..."); @@ -163,8 +162,8 @@ fn spawn_token(mut commands: Commands, net: Res>) { fn init_forks(net: Res>, mut tokens: Query<&mut Token>) { let mut token = tokens.single_mut(); - net.mark::>(&mut token, 1); - net.mark::>(&mut token, 1); + net.mark::>(&mut token, 1); + net.mark::>(&mut token, 1); } fn init_philosopher( @@ -175,6 +174,13 @@ fn init_philosopher( net.mark::>(&mut token, 1); } +const fn side() -> &'static str { + match LR { + LEFT => "left", + RIGHT => "right", + } +} + fn take_fork( net: Res>, mut tokens: Query<&mut Token>, @@ -188,16 +194,16 @@ fn take_fork( } } -fn return_fork( +fn wash_fork( net: Res>, mut tokens: Query<&mut Token>, ) { let mut token = tokens.single_mut(); - if let Ok(()) = net.fire::>(token.bypass_change_detection()) { + if let Ok(()) = net.fire::>(token.bypass_change_detection()) { token.set_changed(); - info!("Philosopher {N} returned the {} fork.", side::()); + info!("Philosopher {N} washed the {} fork.", side::()); } else { - warn!("Philosopher {N} cannot return the {} fork.", side::()); + warn!("Philosopher {N} cannot wash the {} fork.", side::()); } } @@ -212,7 +218,7 @@ fn eat( } else if net.marks::>(&token) > 0 { warn!("Philosopher {N} is already eating."); } else { - warn!("Philosopher {N} cannot eat without both forks."); + warn!("Philosopher {N} cannot eat without both clean forks."); } } @@ -236,85 +242,116 @@ fn despawn_token(mut commands: Commands, tokens: Query) { +fn show_manual(mut terms: Query<&mut Terminal>) { let mut term = terms.single_mut(); let ys = &mut (0..TERM_SIZE[1]).rev().take(19); let mut y = || ys.next().unwrap(); - term.put_string([0, y()], " DINING PHILOSOPHERS "); + term.put_string([0, y()], " DINING PHILOSOPHERS "); term.put_string([0, y()], " "); - term.put_string([0, y()], " Controls "); + term.put_string([0, y()], " Controls "); term.put_string([0, y()], " "); - term.put_string([0, y()], " Philosopher 0 Philosopher 1 "); + term.put_string([0, y()], " Philosopher 0 Philosopher 1 "); term.put_string([0, y()], " "); - term.put_string([0, y()], " take left fork take left fork "); + term.put_string([0, y()], " take left fork take left fork "); term.put_string([0, y()], " "); - term.put_string([0, y()], " take right fork take right fork "); + term.put_string([0, y()], " take right fork take right fork "); term.put_string([0, y()], " "); - term.put_string([0, y()], " start eating start eating "); + term.put_string([0, y()], " start eating start eating "); term.put_string([0, y()], " "); - term.put_string([0, y()], " finish eating finish eating "); + term.put_string([0, y()], " finish eating finish eating "); term.put_string([0, y()], " "); - term.put_string([0, y()], " return left fork return left fork "); + term.put_string([0, y()], " wash left fork wash left fork "); term.put_string([0, y()], " "); - term.put_string([0, y()], " return right fork return right fork "); + term.put_string([0, y()], " wash right fork wash right fork "); term.put_string([0, y()], " "); term.put_string([0, y()], "--------------------------------------------------"); } #[rustfmt::skip] -fn draw_net(mut terms: Query<&mut Terminal>) { +fn show_net(mut terms: Query<&mut Terminal>) { let mut term = terms.single_mut(); let ys = &mut (0..TERM_SIZE[1]).rev().skip(20); let mut y = || ys.next().unwrap(); - term.put_string([0, y()], r" RET FINISH RET "); + term.put_string([0, y()], r" WASH FINISH WASH "); term.put_string([0, y()], r" "); - term.put_string([0, y()], r" ----[ ]<---( )<-----[ ]----->( )--->[ ]---- "); - term.put_string([0, y()], r" | used ^ \ used | "); + term.put_string([0, y()], r" +---[#]<---( )<-----[#]----->( )--->[#]---+ "); + term.put_string([0, y()], r" | dirty ^ \ dirty | "); term.put_string([0, y()], r" | / \ | "); term.put_string([0, y()], r" | / v | "); term.put_string([0, y()], r" | ( ) 0 ( ) | "); - term.put_string([0, y()], r" | eating ^ / thinking | "); + term.put_string([0, y()], r" | eating^ /thinking | "); term.put_string([0, y()], r" | TAKE \ / TAKE | "); term.put_string([0, y()], r" | \ v | "); - term.put_string([0, y()], r" | [ ]--->( )----->[ ]<-----( )<---[ ] | "); - term.put_string([0, y()], r" | ^ taken taken ^ | "); + term.put_string([0, y()], r" | [#]--->( )----->[#]<-----( )<---[#] | "); + term.put_string([0, y()], r" | ^ taken taken ^ | "); term.put_string([0, y()], r" | / EAT \ | "); term.put_string([0, y()], r" V/ \V "); - term.put_string([0, y()], r"L ( ) ( ) R"); + term.put_string([0, y()], r" L( )clean clean( )R "); term.put_string([0, y()], r" ^\ /^ "); term.put_string([0, y()], r" | \ EAT / | "); term.put_string([0, y()], r" | v v | "); - term.put_string([0, y()], r" | [ ]--->( )----->[ ]<-----( )<---[ ] | "); - term.put_string([0, y()], r" | taken / ^ taken | "); + term.put_string([0, y()], r" | [#]--->( )----->[#]<-----( )<---[#] | "); + term.put_string([0, y()], r" | taken / ^ taken | "); term.put_string([0, y()], r" | TAKE / \ TAKE | "); term.put_string([0, y()], r" | v \ | "); term.put_string([0, y()], r" | ( ) 1 ( ) | "); - term.put_string([0, y()], r" | eating \ ^ thinking | "); + term.put_string([0, y()], r" | eating\ ^thinking | "); term.put_string([0, y()], r" | \ / | "); term.put_string([0, y()], r" | v / | "); - term.put_string([0, y()], r" ----[ ]<---( )<-----[ ]----->( )--->[ ]---- "); - term.put_string([0, y()], r" "); - term.put_string([0, y()], r" RET FINISH RET "); + term.put_string([0, y()], r" +---[#]<---( )<-----[#]----->( )--->[#]---+ "); + term.put_string([0, y()], r" dirty dirty "); + term.put_string([0, y()], r" WASH FINISH WASH "); +} + +const W: usize = TERM_SIZE[0]; +const H: usize = TERM_SIZE[1] - 20; + +const ROWS: [usize; 7] = [H - 3, H - 7, H - 11, H / 2, 11, 7, 3]; +const COLS: [usize; 9] = [3, 8, 15, 19, W / 2 - 1, W - 21, W - 17, W - 10, W - 5]; + +fn show_place>( + net: &PetriNet, + token: &Token, + term: &mut Terminal, + [x, y]: [usize; 2], +) { + if net.marks::

(token) > 0 { + term.put_char([x, y], '*'.fg(Color::YELLOW)); + term.put_char([x - 1, y], '('.fg(Color::YELLOW)); + term.put_char([x + 1, y], ')'.fg(Color::YELLOW)); + } else { + term.clear_box([x, y], [1, 1]); + term.put_char([x - 1, y], '('.fg(Color::WHITE)); + term.put_char([x + 1, y], ')'.fg(Color::WHITE)); + } } -fn show_free_fork( +fn show_trans>( + net: &PetriNet, + token: &Token, + term: &mut Terminal, + [x, y]: [usize; 2], +) { + if net.enabled::(token) { + term.put_char([x, y], '#'.fg(Color::GREEN)); + term.put_char([x - 1, y], '['.fg(Color::GREEN)); + term.put_char([x + 1, y], ']'.fg(Color::GREEN)); + } else { + term.clear_box([x, y], [1, 1]); + term.put_char([x - 1, y], '['.fg(Color::WHITE)); + term.put_char([x + 1, y], ']'.fg(Color::WHITE)); + } +} + +fn show_clean_fork( net: Res>, tokens: Query<&Token, Changed>>, mut terms: Query<&mut Terminal>, ) { - let mut term = terms.single_mut(); - let coord = if LR { - [3, TERM_SIZE[1] - 20 - 15] - } else { - [TERM_SIZE[0] - 5, TERM_SIZE[1] - 20 - 15] - }; if let Ok(token) = tokens.get_single() { - if net.marks::>(token) > 0 { - info!("{} fork: on the table", side::()); - term.put_char(coord, '*'.fg(Color::WHITE)); - } else { - term.clear_box(coord, [1, 1]); - } + let mut term = terms.single_mut(); + let x = if LR { COLS[0] } else { COLS[8] }; + show_place::>(&net, token, &mut term, [x, ROWS[3]]); } } @@ -324,97 +361,43 @@ fn show_taken_fork( mut terms: Query<&mut Terminal>, ) { let mut term = terms.single_mut(); - let x = if LR { 15 } else { TERM_SIZE[0] - 17 }; - let y_taken = match N { - 0 => TERM_SIZE[1] - 20 - 11, - 1 => TERM_SIZE[1] - 20 - 19, - _ => unreachable!(), - }; - let y_used = match N { - 0 => TERM_SIZE[1] - 20 - 3, - 1 => TERM_SIZE[1] - 20 - 27, - _ => unreachable!(), - }; if let Ok(token) = tokens.get_single() { - if net.marks::>(token) > 0 { - info!("{} fork: taken by Philosopher {N}", side::()); - term.put_char([x, y_taken], '*'.fg(Color::WHITE)); - } else { - term.clear_box([x, y_taken], [1, 1]); - } - if net.marks::>(token) > 0 { - info!("{} fork: used by Philosopher {N}", side::()); - term.put_char([x, y_used], '*'.fg(Color::WHITE)); - } else { - term.clear_box([x, y_used], [1, 1]); - } - let x = if LR { 8 } else { TERM_SIZE[0] - 10 }; - if net.enabled::>(token) { - term.put_char([x, y_taken], '='.fg(Color::GREEN).bg(Color::GREEN)); - } else { - term.clear_box([x, y_taken], [1, 1]); - } - if net.enabled::>(token) { - term.put_char([x, y_used], '='.fg(Color::GREEN).bg(Color::GREEN)); - } else { - term.clear_box([x, y_used], [1, 1]); - } + let x = if LR { COLS[2] } else { COLS[6] }; + let y = if N == 0 { ROWS[2] } else { ROWS[4] }; + show_place::>(&net, token, &mut term, [x, y]); + let x = if LR { COLS[1] } else { COLS[7] }; + show_trans::>(&net, token, &mut term, [x, y]); } } -fn show_philosopher( +fn show_dirty_fork( net: Res>, tokens: Query<&Token, Changed>>, mut terms: Query<&mut Terminal>, ) { let mut term = terms.single_mut(); - let x_eating = 19; - let x_thinking = 29; - let y = match N { - 0 => TERM_SIZE[1] - 20 - 7, - 1 => TERM_SIZE[1] - 20 - 23, - _ => unreachable!(), - }; if let Ok(token) = tokens.get_single() { - if net.marks::>(token) > 0 { - info!("Philosopher {N}: eating"); - term.put_char([x_eating, y], '*'.fg(Color::WHITE)); - } else { - term.clear_box([x_eating, y], [1, 1]); - } - if net.marks::>(token) > 0 { - info!("Philosopher {N}: thinking"); - term.put_char([x_thinking, y], '*'.fg(Color::WHITE)); - } else { - term.clear_box([x_thinking, y], [1, 1]); - } - let x = 24; - let y_eat = match N { - 0 => TERM_SIZE[1] - 20 - 11, - 1 => TERM_SIZE[1] - 20 - 19, - _ => unreachable!(), - }; - if net.enabled::>(token) { - term.put_char([x, y_eat], '='.fg(Color::GREEN).bg(Color::GREEN)); - } else { - term.clear_box([x, y_eat], [1, 1]); - } - let y_think = match N { - 0 => TERM_SIZE[1] - 20 - 3, - 1 => TERM_SIZE[1] - 20 - 27, - _ => unreachable!(), - }; - if net.enabled::>(token) { - term.put_char([x, y_think], '='.fg(Color::GREEN).bg(Color::GREEN)); - } else { - term.clear_box([x, y_think], [1, 1]); - } + let x = if LR { COLS[2] } else { COLS[6] }; + let y = if N == 0 { ROWS[0] } else { ROWS[6] }; + show_place::>(&net, token, &mut term, [x, y]); + let x = if LR { COLS[1] } else { COLS[7] }; + show_trans::>(&net, token, &mut term, [x, y]); } } -const fn side() -> &'static str { - match LR { - true => "left", - false => "right", +fn show_philosopher( + net: Res>, + tokens: Query<&Token, Changed>>, + mut terms: Query<&mut Terminal>, +) { + let mut term = terms.single_mut(); + if let Ok(token) = tokens.get_single() { + let y = if N == 0 { ROWS[1] } else { ROWS[5] }; + show_place::>(&net, token, &mut term, [COLS[3], y]); + show_place::>(&net, token, &mut term, [COLS[5], y]); + let y = if N == 0 { ROWS[2] } else { ROWS[4] }; + show_trans::>(&net, token, &mut term, [COLS[4], y]); + let y = if N == 0 { ROWS[0] } else { ROWS[6] }; + show_trans::>(&net, token, &mut term, [COLS[4], y]); } } diff --git a/src/lib.rs b/src/lib.rs index 750f7d0..d5b2f6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,9 @@ #![warn(clippy::pedantic)] #![allow(clippy::module_name_repetitions)] +pub use crate::net::{NetId, Nn, PetriNet}; pub use crate::net::place::{Place, PlaceId, PlaceMetadata, Pn}; pub use crate::net::trans::{Arcs, Tn, Trans, TransId, TransMetadata, W}; -pub use crate::net::{NetId, Nn, PetriNet}; pub use crate::plugin::PetriNetPlugin; pub use crate::token::Token; diff --git a/src/net.rs b/src/net.rs index 09334a8..a50f980 100644 --- a/src/net.rs +++ b/src/net.rs @@ -6,8 +6,8 @@ use educe::Educe; use crate::net::place::{Place, Places}; use crate::net::trans::{Arcs, Flows, Inflow, Outflow, Trans, TransId, Transitions}; -use crate::token::Token; use crate::PlaceId; +use crate::token::Token; pub mod place; pub mod trans; @@ -181,9 +181,7 @@ impl PetriNet { #[cfg(test)] mod tests { - use super::place::Place; - use super::trans::{Trans, W}; - use super::{NetId, PetriNet}; + use crate::{NetId, PetriNet, Place, Trans, W}; enum Minimal {} enum ProdCons {} diff --git a/src/net/place.rs b/src/net/place.rs index 4a8d2bd..55fda16 100644 --- a/src/net/place.rs +++ b/src/net/place.rs @@ -1,11 +1,12 @@ //! Petri net places. -use bevy_utils::StableHashMap; -use educe::Educe; use std::any::{type_name, TypeId}; use std::borrow::Cow; use std::marker::PhantomData; +use bevy_utils::StableHashMap; +use educe::Educe; + use crate::net::NetId; /// Place belonging to a Petri net. diff --git a/src/net/trans.rs b/src/net/trans.rs index f6b2978..e5a9544 100644 --- a/src/net/trans.rs +++ b/src/net/trans.rs @@ -1,13 +1,14 @@ //! Petri net transitions. -use bevy_utils::StableHashMap; -use educe::Educe; use std::any::{type_name, TypeId}; use std::borrow::Cow; use std::marker::PhantomData; -use crate::net::place::{Place, PlaceId, PlaceMetadata}; +use bevy_utils::StableHashMap; +use educe::Educe; + use crate::net::NetId; +use crate::net::place::{Place, PlaceId, PlaceMetadata}; /// Transition belonging to a Petri net. pub trait Trans: Send + Sync + 'static {} diff --git a/src/token.rs b/src/token.rs index 3e6eaef..a6dd52e 100644 --- a/src/token.rs +++ b/src/token.rs @@ -56,11 +56,7 @@ impl Token { #[cfg(test)] mod tests { - use crate::net::trans::{Trans, W}; - use crate::net::PetriNet; - use crate::Place; - - use super::*; + use crate::{NetId, PetriNet, Place, Trans, W}; enum N0 {} enum P0 {}