iT邦幫忙

2021 iThome 鐵人賽

DAY 16
0
Software Development

在麥塊的農場裡寫 Lua系列 第 16

Day16 中斷 Lua 的執行 - coroutine

  • 分享至 

  • xImage
  •  

上次看的電腦螢幕程式 rom/programs/monitor.lua
還有一段特別的寫法,是關於 Lua 的 pcall 和 coroutine
這是今天我研究的主題

pcall 有類似於其他語言 try catch 的作用
也就是他可以捕捉錯誤,而不會導致程式意外結束
pcall 的語法如下

status, value = pcall(f, arg1, ...)

status 接收 pcall 第一個回傳值,函數 f 執行成功則回傳 true,否則回傳 false
value 接收 pcall 第二個回傳值,函數 f 執行成功則回傳執行結果,否則回傳錯誤資訊

coroutine

接下來看看 coroutine
coroutine 乍看之下很像 thread,但似乎不太一樣
它是可以中斷及繼續執行的函式
coroutine 在 create 的時候不會執行
一直到呼叫 resume 才開始執行
monitor.lua 就是將使用者要執行的指令與參數先包裝成 coroutine
然後丟進 pcall 裡頭執行一個無窮迴圈
這段時間就是在等待玩家與螢幕的互動操作
主控權暫時轉移到電腦螢幕上

而下面看到的個事件如下

  • term_size / monitor_resize:這是玩家加大或縮小螢幕時觸發的事件
  • mouse_click / monitor_touch / mouse_up:玩家在螢幕上點擊,這我目前只知道在 Advanced Monitor 上畫圖才會用到
local monitor = peripheral.wrap(sName)
local previousTerm = term.redirect(monitor)

local co = coroutine.create(function()
    (shell.execute or shell.run)(sProgram, table.unpack(tArgs, 3))
end)

local function resume(...)
    local ok, param = coroutine.resume(co, ...)
    if not ok then
        printError(param)
    end
    return param
end

local timers = {}

local ok, param = pcall(function()
    local sFilter = resume()
    while coroutine.status(co) ~= "dead" do
        local tEvent = table.pack(os.pullEventRaw())
        if sFilter == nil or tEvent[1] == sFilter or tEvent[1] == "terminate" then
            sFilter = resume(table.unpack(tEvent, 1, tEvent.n))
        end
        if coroutine.status(co) ~= "dead" and (sFilter == nil or sFilter == "mouse_click") then
            if tEvent[1] == "monitor_touch" and tEvent[2] == sName then
                timers[os.startTimer(0.1)] = { tEvent[3], tEvent[4] }
                sFilter = resume("mouse_click", 1, table.unpack(tEvent, 3, tEvent.n))
            end
        end
        if coroutine.status(co) ~= "dead" and (sFilter == nil or sFilter == "term_resize") then
            if tEvent[1] == "monitor_resize" and tEvent[2] == sName then
                sFilter = resume("term_resize")
            end
        end
        if coroutine.status(co) ~= "dead" and (sFilter == nil or sFilter == "mouse_up") then
            if tEvent[1] == "timer" and timers[tEvent[2]] then
                sFilter = resume("mouse_up", 1, table.unpack(timers[tEvent[2]], 1, 2))
                timers[tEvent[2]] = nil
            end
        end
    end
end)

term.redirect(previousTerm)
if not ok then
    printError(param)
end

這一段其實我覺得已經有點深,我只能看出個大概意思與用途,暫時不再深入程式本身的邏輯
以下繼續介紹 coroutine 其他特性

中斷 coroutine 後繼續執行

function hello(n)
    print("hello")
    coroutine.yield()   -- 中斷 coroutine
    print("world")
end
local co = coroutine.create(hello)
coroutine.resume(co)
print(", ")
coroutine.resume(co)

coroutine 的參數傳遞

yield 除了中斷 coroutine 的執行,也可以執行回傳數值
而 coroutine.resume 回傳的第一個值是 true/false
代表執行成功與否
第二個就是我們自訂的回傳值

function hello(n)
    print("hello")
    print("world")
    coroutine.yield(os.date())  --  中斷執行並回傳值
    print("2021 ironman")
end
local co = coroutine.create(hello)
local ok, date = coroutine.resume(co)

coroutine 的執行狀態

可以透過 coroutine.status 取得 coroutine 的執行狀態
如上述 monitor.lua 持續的判斷 coroutine 是否結束執行

function hello(n)
    print("hello")
    coroutine.yield()
    print("world")
end
local co = coroutine.create(hello)
print(coroutine.status(co))     -- suspended
coroutine.resume(co)
print(coroutine.status(co))     -- suspended
coroutine.close(co)
print(coroutine.status(co))     -- 執行結束的狀態為 dead

今天對 coroutine 的小小研究到這裡
下一回我預計會回到 CC: Tweaked 工具箱裡面繼續挖掘新的方塊!


上一篇
Day15 Lua 的全域環境變數 _G 與 _ENV
下一篇
Day17 將電腦接上喇叭 - 談 Lua 的錯誤處理
系列文
在麥塊的農場裡寫 Lua30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言