This page covers MainLoop — the abstract engine loop interface — and SceneTree, its default implementation that manages the live node hierarchy during a running game. SceneTree drives per-frame processing, group calls, timers, pause/suspend state, and scene transitions.
For details on the Node class itself and its lifecycle callbacks, see Node Hierarchy. For how Viewport and Window integrate with the tree root, see Viewport System and Window Management. For how the engine initializes and hands control to the main loop, see Engine Initialization.
MainLoop (core/os/main_loop.h) is an abstract base class defining the engine's game loop contract. The engine constructs a MainLoop at startup, then calls its methods on each iteration.
Key virtual methods:
| Method | Role |
|---|---|
_initialize() | Called once before the loop starts |
_process(delta) -> bool | Called each iteration; returning true exits the loop |
_physics_process(delta) | Called at each fixed physics step |
_finalize() | Called once after the loop exits |
A custom MainLoop subclass can be specified via ProjectSettings.application/run/main_loop_type or the -s command-line flag. If none is specified, SceneTree is used automatically.
MainLoop notifications (sent via notification() to the loop script) include OS-level events such as NOTIFICATION_OS_MEMORY_WARNING, NOTIFICATION_WM_QUIT_REQUEST, and NOTIFICATION_APPLICATION_FOCUS_IN.
Sources: core/os/main_loop.h core/os/main_loop.cpp doc/classes/MainLoop.xml
SceneTree (scene/main/scene_tree.h scene/main/scene_tree.cpp) implements MainLoop and owns the live node tree.
Class hierarchy:
Sources: scene/main/scene_tree.h core/os/main_loop.h
The tree always has a root of type Window — this is the top-level window visible to the player. current_scene is a direct child of root representing the actively running scene. When a scene change occurs, the previous current_scene is removed and a new one is added as a child of root.
SceneTree.root is set during engine initialization and is never null while the tree is running.SceneTree.current_scene is null until a scene is loaded. It is set/cleared automatically during scene transitions.SceneTree.edited_scene_root is used only in the editor to identify the root of the scene being edited.Sources: scene/main/scene_tree.cpp148-165 scene/main/scene_tree.h doc/classes/SceneTree.xml
Each game tick, Main::iteration() (see page [3.1]) calls into SceneTree. The cycle distinguishes between physics steps (fixed timestep, 60 Hz default) and idle frames (every rendered frame).
Per-frame cycle overview:
flush_transform_notifications() processes all nodes on the xform_change_list, firing NOTIFICATION_TRANSFORM_CHANGED scene/main/scene_tree.cpp197-208_flush_ugc() flushes batched unique group calls accumulated during the frame scene/main/scene_tree.cpp317-335process_frame is a monotonically increasing counter accessible via SceneTree.get_frame().Sources: scene/main/scene_tree.cpp197-335 scene/main/scene_tree.h
Groups provide a way to broadcast method calls, property sets, and notifications to named collections of nodes.
Groups are stored in SceneTree.group_map as a HashMap<StringName, Group>. The Group struct holds a Vector<Node*> and a changed flag.
When changed is true, _update_group_order() re-sorts the group nodes using Node::Comparator (tree order) before any broadcast scene/main/scene_tree.cpp337-352
Nodes join and leave groups via add_to_group() / remove_from_group() on Node, which delegate to SceneTree::add_to_group() / SceneTree::remove_from_group(). Both are thread-safe via _THREAD_SAFE_METHOD_ scene/main/scene_tree.cpp171-195
| SceneTree method | Purpose |
|---|---|
call_group(group, method, ...) | Call method on all nodes in group |
notify_group(group, notification) | Send notification integer |
set_group(group, property, value) | Set property on all nodes |
call_group_flags(flags, group, method, ...) | Call with flags |
notify_group_flags(flags, group, notification) | Notify with flags |
set_group_flags(flags, group, property, value) | Set with flags |
All of these ultimately call call_group_flagsp() scene/main/scene_tree.cpp354
GROUP_CALL_DEFERRED posts calls to MessageQueue instead of calling directly.GROUP_CALL_UNIQUE combined with GROUP_CALL_DEFERRED stores calls in unique_group_calls (HashMap<UGCall, Vector<Variant>>) and deduplicates — only one call per (group, method) pair is flushed per frame via _flush_ugc().nodes_removed_on_group_call_lock and nodes_removed_on_group_call track nodes freed mid-iteration to prevent use-after-free scene/main/scene_tree.cpp396-458Sources: scene/main/scene_tree.cpp354-460 doc/classes/SceneTree.xml
SceneTree has two distinct "stopped" states:
| State | Flag | Effect |
|---|---|---|
| Paused | SceneTree.paused | Nodes with PROCESS_MODE_PAUSABLE stop processing |
| Suspended | SceneTree.suspended | All nodes stop processing regardless of process mode |
Node processing eligibility is checked in Node::can_process() scene/main/node.cpp907-909:
!tree->is_suspended() && _can_process(tree->is_paused())
The Node.process_mode property controls behavior during pause:
| ProcessMode | Processes when running | Processes when paused |
|---|---|---|
PROCESS_MODE_INHERIT | (inherits from parent) | (inherits) |
PROCESS_MODE_PAUSABLE | ✓ | ✗ |
PROCESS_MODE_WHEN_PAUSED | ✗ | ✓ |
PROCESS_MODE_ALWAYS | ✓ | ✓ |
PROCESS_MODE_DISABLED | ✗ | ✗ |
The effective mode is resolved by walking up to the nearest non-INHERIT ancestor via data.process_owner scene/main/node.cpp912-938
When the tree's pause state changes, Node::_propagate_pause_notification() traverses the subtree and emits NOTIFICATION_PAUSED or NOTIFICATION_UNPAUSED to affected nodes scene/main/node.cpp727-742
Similarly, suspension emits NOTIFICATION_SUSPENDED / NOTIFICATION_UNSUSPENDED via _propagate_suspend_notification() scene/main/node.cpp744-752
Sources: scene/main/node.cpp907-939 scene/main/node.h76-82
SceneTreeTimer (scene/main/scene_tree.cpp72-120) is a lightweight RefCounted countdown timer returned by SceneTree.create_timer().
Parameters of create_timer():
| Parameter | Default | Effect |
|---|---|---|
time_sec | — | Seconds until timeout emits |
process_always | true | Continues counting when tree is paused |
process_in_physics | false | Counts down in physics tick instead of idle |
ignore_time_scale | false | Ignores Engine.time_scale |
Live timers are stored in SceneTree.timers as a List<Ref<SceneTreeTimer>>. The tree decrements each timer every frame/physics step and removes it when time_left <= 0, emitting timeout. Holding a Ref<SceneTreeTimer> is sufficient to keep it alive; releasing the reference cancels the timer if it hasn't fired yet.
Sources: scene/main/scene_tree.cpp72-120 doc/classes/SceneTree.xml
SceneTree provides two methods for changing the active scene at runtime:
| Method | Description |
|---|---|
change_scene_to_file(path: String) | Loads a .tscn file, instantiates it, and switches to it |
change_scene_to_node(node: Node) | Switches to an already-constructed node |
reload_current_scene() | Re-loads the scene file of current_scene |
unload_current_scene() | Removes current_scene without loading a replacement |
Order of operations for change_scene_to_node():
current_scene is removed from the tree immediately (its get_tree() returns null from this point).current_scene is freed via queue_free().root.current_scene is updated to point to the new node.Note: The new scene's
_readyfires after it is added to the tree, not during thechange_scene_to_*call. Scene changes are processed at the end of the current frame.
Sources: doc/classes/SceneTree.xml
Godot 4 supports offloading node processing to worker threads. Each node's process_thread_group property controls which thread group it belongs to.
PROCESS_THREAD_GROUP_SUB_THREAD (or inherit one) create a separate ProcessGroup and run _process / _physics_process callbacks on a worker thread.process_thread_group_owner. Its subtree (unless overriding) shares that group.NOTIFICATION_ENTER_TREE, nodes are added to the appropriate process group scene/main/node.cpp131-151_add_process_group() / _remove_process_group() on the node.Node.process_thread_messages flags (FLAG_PROCESS_THREAD_MESSAGES, etc.) control whether MessageQueue messages are flushed for that node on the processing thread.Sources: scene/main/node.h84-94 scene/main/node.cpp131-151 scene/main/node.cpp224-232
SceneTree emits these signals to allow external code to observe tree events:
| Signal | Parameters | When emitted |
|---|---|---|
tree_changed | — | Any node added, removed, or reordered |
node_added | node: Node | A node enters the tree |
node_removed | node: Node | A node exits the tree |
node_renamed | node: Node | A node's name changes |
node_configuration_warning_changed | node: Node | Node warning state changes |
process_frame | — | End of each idle frame |
physics_frame | — | End of each physics step |
tree_process_mode_changed | — | Any node's process_mode changes (editor only) |
tree_changed, node_added, node_removed, and node_renamed are emitted directly from SceneTree methods that are called by Node::_propagate_enter_tree() and Node::_propagate_exit_tree() scene/main/scene_tree.cpp148-168 scene/main/node.cpp341-388 scene/main/node.cpp410-457
Sources: scene/main/scene_tree.cpp148-168 doc/classes/SceneTree.xml
SceneTree manages physics interpolation (FTI — Fixed-Timestep Interpolation) via a static flag and a helper inner class.
SceneTree::_physics_interpolation_enabled — static bool, set at startup from the project setting physics/common/physics_interpolation scene/main/scene_tree.cpp145-146SceneTree::is_fti_enabled() — static query method used throughout the engine (e.g. in Node::reset_physics_interpolation()) scene/main/node.cpp974SceneTree::ClientPhysicsInterpolation — an inner class that maintains a list of Node3D nodes using client-side interpolation. Its physics_process() is called each physics tick to update their transform history scene/main/scene_tree.cpp125-142When FTI is enabled, nodes receive NOTIFICATION_RESET_PHYSICS_INTERPOLATION when teleporting (via Node::reset_physics_interpolation()), which propagates down the subtree scene/main/node.cpp973-982
Nodes also receive NOTIFICATION_PAUSED / NOTIFICATION_SUSPENDED which triggers reset_physics_interpolation() automatically to avoid interpolation artifacts across the gap scene/main/node.cpp241-246
Sources: scene/main/scene_tree.cpp122-146 scene/main/node.cpp241-246 scene/main/node.cpp973-982
Refresh this wiki
This wiki was recently refreshed. Please wait 3 days to refresh again.