RLS Studios
ProjectsPatreonCommunityDocsAbout
Join Patreon
BeamNG Modding Docs

Guides

GE Extension SystemGE Architecture Quick ReferenceGE Boot SequenceGE Cross-VM CommunicationHow Mods Are LoadedInter-VM Communication (GE ↔ VE ↔ UI)Vehicle Boot Sequence & Lifecycle

Reference

UI

Resources

BeamNG Game Engine Lua Cheat SheetGE Developer RecipesMCP Server Setup

// RLS.STUDIOS=true

Premium Mods for BeamNG.drive. Career systems, custom vehicles, and immersive gameplay experiences.

Index

HomeProjectsPatreon

Socials

DiscordPatreon (RLS)Patreon (Vehicles)

© 2026 RLS Studios. All rights reserved.

Modding since 2024

GuidesArchitecture

GE Cross-VM Communication

How GE communicates with Vehicle Engines and the UI — bridge methods, hooks, and patterns for cross-context data flow.

BeamNG runs separate Lua VMs for the game engine and each vehicle. They can't share variables directly — all communication happens through bridge methods. Getting this right is essential for any mod that needs to read vehicle data, control vehicles from gameplay code, or update the UI.


Communication Overview

BeamNG.drive uses separate Lua VMs for different contexts:

ContextPurposeMain Objects
GEGlobal game state, UI, missions, level logicbe, extensions, guihooks
VEPer-vehicle physics, controllers, powertrainobj, v, electrics
UIHTML/JavaScript interfaceChromium (CEF) layer

1. GE to Vehicle (VE): be Methods

veh:queueLuaCommand(code) - Primary Method

Send Lua code from GE to a specific vehicle's VE context.

-- GE Context
local veh = be:getObjectByID(vehId)
if veh then
    veh:queueLuaCommand("electrics.values.hazard = 1")
end

Use for:

  • Triggering vehicle functions from gameplay/missions
  • Setting electrics values remotely
  • Activating vehicle features from UI callbacks

be:queueAllObjectLua(code) - Broadcast to All Vehicles

Send Lua code to all vehicles' VE contexts at once.

-- GE Context
be:queueAllObjectLua("obj:setGravity(-9.81)")

2. Vehicle (VE) to GE: obj:queueGameEngineLua

From within a vehicle's Lua context, execute code in the GE context.

-- VE Context
obj:queueGameEngineLua("core_gamestate.setVehicleSpecificName(" .. obj:getId() .. ", 'Hero Car')")

Common patterns:

-- Notify GE of vehicle state change
obj:queueGameEngineLua("extensions.hook('onVehicleCustomEvent', " .. obj:getId() .. ")")

-- Update mission progress
obj:queueGameEngineLua("gameplay_missions.onVehicleCheckpoint(" .. obj:getId() .. ")")

-- Trigger a GE extension hook from vehicle context
obj:queueGameEngineLua("extensions.hook('onMyVehicleEvent', " .. obj:getId() .. ")")

Use for:

  • Reporting vehicle events to gameplay systems
  • Triggering level/mission logic from vehicle actions
  • Requesting GE-level operations (spawning, scene changes)

3. GE to UI: guihooks

Bridge between GE Lua and the HTML/JavaScript UI layer.

Event Triggering

-- Send one-way event to UI
guihooks.trigger("MenuOpenModule", "vehicleselect")

-- Send with data payload
guihooks.trigger("MissionStateChanged", {
    active = true,
    missionId = "delivery_01",
    progress = 0.5
})

Streaming Data to UI

-- High-frequency data via streams (batched, called in onUpdate)
guihooks.queueStream('vehicleSpeed', speedData)
-- Streams are flushed automatically in main.lua render loop

-- Immediate stream send
guihooks.triggerStream('missionData', data)

-- Simple message popup
guihooks.message("Hello!", 5, "info")

Common UI Events

EventDataPurpose
MenuOpenModulemoduleName (string)Open a UI module
MenuHidenilHide menu
ChangeState{state = 'menu'}Change game state
Notification{type, title, message}Show toast notification
ScenarioRewindednilReset scenario UI state
UpdateMissionProgress{id, progress}Update mission UI

4. Extension Hooks: Cross-Module Communication

Extensions communicate via the hook system. Hooks chain across all loaded extensions.

Triggering Hooks

-- Call a hook on all loaded extensions
extensions.hook("onResetGameplay", playerID)
extensions.hook("onVehicleSpawned", vehID)
extensions.hook("onCustomEvent", data)

Handling Hooks

-- In your GE extension (lua/ge/extensions/myExtension.lua)
local M = {}

local function onVehicleSpawned(vehID)
    -- React to vehicle spawn
end

local function onResetGameplay(playerID)
    -- Clean up state
end

local function onMissionStarted(missionId)
    -- Other extensions can listen for this
end

M.onVehicleSpawned = onVehicleSpawned
M.onResetGameplay = onResetGameplay
M.onMissionStarted = onMissionStarted

return M

Available GE Lifecycle Hooks

