pns 0.18.3

A simple and efficient library wrapper for simulating a minimal form of petri nets
Documentation
use std::{
    fs::File,
    io::{Error, ErrorKind, Write},
    mem::MaybeUninit,
    path::Path,
};

use crate::{
    FireChanges, TransitionList, TransitionView,
    core::{
        ids::{Pid, Tid},
        utils::read_values,
    },
    sys::{
        functions::state::*,
        types::{Net, State},
    },
};

/// The error type when state initialization fails.
#[derive(Debug)]
pub struct StateInitializationError;

impl State {
    /// Creates a new state that is compatible with the supplied `net`.
    pub fn new(net: &Net) -> Self {
        let mut state = MaybeUninit::uninit();
        unsafe {
            pnsCreateState(state.as_mut_ptr(), net);
            state.assume_init()
        }
    }

    /// Loads a state from a file and ensures its compatibility with the supplied `net`.
    pub fn load(net: &Net, filename: &Path) -> Result<Self, Error> {
        let values = read_values(filename)?;

        match Self::from_values(net, &values) {
            Ok(state) => Ok(state),
            Err(StateInitializationError) => Err(Error::new(
                ErrorKind::InvalidData,
                "Supplied values do not represent a valid petri net state",
            )),
        }
    }

    /// Reloads the state from file and ensures its compatibility with the supplied `net`.
    pub fn reload(&mut self, net: &Net, filename: &Path) -> Result<(), Error> {
        *self = Self::load(net, filename)?;
        Ok(())
    }

    /// Sets a state from values that is compatible with the supplied `net`.
    pub fn from_values(net: &Net, values: &[u32]) -> Result<Self, StateInitializationError> {
        let mut state = MaybeUninit::uninit();
        if unsafe { pnsLoadState(state.as_mut_ptr(), net, values.len(), &raw const values[0]) } {
            Ok(unsafe { state.assume_init() })
        } else {
            Err(StateInitializationError)
        }
    }

    /// Resets a state from values that is compatible with the supplied `net`.
    pub fn reset(&mut self, net: &Net, values: &[u32]) -> Result<(), StateInitializationError> {
        *self = Self::from_values(net, values)?;
        Ok(())
    }

    /// Refreshes the state to keep it in sync with the edited `net`.
    ///
    /// # Safety
    ///
    /// The `net` must be compatible with the state.
    pub unsafe fn refresh(&mut self, net: &Net) {
        unsafe {
            pnsState_refresh(self, net);
        }
    }

    /// Clones a state that is in sync with the supplied `net`.
    ///
    /// # Safety
    ///
    /// The state must be in sync with the supplied `net`.
    pub unsafe fn clone(&self, net: &Net) -> Self {
        let mut state = MaybeUninit::uninit();
        unsafe {
            pnsCloneState(state.as_mut_ptr(), self, net);
            state.assume_init()
        }
    }

    /// Save the current state of the simulation.
    ///
    /// # Safety
    ///
    /// The state must be in sync with the supplied `net`.
    pub unsafe fn save(&self, net: &Net, filename: &Path) -> Result<(), Error> {
        let mut file = File::create(filename)?;
        for &count in unsafe { self.call_counts(net) } {
            file.write_all(&(count as u32).to_le_bytes())?;
        }

        Ok(())
    }

    /// Get the current call count of the specified transition.
    /// Counts are decreased again when playing backward.
    ///
    /// # Safety
    ///
    /// The state must be in sync with the supplied `net`.
    pub unsafe fn call_count(&self, net: &Net, tid: Tid) -> usize {
        unsafe { self.call_counts(net)[tid.index] }
    }

    /// Get the current token count of the specified place.
    ///
    /// # Safety
    ///
    /// The state must be in sync with the supplied `net`.
    pub unsafe fn token_count(&self, net: &Net, pid: Pid) -> usize {
        unsafe { self.token_counts(net)[pid.index] }
    }

    /// List the current call counts of all transitions.
    /// Counts are counted down again when playing backward.
    ///
    /// # Safety
    ///
    /// The state must be in sync with the supplied `net`.
    pub unsafe fn call_counts(&self, net: &Net) -> &[usize] {
        unsafe { std::slice::from_raw_parts(self.calls.counts, net.transitions.count) }
    }

    /// List the current token counts of all transitions.
    ///
    /// # Safety
    ///
    /// The state must be in sync with the supplied `net`.
    pub unsafe fn token_counts(&self, net: &Net) -> &[usize] {
        unsafe { std::slice::from_raw_parts(self.tokens.counts, net.places.count) }
    }

