Skip to content
Tuxedo CatSTUDIO
Menu

SignalBus Known Limitations

Review draft. This source page has not completed website editorial and release-specific verification.

SignalBus 1.0.0 is designed as a local, game-thread-only publisher-subscriber system for Unreal Engine.

The limitations below are intentional. Some protect correctness and simplicity, while others represent features that may be considered in future releases.


SignalBus publishing, subscribing, unsubscribing, queue processing, diagnostics, and warning analysis must occur on the Unreal game thread.

You should call SignalBus from:

  • Actors
  • Actor Components
  • Game Instance systems
  • World systems
  • Blueprint graphs
  • Game-thread callbacks
  • Replication callbacks such as OnRep
  • RPC handlers executing on the game thread

You should not call SignalBus directly from:

  • Worker threads
  • Async tasks running outside the game thread
  • Background file-loading callbacks
  • Custom thread pools
  • External SDK callbacks that execute on another thread
AsyncTask(ENamedThreads::GameThread, [WeakThis]()
{
if (WeakThis.IsValid())
{
// Publish through SignalBus here.
}
});
Async(EAsyncExecution::ThreadPool, []()
{
// Do not publish directly from this worker thread.
});

SignalBus interacts with Unreal objects, reflected payloads, subsystem state, Blueprint callbacks, diagnostics, and editor tooling. Keeping all access on the game thread avoids:

  • Race conditions
  • UObject lifetime hazards
  • Locking complexity
  • Non-deterministic callback order
  • Thread-unsafe Blueprint execution

SignalBus does not send messages between a server and clients.

A message published on the server remains on the server. A message published on a client remains on that client.

This does not happen automatically:

Server SignalBus publish
-> Client SignalBus subscriber

SignalBus must be combined with Unreal networking.

Client input
-> Server RPC
-> server validates action
-> server updates authoritative state
-> state replicates to client
-> client OnRep publishes local SignalBus message
-> local client systems react
Server applies damage
-> server World SignalBus publishes Combat.DamageApplied
-> server quest and scoring systems react
-> Health replicates normally
Client receives replicated Health
-> OnRep_Health
-> client World SignalBus publishes Combat.HealthChanged
-> HUD, audio, and effects react

Networking requires:

  • Authority rules
  • RPC ownership
  • Relevancy
  • Reliability choices
  • Serialization
  • Bandwidth controls
  • Security validation

Those responsibilities belong to Unreal networking, not a local message bus.


SignalBus cannot send messages between:

  • Separate game processes
  • Dedicated server processes and client processes
  • Different applications
  • Remote tools
  • External services

Even when two processes use the same message ID and payload type, they do not share a bus.

Dedicated server process
└── local server SignalBus
Client process
└── local client SignalBus

The two buses are independent.

Use an appropriate transport such as:

  • Unreal replication
  • RPCs
  • HTTP
  • WebSockets
  • Sockets
  • Online subsystem APIs
  • Platform SDK messaging

SignalBus can still be used locally after the external message arrives.


Once a queued message is accepted, SignalBus does not expose a public API to cancel it before delivery.

You cannot currently do this:

Queue message
-> receive queue handle
-> cancel queued message

Include state in the receiving system so stale work can be ignored.

Example:

USTRUCT()
struct FLoadRequestMessage
{
GENERATED_BODY()
UPROPERTY()
int32 RequestGeneration = 0;
};

The subscriber compares the generation to the current request and ignores old messages.

Use a message that changes state rather than relying on queue cancellation.

Queue Load.Started
Queue Load.Cancelled

The subscriber decides whether work should continue.


All queued messages use FIFO order inside their owning subsystem.

A later high-importance message cannot automatically jump ahead of earlier queued messages.

Queued first -> delivered first
Queued second -> delivered second

Use separate message IDs or separate higher-level systems for urgent work.

Example:

UI.Notification.Normal
UI.Notification.Critical

The subscriber can prioritize how it handles those categories after receiving them.

Immediate dispatch is available when something must run now, but it changes callback timing and should not be used only as a substitute for queue priority.


SignalBus cannot schedule a message for a specific future time.

Deliver in 5 seconds
Deliver at 14:00
Deliver next frame at a chosen priority
Deliver after three queue passes

Use Unreal timing systems:

  • FTimerManager
  • Latent Blueprint nodes
  • Tick-based scheduler systems
  • Gameplay Ability System timers
  • Custom task managers

When the timer expires, publish the SignalBus message.

Example:

World->GetTimerManager().SetTimer(
TimerHandle,
[WeakThis]()
{
if (WeakThis.IsValid())
{
// Publish through SignalBus.
}
},
5.0f,
false);

SignalBus does not replay old messages to subscribers that register later.

Message published
-> no subscriber exists
Subscriber registers later
-> old message is not delivered

SignalBus diagnostics may retain evidence that the message occurred, but diagnostic history is not a replay source.

Store durable state separately.

Example:

Game state stores CurrentObjective
SignalBus publishes Objective.Changed

