# Godot MessageQueue: How Inter-Object Communication Works in the Engine Core

> Discover how Godot's MessageQueue facilitates thread-safe inter-object communication. Learn about deferred calls and notifications in the engine core.

- Repository: [Godot Engine/godot](https://github.com/godotengine/godot)
- Tags: internals
- Published: 2026-02-26

---

**The MessageQueue is Godot’s thread-local singleton that enables deferred inter-object communication by storing callable requests, notifications, and property sets in a page-based buffer that gets flushed during the main loop.**

The `MessageQueue` is a fundamental subsystem in the [godotengine/godot](https://github.com/godotengine/godot) repository that solves the problem of safe, order-preserving communication between objects across different threads and engine phases. Understanding this mechanism is essential for advanced engine development and debugging deferred call behavior.

## What Is the Godot MessageQueue?

At its core, the `MessageQueue` is a **thread-local singleton** that owns a `CallQueue` instance. Rather than executing method calls immediately, the queue stores **messages**—binary descriptors representing deferred operations—until the engine reaches a safe point in the main loop.

The architecture relies on **paged memory allocation**. Each `CallQueue` maintains a list of fixed-size pages (`PAGE_SIZE_BYTES = 4096`), where each page stores raw binary data for one or more `Message` structures plus their serialized `Variant` arguments. This design minimizes allocation overhead during high-frequency deferred calls.

### Core Message Types

The queue supports three distinct operation types defined in [`core/object/message_queue.h`](https://github.com/godotengine/godot/blob/main/core/object/message_queue.h):

- **TYPE_CALL** – Stores a `Callable` and its arguments for later invocation via `Callable::callp()`.
- **TYPE_NOTIFICATION** – Queues an `Object::notification()` call with a specific notification constant.
- **TYPE_SET** – Defers a property assignment via `Object::set()`.

## How MessageQueue Enables Inter-Object Communication

Inter-object communication through the `MessageQueue` follows a strict lifecycle: **packing**, **queuing**, **flushing**, and **execution**. This path ensures that calls made during physics processing, rendering, or from secondary threads execute safely during the main loop iteration.

### The Deferred Call Path

When a script calls `call_deferred("method_name", args)` or C++ code invokes `Object::call_deferred()`, the request enters `Object::_call_deferred_bind()` in [`core/object/object.cpp`](https://github.com/godotengine/godot/blob/main/core/object/object.cpp). This function:

1. Constructs a `Callable` object targeting the specific object ID and method name.
2. Packages the arguments into a `Variant` array.
3. Pushes the callable to the thread-local `MessageQueue` via `push_callable()`.

### Message Flushing and Execution

During each frame, the engine calls `MessageQueue::flush()` (defined in [`core/object/message_queue.cpp`](https://github.com/godotengine/godot/blob/main/core/object/message_queue.cpp)). The flush process:

1. **Locks** the queue mutex to prevent concurrent modifications.
2. **Iterates** through all allocated pages, extracting each `Message` header.
3. **Reconstructs** the original arguments from the page’s binary buffer.
4. **Dispatches** the operation:
   - For `TYPE_CALL`: Invokes `_call_function()` which executes `Callable::callp()`.
   - For `TYPE_NOTIFICATION`: Calls `Object::notification()` directly.
   - For `TYPE_SET`: Invokes `Object::set()` with the deferred property value.
5. **Clears** the queue pages, resetting the buffer for the next frame.

The mutex implementation uses `LOCK_MUTEX`/`UNLOCK_MUTEX` macros that skip locking when operating on the thread-singleton queue, preventing deadlocks when the same thread re-enters the queue during a flush.

## Thread Safety and Memory Architecture

The `MessageQueue` architecture separates **thread-local** and **global** singleton instances. `MessageQueue::get_singleton()` returns `thread_singleton` for the current thread or `main_singleton` for the primary engine thread. This design allows worker threads to queue deferred calls that execute on their owning threads without cross-thread contention.

Memory management uses a **page pool** system. When a page fills (exceeding 4096 bytes), the queue allocates a new page from the pool. During flush, pages return to the pool rather than being freed, eliminating allocation overhead during steady-state engine operation.

## Practical Code Examples

### GDScript: Using call_deferred

The most common high-level interaction with the `MessageQueue` occurs through GDScript’s `call_deferred` method:

```gdscript
extends Node

var health = 100

func _ready():
    # Schedule damage application after the current frame

    call_deferred("apply_damage", 10)

func apply_damage(amount):
    health -= amount
    print("Health reduced to: ", health)

```

Under the hood, this translates to `Object::_call_deferred_bind()` in [`core/object/object.cpp`](https://github.com/godotengine/godot/blob/main/core/object/object.cpp), which pushes a `TYPE_CALL` message onto the thread-local queue.

### C++: Direct Queue Access

For engine developers or GDExtension authors, direct queue manipulation provides finer control:

```cpp
#include "core/object/message_queue.h"

void schedule_delayed_operation(Object *target) {
    Callable callable(target, "do_something");
    Variant arg = 42;
    
    // Push to the global MessageQueue
    MessageQueue::get_singleton()->push_callable(callable, arg);
}

```

This bypasses the `Object` API and directly appends a message to the `CallQueue` pages.

### Posting Notifications

The engine uses `TYPE_NOTIFICATION` messages for deferred event distribution:

```cpp
// Post a custom notification to be handled next frame
int NOTIF_CUSTOM_EVENT = 1000;
MessageQueue::get_singleton()->push_notification(
    target->get_instance_id(), 
    NOTIF_CUSTOM_EVENT
);

```

When flushed, this invokes `target->notification(NOTIF_CUSTOM_EVENT)`, allowing objects to respond to events during the main loop iteration.

### Thread-Safe Property Sets

For modifying properties from worker threads without immediate synchronization:

```cpp
// Thread-safe deferred property modification
StringName prop_name = "position";
Vector2 new_position = Vector2(100, 200);

MessageQueue::get_singleton()->push_set(
    target->get_instance_id(),
    prop_name,
    new_position
);

```

The queue stores a `TYPE_SET` message that executes `target->set("position", new_position)` during the next flush cycle.

## Key Source Files

Understanding the `MessageQueue` requires familiarity with three primary files in the Godot repository:

- **[`core/object/message_queue.h`](https://github.com/godotengine/godot/blob/main/core/object/message_queue.h)** – Defines the `CallQueue` class, `Message` structure, page constants (`PAGE_SIZE_BYTES = 4096`), and the `MessageQueue` singleton interface.
- **[`core/object/message_queue.cpp`](https://github.com/godotengine/godot/blob/main/core/object/message_queue.cpp)** – Implements page allocation, message packing (`push_callablep`), mutex handling (`LOCK_MUTEX`/`UNLOCK_MUTEX`), and the flush logic that dispatches queued operations.
- **[`core/object/object.cpp`](https://github.com/godotengine/godot/blob/main/core/object/object.cpp)** – Contains the high-level API entry points including `Object::call_deferred()` and `Object::_call_deferred_bind()`, which bridge script-level deferred calls to the underlying queue system.

## Summary

- The **MessageQueue** is a thread-local singleton managing deferred operations through a `CallQueue` instance.
- It stores three message types—**TYPE_CALL**, **TYPE_NOTIFICATION**, and **TYPE_SET**—in fixed 4096-byte pages to minimize allocation overhead.
- **Deferred communication** flows from `Object::call_deferred()` through `Object::_call_deferred_bind()` into `MessageQueue::push_callable()`, finally executing during `MessageQueue::flush()` in the main loop.
- **Thread safety** is ensured through mutex protection with re-entrancy guards, allowing the same thread to safely flush while pushing new messages.
- Direct C++ access via `push_callable()`, `push_notification()`, and `push_set()` provides engine developers with fine-grained control over inter-object communication timing.

## Frequently Asked Questions

### What is the difference between call_deferred and a regular method call in Godot?

A regular method call executes immediately on the current thread and stack, while `call_deferred` serializes the callable and arguments into a `MessageQueue` entry that executes during the next main loop iteration. This deferred approach prevents re-entrancy issues during scene tree modifications and ensures thread-safe communication when calling from worker threads to the main thread.

### How does Godot's MessageQueue handle thread safety?

The `MessageQueue` uses a `Mutex` protected by `LOCK_MUTEX` and `UNLOCK_MUTEX` macros to serialize access to the `CallQueue`. However, when operating on the thread-local singleton (`thread_singleton`), the macros skip locking to prevent deadlocks during re-entrancy—allowing the same thread to push messages while flushing the queue. For cross-thread communication, the global `main_singleton` requires full mutex acquisition.

### What are the performance implications of using call_deferred?

Each deferred call allocates space in a 4096-byte page within the `CallQueue`, copying the `Callable` and `Variant` arguments into contiguous memory. While this avoids per-call heap allocation overhead through page pooling, excessive deferred calls can still consume significant memory bandwidth during the flush phase. For high-frequency operations (e.g., every frame), direct method calls are preferred; deferred calls are optimized for sporadic, timing-critical, or cross-thread communication.

### Can I use MessageQueue directly in GDScript?

No, the `MessageQueue` API is not exposed to GDScript. GDScript users interact with the system through high-level methods like `call_deferred()`, `set_deferred()`, and `notify_deferred()` (via `Object` methods). Direct access to `MessageQueue::push_callable()`, `push_notification()`, and `push_set()` is only available in C++ engine code or GDExtension plugins where you include [`core/object/message_queue.h`](https://github.com/godotengine/godot/blob/main/core/object/message_queue.h).