|
| 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 | + |
0 commit comments