    /// Lists fireable transitions.
    pub fn fireable(&self) -> TransitionView<'_> {
        unsafe { pnsState_transitions(self) }
    }

    /// Lists unfireable transitions.
    pub fn unfireable(&self) -> TransitionView<'_> {
        unsafe { pnsState_transitions_backward(self) }
    }

    /// Just fire the transition `tid`.
    ///
    /// # Safety
    ///
    /// The state must be in sync with the supplied `net`.
    /// Be sure to fire only when you know a transition to be fireable.
    pub unsafe fn fire_unchecked(&mut self, net: &Net, tid: Tid) {
        unsafe { pnsState_fire(self, net, tid.index) };
    }

    /// Just unfire the transition `tid`.
    ///
    /// # Safety
    ///
    /// The state must be in sync with the supplied `net`.
    /// Be sure to unfire only when you know a transition to be unfireable.
    pub unsafe fn unfire_unchecked(&mut self, net: &Net, tid: Tid) {
        unsafe { pnsState_fire_backward(self, net, tid.index) };
    }

    /// Try to fire the transition `tid`.
    /// Only fires when it's fireable.
    /// Returns success.
    pub fn try_fire(&mut self, net: &Net, tid: Tid) -> bool {
        if !self.fireable().contains(tid) {
            return false;
        }
        unsafe { pnsState_fire(self, net, tid.index) };
        true
    }

    /// Try to unfire the transition `tid`.
    /// Only unfires when it's unfireable.
    /// Returns success.
    pub fn try_unfire(&mut self, net: &Net, tid: Tid) -> bool {
        if !self.unfireable().contains(tid) {
            return false;
        }
        unsafe { pnsState_fire_backward(self, net, tid.index) };
        true
    }

    /// Get a list of recently changed transitions since the last call of this method.
    /// Automatically clears that list.
    pub fn changed_transitions(&mut self) -> FireChanges {
        unsafe { pnsState_changedTransitions(self) }
    }

    /// Get a list of recently changed transitions when playing backward since the last call of this method.
    /// Automatically clears that list.
    pub fn changed_transitions_backward(&mut self) -> FireChanges {
        unsafe { pnsState_changedTransitions_backward(self) }
    }
}

impl Drop for State {
    fn drop(&mut self) {
        unsafe {
            pnsDestroyState(self);
        }
    }
}

impl TransitionView<'_> {
    /// Checks if the view is empty.
    pub fn is_empty(&self) -> bool {
        self.count == 0
    }

    /// Checks if the list contains the specified `tid`.
    pub fn contains(&self, tid: Tid) -> bool {
        let mut entry = self.transitions;
        loop {
            let Some(value) = (unsafe { entry.as_mut() }) else {
                return false;
            };

            if tid == Tid::new(value.index) {
                return true;
            }

            entry = value.next;
        }
    }
}

impl Iterator for TransitionView<'_> {
    type Item = Tid;

    fn next(&mut self) -> Option<Tid> {
        let mut tid = MaybeUninit::uninit();
        if unsafe { pnsTransitionView_next(self, tid.as_mut_ptr()) } {
            return Some(unsafe { Tid::new(tid.assume_init()) });
        }

        None
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        (self.count, Some(self.count))
    }
}

impl ExactSizeIterator for TransitionView<'_> {
    fn len(&self) -> usize {
        self.count
    }
}

impl TransitionList {
    /// Checks if the list is empty.
    pub fn is_empty(&self) -> bool {
        self.count == 0
    }

    /// Checks if the list contains the specified `tid`.
    pub fn contains(&self, tid: Tid) -> bool {
        let mut entry = self.transitions;
        loop {
            let Some(value) = (unsafe { entry.as_mut() }) else {
                return false;
            };

            if tid == Tid::new(value.index) {
                return true;
            }

            entry = value.next;
        }
    }
}

impl Iterator for TransitionList {
    type Item = Tid;

    fn next(&mut self) -> Option<Tid> {
        let mut tid = MaybeUninit::uninit();
        if unsafe { pnsTransitionList_next(self, tid.as_mut_ptr()) } {
            return Some(unsafe { Tid::new(tid.assume_init()) });
        }

        None
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        (self.count, Some(self.count))
    }
}

impl ExactSizeIterator for TransitionList {
    fn len(&self) -> usize {
        self.count
    }
}

impl Drop for TransitionList {
    fn drop(&mut self) {
        unsafe {
            pnsDestroyTransitionList(self);
        }
    }
}