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