How event.register_handler Callbacks Process Menu and Game Events in YimMenuV2

TLDR: event.register_handler stores Lua function references in a global g_eventHandlers map inside src/game/scripting/libraries/Event.cpp, which the main loop iterates each frame via LuaManager::Update() to dispatch queued game and menu events to registered callbacks.

YimMenuV2 exposes a lightweight Lua scripting layer that allows modders to react to in-game actions and UI interactions through a unified event system. Understanding how event.register_handler callbacks process these events requires examining the C++ implementation in the scripting libraries, the global handler registry, and the main update loop that drives execution.

Event Registration Architecture

The Handler Registry in Event.cpp

At the core of the system is src/game/scripting/libraries/Event.cpp, which implements the RegisterHandler C-function exposed to Lua as event.register_handler. When a script invokes this function, the implementation extracts the event name string and a sol::function reference, storing them in a global std::unordered_map<std::string, std::vector<sol::function>> named g_eventHandlers.

This design allows multiple callbacks for a single event type. The map key is the event identifier (e.g., "entity.damage" or "menu.select"), while the value is a vector of Lua function references maintained alive by the Sol2 binding library.

Lua-to-C++ Binding

The registration process marshals Lua arguments into C++ using Sol2’s abstraction layer. The RegisterHandler function validates the event name, checks for duplicates if necessary, and pushes the callback into the vector associated with that event key. Because the script engine holds these references, the Lua callbacks remain valid until explicitly unregistered or the script is unloaded.

Event Dispatch Pipeline

Game Loop Integration

Event dispatch occurs on the main game thread inside src/core/scripting/LuaManager.cpp. Each tick, the LuaManager::Update() method invokes Event::ProcessPending(), which iterates over a thread-local queue called g_pendingEvents. This queue contains events triggered by native game code or UI actions during the current frame.

For each queued event, the system looks up g_eventHandlers[eventName] and invokes every stored sol::function sequentially. Arguments are passed using sol::variadic_args, allowing callbacks to receive variable-length parameter lists (e.g., victim ID, attacker ID, and damage amount for combat events).

The UI layer in src/game/frontend/Menu.cpp utilizes the same dispatch mechanism. When a user interacts with a widget (e.g., pressing a button or changing a dropdown), the menu code calls event.trigger("menu.buttonPress", buttonId) or similar. These calls enqueue the event into g_pendingEvents, ensuring that menu actions and game events share a consistent processing pipeline.

Because both event types flow through Event::ProcessPending(), scripts can register handlers for game events like "player.join" alongside menu events like "menu.tabChange" without distinguishing between the two at the registration level.

Callback Lifecycle and Thread Safety

Unregistering Handlers

Scripts can remove callbacks via event.unregister_handler (implemented as the UnregisterHandler C-function in Event.cpp). This function searches g_eventHandlers for the specific event name, locates the matching sol::function reference in the vector, and erases it. Once removed, the Sol2 reference is released, allowing Lua’s garbage collector to reclaim the function if no other references exist.

Thread-Safety Guarantees

All handler registration and dispatch operations occur synchronously on the main game thread. The g_eventHandlers map and g_pendingEvents queue are not protected by mutexes because LuaManager::Update() runs sequentially with the game logic, eliminating race conditions between script callbacks and game state modifications.

Practical Code Examples

Registering a Game Event Handler

The following Lua snippet registers a callback for player damage events, allowing the script to modify or log combat data:

-- Register callback for entity damage events
event.register_handler("entity.damage", function(victim, attacker, damage)
    print(string.format("Entity %d took %f damage from %d", victim, damage, attacker))
    -- Apply custom logic, e.g., damage multipliers or alerts
end)

Handling Menu Interactions

To execute code when a specific menu button is pressed, register a handler for the menu event namespace:

-- React to menu button presses
event.register_handler("menu.buttonPress", function(buttonId)
    if buttonId == "godmodeToggle" then
        -- Toggle god mode logic here
        print("God mode toggled via menu")
    end
end)

Triggering Events from C++

Native code can fire events that Lua scripts receive by pushing to the pending queue. This example from src/game/scripting/libraries/Event.cpp demonstrates the internal trigger mechanism:

// Called by game systems or UI code to queue an event
void TriggerGameEvent(const std::string& name, sol::variadic_args args) {
    // Pack arguments and push to the queue for processing next frame
    g_pendingEvents.emplace_back(name, args);
}

Unregistering a Callback

To prevent memory leaks or stop processing specific events, remove the handler when no longer needed:

local myCallback = function(...) print("Event fired") end
event.register_handler("custom.event", myCallback)

-- Later cleanup
event.unregister_handler("custom.event", myCallback)

Summary

  • Centralized Registry: event.register_handler stores callbacks in a global g_eventHandlers map defined in src/game/scripting/libraries/Event.cpp, keyed by event name.
  • Unified Dispatch: Both game events (e.g., "entity.damage") and menu events (e.g., "menu.select") flow through the same g_pendingEvents queue processed by Event::ProcessPending().
  • Main Thread Execution: LuaManager::Update() in src/core/scripting/LuaManager.cpp drives event dispatch on the main game thread, ensuring thread-safe access to game state.
  • Lifecycle Management: Handlers persist until explicitly removed via event.unregister_handler, which releases the underlying sol::function reference.

Frequently Asked Questions

How do I unregister an event handler in YimMenuV2?

Call event.unregister_handler(eventName, callbackFunction) with the same function reference used during registration. The UnregisterHandler implementation in src/game/scripting/libraries/Event.cpp searches the g_eventHandlers map, removes the matching sol::function from the vector, and releases the reference to prevent memory leaks.

Are event handlers thread-safe?

Yes, because all event operations execute on the main game thread. The LuaManager::Update() method processes the g_pendingEvents queue synchronously during the frame update, eliminating the need for locks or mutexes when accessing the handler registry or game state from within callbacks.

Can Lua scripts trigger custom events that other scripts receive?

Yes. Scripts can call event.trigger(eventName, ...) (exposed through bindings in src/core/scripting/libraries/Notify.cpp) to enqueue custom events. These events populate g_pendingEvents and are dispatched to all registered handlers during the next Event::ProcessPending() cycle, allowing cross-script communication.

What is the difference between game events and menu events in the handler system?

Technically, there is no architectural difference. Game events originate from native game code (e.g., player damage, vehicle spawns), while menu events originate from src/game/frontend/Menu.cpp (e.g., button presses). Both use the same event.trigger mechanism and g_eventHandlers registry, allowing scripts to handle UI and gameplay logic through identical callback patterns.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →