|
|
@@ -29,19 +29,52 @@ local log = _G.log
|
|
|
-- lib脚本版本号,只要lib中的任何一个脚本做了修改,都需要更新此版本号
|
|
|
SCRIPT_LIB_VER = "2.3.2"
|
|
|
|
|
|
--- TaskID最大值
|
|
|
+-- 任务定时器id最大值
|
|
|
local TASK_TIMER_ID_MAX = 0x1FFFFF
|
|
|
--- msgId 最大值(请勿修改否则会发生msgId碰撞的危险)
|
|
|
+-- 消息定时器id最大值(请勿修改否则会发生msgId碰撞的危险)
|
|
|
local MSG_TIMER_ID_MAX = 0x7FFFFF
|
|
|
|
|
|
--- 任务定时器id
|
|
|
+-- 任务定时器id(通过sys.wait或者sys.waitUntil接口创建定时器时动态分配的定时器id)
|
|
|
+-- 定时器id的取值范围为0到TASK_TIMER_ID_MAX - 1
|
|
|
local taskTimerId = 0
|
|
|
--- 消息定时器id
|
|
|
+
|
|
|
+-- 消息定时器id(通过sys.timerStart或者sys.timerLoopStart或者sys.waitMsg接口创建定时器时动态分配的定时器id)
|
|
|
+-- 定时器id的取值范围为TASK_TIMER_ID_MAX +1 到 MSG_TIMER_ID_MAX
|
|
|
local msgId = TASK_TIMER_ID_MAX
|
|
|
--- 定时器id表
|
|
|
+
|
|
|
+-- 定时器处理表
|
|
|
+-- 表中记录了两种类型的定时器
|
|
|
+-- 第一种定时器记录的是定时器id和对应的回调函数
|
|
|
+-- 第二种定时器记录的是定时器id和对应的task对象
|
|
|
+-- 数据结构如下:
|
|
|
+-- local timerPool =
|
|
|
+-- {
|
|
|
+-- -- id1表示:通过sys.timerStart或者sys.timerLoopStart或者sys.waitMsg接口创建定时器时,sys.lua内部自动分配的定时器id
|
|
|
+-- -- func1表示:定时器对应的回调函数,例如:sys.timerStart(blink_on, 5000)中的回调函数blink_on
|
|
|
+-- [id1] = func1,
|
|
|
+
|
|
|
+-- -- id2表示:通过sys.wait或者sys.waitUntil接口创建定时器时动态分配的定时器id
|
|
|
+-- -- co2表示:创建定时器时,当前正常运行的协程(task)对象,例如:schedulig.lua中sys.wait所处的task对象
|
|
|
+-- [id2] = co2,
|
|
|
+--
|
|
|
+-- ...
|
|
|
+-- }
|
|
|
local timerPool = {}
|
|
|
---消息定时器参数表
|
|
|
+
|
|
|
+-- 定时器回调函数参数表
|
|
|
+-- 此处的定时器指的是timerPool中记录的第一种定时器
|
|
|
+-- 数据结构如下:
|
|
|
+-- local para =
|
|
|
+-- {
|
|
|
+-- -- id1表示:通过sys.timerStart或者sys.timerLoopStart或者sys.waitMsg接口创建定时器时,sys.lua内部自动分配的定时器id
|
|
|
+-- -- {...}表示:定时器对应的回调函数参数,例如:sys.timerStart(blink_on, 5000, "red", 1)中的{"red", 1}
|
|
|
+-- [id1] = {...},
|
|
|
+
|
|
|
+-- ...
|
|
|
+-- }
|
|
|
local para = {}
|
|
|
+
|
|
|
+
|
|
|
--定时器是否循环表
|
|
|
--local loop = {}
|
|
|
--lua脚本运行出错时,是否回退为本地烧写的版本
|
|
|
@@ -54,26 +87,60 @@ _G.COROUTINE_ERROR_RESTART = true
|
|
|
--local rawcoresume = coroutine.resume
|
|
|
local function wrapper(co,...)
|
|
|
local arg = {...}
|
|
|
+ -- arg[1]为false,表示协程执行出错
|
|
|
+ -- 此时arg[2]为错误信息描述字符串,例如:[string "hello_luatos.lua"]:27: attempt to perform arithmetic on a string value
|
|
|
if not arg[1] then
|
|
|
+ -- 可以获取到完整的当前协程的调用堆栈信息(包括函数调用链和行号),例如:
|
|
|
+ -- stack traceback:
|
|
|
+ -- [string "scheduling.lua"]:27: in function <[string "scheduling.lua"]:19>
|
|
|
local traceBack = debug.traceback(co)
|
|
|
+
|
|
|
+
|
|
|
+ -- arg[2]为错误信息描述:包括文件名,行数,错误信息字符串,例如:
|
|
|
+ -- [string "scheduling.lua"]:27: attempt to perform arithmetic on a string value
|
|
|
traceBack = (traceBack and traceBack~="") and (arg[2].."\r\n"..traceBack) or arg[2]
|
|
|
log.error("coroutine.resume",traceBack)
|
|
|
--if errDump and type(errDump.appendErr)=="function" then
|
|
|
-- errDump.appendErr(traceBack)
|
|
|
--end
|
|
|
+
|
|
|
+ -- 此处COROUTINE_ERROR_ROLL_BACK变量设为了true,表示:
|
|
|
+ -- 如果某一个协程运行错误,则启动一个500毫秒的定时器,500毫秒之后,执行assert(false, traceBack)
|
|
|
+ -- 最终表现为:日志中输出traceBack的信息,然后Lua虚拟机异常退出,15秒后自动重启软件;例如:
|
|
|
+ -- Luat:
|
|
|
+ -- [string "sys.lua"]:434: [string "scheduling.lua"]:27: attempt to perform arithmetic on a string value
|
|
|
+ -- stack traceback:
|
|
|
+ -- [string "scheduling.lua"]:27: in function <[string "scheduling.lua"]:19>
|
|
|
+ -- Lua VM exit!! reboot in 15000ms
|
|
|
+ -- 根据这里的代码设计,我们可以知道,如果应用脚本中某一个task(协程)运行异常时,此时其余task的都还可以正常运行
|
|
|
+ -- 如果不想自动重启软件,则可以在main.lua中的最开始的位置,设置:
|
|
|
+ -- _G.COROUTINE_ERROR_ROLL_BACK = false
|
|
|
+ -- _G.COROUTINE_ERROR_RESTART = false
|
|
|
if _G.COROUTINE_ERROR_ROLL_BACK then
|
|
|
sys.timerStart(assert,500,false,traceBack)
|
|
|
elseif _G.COROUTINE_ERROR_RESTART then
|
|
|
rtos.reboot()
|
|
|
end
|
|
|
+ -- else
|
|
|
+ -- log.info("coroutine.resume return", unpack(arg))
|
|
|
end
|
|
|
return ...
|
|
|
end
|
|
|
sys.coresume = function(...)
|
|
|
local arg = {...}
|
|
|
+ -- 此处先执行coroutine.resume(...),表示运行协程,运行时传入了协程对象和处理函数的可变参数
|
|
|
+ -- coroutine.resume返回多个值:
|
|
|
+ -- 第一个值是布尔值,表示协程是否执行成功
|
|
|
+ -- 如果执行成功,第一个返回值为true
|
|
|
+ -- 后续的返回值是协程运行中,通过coroutine.yield接口使这个协程挂起时传递的参数;
|
|
|
+ -- 或者是协程正常运行结束的返回值(当协程执行完毕时);
|
|
|
+ -- 如果执行中出现错误,第一个返回值为false,第二个返回值是错误信息
|
|
|
return wrapper(arg[1], coroutine.resume(...))
|
|
|
end
|
|
|
|
|
|
+
|
|
|
+-- 判断当前运行环境是否在一个协程的处理函数中
|
|
|
+-- 如果是,co为协程对象
|
|
|
function sys.check_task()
|
|
|
local co, ismain = coroutine.running()
|
|
|
if ismain then
|
|
|
@@ -89,8 +156,14 @@ end
|
|
|
function sys.wait(ms)
|
|
|
-- 参数检测,参数不能为负值
|
|
|
--assert(ms > 0, "The wait time cannot be negative!")
|
|
|
- -- 选一个未使用的定时器ID给该任务线程
|
|
|
+ -- 判断当前运行环境是否在一个协程的处理函数中
|
|
|
+ -- 如果是,co为协程对象
|
|
|
local co = sys.check_task()
|
|
|
+
|
|
|
+ -- 为定时器申请id
|
|
|
+ -- 定时器id的取值范围为0 到 TASK_TIMER_ID_MAX - 1
|
|
|
+ -- 将定时器id和协程对象记录到定时器处理表timerPool中
|
|
|
+ -- timerPool[timerid] = co
|
|
|
while true do
|
|
|
if taskTimerId >= TASK_TIMER_ID_MAX - 1 then
|
|
|
taskTimerId = 0
|
|
|
@@ -103,10 +176,18 @@ function sys.wait(ms)
|
|
|
end
|
|
|
local timerid = taskTimerId
|
|
|
timerPool[timerid] = co
|
|
|
+
|
|
|
-- 调用core的rtos定时器
|
|
|
if 1 ~= rtos.timer_start(timerid, ms) then log.debug("rtos.timer_start error") return end
|
|
|
- -- 挂起调用的任务线程
|
|
|
+
|
|
|
+ -- 挂起调用的任务协程
|
|
|
+ -- 协程恢复时:当再次调用coroutine.resume(co, ...)恢复协程时,resume传入的...参数会作为上此处yield的返回值。
|
|
|
local message = {coroutine.yield()}
|
|
|
+
|
|
|
+ -- 下面这段代码适用于以下场景,例如
|
|
|
+ -- sys.waitUntil("SIM_IND", 120000)
|
|
|
+ -- 在120秒定时器消息达到之前,首先产生了"SIM_IND"消息,会在上一行代码的coroutine.yield()返回,切换为运行状态
|
|
|
+ -- 此时执行下面的代码,将正在运行的定时器停止运行,并且删除
|
|
|
if #message ~= 0 then
|
|
|
rtos.timer_stop(timerid)
|
|
|
timerPool[timerid] = nil
|
|
|
@@ -121,13 +202,38 @@ end
|
|
|
-- @return data 接收到消息返回消息参数
|
|
|
-- @usage result, data = sys.waitUntil("SIM_IND", 120000)
|
|
|
function sys.waitUntil(id, ms)
|
|
|
+ -- 判断当前运行环境是否在一个协程的处理函数中
|
|
|
+ -- 如果是,co为协程对象
|
|
|
local co = sys.check_task()
|
|
|
+
|
|
|
+ -- 在全局消息订阅表subscribers中添加一条记录
|
|
|
+ -- subscribers[id][co] = true
|
|
|
+ -- 表示当前协程需要处理id表示的消息
|
|
|
sys.subscribe(id, co)
|
|
|
+
|
|
|
+ -- 如果传入了ms参数,表示配置了等待超时参数
|
|
|
+ -- 此时直接执行sys.wait(ms):在wait函数内部,创建运行定时器,并且挂起当前协程
|
|
|
+ -- 当定时器超时消息到达或者id表示的消息达到时,当前协程切换为运行状态
|
|
|
+ -- 当定时器超时消息触发的状态切换,此时返回值message为{sys.wait(ms)},为{}
|
|
|
+ -- 当id表示的消息触发的状态切换,此时返回值message为{sys.wait(ms)},为{id, {...}}
|
|
|
+
|
|
|
+ -- 如果没有传入ms参数,表示一直在等待id表示的消息
|
|
|
+ -- 此时直接执行下一行代码中的coroutine.yield(),挂起当前协程
|
|
|
+ -- 当id表示的消息达到时,当前协程切换为运行状态
|
|
|
+ -- 此时返回值message为{coroutine.yield()},为{id, {...}}
|
|
|
local message = ms and {sys.wait(ms)} or {coroutine.yield()}
|
|
|
+
|
|
|
+ -- 在全局消息订阅表subscribers中清除一条记录
|
|
|
+ -- subscribers[id][co] = nil
|
|
|
+ -- 表示当前协程不再需要处理id表示的消息
|
|
|
sys.unsubscribe(id, co)
|
|
|
+
|
|
|
+ -- 如果是id表示的消息到达退出了阻塞等待状态,则message[1] ~= nil为true
|
|
|
+ -- 如果是定时器超时消息到达退出了阻塞等待状态,则message[1] ~= nil为false
|
|
|
return message[1] ~= nil, unpack(message, 2, #message)
|
|
|
end
|
|
|
|
|
|
+-- 此函数不给外部使用,忽略
|
|
|
--- 同上,但不返回等待结果
|
|
|
function sys.waitUntilMsg(id)
|
|
|
local co = sys.check_task()
|
|
|
@@ -137,6 +243,7 @@ function sys.waitUntilMsg(id)
|
|
|
return unpack(message, 2, #message)
|
|
|
end
|
|
|
|
|
|
+-- 此函数不给外部使用,忽略
|
|
|
--- Task任务的条件等待函数扩展(包括事件消息和定时器消息等条件),只能用于任务函数中。
|
|
|
-- @param id 消息ID
|
|
|
-- @number ms 等待超时时间,单位ms,最大等待126322567毫秒
|
|
|
@@ -158,7 +265,12 @@ end
|
|
|
-- @return co 返回该任务的线程号
|
|
|
-- @usage sys.taskInit(task1,'a','b')
|
|
|
function sys.taskInit(fun, ...)
|
|
|
+ -- 创建协程,返回协程对象co
|
|
|
+ -- 创建一个Lua协程,协程的处理函数为fun,返回一个协程对象co
|
|
|
+ -- 执行这一行代码之后,这个协程并不会运行,而是处于挂起状态
|
|
|
local co = coroutine.create(fun)
|
|
|
+
|
|
|
+ -- 启动运行协程,传入协程对象和任务处理函数携带的可变参数
|
|
|
sys.coresume(co, ...)
|
|
|
return co
|
|
|
end
|
|
|
@@ -191,15 +303,21 @@ end
|
|
|
-- @return 无
|
|
|
-- @usage timerStop(1)
|
|
|
function sys.timerStop(val, ...)
|
|
|
- -- val 为定时器ID
|
|
|
+ -- val为number类型时,表示定时器id
|
|
|
if type(val) == 'number' then
|
|
|
+ -- 在定时器处理表timerPool中,清空定时器id val对应的处理函数记录
|
|
|
+ -- 在定时器回调函数参数表para中,清空定时器id val对应的回调函数的参数记录
|
|
|
timerPool[val], para[val] = nil, nil
|
|
|
+
|
|
|
+ -- 调用内核固件的接口,停止并且删除定时器id val对应的定时器
|
|
|
rtos.timer_stop(val)
|
|
|
+ -- val为其他类型时(实际上仅支持function类型),表示定时器的回调函数
|
|
|
else
|
|
|
for k, v in pairs(timerPool) do
|
|
|
- -- 回调函数相同
|
|
|
+ -- 根据回调函数,从定时器处理表timerPool中找到定时器id
|
|
|
+ -- v为function类型,下一行代码实际上判断的是v == val
|
|
|
if type(v) == 'table' and v.cb == val or v == val then
|
|
|
- -- 可变参数相同
|
|
|
+ -- 根据定时器id,从定时器会回调函数参数表中,判断可变参数相同
|
|
|
if cmpTable({...}, para[k]) then
|
|
|
rtos.timer_stop(k)
|
|
|
timerPool[k], para[k] = nil, nil
|
|
|
@@ -216,6 +334,8 @@ end
|
|
|
-- @usage timerStopAll(cbFnc)
|
|
|
function sys.timerStopAll(fnc)
|
|
|
for k, v in pairs(timerPool) do
|
|
|
+ -- 根据回调函数,从定时器处理表timerPool中找到定时器id
|
|
|
+ -- v为function类型,下一行代码实际上判断的是v == fnc
|
|
|
if type(v) == "table" and v.cb == fnc or v == fnc then
|
|
|
rtos.timer_stop(k)
|
|
|
timerPool[k], para[k] = nil, nil
|
|
|
@@ -227,14 +347,21 @@ function sys.timerAdvStart(fnc, ms, _repeat, ...)
|
|
|
--回调函数和时长检测
|
|
|
--assert(fnc ~= nil, "sys.timerStart(first param) is nil !")
|
|
|
--assert(ms > 0, "sys.timerStart(Second parameter) is <= zero !")
|
|
|
+
|
|
|
-- 关闭完全相同的定时器
|
|
|
+ -- 定时器回调函数+回调参数的方式,可以唯一标识一个定时器
|
|
|
+ -- 所以此处调用sys.timerStop接口,传入回调函数+回调参数,就可以关闭之前的重复定时器
|
|
|
local arg = {...}
|
|
|
if #arg == 0 then
|
|
|
sys.timerStop(fnc)
|
|
|
else
|
|
|
sys.timerStop(fnc, ...)
|
|
|
end
|
|
|
- -- 为定时器申请ID,ID值 1-20 留给任务,20-30留给消息专用定时器
|
|
|
+
|
|
|
+ -- 为定时器申请id
|
|
|
+ -- 定时器id的取值范围为TASK_TIMER_ID_MAX +1 到 MSG_TIMER_ID_MAX
|
|
|
+ -- 将定时器id和定时器回调函数记录到定时器处理表timerPool中
|
|
|
+ -- timerPool[msgId] = fnc
|
|
|
while true do
|
|
|
if msgId >= MSG_TIMER_ID_MAX then msgId = TASK_TIMER_ID_MAX end
|
|
|
msgId = msgId + 1
|
|
|
@@ -243,12 +370,17 @@ function sys.timerAdvStart(fnc, ms, _repeat, ...)
|
|
|
break
|
|
|
end
|
|
|
end
|
|
|
- --调用底层接口启动定时器
|
|
|
- if rtos.timer_start(msgId, ms, _repeat) ~= 1 then return end
|
|
|
- --如果存在可变参数,在定时器参数表中保存参数
|
|
|
+ --调用内核固件接口创建并且启动定时器
|
|
|
+ if rtos.timer_start(msgId, ms, _repeat) ~= 1 then
|
|
|
+ log.error("rtos.timer_start", "create fail!!!")
|
|
|
+ return
|
|
|
+ end
|
|
|
+
|
|
|
+ --如果存在回调函数的可变参数,在定时器回调函数参数表para中保存参数
|
|
|
if #arg ~= 0 then
|
|
|
para[msgId] = arg
|
|
|
end
|
|
|
+
|
|
|
--返回定时器id
|
|
|
return msgId
|
|
|
end
|
|
|
@@ -278,8 +410,10 @@ end
|
|
|
-- @param ... 可变参数
|
|
|
-- @return number 开启状态返回true,否则nil
|
|
|
function sys.timerIsActive(val, ...)
|
|
|
+ -- 根据定时器id来判断
|
|
|
if type(val) == "number" then
|
|
|
return timerPool[val]
|
|
|
+ -- 根据定时器回调函数和回调参数来判断
|
|
|
else
|
|
|
for k, v in pairs(timerPool) do
|
|
|
if v == val then
|
|
|
@@ -291,9 +425,39 @@ end
|
|
|
|
|
|
|
|
|
------------------------------------------ LUA应用消息订阅/发布接口 ------------------------------------------
|
|
|
--- 订阅者列表
|
|
|
+-- 全局消息订阅表
|
|
|
+-- 数据结构如下:
|
|
|
+-- local subscribers =
|
|
|
+-- {
|
|
|
+-- [msg1] =
|
|
|
+-- {
|
|
|
+-- cbfunc1 = true, -- sys.subscribe(msg1, cbfunc1)
|
|
|
+-- task1 = true, -- 在task1的处理函数中,调用sys.waitUntil(msg1[, timeout])
|
|
|
+-- cbfunc2 = true,
|
|
|
+-- task2 = true,
|
|
|
+-- ......
|
|
|
+-- cbfuncN = true,
|
|
|
+-- taskN = true,
|
|
|
+-- },
|
|
|
+
|
|
|
+-- [msg2] = { ...... },
|
|
|
+-- [msg3] = { ...... },
|
|
|
+-- ......,
|
|
|
+-- [msgN] = { ...... }
|
|
|
+-- }
|
|
|
local subscribers = {}
|
|
|
---内部消息队列
|
|
|
+
|
|
|
+-- 全局消息队列
|
|
|
+-- 数据结构如下:
|
|
|
+-- local messageQueue =
|
|
|
+-- {
|
|
|
+-- [1] = {msg1, {...}}, --sys.publish(msg1, ...)
|
|
|
+-- [2] = {msg2, {...}},
|
|
|
+-- [3] = {msg3, {...}},
|
|
|
+
|
|
|
+-- ......,
|
|
|
+-- [n] = { ...... }
|
|
|
+-- }
|
|
|
local messageQueue = {}
|
|
|
|
|
|
--- 订阅消息
|
|
|
@@ -306,11 +470,15 @@ function sys.subscribe(id, callback)
|
|
|
-- return
|
|
|
--end
|
|
|
--log.debug("sys", "subscribe", id, callback)
|
|
|
+ -- 下面这段if语句的使用方法,在api文档中没有介绍,在demo代码中从来没有用到,忽略
|
|
|
if type(id) == "table" then
|
|
|
-- 支持多topic订阅
|
|
|
for _, v in pairs(id) do sys.subscribe(v, callback) end
|
|
|
return
|
|
|
end
|
|
|
+
|
|
|
+ -- 下面两行代码
|
|
|
+ -- 在全局消息订阅表中,增加一条“消息和对应的回调函数”的记录
|
|
|
if not subscribers[id] then subscribers[id] = {} end
|
|
|
subscribers[id][callback] = true
|
|
|
end
|
|
|
@@ -324,17 +492,25 @@ function sys.unsubscribe(id, callback)
|
|
|
-- return
|
|
|
--end
|
|
|
--log.debug("sys", "unsubscribe", id, callback)
|
|
|
+ -- 下面这段if语句的使用方法,在api文档中没有介绍,在demo代码中从来没有用到,忽略
|
|
|
if type(id) == "table" then
|
|
|
-- 支持多topic订阅
|
|
|
for _, v in pairs(id) do sys.unsubscribe(v, callback) end
|
|
|
return
|
|
|
end
|
|
|
+
|
|
|
+ -- 下面的if else语句
|
|
|
+ -- 在全局消息订阅表中,删除一条“消息和对应的回调函数”的记录
|
|
|
if subscribers[id] then
|
|
|
subscribers[id][callback] = nil
|
|
|
else
|
|
|
return
|
|
|
end
|
|
|
- -- 判断消息是否无其他订阅
|
|
|
+
|
|
|
+
|
|
|
+ -- 下面的几行代码
|
|
|
+ -- 判断一下当前消息是否还有其他的订阅者
|
|
|
+ -- 如果没有任何订阅者,则清除消息的记录
|
|
|
for k, _ in pairs(subscribers[id]) do
|
|
|
return
|
|
|
end
|
|
|
@@ -346,21 +522,30 @@ end
|
|
|
-- @return 无
|
|
|
-- @usage publish("NET_STATUS_IND")
|
|
|
function sys.publish(...)
|
|
|
+ -- 在全局消息队列末尾插入记录一条全局消息
|
|
|
table.insert(messageQueue, {...})
|
|
|
end
|
|
|
|
|
|
--- 分发消息
|
|
|
+-- 循环分发处理全局消息队列messageQueue中的所有消息
|
|
|
local function dispatch()
|
|
|
while true do
|
|
|
if #messageQueue == 0 then
|
|
|
break
|
|
|
end
|
|
|
+
|
|
|
+ -- 从全局消息队列messageQueue中取出来队首的消息
|
|
|
local message = table.remove(messageQueue, 1)
|
|
|
+
|
|
|
+ -- 根据取出的队首消息,在全部消息订阅表中查找是否有订阅者
|
|
|
if subscribers[message[1]] then
|
|
|
local tmpt = {}
|
|
|
+ -- 将消息的所有订阅者(回调函数和task对象)临时存放到tmpt中
|
|
|
for callback, _ in pairs(subscribers[message[1]]) do
|
|
|
table.insert(tmpt, callback)
|
|
|
end
|
|
|
+
|
|
|
+ -- 遍历所有订阅者
|
|
|
+ -- 根据订阅者类型,执行回调函数或者恢复运行task
|
|
|
for _, callback in ipairs(tmpt) do
|
|
|
if type(callback) == "function" then
|
|
|
callback(unpack(message, 2, #message))
|
|
|
@@ -387,27 +572,43 @@ end
|
|
|
|
|
|
------------------------------------------ Luat 主调度框架 ------------------------------------------
|
|
|
function sys.safeRun()
|
|
|
- -- 分发内部消息
|
|
|
+ -- 循环分发处理全局消息队列messageQueue中的所有消息
|
|
|
dispatch()
|
|
|
- -- 阻塞读取外部消息
|
|
|
+ -- 阻塞读取内核消息队列中第一条消息
|
|
|
local msg, param, exparam = rtos.receive(rtos.INF_TIMEOUT)
|
|
|
- --log.info("sys", msg, param, exparam, tableNSize(timerPool), tableNSize(para), tableNSize(taskTimerPool), tableNSize(subscribers))
|
|
|
+ -- log.info("rtos.receive", msg, param, exparam)
|
|
|
-- 空消息?
|
|
|
if not msg or msg == 0 then
|
|
|
- -- 无任何操作
|
|
|
- -- 判断是否为定时器消息,并且消息是否注册
|
|
|
+ -- 如果走到了这里,说明rtos.receive在内核固件的业务逻辑中:
|
|
|
+ -- 已经取出了一条消息,并且在内核固件中自动处理了,例如:
|
|
|
+ -- uart接收到新数据的中断消息,会在rtos.receive中自动调用uart.on(uart_id, "receive", cbfun)中的cbfun
|
|
|
+ -- log.info("sys.safeRun", "needn't handle")
|
|
|
+
|
|
|
+ -- 从内核消息队列中读到了定时器消息
|
|
|
+ -- msg为rtos.MSG_TIMER
|
|
|
+ -- param为定时器id
|
|
|
+ -- exparam表示是否为循环定时器
|
|
|
elseif msg == rtos.MSG_TIMER and timerPool[param] then
|
|
|
+ -- 任务定时器类型
|
|
|
if param < TASK_TIMER_ID_MAX then
|
|
|
local taskId = timerPool[param]
|
|
|
+ -- 任务定时器只能是单次定时器,所以从定时器处理表中删除此定时器记录
|
|
|
timerPool[param] = nil
|
|
|
+ -- 将等待定时器消息的任务,由挂起阻塞状态切换为运行状态
|
|
|
sys.coresume(taskId)
|
|
|
+ -- 消息定时器类型
|
|
|
else
|
|
|
+ -- 取出来当前定时器对应的回调函数
|
|
|
local cb = timerPool[param]
|
|
|
- --如果不是循环定时器,从定时器id表中删除此定时器
|
|
|
+ -- 如果不是循环定时器,从定时器处理表中删除此定时器记录
|
|
|
if exparam == 0 then timerPool[param] = nil end
|
|
|
+
|
|
|
+ -- 当前定时器的回调函数有回调参数
|
|
|
if para[param] ~= nil then
|
|
|
cb(unpack(para[param]))
|
|
|
+ -- 如果不是循环定时器,从定时器回调函数参数表中删除此定时器记录
|
|
|
if exparam == 0 then para[param] = nil end
|
|
|
+ -- 当前定时器的回调函数没有回调参数
|
|
|
else
|
|
|
cb()
|
|
|
end
|
|
|
@@ -425,6 +626,8 @@ end
|
|
|
--- run()从底层获取core消息并及时处理相关消息,查询定时器并调度各注册成功的任务线程运行和挂起
|
|
|
-- @return 无
|
|
|
-- @usage sys.run()
|
|
|
+-- log.info("_G.SYSP", _G.SYSP)
|
|
|
+-- _G.SYSP为nil,所以此处定义了sys.run()就是一直在循环执行sys.safeRun()
|
|
|
if _G.SYSP then
|
|
|
function sys.run() end
|
|
|
else
|
|
|
@@ -435,10 +638,15 @@ else
|
|
|
end
|
|
|
end
|
|
|
|
|
|
+-- _G是Lua的全局环境表
|
|
|
+-- 这里给_G定义了一个全局函数sys_pub
|
|
|
+-- sys_pub的作用是:内核固件的C代码中直接发布全局消息,Lua脚本中订阅此全局消息的应用都可以接收处理,例如:网络环境准备就绪的消息"IP_READY"
|
|
|
_G.sys_pub = sys.publish
|
|
|
|
|
|
-- 并入原本的sysplus
|
|
|
|
|
|
+
|
|
|
+-- 以下给异步C接口使用的几个api,在sys核心库中,没有开发给用户直接使用;忽略
|
|
|
----------------------------------------------
|
|
|
-- 提供给异步c接口使用, by 晨旭
|
|
|
sys.cwaitMt = {
|
|
|
@@ -481,7 +689,30 @@ end
|
|
|
-------------------------------------------------------------------
|
|
|
------------- 基于任务的task扩展 by 李思琦---------------------------
|
|
|
|
|
|
---任务列表
|
|
|
+-- 高级task的任务列表
|
|
|
+-- 数据结构如下:
|
|
|
+-- local taskList =
|
|
|
+-- {
|
|
|
+-- ["task_name1"] =
|
|
|
+-- {
|
|
|
+-- -- 定向消息队列,用来存储sys.sendMsg("task_name1", param1, param2, param3, param4)给"task_name1"的高级task发布的param1, param2, param3, param4消息参数
|
|
|
+-- -- table.insert(taskList["task_name1"].msgQueue, {param1, param2, param3, param4})
|
|
|
+-- msgQueue = {{param1, param2, param3, param4}, {}, ...},
|
|
|
+
|
|
|
+-- -- sys.waitMsg(taskName, target, ms)时,如果传入了ms超时时长,并且当前task在阻塞等待target消息;
|
|
|
+-- -- 则To表示阻塞等待过程中,是否因为超时原因退出了阻塞等待状态
|
|
|
+-- To = false,
|
|
|
+
|
|
|
+-- -- 非目标消息回调函数
|
|
|
+-- cb = cbFun
|
|
|
+-- },
|
|
|
+
|
|
|
+-- ["task_name2"] =
|
|
|
+-- {
|
|
|
+-- },
|
|
|
+--
|
|
|
+-- ...
|
|
|
+-- }
|
|
|
local taskList = {}
|
|
|
|
|
|
--- 创建一个任务线程,在模块最末行调用该函数并注册模块中的任务函数,main.lua导入该模块即可
|
|
|
@@ -496,7 +727,11 @@ function sys.taskInitEx(fun, taskName, cbFun, ...)
|
|
|
log.error("sys", "taskName is nil", debug.traceback())
|
|
|
return
|
|
|
end
|
|
|
+
|
|
|
+ -- 在高级task任务列表中,申请此高级任务对应的定向消息队列、阻塞等待定向消息超时标志、非目标消息回调函数
|
|
|
taskList[taskName]={msgQueue={}, To=false, cb=cbFun}
|
|
|
+
|
|
|
+ -- 创建一个基础task
|
|
|
return sys.taskInit(fun, ...)
|
|
|
end
|
|
|
|
|
|
@@ -505,9 +740,24 @@ end
|
|
|
-- @return 无
|
|
|
-- @usage sys.taskDel('a')
|
|
|
function sys.taskDel(taskName)
|
|
|
+ -- 释放taskName对应的高级task全部信息表资源,
|
|
|
+ -- 释放的是taskList[taskName] 对应的 {msgQueue={}, To=false, cb=cbFun}占用的内存
|
|
|
+ -- 因为taskList是全局变量,会一直存在;
|
|
|
+ -- 如果taskList[taskName]没有赋值为nil,则taskList[taskName]一直引用{msgQueue={}, To=false, cb=cbFun}
|
|
|
+ -- 此时Lua的垃圾回收机制永远不会自动回收{msgQueue={}, To=false, cb=cbFun}所占用的内存
|
|
|
+ -- 所以在高级task的任务处理函数执行结束,每个return语句正常退出前,或者最后执行结束自然退出前,
|
|
|
+ -- 都要调用sys.taskDel(taskName),释放{msgQueue={}, To=false, cb=cbFun}占用的内存
|
|
|
+ -- 如果高级task的任务处理函数运行过程中,发生异常退出,这种情况下,即使我们写了sys.taskDel(taskName),也不会被执行
|
|
|
+ -- 遇到这种问题,需要怎么处理呢?分为以下两种情况:
|
|
|
+ -- 1、目前的LuatOS设计,当task运行异常退出,默认会自动重启软件系统,
|
|
|
+ -- 这种情况下,软件都重启了,就不用考虑未释放的内存问题了;
|
|
|
+ -- 2、如果你看懂了sys.lua的内部设计,当task运行异常退出时,可以控制不重启软件系统,
|
|
|
+ -- 这种情况下,就需要修改sys.lua的内部设计,可以自动释放异常退出的高级task对应的信息表资源内存,
|
|
|
+ -- 目前还没有实现这种功能,后续如果客户有需求,可以开发支持
|
|
|
taskList[taskName]=nil
|
|
|
end
|
|
|
|
|
|
+-- 等待定向消息定时器回调函数
|
|
|
local function waitTo(taskName)
|
|
|
taskList[taskName].To = true
|
|
|
sys.publish(taskName)
|
|
|
@@ -520,55 +770,102 @@ end
|
|
|
-- @return msg or false 成功返回table型的msg,超时返回false
|
|
|
-- @usage sys.waitMsg('a', 'b', 1000)
|
|
|
function sys.waitMsg(taskName, target, ms)
|
|
|
+ -- 此接口只推荐在sys.taskInitEx创建的高级task的任务处理函数中使用
|
|
|
if taskList[taskName] == nil then
|
|
|
log.error("sys", "sys.taskInitEx启动的task才能使用waitMsg")
|
|
|
return false
|
|
|
end
|
|
|
local msg = false
|
|
|
local message = nil
|
|
|
- if #taskList[taskName].msgQueue > 0 then
|
|
|
+
|
|
|
+ -- 如果定向消息队列中有消息
|
|
|
+ while #taskList[taskName].msgQueue > 0 do
|
|
|
+ -- 取出来队首的消息
|
|
|
msg = table.remove(taskList[taskName].msgQueue, 1)
|
|
|
+
|
|
|
+ -- 如果没有指定期望接收的消息,则表示期望接收任何消息
|
|
|
+ -- 直接返回队首的消息给用户脚本去处理
|
|
|
if target == nil then
|
|
|
return msg
|
|
|
end
|
|
|
+
|
|
|
+ -- 如果队首消息是期望处理的消息
|
|
|
+ -- 直接返回队首的消息给用户脚本去处理
|
|
|
if (msg[1] == target) then
|
|
|
return msg
|
|
|
+ -- 如果队首消息不是期望处理的消息,并且也传入了非目标消息回调函数
|
|
|
+ -- 则直接将队首消息丢给非目标消息回调函数去处理
|
|
|
+ -- 然后while循环继续判断下一条消息
|
|
|
elseif type(taskList[taskName].cb) == "function" then
|
|
|
taskList[taskName].cb(msg)
|
|
|
end
|
|
|
end
|
|
|
+
|
|
|
+ -- 走到了这里,说明定向消息队列中已经没有消息
|
|
|
+
|
|
|
+ -- 使用当前task对象订阅名称为taskName的全局消息
|
|
|
sys.subscribe(taskName, coroutine.running())
|
|
|
+
|
|
|
+ -- 如果传入了ms参数,启动一个等待定向目标消息超时定时器
|
|
|
sys.timerStop(waitTo, taskName)
|
|
|
if ms and ms ~= 0 then
|
|
|
sys.timerStart(waitTo, ms, taskName)
|
|
|
end
|
|
|
taskList[taskName].To = false
|
|
|
+
|
|
|
+
|
|
|
local finish=false
|
|
|
while not finish do
|
|
|
+ -- 阻塞挂起当前task
|
|
|
+ -- 以下两种情况,会退出挂起状态,切换为运行状态:
|
|
|
+ -- 1、当调用sys.sendMsg接口向此定向消息队列中发送一条消息时,会publish一条名称为taskName的全局消息
|
|
|
+ -- 在sys.run()下次调度时,会分发处理这条全局消息,发现当前task订阅了这条全局消息,所以将当前task切换为运行状态
|
|
|
+ -- 2、等待定向目标消息超时定时器超时后,在waitTo函数中,也会publish一条名称为taskName的全局消息
|
|
|
+ -- 在sys.run()下次调度时,会分发处理这条全局消息,发现当前task订阅了这条全局消息,所以将当前task切换为运行状态
|
|
|
message = coroutine.yield()
|
|
|
+
|
|
|
+ -- 如果定向消息队列中有消息
|
|
|
if #taskList[taskName].msgQueue > 0 then
|
|
|
+ -- 取出来队首的消息
|
|
|
msg = table.remove(taskList[taskName].msgQueue, 1)
|
|
|
-- sys.info("check target", msg[1], target)
|
|
|
+ -- 如果没有指定期望接收的消息,则表示期望接收任何消息
|
|
|
+ -- 退出while循环
|
|
|
if target == nil then
|
|
|
finish = true
|
|
|
else
|
|
|
+ -- 如果队首消息是期望处理的消息
|
|
|
+ -- 退出while循环
|
|
|
if (msg[1] == target) then
|
|
|
finish = true
|
|
|
+ -- 如果队首消息不是期望处理的消息,并且也传入了非目标消息回调函数
|
|
|
+ -- 则直接将队首消息丢给非目标消息回调函数去处理
|
|
|
+ -- 不退出wbile循环,下次循环继续判断消息队列中的下一条消息
|
|
|
elseif type(taskList[taskName].cb) == "function" then
|
|
|
taskList[taskName].cb(msg)
|
|
|
end
|
|
|
end
|
|
|
+ -- 如果定向消息队列中没有消息,则表示此次恢复运行时定时器超时消息触发
|
|
|
+ -- 退出此while循环
|
|
|
elseif taskList[taskName].To then
|
|
|
-- sys.info(taskName, "wait message timeout")
|
|
|
finish = true
|
|
|
end
|
|
|
end
|
|
|
+
|
|
|
+ -- 定时器超时消息触发,结束等待消息
|
|
|
+ -- 这种情况下,表示没有等到目标消息
|
|
|
if taskList[taskName].To then
|
|
|
msg = nil
|
|
|
end
|
|
|
+
|
|
|
+ -- 复位定时器超时标志,关闭定时器,取消全局消息订阅
|
|
|
taskList[taskName].To = false
|
|
|
sys.timerStop(waitTo, taskName)
|
|
|
sys.unsubscribe(taskName, coroutine.running())
|
|
|
+
|
|
|
+ -- 接收到目标消息,返回table类型的定向消息{msg, para2, para3, para4}
|
|
|
+ -- 超时返回nil
|
|
|
return msg
|
|
|
end
|
|
|
|
|
|
@@ -582,13 +879,21 @@ end
|
|
|
-- @usage sys.sendMsg('a', 'b')
|
|
|
function sys.sendMsg(taskName, param1, param2, param3, param4)
|
|
|
if taskList[taskName]~=nil then
|
|
|
+ -- 向taskName所表示的高级task的定向消息队列中,插入一条定向消息
|
|
|
table.insert(taskList[taskName].msgQueue, {param1, param2, param3, param4})
|
|
|
+
|
|
|
+ -- 此处发布一条名称为taskName的全局消息,此消息的作用分为以下两种情况来描述:
|
|
|
+ -- 1、如果taskName所表示的高级task正在sys.waitMsg接口阻塞等待接收消息,
|
|
|
+ -- 则可以控制task切换为运行状态,从定向消息队列中读消息进行后续处理
|
|
|
+ -- 2、如果taskName所表示的高级task不在sys.waitMsg接口阻塞等待接收消息,
|
|
|
+ -- 则此处的publish消息接口没有任何作用,在下次sys.run()取出此消息后,会自动丢弃,不做任何处理
|
|
|
sys.publish(taskName)
|
|
|
return true
|
|
|
end
|
|
|
return false
|
|
|
end
|
|
|
|
|
|
+-- 清空taskName所表示的高级task定向消息队列中的所有消息
|
|
|
function sys.cleanMsg(taskName)
|
|
|
if taskList[taskName]~=nil then
|
|
|
taskList[taskName].msgQueue = {}
|
|
|
@@ -597,6 +902,7 @@ function sys.cleanMsg(taskName)
|
|
|
return false
|
|
|
end
|
|
|
|
|
|
+-- 这个函数目前没有直接开放给用户使用,忽略
|
|
|
function sys.taskCB(taskName, msg)
|
|
|
if taskList[taskName]~=nil then
|
|
|
if type(taskList[taskName].cb) == "function" then
|
|
|
@@ -607,6 +913,10 @@ function sys.taskCB(taskName, msg)
|
|
|
log.error(taskName, "no cb fun")
|
|
|
end
|
|
|
|
|
|
+-- _G是Lua的全局环境表
|
|
|
+-- 这里给_G定义了两个全局函数sys_send和sys_wait
|
|
|
+-- sys_send的作用是:内核固件的C代码中直接发送定向消息给Lua脚本中的高级task,例如socket.EVENT类型的消息
|
|
|
+-- sys_wait的作用是:目前没有直接开放给用户使用,忽略
|
|
|
_G.sys_send = sys.sendMsg
|
|
|
_G.sys_wait = sys.waitMsg
|
|
|
|