A new subscriber should:

  1. Read CurrentObjective.
  2. Subscribe for future Objective.Changed messages.

Events and state serve different purposes:

State
= what is true now
Message
= something happened

SignalBus is primarily for messages, not durable state storage.


Payload values displayed in diagnostics or the Inspector are read-only.

  • Inspect payload metadata
  • Inspect captured values when policy allows
  • Search diagnostics
  • Compare message activity
  • Review warnings
  • Trace message flow
  • Edit a payload in the Inspector
  • Resend a modified payload
  • Change a queued payload
  • Alter a previously dispatched event
  • Modify live gameplay state through the Broadcast Graph

The Inspector is observational. Allowing payload edits would introduce:

  • Unclear authority
  • Unsafe object mutation
  • Replay semantics
  • Editor/runtime coupling
  • Difficult debugging side effects

Use normal gameplay or debug commands to change runtime state.


9. No Static C++ or Blueprint Source Scanning

Section titled “9. No Static C++ or Blueprint Source Scanning”

SignalBus does not scan project source code or Blueprint graphs to discover every possible publisher and subscriber.

The Inspector and Broadcast Graph show runtime-observed behavior, not every possible code path.

A publisher appears only after relevant runtime activity is observed.

A Blueprint contains a publish node but that branch never executes:

Unused branch
└── Publish Signal Immediate

That publisher may not appear in the Broadcast Graph.

Static analysis would require:

  • C++ source indexing
  • Macro interpretation
  • Blueprint asset scanning
  • Graph expansion analysis
  • Wildcard payload resolution
  • Handling dynamically constructed message IDs
  • Build-system integration

Runtime observation is simpler and reflects what actually happened.


10. Publisher Graph Nodes Depend on Retained Diagnostics

Section titled “10. Publisher Graph Nodes Depend on Retained Diagnostics”

Subscribers are registered, but publishers are not permanently registered.

Publisher nodes in the Broadcast Graph are inferred from observed diagnostic history.

A publisher may disappear from the graph when:

  • Diagnostic history is cleared
  • Old events are evicted
  • Diagnostics are disabled
  • A different context is selected
  • The editor session ends
Publisher runs once
-> appears in graph
History is cleared
-> publisher node may disappear

This does not mean routing is broken. It means the graph no longer has retained evidence of that publisher.

Subscribers can appear through active subscription snapshots because they are explicitly registered.

Publishers are discovered through runtime activity.


11. Broadcast Graph Layout Is Session-Only

Section titled “11. Broadcast Graph Layout Is Session-Only”

Broadcast Graph node positions are not saved permanently.

After closing the editor or reopening the project:

  • Manually adjusted positions may reset
  • Pan and zoom state may reset
  • The graph may rebuild from current runtime observations

The graph attempts to keep stable node identities and positions across refreshes.

Persistent layouts require:

  • Per-project storage
  • Stable identity across builds
  • Rename handling
  • Deleted-node cleanup
  • User-specific versus shared layout policy
  • Source-control considerations

SignalBus 1.0.0 keeps the graph observational and temporary.


SignalBus emits structured Unreal Trace events, but Unreal Insights does not include a dedicated SignalBus tab or timing track.

  • SignalBus trace channel
  • Structured runtime trace events
  • Real .utrace capture
  • Editor-side trace analysis parser
  • Automated trace verification
  • Dedicated SignalBus Insights window
  • Dispatch timeline track
  • Subscriber table
  • Queue table
  • Warning timeline
  • Context filtering UI inside Insights

Use:

SignalBus Runtime Inspector

for live debugging.

Use:

SignalBus.Editor.Trace.AnalysisVerification

to verify structured trace capture and parsing.

The standard Unreal Insights Timing window does not automatically display custom SignalBus event rows.


13. Trace Capture Is Verification-Oriented

Section titled “13. Trace Capture Is Verification-Oriented”

SignalBus trace support currently focuses on recording structured events and verifying them through an editor-side parser.

  • Context identity
  • Scope
  • PIE instance
  • Message ID
  • Payload type
  • Subscription handle
  • Queue ID
  • Dispatch ID
  • Dispatch mode
  • Callback duration
  • Dispatch duration
  • Queue delay
  • Warning state changes
  • Full payload values
  • Hidden payload fields
  • Arbitrary serialized payload bytes
  • Editable runtime state

Payload tracing is metadata-only. Properties marked with SignalBus hidden behavior are not emitted as trace values.


SignalBus 1.0.0 uses fixed queue limits.

Current limits:

Queue capacity: 10,000 entries per subsystem
Queue processing limit: 1,000 entries per pass

When the queue reaches capacity, further enqueue attempts are rejected until space becomes available.

During processing, SignalBus handles at most the per-pass limit. Remaining entries wait for later passes.

10,000 messages queued
-> accepted
10,001st message
-> rejected as QueueFull

Processing:

Pass 1 -> up to 1,000
Pass 2 -> up to 1,000
...

They protect the game thread from:

  • Unbounded memory growth
  • Extremely long queue-processing frames
  • Accidental infinite producers
  • Runaway Blueprint logic

