Skip to content

Commit

Permalink
Add simple Bevy integration, add place-transition arc data
Browse files Browse the repository at this point in the history
  • Loading branch information
nxsaken committed Jan 3, 2024
1 parent 976495d commit a556c51
Show file tree
Hide file tree
Showing 10 changed files with 607 additions and 382 deletions.
65 changes: 50 additions & 15 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,55 @@
name: Rust
on: [push, pull_request]

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

env:
CARGO_TERM_COLOR: always
name: CI

jobs:
build:
check:
name: check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Install alsa and udev
run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev
if: runner.os == 'linux'
- run: cargo check

test:
name: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
- name: Install alsa and udev
run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev
if: runner.os == 'linux'
- run: cargo test

fmt:
name: rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: rustfmt
- run: cargo fmt --all -- --check

clippy:
name: clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose --all-features
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
components: clippy
- name: Install alsa and udev
run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev
if: runner.os == 'linux'
- run: cargo clippy -- -D warnings
11 changes: 9 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "petnat"
version = "0.0.1"
version = "0.0.2"
authors = ["Nurzhan Sakén <[email protected]>"]
edition = "2021"
description = "A Petri net plugin for Bevy Engine."
Expand All @@ -11,4 +11,11 @@ keywords = ["bevy", "petri", "petri-net", "state-machine", "gamedev"]
categories = ["simulation", "game-development", "science"]

[dependencies]
# bevy = { version = "0.12", default-features = false }
bevy_ecs = { version = "0.12", default-features = false }
bevy_app = { version = "0.12" }
bevy_utils = { version = "0.12" }

[dev-dependencies]
bevy = { version = "0.12" }

