summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorthe lemons <citrons@mondecitronne.com>2023-03-29 01:47:25 -0500
committerthe lemons <citrons@mondecitronne.com>2023-03-29 01:47:25 -0500
commit2046b5e1e0d5c80994cb5fd5e2f4e739ab142706 (patch)
treea1ec29af7b1d311062fe677909c7c4aad3ff7f29
parentb509373d52646702dd1e98f576ca365870911f04 (diff)
evloop refactor
-rw-r--r--evloop.lua201
-rw-r--r--game/gfx.lua7
-rw-r--r--game/init.lua69
-rw-r--r--main.lua67
4 files changed, 197 insertions, 147 deletions
diff --git a/evloop.lua b/evloop.lua
index 302a29a..637d266 100644
--- a/evloop.lua
+++ b/evloop.lua
@@ -1,133 +1,144 @@
-local M = {}
-M.__index = M
+-- module implementing nestable asynchronous event loop objects. the module
+-- itself is an event loop object, representing the main event loop.
-local queue = {}
+local M
-local function resume_task(task, e)
- if coroutine.status(task.co) == "dead" then
- return false
+local function new(...)
+ local new = setmetatable({}, M)
+ new.event_queue = {}
+ new.tasks = {}
+ for i = 1, select("#", ...) do
+ new:wrap(select(i, ...))
end
+ return new
+end
- if task.filter then
- for _, f in ipairs(task.filter) do
- if f == e[1] then
- goto resume
- end
- end
- if e[1] == "update" and
- task.timeout and love.timer.getTime() >= task.timeout then
- e = {}
- goto resume
- end
- return true
- end
+M = new()
+M.__index = M
- ::resume::
- local ok, ret, timeout = coroutine.resume(task.co, e)
- if not ok then
- io.stderr:write(debug.traceback(task.co, ret)..'\n\n')
- error "error in event loop"
- end
- task.filter = ret
- task.timeout = timeout and love.timer.getTime() + timeout
- return coroutine.status(task.co) ~= "dead"
+M.new = new
+
+-- create a new task in the event loop.
+function M:wrap(fn, ...)
+ table.insert(self.event_queue, {coroutine.create(fn), ...})
end
-local function create_task(fn)
- local task = {}
- task.co = coroutine.create(fn)
- resume_task(task)
- return task
+-- send an event: evloop:queue(event, args...)
+function M:queue(...)
+ assert(type((...)))
+ table.insert(self.event_queue, {...})
end
-function M.poll(timeout, ...)
- local filter = {...}
+local function event_filter(timeout, ...)
+ local filter = {}
+ for i = 1, select("#", ...) do
+ filter[select(i, ...)] = true
+ end
if type(timeout) == "string" then
- table.insert(filter, timeout)
+ filter[timeout] = true
timeout = nil
end
- return unpack(
- coroutine.yield((timeout or #filter > 0) and filter, timeout))
-end
-
-function M.sleep(secs)
- M.poll(secs)
+ return filter, timeout
end
-function M.queue(...)
- table.insert(queue, {...})
+-- 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
-function M.await_any(...)
- local tasks = {...}
- for i, t in ipairs(tasks) do
- tasks[i] = create_task(t)
- end
- while true do
- local e = coroutine.yield()
- for _, t in ipairs(tasks) do
- if not resume_task(t, e) then return 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)
- M.paused = true
local t = 0
+ M.queue "debug.pause"
while t < secs do
- local _, dt = M.poll "debug.update"
+ local _, dt = M.poll "debug.timer"
t = t + dt
end
- M.paused = false
+ M.queue "debug.unpause"
end
-local function poll_love()
- love.event.pump()
- local es = love.event.poll()
- while true do
- local e = {es()}
- if not e[1] then break end
- if love.handlers[e[1]] then
- love.handlers[e[1]](unpack(e, 2))
- end
- table.insert(queue, e)
+local function resume_task(co, ...)
+ if coroutine.status(co) == "dead" then
+ return false
end
-end
-function M.mainloop(start)
- local main = create_task(function()
- start()
- 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 function()
- if not M.paused then poll_love() end
+ return coroutine.status(co) ~= "dead"
+end
- local q = queue
- queue = {}
- for i, e in ipairs(q) do
- if e[1] == "quit" then
- if not love.quit or not love.quit() then
- return e[2] or 0
+-- 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
+ if resume_task(e[1], unpack(e, 2)) then
+ self.tasks[e[1]] = true
+ 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
- if not resume_task(main, e) then return 0 end
end
-
- local dt = love.timer.step()
- if not resume_task(
- main, {M.paused and "debug.update" or "update", dt}) then
- return 0
+ 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
-
- ::graphics::
- if love.graphics and love.graphics.isActive() then
- love.graphics.clear(love.graphics.getBackgroundColor())
- require "viewport".origin()
- if not resume_task(main, {"draw", dt}) then return 0 end
- love.graphics.present()
- end
-
- love.timer.sleep(0.001)
end
end
diff --git a/game/gfx.lua b/game/gfx.lua
index a5a05a8..93bb3b0 100644
--- a/game/gfx.lua
+++ b/game/gfx.lua
@@ -164,13 +164,10 @@ function M:draw(dt)
end
end
-function M:loop()
- local function loop()
- local _, dt = evloop.poll "draw"
+function M:run()
+ for _, dt in evloop.events "draw" do
self:draw(dt)
- return loop()
end
- return loop
end
return M
diff --git a/game/init.lua b/game/init.lua
index 085b0e8..a7fd64f 100644
--- a/game/init.lua
+++ b/game/init.lua
@@ -41,16 +41,15 @@ function M.new(params)
end
function M:input_loop()
- local function loop()
+ for e, key in evloop.events("keypressed", "keyreleased") do
-- TODO: interface with a remappable input system (it should generate
-- its own events)
- local e, key = evloop.poll("keypressed", "keyreleased")
if not self.piece then
- return loop()
+ goto continue
end
- local moved
if e == "keypressed" then
+ local moved
if key == "left" then
moved = self.piece:move(0, -1)
elseif key == "right" then
@@ -80,24 +79,23 @@ function M:input_loop()
local tmp = self.hold
self.hold = self.piece.poly
self.piece = tmp:drop(self.field)
- evloop.queue "game.lock_cancel"
+ evloop:queue "game.lock_cancel"
end
self.can_hold = false
::bypass::
end
- end
- if moved then
- if self.infinity then
- evloop.queue "game.lock_cancel"
- elseif not self.piece:can_move(-1, 0) then
- evloop.queue "game.lock"
+ if moved then
+ if self.infinity then
+ evloop:queue "game.lock_cancel"
+ elseif not self.piece:can_move(-1, 0) then
+ evloop:queue "game.lock"
+ end
end
end
- return loop()
+ ::continue::
end
- return loop
end
function M:on_rotated()
@@ -114,7 +112,7 @@ function M:place_piece()
if not self.piece:can_move(-1, 0) then
self.piece:place()
self.stats.pieces = self.stats.pieces + 1
- evloop.queue "game.lock_cancel"
+ evloop:queue "game.lock_cancel"
local cleared = self.field:remove_cleared()
if cleared > 0 then
self.combo = self.combo + 1
@@ -123,11 +121,11 @@ function M:place_piece()
local sound = ({"tspinsingle","tspindouble","tspintriple"})[cleared]
sfx.play(sound)
end
- evloop.queue "game.line_clear"
+ evloop:queue "game.line_clear"
else
self.combo = -1
end
- evloop.queue "game.piece_placed"
+ evloop:queue "game.piece_placed"
self:next_piece()
return true
else
@@ -142,43 +140,34 @@ function M:next_piece()
end
function M:lock_loop()
- local function loop()
- evloop.poll "game.lock"
+ for _ in evloop.events "game.lock" do
local e = evloop.poll(self.lock_delay, "game.lock_cancel")
- if e then
- return loop()
- else
+ if not e then
self:place_piece()
end
- return loop()
end
- return loop
end
function M:gravity_loop()
- local function loop()
- evloop.sleep(self.gravity_delay)
+ for _ in evloop.events(self.gravity_delay) do
if self.piece and not self.piece:move(-1, 0) then
- evloop.queue "game.lock"
+ evloop:queue "game.lock"
end
- if self.piece then
- return loop()
+ if not self.piece then
+ self.loop:quit()
end
end
- return loop
end
-function M:loop()
- local function loop()
- self:next_piece()
- evloop.await_any(
- self:input_loop(),
- self:gravity_loop(),
- self:lock_loop(),
- self.gfx:loop()
- )
- end
- return loop
+function M:run()
+ self:next_piece()
+ self.loop = evloop.new(
+ function() self:input_loop() end,
+ function() self:gravity_loop() end,
+ function() self:lock_loop() end,
+ function() self.gfx:run() end
+ )
+ return self.loop:run()
end
return M
diff --git a/main.lua b/main.lua
index 586ccdf..2957bda 100644
--- a/main.lua
+++ b/main.lua
@@ -1,16 +1,69 @@
local evloop = require "evloop"
local game = require "game"
-local game_obj
+local function main()
+ evloop.poll "load"
+ local game_obj = game.new {}
+ evloop.poll "loaded"
+ game_obj:run()
+ evloop:quit()
+end
+
+local function update()
+ evloop:queue("load", love.arg.parseGameArguments(arg), arg)
+ evloop:queue "loaded"
+ evloop.poll "loaded"
+ local paused = false
+ for e in evloop.events("update", "debug.pause", "debug.unpause") do
+ if e == "debug.pause" then
+ paused = true
+ elseif e == "debug.unpause" then
+ paused = false
+ end
+ if not paused then
+ love.event.pump()
+ local es = love.event.poll()
+ while true do
+ local e = {es()}
+ if not e[1] then break end
+ if love.handlers[e[1]] then
+ love.handlers[e[1]](unpack(e, 2))
+ end
+ evloop:queue(unpack(e))
+ end
+ end
+
+ local dt = love.timer.step()
+ evloop:queue(not paused and "timer" or "debug.timer", dt)
-function love.load()
- game_obj = game.new {}
+ if love.graphics and love.graphics.isActive() then
+ love.graphics.clear(love.graphics.getBackgroundColor())
+ require "viewport".origin()
+ evloop:queue "draw"
+ evloop:queue "draw_complete"
+ evloop.poll "draw_complete"
+ love.graphics.present()
+ end
+
+ love.timer.sleep(0.001)
+ end
end
+evloop:wrap(update)
+evloop:wrap(main)
+
+local run = coroutine.wrap(function() evloop:run() end)
+
function love.run()
- if love.load then love.load(love.arg.parseGameArguments(arg), arg) end
if love.timer then love.timer.step() end
- return evloop.mainloop(function()
- game_obj:loop()()
- end)
+ return function()
+ local val = run({"update"})
+ if val == true then
+ return
+ elseif val == nil then
+ return 0
+ else
+ return val
+ end
+ end
end