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

Inter-VM Communication (GE ↔ VE ↔ UI)

Complete guide to cross-VM communication — GE to VE commands, VE to GE callbacks, peer vehicle messaging, UI events, and the electrics data bus.

BeamNG's three Lua contexts (GE, VE, UI) each run in isolation. This guide covers every bridge method available for passing data between them — from sending commands to vehicles, to streaming telemetry to the UI, to coordinating behavior between vehicles.


Architecture Overview

BeamNG.drive uses separate Lua VMs for different contexts:

ContextVMPurposeMain Objects
GEGame EngineGlobal game state, UI, missions, level logicbe, extensions, guihooks
VEVehicle EnginePer-vehicle physics, controllers, powertrainobj, v, electrics
UIChromium CEFHTML/JavaScript interface layerJavaScript event handlers

1. VE → GE: obj:queueGameEngineLua

Send Lua code from a vehicle's VE context to the Game Engine (GE).

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

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() .. ")")

-- Spawn something from vehicle context
obj:queueGameEngineLua("be:createVehicle('pickup', pos, rot, color)")

-- Call a GE extension function with data
local data = {speed = electrics.values.wheelspeed, gear = electrics.values.gear}
obj:queueGameEngineLua("myExtension.onVehicleData(" .. obj:getId() .. ", " .. serialize(data) .. ")")

Use for:

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

2. GE → VE: be:sendToVehicle / veh:queueLuaCommand

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

Primary Method: veh:queueLuaCommand

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

Direct Messaging: be:sendToVehicle

-- GE Context
be:sendToVehicle(vehId, "setTargetSpeed", 30)  -- Tell vehicle to target 30 m/s

Note: The VE must implement onExternalCommand or similar handlers to receive sendToVehicle messages.

Patterns:

-- Send data via JSON encoding
local data = jsonEncode({speed = 50, mode = "eco"})
veh:queueLuaCommand("myMod.receiveData('" .. data .. "')")

-- Trigger GE callback from VE result
veh:queueLuaCommand(string.format(
    "partCondition.initConditions(nil, %d) obj:queueGameEngineLua('myExt.onFinished(%d)')",
    condVal, vehId
))

-- Send to all vehicles
for i = 0, be:getObjectCount() - 1 do
    be:getObject(i):queueLuaCommand("extensions.load('fuelMultiplier')")
end

Use for:

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

3. VE → VE (Peer Vehicles): obj:queueObjectLuaCommand

Vehicles can communicate directly with each other via their unique object IDs.

-- VE Context (Vehicle A)
local targetId = 1234 -- ID of Vehicle B
obj:queueObjectLuaCommand(targetId, "print('Message from Vehicle A')")

-- Send data to another vehicle
obj:queueObjectLuaCommand(targetId, "electrics.values.customSignal = 1")

-- Complex inter-vehicle communication
local message = {
    type = "convoy_follow",
    leaderId = obj:getId(),
    speed = electrics.values.wheelspeed
}
obj:queueObjectLuaCommand(followerId, "convoySystem.receiveCommand(" .. serialize(message) .. ")")

Use for:

  • Convoy/platoon systems
  • Vehicle-to-vehicle signaling
  • Multi-vehicle coordinated behaviors

4. VE → UI: guihooks

The UI runs in a browser context (Chromium/CEF). Use guihooks to trigger events or stream data.

One-Time Events

-- VE Context
-- Trigger a one-time event
guihooks.trigger("VehicleAlert", {message = "Engine Overheating!", severity = "high"})

-- Common UI events
guihooks.trigger("AIStateChange")  -- Refresh AI app state
guihooks.trigger("HydrosUpdate")   -- Update UI with hydraulic states
guihooks.trigger("appMessage", {msg = "Custom message"})

Streaming Data (High Frequency)

-- Stream high-frequency data (use in onGraphicsStep)
guihooks.queueStream("customSpeed", electrics.values.airspeed * 2.237) -- mph
guihooks.queueStream("engineTemp", electrics.values.watertemp)

-- Send all queued streams to UI (called automatically in onGraphicsStep)
guihooks.sendStreams()

-- Immediate stream send (bypass queue)
guihooks.triggerStream("emergency_telemetry", {status = "critical"})

Notifications

-- Display UI notification toast
guihooks.message("Safety Alert!", 5, "safety", "warning")

-- Full notification table
guihooks.trigger("Notification", {
    type = "warning",  -- 'info', 'warning', 'error', 'success'
    title = "Engine Overheat",
    message = "Temperature exceeding safe limits!"
})

Use for:

  • Vehicle alerts and warnings
  • Telemetry dashboards
  • Real-time data visualization
  • Player notifications

5. GE → UI: guihooks (GE Context)

Same API as VE → UI, but called from GE extensions.

-- GE Context
guihooks.trigger("MenuOpenModule", "vehicleselect")

guihooks.trigger("MissionStateChanged", {
    active = true,
    missionId = "delivery_01",
    progress = 0.5
})

-- Request/Response pattern
guihooks.triggerRequest("GetPlayerData", {}, function(data)
    print(dump(data))
end)

See BeamNGGE/references/guihooks.md for complete GE → UI reference.


6. Extension Hooks: Cross-Module Communication

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

VE Extension Hooks

-- In your VE extension (lua/vehicle/extensions/myExt.lua)
local M = {}

function M.onInit()
    -- Your init logic
end

function M.onUpdate(dt)
    -- Called every frame
end

function M.onReset()
    -- Reset state
end

-- Custom hook for inter-extension communication
function M.onCustomEvent(data)
    -- Handle event from other extensions
end

return M

