1
2 #include "async.h"
3
4 #include "executor/CombinedExecutionContext.h"
5 #include "executor/GameStateExecutionContext.h"
6 #include "executor/global_executors.h"
7 #include "scripting/api/LuaCoroutineRunner.h"
8 #include "scripting/api/LuaExecutionContext.h"
9 #include "scripting/api/objs/execution_context.h"
10 #include "scripting/api/objs/executor.h"
11 #include "scripting/api/objs/promise.h"
12 #include "scripting/lua/LuaThread.h"
13
14 namespace scripting {
15 namespace api {
16
17 //**********LIBRARY: Async
18 ADE_LIB(l_Async, "Async", "async", "Support library for asynchronous operations");
19
executorGetter(lua_State * L,const std::shared_ptr<executor::Executor> & executor)20 int executorGetter(lua_State* L, const std::shared_ptr<executor::Executor>& executor)
21 {
22 if (ADE_SETTING_VAR) {
23 LuaError(L, "This property is read only!");
24 return ADE_RETURN_NIL;
25 }
26
27 return ade_set_args(L, "o", l_Executor.Set(executor_h(executor)));
28 }
29
30 ADE_VIRTVAR(OnFrameExecutor,
31 l_Async,
32 nullptr,
33 "An executor that executes operations at the end of rendering a frame.",
34 "executor",
35 "The executor handle")
36 {
37 return executorGetter(L, executor::OnFrameExecutor);
38 }
39
40 ADE_VIRTVAR(OnSimulationExecutor,
41 l_Async,
42 nullptr,
43 "An executor that executes operations after all object simulation has been done but before rendering starts. This "
44 "is the place to do physics manipulations.",
45 "executor",
46 "The executor handle")
47 {
48 return executorGetter(L, executor::OnSimulationExecutor);
49 }
50
51 /**
52 * @brief A simple run context that allows a promise to be resolved by calling a lua function
53 *
54 * This is very similar to JavaScript Promises where a user defined function is passed a special "resolve" function that
55 * should be called when the coroutine is finished.
56 */
57 class lua_func_resolve_context : public resolve_context {
58 public:
lua_func_resolve_context(lua_State * L,luacpp::LuaFunction callback)59 lua_func_resolve_context(lua_State* L, luacpp::LuaFunction callback) : _luaState(L), _callback(std::move(callback))
60 {
61 }
62
setResolver(Resolver resolver)63 void setResolver(Resolver resolver) override
64 {
65 struct shared_state {
66 Resolver resolver;
67 bool alreadyCalled = false;
68 };
69
70 auto state = std::make_shared<shared_state>();
71 state->resolver = std::move(resolver);
72
73 const auto resolveFunc = luacpp::LuaFunction::createFromStdFunction(_callback.getLuaState(),
74 [state](lua_State* L, const luacpp::LuaValueList& resolveVals) {
75 if (state->alreadyCalled) {
76 LuaError(L, "Promise has already been resolved or rejected before!");
77 return luacpp::LuaValueList();
78 }
79
80 state->resolver(false, resolveVals);
81 state->alreadyCalled = true;
82
83 state->resolver = nullptr;
84 return luacpp::LuaValueList();
85 });
86
87 const auto rejectFunc = luacpp::LuaFunction::createFromStdFunction(_callback.getLuaState(),
88 [state](lua_State* L, const luacpp::LuaValueList& resolveVals) {
89 if (state->alreadyCalled) {
90 LuaError(L, "Promise has already been resolved or rejected before!");
91 return luacpp::LuaValueList();
92 }
93
94 state->resolver(true, resolveVals);
95 state->alreadyCalled = true;
96
97 state->resolver = nullptr;
98 return luacpp::LuaValueList();
99 });
100
101 _callback(_luaState, {resolveFunc, rejectFunc});
102 }
103
104 private:
105 lua_State* _luaState = nullptr;
106 luacpp::LuaFunction _callback;
107 };
108
109 ADE_FUNC(promise,
110 l_Async,
111 "function(function(any resolveVal) => void resolve, function(any errorVal) => void reject) => void body",
112 "Creates a promise that resolves when the resolve function of the callback is called or errors if the reject "
113 "function is called. The function will be called "
114 "on it's own.",
115 "promise",
116 "The promise or nil on error")
117 {
118 luacpp::LuaFunction callback;
119 if (!ade_get_args(L, "u", &callback)) {
120 return ADE_RETURN_NIL;
121 }
122
123 if (!callback.isValid()) {
124 LuaError(L, "Invalid function supplied!");
125 return ADE_RETURN_NIL;
126 }
127
128 std::unique_ptr<resolve_context> resolveCtx(new lua_func_resolve_context(L, std::move(callback)));
129
130 return ade_set_args(L, "o", l_Promise.Set(LuaPromise(std::move(resolveCtx))));
131 }
132
133 ADE_FUNC(resolved,
134 l_Async,
135 "any... resolveValues",
136 "Creates a resolved promise with the values passed to this function.",
137 "promise",
138 "Resolved promise")
139 {
140 auto nargs = lua_gettop(L);
141 luacpp::LuaValueList values;
142 values.reserve(nargs);
143
144 for (int i = 1; i <= nargs; ++i) {
145 luacpp::LuaValue val;
146 val.setReference(luacpp::UniqueLuaReference::create(L, i));
147 values.push_back(val);
148 }
149
150 return ade_set_args(L, "o", l_Promise.Set(LuaPromise::resolved(values)));
151 }
152
153 ADE_FUNC(errored,
154 l_Async,
155 "any... errorValues",
156 "Creates an errored promise with the values passed to this function.",
157 "promise",
158 "Errored promise")
159 {
160 auto nargs = lua_gettop(L);
161 luacpp::LuaValueList values;
162 values.reserve(nargs);
163
164 for (int i = 1; i <= nargs; ++i) {
165 luacpp::LuaValue val;
166 val.setReference(luacpp::UniqueLuaReference::create(L, i));
167 values.push_back(val);
168 }
169
170 return ade_set_args(L, "o", l_Promise.Set(LuaPromise::errored(values)));
171 }
172
173 ADE_FUNC(run,
174 l_Async,
175 "function() => any body, [executor executeOn = nil, boolean|execution_context captureContext = true /* Captures "
176 "game state context by default */]",
177 "Runs an asynchronous function. Inside this function you can use async.await to suspend the function until a "
178 "promise resolves. Also allows to specify an executor on which the code of the coroutine should be executed. If "
179 "captureContext is true then the game context (the game state) at the time of the call is captured and the "
180 "coroutine is only run if that state is still active.",
181 "promise",
182 "A promise that resolves with the return value of the body when it reaches a return statement")
183 {
184 luacpp::LuaFunction body;
185 executor_h executor;
186 std::shared_ptr<executor::IExecutionContext> context;
187
188 if (lua_type(L, 3) == LUA_TUSERDATA) {
189 execution_context_h* ctx = nullptr;
190 if (!ade_get_args(L, "u|oo", &body, l_Executor.Get(&executor), l_ExecutionContext.GetPtr(&ctx))) {
191 return ADE_RETURN_NIL;
192 }
193 if (ctx != nullptr) {
194 context = ctx->getExecutionContext();
195 }
196 } else {
197 bool captureContext = true;
198 if (!ade_get_args(L, "u|ob", &body, l_Executor.Get(&executor), &captureContext)) {
199 return ADE_RETURN_NIL;
200 }
201 if (executor.isValid() && captureContext) {
202 context = executor::GameStateExecutionContext::captureContext();
203 }
204 }
205
206 auto coroutine = luacpp::LuaThread::create(L, body);
__anon02a18e2c0302(lua_State*, lua_State* thread) 207 coroutine.setErrorCallback([](lua_State*, lua_State* thread) {
208 LuaError(thread);
209 return true;
210 });
211
212 return ade_set_args(L,
213 "o",
214 l_Promise.Set(runAsyncCoroutine(std::move(coroutine), executor.getExecutor(), std::move(context))));
215 }
216
217 ADE_FUNC(await,
218 l_Async,
219 "promise",
220 "Suspends an asynchronous coroutine until the passed promise resolves.",
221 "any",
222 "The resolve value of the promise")
223 {
224 // await cannot be used on the main thread since there is nothing that will wait for the promise
225 if (lua_pushthread(L)) {
226 // We are the main thread
227 lua_pop(L, 1);
228
229 LuaError(L, "Tried to await something on the main thread! That is not supported.");
230 return ADE_RETURN_NIL;
231 }
232 lua_pop(L, 1);
233
234 LuaPromise* promise = nullptr;
235 if (!ade_get_args(L, "o", l_Promise.GetPtr(&promise))) {
236 return ADE_RETURN_NIL;
237 }
238
239 if (promise == nullptr || !promise->isValid()) {
240 LuaError(L, "Invalid promise detected. This should not happen. Please contact a developer.");
241 return ADE_RETURN_NIL;
242 }
243
244 if (promise->isResolved()) {
245 // No need to suspend if the promise is already resolved. Just take the resolve values from the promise and
246 // return them
247 const auto& retVals = promise->resolveValue();
248 for (const auto& retVal : retVals) {
249 retVal.pushValue(L); // Push onto this stack to ensure it is actually returned from this function
250 }
251 return static_cast<int>(retVals.size());
252 }
253
254 // Return the promise via the yield so that the resumer can register themself on the promise to resume when that
255 // resolves
256 return lua_yield(L, 1);
257 }
258
259 ADE_FUNC(yield,
260 l_Async,
261 nullptr,
262 "Returns a promise that will resolve on the next execution of the current executor. Effectively allows to "
263 "asynchronously wait until the next frame.",
264 "promise",
265 "The promise")
266 {
267 if (executor::currentExecutor() == nullptr) {
268 LuaError(L,
269 "There is no running executor at the moment. This function needs to be called from a executor callback.");
270 return ADE_RETURN_NIL;
271 }
272
273 class yield_resolve_context : public resolve_context {
274 public:
yield_resolve_context(executor::Executor * executor)275 explicit yield_resolve_context(executor::Executor* executor) : m_exec(executor) {}
setResolver(Resolver resolver)276 void setResolver(Resolver resolver) override
277 {
278 m_exec->post([resolver]() {
279 resolver(false, luacpp::LuaValueList());
280 return executor::Executor::CallbackResult::Done;
281 });
282 }
283
284 private:
285 executor::Executor* m_exec = nullptr;
286 };
287 return ade_set_args(L,
288 "o",
289 l_Promise.Set(LuaPromise(std::make_shared<yield_resolve_context>(executor::currentExecutor()))));
290 }
291
292 ADE_FUNC(error,
293 l_Async,
294 "any... errorValues",
295 "Causes the currently running coroutine to fail with an error with the specified values.",
296 nullptr,
297 "Does not return")
298 {
299 // error cannot be used on the main thread since there is nothing that will wait for the promise
300 if (lua_pushthread(L)) {
301 // We are the main thread
302 lua_pop(L, 1);
303
304 LuaError(L, "Tried to cause a coroutine error on the main thread! That is not supported.");
305 return ADE_RETURN_NIL;
306 }
307 lua_pop(L, 1);
308
309 auto nargs = lua_gettop(L);
310 luacpp::LuaValueList values;
311 values.reserve(nargs);
312
313 for (int i = 1; i <= nargs; ++i) {
314 luacpp::LuaValue val;
315 val.setReference(luacpp::UniqueLuaReference::create(L, i));
316 values.push_back(val);
317 }
318
319 // Push an errored promise on the stack and then yield the coroutine. The awaiter will get this promise and stop
320 // execution.
321 ade_set_args(L, "o", l_Promise.Set(LuaPromise::errored(values)));
322 return lua_yield(L, 1);
323 }
324
325 //**********SUBLIBRARY: async/context
326 ADE_LIB_DERIV(l_Async_Context, "context", nullptr, "Support library for creating execution contexts.", l_Async);
327
328 ADE_FUNC(captureGameState,
329 l_Async_Context,
330 nullptr,
331 "Captures the current game state as an execution context",
332 "execution_context",
333 "The execution context or invalid handle on error")
334 {
335 return ade_set_args(L,
336 "o",
337 l_ExecutionContext.Set(execution_context_h(executor::GameStateExecutionContext::captureContext())));
338 }
339
340 ADE_FUNC(createLuaState,
341 l_Async_Context,
342 "function() => enumeration",
343 "Creates an execution state by storing the passed function and calling that when the state is required.",
344 "execution_context",
345 "The execution context or invalid handle on error")
346 {
347 luacpp::LuaFunction body;
348 if (!ade_get_args(L, "u", &body)) {
349 return ade_set_args(L, "o", l_ExecutionContext.Set(execution_context_h(nullptr)));
350 }
351
352 return ade_set_args(L,
353 "o",
354 l_ExecutionContext.Set(execution_context_h(std::make_shared<LuaExecutionContext>(std::move(body)))));
355 }
356
357 ADE_FUNC(combineContexts,
358 l_Async_Context,
359 "execution_context... contexts",
360 "Combines several execution contexts into a single one by only return a valid state if all contexts are valid.",
361 "execution_context",
362 "The execution context or invalid handle on error")
363 {
364 SCP_vector<std::shared_ptr<executor::IExecutionContext>> contexts;
365 for (int i = 0; i < lua_gettop(L); ++i) {
366 execution_context_h* ctx;
367 internal::Ade_get_args_skip = i;
368 if (!ade_get_args(L, "o", l_ExecutionContext.GetPtr(&ctx))) {
369 return ade_set_args(L, "o", l_ExecutionContext.Set(execution_context_h(nullptr)));
370 }
371 contexts.emplace_back(ctx->getExecutionContext());
372 }
373
374 return ade_set_args(L,
375 "o",
376 l_ExecutionContext.Set(
377 execution_context_h(std::make_shared<executor::CombinedExecutionContext>(std::move(contexts)))));
378 }
379
380 } // namespace api
381 } // namespace scripting
382