コルーチンの利用

Lua では並列動作を行う仕組みとして、スレッドの代わりにコルーチンと呼ばれる仕組みを用意しています。コルーチンとは、関数呼び出しを途中で中断し、再開させられる仕組みです。

例えば、キャラクターに「右に進んで、1秒待って、左に進んで...」という行動をとらせたい場合、コルーチンを用いた実装は以下のように記述できます。

-- !!! このクラスは以下で紹介する
local scheduler = Schedulfe:new()

function character_move()
   move_right()

   -- !!! 以下の Scheduler:wait() でこの関数を一時中断する
   -- !!! Scheduler クラスの実装で、1000 msec 後に処理が再開されるようになっている
   Scheduler:wait(1000)

   move_left()
end

while true do
   -- !!! コルーチンの中断、再開の処理は、このメソッドを呼び出して管理する
   scheduler:execute()
   delay(100)
end 

便利ですね。
以下に、スケジューラのソースコードと、スケジューラの動作サンプルを示します。ここで示すサンプルは bindCpp.cpp を用いて実行します。

コルーチンの管理クラス
Scheduler.lua

-- コルーチンの管理クラス
-- Satofumi KAMIMURA
-- $Id: Scheduler.lua 772 2009-05-05 06:57:57Z satofumi $

Scheduler = {}


function Scheduler:new()

   local members = {
      coroutines_ = {},
      size_ = 0,
      serial_id_ = 1,
      id_table_ = {}
   }

   Scheduler.__index = Scheduler
   setmetatable(members, Scheduler)

   return members
end


-- 関数をコルーチンに登録
function Scheduler:insert(func, arg)

   local index = self.size_ + 1
   local id = self.serial_id_

   if self.size_ >= #self.coroutines_ then
      -- コルーチンの新規作成
      self.coroutines_[index] = {
         func_ = func,
         arg_ = arg,
         co_ = nil,
         id_ = id,
         wakeup_ticks_ = false
      }

      -- テーブルが伸張している可能性があるので
      -- self_function は、ここで宣言する
      local self_function = self.coroutines_[index]
      self_function.co_ =
         coroutine.create(function()
                             while true do
                                self_function.func_(self_function.arg_)
                                coroutine.yield(nil)
                             end
                          end
                       )
   else
      -- コルーチンの再利用
      local self_function = self.coroutines_[index]
      self_function.func_ = func
      self_function.arg_ = arg
      self_function.id_ = id
      self_function.wakeup_ticks_ = false
   end

   self.id_table_[id] = index
   self.serial_id_ = self.serial_id_ + 1
   self.size_ = index
   return id
end


-- コルーチン実行を待機させる
function Scheduler:wait(msec)

   coroutine.yield(msec)
end


-- コルーチン実行を中断して処理を戻す
function Scheduler:yield()

   coroutine.yield(true)
end


-- スケジューラの実行
function Scheduler:execute()

   local terminated_id = {}

   for i = 1, self.size_ do
      local self_function = self.coroutines_[i]
      if self_function.id_ >= 0 then

         -- 次の実行時間になっていなければ、処理しない
         local wakeup_ticks = self_function.wakeup_ticks_
         if (wakeup_ticks == false) or (getTicks() >= wakeup_ticks) then
            self_function.wakeup_ticks_ = false

            -- 登録されている関数の実行
            local ret, alive = coroutine.resume(self_function.co_,
                                                self_function.arg_)
            if ret == false then
               -- エラーメッセージの出力
               -- !!! ファイル、行番号なども表示させる
               print(alive)
            end

            if alive == nil then
               self.id_table_[self_function.id_] = nil
               self_function.id_ = -1

               -- 終了したコルーチンの index を記録しておき、最後に削除する
               table.insert(terminated_id, i)

            elseif type(alive) == 'number' then
               -- 次の起床時間を登録
               self_function.wakeup_ticks_ = getTicks() + alive
            end
         end
      end
   end

   -- 終了したコルーチンを末尾のコルーチンと入れ替え
   for key, value in pairs(terminated_id) do
      self.coroutines_[value], self.coroutines_[self.size_] =
         self.coroutines_[self.size_], self.coroutines_[value]
      self.size_ = self.size_ - 1
   end

   return self.size_
end


-- 指定した ID のコルーチンが有効かを返す
function Scheduler:isActive(id)

   if self.id_table_[id] == nil then
      return false
   else
      return true
   end
end


複数のコルーチンを実行するサンプル
scheduler_sample.lua

-- scheduler.lua の動作サンプル
-- Satofumi KAMIMURA
-- $Id: scheduler_sample.lua 772 2009-05-05 06:57:57Z satofumi $

require("Scheduler")


-- 1 から 4 まで表示する
function print4()

   for i = 1, 4 do
      print("<" .. '0' + i .. ">")
      Scheduler:yield()
   end
   Scheduler:yield()
end


-- 1 から 2 まで表示する
function print2()

   for i = 1, 2 do
      print("[" .. '0' + i .. "]")
      Scheduler:yield()
   end
end


-- 2つのコルーチンを登録する
local scheduler = Scheduler:new()

scheduler:insert(print4)
scheduler:insert(print2)


local index = 1
for i = 1, 6 do
   print("--- " .. index .. " [times] ---")
   index = index + 1
   scheduler:execute()
end


-- コルーチン再利用の動作確認
scheduler:insert(print2)

for i = 1, 3 do
   print("--- " .. index .. " [times] ---")
   index = index + 1
   scheduler:execute()
end

実行結果

% ./bindCpp scheduler_sample.lua
--- 1 [times] ---
<1>
[1]
--- 2 [times] ---
<2>
[2]
--- 3 [times] ---
<3>
--- 4 [times] ---
<4>
--- 5 [times] ---
--- 6 [times] ---
--- 7 [times] ---
[1]
--- 8 [times] ---
[2]
--- 9 [times] ---
end 


指定時間だけ待機を行うサンプル
wait_sample.lua

-- Scheduler:wait() の動作サンプル
-- Satofumi KAMIMURA
-- $Id: wait_sample.lua 772 2009-05-05 06:57:57Z satofumi $

require("Scheduler")


function print_message()

   -- メッセージを1秒毎に表示する
   print("start")

   print("0 [sec]")
   Scheduler:wait(1000)

   print("1 [sec]")
   Scheduler:wait(1000)

   print("2 [sec]")
   Scheduler:wait(1000)

   print("3 [sec]")
end


local scheduler = Scheduler:new()
local id = scheduler:insert(print_message)

while scheduler:isActive(id) do
   scheduler:execute()
   delay(100)
end

print("end")

実行結果

% ./bindCpp wait_sample.lua
start
0 [sec]
1 [sec]
2 [sec]
3 [sec]
end 

簡単ですね。
より本格的なサンプルについては Lua スクリプトを用いた ハエ叩き をご覧下さい。


コメントページ


参考



Generated on Mon May 18 11:11:10 2009 by  doxygen 1.5.7.1