modm API documentation
Resumable Functions
#define RF_BEGIN(index)
 
#define RF_BEGIN()
 
#define RF_END_RETURN(...)
 
#define RF_END()
 
#define RF_END_RETURN_CALL(...)
 
#define RF_YIELD()
 
#define RF_WAIT_THREAD(...)
 
#define RF_WAIT_WHILE(...)
 
#define RF_WAIT_UNTIL(...)
 
#define RF_CALL(...)
 
#define RF_CALL_BLOCKING(...)
 
#define RF_RETURN_CALL(...)
 
#define RF_RETURN(...)
 
template<typename T >
using modm::ResumableResult = T
 Resumable functions implemented via fibers return like a normal function.
 

Detailed Description

lbuild module: modm:processing:resumable

Warning
Resumable Functions are deprecated! Unfortunately GCC12 is the last version that supported the way we implemented Resumables. Since GCC13 the implementation does not compile anymore and cannot be fixed without significant breaking changes. Please port your code to use modm:processing:fiber instead.

An implementation of lightweight resumable functions which allow for nested calling.

This base class and its macros allows you to implement and use several resumable functions in one class. This allows you to modularize your code by placing it into its own resumable functions instead of the placing everything into one big method. It also allows you to call and run resumable functions within your resumables, so you can reuse their functionality.

Note that you should call resumable functions within a protothreads, with the PT_CALL(group.resumable()) macro, which will return the result of the resumable function. To call a resumable function inside another resumable function, use the RF_CALL(group.resumable()).

You may use the RF_CALL_BLOCKING(group.resumable()) macro to execute a resumable function outside of a protothread, however, this which will force the CPU to busy-wait until the resumable function ended.

Warning
Resumable functions are not thread-safe! Use an external mutex to arbitrate access if two Protothreads access the same resumable function.

You can either run your group of resumable functions independently from each other, or only run one function at a time, but it may nest calls to its own group's functions. So you need to inherit your group from modm::Resumable<#Functions>, and from modm::NestedResumable<#Functions> respectively.

Independent operation

You must begin each resumable function using RF_BEGIN(index) where index is the unique index of your resumable function starting at zero. You may exit and return a value by using RF_RETURN(value) or return the result of another resumable function using RF_RETURN_CALL(resumable()). This return value is wrapped in a modm::ResumableResult<Type> struct and transparently returned by the RF_CALL macro so it can be used to influence your program flow. If the resumable function reaches RF_END() it will exit automatically, with the result of 0 cast to the return type. Should you wish to return a value at the end, you may use RF_END_RETURN(value). You may also return the result of another resumable function using RF_END_RETURN_CALL(resumable()).

Be aware that this class keeps a separate state for each of your resumable functions. This allows each resumable function to be run at the same time. This might require the use of an internal semaphore or mutex if such dependencies exist in your use case. Take a look at the NestedResumable class for mutually exclusive resumable functions, which also require a little less memory.

Nested operation

You are responsible to choosing the right nesting depth! This class will guard itself against calling another resumable function at too deep a nesting level and fail the rf.nest assertion! It is then up to you to recognize this in your program design and increase the nesting depth or rethink your code.

The resumable functions of this class are mutually exclusive, so only one resumable function of the same object can run at the same time. Even if you call another resumable function, it will simply return modm::rf::WrongState. Using the RF_CALL(resumable()) macro, you can wait for these resumable functions to become available and then run them, so you usually do not need to worry about those cases.

You must begin each resumable function using RF_BEGIN(). You may exit and return a value by using RF_RETURN(value) or return the result of another resumable function using RF_RETURN_CALL(resumable()). This return value is wrapped in a modm::ResumableResult<Type> struct and transparently returned by the RF_CALL macro so it can be used to influence your program flow. If the resumable function reaches RF_END() it will exit automatically, with the result of 0 cast to the return type. Should you wish to return a value at the end, you may use RF_END_RETURN(value). You may also return the result of another resumable function using RF_END_RETURN_CALL(resumable()).

Example

Here is a (slightly over-engineered) example:

