Git Product home page Git Product logo

statig's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

statig's Issues

Accessing hardware peripherals from states

Can anyone give a small example of accessing a GPIO or some other peripheral from a state? I got the example blinky working on an ESP32-C3, but I can't get an LED to blink.

Add feature matrix support

Hi! Thanks for the library.

I have some experience with Hierarchial State Machines in C++ world. I've used HSM library: https://github.com/erikzenker/hsm

Could you please compare statig to the HSM? E.g. which feature from HSM are already supported in statig. I hope HSM would be for you at least a good roadmap :)

Thanks in advance!

Error

Hi

I am attempting to run the example (https://github.com/mdeloof/statig/tree/main/examples/macro/basic) as an independent project and receiving this error . Am I missing anything?

Compiling machine v0.1.0 (/Users/chetanconikee/pgithub/machine)
error[E0599]: no method named `uninitialized_state_machine` found for struct `Blinky` in the current scope
  --> src/main.rs:40:43
   |
7  | pub struct Blinky {
   | ----------------- method `uninitialized_state_machine` not found for this struct
...
40 |     let state_machine = Blinky::default().uninitialized_state_machine();
   |                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `Blinky`

For more information about this error, try `rustc --explain E0599`.
error: could not compile `machine` due to previous error

Multiple refrences with different lifetimes as 'Context'

Hi!
I'm liking this library and would like to switch to it but I need to pass multiple references in for context. Creating a type that composes several references like so

struct Context<'a, 'b>{
   foo: &'a mut LargeStruct1,
   bar: &'b mut LargeStruct2
}

won't work since I cant create function-level generic lifetimes. Is there a way around this?

Thanks!

Allow generics to be used in the state machine

I'm trying to add an FnMut to a state machine via:

pub struct Thing<F>
where
    F: FnMut(Signal)
{
    emit: F
}

#[state_machine(initial = "State::init()")]
impl<F> Thing<F>
where
    F: FnMut(Signal),
{

in order to allow a state machine to emit a signal as it handles transitions. Unfortunately, adding generics like that to the state machine appears to cause a proc macro panic that just says error: custom attribute panicked with message: expected function call expression

When I run RUSTFLAGS="-Z proc-macro-backtrace" cargo +nightly check, I get the following lines in the backtrace:

  16:     0x7fe276683569 - core::panicking::panic_display::hbb06a95bf5a75324
  17:     0x7fe27663a60c - syn::parse_quote::parse::h36c31769ce163720
  18:     0x7fe27667b8bb - statig_macro::lower::lower_state::h72f60dda88bc84b7
  19:     0x7fe27664e470 - statig_macro::lower::lower::{{closure}}::hafaf3d3de1282b22
  20:     0x7fe27666c0ab - core::iter::adapters::map::map_fold::{{closure}}::h2a21cabf95abed4d
  21:     0x7fe27665e579 - core::iter::traits::iterator::Iterator::fold::ha24dab897807eb32
  22:     0x7fe27666abc2 - <core::iter::adapters::map::Map<I,F> as core::iter::traits::iterator::Iterator>::fold::h97d4d2d660189e34
  23:     0x7fe27666b79f - core::iter::traits::iterator::Iterator::for_each::h78d7f095ca550b39
  24:     0x7fe276636e9a - <hashbrown::map::HashMap<K,V,S,A> as core::iter::traits::collect::Extend<(K,V)>>::extend::h407fb9dd206eee45
  25:     0x7fe27665d7d5 - <std::collections::hash::map::HashMap<K,V,S> as core::iter::traits::collect::FromIterator<(K,V)>>::from_iter::h0325534979972350
  26:     0x7fe27666b47c - core::iter::traits::iterator::Iterator::collect::hb88b7d1cd8683440
  27:     0x7fe2766784a6 - statig_macro::lower::lower::he8b7b3aa35a21378
  28:     0x7fe27663eaef - statig_macro::state_machine::{{closure}}::h25adf5f267d5cf6b
  29:     0x7fe27666e706 - <core::panic::unwind_safe::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once::haed139dfc15334ae
  30:     0x7fe27665ec9d - std::panicking::try::do_call::haa01aefa55582a9c
  31:     0x7fe27665f0bb - __rust_try
  32:     0x7fe27665e7de - std::panicking::try::h239dda7bdbd68854
  33:     0x7fe276681fed - std::panic::catch_unwind::h2aef9b4fc512d321
  34:     0x7fe276674b70 - proc_macro_error::entry_point::h249af567a823643d
  35:     0x7fe27666898b - statig_macro::state_machine::hfdbd09263af6b545
  36:     0x7fe276656824 - core::ops::function::Fn::call::h98bd3f42fef67228

Unfortunately for some reason I can't seem to get line numbers for the lines that occur within statig_macro, however I tried to narrow down the line causing the issue, and it appears to be this line in lower.rs, in the lower_state function:

let handler_call =
        parse_quote!(#shared_storage_type::#state_handler_name(#(#handler_inputs),*));

Unfortunately I'm not very good with proc macros, so I'm not sure how to further debug this, but hopefully this is helpful?

Alternatively, perhaps I just screwed something up. In any case, let me know if there's anything else I can do to help diagnose this.

Thanks!

Using multiple async state machines in parallel

I'm having trouble making multiple simple async state machines run in tokio::spawn tasks since it requires the Send trait and I don't think I can resolve this inside my crate. I recently saw a commit on cleaning up some trait bounds so I'm unsure if the async HSM have statig::Response<rescources::State> not Send on purpose and I should deal with this on my side.

Am I using a wrong approach to try and create multiple state machines and manage them in some larger state machine? Or should I just make one larger with lots of superstates. I find that having multiple would be nice to independently test how simpler state machines react to their events.

If needed I can supply a minimal reproducible main.rs, for now here is the cargo build error I get:

note: future is not `Send` as this value is used across an await
   --> /home/ivan/.cargo/git/checkouts/statig-9f81b8df0f2647e3/19da112/statig/src/inner.rs:69:13
    |
67  | /         self.state
68  | |             .enter(&mut self.shared_storage, context, enter_levels)
    | |___________________________________________________________________- has type `std::pin::Pin<Box<dyn Future<Output = ()>>>` which is not `Send`
69  |               .await;
    |               ^^^^^^- the value is later dropped here
    |               |
    |               await occurs here, with the value maybe used later
note: required by a bound in `tokio::spawn`
    |
163 |         T: Future + Send + 'static,
    |                     ^^^^ required by this bound in `spawn`

error: future cannot be sent between threads safely
   --> src/rescources/prepare.rs:29:17
    |
29  |                 async move { material_magazine.unload().await },
    |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ future created by async block is not `Send`
    |
    = help: the trait `Send` is not implemented for `dyn Future<Output = statig::Response<rescources::State>>`
note: required by a bound in `tokio::spawn`
   --> /home/ivan/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.26.0/src/task/spawn.rs:163:21
    |
163 |         T: Future + Send + 'static,
    |                     ^^^^ required by this bound in `spawn`

warning: `millimess-1003` (bin "millimess-1003") generated 6 warnings
error: could not compile `millimess-1003` due to 2 previous errors; 6 warnings emitted

Add ability to derive traits on the final state machine

Traits like Clone & serde's (De)serialize come to mind mostly. Something like the proc-macro generating the final state machine's struct directly could work? It'd be nice to have same ability to derive traits similar to deriving them on State/SuperState.

Issue with context lifetimes

Hey all, I've been trying to integrate this library into Bevy for helping managing entity states. However I've encountered some issues with the borrow checker and passing in mutable references via Context to the state machine. I believe the root of the issue is that in IntoStateMachine type Ctx<'ctx> only has a single lifetime, which is not expressive enough to pass around mutable references to structs that contain mutable borrows themselves.

I've tried to reduce the problem to some minimal examples

mod example_broken {
    struct Foo;

    struct Event<'a> {
        value: &'a mut Foo,
    }

    struct Context<'a, 'b> {
        event: &'a mut Event<'b>,
    }

    type Ctx<'a> = Context<'a, 'a>;

    fn takes_context(context: &mut Ctx<'_>) {
        unimplemented!()
    }

    fn example(mut event: Event) {
        // Error: Event does not live long enough
        takes_context(&mut Context { event: &mut event })
    }
}

This example I think shows the root of the problem, the type alias type Ctx<'a> = Context<'a, 'a> forces the lifetime of the borrow 'a in event: &'a mut Event<'b> to be the same as the data borrowed by Event 'b. In this case, Event contains data borrowed from the ECS system, whereas 'a only lives for the lifetime of example. Hence the problem, it wants event in example to life as long as the data borrowed from the ECS system.

If we allow type Ctx<'a> to have two lifetime parameters type Ctx<'a, 'b> this is enough to allow the borrow checker to track the lifetimes independently.

mod example_working {
    struct Foo;

    struct Event<'a> {
        value: &'a mut Foo,
    }

    struct Context<'a, 'b> {
        event: &'a mut Event<'b>,
    }

    type Ctx<'a, 'b> = Context<'a, 'b>;

    fn takes_context(context: &mut Ctx<'_, '_>) {
        unimplemented!()
    }

    fn example(mut event: Event) {
        takes_context(&mut Context { event: &mut event })
    }
}

Another solution I explored was adding lifetime constraints to the function itself, however this makes the function longer a valid system for bevy.

mod example_lifetime_constraints {
    struct Foo;

    struct Event<'a> {
        value: &'a mut Foo,
    }

    struct Context<'a, 'b> {
        event: &'a mut Event<'b>,
    }

    type Ctx<'a> = Context<'a, 'a>;

    fn takes_context(context: &mut Ctx<'_>) {
        unimplemented!()
    }

    fn example<'event, 'value>(event: &'event mut Event<'value>)
    where
        'event: 'value,
    {
        takes_context(&mut Context { event: event })
    }
}

Another approach is to use Rc<RefCell<Event>> which lets us eliminate the compile checking of the problematic borrow of event so that Context only has a single lifetime and is thus compatible with the type Ctx<'ctx> definition.

mod example_ref_cell {
    use std::{cell::RefCell, rc::Rc};

    struct Foo;

    struct Event<'a> {
        value: &'a mut Foo,
    }

    impl<'a> Event<'a> {
        fn mutate(&mut self) {
            unimplemented!()
        }
    }

    struct Context<'a> {
        event: Rc<RefCell<Event<'a>>>,
    }

    type Ctx<'a> = Context<'a>;

    fn takes_context(context: &mut Ctx<'_>) {
        context.event.borrow_mut().mutate()
    }

    fn example(mut event: Event) {
        let rc = Rc::new(RefCell::new(event));

        for i in 1..3 {
            takes_context(&mut Context { event: rc.clone() })
        }
    }
}

