Virtual Machines

Introduction

A virtual machine is each of the environments in which code can be executed. There’s 3 types of virtual machines:

  • Vehicle Lua VM (VLUA): runs code specific to a vehicle (such as turbo boost calculations or AI steering inputs).
  • GameEngine Lua VM (GELUA): runs general code that’s not specific to a vehicle (such as career mode, traffic vehicles orchestration or replays).
  • User Interface VM (UI): runs javascript/html to generate a visual interface (such as the main menu).

The GELUA and Main Menu UI VMs always exist; however the rest of VMs depend on which vehicles you’re using (and in some cases even maps, or mods in general).

Example

Let’s imagine you have spawned a modern taxi and a ’90s hatchback. You might have the following VMs:

  • General VMs:
    • GELUA
    • Main Menu UI
  • Taxi VMs:
    • VLUA
    • Dashboard UI
    • GPS navigator UI
    • Infotainment UI
  • Hatchback VMs:
    • VLUA

That would be a total of 7 different VMs running in parallel.

Source code location

Folder structure:

  • /ui/: UI VM code (javascript and html).
  • /lua/vehicle/: VLUA VM code.
  • /lua/ge/: GELUA VM code.
  • /lua/common/: libraries that can be used by both VLUA and GELUA.

The libraries found in /lua/common/ are a convenient way to avoid code duplication, however they are not a way to share or send information between different VMs. Please check communication if you want to share information.

Threading

Each VM runs in its own isolated environment:

  • UI vms: they run in a separate process each.
  • VLUA vms: they run in a separate thread each.
  • GELUA VM: it runs in the main thread.

Update rate

Each VM can update at a different rate, depending on its purpose. As a general rule:

  • VLUA: can run at physics frequency (2000Hz, see lua/vehicle/main.lua::onPhysicsStep()), or graphics frequency (see lua/vehicle/main.lua::onGraphicsStep()).
  • GELUA: can run at graphics frequency (the current “framerate”, see lua/ge/main.lua::update()), or UI frequency (see lua/ge/main.lua::onGuiUpdate event).
  • Main UI: runs at a variable rate. Ideally 60 FPS, but often 30 FPS or less. This depends on the available computer resources.
  • Secondary UIs: run at a variable rate, often lower than the Main UI update rate.

On top of that, you could conceivably implement your own fixed update rate. One way you can do this, is by hooking your code to the graphics update() call, and then manually skipping ticks based on basic time tracking. This way your code will run no more often than the graphics rate, typically at the fixed rate you have decided. However, we recommend against using fixed-rate logic due to performance reasons. Follow the link below for more information.

Note: we understand that writing math that can work under extreme rate variations is hard. For this reason, the reported graphics “update rate” is guaranteed to be a minimum of 20Hz. When the computer is unable to reach 20fps, then the simulation will slow down as needed. This is a guarantee that helps you to program your math with a safe baseline rate.

Picking the correct update rate for your code is essential to ensure your mod can work in a variety of computers. See Improving Framerate for more information.

Reloading

During development of your mod, you may want to modify the source code that runs in a particular VM. To improve iteration times, BeamNG allows you to reload the VM of your choice on the fly, in order to test your new changes immediately without restarting the program:

  • Main UI: press F5.
  • GELUA: press Ctrl-L.
  • VLUA: press Tab to the desired vehicle, then press Ctrl-R.
  • VLUA (all of them): press Ctrl-Shift-R.

These reloads will start with a clean slate, spinning up an entirely new VM from scratch and delete the old one; however BeamNG has a convenient feature: it will attempt to recover/resume where you left, by storing the old state, and loading it when the new VM is running.

The idea is that you can continue development exactly where you were previously rather than, say, having to load your Career saved game again each time you edit some code.

Communication

Every virtual machine is running in parallel to the rest, and fully isolated from them all.

If you want a piece of information from one VM to be available in other VM, you need to explicitly send the information from one VM to another VM.

