--- /dev/null
+lua_async.async_await = {}
+
+function lua_async.resume(co)
+ local status, err = coroutine.resume(co)
+
+ if coroutine.status(co) == "dead" or err then
+ lua_async.limiting.unset_limit(co)
+ end
+
+ if not status then
+ error("Error (in async function): " .. err)
+ end
+end
+
+function await(promise)
+ local co = assert(coroutine.running(), "await called outside of an async function")
+
+ if promise.state == "pending" then
+ promise:then_(function()
+ lua_async.resume(co)
+ end)
+
+ coroutine.yield()
+ end
+
+ return unpack(promise.values)
+end
+
+function async(func)
+ return function(...)
+ local promise = Promise()
+ promise.__on_resolve = func
+
+ local args = {...}
+
+ lua_async.resume(coroutine.create(function()
+ promise:resolve(unpack(args))
+ end))
+
+ return promise
+ end
+end
+
--- /dev/null
+lua_async.immediates = {
+ pool = {},
+ last_id = 0,
+}
+
+function setImmediate(callback, ...)
+ local id = lua_async.immediates.last_id + 1
+ lua_async.immediates.last_id = id
+ lua_async.immediates.pool[id] = {
+ callback = callback,
+ args = {...},
+ }
+ return id
+end
+
+function clearImmediate(id)
+ lua_async.immediates.pool[id] = nil
+end
+
+function lua_async.immediates.step(dtime)
+ for id, immediate in pairs(lua_async.immediates.pool) do
+ immediate.callback(unpack(immediate.args))
+ clearImmediate(id)
+ end
+end
+
--- /dev/null
+lua_async = {}
+
+function lua_async.step(dtime)
+ -- timers phase
+ lua_async.timeouts.step(dtime)
+ lua_async.intervals.step(dtime)
+
+ -- pending callbacks phase is done by minetest
+
+ -- idle & prepare phase are obsolete
+
+ -- poll phase is obsolete
+
+ -- check phase
+ lua_async.immediates.step(dtime)
+
+ -- close phase is obsolete
+end
+
+return function(path)
+ for _, f in ipairs {
+ "timeouts",
+ "intervals",
+ "immediates",
+ "promises",
+ "async_await",
+ "util",
+ "limiting",
+ } do
+ dofile(path .. f .. ".lua")
+ end
+end
--- /dev/null
+lua_async.intervals = {
+ pool = {},
+ last_id = 0,
+}
+
+function setInterval(callback, ms, ...)
+ local id = lua_async.intervals.last_id + 1
+ lua_async.intervals.last_id = id
+ local step_time = (ms or 0) / 1000
+ lua_async.intervals.pool[id] = {
+ time_left = step_time,
+ step_time = step_time,
+ callback = callback,
+ args = {...},
+ }
+ return id
+end
+
+function clearInterval(id)
+ lua_async.intervals.pool[id] = nil
+end
+
+function lua_async.intervals.step(dtime)
+ for id, interval in pairs(lua_async.intervals.pool) do
+ interval.time_left = timeout.time_left - dtime
+
+ if interval.time_left <= 0 then
+ interval.callback(unpack(interval.args))
+ interval.time_left = interval.step_time
+ end
+ end
+end
+
--- /dev/null
+lua_async.limiting = {
+ pool = {},
+}
+
+function lua_async.limiting.unset_limit(co)
+ lua_async.limiting.pool[co] = nil
+end
+
+function lua_async.set_limit(ms)
+ local co = assert(coroutine.running(), "set_limit called outside of an async function")
+
+ local limit = ms / 1000
+
+ lua_async.limiting.pool[co] = {
+ limit = limit,
+ next_yield = os.clock() + limit,
+ }
+end
+
+function lua_async.unset_limit()
+ local co = assert(coroutine.running(), "unset_limit called outside of an async function")
+ lua_async.limiting.unset_limit(co)
+end
+
+function lua_async.check_limit()
+ local co = assert(coroutine.running(), "check_limit called outside of an async function")
+ local limit = lua_async.limiting.pool[co]
+
+ if limit and os.clock() >= limit.next_yield then
+ lua_async.yield()
+ limit.next_yield = os.clock() + limit.limit
+ return true
+ end
+
+ return false
+end
+
--- /dev/null
+local PromisePrototype = {}
+
+function PromisePrototype:__run_handler(func, ...)
+ local values = {pcall(func, ...)}
+
+ if table.remove(values, 1) then
+ self:__resolve_raw(unpack(values))
+ else
+ self:__reject_raw(values[1])
+ end
+end
+
+function PromisePrototype:__add_child(promise)
+ if self.state == "resolved" then
+ promise:__resolve(unpack(self.values))
+ elseif self.state == "rejected" then
+ promise:__reject(self.reason)
+ else
+ table.insert(self.__children, promise)
+ end
+end
+
+function PromisePrototype:__resolve_raw(...)
+ self.state = "resolved"
+ self.values = {...}
+ self.reason = nil
+
+ for _, child in ipairs(self.__children) do
+ child:resolve(...)
+ end
+end
+
+function PromisePrototype:__reject_raw(reason)
+ self.state = "rejected"
+ self.values = nil
+ self.reason = reason
+
+ local any_child = false
+
+ for _, child in ipairs(self.__children) do
+ child:reject(reason)
+ end
+
+ assert(any_child, "Uncaught (in promise): " .. reason)
+end
+
+function PromisePrototype:resolve(...)
+ assert(self.state == "pending")
+
+ if self.__on_resolve then
+ self:__run_handler(self.__on_resolve, ...)
+ else
+ self:__resolve_raw(...)
+ end
+end
+
+function PromisePrototype:reject(reason)
+ assert(self.state == "pending")
+
+ if self.__on_reject then
+ self:__run_handler(self.__on_reject, reason)
+ else
+ self:__reject_raw(reason)
+ end
+end
+
+function PromisePrototype:then_(func)
+ local promise = Promise()
+ promise.__on_resolve = func
+
+ self:__add_child(promise)
+
+ return promise
+end
+
+function PromisePrototype:catch(func)
+ local promise = Promise(function() end)
+ promise.__on_reject = func
+
+ self:__add_child(promise)
+
+ return promise
+end
+
+Promise = setmetatable({}, {
+ __call = function(_, resolver)
+ local promise = {
+ state = "pending",
+ __children = {},
+ }
+
+ setmetatable(promise, {__index = PromisePrototype})
+
+ if resolver then
+ resolver(
+ function(...)
+ promise:resolve(...)
+ end,
+ function(...)
+ promise:reject(...)
+ end
+ )
+ end
+
+ return promise
+ end
+})
+
+function Promise.resolve(...)
+ local args = {...}
+ return Promise(function(resolve)
+ resolve(unpack(args))
+ end)
+end
--- /dev/null
+lua_async.timeouts = {
+ pool = {},
+ last_id = 0,
+}
+
+function setTimeout(callback, ms, ...)
+ local id = lua_async.timeouts.last_id + 1
+ lua_async.timeouts.last_id = id
+ lua_async.timeouts.pool[id] = {
+ time_left = (ms or 0) / 1000,
+ callback = callback,
+ args = {...},
+ }
+ return id
+end
+
+function clearTimeout(id)
+ lua_async.timeouts.pool[id] = nil
+end
+
+function lua_async.timeouts.step(dtime)
+ for id, timeout in pairs(lua_async.timeouts.pool) do
+ timeout.time_left = timeout.time_left - dtime
+
+ if timeout.time_left <= 0 then
+ timeout.callback(unpack(timeout.args))
+ clearTimeout(id)
+ end
+ end
+end
--- /dev/null
+function lua_async.yield()
+ local co = assert(coroutine.running(), "yield called outside of an async function")
+
+ setTimeout(lua_async.resume, 0, co)
+
+ coroutine.yield()
+end
+
+function lua_async.sleep(ms)
+ await(Promise(function(resolve)
+ setTimeout(resolve, ms)
+ end))
+end