Curiously, if you pass the &mut event directly i.e. Context is just a type alias, it works?

mod example_bare {
    struct Foo;

    struct Event<'a> {
        value: &'a mut Foo,
    }

    impl<'a> Event<'a> {
        fn mutate(&mut self) {
            unimplemented!()
        }
    }

    type Context<'a> = Event<'a>;

    type Ctx<'a> = Context<'a>;

    fn takes_context(context: &mut Ctx<'_>) {
        context.mutate()
    }

    fn example(mut event: Event) {
        for i in 1..3 {
            takes_context(&mut event);
        }
    }
}```

Problem with using StateMachine as Bevy Component

Hey all, I noticed the Bevy feature flag when installing statig which allows StateMachine to implement Component however I can't seem to get this to work correctly. I get the error

the trait bound `statig::prelude::StateMachine<monster::Behaviour>: bevy::prelude::Component` is not satisfied
the following other types implement trait `bevy::prelude::Component`:
  animation_link::AnimationLink
...

Code snippets

#[derive(Bundle)]
pub struct MonsterBundle {
    pub scene: SceneBundle,
    pub speed: Speed,
    pub stats: Stats,
    pub animations: NamedAnimations,
    pub behaviour: StateMachine<Behaviour>,
}

#[derive(Default)]
pub struct Behaviour;

pub enum Event {
    TimerElapsed,
    Start,
    Stop,
    Dance,
}

#[state_machine(initial = "State::idle()")]
impl Behaviour {
    #[state(entry_action = "enter_idle")]
    fn idle(event: &Event) -> Response<State> {
...

Wrapping it in a newtype seems to work correctly

#[derive(Component)]
pub struct BehaviourState(pub StateMachine<Behaviour>);

Is it possible to split state machines across files?

Hi,

I really love the idea and your implementation and was wondering if it's currently possible to define states for a single state machine across multiple files, by having multiple 'impl' blocks? I tried different approaches but couldn't find a working solution. Or is that something that would be feasible and that you would maybe consider implementing?

Thanks!
Valentin

Difficulty getting generics in state to compile

This is tangential to #7, but not completely related. I don't know if I'd consider it a bug, but it's a bit of a pain.

I've been trying to get a state machine working where I have a generic type in the state.

My initial attempt looks something like this:

use std::marker::PhantomData;
use statig::prelude::*;

pub struct Event;

pub enum State<T> {
    Init,
    Other(PhantomData<T>),
}

impl<T> statig::State<Machine> for State<T> {
    fn call_handler(
        &mut self,
        machine: &mut Machine,
        event: &Event,
    ) -> Response<Self> {
        todo!()
    }
}

#[derive(Default)]
pub struct Machine;

impl<T> statig::StateMachine for Machine {
    type Event<'a> = Event;

    type State = State<T>;

    type Superstate<'a> = ();

    const INITIAL: Self::State = State::Init;
}


fn main() {
    let machine = Machine::default().state_machine().init();
}

However I get the error:

error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predicates
  --> src/main.rs:26:6
   |
26 | impl<T> statig::StateMachine for Machine {
   |      ^ unconstrained type parameter

For more information about this error, try `rustc --explain E0207`.

Okay, fair enough. So I add to the Machine itself to constrain it:

use std::marker::PhantomData;
use statig::prelude::*;

pub struct Event;

pub enum State<T> {
    Init,
    Other(PhantomData<T>),
}

impl<T> statig::State<Machine<T>> for State<T> {
    fn call_handler(
        &mut self,
        machine: &mut Machine<T>,
        event: &Event,
    ) -> Response<Self> {
        todo!()
    }
}

#[derive(Default)]
pub struct Machine<T> {
    _marker: PhantomData<T>,
}

impl<T> statig::StateMachine for Machine<T> {
    type Event<'a> = Event;

    type State = State<T>;

    type Superstate<'a> = ();

    const INITIAL: Self::State = State::Init;
}


fn main() {
    let machine = Machine::<u32>::default().state_machine().init();
}

But now I get an even stranger error message:

error[E0309]: the parameter type `T` may not live long enough
  --> src/main.rs:31:27
   |
31 |     type Superstate<'a> = ();
   |                           ^^- help: consider adding a where clause: `where T: 'a`
   |                           |
   |                           ...so that the type `State<T>` will meet its required lifetime bounds

