How EventBus Pub/Sub Communication Works Between Fincept Modules
Fincept modules communicate through a singleton EventBus that enables decoupled publish/subscribe patterns using std::function handlers and thread-safe Qt queued connections.
The FinceptTerminal codebase relies on a lightweight EventBus implementation to enable loose coupling between UI screens, trading engines, and data tools. This publish/subscribe (pub/sub) architecture allows any module to broadcast events without hard-wired dependencies on listeners. Understanding how this EventBus pub/sub communication works is essential for extending the platform or debugging event flows across the fincept-qt source tree.
Core Architecture of the EventBus
The EventBus is implemented as a singleton QObject defined in fincept-qt/src/core/events/EventBus.h and implemented in fincept-qt/src/core/events/EventBus.cpp. It maintains an internal map of event names to handler lists, providing three primary operations:
subscribe(event, handler): Registers astd::function<void(const QVariantMap&)>callback for a specific event string and returns a HandlerId (integer) for later removal.publish(event, data): Dispatches aQVariantMappayload to all registered handlers for that event name.unsubscribe(id): Removes a handler using the HandlerId returned during subscription.
The singleton pattern (EventBus::instance()) guarantees that every module references the same bus instance throughout the application lifecycle. Because the class inherits from QObject, it leverages Qt's meta-object system to ensure handlers execute on the correct thread.
Real-World Pub/Sub Patterns in Fincept
The Fincept codebase demonstrates EventBus pub/sub communication across multiple modules without compile-time coupling:
Navigation Events
- Publisher:
NavigationTools.cpp(line 115) emits"nav.switch_screen"with a payload containing the target screen name. - Subscriber:
MainWindow.cpp(line 274) listens for"nav.switch_screen"to execute actual screen transitions. - Additional Publisher:
SettingsScreen.cpp(line 1954) publishes the same event when users click navigation buttons.
Research Workflows
- Publisher:
CommandBar.cpp(line 1267) broadcasts"equity_research.load_symbol"when users enter ticker symbols. - Subscriber:
EquityResearchScreen.cpp(line 41) subscribes to load financial data for the requested symbol without directly referencing the command bar.
Trading System Events
- Publisher:
PaperTrading.cpp(line 319) emits"paper_trading.order_filled"after simulated trades execute. - Publisher:
OrderMatcher.cpp(line 282) broadcasts"paper_trading.position_update"when positions change. - Publisher:
WatchlistTools.cpp(line 70) publishes"watchlist.created","watchlist.deleted", and"watchlist.updated"for CRUD operations.
This decoupling allows the trading engine to emit events without knowing which UI screens (if any) are listening, and allows screens to react to data changes without importing trading module headers.
Thread Safety and Dispatch Mechanism
The EventBus guarantees thread safety through Qt's queued connection mechanism. When publish() is called from a worker thread, the implementation marshals the call to the bus's own thread (the UI thread) before invoking handlers.
As implemented in fincept-qt/src/core/events/EventBus.cpp, the dispatch uses QMetaObject::invokeMethod with Qt::QueuedConnection. This ensures that UI code never executes off the main thread, preventing race conditions in widget updates while allowing background tasks to trigger UI changes safely.
Implementing Pub/Sub in Practice
To participate in EventBus pub/sub communication, modules follow a three-step pattern:
Subscribing to Events
// In a screen constructor (e.g., EquityResearchScreen.cpp)
handlerId = EventBus::instance().subscribe(
"equity_research.load_symbol",
[this](const QVariantMap& data) {
QString symbol = data.value("symbol").toString();
loadFinancialData(symbol);
});
Store the returned HandlerId as a member variable for cleanup.
Publishing Events
// When a user action triggers navigation (e.g., CommandBar.cpp)
QVariantMap payload;
payload.insert("symbol", userInput);
EventBus::instance().publish("equity_research.load_symbol", payload);
Unsubscribing on Destruction
// In the class destructor to prevent dangling callbacks
EventBus::instance().unsubscribe(handlerId);
This pattern appears throughout fincept-qt/src/screens/ and fincept-qt/src/mcp/tools/, ensuring widgets clean up their subscriptions when destroyed.
Summary
- EventBus is a singleton QObject providing centralized pub/sub functionality via
subscribe(),publish(), andunsubscribe(). - Handlers are
std::function<void(const QVariantMap&)>callbacks identified by HandlerId integers. - Event names are plain strings (e.g.,
"nav.switch_screen","paper_trading.order_filled") with no centralized registry required. - Thread safety is enforced through Qt queued connections, ensuring handlers always run on the UI thread.
- Modules in
fincept-qt/src/trading/publish business events while modules infincept-qt/src/screens/subscribe, maintaining zero compile-time dependencies between layers.
Frequently Asked Questions
How does the EventBus ensure thread safety when publishing from background threads?
The EventBus uses QMetaObject::invokeMethod with Qt::QueuedConnection inside EventBus::publish(). If a worker thread calls publish, Qt automatically queues the handler invocation to the bus's thread (the main UI thread), ensuring UI updates never occur from background contexts.
What data type does the EventBus use for event payloads?
All payloads are QVariantMap (Qt's variant dictionary). Publishers insert key-value pairs using payload.insert("key", value), and subscribers extract data using data.value("key").toType(). This provides type-safe, flexible data transfer without custom event classes.
Can a module both publish and subscribe to the same event?
Yes. Any module can call both subscribe() and publish() for identical event strings. The bus maintains a list of all registered handlers and invokes them sequentially when publish() is called, regardless of whether the publisher is also a subscriber.
How do I remove an event subscription when a widget is destroyed?
Store the HandlerId returned by subscribe() as a member variable (typically int eventHandlerId_). In the widget's destructor, call EventBus::instance().unsubscribe(eventHandlerId_). This prevents callbacks from firing after the widget's memory is released.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →