summaryrefslogtreecommitdiff
path: root/Data/BuiltIn/Libraries/coil/coil.lua
blob: fbd20c9af00bc666e5175cd9793562546a60287a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
--
-- coil
--
-- Copyright (c) 2014 rxi
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
--

local coil = { _version = "0.1.0" }
coil.__index = coil
coil.tasks = {}


local unpack = unpack or table.unpack

local _assert = function(cond, msg, lvl) 
  if cond then return cond, msg, lvl end
  error(msg, lvl + 1)
end

local callback_mt = { 
  __call = function(t, ...)
    t.args = {...}
    t.ready = true
  end}


local task = {} 
task.__index = task


function task.new(fn, parent)
  local self = setmetatable({}, task)
  self.routine = coroutine.wrap(fn)
  self.parent = parent
  self.pausecount = 0
  return self
end


function task:pause()
  self.pausecount = self.pausecount + 1
end


function task:resume()
  _assert(self.pausecount > 0, "unbalanced resume()", 2)
  self.pausecount = self.pausecount - 1
end


function task:stop()
  coil.remove(self.parent, self)
end



function coil:update(dt)
  if #self == 0 then return end
  coil.deltatime = dt
  for i = #self, 1, -1 do 
    local task = self[i]
    if task.wait then
      -- Handle wait
      if type(task.wait) == "number" then
        -- Handle numerical wait
        task.wait = task.wait - dt
        if task.wait <= 0 then
          task.waitrem = task.wait
          task.wait = nil
        end
      elseif type(task.wait) == "table" then
        -- Handle callback object
        if task.wait.ready then
          task.wait = nil
        end
      end
    end
    if not task.wait and task.pausecount == 0 then
      -- Run task
      coil.current = task
      if not task.routine() then
        coil.remove(self, i)
      end
    end
  end
  coil.current = nil
end


function coil:add(fn)
  local t = task.new(fn, self)
  table.insert(self, t)
  return t
end


function coil:remove(t)
  if type(t) == "number" then
    self[t] = self[#self]
    table.remove(self)
    return
  end
  for i, task in ipairs(self) do
    if task == t then
      return coil.remove(self, i)
    end
  end
end


function coil.wait(x, y)
  -- Discard first argument if its a coil group
  x = getmetatable(x) == coil and y or x
  local c = coil.current
  _assert(c, "wait() called from outside a coroutine", 2)
  if type(x) == "number" then
    -- Handle numerical wait
    c.wait = (c.waitrem or 0) + x
    if c.wait <= 0 then
      c.waitrem = c.wait
      return
    else
      c.waitrem = nil
    end
  else
    -- Handle next-frame / callback wait
    _assert(x == nil or getmetatable(x) == callback_mt,
            "wait() expected number, callback object or nothing as argument",
            2)
    c.waitrem = nil
    c.wait = x
  end
  coroutine.yield(true)
  -- Return args if wait was a callback object
  if type(x) == "table" then
    return unpack(x.args)
  end
  -- Return delta time if wait had no args
  if x == nil then
    return coil.deltatime
  end
end


function coil.callback()
  return setmetatable({ ready = false }, callback_mt)
end


function coil.group()
  return setmetatable({}, coil)
end



local bound = {
  update  = function(...) return coil.update(coil.tasks, ...) end,
  add     = function(...) return coil.add(coil.tasks, ...) end,
  remove  = function(...) return coil.remove(coil.tasks, ...) end,
}
setmetatable(bound, coil)

return bound