Core Concepts
Architecture Overview
GDSignalBus uses a global singleton pattern to provide a high-performance signal bus system for Godot 4. Its core architecture includes:
- Global Signal Bus: Centralized management of all signals
- Channel System: Signal grouping and isolation
- Instance-level Signals: Object-level unique signals
- Async Processing: Background thread support
- Signal Filtering: Conditional subscriptions
- Signal Bridging: Native signal connections
Global Signal Bus
Singleton Pattern
GDSignalBus uses a global singleton pattern, ensuring only one signal bus instance exists throughout the game:
gdscript
# Get singleton
var bus = SignalBus.get_singleton()
# Accessible from anywhere
bus.emit("global_event", ["data"])
bus.subscribe("global_event", handler)Advantages
- Decoupling: Eliminates direct dependencies between nodes
- Centralized Management: All signals managed in one place
- Performance Optimization: High-performance signal distribution with C++ implementation
- Memory Safety: Automatic cleanup of invalid connections
Channel System
Channel Concept
Channels allow grouping signals to avoid naming conflicts:
gdscript
# Same signal name in different channels
bus.emit_on_channel("gameplay", "player_action", ["jump"])
bus.emit_on_channel("ui", "player_action", ["menu_open"])
# Listen to specific channels
bus.subscribe_on_channel("gameplay", "player_action", _on_gameplay_action)
bus.subscribe_on_channel("ui", "player_action", _on_ui_action)Channel Isolation
- Namespace: Avoid signal name conflicts
- Logical Grouping: Related signals in same channel
- Performance Optimization: Reduce unnecessary signal processing
Instance-level Signals
Unique Identification
Provide unique signals for different object instances using the same script:
gdscript
# Player.gd
extends Node
func _ready():
# Create unique signal for each instance
bus.subscribe_instance(self, "health_changed", _on_health_changed)
func take_damage(amount):
health -= amount
# Send instance-level signal
bus.emit_instance(self, "take_damage", [amount])
func _on_health_changed(args):
var amount = args[0]
update_health_bar(health)Application Scenarios
- Multiplayer Games: Independent events for each player
- Enemy AI: State changes of different enemies
- UI Components: Different instances of same component
Signal Filtering
Conditional Subscriptions
Only receive signals that meet specific conditions:
gdscript
# Only receive specific type of item collection events
bus.subscribe_filtered("item_collected",
func(args):
var value = args[0]
return value > 10,
_on_valuable_item
)
# Only receive player health events below 30%
bus.subscribe_filtered("health_changed",
func(args):
var health = args[0]
return health < 30,
_on_low_health
)Filter Types
- Type Filtering: Based on parameter types
- Value Filtering: Based on parameter values
- Custom Filtering: Complex conditional logic
Async Processing
Background Threads
Time-consuming operations are processed in background threads:
gdscript
# Async file loading
bus.subscribe_async_with_callbacks("load_file",
func(args):
var path = args[0]
# Simulate time-consuming operation
OS.delay_msec(1000)
return "Loaded data from " + path,
_on_load_complete,
_on_load_error
)
# Emit async signal
bus.emit_async("load_file", ["save_data.dat"])Async Advantages
- Non-blocking: Doesn't block main thread
- Performance Improvement: Maintains smooth game experience
- Resource Management: Automatic thread lifecycle management
Signal Bridging
Native Signal Connections
Connect Godot node's native signals to SignalBus:
gdscript
# Bridge button signal
bus.bridge_signal($Button, "pressed")
# Forward signal to different name
bus.relay_node_signal($Button, "pressed", "ui_button_pressed")
# Subscribe to bridged signals
bus.subscribe("pressed", _on_button_pressed)
bus.subscribe("ui_button_pressed", _on_ui_button_pressed)Bridging Advantages
- UI Decoupling: Separate UI logic from game logic
- Centralized Management: All UI events processed through SignalBus
- Flexible Configuration: Can rename and reroute signals
Memory Management
Automatic Cleanup
GDSignalBus automatically manages memory:
gdscript
# Connections automatically cleaned up when object is freed
func _exit_tree():
# Optional: Explicit cleanup (recommended)
bus.unsubscribe_all(self)
# Or rely on automatic cleanupBest Practices
- Timely Disconnection: Disconnect when no longer needed
- Avoid Circular References: Be mindful of reference relationships between objects
- Use Weak References: Use weak references when possible
Performance Considerations
Optimization Recommendations
- Reasonable Channel Usage: Avoid too many channels
- Signal Filtering: Reduce unnecessary callbacks
- Batch Processing: Combine related signals
- Async Processing: Use async for time-consuming operations
Performance Metrics
- Signal Distribution: High-performance C++ implementation
- Memory Usage: Minimized memory footprint
- CPU Overhead: Zero-cost abstraction design
Debugging Tools
Built-in Debugging
gdscript
# Enable debug mode
bus.set_debug_enabled(true)
# View signal statistics
print("Signal list: ", bus.get_signal_list())
print("Channel list: ", bus.get_channel_list())
print("Subscriber count: ", bus.get_subscriber_count("player_died"))
print("Total signals emitted: ", bus.get_total_signals_emitted())Debugging Features
- Signal Statistics: View signal usage
- Performance Analysis: Monitor signal distribution performance
- Connection Tracking: Debug signal connection issues