You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The current event system is built on virtual inheritance and RTTI. Every event carries a vtable pointer and a virtual destructor, dispatching goes through dynamic_cast, and event categories are represented as bitfield enums that layers test at runtime. This adds overhead at every dispatch site, forces all event types into a single inheritance hierarchy, and makes it impossible for user-defined events to participate cleanly in the same system.
The goal of this refactor is to eliminate all of that with a design that has zero runtime overhead for type identification, no heap allocation in the hot path, and a clean separation between hardware/OS events (which need ordered, synchronous propagation) and other subsystem events (which benefit from a decoupled pub/sub model).
Proposed Solution
Event types become plain structs satisfying an IsEvent concept (standard layout, trivially copyable, named). There are no base classes, no RTTI, and no global type registries.
Replace the current system with a hybrid architecture:
Input events use a closed std::variant (InputEvent) dispatched back-to-front through the LayerStack. This preserves the existing propagation semantics with no virtual calls.
Non-input events use a new EventBus with a subscriber registry,
compile-time sequential type IDs, supporting both immediate dispatch and deferred dispatch with a double-buffered queue safe for cross-thread use.
Key Design Decisions
Area
Decision
Input/Window routing
std::variant-based InputEventData, dispatched through Layer::OnEvent() back-to-front
Gameplay/Subsystem routing
EventBus with pub/sub, immediate + double-buffered queued
Type identification
consteval FNV-1a hash of T::Name (deterministic, no global state)
EventBus.cppm / EventBus.cpp: new pub/sub system with compile-time sequential type IDs, Subscribe, Unsubscribe, Dispatch, QueueEvent, Flush, double-buffered queue write buffer
Problem
The current event system is built on virtual inheritance and RTTI. Every event carries a vtable pointer and a virtual destructor, dispatching goes through
dynamic_cast, and event categories are represented as bitfield enums that layers test at runtime. This adds overhead at every dispatch site, forces all event types into a single inheritance hierarchy, and makes it impossible for user-defined events to participate cleanly in the same system.The goal of this refactor is to eliminate all of that with a design that has zero runtime overhead for type identification, no heap allocation in the hot path, and a clean separation between hardware/OS events (which need ordered, synchronous propagation) and other subsystem events (which benefit from a decoupled pub/sub model).
Proposed Solution
Event types become plain structs satisfying an
IsEventconcept (standard layout, trivially copyable, named). There are no base classes, no RTTI, and no global type registries.Replace the current system with a hybrid architecture:
std::variant(InputEvent) dispatched back-to-front through theLayerStack. This preserves the existing propagation semantics with no virtual calls.EventBuswith a subscriber registry,Key Design Decisions
std::variant-basedInputEventData, dispatched throughLayer::OnEvent()back-to-frontEventBuswith pub/sub, immediate + double-buffered queuedconstevalFNV-1a hash ofT::Name(deterministic, no global state)ImGuiLayerusesIsMouseEvent()/IsKeyboardEvent()helpersfmt::formatter<T>specializations, external to event structsTasks
Core
Event.cppm: complete rewrite with plain structs,IsEventconcept,InputEvents.cppm:InputEventvariant,RoutedInputEvent,InputEventDispatcher,IsMouseEvent/IsKeyboardEventhelpers,fmtformattersEventBus.cppm/EventBus.cpp: new pub/sub system with compile-time sequential type IDs,Subscribe,Unsubscribe,Dispatch,QueueEvent,Flush, double-buffered queue write bufferCMakeLists.txt: addEventBus.cppm/EventBus.cpp, removeAsyncEventBusfilesInterface updates (depend on
Event.cppm)Layer.cppm:OnEvent(Event&)→OnEvent(InputEventData&)Window.cppm:EventCallbackFntype updated tostd::function<void(InputEventData&)>GlfwWindow.cpp: GLFW callbacks construct plain structs wrapped inInputEventDataApplication.cppm/Application.cpp: updateOnEvent, addimport Nodens.EventBus, addEventBus::Get().flush()to main loopImGuiLayer.cppm/ImGuiLayer.cpp:OnEvent(InputEventData&), useIsMouseEvent/IsKeyboardEventhelpersNodens.cppm: replaceexport import Nodens.AsyncEventBuswithexport import Nodens.EventBusExamples
PlanetaryScanLayer.cppm/.cpp:PlanetaryScanEventbecomes a plain struct; subscribe viaEventBusCircularWave3DLayer.cppm/.cpp:OnEventupdated toRoutedInputEvent&, dispatch via newEventDispatcherCleanup
AsyncEventBus.cppmandAsyncEventBus.cppBreaking Changes
This is a breaking change for any downstream code using the
Layerinterface.Acceptance Criteria
example-asyncevent: scatter plot and latency monitor populate correctly at runtimeexample-circularwave3d: rendering is correct and window resize worksAsyncEventBus,EventType,EventCategory, or theEventbase class remain in the codebase