forked from DarkEnergyProcessor/livesim2_async
-
Notifications
You must be signed in to change notification settings - Fork 0
/
async.lua
186 lines (155 loc) · 4.79 KB
/
async.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
-- Asynchronous operation wrapper
-- Part of Live Simulator: 2
-- See copyright notice in main.lua
-- Async operation requires it to run in coroutine
-- All operation return values that it's compatible
-- with assert(...) statement (values, or nil+error message)
local coroutine = require("coroutine")
local Luaoop = require("libs.Luaoop")
local lily = require("lily")
local Async = {
events = {},
backEvents = {},
}
local function asyncFunc(func)
return function(...)
if coroutine.running() == nil then
return nil, "Async cannot function in main thread"
end
return func(...)
end
end
-----------------------------
-- Base async object class --
-----------------------------
---@class Async.Base
local AsyncObject = Luaoop.class("async.Base")
function AsyncObject.__construct()
error("attempt to construct abstract class 'async.base'", 2)
end
function AsyncObject.sync()
error("attempt to call abstract method 'sync'", 2)
end
-------------------------------------
-- Lily wrapper async object class --
-------------------------------------
---@class Async.Lily: Async.Base
local LilyWrapperAsync = Luaoop.class("async.Lily", AsyncObject)
---@cast
function LilyWrapperAsync:__construct(lobj)
self.lily = lobj
end
function LilyWrapperAsync:isComplete()
return self.lily:isComplete()
end
function LilyWrapperAsync:sync()
if coroutine.running() == nil then
return nil, "Async cannot function in main thread"
end
while self.lily:isComplete() == false do
Async.wait()
end
end
function LilyWrapperAsync:getValues()
self:sync()
return self.lily:getValues()
end
function LilyWrapperAsync:getLily()
return self.lily
end
--------------------------
-- Function async class --
--------------------------
---@class Async.Function: Async.Base
local FuncAsync = Luaoop.class("async.Function", AsyncObject)
function FuncAsync:__construct(func)
self.func = func
self.coro = coroutine.create(func)
self.running = false
end
function FuncAsync:run(...)
assert(self.running == false, "attempt to run already running function")
local status, msg = coroutine.resume(self.coro, ...)
if status == false then
error(debug.traceback(self.coro, msg), 0)
end
self.running = true
end
function FuncAsync:sync()
local status = coroutine.status(self.coro)
while status ~= "dead" do
Async.wait()
status = coroutine.status(self.coro)
end
self.coro = coroutine.create(self.func)
self.running = false
end
---------------------
-- Async functions --
---------------------
--- Sends control back to async scheduler
---@param dt number? Time to wait
---@return number|nil Time since the last update in seconds, or none if dt is specified.
---@type fun(dt:number?):(number|nil)
Async.wait = asyncFunc(function(dt)
if dt and dt > 0 then
while dt > 0 do
dt = dt - Async.wait()
end
else
local a = coroutine.running()
if a == nil then
error("async cannot function in main thread", 2)
end
Async.events[#Async.events + 1] = a
return coroutine.yield()
end
end)
---Calls pending asynchronous task.
---@param dt number Time since the last update in seconds.
function Async.loop(dt)
Async.backEvents, Async.events = Async.events, Async.backEvents
for i = #Async.backEvents, 1, -1 do
local coro = table.remove(Async.backEvents, i)
local status, err = coroutine.resume(coro, dt)
if status == false then
error(debug.traceback(coro, err), 0)
end
end
end
---------------------
-- Object creation --
---------------------
--- Load image in asynchronous way.
-- @param path Path to image.
-- @tparam table settings Image loading settings, as per love.graphics.newImage
-- @tparam function errhand Error handler of the object
-- @treturn WrapLilyClass Asynchronous object (Lily wrapper)
function Async.loadImage(path, settings, errhand)
local l = lily.newImage(path, settings)
if errhand then
l:setUserData(path):onError(errhand)
end
return LilyWrapperAsync(l)
end
--- Load font in asynchronous way.
-- @tparam string path Path to font.
-- @tparam number size Font size.
-- @tparam number hinting For TTF fonts, type hinting of the font.
-- @tparam number dpiscale For LOVE 11.0 and later, DPI scale of the font.
-- @treturn WrapLilyClass Asynchronous object (Lily wrapper)
function Async.loadFont(path, size, hinting, dpiscale)
return LilyWrapperAsync(lily.newFont(path, size, hinting, dpiscale))
end
--- Load data with Lily
function Async.syncLily(lobj)
return LilyWrapperAsync(lobj)
end
--- Run function as asynchronous task.
-- You can call async.wait() inside the function
-- @tparam function func The function to run.
-- @treturn FunctionAsync object (call `:run(arg)` to run it)
function Async.runFunction(func)
return FuncAsync(func)
end
return Async