]> git.lizzy.rs Git - lua_async.git/commitdiff
Add source code
authorElias Fleckenstein <eliasfleckenstein@web.de>
Fri, 6 Aug 2021 17:19:23 +0000 (19:19 +0200)
committerElias Fleckenstein <eliasfleckenstein@web.de>
Fri, 6 Aug 2021 17:19:23 +0000 (19:19 +0200)
async_await.lua [new file with mode: 0644]
immediates.lua [new file with mode: 0644]
init.lua [new file with mode: 0644]
intervals.lua [new file with mode: 0644]
limiting.lua [new file with mode: 0644]
promises.lua [new file with mode: 0644]
timeouts.lua [new file with mode: 0644]
util.lua [new file with mode: 0644]

diff --git a/async_await.lua b/async_await.lua
new file mode 100644 (file)
index 0000000..97d4f7d
--- /dev/null
@@ -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 (file)
index 0000000..6e06372
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..72a0cc7
--- /dev/null
@@ -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 (file)
index 0000000..d5df448
--- /dev/null
@@ -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 (file)
index 0000000..fb86af7
--- /dev/null
@@ -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 (file)
index 0000000..b3b69e8
--- /dev/null
@@ -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 (file)
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