For more information about this error, try `rustc --explain E0309`.

If I change the line to this, as it suggests:

    type Superstate<'a> = () where T: 'a;

I now get a third different error:

error[E0276]: impl has stricter requirements than trait
  --> src/main.rs:31:39
   |
31 |     type Superstate<'a> = () where T: 'a;
   |                                       ^^ impl has extra requirement `T: 'a`

For more information about this error, try `rustc --explain E0276`.

I've tried several things to try getting around the two latter errors, but I've given up sadly. :(

I'm not sure how to get generics to work in state, apart from forking statig and removing every mention of Superstate, which is a very... hacky brute-force solution.

Do you have any ideas on things I could try? I've unfortunately run out of ideas.

Allow async functions as state handler or actions

It is a well documented and clean library on HSM. I would have used it in my next project but my use case demands to perform async operations inside state handler and possibly in actions.

It would be nice to have a crate feature to enable async support for such use cases. By default, it can be off.

Would be awesome to have an attribute that draws an ASCII picture of the resulting tree

Thinking something like

#[state_machine(visualize)]
impl MyStruct { ... } 

fn main() { 
  let mut state_machine = Blinky::default().uninitialized_state_machine().init();
  let ascii_visualization: String = state_machine.visualize();
  println!("{}", ascii_visualiztion);
}

Which would print something like

                          ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐             
                                    Top                       
                          └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘             
                                     │                        
                        ┌────────────┴────────────┐           
                        │                         │           
             ┌─────────────────────┐   ╔═════════════════════╗
             │      Blinking       │   ║     NotBlinking     ║
             │─────────────────────│   ╚═════════════════════╝
             │ counter: &'a usize  │                          
             └─────────────────────┘                          
                        │                                     
           ┌────────────┴────────────┐                        
           │                         │                        
╔═════════════════════╗   ╔═════════════════════╗             
║        LedOn        ║   ║        LedOff       ║             
║─────────────────────║   ║─────────────────────║             
║ counter: usize      ║   ║ counter: usize      ║             
╚═════════════════════╝   ╚═════════════════════╝

Refactor async functionality for `no_std`/`no_alloc`

Looking to continue the discussion from #3 for refactoring the async implementation to work in both no_std and no_alloc environments. In particular, I would like to use this crate within an embassy-rs project.

In the previous issue, you said you had thoughts as to how you could statically allocate space for the futures. Do you still view this as a viable approach? I have not had luck in finding this done elsewhere.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.