boost-ext / sml

C++14 State Machine library
https://boost-ext.github.io/sml
Boost Software License 1.0
1.1k stars 173 forks source link

What's the actual point of this all? #591

Closed Bracket-H closed 10 months ago

Bracket-H commented 10 months ago

I'm gonna be honest, I'm pretty ignorant about this, and I am also a bit dumb on some specific fronts, this is one of them.

I watched several talks on this now, I read through the docs to the best of my ability, but I'm still stumped.

What's the actual practical point of this all?

It seems at the end of the day, to actually work with the states, and use them to change the behavior or state of an object, or the program itself, you have to still query the state machine if its in a particular state.

aka sm.is("state") == true so to speak

Which is more elegant than those nasty spaghetti if else trees, but that, to me, seems to be the only way to affect anything, since otherwise everything seems to be const?

This feels a bit like functional programing to me, where everything is in a little capsule, which is neat and all, and the examples show how one state goes to the other. But ...what good it is it?

I am definetely missing something here. How would a virtual pez dispenser dispense its finite pezzes with this? You could use an action that triggers when one actuates the pez dispenser, then either a copy, or a const reference will be sent to the action. If it's a copy, then the dispensing will be allowed, but the original will still have the same amount of pezzes in it. If it's a const reference then the compiler will complain that one is trying to change the state of a const object.

Whoop de doodle do.

Can you help me be less ignorant and/or dumb about this? Cause I do think that I can ultimately understand, just not from the docs.

Bracket-H commented 10 months ago

To clarify, to me, the way to dispense the pez and remove it from the dispenser is. query state machine if state is "dispensing pez" subtract pez amounts then have it process an event to reset back to allowing dispensal with the guard that it has more than zero pezzes.

Whch in the end would be a different if else tree lol.

Surely that cannot be it.

uyha commented 10 months ago

Have a look at https://godbolt.org/z/9YvYWhq4M

I wrote it quickly to demonstrate roughly what you describe above, please ask any question if you want to know more.

Bracket-H commented 10 months ago

Thank you very much! That does indeed help, especially with the research I've been doing on the sideline (like chewing through that data example).

Some things still confuse me mightily, like why I can just keep on adding objects to the state machines initializer thingy (the curly braces, I guess that's modern C++ which I have set 'enabled' for a long time but never really used lol)

like sm{foo,bar,baz,bing,bong} etc. Am I in the ballpark with the assumption that these things get stored internally, and the matched to any handler that fits a particular function/lambda signature?

So even if I add like 5 different objects, if I only use one type of them like Foo foo in a lambda/handler that is like const auto is_foo_valid = [](const Foo& foo_){ return foo_.valid; }; ? It seems that is indeed using the object I pass to it initially for that.

But that might be a completely different subject, one that I probably should ask about specifically if I still get hung up on it.

Anyway, I'll leave this issue open for a day or two just in case I bump into something that is relevant to that 'pez' example (I didn't want to use a videogame one lol )

Again, thanks so far. I had a similar snag with boost spirit years ago, then one particular practical example someone gave me made everything click.

uyha commented 10 months ago

Some things still confuse me mightily, like why I can just keep on adding objects to the state machines initializer thingy (the curly braces, I guess that's modern C++ which I have set 'enabled' for a long time but never really used lol)

This is the magic that sml is doing behind the scene with dependency injection, I don't understand it fully how it works under the hood, just if you pass stuff in the constructor, stuff will be available for the actions and guards. So this is not a C++ thing but rather an sml thing.

Bracket-H commented 10 months ago

I see, thanks. Also, I just realized that sml seems to call the constructor of a state machine transition table owning class several times. Do you know anything about that?

In any case, that's probably a good reason to put 'tricky' state stuff into a separate, shared object, or something, yes?

For example, a Lua state, or something that is not intended to be duplicated 'willy nilly' (which this feels to me, but only cause I know nothing about the workings on that front)

Edit: did a little backtracing check and yeah some sml internal "pool_type_impl" seemed to have called it a bunch of times. That's fair enough, just gotta work around that, etc.

uyha commented 10 months ago

I just realized that sml seems to call the constructor of a state machine transition table owning class several times.

https://godbolt.org/z/f5Ynxce8o doesn't look like that to me, could you give your snippet?

In any case, that's probably a good reason to put 'tricky' state stuff into a separate, shared object, or something, yes? For example, a Lua state, or something that is not intended to be duplicated 'willy nilly' (which this feels to me, but only cause I know nothing about the workings on that front)

That is entirely up to you, I use it for states that are shared between sub state machines. A Lua state may be too coarse or just right depending on the size and complexity of your state machine. If you have a lot of sub state machines, a better approach would be create small types that hold a reference to the Lua state, provide some interface to do some specific things, and have the actions and guards requiring only those small types.

Edit: it's not even copied or moved, just being constructed in place https://godbolt.org/z/3YEMn49vj

Bracket-H commented 10 months ago

Mmm, interesting. I think this is coming from me working with a variant of the Data example. https://boost-ext.github.io/sml/examples.html#data

which passes an instance of its class to another of itself, it seems.

My snippet is similar.

TestFsm fsm;
sml::<TestFsm>{fsm};

After reworking some things, the constructor amount is now three, which probably matches the three mentions.

this is your example after writing it similarily https://godbolt.org/z/r4r8WMoj5

edit: the solution seems to be to do it 'in place' https://godbolt.org/z/186T69Eac

Edit2: Hmmm, there are still some edge cases, but I think I'l lclose this issue for now and look at everything with fresh eyes tomorrow. pretty sleep deprived at the moment, which does not help.

uyha commented 10 months ago

but why would you pass Dispenser as a state into the sm constructor? sm will construct it for you, you don't have to pass it.

Bracket-H commented 10 months ago

That was just an example, because the tutorial example from the docs also does that. https://boost-ext.github.io/sml/examples.html#data ( I think it's because of those set and update things) I'm a newbie after all, don't know better, anyway. Thanks for the help so far.

I think my problems now are beyond the initial problem of not even seeing the point of this all.