Skip to content

Commit 6d9c5a4

Browse files
authored
Example Lua module for coroutining (#2851)
1 parent 21b3f38 commit 6d9c5a4

File tree

3 files changed

+118
-0
lines changed

3 files changed

+118
-0
lines changed

docs/lua-modules/cohelper.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# cohelper Module
2+
| Since | Origin / Contributor | Maintainer | Source |
3+
| :----- | :-------------------- | :---------- | :------ |
4+
| 2019-07-24 | [TerryE](https://github.com/TerryE) | [TerryE](https://github.com/TerryE) | [cohelper.lua](../../lua_modules/cohelper/cohelper.lua) |
5+
6+
This module provides a simple wrapper around long running functions to allow
7+
these to execute within the SDK and its advised limit of 15 mSec per individual
8+
task execution. It does this by exploiting the standard Lua coroutine
9+
functionality as described in the [Lua RM §2.11](https://www.lua.org/manual/5.1/manual.html#2.11) and [PiL Chapter 9](https://www.lua.org/pil/9.html).
10+
11+
The NodeMCU Lua VM fully supports the standard coroutine functionality. Any
12+
interactive or callback tasks are executed in the default thread, and the coroutine
13+
itself runs in a second separate Lua stack. The coroutine can call any library
14+
functions, but any subsequent callbacks will, of course, execute in the default
15+
stack.
16+
17+
Interaction between the coroutine and the parent is through yield and resume
18+
statements, and since the order of SDK tasks is indeterminate, the application
19+
must take care to handle any ordering issues. This particular example uses
20+
the `node.task.post()` API with the `taskYield()`function to resume itself,
21+
so the running code can call `taskYield()` at regular points in the processing
22+
to spilt the work into separate SDK tasks.
23+
24+
A similar approach could be based on timer or on a socket or pipe CB. If you
25+
want to develop such a variant then start by reviewing the source and understanding
26+
what it does.
27+
28+
### Require
29+
```lua
30+
local cohelper = require("cohelper")
31+
-- or linked directly with the `exec()` method
32+
require("cohelper").exec(func, <params>)
33+
```
34+
35+
### Release
36+
37+
Not required. All resources are released on completion of the `exec()` method.
38+
39+
## `cohelper.exec()`
40+
Execute a function which is wrapped by a coroutine handler.
41+
42+
#### Syntax
43+
`require("cohelper").exec(func, <params>)`
44+
45+
#### Parameters
46+
- `func`: Lua function to be executed as a coroutine.
47+
- `<params>`: list of 0 or more parameters used to initialise func. the number and types must be matched to the funct declaration
48+
49+
#### Returns
50+
Return result of first yield.
51+
52+
#### Notes
53+
1. The coroutine function `func()` has 1+_n_ arguments The first is the supplied task yield function. Calling this yield function within `func()` will temporarily break execution and cause an SDK reschedule which migh allow other executinng tasks to be executed before is resumed. The remaining arguments are passed to the `func()` on first call.
54+
2. The current implementation passes a single integer parameter across `resume()` / `yield()` interface. This acts to count the number of yields that occur. Depending on your appplication requirements, you might wish to amend this.
55+
56+
### Full Example
57+
58+
Here is a function which recursively walks the globals environment, the ROM table
59+
and the Registry. Without coroutining, this walk terminate with a PANIC following
60+
a watchdog timout. I don't want to sprinkle the code with `tmr.wdclr(`) that could
61+
in turn cause the network stack to fail. Here is how to do it using coroutining:
62+
63+
```Lua
64+
require "cohelper".exec(
65+
function(taskYield, list)
66+
local s, n, nCBs = {}, 0, 0
67+
68+
local function list_entry (name, v) -- upval: taskYield, nCBs
69+
print(name, v)
70+
n = n + 1
71+
if n % 20 == 0 then nCBs = taskYield(nCBs) end
72+
if type(v):sub(-5) ~= 'table' or s[v] or name == 'Reg.stdout' then return end
73+
s[v]=true
74+
for k,tv in pairs(v) do
75+
list_entry(name..'.'..k, tv)
76+
end
77+
s[v] = nil
78+
end
79+
80+
for k,v in pairs(list) do
81+
list_entry(k, v)
82+
end
83+
print ('Total lines, print batches = ', n, nCBs)
84+
end,
85+
{_G = _G, Reg = debug.getregistry(), ROM = ROM}
86+
)
87+
```
88+

lua_modules/cohelper/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Coroutine Helper Module
2+
3+
Documentation for this Lua module is available in the [Lua Modules->cohelper](../../docs/lua-modules/cohelper.md) MD file and in the [Official NodeMCU Documentation](https://nodemcu.readthedocs.io/) in `Lua Modules` section.

lua_modules/cohelper/cohelper.lua

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--[[ A coroutine Helper T. Ellison, June 2019
2+
3+
This version of couroutine helper demonstrates the use of corouting within
4+
NodeMCU execution to split structured Lua code into smaller tasks
5+
6+
]]
7+
--luacheck: read globals node
8+
9+
local modname = ...
10+
11+
local function taskYieldFactory(co)
12+
local post = node.task.post
13+
return function(nCBs) -- upval: co,post
14+
post(function () -- upval: co, nCBs
15+
coroutine.resume(co, nCBs or 0)
16+
end)
17+
return coroutine.yield() + 1
18+
end
19+
end
20+
21+
return { exec = function(func, ...) -- upval: modname
22+
package.loaded[modname] = nil
23+
local co = coroutine.create(func)
24+
return coroutine.resume(co, taskYieldFactory(co), ... )
25+
end }
26+
27+

0 commit comments

Comments
 (0)