Skip to main content

Events

Events are the logical building blocks of our framework and they are used to pass information back and forth both between the native engine and other scripts. They are modeled after the JavaScript equivalent EventEmitter.

Event creation

Events are constructed simply by their name, as convention the variable name should be prefixed with on in local scope and On in the global scope. Event names themselves are PascalCase.

-- Creates an event.
Event Event.Create(string? name)

Event names can be read using the field .name, although cannot be changed.

info

Event objects supports comparison and string conversion metamethods.

Example/index.ts
export const OnTest = Event.Create<{ level: number }>('Test');
print(OnTest == OnTest, onTest); // Prints "true, Test"

Event subscriptions

Event subscription is done using the function Event.each and they are emitted using the Event.emit function. This function takes an arbitrary context paramter to pass along the chain and returns it back on completion.

-- Registers a callback to be invoked each time an event is emitted with the given priority.
-- Returns a key that can be used to remove the event later on.
--
uint Event:each(function cb, int? priority)

-- Emits a new event.
void Event:emit(any? context)

We recommend using a table as it can be a powerful primitive to pass information across the chain.

Example/index.ts
const callback = (ctx: EventContextOf<typeof OnTest>) => {
print(ctx.level);
ctx.level += 1;
};
OnTest(callback);
OnTest(callback);
OnTest(callback);

const ctx = { level: 0 };
OnTest.emit(ctx); // Prints 0, 1, 2
print(ctx.level); // Prints 3
tip

As an alternative to Event.each, the shorthand call syntax can be used:

Example/index.ts
OnTest(({ level }) => {
// ...
});

Subscriptions can be removed by returning true from the callback, by externally calling Event.remove with the handle returned from the subscription, or alternatively the entire callback list can be cleared using Event.clear.

-- Removes all subscribers of the event instance.
--
void Event:clear()

-- Given the handle returned from the even subscription, erases the entry, returns false if not found.
--
bool Event:remove(uint handle)
Example/index.ts
OnTest(({ level }) => {
print(`Callback #1 - ${level}`);
});
OnTest(({ level }) => {
print(`Callback #2 - ${level}`);
return true;
});

OnTest.emit({ level: 4 });
// Callback #1 - 4
// Callback #2 - 4
OnTest.emit({ level: 5 });
// Callback #1 - 5
caution

If an exception occurs during the event chain, the subscriber that caused the exception will be removed from the event queue and an error will be printed on the console.

Example/index.ts
OnTest(() => {
print("I'm here!");
});
OnTest(() => {
print('Me too!');
throw 'Exception!';
});

OnTest.emit({ level: 1 });
OnTest.emit({ level: 1 });
/*
Prints:
I'm here!
Me too!
Error while emitting event 'Test': [string "test.ts"]:6: Exception!
I'm here!
*/

Asynchronous subscriptions

Each event also implements PromiseLike<T> and AsyncIterable<T> interfaces meaning they can be used with await and for await expressions. Additionally you can call Event.stream to generate a buffered AsyncIterable<T> that does not miss events even if you were awaiting another expression when an event was triggered.

-- Gets an async iterable subscription to the event.
AsyncIterable<T> Event:stream(int? priority)
Example/index.ts
(async () => {
// Wait for the initialization event.
//
await Event.OnInit;

// Print the name of every player that spawns after initialization.
//
for await (const ent of Event.OnSpawn) {
if (ent.type === Entity.PLAYER) {
print(`Player spawned: ${ent.playerName ?? ent.name}`);
}
}
})();

Event priorities

By default events are registered with the priority 0, however during registration you can specify a an integer value within [-Event.PRIORITY_LEVELS, Event.PRIORITY_LEVELS] to change the priority of your callback. Any numbers outside this range will be clamped.

int Event.PRIORITY_LEVELS
int Event.MAX_PRIORITY -- +Event.PRIORITY_LEVELS
int Event.MIN_PRIORITY -- -Event.PRIORITY_LEVELS
Example/index.ts
OnTest(() => {
print("Function #3");
}, 3);
OnTest(() => {
print("Function #2");
}, 2);
OnTest(() => {
print("Function #1");
}, 1);
OnTest.emit({ level: 0 });

--[[
Prints:
Function #3
Function #2
Function #1
]]
caution

Event callbacks registered at the exact same priority level will be invoked in an undefined order so make sure to not rely on the invocation order within priority levels.