digint / tinyfsm

A simple C++ finite state machine library
https://digint.ch/tinyfsm
MIT License
931 stars 173 forks source link

Any chance to pass an Action via an Event #12

Open taliesin opened 6 years ago

taliesin commented 6 years ago

Probably this is more a general C++ question than it is a tinyfsm one, but maybe somebody did this already.

I have an event called ShouldEstimateAndSend which transits to a new state on a few conditions. I'd like to have it call transit\<NewState>(ShoudEstimateAndSend::action_function). Using std::function would work, but this introduces some runtime overhead and pulls in exception handling which is not what I like on my embedded target. Using a template event class 'breaks' inheritance and would need a template react function as far as I can see?!

Can't wiggle my head around this.

digint commented 6 years ago

If your ShoudEstimateAndSend::action_function is static void, this should work.

Using a template event class 'breaks' inheritance and would need a template react function as far as I can see?!

Not sure what you mean here. Do you have an example to clarify?

taliesin commented 6 years ago

Just thinking too complicated .. a plain old C function pointer works pretty well.

struct ShouldEstimateAndSend : public tinyfsm::Event { typedef void (*action_function_t)(void); action_function_t _action_function; explicit ShouldEstimateAndSend(action_function_t af) : _action_function(af) {} };

... and you can even pass lambdas and bind arguments there, which extends the usability dramatically.

FSM::dispatch(ShouldEstimateAndSend(ts, []() { printf("lambda called\n"); } ));

... and default it to a do nothing:

explicit ShouldEstimateAndSend(action_function_t af = [](){}) : _action_function(af) {}

In the end the problem was just how to store the function in the event class and I did not want std::function for the given reasons.

Thanks for pulling me back to earth.

taliesin commented 6 years ago

Ah damn, binding does not work, see https://stackoverflow.com/questions/28746744/passing-lambda-as-function-pointer

digint commented 6 years ago

Yes, I think that's one reason why std::function exists (I never really used it though, remember playing around with it when testing these TinyFSM action functions).

For the record: example usage of lamda function: elevator.cpp

taliesin commented 6 years ago

Yes I did see that example, but still had the trouble to pass it via the event. There are some nifty workarounds for std::function, not as complete, but for a void() function it would be easy enough, probably I'll go that way if I really need to.

taliesin commented 6 years ago

Finally, to close that topic, my solution:

// ActionFunctions may be passed via Events to be used for transit<state>(action_function)
// It allows to pass capturing lambdas as it stores context locally (other than a normal
// C style function pointer).
// This is mainly to avoid std::function which pulls in exception handling.
class ActionFunction
{
public:
    using action_function_t = void (*)(void *);
private:
    action_function_t _action_function;
    void *_context;
public:
    ActionFunction(action_function_t f, void *ctx) :
        _action_function(f), _context(ctx) {}
    void operator()() const { _action_function(_context); }
    // helper to instantiate a calling instance for each lambda passed
    template<typename T> static void caller(T* v) { return (*v)(); }
};

template <typename Func>
ActionFunction make_action_function(Func f)
{
    return ActionFunction(reinterpret_cast<ActionFunction::action_function_t>(ActionFunction::caller<Func>), &f);
}

And the event:

struct ShouldEstimateAndSend : public tinyfsm::Event
{
    ActionFunction _action;

    explicit ShouldEstimateAndSend(ActionFunction af = make_action_function([](){})) : _action(af) {}
    const ActionFunction & action() const { return _action; }
};

Usage (binding the stream for ChibiOS as example):

auto af = make_action_function([chp] () { chprintf(chp, "capturing lambda called\n"); });
AnchorFSM::dispatch(ShouldEstimateAndSend(af));

in transit

transit<SomeState>(passed_event.action());

This has some potential to be more type-safe and probably shorter ...