What are Lua coroutines even for? Why doesn't this code work as I expect it?

断了今生、忘了曾经 提交于 2019-11-29 22:14:43
deft_code

Coroutines aren't threads.

Coroutines are like threads that are never actively scheduled. So yes you are kinda correct that you would have to write you own scheduler to have both coroutines run simultaneously.

However you are missing the bigger picture when it comes to coroutines. Check out wikipedia's list of coroutine uses. Here is one concrete example that might guide you in the right direction.

-- level script
-- a volcano erupts every 2 minutes
function level_with_volcano( interface )

   while true do
      wait(seconds(5))
      start_eruption_volcano()
      wait(frames(10))
      s = play("rumble_sound")
      wait( end_of(s) )
      start_camera_shake()

      -- more stuff

      wait(minutes(2))
    end


end

The above script could be written to run iteratively with a switch statement and some clever state variables. But it is much more clear when written as a coroutine. The above script could be a thread but do you really need to dedicate a kernel thread to this simple code. A busy game level could have 100's of these coroutines running without impacting performance. However if each of these were a thread you might get away with 20-30 before performance started to suffer.

A coroutine is meant to allow me to write code that stores state on the stack so that I can stop running it for a while (the wait functions) and start it again where I left off.

Since there have been a number of comments asking how to implement the wait function that would make deft_code's example work, I've decided to write a possible implementation. The general idea is that we have a scheduler with a list of coroutines, and the scheduler decides when to return control to the coroutines after they give up control with their wait calls. This is desirable because it makes asynchronous code be readable and easy to reason about.

This is only one possible use of coroutines, they are a more general abstraction tool that can be used for many different purposes (such as writing iterators and generators, writing stateful stream processing objects (for example, multiple stages in a parser), implementing exceptions and continuations, etc.).

First: the scheduler definition:

local function make_scheduler()
    local script_container = {}
    return {
        continue_script = function(frame, script_thread)
            if script_container[frame] == nil then
                script_container[frame] = {}
            end
            table.insert(script_container[frame],script_thread)
        end,
        run = function(frame_number, game_control)
            if script_container[frame_number] ~= nil then
                local i = 1
                --recheck length every time, to allow coroutine to resume on
                --the same frame
                local scripts = script_container[frame_number]
                while i <= #scripts do
                    local success, msg =
                        coroutine.resume(scripts[i], game_control)
                    if not success then error(msg) end
                    i = i + 1
                end
            end
        end
    }
end

Now, initialising the world:

local fps = 60
local frame_number = 1
local scheduler = make_scheduler()

scheduler.continue_script(frame_number, coroutine.create(function(game_control)
    while true do
        --instead of passing game_control as a parameter, we could
        --have equivalently put these values in _ENV.
        game_control.wait(game_control.seconds(5))
        game_control.start_eruption_volcano()
        game_control.wait(game_control.frames(10))
        s = game_control.play("rumble_sound")
        game_control.wait( game_control.end_of(s) )
        game_control.start_camera_shake()

        -- more stuff

        game_control.wait(game_control.minutes(2))
    end
end))

The (dummy) interface to the game:

local game_control = {
    seconds = function(num)
        return math.floor(num*fps)
    end,
    minutes = function(num)
        return math.floor(num*fps*60)
    end,
    frames = function(num) return num end,
    end_of = function(sound)
        return sound.start+sound.duration-frame_number
    end,
    wait = function(frames_to_wait_for)
        scheduler.continue_script(
            frame_number+math.floor(frames_to_wait_for),
            coroutine.running())
        coroutine.yield()
    end,
    start_eruption_volcano = function()
        --obviously in a real game, this could 
        --affect some datastructure in a non-immediate way
        print(frame_number..": The volcano is erupting, BOOM!")
    end,
    start_camera_shake = function()
        print(frame_number..": SHAKY!")
    end,
    play = function(soundname)
        print(frame_number..": Playing: "..soundname)
        return {name = soundname, start = frame_number, duration = 30}
    end
}

And the game loop:

while true do
    scheduler.run(frame_number,game_control)
    frame_number = frame_number+1
end
co1 = coroutine.create(
    function()
        for i = 1, 100 do
            print("co1_"..i)
            coroutine.yield(co2)
        end
    end
)

co2 = coroutine.create(
    function()
        for i = 1, 100 do
            print("co2_"..i)
            coroutine.yield(co1)
        end
    end
)

for i = 1, 100 do
    coroutine.resume(co1)
    coroutine.resume(co2)
end
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!