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.
Event objects supports comparison and string conversion metamethods.
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.
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
As an alternative to Event.each
, the shorthand call syntax can be used:
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)
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
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.
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)
(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
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
]]
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.