You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

core.lua 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. --
  2. -- Minetest asyncio: Core
  3. --
  4. local coroutines = {}
  5. local iterators = {}
  6. -- Make coroutines and iterators "weak tables".
  7. setmetatable(coroutines, {__mode = 'k'})
  8. setmetatable(iterators, {__mode = 'kv'})
  9. -- Resume a coroutine
  10. local last_error
  11. local function resume(co, ...)
  12. local good, msg = coroutine.resume(co, ...)
  13. local status = coroutine.status(co)
  14. if status == 'dead' then coroutines[co], iterators[co] = nil, nil end
  15. if not good then
  16. assert(status == 'dead')
  17. if last_error then
  18. last_error.msg = msg
  19. minetest.after(0, last_error.cleanup)
  20. end
  21. minetest.log('error', 'Error in coroutine: ' .. tostring(msg))
  22. for _, line in ipairs(debug.traceback(co):split('\n')) do
  23. minetest.log('error', line)
  24. end
  25. elseif iterators[co] then
  26. if msg == nil then return false end
  27. return msg
  28. end
  29. end
  30. -- Run coroutines directly
  31. local function run(func, ...)
  32. local thread = coroutine.running()
  33. if coroutines[thread] then
  34. return func(...)
  35. end
  36. local co = coroutine.create(func)
  37. coroutines[co] = true
  38. resume(co, ...)
  39. end
  40. function asyncio.run(func, ...)
  41. if type(func) ~= 'function' then
  42. error('Invalid function passed to asyncio.run.', 2)
  43. end
  44. return run(func, ...)
  45. end
  46. -- Wrap a function
  47. function asyncio.coroutine(func)
  48. if type(func) ~= 'function' then
  49. error('Invalid function passed to asyncio.coroutine.', 2)
  50. end
  51. return function(...)
  52. return run(func, ...)
  53. end
  54. end
  55. -- Run a coroutine in a separate "thread"
  56. function asyncio.run_async(func, ...)
  57. local co = coroutine.create(func)
  58. coroutines[co] = true
  59. resume(co, ...)
  60. end
  61. -- Make sure functions are being run inside coroutines.
  62. local function get_coroutine()
  63. local co = coroutine.running()
  64. if not coroutines[co] then
  65. error('asyncio function called outside coroutine.', 3)
  66. end
  67. return co
  68. end
  69. -- This table is used internally for iterators
  70. local suspend = {}
  71. function asyncio.await()
  72. get_coroutine()
  73. return coroutine.yield(suspend)
  74. end
  75. -- Create asyncio.resume on-the-fly when required.
  76. setmetatable(asyncio, {__index = function(_, name)
  77. if name == 'resume' then
  78. local co = coroutine.running()
  79. if not coroutines[co] then return end
  80. while iterators[co] do co = iterators[co] end
  81. return function(...)
  82. if coroutines[co] then
  83. resume(co, ...)
  84. end
  85. end
  86. elseif name == 'running' then
  87. return coroutines[coroutine.running()] and true or false
  88. end
  89. end})
  90. -- Wait a specified amount of seconds before continuing.
  91. function asyncio.sleep(delay)
  92. if type(delay) ~= 'number' or delay ~= delay or delay >= math.huge then
  93. error('Invalid asyncio.sleep() invocation.', 2)
  94. end
  95. asyncio.await(minetest.after(delay, asyncio.resume))
  96. end
  97. -- Defer execution until the next globalstep.
  98. function asyncio.defer()
  99. asyncio.await(minetest.after(0, asyncio.resume))
  100. end
  101. -- Only defer if it has been 5ms since the last optional_defer()
  102. local next_defer = minetest.get_us_time()
  103. function asyncio.optional_defer()
  104. if minetest.get_us_time() > next_defer then
  105. asyncio.defer()
  106. next_defer = minetest.get_us_time() + 5000
  107. return true
  108. end
  109. return false
  110. end
  111. asyncio.odefer = asyncio.optional_defer
  112. -- Pack and unpack using nil
  113. local function pack_len(...)
  114. return select('#', ...), {...}
  115. end
  116. local function unpack_len(length, t, i)
  117. i = i or 1
  118. if i <= length then
  119. return t[i], unpack_len(length, t, i + 1)
  120. end
  121. end
  122. -- asyncio.yield returns a table
  123. function asyncio.yield(...)
  124. if not iterators[get_coroutine()] then
  125. error('asyncio.yield() called outside of async iterator.', 2)
  126. end
  127. local l, t = pack_len(...)
  128. t.length = l
  129. coroutine.yield(t)
  130. end
  131. -- Iterate over a string
  132. local function str_iter(s)
  133. for n = 1, #s do
  134. asyncio.yield(n, s:sub(n, n))
  135. end
  136. end
  137. -- The standard iteration function (LuaJIT, Lua 5.2+)
  138. local rawequal = rawequal
  139. local function iter(func, ...)
  140. local co = get_coroutine()
  141. if type(func) == 'string' and select('#', ...) == 0 then
  142. return iter(str_iter, func)
  143. elseif type(func) ~= 'function' then
  144. error('Invalid asyncio.iter() invocation.')
  145. end
  146. local iterco = coroutine.create(func)
  147. coroutines[iterco], iterators[iterco] = true, co
  148. local l, m = pack_len(...)
  149. return function()
  150. if iterco == nil then return end
  151. assert(get_coroutine() == co)
  152. local msg = resume(iterco, unpack_len(l, m))
  153. l, m = 0, nil
  154. -- Suspend the parent coroutine if required
  155. while msg and rawequal(msg, suspend) do
  156. msg = resume(iterco, coroutine.yield(suspend))
  157. end
  158. -- Unpack asyncio.yield()s
  159. if type(msg) == 'table' and type(msg.length) == 'number' then
  160. if msg[1] == nil then
  161. coroutines[iterco], iterators[iterco] = nil, nil
  162. iterco = nil
  163. return
  164. end
  165. return unpack_len(msg.length, msg)
  166. end
  167. -- Return the result
  168. if msg == nil then
  169. coroutines[iterco], iterators[iterco], iterco = nil, nil, nil
  170. end
  171. return msg
  172. end
  173. end
  174. asyncio.iter = iter
  175. asyncio._iter_start = iter
  176. -- Lua 5.1 (excluding LuaJIT) compatibility
  177. asyncio.lua51 = _VERSION < 'Lua 5.2'
  178. if asyncio.lua51 then
  179. asyncio.lua51 = not pcall(coroutine.wrap(function()
  180. for i in coroutine.yield do return end
  181. end))
  182. end
  183. if asyncio.lua51 then
  184. last_error = {}
  185. assert(loadfile((minetest.get_modpath('asyncio') ..
  186. '/lua51.lua')))(last_error)
  187. end
  188. -- Defer pairs() and ipairs() until the next globalstep
  189. function asyncio.pairs(n, defer_every)
  190. if not iterators[get_coroutine()] then
  191. error('asyncio.pairs() called outside of an async iterator.', 2)
  192. end
  193. if type(defer_every) == 'number' and defer_every == defer_every and
  194. defer_every > 1 then
  195. local i = 0
  196. for k, v in pairs(n) do
  197. i = i + 1
  198. if i >= defer_every then asyncio.defer(); i = 0 end
  199. asyncio.yield(k, v)
  200. end
  201. else
  202. for k, v in pairs(n) do
  203. asyncio.optional_defer()
  204. asyncio.yield(k, v)
  205. end
  206. end
  207. end
  208. function asyncio.ipairs(n, defer_every)
  209. if not iterators[get_coroutine()] then
  210. error('asyncio.ipairs() called outside of an async iterator.', 2)
  211. end
  212. if type(defer_every) == 'number' and defer_every == defer_every and
  213. defer_every > 1 then
  214. asyncio.defer()
  215. defer_every = math.ceil(defer_every)
  216. for k, v in ipairs(n) do
  217. if k % defer_every == 0 then asyncio.defer() end
  218. asyncio.yield(k, v)
  219. end
  220. else
  221. -- Defer every 5ms
  222. for k, v in ipairs(n) do
  223. asyncio.optional_defer()
  224. asyncio.yield(k, v)
  225. end
  226. end
  227. end