web-dev-qa-db-ja.com

Luaコルーチンとは何ですか?このコードが期待どおりに機能しないのはなぜですか?

このコードを理解するのに問題があります...ランダムに "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)
33
kellpossible

コルーチンはスレッドではありません。

コルーチンは、アクティブにスケジュールされないスレッドのようなものです。ですから、両方のコルーチンを同時に実行するために独自のスケジューラーを作成する必要があるということは、まあまあです。

ただし、コルーチンに関しては、全体像が欠けています。ウィキペディアの コルーチン使用のリスト を確認してください。ここにあなたを正しい方向に導くかもしれない1つの具体的な例があります。

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

上記のスクリプトは、switchステートメントといくつかの巧妙な状態変数を使用して繰り返し実行するように作成できます。しかし、コルーチンとして書くと、はるかに明確になります。上記のスクリプトはスレッドでもかまいませんが、カーネルスレッドをこの単純なコード専用にする必要がありますか?忙しいゲームレベルでは、パフォーマンスに影響を与えることなく、これらのコルーチンの数百を実行できます。ただし、これらのそれぞれがスレッドである場合、パフォーマンスが低下し始める前に可能性があります20-30です。

コルーチンは、スタックに状態を格納するコードを記述して、しばらく実行を停止し(wait関数)、中断したところから再び開始できるようにするためのものです。

52
deft_code

wait関数を実装してdeft_codeのサンプル作品、私は可能な実装を書くことにしました。一般的な考え方は、コルーチンのリストを備えたスケジューラがあり、スケジューラは、wait呼び出しで制御を放棄した後、いつコルーチンに制御を戻すかを決定するということです。これは非同期コードを読みやすく推論しやすくするので望ましいです。

これは、コルーチンの1つの可能な使用法であり、イテレータやジェネレーターの書き込み、ステートフルストリーム処理オブジェクトの書き込み(パーサーの複数のステージなど)、例外の実装など、さまざまな目的に使用できるより一般的な抽象化ツールです。および継続など)。

まず、スケジューラの定義:

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
14
Mankarse
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
10
dejf