From a2770298f040307f8dd59c7a88d7e40d37faec14 Mon Sep 17 00:00:00 2001 From: Elias Fleckenstein Date: Fri, 6 Aug 2021 19:19:23 +0200 Subject: [PATCH] Add source code --- async_await.lua | 43 ++++++++++++++++++ immediates.lua | 26 +++++++++++ init.lua | 32 ++++++++++++++ intervals.lua | 33 ++++++++++++++ limiting.lua | 37 ++++++++++++++++ promises.lua | 114 ++++++++++++++++++++++++++++++++++++++++++++++++ timeouts.lua | 30 +++++++++++++ util.lua | 13 ++++++ 8 files changed, 328 insertions(+) create mode 100644 async_await.lua create mode 100644 immediates.lua create mode 100644 init.lua create mode 100644 intervals.lua create mode 100644 limiting.lua create mode 100644 promises.lua create mode 100644 timeouts.lua create mode 100644 util.lua diff --git a/async_await.lua b/async_await.lua new file mode 100644 index 0000000..97d4f7d --- /dev/null +++ b/async_await.lua @@ -0,0 +1,43 @@ +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 + diff --git a/immediates.lua b/immediates.lua new file mode 100644 index 0000000..6e06372 --- /dev/null +++ b/immediates.lua @@ -0,0 +1,26 @@ +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 + diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..749bee6 --- /dev/null +++ b/init.lua @@ -0,0 +1,32 @@ +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 diff --git a/intervals.lua b/intervals.lua new file mode 100644 index 0000000..72a0cc7 --- /dev/null +++ b/intervals.lua @@ -0,0 +1,33 @@ +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 + diff --git a/limiting.lua b/limiting.lua new file mode 100644 index 0000000..d5df448 --- /dev/null +++ b/limiting.lua @@ -0,0 +1,37 @@ +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 + diff --git a/promises.lua b/promises.lua new file mode 100644 index 0000000..fb86af7 --- /dev/null +++ b/promises.lua @@ -0,0 +1,114 @@ +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 diff --git a/timeouts.lua b/timeouts.lua new file mode 100644 index 0000000..b3b69e8 --- /dev/null +++ b/timeouts.lua @@ -0,0 +1,30 @@ +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 diff --git a/util.lua b/util.lua new file mode 100644 index 0000000..c4067f1 --- /dev/null +++ b/util.lua @@ -0,0 +1,13 @@ +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 -- 2.44.0