Triggering Hooks (Both GE and VE)

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

-- VE-only: Check if hook exists
if extensions.hookExists("onMyHook") then
    extensions.hook("onMyHook", data)
end

Available VE Lifecycle Hooks

HookTimingUse For
onInitExtension loadOne-time setup
onUpdate(dt)Every frameContinuous updates
onReset()Vehicle resetClean/reset state
onDespawn()Vehicle removedFinal cleanup

See extensions.md for VE extension system details.


7. The electrics Bus (VE Internal Shared Memory)

Within the same vehicle VM, the electrics module acts as a global blackboard. Use it to share data between controllers and extensions without direct coupling.

-- Controller A sets a value
electrics.values.myMod_value = 1.0

-- Extension B reads it
local val = electrics.values.myMod_value

-- Standard electrics values
electrics.values.rpm = 3000
electrics.values.wheelspeed = 25.5
electrics.values.lights = 1

Best Practice: Namespace your custom electrics: myMod_value not value


Communication Matrix

FromToMethodSyntax
VEGEC++ Bridgeobj:queueGameEngineLua("code")
GEVEC++ Bridgeveh:queueLuaCommand("code")
GEVEDirect Messagebe:sendToVehicle(id, ...)
VEVEPeer-to-Peerobj:queueObjectLuaCommand(id, "code")
VEUIEventguihooks.trigger("event", data)
VEUIStreamguihooks.queueStream("key", value)
GEUIEventguihooks.trigger("event", data)
VEVE (Internal)Data Buselectrics.values.key = value
GEGEExtension Hookextensions.hook("event", ...)
VEVEExtension Hookextensions.hook("event", ...)

Bidirectional Patterns

Pattern: GE ↔ Vehicle State Sync

-- VE Extension (lua/vehicle/extensions/stateReporter.lua)
local M = {}

function M.onUpdate(dt)
    -- Report state to GE periodically
    if dt > 1.0 then  -- Throttle to 1Hz
        local state = {
            rpm = electrics.values.rpm,
            speed = electrics.values.wheelspeed
        }
        obj:queueGameEngineLua("vehicleStateExt.update(" .. obj:getId() .. ", " .. jsonEncode(state) .. ")")
    end
end

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

function M.update(vehID, state)
    M.vehicleData[vehID] = state
    -- Notify UI
    guihooks.trigger("VehicleStateUpdated", {id = vehID, state = state})
end

function M.setVehicleConfig(vehID, config)
    local veh = be:getObjectByID(vehID)
    if veh then
        veh:queueLuaCommand("extensions.load('" .. config.extension .. "')")
    end
end

return M

Pattern: UI ↔ Vehicle Control Bridge

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

-- Called from UI via guihooks
guihooks.trigger("SetVehicleLights", {vehId = 123, state = 1})

function M.onSetVehicleLights(data)
    local veh = be:getObjectByID(data.vehId)
    if veh then
        veh:queueLuaCommand("electrics.setLightsState(" .. data.state .. ")")
    end
end

return M

Best Practices

1. Always Validate Objects

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

-- VE → GE (obj always valid in VE context)
obj:queueGameEngineLua("...")  -- Safe

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 .. "')")

-- In VE sending to GE
local result = {rpm = electrics.values.rpm}
obj:queueGameEngineLua("myGEExt.onData(" .. obj:getId() .. ", '" .. jsonEncode(result) .. "')")

5. Sync UI After State Changes (No Ghost Values!)

-- VE: After modifying state, notify UI
myMod.state.active = true
guihooks.trigger("MyModStateChanged", myMod.state)

-- GE: Same pattern
M.updateState = function(newState)
    M.state = newState
    guihooks.trigger("MyGEStateChanged", M.state)
end

6. Throttle High-Frequency Communication

-- Don't send every physics frame
local timer = 0
function M.onUpdate(dt)
    timer = timer + dt
    if timer > 0.1 then  -- 10Hz max
        timer = 0
        obj:queueGameEngineLua("...")
    end
end

See Also

  • guihooks.md - Complete UI communication reference (VE)
  • obj.md - obj binding including queue methods
  • extensions.md - VE extension system
  • electrics.md - Internal data bus
  • BeamNGGE/references/communication.md - GE-side communication
  • BeamNGGE/references/guihooks.md - GE UI communication
  • BeamNGGE/references/be.md - be engine binding
  • BeamNGGE/references/extensions.md - GE extension system

How Mods Are Loaded

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

Vehicle Boot Sequence & Lifecycle

The exact order in which vehicle systems initialize — from JBeam compilation to physics loops. Essential for timing your VE code correctly.

On this page

Architecture Overview1. VE → GE: obj:queueGameEngineLua2. GE → VE: be:sendToVehicle / veh:queueLuaCommandPrimary Method: veh:queueLuaCommandDirect Messaging: be:sendToVehicle3. VE → VE (Peer Vehicles): obj:queueObjectLuaCommand4. VE → UI: guihooksOne-Time EventsStreaming Data (High Frequency)Notifications5. GE → UI: guihooks (GE Context)6. Extension Hooks: Cross-Module CommunicationVE Extension HooksTriggering Hooks (Both GE and VE)Available VE Lifecycle Hooks7. The electrics Bus (VE Internal Shared Memory)Communication MatrixBidirectional PatternsPattern: GE ↔ Vehicle State SyncPattern: UI ↔ Vehicle Control BridgeBest Practices1. Always Validate Objects2. Keep Commands Simple3. Use Hooks for Cross-Extension Comms4. Escape Data Properly5. Sync UI After State Changes (No Ghost Values!)6. Throttle High-Frequency CommunicationSee Also