Events¶
Doriax events use FunctionSubscribe<Ret(Args...)>, a multicast delegate that stores
callbacks with string tags. Adding a callback with an existing tag replaces the
previous one, which prevents duplicate subscriptions from the same source.
Events work identically in C++ and Lua. C++ uses macros from FunctionSubscribe.h.
Lua uses RegisterEvent and RegisterEngineEvent globals registered by LuaBinding.
How dispatch works¶
flowchart LR
Platform[Platform input / frame tick]
Engine[Engine.systemTouchStart / systemUpdate]
FS[FunctionSubscribe.call]
CPP[C++ callback]
Lua[Lua closure]
Platform --> Engine --> FS
FS --> CPP
FS --> Lua
- The platform layer calls
Engine::systemUpdate(),systemTouchStart(), etc. - Engine forwards to static
FunctionSubscribemembers (onUpdate.call(), etc.). - Subsystems and components fire their own events during simulation (physics contacts, UI pointer events, action start/stop).
- Each subscriber runs in registration order unless disabled or removed.
FunctionSubscribe API¶
Defined in engine/core/util/FunctionSubscribe.h.
| Method | Purpose |
|---|---|
add(tag, callback) |
Register a callback; replaces existing tag |
add(tag, lua_State*) |
Register a Lua function with tag |
remove(tag) |
Remove one callback by exact tag |
removeByTagSubstring(substr) |
Remove all callbacks whose tag contains substring |
clear() |
Remove all callbacks |
call(args...) / operator() |
Invoke all enabled callbacks |
setEnabled(bool) |
Enable or disable the entire event without removing subscribers |
With DORIAX_CRASH_GUARD enabled, failing subscribers are removed automatically and
optionally reported through a global crash handler.
Engine events¶
Engine exposes frame, lifecycle, input, and pointer events.
| Event | Signature | Use |
|---|---|---|
onInit |
void() |
User init via DORIAX_INIT macro (access through getOnInit()) |
onViewLoaded |
void() |
View/surface ready |
onViewChanged |
void() |
Canvas/view changed; Lua name: onCanvasChanged |
onViewDestroyed |
void() |
View destroyed |
onDraw |
void() |
Draw callback (after scene draw) |
onUpdate |
void() |
Variable-step gameplay update |
onFixedUpdate |
void() |
Fixed-step simulation update |
onPostUpdate |
void() |
Work after regular update |
onPause / onResume |
void() |
App/game pause and resume |
onShutdown |
void() |
Runtime shutdown |
onTouchStart/End/Move |
void(int, float, float) |
Touch pointer index, x, y |
onTouchCancel |
void() |
Touch sequence canceled |
onMouseDown/Up |
void(int, float, float, int) |
Button, x, y, modifiers |
onMouseMove/Scroll |
void(float, float, int) |
Position or scroll offsets plus modifiers |
onMouseEnter/Leave |
void() |
Pointer enters/leaves view |
onKeyDown/Up |
void(int, bool, int) |
Key, repeat flag, modifiers |
onCharInput |
void(wchar_t) |
Text input codepoint |
Input routing options¶
Configure how events reach gameplay vs. UI:
| Engine setting | Effect |
|---|---|
setIgnoreEventsHandledByUI(true) |
Gameplay handlers skip events already consumed by UI |
setAllowEventsOutCanvas(false) |
Clip events to the logical canvas |
pauseGameEvents(true) |
Pause game event delivery (editor pause, etc.) |
setCallMouseInTouchEvent / setCallTouchInMouseEvent |
Mirror mouse/touch for cross-platform testing |
C++ registration macros¶
All macros live in FunctionSubscribe.h and generate a unique tag:
typeid(ClassName)_objectAddress_methodName.
Engine events¶
Any FunctionSubscribe member¶
auto* physics = scene->getPhysicsSystem();
REGISTER_EVENT(physics->beginContact2D, onBeginContact);
UNREGISTER_EVENT(physics->beginContact2D, onBeginContact);
Component events on the same entity¶
REGISTER_COMPONENT_EVENT(UIComponent, onClick, onClick);
REGISTER_UI_EVENT(onClick, onClick); // shorthand for UIComponent
REGISTER_BUTTON_EVENT(onPress, onPress); // ButtonComponent
REGISTER_SCROLLBAR_EVENT(onChange, onChange); // ScrollbarComponent
REGISTER_PANEL_EVENT(onMove, onMove); // PanelComponent
Matching UNREGISTER_* macros remove subscriptions in destructors.
Example: full C++ script¶
class DoorTrigger : public doriax::ScriptBase {
public:
DoorTrigger(doriax::Scene* scene, doriax::Entity entity);
~DoorTrigger();
void onUpdate();
void onClick(float x, float y);
void onBeginContact(doriax::Body2D bodyA, int shapeA, doriax::Body2D bodyB, int shapeB);
private:
doriax::PhysicsSystem* physics = nullptr;
};
DoorTrigger::DoorTrigger(Scene* scene, Entity entity) : ScriptBase(scene, entity) {
physics = scene->getPhysicsSystem();
REGISTER_ENGINE_EVENT(onUpdate);
REGISTER_UI_EVENT(onClick, onClick);
if (physics) {
REGISTER_EVENT(physics->beginContact2D, onBeginContact);
}
}
DoorTrigger::~DoorTrigger() {
UNREGISTER_ENGINE_EVENT(onUpdate);
UNREGISTER_UI_EVENT(onClick, onClick);
if (physics) {
UNREGISTER_EVENT(physics->beginContact2D, onBeginContact);
}
}
Lua event registration¶
LuaBinding::registerHelpersFunctions exposes two globals:
| Function | Arguments | Behavior |
|---|---|---|
RegisterEvent(self, event, methodName [, tag]) |
Script table, event object, method name | Subscribes self:methodName(...) to any exposed FunctionSubscribe |
RegisterEngineEvent(self, methodName [, tag]) |
Script table, engine event name | Looks up Engine.<methodName> and subscribes |
If tag is omitted, Doriax builds:
ClassName_instancePointer_methodName
Engine event (Lua)¶
function Player:init()
RegisterEngineEvent(self, "onUpdate")
end
function Player:onUpdate()
-- called every frame
end
Component event (Lua)¶
function Trigger:init()
local ui = self.scene:findComponent(UIComponent, self.entity)
RegisterEvent(self, ui.onClick, "onClick")
end
function Trigger:onClick(x, y)
print("clicked", x, y)
end
Physics event (Lua)¶
function Trap:init()
local physics = self.scene:getPhysicsSystem()
RegisterEvent(self, physics.beginContact2D, "onBeginContact")
end
function Trap:onBeginContact(bodyA, shapeA, bodyB, shapeB)
print("2D contact")
end
Custom tag¶
Use custom tags when you need to replace or manually remove a specific subscription.
Physics events¶
PhysicsSystem exposes 2D (Box2D) and 3D (Jolt) collision events.
2D events¶
| Event | Signature | Meaning |
|---|---|---|
beginContact2D |
void(Body2D, int, Body2D, int) |
Contact begins |
endContact2D |
void(Body2D, int, Body2D, int) |
Contact ends |
hitContact2D |
void(Body2D, int, Body2D, int, Vector2, Vector2, float) |
Impact with hit data |
beginSensorContact2D |
void(Body2D, int, Body2D, int) |
Sensor overlap begins |
endSensorContact2D |
void(Body2D, int, Body2D, int) |
Sensor overlap ends |
preSolve2D |
bool(Body2D, int, Body2D, int) |
Return false to disable contact before solve |
shouldCollide2D |
bool(Body2D, int, Body2D, int) |
Filter pairs before collision |
3D events¶
| Event | Meaning |
|---|---|
onBodyActivated3D / onBodyDeactivated3D |
Body activation state changed |
onContactAdded3D / onContactPersisted3D / onContactRemoved3D |
3D contact lifecycle |
shouldCollide3D |
Filter whether two 3D bodies/shapes should collide |
UI component events¶
UIComponent events (also available through REGISTER_UI_EVENT):
| Event | Signature | When |
|---|---|---|
onGetFocus / onLostFocus |
void() |
Focus changes |
onPointerEnter / onPointerLeave |
void(float, float) |
Pointer enters/leaves widget bounds |
onPointerMove |
void(float, float) |
Pointer moves over widget |
onPointerDown / onPointerUp |
void(float, float) |
Pointer button down/up |
onClick / onDoubleClick |
void(float, float) |
Click gestures |
onDragStart / onDrag / onDragEnd |
void(float, float) |
Drag gestures |
Widget-specific events¶
| Component | Events |
|---|---|
ButtonComponent |
onPress, onRelease |
ScrollbarComponent |
onChange |
PanelComponent |
onMove, onResize |
TextEditComponent |
onChange, onSubmit |
Action and sound events¶
| Component | Events |
|---|---|
ActionComponent |
onStart, onStop, onPause, onStep |
SoundComponent |
onStart, onStop, onPause |
Use these to chain gameplay reactions to tweens, timelines, or audio playback.
Easing as an event¶
Ease extends FunctionSubscribe<float(float)>. Timed actions can use custom easing
curves by subscribing to an Ease instance.
Cleanup¶
| Context | Cleanup mechanism |
|---|---|
| C++ script destructor | Call matching UNREGISTER_* macros |
| Lua script unload | cleanupLuaScripts removes tags containing instance pointer |
| Scene unload | Scene::removeSubscriptionsByTag |
| Full reset | Engine::clearAllSubscriptions() |
Always unregister in C++ destructors when the object can outlive a scene reload.
Best practices¶
- Register in a constructor or Lua
init(). Never register in hot per-frame code. - Unregister in destructors (C++) or rely on Lua cleanup for script components.
- Use
onFixedUpdatefor physics-sensitive simulation, notonUpdate. - Enable
setIgnoreEventsHandledByUI(true)when UI and gameplay share screen space. - Return
falsefrompreSolve2DorshouldCollide*filters to implement one-way platforms, team filters, or trigger volumes.
Reference¶
- Engine — engine events (
onUpdate, input, lifecycle) - PhysicsSystem — collision and contact events
- Script workflow: Creating Scripts