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

后端 未结 3 364
孤街浪徒
孤街浪徒 2020-12-23 20:37

I\'m having trouble understanding this code... I was expecting something similar to threading where I would get an output with random \"nooo\" and \"yaaaay\"s interspersed w

3条回答
  •  死守一世寂寞
    2020-12-23 21:16

    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
    

提交回复
热议问题