[features]
77 changes: 9 additions & 68 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,81 +4,22 @@ A Petri net plugin for [Bevy Engine](https://github.com/bevyengine/bevy). 🍾

## About

`petnat` will equip you with [Petri nets](https://en.wikipedia.org/wiki/Petri_net) to use in your Bevy projects.
`petnat` equips you with [Petri nets](https://en.wikipedia.org/wiki/Petri_net) to use in your Bevy projects.
It's a powerful way to model states, processes, resources, and more.

This is a very experimental project, and actual Bevy integration is unavailable yet.
Feedback and thoughts are welcome!
This is a very experimental project, and I mostly started it because I wanted to play with Petri nets
and improve my Rust. I am not sure about the possible usefulness of this plugin, but I hope to discover
how I can improve it with time and usage.

## Rough idea

1. Model something using places and transitions.
1. Build a model using places and transitions.
2. Define a `PetriNet<NetId>` resource.
3. Add a `Token<NetId>` component to an entity.
4. Mark places with the `Token` according to the context.
5. Transitions will be scheduled or triggered with Events (needs experimenting).
4. Mark some (probably initial) places with the `Token` according to the model.
5. Fire transitions when it makes sense according to the model.
6. Implement game logic based on the current marking of the `Token`.
7. Hopefully add static analysis on the net somehow via Rust's type system?

## Example
## Examples

Here is an example of how Petri nets are defined and used.

```rust
// impl NetId
enum Choice {}

// impl Place<Choice>
enum P0 {}
enum P1 {}
enum P2 {}
enum P3 {}

// impl Trans<Choice>
enum T0 {}
enum T1 {}

/// Two transitions sharing a preset place (P1).
/// When one of them fires, the other ceases to be enabled.
///
/// ## Before (asterisks denote the current marking):
///
/// (P0)* --> |T0| -\
/// (P1)* -< >-> (P3)
/// (P2)* --> |T1| -/
///
/// ## After T0 fires:
///
/// (P0) --> |T0| -\
/// (P1) -< >-> (P3)*
/// (P2)* --> |T1| -/
fn choice() -> PetriNet<Choice> {
PetriNet::empty()
.add_place::<P0>()
.add_place::<P1>()
.add_place::<P2>()
.add_place::<P3>()
.add_trans::<
T0,
((P0, W1), (P1, W1)),
(P3, W1) // W1, W2, W3, ... – arc weights
>()
.add_trans::<
T1,
((P1, W1), (P2, W1)),
(P3, W1)
>()
}

fn test_choice() {
let net = choice();
let mut token = net.spawn_token();
token.mark::<P0>(1);
token.mark::<P1>(1);
token.mark::<P2>(1);
assert!(net.is_enabled::<T0>(&token));
assert!(net.is_enabled::<T1>(&token));
assert!(net.fire::<T0>(&mut token).is_some());
assert!(!net.is_enabled::<T1>(&token));
}
```
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.
82 changes: 82 additions & 0 deletions examples/simple.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//! A simple Petri net being manipulated via systems.
use bevy::input::common_conditions::input_just_pressed;
use bevy::prelude::*;
use petnat::{NetId, Nn, PetriNet, PetriNetPlugin, Place, Pn, Tn, Token, W};
use std::any::type_name;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
// (P0) -\ 1 1
// >-> |T0| -> (P2)
// (P1) -/ 2
.add_plugins(PetriNetPlugin::<Nn<0>> {
build: |builder| {
builder
.add_place::<Pn<0>>()
.add_place::<Pn<1>>()
.add_place::<Pn<2>>()
// T0 requires 1 token in P0 and 2 tokens in P1 to be enabled
// and it will produce 1 token in P2 when fired
.add_trans::<Tn<0>, ((Pn<0>, W<1>), (Pn<1>, W<2>)), (Pn<2>, W<1>)>()
},
})
.add_systems(Startup, spawn_token::<Nn<0>>)
.add_systems(
Update,
(
// press 1 and 2 to mark `P0` and `P1`
mark::<Nn<0>, Pn<0>>.run_if(input_just_pressed(KeyCode::Key1)),
mark::<Nn<0>, Pn<1>>.run_if(input_just_pressed(KeyCode::Key2)),
// press T to fire `T0`
trans_t0::<Nn<0>>.run_if(input_just_pressed(KeyCode::T)),
print_net::<Nn<0>>,
),
)
.run();
}

fn spawn_token<Net: NetId>(mut commands: Commands, net: Res<PetriNet<Net>>) {
// A token can represent the current state of some game object,
// or some sort of resource
commands.spawn(net.spawn_token());
info!("Spawning a token...");
}

fn mark<Net: NetId, P: Place<Net>>(net: Res<PetriNet<Net>>, mut tokens: Query<&mut Token<Net>>) {
for mut token in &mut tokens {
net.mark::<P>(&mut token, 1);
// TODO: better place/trans names
let (_, name) = type_name::<P>()
.rsplit_once(':')
.unwrap_or(("", type_name::<P>()));
info!("{} marked!", name);
}
}

fn trans_t0<Net: NetId>(net: Res<PetriNet<Net>>, mut tokens: Query<&mut Token<Net>>) {
for mut token in &mut tokens {
// TODO: better handling of change detection
if let Some(()) = net.fire::<Tn<0>>(token.bypass_change_detection()) {
info!("T0 fired!");
token.set_changed();
} else {
info!("T0 cannot fire! (Need: 1 in P0 + 2 in P1)");
}
}
}

fn print_net<Net: NetId>(
net: Res<PetriNet<Net>>,
tokens: Query<(Entity, &Token<Net>), Changed<Token<Net>>>,
) {
for (id, token) in &tokens {
info!("== TOKEN {:?} STATE ==", id);
info!("P0: {}", token.marks::<Pn<0>>());
info!("P1: {}", token.marks::<Pn<1>>());
info!("T0 enabled: {}", net.enabled::<Tn<0>>(token));
info!("P2: {}", token.marks::<Pn<2>>());
info!("=====================");
}
}
21 changes: 9 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
//! A Petri net plugin for [Bevy Engine](https://github.com/bevyengine/bevy).
//#![doc = include_str!("../README.md")]
#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#![deny(clippy::all)]

pub mod net;
pub mod plugin;
pub mod token;
pub use crate::net::place::{Place, PlaceId, Pn};
pub use crate::net::trans::{Arcs, Tn, Trans, TransId, W};
pub use crate::net::{NetId, Nn, PetriNet, PetriNetBuilder};
pub use crate::plugin::PetriNetPlugin;
pub use crate::token::Token;

// todo:
// - petri net resource / component
// - tokens are components, so they can be attached to entities
// - state management via petri nets (total game state & entity states)
// - safe, special cases of petri nets explored
// - workflow patterns
// - one petri net reused by multiple tokens (colored)
mod net;
mod plugin;
mod token;

#[cfg(test)]
mod tests {}
Loading

0 comments on commit a556c51

Please sign in to comment.