use std::collections::HashSet;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::action::{BoxedAction, passthrough};
use crate::arc::{Inhibitor, Read, Reset};
use crate::input::In;
use crate::output::{Out, all_places, find_forward_inputs, find_timeout};
use crate::place::PlaceRef;
use crate::timing::{Timing, immediate};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TransitionId(u64);
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
impl TransitionId {
fn next() -> Self {
Self(NEXT_ID.fetch_add(1, Ordering::Relaxed))
}
}
#[derive(Clone)]
pub struct Transition {
id: TransitionId,
name: Arc<str>,
input_specs: Vec<In>,
output_spec: Option<Out>,
inhibitors: Vec<Inhibitor>,
reads: Vec<Read>,
resets: Vec<Reset>,
timing: Timing,
action_timeout: Option<u64>,
action: BoxedAction,
priority: i32,
input_places: HashSet<PlaceRef>,
read_places: HashSet<PlaceRef>,
output_places: HashSet<PlaceRef>,
}
impl Transition {
pub fn id(&self) -> TransitionId {
self.id
}
pub fn name(&self) -> &str {
&self.name
}
pub fn name_arc(&self) -> &Arc<str> {
&self.name
}
pub fn input_specs(&self) -> &[In] {
&self.input_specs
}
pub fn output_spec(&self) -> Option<&Out> {
self.output_spec.as_ref()
}
pub fn inhibitors(&self) -> &[Inhibitor] {
&self.inhibitors
}
pub fn reads(&self) -> &[Read] {
&self.reads
}
pub fn resets(&self) -> &[Reset] {
&self.resets
}
pub fn timing(&self) -> &Timing {
&self.timing
}
pub fn action_timeout(&self) -> Option<u64> {
self.action_timeout
}
pub fn has_action_timeout(&self) -> bool {
self.action_timeout.is_some()
}
pub fn action(&self) -> &BoxedAction {
&self.action
}
pub fn priority(&self) -> i32 {
self.priority
}
pub fn input_places(&self) -> &HashSet<PlaceRef> {
&self.input_places
}
pub fn read_places(&self) -> &HashSet<PlaceRef> {
&self.read_places
}
pub fn output_places(&self) -> &HashSet<PlaceRef> {
&self.output_places
}
pub fn builder(name: impl Into<Arc<str>>) -> TransitionBuilder {
TransitionBuilder::new(name)
}
}
impl std::fmt::Debug for Transition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Transition")
.field("id", &self.id)
.field("name", &self.name)
.field("timing", &self.timing)
.field("priority", &self.priority)
.finish()
}
}
impl PartialEq for Transition {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Transition {}
impl std::hash::Hash for Transition {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl std::fmt::Display for Transition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Transition[{}]", self.name)
}
}
pub struct TransitionBuilder {
name: Arc<str>,
input_specs: Vec<In>,
output_spec: Option<Out>,
inhibitors: Vec<Inhibitor>,
reads: Vec<Read>,
resets: Vec<Reset>,
timing: Timing,
action: BoxedAction,
priority: i32,
}
impl TransitionBuilder {
pub fn new(name: impl Into<Arc<str>>) -> Self {
Self {
name: name.into(),
input_specs: Vec::new(),
output_spec: None,
inhibitors: Vec::new(),
reads: Vec::new(),
resets: Vec::new(),
timing: immediate(),
action: passthrough(),
priority: 0,
}
}
pub fn input(mut self, spec: In) -> Self {
self.input_specs.push(spec);
self
}
pub fn inputs(mut self, specs: Vec<In>) -> Self {
self.input_specs.extend(specs);
self
}
pub fn output(mut self, spec: Out) -> Self {
self.output_spec = Some(spec);
self
}
pub fn inhibitor(mut self, inh: Inhibitor) -> Self {
self.inhibitors.push(inh);
self
}
pub fn inhibitors(mut self, inhs: Vec<Inhibitor>) -> Self {
self.inhibitors.extend(inhs);
self
}
pub fn read(mut self, r: Read) -> Self {
self.reads.push(r);
self
}
pub fn reads(mut self, rs: Vec<Read>) -> Self {
self.reads.extend(rs);
self
}
pub fn reset(mut self, r: Reset) -> Self {
self.resets.push(r);
self
}
pub fn resets(mut self, rs: Vec<Reset>) -> Self {
self.resets.extend(rs);
self
}
pub fn timing(mut self, timing: Timing) -> Self {
self.timing = timing;
self
}
pub fn action(mut self, action: BoxedAction) -> Self {
self.action = action;
self
}
pub fn priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
pub fn build(self) -> Transition {
if let Some(ref out) = self.output_spec {
let input_place_names: HashSet<_> =
self.input_specs.iter().map(|s| s.place_name()).collect();
for (from, _) in find_forward_inputs(out) {
assert!(
input_place_names.contains(from.name()),
"Transition '{}': ForwardInput references non-input place '{}'",
self.name,
from.name()
);
}
}
let action_timeout = self
.output_spec
.as_ref()
.and_then(|o| find_timeout(o).map(|(ms, _)| ms));
let input_places: HashSet<PlaceRef> =
self.input_specs.iter().map(|s| s.place().clone()).collect();
let read_places: HashSet<PlaceRef> = self.reads.iter().map(|r| r.place.clone()).collect();
let output_places: HashSet<PlaceRef> = self
.output_spec
.as_ref()
.map(all_places)
.unwrap_or_default();
Transition {
id: TransitionId::next(),
name: self.name,
input_specs: self.input_specs,
output_spec: self.output_spec,
inhibitors: self.inhibitors,
reads: self.reads,
resets: self.resets,
timing: self.timing,
action_timeout,
action: self.action,
priority: self.priority,
input_places,
read_places,
output_places,
}
}
}
pub(crate) fn rebuild_with_action(t: &Transition, action: BoxedAction) -> Transition {
let mut builder = Transition::builder(Arc::clone(&t.name))
.timing(t.timing)
.priority(t.priority)
.action(action)
.inputs(t.input_specs.clone())
.inhibitors(t.inhibitors.clone())
.reads(t.reads.clone())
.resets(t.resets.clone());
if let Some(ref out) = t.output_spec {
builder = builder.output(out.clone());
}
builder.build()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::input::one;
use crate::output::out_place;
use crate::place::Place;
#[test]
fn transition_builder_basic() {
let p_in = Place::<i32>::new("in");
let p_out = Place::<i32>::new("out");
let t = Transition::builder("test")
.input(one(&p_in))
.output(out_place(&p_out))
.build();
assert_eq!(t.name(), "test");
assert_eq!(t.input_specs().len(), 1);
assert!(t.output_spec().is_some());
assert_eq!(t.timing(), &Timing::Immediate);
assert_eq!(t.priority(), 0);
}
#[test]
fn transition_identity() {
let t1 = Transition::builder("test").build();
let t2 = Transition::builder("test").build();
assert_ne!(t1, t2); }
#[test]
fn transition_places_computed() {
let p_in = Place::<i32>::new("in");
let p_out = Place::<i32>::new("out");
let p_read = Place::<String>::new("ctx");
let t = Transition::builder("test")
.input(one(&p_in))
.output(out_place(&p_out))
.read(crate::arc::read(&p_read))
.build();
assert!(t.input_places().contains(&PlaceRef::new("in")));
assert!(t.output_places().contains(&PlaceRef::new("out")));
assert!(t.read_places().contains(&PlaceRef::new("ctx")));
}
#[test]
#[should_panic(expected = "ForwardInput references non-input place")]
fn forward_input_validation() {
let from = Place::<i32>::new("not-an-input");
let to = Place::<i32>::new("to");
Transition::builder("test")
.output(crate::output::forward_input(&from, &to))
.build();
}
#[test]
fn action_timeout_detected() {
let p = Place::<i32>::new("timeout");
let t = Transition::builder("test")
.output(crate::output::timeout_place(5000, &p))
.build();
assert_eq!(t.action_timeout(), Some(5000));
}
#[test]
fn no_action_timeout() {
let p = Place::<i32>::new("out");
let t = Transition::builder("test").output(out_place(&p)).build();
assert_eq!(t.action_timeout(), None);
}
}