#ifndef EVENTS_H #define EVENTS_H #include "ui.h" // Source id for events/writes originating from C. Container ids are nonzero // (load_container rejects 0), so 0 is free to mean "the engine". #define EVENT_SOURCE_ENGINE 0 // Names under "engine." can only be emitted by C; app.emit rejects them. // Property writes announce themselves as "engine.changed.". #define EVENT_ENGINE_PREFIX "engine." #define EVENT_CHANGED_PREFIX "engine.changed." // Dispatched directly (never queued) once per drain after the queue is // exhausted, so handlers reliably run after every change event of the // frame — the coalescing point for dirty-flag subscribers. Events emitted // by its handlers queue for the next frame. #define EVENT_FRAME "engine.frame" // Max events processed per drain; hitting it means a handler cycle is // endlessly re-emitting, so the remainder is dumped to stderr and dropped. #define EVENT_DRAIN_MAX 1024 typedef enum PropertyTypeEnum { PROPERTY_NUMBER, PROPERTY_STRING, PROPERTY_BOOL, } PropertyType; typedef struct PropertyStruct { char* name; PropertyType type; union { double number; char* string; // owned; NULL until first set bool boolean; } value; } Property; // C subscriber. The event's packed args table is on the Lua stack at // args_index (t[1..t.n]); it must still be there when the handler returns. typedef void (*EventHandler)( void* userdata, const char* event, uint32_t source, lua_State* L, int args_index); typedef struct EventSubscriptionStruct { char* event; // Registry ref of the script this subscription was made from, restored as // the ambient "current script" before invoking a Lua handler — so // ui.create_overlay called from inside it attributes correctly. Not used // for lifecycle: subscriptions currently live for the process, since // nothing unloads a script (only overlays, which are a separate // lifecycle — see events-design memory). int script_env; int lua_ref; // handler function ref, LUA_NOREF for C handlers EventHandler handler; // NULL for Lua handlers void* userdata; // Removal during a drain marks instead of compacting so in-flight // iteration stays valid; compaction happens when the drain finishes uint8_t dead; } EventSubscription; typedef struct QueuedEventStruct { char* name; uint32_t source; int args_ref; // registry ref to the packed args table } QueuedEvent; struct EventBusStruct { lua_State* L; EventSubscription* subs; uint32_t sub_count; uint32_t sub_cap; QueuedEvent* queue; uint32_t queue_count; uint32_t queue_cap; Property* properties; uint32_t property_count; uint32_t property_cap; bool draining; }; // Registers the `app` global (emit/subscribe/get/set) in the Lua state VkResult event_bus_init(EventBus* bus, lua_State* L); // Frees subs/queue/properties. Must run while bus->L is still open - it // luaL_unrefs each subscription's handler and each queued event's args // table out of the registry. void event_bus_destroy(EventBus* bus); // Pops nargs values off the top of the Lua stack into the event's packed // args and appends to the queue. Never dispatches; the queue drains once // per frame from the top level. VkResult event_emit(EventBus* bus, const char* name, uint32_t source, int nargs); // Subscribes a C handler; Lua handlers subscribe via app.subscribe VkResult event_subscribe(EventBus* bus, const char* name, EventHandler handler, void* userdata); // Dispatches queued events in emit order, including events emitted by // handlers during the drain, up to EVENT_DRAIN_MAX void event_bus_drain(EventBus* bus, UIContext* ui, RenderContext* gpu, double delta_time); VkResult event_property_register(EventBus* bus, const char* name, PropertyType type); // Direct read access; NULL if unregistered Property* event_property(EventBus* bus, const char* name); // Setters no-op when the value is unchanged; otherwise they store the value // and emit engine.changed. with the new value as the single argument. // Emission is unconditional on real changes regardless of who wrote. VkResult event_property_set_number(EventBus* bus, const char* name, double value, uint32_t source); VkResult event_property_set_string(EventBus* bus, const char* name, const char* value, uint32_t source); VkResult event_property_set_bool(EventBus* bus, const char* name, bool value, uint32_t source); // Arg accessors for C EventHandler bodies int event_arg_count(lua_State* L, int args_index); double event_arg_number(lua_State* L, int args_index, int i); bool event_arg_bool(lua_State* L, int args_index, int i); // Returned pointer is anchored by the args table; valid until the drain // releases the event const char* event_arg_string(lua_State* L, int args_index, int i); #endif