Arma 3 Scripting

Topic in Data Types

Script Handle

Also available as Markdown and JSON, or read the original wiki page.

A Script Handle, used to identify scripts in script operations called using spawn or execVM. Introduced with Armed Assault, does not refer to Operation Flashpoint or Operation Flashpoint: Resistance.

This handle's status can be checked with scriptDone, it can be terminated with terminate.

Note

Arma 3: In Arma 3:

  • the handle's status can also be checked with isNull (A completed script will become null)
  • the handle can be accessed from whithin itself using _thisScript (since Arma 3 v1.54)

Note

For more information about scripts and scripting, see Scripting Topics and Introduction to Arma Scripting.

Promise Handles (Arma 3 v2.22)

Script handles can be used as promises.

A Promise is either a Script Handle to a spawned or execVMed script, or an "Empty Handle" that does not hold a backing script. The Handle can be used to wait for the a result to be available. In case of a "real" handle that means the scheduled script returning a value, or in the case of a "Empty Handle" by using terminate to set a result value.

A Promise can have continuations added onto it with continueWith, which is script code that will execute once the handle is completed.

Handles can be used with waitUntil, continueWith, terminate, scriptDone, name.

Creating a Promise

Every handle returned by spawn or execVM is a promise that gets resolved once the script exits.

It's also possible to create a "Empty Handle" which does not hold any scheduled script, and can also be used in unscheduled code.

A "Empty Handle" needs to be "completed" by calling terminate on it.

private _emptyHandle = spawn "HandleName"; // does not contain an actual script, it's only a holder for name and potentially a value
private _normalHandle = 0 spawn { scriptName "HandleName"; }; // contains an actual scheduled script that will execute and return a value at the end

Using Promises

Promises can be used to simplify asynchronous operations. For example, when calling a function that takes some time to complete, and then wanting to execute code after it has completed.

To show it on an example, let's say we have a function that paradrops a vehicle. And we want to wait for the vehicle to touch the ground and then spawn a smoke grenade on it.

Previously, that could be solved like so:

fnc_StartParadrop = {
    params ["_object"];

    private _parachute = createVehicle ["B_Parachute_02_F", [0, 0, 0], [], 0, "CAN_COLLIDE"];

    // Put the parachute into the right position (setPos doesn't work on them)
    _parachute attachTo [_object, [0, 0, 0]];
    detach _parachute;

    _object attachTo [_parachute, [0, 0, 0]];
    // Now the object is hanging on parachute and gliding down
    // But we want to wait for it to arrive at the bottom, the parachute will be deleted once it lands, so we can use that

    // The parachute won't know about us, and we cannot get a value out of the eventhandler, so we will give the parachute a place to store the result
    // Note this will not work across savegame save/load because the "result" reference will be lost.

    private _result = [];
    _parachute setVariable ["result", _result];
    _parachute addEventHandler ["Deleted", {
        params ["_entity"];
        (_entity getVariable "result") set [0, getPos _entity]; // Store the landing position of the parachute
    }];

    // _result will be set once the parachute landed. How we wait on the result now depends on whether this is a scheduled or unscheduled function
    // In unscheduled code we cannot wait, we would need to get some callback function in our arguments to notify our caller that it can continue.
    // For simplicity we show the scheduled variant here, the unscheduled would be more complex

    // Keep looping, until someone (Hopefully the Deleted eventhandler) wrote a result into our array
    waitUntil { _result isNotEqualTo []; };

    _result select 0; // We return the result, our user must have call'ed us to wait for us to finish and get the result
};

// The fnc_StartParadrop will wait, so we have to spawn a new script to not hold up everything else
0 spawn {
    private _dropPos = ParadropVehicle call fnc_StartParadrop; // This will wait until the vehicle has landed
    "SmokeshellGreen" createVehicle _dropPos; // Spawn a smoke at the landing position
};

Also note how the waitUntil always runs. Even if we as the caller do not care for the paradrop to finish, we wouldn't easily have the option to tell the script that, without adding another parameter to it. Also note that this function is not usable in unscheduled scripts, because it uses waitUntil to wait for the landing.

With promises, it can be solved like this:

fnc_StartParadrop = {
    params ["_object"];

    private _parachute = createVehicle ["B_Parachute_02_F", [0, 0, 0], [], 0, "CAN_COLLIDE"];

    // Put the parachute into the right position (setPos doesn't work on them)
    _parachute attachTo [_object, [0, 0, 0]];
    detach _parachute;

    _object attachTo [_parachute, [0, 0, 0]];
    // Now the object is hanging on parachute and gliding down
    // But we want to wait for it to arrive at the bottom, the parachute will be deleted once it lands, so we can use that

    // The parachute won't know about us, and we cannot get a value out of the eventhandler, so we will give the parachute a place to store the result
    private _paradropHandle = spawn "Paradrop"; // We create new Promise, that we will use to return the result

    _parachute setVariable ["handle", _paradropHandle];
    _parachute addEventHandler ["Deleted", {
        params ["_entity"];
        (_entity getVariable "handle") terminate (getPos _entity); // Complete the promise and pass the landing position as result
    }];
    _paradropHandle // We return the promise, the caller can decide if they want to use it or not
};

// Note we do not need to spawn another script because we are not waiting for anything, this can fully be handled in unscheduled code now

_paradropHandle = ParadropVehicle call fnc_StartParadrop; // This will start the paradrop, and return a handle that we can use to wait for it to land
_paradropHandle continueWith { "SmokeshellGreen" createVehicle _this; }; // We use a continuation, it will be triggered once terminate is called on the handle (Inside the "Deleted" eventhandler)

Note that the paradrop function can now also be used in scheduled scripts like so:

private _paradropHandle = ParadropVehicle call fnc_StartParadrop; // This will start the paradrop, and return a handle that we can use to wait for it to land
private _dropPos = waitUntil _paradropHandle; // Wait for the promise to be completed and get its result
"SmokeshellGreen" createVehicle _dropPos; // Spawn a smoke at the landing position

Or like so:

private _paradropHandle = ParadropVehicle call fnc_StartParadrop; // This will start the paradrop, and return a handle that we can use to wait for it to land
private _dropPos = waitUntil [_paradropHandle, 60]; // Wait for the promise to be completed, but at most for 60 seconds, if it still has not landed yet, we will report it to the player

if (isNil "_dropPos") then {
    // The promise yielded no result. Either it was terminated without a result, or our timeout has elapsed
    hint "The Paradrop has failed!";
} else {
    "SmokeshellGreen" createVehicle _dropPos; // Spawn a smoke at the landing position
};