Skip to content

Commit c9c43cc

Browse files
committed
[processing] Implement Protothreads/Resumables using Fibers
Adds an lbuild option to replace the Protothread and (Nested)Resumable implementation with a fiber one that is almost entirely backwards compatible.
1 parent c1af10d commit c9c43cc

File tree

11 files changed

+472
-60
lines changed

11 files changed

+472
-60
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (c) 2011, 2018, Fabian Greif
3+
* Copyright (c) 2012, 2014-2015, 2018, 2023, Niklas Hauser
4+
* Copyright (c) 2017, Raphael Lehmann
5+
*
6+
* This file is part of the modm project.
7+
*
8+
* This Source Code Form is subject to the terms of the Mozilla Public
9+
* License, v. 2.0. If a copy of the MPL was not distributed with this
10+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
11+
*/
12+
// ----------------------------------------------------------------------------
13+
14+
#ifndef MODM_PT_MACROS_FIBERS_HPP
15+
#define MODM_PT_MACROS_FIBERS_HPP
16+
17+
#include <modm/architecture/utils.hpp>
18+
19+
/// @ingroup modm_processing_protothread
20+
/// @{
21+
22+
/**
23+
* Declare start of protothread
24+
*
25+
* \warning Use at start of the run() implementation!
26+
* \hideinitializer
27+
*/
28+
#define PT_BEGIN()
29+
30+
/**
31+
* Stop protothread and end it
32+
*
33+
* \warning Use at end of the run() implementation!
34+
* \hideinitializer
35+
*/
36+
#define PT_END() \
37+
return false;
38+
39+
/// Yield protothread till next call to its run().
40+
/// \hideinitializer
41+
#define PT_YIELD() \
42+
modm::fiber::yield()
43+
44+
/// Cause protothread to wait **while** given condition is true.
45+
/// \hideinitializer
46+
#define PT_WAIT_WHILE(...) \
47+
while(__VA_ARGS__) { PT_YIELD(); }
48+
49+
/// Cause protothread to wait **until** given condition is true.
50+
/// \hideinitializer
51+
#define PT_WAIT_UNTIL(...) \
52+
PT_WAIT_WHILE(!(__VA_ARGS__))
53+
54+
/// Cause protothread to wait until given child protothread completes.
55+
/// \hideinitializer
56+
#define PT_WAIT_THREAD(...) \
57+
PT_WAIT_WHILE((__VA_ARGS__).run())
58+
59+
/// Restart and spawn given child protothread and wait until it completes.
60+
/// \hideinitializer
61+
#define PT_SPAWN(...) \
62+
PT_WAIT_THREAD(__VA_ARGS__);
63+
64+
65+
/**
66+
* Calls a given resumable function and returns
67+
* whether it completed successfully or not.
68+
* \hideinitializer
69+
*/
70+
#define PT_CALL(...) \
71+
__VA_ARGS__
72+
73+
/**
74+
* Reset protothread to start from the beginning
75+
*
76+
* In the next executing cycle the protothread will restart its execution at
77+
* its PT_BEGIN.
78+
* \hideinitializer
79+
*/
80+
#define PT_RESTART() \
81+
return true;
82+
83+
/// Stop and exit from protothread.
84+
/// \hideinitializer
85+
#define PT_EXIT() \
86+
return false;
87+
88+
/// @}
89+
90+
#endif // MODM_PT_MACROS_FIBERS_HPP

src/modm/processing/protothread/module.lb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,22 @@ def init(module):
1717

1818
def prepare(module, options):
1919
module.depends(":architecture")
20+
module.add_option(
21+
BooleanOption(
22+
name="use_fiber",
23+
default=False,
24+
description="Implement via Fibers",
25+
dependencies=lambda v: ":processing:fiber" if v else None))
26+
2027
return True
2128

2229
def build(env):
2330
env.outbasepath = "modm/src/modm/processing/protothread"
24-
env.copy(".")
31+
if env["use_fiber"]:
32+
env.copy("macros_fiber.hpp", "macros.hpp")
33+
env.copy("protothread_fiber.hpp", "protothread.hpp")
34+
else:
35+
env.copy("macros.hpp")
36+
env.copy("protothread.hpp")
37+
env.copy("semaphore.hpp")
2538
env.copy("../protothread.hpp")

