A virtual machine is each of the environments in which code can be executed. There’s 3 types of virtual machines:
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).
Let’s imagine you have spawned a modern taxi and a ’90s hatchback. You might have the following VMs:
That would be a total of 7 different VMs running in parallel.
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.
Each VM runs in its own isolated environment:
Each VM can update at a different rate, depending on its purpose. As a general rule:
lua/vehicle/main.lua
::onPhysicsStep()
), or graphics frequency (see lua/vehicle/main.lua
::onGraphicsStep()
).lua/ge/main.lua
::update()
), or UI frequency (see lua/ge/main.lua
::onGuiUpdate
event).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.
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:
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.
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:
"input.event('throttle', 0.6)"
into a VLUA VM."MenuHide"
.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:
guihooks.trigger("myEvent")
(from VLUA)guihooks.trigger("myEvent")
(from GELUA)$scope.$emit("myEvent")
(from UI)$scope.$on("myEvent", function() { myJavascriptCode() } )
bngApi.engineLua('myLuaCode()')
(from UI)obj:queueGameEngineLua('myLuaCode()')
(from VLUA)bngApi.engineLua("be:getPlayerVehicle(0):queueLuaCommand('myLuaCode()')")
(from UI)be:getPlayerVehicle(0):queueLuaCommand('myLuaCode()')
(from GELUA)bngApi.engineLua("be:queueAllObjectLua('myLuaCode()')")
(from UI)be:queueAllObjectLua('myLuaCode()')
(from GELUA)BeamEngine:queueAllObjectLua('myLuaCode()')
(from VLUA)BeamEngine:queueAllObjectLuaExcept('myLuaCode()', exceptObjectID)
(from VLUA)be:sendToMailbox("myMailboxAddress", myData)
(from GELUA)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.
Please refer to Improving Framerate for details on how to reduce the negative impact that VM communications can have.
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:
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.
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.