Skip to content
Flecs v4.1
script_async.h
Go to the documentation of this file.
1/**
2 * @file addons/script_async.h
3 * @brief Async/await support for Flecs script.
4 */
5
6#ifdef FLECS_SCRIPT_ASYNC
7
8#ifndef FLECS_SCRIPT
9#define FLECS_SCRIPT
10#endif
11
12/**
13 * @defgroup c_addons_script_async Script Async
14 * @ingroup c_addons
15 * Async/await support for Flecs script.
16 *
17 * @{
18 */
19
20#ifndef FLECS_SCRIPT_ASYNC_H
21#define FLECS_SCRIPT_ASYNC_H
22
23#ifdef __cplusplus
24extern "C" {
25#endif
26
27FLECS_API
28extern ECS_COMPONENT_DECLARE(EcsScriptTask);
29
30/** Future for the result of an async function call.
31 * A future is created when an async function is called, either by a script
32 * that awaits the function or by ecs_async_function_call(), and is passed to
33 * the async function callback. The callback (or code invoked by it, possibly
34 * on another thread) completes the future with ecs_script_future_resolve() or
35 * ecs_script_future_reject(), after which the awaiting task can resume.
36 */
37typedef struct ecs_script_future_t ecs_script_future_t;
38
39/** Resumable script execution.
40 * A task runs a script incrementally, suspending when the script awaits an
41 * async function and resuming when the awaited future is completed. Tasks are
42 * created with ecs_script_task_new() and advanced with
43 * ecs_script_task_resume().
44 */
45typedef struct ecs_script_task_t ecs_script_task_t;
46
47/** Async script function callback.
48 * Instead of returning a result directly, an async function receives a future
49 * that must be completed with ecs_script_future_resolve() or
50 * ecs_script_future_reject(). The future may be completed from inside the
51 * callback or at a later point in time, from any thread.
52 */
53typedef void(*ecs_async_function_callback_t)(
54 const ecs_function_ctx_t *ctx,
55 int32_t argc,
56 const ecs_value_t *argv,
57 ecs_script_future_t *future);
58
59/** Async script function cancel callback.
60 * Invoked when a pending future is cancelled, which allows an async function
61 * to abort in-flight work. After this callback the future can no longer be
62 * resolved or rejected.
63 */
64typedef void(*ecs_async_function_cancel_t)(
65 const ecs_function_ctx_t *ctx,
66 ecs_script_future_t *future);
67
68/** Task component.
69 * This component is added to entities for which tasks are created (see the
70 * entity member of ecs_script_task_desc_t) and removed when their last task
71 * is freed. The component does not own the tasks; it is kept in sync by
72 * ecs_script_task_new() and ecs_script_task_free().
73 *
74 * When an entity with this component is deleted (or the component is
75 * removed), the remaining tasks are cancelled (see ecs_script_task_cancel()).
76 * The application remains responsible for freeing them.
77 *
78 * The component is registered as an opaque type that serializes the current
79 * state of each task: the script entity (0 for unmanaged scripts), task
80 * status, the line/column of the statement the task is suspended on, the
81 * async function being awaited and the number of completed iterations. This
82 * makes it possible to
83 * inspect where in a script a task is from tools like the explorer.
84 */
85typedef struct EcsScriptTask {
86 ecs_vec_t tasks; /**< vec<ecs_script_task_t*>. Not owned by component. */
87} EcsScriptTask;
88
89/** Script task status. */
90typedef enum ecs_script_task_status_t {
91 EcsScriptTaskPending, /**< Task is awaiting a future or hasn't run yet. */
92 EcsScriptTaskDone, /**< Task ran to completion. */
93 EcsScriptTaskError, /**< Task encountered an error. */
94 EcsScriptTaskCancelled /**< Task was cancelled. */
95} ecs_script_task_status_t;
96
97/** Script future status. */
98typedef enum ecs_script_future_status_t {
99 EcsScriptFuturePending, /**< Future hasn't been completed yet. */
100 EcsScriptFutureResolved, /**< Future was resolved with a value. */
101 EcsScriptFutureRejected, /**< Future was rejected with an error. */
102 EcsScriptFutureCancelled /**< Future was cancelled. */
103} ecs_script_future_status_t;
104
105/** Script task loop mode. */
106typedef enum ecs_script_task_loop_t {
107 EcsScriptTaskLoopOnce, /**< Run script once (default). */
108 EcsScriptTaskLoopCount, /**< Run script a fixed number of times. */
109 EcsScriptTaskLoopForever /**< Restart script after each completion. */
110} ecs_script_task_loop_t;
111
112/** Used with ecs_script_task_new(). */
113typedef struct ecs_script_task_desc_t {
114 ecs_entity_t entity; /**< Entity associated with task. When set,
115 * the script gets a "this" variable that
116 * is bound to the entity, and the entity
117 * gets an EcsScriptTask component that
118 * can be used to inspect task state. */
119 void *ctx; /**< User context. */
120 ecs_ctx_free_t ctx_free; /**< Callback to free ctx. */
121 ecs_script_task_loop_t loop; /**< Loop mode. */
122 int32_t iterations; /**< Number of iterations. Must be set when
123 * loop is EcsScriptTaskLoopCount. */
124} ecs_script_task_desc_t;
125
126/** Create task.
127 * This operation creates a task that runs the provided script. Unlike
128 * ecs_script_eval(), a task evaluates a script incrementally: when the script
129 * awaits an async function, evaluation suspends until the awaited future is
130 * completed. This makes it possible for scripts to use the await keyword.
131 *
132 * The returned task does not start running until ecs_script_task_resume() is
133 * called. The task must be freed with ecs_script_task_free().
134 *
135 * @param script The script to run.
136 * @param desc Task creation parameters (optional).
137 * @return The task, or NULL if failed.
138 */
139FLECS_API
140ecs_script_task_t* ecs_script_task_new(
141 const ecs_script_t *script,
142 const ecs_script_task_desc_t *desc);
143
144/** Resume task.
145 * This operation runs the task until it completes or awaits a future that is
146 * still pending. It is safe to call this operation while the task is waiting
147 * on a pending future, in which case it returns EcsScriptTaskPending without
148 * doing work. To avoid unnecessary calls, an application can first check
149 * ecs_script_task_is_ready().
150 *
151 * If the task loop mode is EcsScriptTaskLoopCount or EcsScriptTaskLoopForever,
152 * the task restarts from the beginning of the script after each completed
153 * iteration, and EcsScriptTaskDone is not returned until all iterations have
154 * completed (never, for EcsScriptTaskLoopForever).
155 *
156 * When the result parameter is not NULL, the task will capture errors and
157 * return them in the output struct. If result.error is set, it must be freed
158 * by the application.
159 *
160 * @param task The task.
161 * @param result Output of script evaluation.
162 * @return The task status after resuming.
163 */
164FLECS_API
165ecs_script_task_status_t ecs_script_task_resume(
166 ecs_script_task_t *task,
168
169/** Check if task is ready to resume.
170 * This operation returns false if the task is waiting on a future that hasn't
171 * been completed yet, in which case calling ecs_script_task_resume() would not
172 * make progress. This operation is threadsafe with respect to future
173 * completion, which makes it safe to poll tasks that wait on futures that are
174 * resolved from other threads.
175 *
176 * @param task The task.
177 * @return True if calling ecs_script_task_resume() would make progress.
178 */
179FLECS_API
180bool ecs_script_task_is_ready(
181 const ecs_script_task_t *task);
182
183/** Cancel task.
184 * This operation cancels a running task. If the task is waiting on a pending
185 * future, the future is cancelled and the async function's cancel callback is
186 * invoked (if provided). Cancelling a task that already completed has no
187 * effect. A cancelled task can no longer be resumed.
188 *
189 * @param task The task.
190 */
191FLECS_API
192void ecs_script_task_cancel(
193 ecs_script_task_t *task);
194
195/** Free task.
196 * This operation frees a task. If the task hasn't finished yet it is
197 * cancelled (see ecs_script_task_cancel()).
198 *
199 * @param task The task.
200 */
201FLECS_API
202void ecs_script_task_free(
203 ecs_script_task_t *task);
204
205/** Progress async block tasks.
206 * Scripts and templates can contain async blocks:
207 *
208 * @code
209 * async {
210 * const value = await fetch()
211 * // ...
212 * }
213 * @endcode
214 *
215 * An async block creates a task when the script is evaluated or the template
216 * is instantiated. This operation resumes all async block tasks that are ready
217 * to make progress, and frees tasks that completed. When the pipeline addon is
218 * enabled, this operation is invoked by the flecs.script.ProgressTasks system
219 * in the EcsPreUpdate phase.
220 *
221 * @param world The world.
222 * @return The number of tasks that were resumed.
223 */
224FLECS_API
225int32_t ecs_script_tasks_progress(
226 ecs_world_t *world);
227
228/** Resolve future.
229 * This operation completes a future with a result value. The value must be of
230 * the return type of the async function, and is copied into the future. An
231 * awaiting task can afterwards be resumed with ecs_script_task_resume().
232 *
233 * This operation is threadsafe and may be called from a different thread than
234 * the one that runs the task.
235 *
236 * @param future The future.
237 * @param value The result value.
238 * @return Zero if success, non-zero if the future was already completed or
239 * cancelled.
240 */
241FLECS_API
242int ecs_script_future_resolve(
243 ecs_script_future_t *future,
244 const ecs_value_t *value);
245
246/** Reject future.
247 * This operation completes a future with an error. When the awaiting task is
248 * resumed, evaluation of the script fails with the provided error message.
249 *
250 * This operation is threadsafe and may be called from a different thread than
251 * the one that runs the task.
252 *
253 * @param future The future.
254 * @param error The error message.
255 * @return Zero if success, non-zero if the future was already completed or
256 * cancelled.
257 */
258FLECS_API
259int ecs_script_future_reject(
260 ecs_script_future_t *future,
261 const char *error);
262
263/** Reject future with error id.
264 * Same as ecs_script_future_reject(), but additionally associates the error
265 * with an error identifier. Scripts can catch errors with a specific
266 * identifier with a typed catch clause:
267 *
268 * @code
269 * try {
270 * await fetch()
271 * } catch(ConnectionLost) {
272 * // runs when the future was rejected with the ConnectionLost entity
273 * } catch {
274 * // runs for any other error
275 * }
276 * @endcode
277 *
278 * Errors rejected without an error id (or with ecs_script_future_reject())
279 * can only be caught by a catch-all clause. When msg is NULL a default
280 * message is used.
281 *
282 * This operation is threadsafe and may be called from a different thread than
283 * the one that runs the task.
284 *
285 * @param future The future.
286 * @param error The error identifier. May be 0 if msg is not NULL.
287 * @param msg The error message (optional if error is set).
288 * @return Zero if success, non-zero if the future was already completed or
289 * cancelled.
290 */
291FLECS_API
292int ecs_script_future_reject_id(
293 ecs_script_future_t *future,
294 ecs_entity_t error,
295 const char *msg);
296
297/** Get future status.
298 * This operation returns whether a future is pending, resolved, rejected or
299 * cancelled. Applications that call ecs_async_function_call() can use it to
300 * poll for completion. Async functions can use it to detect that the result is
301 * no longer needed because the future was cancelled.
302 *
303 * This operation is threadsafe.
304 *
305 * @param future The future.
306 * @return The future status.
307 */
308FLECS_API
309ecs_script_future_status_t ecs_script_future_status(
310 const ecs_script_future_t *future);
311
312/** Get future value.
313 * This operation returns the value a future was resolved with. The value is
314 * owned by the future, and is valid until the future is released.
315 *
316 * This operation is threadsafe.
317 *
318 * @param future The future.
319 * @return The result value, or NULL if the future was not resolved.
320 */
321FLECS_API
322const ecs_value_t* ecs_script_future_get_value(
323 const ecs_script_future_t *future);
324
325/** Get future error.
326 * This operation returns the error message a future was rejected with. The
327 * message is owned by the future, and is valid until the future is released.
328 *
329 * This operation is threadsafe.
330 *
331 * @param future The future.
332 * @return The error message, or NULL if the future was not rejected.
333 */
334FLECS_API
335const char* ecs_script_future_get_error(
336 const ecs_script_future_t *future);
337
338/** Get future error id.
339 * This operation returns the error identifier a future was rejected with (see
340 * ecs_script_future_reject_id()).
341 *
342 * This operation is threadsafe.
343 *
344 * @param future The future.
345 * @return The error identifier, or 0 if the future was not rejected or was
346 * rejected without an error identifier.
347 */
348FLECS_API
349ecs_entity_t ecs_script_future_get_error_id(
350 const ecs_script_future_t *future);
351
352/** Cancel future.
353 * This operation cancels a pending future and invokes the cancel callback of
354 * the async function (if provided). A cancelled future can no longer be
355 * resolved or rejected. Cancelling a future that already completed has no
356 * effect.
357 *
358 * Cancelling a future does not release it. Futures awaited by a task are
359 * cancelled when the task is cancelled or freed.
360 *
361 * @param future The future.
362 */
363FLECS_API
364void ecs_script_future_cancel(
365 ecs_script_future_t *future);
366
367/** Increase future refcount.
368 * This operation increases the refcount of a future, which allows an async
369 * function to keep the future alive after the callback returns. Each call to
370 * this operation must be matched with a call to ecs_script_future_release().
371 *
372 * A future passed to an async function callback is already retained on behalf
373 * of the callback, and must be released after the future is completed. A
374 * future returned by ecs_async_function_call() is retained on behalf of the
375 * application.
376 *
377 * This operation is threadsafe.
378 *
379 * @param future The future.
380 * @return The future.
381 */
382FLECS_API
383ecs_script_future_t* ecs_script_future_retain(
384 ecs_script_future_t *future);
385
386/** Decrease future refcount.
387 * This operation decreases the refcount of a future, and frees the future when
388 * the refcount reaches zero.
389 *
390 * This operation is threadsafe.
391 *
392 * @param future The future.
393 */
394FLECS_API
395void ecs_script_future_release(
396 ecs_script_future_t *future);
397
398/** Set future context.
399 * This operation attaches a user context to a future, which async functions
400 * can use to store state associated with the in-flight operation (for example
401 * a request handle, so it can be aborted from the cancel callback). The
402 * context can only be set once. If a ctx_free callback is provided, it is
403 * invoked when the future is freed.
404 *
405 * @param future The future.
406 * @param ctx User context.
407 * @param ctx_free Callback to free ctx (optional).
408 */
409FLECS_API
410void ecs_script_future_set_ctx(
411 ecs_script_future_t *future,
412 void *ctx,
413 ecs_ctx_free_t ctx_free);
414
415/** Get future context.
416 * Returns the context set with ecs_script_future_set_ctx().
417 *
418 * @param future The future.
419 * @return The user context, or NULL if not set.
420 */
421FLECS_API
422void* ecs_script_future_get_ctx(
423 const ecs_script_future_t *future);
424
425/** Used with ecs_async_function_init() and ecs_async_method_init(). */
426typedef struct ecs_async_function_desc_t {
427 /** Function name. */
428 const char *name;
429
430 /** Parent of function entity. */
431 ecs_entity_t parent;
432
433 /** Function parameters. */
434 ecs_script_parameter_t params[FLECS_SCRIPT_FUNCTION_ARGS_MAX];
435
436 /** Function return type. */
437 ecs_entity_t return_type;
438
439 /** Function implementation. Receives the future to complete instead of a
440 * result output parameter. */
441 ecs_async_function_callback_t callback;
442
443 /** Invoked when a pending future of this function is cancelled
444 * (optional). */
445 ecs_async_function_cancel_t cancel;
446
447 /** Context passed to function implementation. */
448 void *ctx;
449} ecs_async_function_desc_t;
450
451/** Create new async function.
452 * This operation creates a new async function. Async functions do not return
453 * their result directly, but instead receive a future that can be completed
454 * at a later point in time (see ecs_script_future_resolve()).
455 *
456 * Scripts call async functions with the await keyword, which can only be used
457 * by scripts that are evaluated as a task (see ecs_script_task_new()). C code
458 * calls async functions with ecs_async_function_call().
459 *
460 * @param world The world.
461 * @param desc Async function init parameters.
462 * @return The function, or 0 if failed.
463*/
464FLECS_API
465ecs_entity_t ecs_async_function_init(
466 ecs_world_t *world,
467 const ecs_async_function_desc_t *desc);
468
469#define ecs_async_function(world, ...)\
470 ecs_async_function_init(world, &(ecs_async_function_desc_t)__VA_ARGS__)
471
472/** Create new async method.
473 * Like ecs_async_function_init(), but callable on instances of the type set as
474 * parent. The callback receives the instance as argv[0].
475 *
476 * @param world The world.
477 * @param desc Async method init parameters.
478 * @return The method, or 0 if failed.
479 */
480FLECS_API
481ecs_entity_t ecs_async_method_init(
482 ecs_world_t *world,
483 const ecs_async_function_desc_t *desc);
484
485#define ecs_async_method(world, ...)\
486 ecs_async_method_init(world, &(ecs_async_function_desc_t)__VA_ARGS__)
487
488/** Call async function.
489 * This operation calls an async function from C. The function callback
490 * receives a new future, which is also returned to the application. Use
491 * ecs_script_future_status() to check whether the call completed, and
492 * ecs_script_future_get_value() or ecs_script_future_get_error() to get the
493 * result.
494 *
495 * The returned future must be released with ecs_script_future_release(). To
496 * abort a call that is still pending, use ecs_script_future_cancel().
497 *
498 * Arguments must match the function parameters, and are only valid during the
499 * callback. Async functions that need arguments later on must copy them.
500 *
501 * @param world The world.
502 * @param function The async function.
503 * @param argc The number of arguments.
504 * @param argv The arguments.
505 * @return The future, or NULL if the call failed.
506 */
507FLECS_API
508ecs_script_future_t* ecs_async_function_call(
509 ecs_world_t *world,
510 ecs_entity_t function,
511 int32_t argc,
512 const ecs_value_t *argv);
513
514#ifdef __cplusplus
515}
516#endif
517
518#endif
519
520/** @} */
521
522#endif
struct ecs_script_parameter_t ecs_script_parameter_t
Function argument type.
ecs_id_t ecs_entity_t
An entity identifier.
Definition flecs.h:396
struct ecs_world_t ecs_world_t
A world is the container for all ECS data and supporting features.
Definition flecs.h:440
script_builder script(const char *name=nullptr) const
Build a script.
Definition mixin.inl:31
#define ECS_COMPONENT_DECLARE(id)
Forward declare a component.
Definition flecs_c.h:65
void(*) ecs_ctx_free_t(void *ctx)
Function to clean up context data.
Definition flecs.h:661
Script function context.
Definition script.h:157
Used to capture error output from script evaluation.
Definition script.h:226
Script object.
Definition script.h:118
Value of a dynamic type.
Definition flecs.h:1069
flecs::entity entity(Args &&... args) const
Create an entity.