src/modm/processing/protothread/module.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,58 @@ while (true) {
7474
light.run();
7575
}
7676
```
77+
78+
79+
## Using Fibers
80+
81+
Protothreads can be implemented using stackful fibers by setting the `use_fiber`
82+
option, which replaces the preprocessor macros and C++ implementations of this
83+
and the `modm:processing:resumable` module with a fiber version.
84+
85+
Specifically, the `PT_*` and `RF_*` macros are now forwarding their arguments
86+
unmodified and instead relying on `modm::fiber::yield()` for context switching:
87+
88+
```cpp
89+
#define PT_YIELD() modm::fiber::yield()
90+
#define PT_WAIT_WHILE(cond) while(cond) { modm::fiber::yield(); }
91+
#define PT_CALL(func) func
92+
```
93+
94+
The `modm::pt::Protothread` class is implemented using `modm::Fiber<>` with the
95+
default stack size `MODM_PROTOTHREAD_STACK_SIZE`. It automatically runs the two
96+
virtual methods `bool run()` and `bool update()` if they are defined in the
97+
protothread class.
98+
99+
There should be no modification of the existing code necessary with the
100+
exception that you must replace the main loop calling all protothreads with the
101+
fiber scheduler:
102+
103+
```cpp
104+
int main()
105+
{
106+
/*
107+
while(true)
108+
{
109+
protothread1.update();
110+
protothread2.update();
111+
}
112+
*/
113+
modm::fiber::Scheduler::run();
114+
return 0;
115+
}
116+
```
117+
118+
119+
### Restrictions
120+
121+
If the default stack size is too low, you can set `MODM_PROTOTHREAD_STACK_SIZE`
122+
to a higher value, however, this will apply to *all* protothreads, consuming a
123+
lot more memory. Instead, we recommend refactoring the protothread into a fiber
124+
function.
125+
126+
All functions and macros work as expected, except for the `Protothread::stop()`
127+
function, which not implementable using fibers. It is left unimplemented,
128+
so that you may define it with the behavior most suitable to your use case.
129+
130+
See the `modm:processing:resumable` module for additional restrictions when
131+
calling resumable functions from a protothread.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (c) 2023, Niklas Hauser
3+
*
4+
* This file is part of the modm project.
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public
7+
* License, v. 2.0. If a copy of the MPL was not distributed with this
8+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*/
10+
// ----------------------------------------------------------------------------
11+
12+
#pragma once
13+
14+
#include "macros.hpp"
15+
#include <modm/processing/fiber.hpp>
16+
17+
/// @ingroup modm_processing_protothread
18+
#define MODM_PROTOTHREAD_IS_FIBER
19+
20+
#ifndef MODM_PROTOTHREAD_STACK_SIZE
21+
/// @ingroup modm_processing_protothread
22+
#define MODM_PROTOTHREAD_STACK_SIZE ::modm::fiber::StackSizeDefault
23+
#endif
24+
25+
namespace modm::pt
26+
{
27+
28+
/// @ingroup modm_processing_protothread
29+
class Protothread : public modm::Fiber< MODM_PROTOTHREAD_STACK_SIZE >
30+
{
31+
public:
32+
Protothread(modm::fiber::Start start=modm::fiber::Start::Now)
33+
: Fiber([this](){ while(update()) modm::fiber::yield(); }, start)
34+
{}
35+
36+
void restart() { this->start(); }
37+
void stop();
38+
// isRunning() is implemented in fiber::Task
39+
40+
// The run() function name was never enforced by the Protothread interface
41+
virtual bool run() { return false; };
42+
// Instead update() was often chosen to align it more with other parts of
43+
// modm that use an update() function to update their state periodically.
44+
// Therefore we cover both here to not have to change too much code.
45+
virtual bool update() { return run(); };
46+
};
47+
48+
}

src/modm/processing/resumable.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,3 @@
1414
// ----------------------------------------------------------------------------
1515

1616
#include "resumable/resumable.hpp"
17-
#include "resumable/nested_resumable.hpp"
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2023, Niklas Hauser
3+
*
4+
* This file is part of the modm project.
5+
*
6+
* This Source Code Form is subject to the terms of the Mozilla Public
7+
* License, v. 2.0. If a copy of the MPL was not distributed with this
8+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
9+
*/
10+
// ----------------------------------------------------------------------------
11+
12+
#ifndef MODM_RF_MACROS_FIBER_HPP
13+
#define MODM_RF_MACROS_FIBER_HPP
14+
15+
#include <modm/processing/fiber.hpp>
16+
17+
/// @ingroup modm_processing_resumable
18+
/// @{
19+
20+
/// Declare start of resumable function with index.
21+
/// @warning Use at start of the `resumable()` implementation!
22+
#define RF_BEGIN(...)
23+
24+
25+
/**
26+
* End the resumable function and return a result.
27+
*
28+
* @warning Use at end of the `resumable()` implementation only!
29+
* @hideinitializer
30+
*/
31+
#define RF_END_RETURN(...) \
32+
return __VA_ARGS__
33+
34+
/**
35+
* End the resumable function. You can use this to return `void`, or if the result does not matter.
36+
*
37+
* @warning Use at end of the `resumable()` implementation only!
38+
* @hideinitializer
39+
*/
40+
#define RF_END() \
41+
return
42+
43+
/**
44+
* End the resumable function by calling another resumable function and returning its result.
45+
*
46+
* @warning Use at end of the `resumable()` implementation only!
47+
* @hideinitializer
48+
*/
49+
#define RF_END_RETURN_CALL(...) \
50+
return __VA_ARGS__
51+
52+
/// Yield resumable function until next invocation.
53+
/// @hideinitializer
54+
#define RF_YIELD() \
55+
modm::fiber::yield()
56+
57+
/// Cause resumable function to wait until given child protothread completes.
58+
/// @hideinitializer
59+
#define RF_WAIT_THREAD(...) \
60+
RF_WAIT_WHILE((__VA_ARGS__).run())
61+
62+
/// Cause resumable function to wait **while** given `condition` is true.
63+
/// @hideinitializer
64+
#define RF_WAIT_WHILE(...) \
65+
while(__VA_ARGS__) { RF_YIELD(); }
66+
67+
/// Cause resumable function to wait **until** given `condition` is true.
68+
/// @hideinitializer
69+
#define RF_WAIT_UNTIL(...) \
70+
RF_WAIT_WHILE(!(__VA_ARGS__))
71+
72+
/// Calls a resumable function and returns its result.
73+
/// @hideinitializer
74+
#define RF_CALL(...) \
75+
__VA_ARGS__
76+
77+
/**
78+
* Calls a resumable function, busy-waits and returns its result.
79+
*
80+
* @hideinitializer
81+
*/
82+
#define RF_CALL_BLOCKING(...) \
83+
__VA_ARGS__
84+
85+
/// Exits a resumable function and returns another resumable function's result.
86+
/// @hideinitializer
87+
#define RF_RETURN_CALL(...) \
88+
return __VA_ARGS__
89+
90+
/// Stop and exit from resumable function with an optional result.
91+
/// @hideinitializer
92+
#define RF_RETURN(...) \
93+
return __VA_ARGS__
94+
95+
/// @}
96+
97+
#endif // MODM_RF_MACROS_FIBER_HPP

src/modm/processing/resumable/module.lb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,14 @@ def prepare(module, options):
2929

3030
def build(env):
3131
env.outbasepath = "modm/src/modm/processing/resumable"
32-
env.copy(".", ignore=env.ignore_files("*.md", "*.in"))
32+
if env.get(":processing:protothread:use_fiber", False):
33+
env.copy("macros_fiber.hpp", "macros.hpp")
34+
env.copy("resumable_fiber.hpp", "resumable.hpp")
35+
else:
36+
env.copy("macros.hpp")
37+
env.copy("resumable.hpp")
38+
env.template("nested_resumable.hpp.in")
3339
env.copy("../resumable.hpp")
34-
env.template("nested_resumable.hpp.in")
3540

3641

3742
# ============================ Option Descriptions ============================

0 commit comments

Comments
 (0)