Engine Main Loop

Chapters:

This article describes in detail the steps taken by the UNIGINE engine when the Engine::update(), Engine::render(), and Engine::swap() functions are called. For other steps and general information about execution sequence, see the Execution Sequence article.

Notice

Each stage of the main loop is initiated by the application window created during the Initialization stage.

In the performance profiler, the total time the main loop has taken, is displayed by the Total counter.

Calculation of FPS starts only with the second rendered frame, while the very first one is skipped.

All pending console commands that were called during the previous frame are executed. The commands are executed in the beginning of the update() cycle, but before the scripts are updated, because otherwise they may violate the current process of rendering or physical calculations.

If the video_restart console command was executed previously (not in the current update stage), world shaders are created.

Notice

When video is restarted (for example, when the video mode is changed), the application window calls the destroy() methods of the world, system, and editor logic and plugins. However, it is performed not in the current update stage, but earlier.

A pluginupdate() function is called. What happens during it, solely depends on the content of this custom function.

The editor logic that handles all editor-related GUI and logic is updated.

The system logic is updated. The default system script update() function performs the following:

The system script handles the mouse. It controls whether the mouse is grabbed when clicked (by default), the mouse cursor disappears when not moved for some time (set by the MOUSE_SOFT definition), or not handled by the system (set by the MOUSE_USER definition, which allows input handling by some custom module).

The main menu logic is updated (if the MENU_USER definition is not set).

Other system-related user input is handled. For example, the world state is saved or restored, or a screenshot of the current UNIGINE window contents is made, if required.

If the GPU Monitor plugin is initialized (the HAS_GPU_MONITOR definition is set), the plugin output is displayed in the application window and additional console commands become available.

Ambient sound sources timers are updated.

If the world is loaded (it can be done from the console or via the system logic), the world logic gets updated. In the world logic update() function, you can code frame-by-frame behavior of your application (see details here).

Notice

Physics, if any, and continuous operations (pushing a car forward depending on current motor's RPM, simulating wind blowing constantly, performing immediate collision response, etc.), can be implemented separately in the flush() function. This function is called with a fixed frame rate (while the update() function is called each frame).

The world and its world logic are updated in the following order:

The world logicupdate() function is executed: node parameters are updated, transformations for non-physical nodes are set and so on.

The state of nodes existing in the world is updated (mostly for visible nodes): skinned animation is played, particle systems spawn new particles, players are repositioned, and so on. Triggered world callbacks are added to a stack (they will be executed later).

World Expressions are updated. Code in World Expressions can be written via UnigineEditor.

The world logicrender() function is called.

The system logicrender() function is executed, if necessary (see details here). It can access the updated data on node states and correct the behavior accordingly in the same frame.

The postUpdate() function of all plugins is called.

The world spatial tree is updated.

GUI is updated.

In the performance profiler, the total time of update stage is displayed by the Update counter.

The engine uses all available threads to update visible nodes, instead of updating them one by one. The world_threaded command sets the mode: multiple-threaded or single mode.

Notice

Child nodes under 1 parent are always updated in 1 thread. To benefit from the advantages of multi-threading, computationally heavy nodes (like particles systems) should have different or no parents.
As each Node Reference is handled as a root node without any parent, it is automatically optimized for multi-threading.

At the end of the update stage, multi-threaded physics and pathfinding start to be updated in their separate threads. Then they perform their tasks on all available threads in parallel to rendering. Use physics_threaded and pathfind_threaded commands to select the mode of the calculations.

Notice

Nodes with computationally heavy bodies (like clothes and ropes) should not have one parent; otherwise, they will be updated in one thread.

As soon as the update stage is completed, UNIGINE can start rendering the world. In parallel, physics calculations and pathfinding are performed. This approach enables to effectively balance the load between CPU and GPU, and thus allows for higher framerate in the UNIGINE-based application.

UNIGINE renders the graphics scene (the world) and the sound scene, as they should be in the current frame. The graphics scene is sent to GPU, while the sound scene is sent to the sound card. As soon as the CPU finishes preparation of data and feeds rendering commands to the GPU, the GPU becomes busy with rendering the frame.

In the performance profiler, the total time of rendering stage is displayed by the Render counter. After that, the CPU is free, so we can load it with calculations we need.

The physics module is updated: internal physics simulation starts. During this step, Unigine performs collision detection for all objects that have physical bodies and collision shapes.
In the performance
profiler, the total time of flush() together with physics simulation is displayed by the Physics counter.

The pathfinding module is updated. In the thread performance profiler, the total time of pathfinding is displayed by the PathFind counter.

The gui() function of plugins (if any) is called.

At last and atop of all, GUI is rendered, if required. In the performance profiler, the total time of interface rendering is displayed by the Interface counter.

Synchronization of physics with the rendered world. Results of the physical calculations are applied to the world. That is, on the previous step we have calculated how physical bodies with collision shapes have changed their position and orientation (due to our flush-based logic or interaction). Now these transformations can be finally applied to nodes, i.e. rendered meshes.

Notice

As synchronization of physics follows the rendering stage, applied physical transformations will be visible on the screen only in the next frame.

Values shown in the performance profiler are updated.

After the swap() is completed, the application window initiates GPU buffers swapping as described here.

Complementary to the steps described above, the swap stage in the multi-threaded mode includes waiting for all additional threads to finish their tasks. After that, physics and pathfinding threads are synchronized and calculations are applied to the world.

Last update:
2019-02-14

We use cookies to ensure that we give you the best experience on our website. Click here for more information.