These values are not currently configurable through project settings.


15. Diagnostic and Warning Thresholds Are Hard-Coded

Section titled “15. Diagnostic and Warning Thresholds Are Hard-Coded”

SignalBus 1.0.0 uses fixed limits and thresholds for diagnostics and warnings.

Examples include:

Diagnostic history: up to 2,000 events per subsystem
Warning history: up to 1,000 warnings per subsystem
Payload snapshot budget: 64 KiB
Container inspection limit: 64 elements
Nested payload depth: 8
Scalar text limit: 1,024 characters

Older diagnostic or warning entries are evicted as limits are reached.

Large or deeply nested payload snapshots may be truncated.

A payload contains an array with 500 elements:

First allowed elements captured
Remaining elements omitted
Snapshot marked truncated

These limits protect editor responsiveness and runtime memory.

They are not configurable through plugin settings in 1.0.0.


16. Blueprint Listener Callbacks Copy Payloads

Section titled “16. Blueprint Listener Callbacks Copy Payloads”

Before invoking a Blueprint listener event, SignalBus copies payload data into a safe Blueprint wrapper.

Blueprint must not receive a pointer to temporary native dispatch memory.

The copy ensures:

  • Payload lifetime is valid during the Blueprint callback
  • Queued payload ownership remains safe
  • Blueprint does not access freed native memory
  • Immediate native routing semantics remain correct

Copying has a runtime cost proportional to payload size.

Small gameplay payloads should be inexpensive.

Large structs, arrays, maps, nested containers, or strings can be more expensive.

Prefer compact event payloads.

Good:

USTRUCT(BlueprintType)
struct FHealthChangedMessage
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite)
float OldHealth = 0.0f;
UPROPERTY(BlueprintReadWrite)
float NewHealth = 0.0f;
};

Avoid treating SignalBus as a bulk-data transport for:

  • Large inventories
  • Full save-game objects
  • Huge arrays
  • Large text blobs
  • Asset data copies

Pass identifiers or lightweight summaries and let the receiver query durable state where appropriate.


17. FInstancedStruct and StructUtils Compatibility

Section titled “17. FInstancedStruct and StructUtils Compatibility”

SignalBus currently uses FInstancedStruct for safe reflected payload ownership.

In UE 5.7, the current StructUtils dependency produces a compatibility or deprecation concern that should be reviewed when Epic provides a confirmed stable replacement path.

FInstancedStruct provides:

  • Exact reflected struct identity
  • Owned payload memory
  • Safe queued payload copies
  • Generic payload support
  • Blueprint-compatible reflected struct handling

This is a compatibility concern, not a known runtime failure.

SignalBus 1.0.0 builds and tests successfully with the current implementation.

When upgrading Unreal Engine:

  1. Review Epic’s current FInstancedStruct location and module requirements.
  2. Build both runtime and editor targets.
  3. Run all SignalBus runtime and editor tests.
  4. Validate queued payload ownership.
  5. Validate Blueprint payload extraction.
  6. Validate packaged Development and Shipping builds.

Do not replace the payload-storage layer without confirming equivalent ownership and reflection behavior.


Some reflected test payloads or helper types remain compiled because Unreal Header Tool processing does not always allow them to be conditionally removed cleanly.

These types may exist in compiled modules, but they are inert unless:

  • Used by automation
  • Explicitly instantiated
  • Manually placed in a test map

They do not:

  • Publish automatically
  • Subscribe automatically
  • Run every frame
  • Affect production routing
  • Add network behavior
  • Change packaged gameplay by themselves

Reflected Unreal types require stable UHT visibility and generated code. Removing them conditionally can create fragile build behavior.

They remain private validation support rather than public production APIs.


  • Local gameplay events
  • Decoupled UI updates
  • World-specific notifications
  • Game Instance session events
  • Immediate synchronous fan-out
  • Deferred FIFO fan-out
  • Blueprint and C++ interoperability
  • Runtime diagnostics and topology inspection
  • Network transport
  • Authoritative client requests
  • Worker-thread messaging
  • Durable event sourcing
  • Large data transfer
  • Persistent graph authoring
  • Arbitrary runtime state editing
  • Automatic source-code discovery

Limitation Practical alternative
Game-thread-only Return to game thread with AsyncTask
No replication Use RPCs or replicated properties
No cross-process routing Use networking, sockets, HTTP, or platform APIs
No queue cancellation Ignore stale messages using IDs or generations
No queue priority Separate message categories or use immediate dispatch carefully
No scheduling Use FTimerManager or another scheduler
No replay Store durable state separately
No payload editing Use normal debug or gameplay commands
No static scanning Use runtime diagnostics and Broadcast Graph
Publisher graph depends on history Keep diagnostics enabled and retain history
Session-only graph layout Reorganize during each session
No custom Insights tab Use Inspector and trace verification test
Hard-coded limits Design within documented limits
Blueprint payload copy Keep Blueprint payloads compact
StructUtils concern Revalidate during engine upgrades
Reflected test types compiled Leave inert unless running tests