Example Lua module for coroutining (#2851)
This commit is contained in:
parent
21b3f38ab8
commit
6d9c5a49a4
|
@ -0,0 +1,88 @@
|
|||
# cohelper Module
|
||||
| Since | Origin / Contributor | Maintainer | Source |
|
||||
| :----- | :-------------------- | :---------- | :------ |
|
||||
| 2019-07-24 | [TerryE](https://github.com/TerryE) | [TerryE](https://github.com/TerryE) | [cohelper.lua](../../lua_modules/cohelper/cohelper.lua) |
|
||||
|
||||
This module provides a simple wrapper around long running functions to allow
|
||||
these to execute within the SDK and its advised limit of 15 mSec per individual
|
||||
task execution. It does this by exploiting the standard Lua coroutine
|
||||
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).
|
||||
|
||||
The NodeMCU Lua VM fully supports the standard coroutine functionality. Any
|
||||
interactive or callback tasks are executed in the default thread, and the coroutine
|
||||
itself runs in a second separate Lua stack. The coroutine can call any library
|
||||
functions, but any subsequent callbacks will, of course, execute in the default
|
||||
stack.
|
||||
|
||||
Interaction between the coroutine and the parent is through yield and resume
|
||||
statements, and since the order of SDK tasks is indeterminate, the application
|
||||
must take care to handle any ordering issues. This particular example uses
|
||||
the `node.task.post()` API with the `taskYield()`function to resume itself,
|
||||
so the running code can call `taskYield()` at regular points in the processing
|
||||
to spilt the work into separate SDK tasks.
|
||||
|
||||
A similar approach could be based on timer or on a socket or pipe CB. If you
|
||||
want to develop such a variant then start by reviewing the source and understanding
|
||||
what it does.
|
||||
|
||||
### Require
|
||||
```lua
|
||||
local cohelper = require("cohelper")
|
||||
-- or linked directly with the `exec()` method
|
||||
require("cohelper").exec(func, <params>)
|
||||
```
|
||||
|
||||
### Release
|
||||
|
||||
Not required. All resources are released on completion of the `exec()` method.
|
||||
|
||||
## `cohelper.exec()`
|
||||
Execute a function which is wrapped by a coroutine handler.
|
||||
|
||||
#### Syntax
|
||||
`require("cohelper").exec(func, <params>)`
|
||||
|
||||
#### Parameters
|
||||
- `func`: Lua function to be executed as a coroutine.
|
||||
- `<params>`: list of 0 or more parameters used to initialise func. the number and types must be matched to the funct declaration
|
||||
|
||||
#### Returns
|
||||
Return result of first yield.
|
||||
|
||||
#### Notes
|
||||
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.
|
||||
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.
|
||||
|
||||
### Full Example
|
||||
|
||||
Here is a function which recursively walks the globals environment, the ROM table
|
||||
and the Registry. Without coroutining, this walk terminate with a PANIC following
|
||||
a watchdog timout. I don't want to sprinkle the code with `tmr.wdclr(`) that could
|
||||
in turn cause the network stack to fail. Here is how to do it using coroutining:
|
||||
|
||||
```Lua
|
||||
require "cohelper".exec(
|
||||
function(taskYield, list)
|
||||
local s, n, nCBs = {}, 0, 0
|
||||
|
||||
local function list_entry (name, v) -- upval: taskYield, nCBs
|
||||
print(name, v)
|
||||
n = n + 1
|
||||
if n % 20 == 0 then nCBs = taskYield(nCBs) end
|
||||
if type(v):sub(-5) ~= 'table' or s[v] or name == 'Reg.stdout' then return end
|
||||
s[v]=true
|
||||
for k,tv in pairs(v) do
|
||||
list_entry(name..'.'..k, tv)
|
||||
end
|
||||
s[v] = nil
|
||||
end
|
||||
|
||||
for k,v in pairs(list) do
|
||||
list_entry(k, v)
|
||||
end
|
||||
print ('Total lines, print batches = ', n, nCBs)
|
||||
end,
|
||||
{_G = _G, Reg = debug.getregistry(), ROM = ROM}
|
||||
)
|
||||
```
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Coroutine Helper Module
|
||||
|
||||
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.
|
|
@ -0,0 +1,27 @@
|
|||
--[[ A coroutine Helper T. Ellison, June 2019
|
||||
|
||||
This version of couroutine helper demonstrates the use of corouting within
|
||||
NodeMCU execution to split structured Lua code into smaller tasks
|
||||
|
||||
]]
|
||||
--luacheck: read globals node
|
||||
|
||||
local modname = ...
|
||||
|
||||
local function taskYieldFactory(co)
|
||||
local post = node.task.post
|
||||
return function(nCBs) -- upval: co,post
|
||||
post(function () -- upval: co, nCBs
|
||||
coroutine.resume(co, nCBs or 0)
|
||||
end)
|
||||
return coroutine.yield() + 1
|
||||
end
|
||||
end
|
||||
|
||||
return { exec = function(func, ...) -- upval: modname
|
||||
package.loaded[modname] = nil
|
||||
local co = coroutine.create(func)
|
||||
return coroutine.resume(co, taskYieldFactory(co), ... )
|
||||
end }
|
||||
|
||||
|
Loading…
Reference in New Issue