#include <modm/platform.hpp>
#include <modm/processing.hpp>
using Led = GpioOutputB0;
class BlinkingLight : public modm::pt::Protothread, private modm::NestedResumable<2>
{
public:
bool
run()
{
// set everything up
Led::setOutput();
Led::set();
while (true)
{
Led::set();
PT_CALL(waitForTimer());
Led::reset();
PT_CALL(setTimer(200));
PT_WAIT_UNTIL(timeout.isExpired());
}
PT_END();
}
waitForTimer()
{
// nested calling is allowed
if (RF_CALL(setTimer(100)))
{
RF_WAIT_UNTIL(timeout.isExpired());
RF_RETURN(true);
}
RF_END_RETURN(false);
}
setTimer(uint16_t new_timeout)
{
timeout.restart(std::chrono::milliseconds(new_timeout));
if(timeout.isArmed()) {
RF_RETURN(true);
}
// clean up code goes here
RF_END_RETURN(false);
}
private:
};
BlinkingLight light;
while (true) {
light.run();
}

For other examples take a look in the examples folder in the modm root folder. The given example is in modm/examples/generic/resumable.

Using Fibers

Resumable functions can be implemented using stackful fibers by setting the modm:processing:protothread:use_fiber option, which replaces the preprocessor macros and C++ implementations of this and the modm:processing:protothreads module with a fiber version.

Specifically, the PT_* and RF_* macros are now forwarding their arguments unmodified and instead relying on modm::this_fiber::yield() for context switching:

#define RF_YIELD() modm::this_fiber::yield()
#define RF_WAIT_WHILE(cond) while(cond) { modm::this_fiber::yield(); }
#define RF_CALL(func) func
#define RF_RETURN(value) return value

You may call RF_CALL_BLOCKING(resumable) outside a fiber context, in which case the modm::this_fiber::yield() will return immediately, which is the same behavior as before.

However, the modm::ResumableResult, modm::Resumable, and modm::NestedResumable classes are now empty implementations:

// Only forwards the return value
template <typename T> using ResumableResult = T;
// Empty base classes
template<uint8_t Functions=0> class Resumable {};
template<uint8_t Levels=0> class NestedResumable {};

There should be no modification necessary to the resumable functions itself.

Restrictions

There is one minor limitations when using fibers: 1) no nesting depth checking for NestedResumable, 2) stopping resumable functions is not implementable.

The nesting depth is limited by the stack size of the fiber, so it would manifest as a stack overflow, which would need to be checked differently.

The utility member functions

Module Options

modm:processing:resumable:check_nesting_depth: Check nesting call depth

Nested resumable functions protect against memory corruption by checking if the nesting level is within the allocated nesting level depth, on first entry to the function. If the allocated nesting level is exceeded, the assertion resumable.begin.nesting fails.

You may disable this behavior by disabling this check, then instead of the assertion, the function on entry returns the modm::rf::NestingError state value. PT_CALL() and RF_CALL() macros will respond to this error by stopping function polling and just continuing program execution.

Note
Performance Penalty This check is performed during the call to RF_BEGIN(N), so exactly once on function entry and not during every polling call, so the performance penalty is relatively small.

Generated with: yes in [yes, no]

Macro Definition Documentation

#define RF_BEGIN

Declare start of resumable function with index.

Warning
Use at start of the resumable() implementation!

Declare start of a nested resumable function. This will immediately return if the nesting is too deep.

Warning
Use at start of the resumable() implementation!
#define RF_BEGIN

Declare start of resumable function with index.

Warning
Use at start of the resumable() implementation!

Declare start of a nested resumable function. This will immediately return if the nesting is too deep.

Warning
Use at start of the resumable() implementation!
#define RF_CALL

Calls a resumable function and returns its result.

#define RF_CALL_BLOCKING

Calls a resumable function, busy-waits and returns its result.

#define RF_END

End the resumable function. You can use this to return void, or if the result does not matter.

Warning
Use at end of the resumable() implementation only!
#define RF_END_RETURN

End the resumable function and return a result.

Warning
Use at end of the resumable() implementation only!
#define RF_END_RETURN_CALL

End the resumable function by calling another resumable function and returning its result.

Warning
Use at end of the resumable() implementation only!
#define RF_RETURN

Stop and exit from resumable function with an optional result.

#define RF_RETURN_CALL

Exits a resumable function and returns another resumable function's result.

#define RF_WAIT_THREAD

Cause resumable function to wait until given child protothread completes.

#define RF_WAIT_UNTIL

Cause resumable function to wait until given condition is true.

#define RF_WAIT_WHILE

Cause resumable function to wait while given condition is true.

#define RF_YIELD

Yield resumable function until next invocation.