Подтвердить что ты не робот

Что такое Lua coroutines? Почему этот код не работает, как я ожидаю?

У меня возникли проблемы с пониманием этого кода... Я ожидал чего-то похожего на потокование, где я получал бы выход со случайными "nooo" и "yaaaay", чередующимися друг с другом, поскольку они оба делают асинхронную печать, но скорее, я обнаружил, что основной поток, кажется, блокируется при первом вызове coroutine.resume() и, таким образом, предотвращает запуск следующего, пока первый не даст.

Если это предназначенные операционные сопрограммы, для чего они полезны и как я могу достичь цели, на которую я надеялся? Должен ли я реализовать собственный планировщик для этих сопрограмм для работы асинхронно?, потому что это кажется беспорядочным, и я могу также использовать функции!

co1 = coroutine.create(function ()
        local i = 1
        while i < 200 do
                print("nooo")
                i = i + 1
        end
        coroutine.yield()
end)

co2 = coroutine.create(function ()
        local i = 1
        while i < 200 do
                print("yaaaay")
                i = i + 1
        end
        coroutine.yield()
end)

coroutine.resume(co1)
coroutine.resume(co2)
4b9b3361

Ответ 1

Corouts - это не потоки.

Coroutines - это потоки, которые никогда не планируются. Так что да, вы правы, что вам придется написать собственный планировщик, чтобы одновременно выполнять сопрограммы.

Однако вам не хватает более крупного изображения, когда дело доходит до сопрограмм. Ознакомьтесь с википедии списка использования coroutine. Вот один конкретный пример, который может помочь вам в правильном направлении.

-- 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

Вышеупомянутый script можно записать для итеративного запуска с помощью оператора switch и некоторых умных переменных состояния. Но это гораздо яснее, когда написано как сопрограмма. Вышеупомянутый script может быть потоком, но вам действительно нужно посвятить поток ядра этому простому коду. Уровень занятой игры может иметь 100 из этих сопрограмм, работающих без влияния на производительность. Однако, если каждый из них был потоком, вы могли бы уйти с 20-30 до того, как производительность начнет страдать.

Предполагается, что сопрограмма предназначена для того, чтобы разрешить мне писать код, который хранит состояние в стеке, чтобы я мог некоторое время его остановить (функции wait) и запустить его снова, где я остановился.

Ответ 2

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

Ответ 3

Поскольку было высказано несколько комментариев о том, как реализовать функцию wait, которая сделает пример deft_code, я решил написать возможную реализацию. Общая идея заключается в том, что у нас есть планировщик со списком сопрограмм, и планировщик решает, когда вернуть управление сопрограммам после того, как они откажутся от контроля со своими вызовами wait. Это желательно, потому что он делает асинхронный код понятным и понятным.

Это всего лишь одно возможное использование сопрограмм, это более общий инструмент абстракции, который может использоваться для различных целей (например, для написания итераторов и генераторов, для записи объектов с обработкой объектами с потоком данных (например, несколько этапов в синтаксическом анализаторе), выполнение исключений и продолжений и т.д.).

Сначала: определение планировщика:

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

Теперь, инициализируя мир:

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))

Интерфейс (фиктивный) к игре:

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
}

И игровой цикл:

while true do
    scheduler.run(frame_number,game_control)
    frame_number = frame_number+1
end