digint/tinyfsm

Any chance to pass an Action via an Event

taliesin opened this issue · 6 comments

Probably this is more a general C++ question than it is a tinyfsm one, but maybe some 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.

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?

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.

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

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.

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 ...