Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Any chance to pass an Action via an Event #12

Open
taliesin opened this issue Jul 25, 2018 · 6 comments
Open

Any chance to pass an Action via an Event #12

taliesin opened this issue Jul 25, 2018 · 6 comments
Labels

Comments

@taliesin
Copy link

taliesin commented Jul 25, 2018

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
Copy link
Owner

digint commented Jul 26, 2018

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
Copy link
Author

taliesin commented Jul 26, 2018

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
Copy link
Author

@digint
Copy link
Owner

digint commented Jul 26, 2018

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
Copy link
Author

taliesin commented Jul 26, 2018

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
Copy link
Author

taliesin commented Jul 31, 2018

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants