Files
GDevelop/GDJS/Runtime/AsyncTasksManager.ts
T
Florian Rival 7cdc92c776 Introduce the "Wait X seconds" action (#3852)
* This action allow to wait for a few seconds before continuing to run the next actions and sub-events.
* It is perfect to create cut scenes, advanced logic or just run things progressively without relying on timers. In a lot of cases, it's simpler and faster to use this new action.
* The action remember the picked objects: it works like an usual event, but run the actions (and sub-events) a bit later in time. While the action wait, other events continue to run as usual.
* Thanks to @arthuro555 for the ground work and follow up on this new feature.

Only show the rest in developer changelog:

* Add support for asynchronous actions (including for objects).
* Add exhaustive test cases for asynchronous actions. 

Co-authored-by: Arthur Pacaud <arthur.pacaud@hotmail.fr>
2022-05-02 19:44:15 +02:00

121 lines
3.3 KiB
TypeScript

/*
* GDevelop JS Platform
* Copyright 2013-2022 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
namespace gdjs {
/**
* This stores all asynchronous tasks waiting to be completed,
* for a given scene.
* @see {@link RuntimeScene.getAsyncTasksManager}.
*/
export class AsyncTasksManager {
/**
* Maps a task to the callback to be executed once it is finished.
*/
private tasksWithCallback = new Array<{
asyncTask: AsyncTask;
callback: (runtimeScene: gdjs.RuntimeScene) => void;
}>();
/**
* Run all pending asynchronous tasks.
*/
processTasks(runtimeScene: RuntimeScene): void {
for (let i = 0; i < this.tasksWithCallback.length; i++) {
const taskWithCallback = this.tasksWithCallback[i];
if (taskWithCallback.asyncTask.update(runtimeScene)) {
// The task has finished, run the callback and remove it.
taskWithCallback.callback(runtimeScene);
this.tasksWithCallback.splice(i--, 1);
}
}
}
/**
* Adds a task to be processed between frames and a callback for when it is done to the manager.
* @param asyncTask The {@link AsyncTask} to run.
* @param callback The callback to execute once the task is finished.
*/
addTask(
asyncTask: AsyncTask,
callback: (runtimeScene: RuntimeScene) => void
): void {
this.tasksWithCallback.push({ asyncTask, callback });
}
/**
* For testing only - removes all tasks.
* @internal
*/
clearTasks() {
this.tasksWithCallback.length = 0;
}
}
/**
* An asynchronous task to be run between frames.
*/
export abstract class AsyncTask {
/**
* Called every frame where the scene is active.
* @param runtimeScene - The scene the task runs on.
* @return True if the task is finished, false if it needs to continue running.
*/
abstract update(runtimeScene: RuntimeScene): boolean;
}
export class TaskGroup extends AsyncTask {
private tasks = new Array<AsyncTask>();
addTask(task: AsyncTask) {
this.tasks.push(task);
}
update(runtimeScene: gdjs.RuntimeScene) {
for (let i = 0; i < this.tasks.length; i++) {
const task = this.tasks[i];
if (task.update(runtimeScene)) this.tasks.splice(i--, 1);
}
return this.tasks.length === 0;
}
}
export class ResolveTask extends AsyncTask {
update() {
return true;
}
}
const logger = new gdjs.Logger('Internal PromiseTask');
/**
* A task that resolves with a promise.
*/
export class PromiseTask extends AsyncTask {
private isResolved: boolean = false;
promise: Promise<void>;
constructor(promise: Promise<void>) {
super();
this.promise = promise
.catch((error) => {
logger.error(
`A promise error has not been handled, this should never happen!
If you are using JavaScript promises in an asynchronous action, make sure to add a .catch().
Otherwise, report this as a bug on the GDevelop forums!
${error ? 'The following error was thrown: ' + error : ''}`
);
})
.then(() => {
this.isResolved = true;
});
}
update() {
return this.isResolved;
}
}
}