HookTimingUse For
onExtensionLoadedExtension loadSetup, registration
onClientPostStartMission(levelPath)Level objects availableLevel-specific init
onWorldReadyState(state)World ready (2=loaded)Post-spawn setup
onUpdate(dtReal, dtSim, dtRaw)Every frameGame logic, timers
onPreRender(dtReal, dtSim, dtRaw)Every frame (after physics)Visual updates
onVehicleSpawned(vid, vehObj)Vehicle addedTrack vehicles
onVehicleDestroyed(vid)Vehicle removedCleanup
onResetGameplay(playerID)Gameplay resetClean state
onClientEndMission(levelPath)Leaving levelLevel cleanup

5. Bidirectional Patterns

Pattern: GE ↔ Vehicle State Sync

-- GE Extension (lua/ge/extensions/vehicleManager.lua)
local M = {}
local vehicleData = {}

local function onVehicleSpawned(vehID)
    vehicleData[vehID] = { spawnTime = os.time() }

    local veh = be:getObjectByID(vehID)
    if veh then
        veh:queueLuaCommand("extensions.load('customController')")
    end
end

local function updateVehicleState(vehID, state)
    if vehicleData[vehID] then
        vehicleData[vehID].state = state
        guihooks.trigger("VehicleStateUpdated", {id = vehID, state = state})
    end
end

M.onVehicleSpawned = onVehicleSpawned
M.updateVehicleState = updateVehicleState

return M

Pattern: UI ↔ Vehicle Control

-- GE Extension bridging UI and Vehicle
local M = {}

local function setVehicleLights(vehID, state)
    local veh = be:getObjectByID(vehID)
    if veh then
        veh:queueLuaCommand("electrics.setLightsState(" .. state .. ")")
    end
end

local function getVehicleInfo(vehID)
    local veh = be:getObjectByID(vehID)
    if not veh then return nil end

    return {
        id = vehID,
        position = veh:getPosition(),
        name = veh:getJBeamFilename()
    }
end

M.setVehicleLights = setVehicleLights
M.getVehicleInfo = getVehicleInfo

return M

6. The electrics Bus (VE Internal)

While GE cannot directly access VE's electrics, it can read/write via commands:

-- GE reading vehicle state (via command)
local veh = be:getObjectByID(vehID)
if veh then
    -- This sets a value; reading requires callback pattern
    veh:queueLuaCommand([[
        obj:queueGameEngineLua("myExtension.onElectricsUpdate(" .. obj:getId() .. ", " .. (electrics.values.rpm or 0) .. ")")
    ]])
end

Communication Matrix

FromToMethodSyntax
GEVEC++ Bridgeveh:queueLuaCommand("code")
GEAll VEsBroadcastbe:queueAllObjectLua("code")
VEGEC++ Bridgeobj:queueGameEngineLua("code")
GEUIEventguihooks.trigger("event", data)
GEUIStreamguihooks.queueStream("key", data)
GEGEExtension Hookextensions.hook("event", ...)

Best Practices

1. Always Validate Objects

local veh = be:getObjectByID(vehID)
if veh then  -- Critical check!
    veh:queueLuaCommand("...")
end

2. Keep Commands Simple

-- GOOD: Simple, single-purpose command
veh:queueLuaCommand("electrics.values.hazard = 1")

-- AVOID: Complex logic in strings
-- veh:queueLuaCommand("if electrics.values.speed > 10 then ... end")

3. Use Hooks for Cross-Extension Comms

-- GOOD: Hook pattern
extensions.hook("onMyCustomEvent", data)

-- AVOID: Direct calls that may break if extension unloaded
-- myOtherExtension.func(data)

4. Escape Data Properly

-- Use jsonEncode for complex data
local data = jsonEncode({speed = 50, gear = "D"})
veh:queueLuaCommand("myController.receiveData('" .. data .. "')")

5. Sync UI After State Changes

-- No ghost values rule
myMod.state.active = true
myMod.saveState()
guihooks.trigger("MyModStateChanged", myMod.state)

See Also

  • UI Bridge - Lua to UI communication details
  • GUI Hooks - UI event reference
  • Vehicle Control - Vehicle operations
  • Architecture - Extension system reference

GE Boot Sequence

Complete documentation of the GE boot sequence — when extensions load, hook execution order, and how to initialize correctly.

How Mods Are Loaded

How BeamNG discovers, mounts, and activates mods — ZIP structure, virtual filesystem overlay, and the mod manager API.

On this page

Communication Overview1. GE to Vehicle (VE): be Methodsveh:queueLuaCommand(code) - Primary Methodbe:queueAllObjectLua(code) - Broadcast to All Vehicles2. Vehicle (VE) to GE: obj:queueGameEngineLua3. GE to UI: guihooksEvent TriggeringStreaming Data to UICommon UI Events4. Extension Hooks: Cross-Module CommunicationTriggering HooksHandling HooksAvailable GE Lifecycle Hooks5. Bidirectional PatternsPattern: GE ↔ Vehicle State SyncPattern: UI ↔ Vehicle Control6. The electrics Bus (VE Internal)Communication MatrixBest Practices1. Always Validate Objects2. Keep Commands Simple3. Use Hooks for Cross-Extension Comms4. Escape Data Properly5. Sync UI After State ChangesSee Also