-- コルーチンの管理クラス
-- Satofumi KAMIMURA
-- $Id: Scheduler.lua 1815 2010-05-06 17:34:19Z satofumi $

_G.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_

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

   -- テーブルが伸張している可能性があるので
   -- 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
                    )

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

   return id
end


-- 登録した関数を取り除く
function Scheduler:remove(id)
   if not self:isActive(id) then
      return
   end

   local remove_index = self.id_table_[id]
   local from = self.coroutines_[self.size_]
   local to = self.coroutines_[remove_index]
   to.func_ = from.func_
   to.arg_ = from.arg_
   to.id_ = from.id_
   to.wakeup_ticks_ = from.wakeup_ticks_
   to.co_ = from.co_

   -- self.size_ 位置を示す id を探索して変更する
   self.id_table_[from.id_] = remove_index

   from.func_ = nil
   from.arg_ = nil
   from.id_ = nil
   from.wakeup_ticks_ = nil
   from.co_ = nil
   from = nil

   self.size_ = self.size_ - 1

   self.id_table_[id] = nil
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 (ticks() >= 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
               -- 終了したコルーチンの index を記録しておき、最後に削除する
               table.insert(terminated_id, self.coroutines_[i].id_)

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

   -- 終了したコルーチンを末尾のコルーチンと入れ替える
   for key, id in pairs(terminated_id) do
      self:remove(id)
   end

   return self.size_
end


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

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