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
|
-- module implementing nestable asynchronous event loop objects. the module
-- itself is an event loop object, representing the main event loop.
local M
local function new(...)
local new = setmetatable({}, M)
new.event_queue = {}
new.tasks = {}
new.named_tasks = {}
for i = 1, select("#", ...) do
new:wrap(select(i, ...))
end
return new
end
M = new()
M.__index = M
M.new = new
-- create a new task in the event loop.
function M:wrap(fn, ...)
local name = false
if type(fn) == "table" then
name = fn.name
fn = fn[1]
end
table.insert(self.event_queue, {coroutine.create(fn), name, ...})
end
-- send an event: evloop:queue(event, args...)
function M:queue(...)
assert(type((...)))
table.insert(self.event_queue, {...})
end
-- destroy a named task
function M:kill(name)
if self.named_tasks[name] then
self.tasks[self.named_tasks[name]] = nil
self.named_tasks[name] = nil
end
end
local function event_filter(timeout, ...)
local filter = {}
for i = 1, select("#", ...) do
filter[select(i, ...)] = true
end
if type(timeout) == "string" then
filter[timeout] = true
timeout = nil
end
return filter, timeout
end
-- poll for an event: evloop.poll([timeout,] [events...])
-- returns: event, args...
function M.poll(...)
local filter, timeout = event_filter(...)
local e
local t = 0
repeat
e = coroutine.yield(true)
if e[1] == "timer" then
t = t + e[2]
end
if timeout and t >= timeout then
return
end
until filter[e[1]] or (not timeout and not next(filter))
return unpack(e)
end
-- like evloop.poll but returns an iterator
function M.events(...)
local filter, timeout = event_filter(...)
local t = 0
local function iter()
if timeout and t >= timeout then
t = t - timeout
return false
end
local e = coroutine.yield(true)
if e[1] == "timer" then
t = t + e[2]
end
if (timeout or next(filter)) and not filter[e[1]] then
return iter()
else
return unpack(e)
end
end
return iter
end
function M.sleep(secs)
M.poll(secs)
end
function M:quit(...)
self:queue("quit", ...)
end
function M.debug_sleep(secs)
local t = 0
M.queue "debug.pause"
while t < secs do
local _, dt = M.poll "debug.timer"
t = t + dt
end
M.queue "debug.unpause"
end
local function resume_task(co, ...)
if coroutine.status(co) == "dead" then
return false
end
local ok, ret = coroutine.resume(co, ...)
if not ok then
-- TODO: make this better somehow
io.stderr:write(debug.traceback(co, ret)..'\n\n')
error "error in event loop"
end
return coroutine.status(co) ~= "dead"
end
-- run each task in the event loop at once
function M:run()
while true do
::send_events::
local q = self.event_queue
self.event_queue = {}
for _, e in ipairs(q) do
if type(e[1]) == "thread" then
-- start a new task
local name = e[2]
if resume_task(e[1], unpack(e, 3)) then
self.tasks[e[1]] = true
if name then self.named_tasks[name] = e[1] end
end
elseif e[1] == "quit" then
return unpack(e, 2)
else
for task in pairs(self.tasks) do
self.tasks[task] = resume_task(task, e) or nil
end
end
end
if #self.event_queue == 0 then
if not next(self.tasks) then return end
assert(coroutine.running(), "event queue depleted!")
table.insert(self.event_queue, coroutine.yield(true))
end
end
end
return M
|