Asynchronous Programming

This feature is highly experimental and might be unstable.

Since 1.0.4 CLEO Redux receives support for Promises and async/await syntax. This enables lots of advanced code patterns.

Note that asynchoronous programming is a fairly complex topic and if you want to learn more about visit the following resources:

See an example of an async script for GTA III here.

Async API

CLEO Redux 1.0.4 has one native async command: asyncWait. This command is used to pause a script for a specified amount of time. It returns a Promise that resolves after the specified amount of time. asyncWait can only be used inside a async function.

async function delay(ms) {
  log("Waiting for " + ms + "ms");
  await asyncWait(ms);
  log("Done waiting");
}

delay(1000);

Difference between asyncWait and wait is that the former is not a blocking command. If you don't put the await keyword in front of it, the script execution will continue.

async function delay(ms) {
  log("Waiting for " + ms + "ms");
  asyncWait(ms); // no await here, the script continues
  log("Executed immediately");
}

delay(1000);

Async Functions

CLEO Redux does not support a top-level await. This means that you cannot use the await keyword in the main body of the script.

// script begins
await asyncWait(1000); // won't work
showTextBox("Hello");

To bypass this limitation wrap your code in an anonymous async function.

(async function () {
  // script begins
  await asyncWait(1000); // works, because it's inside an async function
  showTextBox("Hello");
})();

Note that by design any exception thrown inside an async function is not caught automatically. This means that if you want to catch an exception you need to wrap your code in a try/catch block or add .catch handler to the promise.

(async function () {
  // script code goes here
  non_existing_function();
})().catch((e) => {
  log(e);
});

The log will contain the following message:

"ReferenceError: 'non_existing_function' is not defined"

Concurrent Async Functions

One of the advantages of async functions is that they could run concurrently. This means that you can run multiple async functions at the same time. Some languages call these coroutines.

import { KeyCode } from "./.config/enums";
let gVar = 0;

(async () => {
  await asyncWait(0);

  // running task1, task2, and task3 concurrently
  task1();
  task2();
  task3();
})();

// shakes the camera
async function task1() {
  while (true) {
    await asyncWait(0);
    Camera.Shake(30);
  }
}

// awards the player with $1000 every second and increments the global variable gVar
async function task2() {
  let p = new Player(0);
  while (true) {
    await asyncWait(1000);

    p.addScore(1);
    gVar++;
  }
}

// waits for the button J to be pressed and displays the value of gVar
async function task3() {
  while (true) {
    await asyncWait(0);
    if (Pad.IsKeyDown(KeyCode.J)) {
      showTextBox("gVar is " + gVar);
    }
  }
}

This is very similar to writing traditional while(true){} loops with a wait command in it, with the difference that the functions must have async keyword and use asyncWait instead of wait Each of the three tasks are executed independently from each other. Note that runtime guarantees that all async functions are executed in the same thread. They could share global variables and mutate them. Look at how gVar is incremented in task2 and read in task3.

Dynamic Imports

CLEO Redux supports dynamic imports. It allows to load script files on-demand when they are needed. This is useful for large scripts that are not needed all the time.

(async () => {
  if (somethingIsTrue) {
    // import module for side effects
    await import("./my-module.mjs");
  }
})();

Imported modules can export functions and variables. They can be used in the same way as in regular scripts.

my-module.mjs:

export const myVar = 42;

main.js:

(async () => {
  const { myVar } = await import("./my-module.mjs");
  log(myVar); // prints 42
})();