Lua ↔ UI Bridge
How Lua communicates with the HTML/JavaScript UI layer — events, streams, engine calls, toast messages, and the fade screen system.
The UI bridge connects your Lua code to BeamNG's Chromium-based UI layer. Understanding this bridge is essential for any mod that displays information to the player or responds to UI interactions.
Architecture Overview
┌──────────────┐ guihooks.trigger() ┌──────────────────┐
│ GE Lua │ ──────────────────────► │ CEF (Vue/Angular) │
│ (main VM) │ be:queueHookJS() │ Browser UI │
│ │ ◄───────────────────── │ │
│ │ bngApi.engineLua() │ │
└──────────────┘ └──────────────────────┘
▲ ▲
│ obj:queueGameEngineLua() │
│ │
┌──────────────┐ guihooks.trigger() │
│ VE Lua │ ──────────────────────► │
│ (per-vehicle)│ obj:queueHookJS() │
└──────────────┘ Lua → UI: Events (guihooks.trigger)
One-shot events. The UI receives them as JavaScript hooks.
-- GE or VE: Send event to UI
guihooks.trigger('MyEventName', data1, data2)How it works (from guihooks.lua:29-40):
- Arguments are JSON-encoded via
jsonEncodeWorkBuffer - Passed to C++
be:queueHookJS()(GE) orobj:queueHookJS()(VE) - CEF dispatches to registered JavaScript listeners
UI side (Vue):
// Register a hook listener
bngApi.engineLua('extensions.hook("onSomething")') // Call Lua from JS
// Listen for Lua events
$scope.$on('MyEventName', function(event, data1, data2) {
// Handle the event
})Targeted Events
-- Send to a specific UI window/DSM target
guihooks.triggerClient(targetDsmId, 'EventName', data)Raw JS Events
-- Send pre-formatted JSON (avoids encoding overhead)
guihooks.triggerRawJS('EventName', '{"key":"value"}')Lua → UI: Streams (guihooks.queueStream)
High-frequency data channels. More efficient than trigger() for per-frame data.
-- Queue stream data (batched and sent once per frame)
guihooks.queueStream('streamName', dataTable)How streams work (from guihooks.lua:103-118):
- Data is cached in a table
- All cached streams are sent in one batch via
guihooks.sendStreams()(called from main.lua's render loop) - More efficient than individual triggers for high-frequency data
GE vs VE behavior:
- VE: Data cached, sent during
sendStreams()in the vehicle's graphics step - GE: Sets
M.updateStreams = true, data cached, sent when main.lua callsguihooks.sendStreams()
-- Stream example: Send vehicle speed every frame
local streamData = {speed = 0} -- Reuse table!
local function onUpdate(dt)
streamData.speed = getCurrentSpeed()
guihooks.queueStream('myModSpeed', streamData)
endUI → Lua: Engine Calls
JavaScript calls Lua functions via the bngApi:
// Execute arbitrary GE Lua code
bngApi.engineLua('extensions.hook("myHook", ' + bngApi.serializeToLua(data) + ')')
// Call a specific extension function
bngApi.engineLua('mymod_myfeature.doSomething()')UI Toast Messages
-- Simple message popup (guihooks.lua:127)
guihooks.message("Hello World!", 5, "info", "icon_name")
-- Args: message, ttl (seconds), category, iconFor toast-style notifications:
guihooks.trigger("toastrMsg", {
type = "info", -- "info", "warning", "error", "success"
title = "Title",
msg = "Message body",
config = {
closeButton = true,
timeOut = 5000, -- ms, 0 = persistent
extendedTimeOut = 0
}
})UI Graph App
Built-in graph visualization for debugging:
-- guihooks.lua:138-152
-- Each arg: {key, value, scale, unit, renderNegatives, color}
guihooks.graph(
{"RPM", rpm, 8000, "rpm"},
{"Throttle", throttle, 1, "%"},
{"Speed", speed, 200, "km/h"}
)Loading Screen / Fade Screen
The fade screen is controlled via UI triggers:
-- From ui/fadeScreen.lua
-- Fade to black:
ui_fadeScreen.fadeToBlack(fadeTime, screenData, args)
-- screenData = {image, title, text} shown during black
-- Fade from black:
ui_fadeScreen.fadeFromBlack(fadeTime, args)
-- Full cycle (fade in → pause → fade out):
ui_fadeScreen.fadeSequence(fadeIn, pause, fadeOut, screenData, args)onScreenFadeState hook receives state changes:
- 1 = Started fading to black
- 2 = Fully black (screen is hidden - safe to do visual work like teleporting, swapping vehicles)
- 3 = Started fading from black
local function onScreenFadeState(state)
if state == 2 then
-- Screen is fully black, do visual changes here
teleportVehicle()
end
end
M.onScreenFadeState = onScreenFadeStatePerformance Notes
guihooks.trigger()JSON-encodes all arguments - avoid large tablesguihooks.queueStream()is more efficient for per-frame data (batched)- Reuse data tables passed to
queueStream(see Performance Patterns doc) - The
tmpTabin guihooks.lua is reused and cleared after each trigger call
See Also
- GUI Hooks - Full guihooks reference
- UI Apps - Creating HUD widgets
- Cross-VM Comms - Full communication patterns
- Performance - Avoiding per-frame allocation
Creating HUD Apps
How to create UI apps (HUD widgets) for BeamNG.drive — directory structure, AngularJS directives, receiving Lua data, and styling.
GUI Hooks Reference
Complete reference for guihooks — sending events to the UI, toast notifications, streaming data, state changes, and common UI event patterns.