We offer these asynchronous communication methods:

  • Asynchronous queues in VLUA and GELUA: you enqueue a string of source code in the target LUA VM. This code contains the information you want. For example, you might enqueue the string "electrics.event('throttle', 0.6)" into a VLUA VM.
  • Asynchronous events in UI: you enqueue an event that javascript will be notified about, and various listeners can react to it. For example, you might enqueue the event "MenuHide".
  • Asynchronous mailboxes in VLUA: VLUA vms can also read data from a virtual mailbox storage system: the sender writes data to the mailbox, and the recipient(s) can check what’s the last information available in that mailbox. This is a more performant way to communicate than queues, but you cannot run arbitrary code: instead, the recipient VM must manually check their mailbox in order to notice the information has been updated, and act upon it however they see fit.

Note: if you were wondering about traditional communication methods (such as mutex, barrier…), those are intentionally not made available, for robustness and simplicity.

Here’s a quick visual guide with the code needed to communicate between APIs:

And here’s the same information explained with plausible code examples:

  • Sending events to UI:
    • guihooks.trigger("myEvent") (from VLUA)
    • guihooks.trigger("myEvent") (from GELUA)
    • $scope.$emit("myEvent") (from UI)
  • Listening for events in a UI:
    • $scope.$on("myEvent", function() { myJavascriptCode() } )
  • Enqueing code to GELUA:
    • bngApi.engineLua('myLuaCode()') (from UI)
    • obj:queueGameEngineLua('myLuaCode()') (from VLUA)
  • Enqueuing code to one particular VLUA:
    • bngApi.engineLua("be:getPlayerVehicle(0):queueLuaCommand('myLuaCode()')") (from UI)
    • be:getPlayerVehicle(0):queueLuaCommand('myLuaCode()') (from GELUA)
  • Enqueuing code to all VLUA:
    • bngApi.engineLua("be:queueAllObjectLua('myLuaCode()')") (from UI)
    • be:queueAllObjectLua('myLuaCode()') (from GELUA)
    • BeamEngine:queueAllObjectLua('myLuaCode()') (from VLUA)
  • Enqueuing code to all VLUA except one:
    • BeamEngine:queueAllObjectLuaExcept('myLuaCode()', exceptObjectID) (from VLUA)
  • Leaving information in a VLUA mailbox:
    • be:sendToMailbox("myMailboxAddress", myData) (from GELUA)
  • Reading information left in a VLUA mailbox:
    • local myData = obj:getLastMailbox("myMailboxAddress")

Note: reading data from a mailbox does not delete the information. It’s still available to be read in the future and by any other VLUA VM.

We recommend you look at existing official code to get a better idea of the possibilities and to see working examples instead of the pseudoc-code listed above.

Performance considerations

Please refer to Improving Framerate for details on how to reduce the negative impact that VM communications can have.

Full architecture

The sections above cover all the basics you need to know when programming your mods.

However, if you want additional detail, there are additional components that link all these VMs together:

  • C++ Game Engine: the “real” high-performance engine, written mostly in C++, and implementign features such as frame rendering, input handling, VR, audio generation, etc.
  • C++ Physics Core: while VLUA VMs compute accessory parts of the physics that relate to vehicles (such as powertrain, etc), there’s also the core physics engine. This computes the soft-body physics, friction models, node-grabbing physics, etc. It also orchestrates the execution and synchronization of all VLUA VMs.
  • TS VM: this component has not been completely removed yet, so remnants of the old TorqueScript VM still exist in our engine. It runs on the main thread and shouldn’t be used for any new development or mods.

This is a detailed overview of all VMs, all conceptual sections of the engine, and which threads/processes each runs in:

As you can see, in addition to the multiple threads from spawned vehicles and the different UI vms, the C++ Game Engine itself is internally multithreaded too (for tasks such as Vulkan rendering, audio tasks, etc). However, for modding purposes, you can ignore them all, and focus only on UI/VLUA/GELUA VMs.

Programming documentation feedback

If you feel this programming documentation is too high level, too low level, is missing important topics, is erroneous anywhere, etc, please write a post at this thread and ping me personally by typing @stenyak so I will get notified.

Last modified: 8/4/2024 17:53

Any further questions?

Join our discord