Notification System
Overview
What are notifications? Notifications are Godot’s way of telling your objects when important things happen. Think of them as automatic messages sent by the engine - “you’ve been added to the scene tree,” “it’s time to process this frame,” “your transform changed,” etc. They’re different from signals because they’re sent automatically by the engine, not by your code.
Why use notifications? Notifications let you respond to engine events without having to constantly check for changes. Instead of asking “am I in the tree yet?” every frame, you simply wait for the NOTIFICATION_ENTER_TREE notification. This is more efficient and ensures you don’t miss important state changes.
The notification system in Godot provides a unified way to inform objects about state changes, lifecycle events, and system events. Notifications can propagate through the scene tree hierarchy, be consumed by handlers, or pass through to parent classes. This system is fundamental to how nodes communicate state changes without tight coupling.
Notification Flow
---
config:
theme: 'base'
curve: 'straight'
themeVariables:
darkMode: true
clusterBkg: '#22272f62'
clusterBorder: '#6a6f77ff'
clusterTextColor: '#6a6f77ff'
lineColor: '#C1C4CAAA'
background: '#262B33'
primaryColor: '#2b4268ff'
primaryTextColor: '#C1C4CAff'
primaryBorderColor: '#6a6f77ff'
primaryLabelBkg: '#262B33'
secondaryColor: '#425f5fff'
secondaryBorderColor: '#8c9c81ff'
secondaryTextColor: '#C1C4CAff'
tertiaryColor: '#4d4962ff'
tertiaryBorderColor: '#8983a5ff'
tertiaryTextColor: '#eeeeee55'
nodeTextColor: '#C1C4CA'
defaultLinkColor: '#C1C4CA'
edgeLabelBackground: '#262B33'
edgeLabelBorderColor: '#C1C4CA'
labelTextColor: '#C1C4CA'
errorBkgColor: '#724848ff'
errorTextColor: '#C1C4CA'
flowchart:
curve: 'basis'
nodeSpacing: 50
rankSpacing: 50
subGraphTitleMargin:
top: 15
bottom: 15
left: 15
right: 15
---
flowchart TD
A[Event Occurs] --> B{Notification Type}
B --> |Lifecycle| C[NOTIFICATION_READY]
B --> |Process| D[NOTIFICATION_PROCESS]
B --> |Physics| E[NOTIFICATION_PHYSICS_PROCESS]
B --> |Transform| F[NOTIFICATION_TRANSFORM_CHANGED]
C --> G["notification() called"]
D --> G
E --> G
F --> G
G --> H{Handled?}
H --> |Yes| I[Stop Propagation]
H --> |No| J[Propagate to Parent Class]
J --> K{Parent Handler?}
K --> |Yes| L[Parent Handles]
K --> |No| M[Default Behavior]
linkStyle default stroke:#C1C4CAaa,stroke-width:2px,color:#C1C4CA
style A fill:#425f5fff,stroke:#8c9c81ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style B fill:#4d4962ff,stroke:#8983a5ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style G fill:#2b4268ff,stroke:#779DC9ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style H fill:#4d4962ff,stroke:#8983a5ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style I fill:#724848ff,stroke:#ac9696ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style J fill:#7a6253ff,stroke:#c7ac9bff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style K fill:#4d4962ff,stroke:#8983a5ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style L fill:#2b4268ff,stroke:#779DC9ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
style M fill:#3a3f47ff,stroke:#6a6f77ff,stroke-width:2px,color:#C1C4CA,rx:8,ry:8
Notification Architecture
How notifications flow through objects: When something important happens in the engine, it generates a notification with a specific ID number. This notification gets sent to the relevant object, which can choose to handle it in its _notification() method. If not handled, it can be passed up to the parent class, allowing for layered handling of events.
Core Notification System
// Base notification handling in Object
class Object {
public:
// Public interface for sending notifications
void notification(int p_notification, bool p_reversed = false) {
if (!p_reversed) {
// Normal order: derived -> base
_notification(p_notification);
} else {
// Reversed order: base -> derived
_notification_reversed(p_notification);
}
}
protected:
// Virtual method for handling notifications
virtual void _notification(int p_what) {
// Base implementation handles common notifications
switch (p_what) {
case NOTIFICATION_POSTINITIALIZE:
// Object fully constructed
break;
case NOTIFICATION_PREDELETE:
// About to be deleted
break;
}
}
private:
void _notification_reversed(int p_what) {
// Call parent first, then derived
// Used for cleanup notifications
}
};
Notification Constants
// Common notification constants (from object.h)
enum {
// Object notifications
NOTIFICATION_POSTINITIALIZE = 0,
NOTIFICATION_PREDELETE = 1,
// Node notifications
NOTIFICATION_ENTER_TREE = 10,
NOTIFICATION_EXIT_TREE = 11,
NOTIFICATION_MOVED_IN_PARENT = 12,
NOTIFICATION_READY = 13,
NOTIFICATION_PAUSED = 14,
NOTIFICATION_UNPAUSED = 15,
NOTIFICATION_PHYSICS_PROCESS = 16,
NOTIFICATION_PROCESS = 17,
NOTIFICATION_PARENTED = 18,
NOTIFICATION_UNPARENTED = 19,
NOTIFICATION_SCENE_INSTANTIATED = 20,
NOTIFICATION_DRAG_BEGIN = 21,
NOTIFICATION_DRAG_END = 22,
NOTIFICATION_PATH_RENAMED = 23,
NOTIFICATION_CHILD_ORDER_CHANGED = 24,
NOTIFICATION_INTERNAL_PROCESS = 25,
NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26,
NOTIFICATION_POST_ENTER_TREE = 27,
NOTIFICATION_DISABLED = 28,
NOTIFICATION_ENABLED = 29,
// CanvasItem notifications
NOTIFICATION_DRAW = 30,
NOTIFICATION_VISIBILITY_CHANGED = 31,
NOTIFICATION_ENTER_CANVAS = 32,
NOTIFICATION_EXIT_CANVAS = 33,
NOTIFICATION_LOCAL_TRANSFORM_CHANGED = 35,
NOTIFICATION_WORLD_2D_CHANGED = 36,
// Node3D notifications
NOTIFICATION_TRANSFORM_CHANGED = 2000,
NOTIFICATION_ENTER_WORLD = 41,
NOTIFICATION_EXIT_WORLD = 42,
NOTIFICATION_VISIBILITY_CHANGED_3D = 43,
NOTIFICATION_LOCAL_TRANSFORM_CHANGED_3D = 44,
// Control notifications
NOTIFICATION_RESIZED = 40,
NOTIFICATION_MOUSE_ENTER = 41,
NOTIFICATION_MOUSE_EXIT = 42,
NOTIFICATION_MOUSE_ENTER_SELF = 60,
NOTIFICATION_MOUSE_EXIT_SELF = 61,
NOTIFICATION_FOCUS_ENTER = 43,
NOTIFICATION_FOCUS_EXIT = 44,
NOTIFICATION_THEME_CHANGED = 45,
NOTIFICATION_SCROLL_BEGIN = 47,
NOTIFICATION_SCROLL_END = 48,
NOTIFICATION_LAYOUT_DIRECTION_CHANGED = 49,
// Window notifications
NOTIFICATION_WM_MOUSE_ENTER = 1002,
NOTIFICATION_WM_MOUSE_EXIT = 1003,
NOTIFICATION_WM_WINDOW_FOCUS_IN = 1004,
NOTIFICATION_WM_WINDOW_FOCUS_OUT = 1005,
NOTIFICATION_WM_CLOSE_REQUEST = 1006,
NOTIFICATION_WM_GO_BACK_REQUEST = 1007,
NOTIFICATION_WM_SIZE_CHANGED = 1008,
NOTIFICATION_WM_DPI_CHANGE = 1009,
NOTIFICATION_WM_ABOUT = 1011,
// Application notifications
NOTIFICATION_OS_MEMORY_WARNING = 2009,
NOTIFICATION_TRANSLATION_CHANGED = 2010,
NOTIFICATION_WM_ABOUT = 2011,
NOTIFICATION_CRASH = 2012,
NOTIFICATION_OS_IME_UPDATE = 2013,
NOTIFICATION_APPLICATION_RESUMED = 2014,
NOTIFICATION_APPLICATION_PAUSED = 2015,
NOTIFICATION_APPLICATION_FOCUS_IN = 2016,
NOTIFICATION_APPLICATION_FOCUS_OUT = 2017,
NOTIFICATION_TEXT_SERVER_CHANGED = 2018,
// Editor notifications (editor builds only)
NOTIFICATION_EDITOR_PRE_SAVE = 9001,
NOTIFICATION_EDITOR_POST_SAVE = 9002,
};
Notification Types
Categories of notifications: Godot sends many different types of notifications, organized into categories. Lifecycle notifications tell you about object creation and destruction. Process notifications happen every frame or physics step. Transform notifications tell you when objects move or change. Understanding these categories helps you know which notifications your objects should care about.
Lifecycle Notifications
class LifecycleNode : public Node {
GDCLASS(LifecycleNode, Node)
protected:
virtual void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
// Node added to scene tree
// Parent exists, children might not be ready
print_line("Entered tree");
setup_connections();
break;
case NOTIFICATION_READY:
// Node and all children are in tree
// Safe to access child nodes
print_line("Node ready");
initialize_gameplay();
break;
case NOTIFICATION_EXIT_TREE:
// Node being removed from tree
// Clean up connections
print_line("Exiting tree");
cleanup_connections();
break;
case NOTIFICATION_PREDELETE:
// Object about to be destroyed
// Final cleanup
print_line("Pre-delete");
release_resources();
break;
case NOTIFICATION_PARENTED:
// Node got a new parent
print_line("Parented to: " + get_parent()->get_name());
break;
case NOTIFICATION_UNPARENTED:
// Node removed from parent
print_line("Unparented");
break;
}
}
private:
void setup_connections() {
// Connect to parent signals
if (get_parent()) {
get_parent()->connect("some_signal", Callable(this, "on_parent_signal"));
}
}
void cleanup_connections() {
// Disconnect from parent
if (get_parent() && get_parent()->is_connected("some_signal", Callable(this, "on_parent_signal"))) {
get_parent()->disconnect("some_signal", Callable(this, "on_parent_signal"));
}
}
void initialize_gameplay() {
// Initialize once everything is ready
}
void release_resources() {
// Clean up resources
}
};
Process Notifications
class ProcessNode : public Node {
GDCLASS(ProcessNode, Node)
protected:
virtual void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PROCESS:
// Called every frame if processing enabled
// Alternative to _process()
process_frame(get_process_delta_time());
break;
case NOTIFICATION_PHYSICS_PROCESS:
// Called every physics frame
// Alternative to _physics_process()
physics_update(get_physics_process_delta_time());
break;
case NOTIFICATION_INTERNAL_PROCESS:
// Internal processing (used by engine)
internal_update();
break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS:
// Internal physics processing
internal_physics();
break;
}
}
private:
void process_frame(double delta) {
// Frame processing logic
}
void physics_update(double delta) {
// Physics processing logic
}
void internal_update() {
// Internal updates
}
void internal_physics() {
// Internal physics
}
};
Transform Notifications
class TransformNode : public Node3D {
GDCLASS(TransformNode, Node3D)
private:
Transform3D cached_transform;
bool transform_dirty = false;
protected:
virtual void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_TRANSFORM_CHANGED:
// Global transform changed
on_global_transform_changed();
break;
case NOTIFICATION_LOCAL_TRANSFORM_CHANGED:
// Local transform changed
on_local_transform_changed();
break;
case NOTIFICATION_ENTER_WORLD:
// Entered 3D world
register_with_physics();
break;
case NOTIFICATION_EXIT_WORLD:
// Left 3D world
unregister_from_physics();
break;
case NOTIFICATION_VISIBILITY_CHANGED:
// Visibility state changed
on_visibility_changed(is_visible());
break;
}
}
private:
void on_global_transform_changed() {
Transform3D new_transform = get_global_transform();
if (new_transform != cached_transform) {
cached_transform = new_transform;
transform_dirty = true;
// Update dependent systems
update_collision_shape();
update_children_transforms();
}
}
void on_local_transform_changed() {
// Local transform modified
mark_for_update();
}
void on_visibility_changed(bool visible) {
if (visible) {
enable_processing();
} else {
disable_processing();
}
}
void register_with_physics() {
// Register collision shapes
}
void unregister_from_physics() {
// Remove from physics
}
void update_collision_shape() {
// Update physics representation
}
void update_children_transforms() {
// Propagate changes to children
}
void mark_for_update() {
transform_dirty = true;
}
void enable_processing() {
set_process(true);
set_physics_process(true);
}
void disable_processing() {
set_process(false);
set_physics_process(false);
}
};
Notification Propagation
How notifications move between objects: Unlike signals that you explicitly connect, notifications follow built-in propagation rules. Some notifications (like NOTIFICATION_READY) go to children first, then parents. Others go to parents first. You can also stop propagation by handling a notification without calling the parent class, giving you fine control over event processing.
Propagation Rules
class PropagationExample : public Control {
GDCLASS(PropagationExample, Control)
protected:
virtual void _notification(int p_what) {
// Check if we want to handle this notification
bool handled = false;
switch (p_what) {
case NOTIFICATION_MOUSE_ENTER:
// Handle mouse enter
print_line("Mouse entered: " + get_name());
on_mouse_entered();
handled = true; // We handled it
break;
case NOTIFICATION_THEME_CHANGED:
// Handle theme change
update_theme();
// Don't set handled - let parent also update
break;
case NOTIFICATION_RESIZED:
// Handle resize
adjust_layout();
// Continue propagation for parent layout
break;
}
// Call parent implementation if not fully handled
if (!handled) {
Control::_notification(p_what); // Explicit parent call
}
}
private:
void on_mouse_entered() {
// Handle mouse enter
modulate = Color(1.2, 1.2, 1.2); // Brighten
}
void update_theme() {
// Update from theme
// This might also trigger parent theme updates
}
void adjust_layout() {
// Adjust our layout
// Parent might also need to adjust
}
};
Controlling Propagation
class NotificationControl : public Node {
GDCLASS(NotificationControl, Node)
private:
bool block_notifications = false;
HashSet<int> consumed_notifications;
protected:
virtual void _notification(int p_what) {
// Option 1: Complete blocking
if (block_notifications) {
return; // Don't process or propagate
}
// Option 2: Selective consumption
if (consumed_notifications.has(p_what)) {
handle_consumed_notification(p_what);
return; // Don't propagate to parent
}
// Option 3: Pre-process and propagate
bool continue_propagation = true;
switch (p_what) {
case NOTIFICATION_READY:
// Pre-process
before_ready();
// Call parent
Node::_notification(p_what);
// Post-process
after_ready();
continue_propagation = false; // Already called parent
break;
case NOTIFICATION_PROCESS:
// Conditionally propagate
if (should_process()) {
Node::_notification(p_what);
}
continue_propagation = false;
break;
}
// Default propagation
if (continue_propagation) {
Node::_notification(p_what);
}
}
public:
void set_block_notifications(bool p_block) {
block_notifications = p_block;
}
void consume_notification(int p_notification) {
consumed_notifications.insert(p_notification);
}
void release_notification(int p_notification) {
consumed_notifications.erase(p_notification);
}
private:
void handle_consumed_notification(int p_what) {
// Handle without propagation
print_line("Consumed notification: " + itos(p_what));
}
void before_ready() {
// Pre-processing
}
void after_ready() {
// Post-processing
}
bool should_process() {
// Conditional logic
return Engine::get_singleton()->get_frames_per_second() > 30;
}
};
Tree-Wide Notifications
class TreeNotifications : public Node {
GDCLASS(TreeNotifications, Node)
public:
// Send notification to entire subtree
void propagate_notification_to_children(int p_notification) {
// Send to self
notification(p_notification);
// Send to all children recursively
for (int i = 0; i < get_child_count(); i++) {
Node *child = get_child(i);
if (TreeNotifications *tn = Object::cast_to<TreeNotifications>(child)) {
tn->propagate_notification_to_children(p_notification);
} else {
child->notification(p_notification);
}
}
}
// Send notification up the tree
void propagate_notification_to_parents(int p_notification) {
// Send to self
notification(p_notification);
// Send to parent
if (Node *parent = get_parent()) {
if (TreeNotifications *tn = Object::cast_to<TreeNotifications>(parent)) {
tn->propagate_notification_to_parents(p_notification);
} else {
parent->notification(p_notification);
}
}
}
// Broadcast to siblings
void broadcast_to_siblings(int p_notification) {
if (Node *parent = get_parent()) {
for (int i = 0; i < parent->get_child_count(); i++) {
Node *sibling = parent->get_child(i);
if (sibling != this) {
sibling->notification(p_notification);
}
}
}
}
};
Handling Notifications
Writing code that responds to notifications: You handle notifications by overriding the _notification(int what) method in your C++ class. The what parameter tells you which notification was sent. Use a switch statement to handle different notification types, and remember to call the parent class for notifications you don’t handle yourself.
Best Practices
class BestPracticesNode : public Node {
GDCLASS(BestPracticesNode, Node)
protected:
virtual void _notification(int p_what) {
// 1. Use switch for multiple notifications
switch (p_what) {
case NOTIFICATION_READY:
handle_ready();
break;
case NOTIFICATION_ENTER_TREE:
handle_enter_tree();
break;
case NOTIFICATION_EXIT_TREE:
handle_exit_tree();
break;
// 2. Group related notifications
case NOTIFICATION_VISIBILITY_CHANGED:
case NOTIFICATION_VISIBILITY_CHANGED_3D:
handle_visibility_change();
break;
// 3. Always have default case
default:
// Call parent for unhandled notifications
Node::_notification(p_what);
break;
}
}
private:
// 4. Separate handlers for clarity
void handle_ready() {
// Ready logic
setup_node();
}
void handle_enter_tree() {
// Enter tree logic
connect_signals();
}
void handle_exit_tree() {
// Exit tree logic
disconnect_signals();
}
void handle_visibility_change() {
// Visibility logic
update_render_state();
}
// 5. Keep notification handlers lightweight
void setup_node() {
// Defer heavy initialization
call_deferred("heavy_initialization");
}
void heavy_initialization() {
// Heavy work here
}
void connect_signals() {
// Connect to signals
}
void disconnect_signals() {
// Disconnect from signals
}
void update_render_state() {
// Update rendering
}
};
Notification vs Virtual Methods
class NotificationVsVirtual : public Node {
GDCLASS(NotificationVsVirtual, Node)
protected:
// Option 1: Use virtual methods (preferred for common callbacks)
virtual void _ready() override {
print_line("_ready() virtual method");
// Easier to override in derived classes
// Better performance (direct call)
}
virtual void _process(double delta) override {
print_line("_process() virtual method");
// Clear intent
// Type-safe parameters
}
// Option 2: Use notifications (for less common or system events)
virtual void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY:
// Alternative to _ready()
// Use when you need more control over propagation
print_line("NOTIFICATION_READY");
break;
case NOTIFICATION_PROCESS:
// Alternative to _process()
// Use when you need unified notification handling
print_line("NOTIFICATION_PROCESS");
break;
case NOTIFICATION_WM_WINDOW_FOCUS_IN:
// No virtual method equivalent
// Must use notification
print_line("Window focused");
break;
case NOTIFICATION_TRANSLATION_CHANGED:
// System event - notification only
update_translated_text();
break;
}
}
private:
void update_translated_text() {
// Update UI text
}
};
Custom Notifications
Creating your own notification types: While most notifications come from the engine, you can define custom notification constants for your own events. This is useful for creating reusable components that need to communicate state changes. Custom notifications work just like built-in ones but use ID numbers starting from a safe range.
Defining Custom Notifications
class CustomNotificationNode : public Node {
GDCLASS(CustomNotificationNode, Node)
public:
// Define custom notification constants
enum {
// Start after Godot's notifications
NOTIFICATION_CUSTOM_BASE = 10000,
NOTIFICATION_CUSTOM_EVENT = NOTIFICATION_CUSTOM_BASE + 1,
NOTIFICATION_DATA_CHANGED = NOTIFICATION_CUSTOM_BASE + 2,
NOTIFICATION_STATE_TRANSITION = NOTIFICATION_CUSTOM_BASE + 3,
};
protected:
static void _bind_methods() {
// Bind notification constants for GDScript
BIND_CONSTANT(NOTIFICATION_CUSTOM_EVENT);
BIND_CONSTANT(NOTIFICATION_DATA_CHANGED);
BIND_CONSTANT(NOTIFICATION_STATE_TRANSITION);
}
virtual void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_CUSTOM_EVENT:
handle_custom_event();
break;
case NOTIFICATION_DATA_CHANGED:
handle_data_change();
break;
case NOTIFICATION_STATE_TRANSITION:
handle_state_transition();
break;
default:
Node::_notification(p_what);
break;
}
}
public:
// Public methods to trigger custom notifications
void trigger_custom_event() {
notification(NOTIFICATION_CUSTOM_EVENT);
// Also notify children
for (int i = 0; i < get_child_count(); i++) {
get_child(i)->notification(NOTIFICATION_CUSTOM_EVENT);
}
}
void notify_data_changed() {
notification(NOTIFICATION_DATA_CHANGED);
}
void transition_state() {
notification(NOTIFICATION_STATE_TRANSITION);
}
private:
void handle_custom_event() {
print_line("Custom event received");
}
void handle_data_change() {
print_line("Data changed");
update_display();
}
void handle_state_transition() {
print_line("State transitioned");
update_state_machine();
}
void update_display() {
// Update UI
}
void update_state_machine() {
// Update state
}
};
Notification Ordering
Understanding when notifications arrive: The order of notifications is predictable and important. For example, NOTIFICATION_ENTER_TREE always comes before NOTIFICATION_READY, and NOTIFICATION_READY goes to children before parents. Understanding this order helps you write initialization code that runs at the right time.
Execution Order
class NotificationOrder : public Node {
GDCLASS(NotificationOrder, Node)
protected:
virtual void _notification(int p_what) {
// Notification order for tree operations:
// 1. NOTIFICATION_ENTER_TREE (parent before children)
// 2. NOTIFICATION_READY (children before parent)
// 3. NOTIFICATION_EXIT_TREE (children before parent)
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
print_line(get_name() + ": ENTER_TREE");
// Parent processes before children
break;
case NOTIFICATION_READY:
print_line(get_name() + ": READY");
// Children process before parent
// All children are ready here
break;
case NOTIFICATION_EXIT_TREE:
print_line(get_name() + ": EXIT_TREE");
// Children process before parent
break;
case NOTIFICATION_PREDELETE:
print_line(get_name() + ": PREDELETE");
// Called in deletion order
break;
}
// Call parent
Node::_notification(p_what);
}
public:
void demonstrate_order() {
// Create tree structure
Node *parent = memnew(NotificationOrder);
parent->set_name("Parent");
Node *child1 = memnew(NotificationOrder);
child1->set_name("Child1");
Node *child2 = memnew(NotificationOrder);
child2->set_name("Child2");
Node *grandchild = memnew(NotificationOrder);
grandchild->set_name("Grandchild");
// Build tree
parent->add_child(child1);
parent->add_child(child2);
child1->add_child(grandchild);
// Output order:
// ENTER_TREE: Parent, Child1, Grandchild, Child2
// READY: Grandchild, Child1, Child2, Parent
// Cleanup
parent->queue_free();
// EXIT_TREE: Grandchild, Child1, Child2, Parent
// PREDELETE: (deletion order)
}
};
Common Patterns
Proven ways to use notifications: Over time, certain patterns have emerged as effective ways to use the notification system. These include state machines that transition based on notifications, deferred processing systems, and component architectures that use notifications for loose coupling between systems.
State Machine Notifications
class StateMachineNode : public Node {
GDCLASS(StateMachineNode, Node)
public:
enum State {
STATE_IDLE,
STATE_ACTIVE,
STATE_PAUSED,
STATE_COMPLETE
};
private:
State current_state = STATE_IDLE;
State previous_state = STATE_IDLE;
protected:
static void _bind_methods() {
BIND_ENUM_CONSTANT(STATE_IDLE);
BIND_ENUM_CONSTANT(STATE_ACTIVE);
BIND_ENUM_CONSTANT(STATE_PAUSED);
BIND_ENUM_CONSTANT(STATE_COMPLETE);
}
virtual void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_READY:
transition_to(STATE_IDLE);
break;
case NOTIFICATION_PROCESS:
update_state(get_process_delta_time());
break;
case NOTIFICATION_PAUSED:
if (current_state == STATE_ACTIVE) {
transition_to(STATE_PAUSED);
}
break;
case NOTIFICATION_UNPAUSED:
if (current_state == STATE_PAUSED) {
transition_to(STATE_ACTIVE);
}
break;
}
}
public:
void transition_to(State p_state) {
if (current_state == p_state) {
return;
}
previous_state = current_state;
current_state = p_state;
// Send state change notification
notification(NOTIFICATION_CUSTOM_BASE + current_state);
// Handle state entry
switch (current_state) {
case STATE_IDLE:
enter_idle();
break;
case STATE_ACTIVE:
enter_active();
break;
case STATE_PAUSED:
enter_paused();
break;
case STATE_COMPLETE:
enter_complete();
break;
}
}
private:
void update_state(double delta) {
switch (current_state) {
case STATE_IDLE:
update_idle(delta);
break;
case STATE_ACTIVE:
update_active(delta);
break;
case STATE_PAUSED:
// No update when paused
break;
case STATE_COMPLETE:
// No update when complete
break;
}
}
void enter_idle() { set_process(false); }
void enter_active() { set_process(true); }
void enter_paused() { set_process(false); }
void enter_complete() { set_process(false); }
void update_idle(double delta) {}
void update_active(double delta) {}
};
Deferred Notifications
class DeferredNotificationNode : public Node {
GDCLASS(DeferredNotificationNode, Node)
private:
Vector<int> pending_notifications;
bool processing_deferred = false;
protected:
virtual void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PROCESS:
process_deferred_notifications();
break;
default:
// Handle or defer
if (should_defer(p_what)) {
defer_notification(p_what);
} else {
handle_notification(p_what);
}
break;
}
}
public:
void defer_notification(int p_notification) {
if (!pending_notifications.has(p_notification)) {
pending_notifications.push_back(p_notification);
set_process(true); // Enable processing to handle deferred
}
}
private:
bool should_defer(int p_what) {
// Defer notifications during certain states
return is_physics_processing() &&
p_what != NOTIFICATION_PHYSICS_PROCESS;
}
void process_deferred_notifications() {
if (processing_deferred || pending_notifications.is_empty()) {
return;
}
processing_deferred = true;
Vector<int> to_process = pending_notifications;
pending_notifications.clear();
for (int notification : to_process) {
handle_notification(notification);
}
processing_deferred = false;
if (pending_notifications.is_empty()) {
set_process(false); // Disable if no more deferred
}
}
void handle_notification(int p_what) {
// Actual notification handling
print_line("Handling notification: " + itos(p_what));
}
};
Performance Considerations
Keeping notification handling efficient: Since notifications are sent frequently (especially process notifications), it’s important to keep your notification handlers fast. Use early exits, cache expensive computations, and consider batching operations. Remember that slow notification handling can directly impact your game’s frame rate.
Optimizing Notification Handling
class OptimizedNotificationNode : public Node {
GDCLASS(OptimizedNotificationNode, Node)
private:
// Cache frequently checked states
bool is_in_tree = false;
bool is_ready = false;
bool is_processing = false;
// Batch notification flags
uint32_t notification_flags = 0;
protected:
virtual void _notification(int p_what) {
// Early exit for disabled notifications
if (!should_process_notification(p_what)) {
return;
}
// Use bit flags for batching
switch (p_what) {
case NOTIFICATION_ENTER_TREE:
is_in_tree = true;
notification_flags |= FLAG_ENTERED_TREE;
break;
case NOTIFICATION_READY:
is_ready = true;
notification_flags |= FLAG_READY;
// Process batched operations
if (notification_flags & FLAG_BATCH_MASK) {
process_batched_notifications();
}
break;
case NOTIFICATION_PROCESS:
// Skip expensive operations based on state
if (is_processing && is_ready) {
lightweight_update();
}
break;
case NOTIFICATION_EXIT_TREE:
is_in_tree = false;
is_ready = false;
notification_flags = 0;
break;
}
}
private:
enum NotificationFlags {
FLAG_ENTERED_TREE = 1 << 0,
FLAG_READY = 1 << 1,
FLAG_BATCH_MASK = FLAG_ENTERED_TREE | FLAG_READY
};
bool should_process_notification(int p_what) const {
// Filter notifications based on state
if (!is_in_tree &&
(p_what == NOTIFICATION_PROCESS ||
p_what == NOTIFICATION_PHYSICS_PROCESS)) {
return false;
}
return true;
}
void process_batched_notifications() {
// Process all flagged notifications at once
notification_flags = 0;
}
void lightweight_update() {
// Optimized update path
}
};
Notification Profiling
class ProfiledNotificationNode : public Node {
GDCLASS(ProfiledNotificationNode, Node)
private:
HashMap<int, uint64_t> notification_times;
HashMap<int, uint64_t> notification_counts;
protected:
virtual void _notification(int p_what) {
uint64_t start = OS::get_singleton()->get_ticks_usec();
// Process notification
handle_notification_internal(p_what);
uint64_t elapsed = OS::get_singleton()->get_ticks_usec() - start;
// Track metrics
notification_times[p_what] += elapsed;
notification_counts[p_what]++;
// Report slow notifications
if (elapsed > 1000) { // Over 1ms
print_verbose(vformat("Slow notification %d: %d us",
p_what, elapsed));
}
}
private:
void handle_notification_internal(int p_what) {
// Actual notification handling
Node::_notification(p_what);
}
public:
void print_notification_stats() {
print_line("Notification Statistics:");
for (const KeyValue<int, uint64_t> &E : notification_counts) {
int notification = E.key;
uint64_t count = E.value;
uint64_t total_time = notification_times[notification];
uint64_t avg_time = count > 0 ? total_time / count : 0;
print_line(vformat(" Notification %d: %d calls, avg %d us",
notification, count, avg_time));
}
}
};
Conclusion
The notification system is a powerful mechanism for handling events and state changes in Godot. Understanding notification types, propagation rules, and consumption patterns enables you to build responsive and well-structured node hierarchies. Proper notification handling ensures your nodes react appropriately to engine events while maintaining performance through selective processing and optimized propagation control.