From 185d151238f8f3beca7c06fd7dde12d259415dd0 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Sun, 4 Oct 2026 20:40:28 +0300 Subject: [PATCH 1/6] fix: make event dispatch safe and cheap to register Handlers removed during dispatch are skipped instead of called, Event::Handled stops propagation, ids come from a counter instead of random_device, and EventSubscription unregisters on destruction. Application now unregisters its own handlers. --- include/public/Core/Application.hpp | 4 + include/public/Events/Delegate.hpp | 59 +++---- include/public/Events/Event.hpp | 232 +++++++++++++++++++++------- src/Core/Application.cpp | 14 +- 4 files changed, 222 insertions(+), 87 deletions(-) diff --git a/include/public/Core/Application.hpp b/include/public/Core/Application.hpp index 896cafaa..def68e94 100644 --- a/include/public/Core/Application.hpp +++ b/include/public/Core/Application.hpp @@ -215,6 +215,10 @@ class ENGINE_API Application { Timer FrameTimer; + EventSubscription m_keyPressedSub; + EventSubscription m_windowResizeSub; + EventSubscription m_windowFullScreenSub; + // Deferred resize — set in the event handler, applied at start of next frame bool m_pendingResize = false; uint32_t m_pendingResizeW = 0; diff --git a/include/public/Events/Delegate.hpp b/include/public/Events/Delegate.hpp index 932a27af..6470b12c 100644 --- a/include/public/Events/Delegate.hpp +++ b/include/public/Events/Delegate.hpp @@ -1,15 +1,15 @@ #ifndef _DELEGATE_H_ #define _DELEGATE_H_ +#include +#include +#include #include -#include #include +#include #include #include - -#include -#include -#include +#include namespace Sleak { /// Type-erased callable interface storable in a homogeneous handler list. @@ -54,8 +54,28 @@ namespace Sleak { /// @ingroup events class EventDelegateBase : public IDelegate { public: - virtual ~EventDelegateBase() = default; - virtual std::string GetID() = 0; + EventDelegateBase() { GenerateID(); } + virtual ~EventDelegateBase() = default; + + /// Assigns a fresh process-unique ID, used to identify this delegate + /// for unregistration. + std::string GenerateID() { + static std::atomic nextHandle{0}; + handle = nextHandle.fetch_add(1, std::memory_order_relaxed) + 1; + id = std::to_string(handle); + return id; + } + + std::string GetID() override { return id; } + /// Numeric form of GetID(). + uint64_t GetHandle() const { return handle; } + + private: + friend class EventDispatcher; + + uint64_t handle = 0; + std::string id; + bool removed = false; }; /// Delegate bound to a single EventT callback; holds a non-owning pointer @@ -66,9 +86,7 @@ namespace Sleak { public: using FunctionType = std::function; - EventDelegate(FunctionType func) : function(func) { - GenerateID(); - } + EventDelegate(FunctionType func) : function(std::move(func)) {} void SetEvent(const EventT& event) { eventPtr = &event; @@ -80,29 +98,14 @@ namespace Sleak { } } - /// Assigns a fresh random 32-character ID, used to identify this delegate for unregistration. - std::string GenerateID() { - uuid = ""; - std::string str = - "0123456789!^#%&=*?+-_/" - "[]{}()" - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - std::random_device dev; - std::mt19937 rand(dev()); - std::uniform_int_distribution<> dist(0,str.size() - 1); - - for(int i = 0; i < 32; i++) - uuid += str[dist(rand)]; - - return uuid; + /// Calls the handler with event directly. + void Invoke(const EventT& event) { + if (function) function(event); } - std::string GetID() override { return uuid; } - private: FunctionType function; const EventT* eventPtr = nullptr; // Store a pointer to avoid copying - std::string uuid; }; /// Fan-out list of Delegate instances, all invoked together via Broadcast. diff --git a/include/public/Events/Event.hpp b/include/public/Events/Event.hpp index 68daaf55..13a9e46a 100644 --- a/include/public/Events/Event.hpp +++ b/include/public/Events/Event.hpp @@ -2,18 +2,23 @@ #define _EVENT_H_ #include -#include +#include +#include +#include #include +#include #include #include -#include - #define BIND_LAMBDA(fn) [this](auto&&... args) -> decltype(auto) { return this->fn(std::forward(args)...); } #define BIND_FUNC_0(Function, class_name) std::bind(&class_name::Function, this) #define BIND_FUNC_1(Function, class_name) std::bind(&class_name::Function, this, std::placeholders::_1) -#define BIND_FUNC_2(Function, class_name) std::bind(&class_name::Function, this, std::placeholders::_2) -#define BIND_FUNC_3(Function, class_name) std::bind(&class_name::Function, this, std::placeholders::_3) +#define BIND_FUNC_2(Function, class_name) \ + std::bind(&class_name::Function, this, std::placeholders::_1, \ + std::placeholders::_2) +#define BIND_FUNC_3(Function, class_name) \ + std::bind(&class_name::Function, this, std::placeholders::_1, \ + std::placeholders::_2, std::placeholders::_3) #define GET_DISPATCHER Sleak::EventDispatcher::GetInstance() @@ -58,7 +63,7 @@ namespace Sleak { { return static_cast(category); } /// Base for all engine events; carries type/category identity and the - /// Handled flag consumers can set to stop further propagation. + /// Handled flag a handler can set to stop further propagation. /// @ingroup events class ENGINE_API Event { public: @@ -66,7 +71,8 @@ namespace Sleak { virtual ~Event() = default; - bool Handled = false; + /// Set from a handler to skip the handlers registered after it. + mutable bool Handled = false; virtual EventType GetEventType() const = 0; virtual const char* GetName() const = 0; @@ -79,6 +85,8 @@ namespace Sleak { } }; + class EventSubscription; + /// Static registry mapping EventType to subscribed handlers; dispatches /// events synchronously to every handler registered for its type. /// @@ -92,11 +100,16 @@ namespace Sleak { /// Both return a string id. Keep it and pass it to UnregisterEvent() /// when the subscriber goes away, typically in a scene's OnDeactivate() /// or destructor. Handlers outlive the objects they were bound to - /// otherwise, and the next dispatch calls into freed memory. + /// otherwise, and the next dispatch calls into freed memory. Subscribe() + /// does the same but returns an EventSubscription that unregisters + /// itself when destroyed. /// /// Dispatch is synchronous and runs in registration order on the - /// calling thread. The handler list is copied before iteration, so a - /// handler may register or unregister during dispatch safely. + /// calling thread, stopping early once a handler sets Event::Handled. + /// A handler may register or unregister during dispatch: a handler + /// removed mid-dispatch is not called again, and one added mid-dispatch + /// first runs on the next event. Not thread-safe; use from the main + /// thread only. /// /// @code{.cpp} /// class WorldScene : public Sleak::Scene { @@ -126,90 +139,199 @@ namespace Sleak { /// }; /// @endcode /// - /// @see Event, EventType, Events::Input::KeyPressedEvent, - /// Events::Input::MouseMovedEvent + /// @see Event, EventType, EventSubscription, + /// Events::Input::KeyPressedEvent, Events::Input::MouseMovedEvent /// @ingroup events class ENGINE_API EventDispatcher { public: - // Register a handler for any event type /// Registers a free-function/lambda callback for EventT, returning an ID for later unregistration. template static std::string RegisterEventCallback(std::function callback) { - EventType type = EventT::GetStaticType(); - - auto delegate = std::make_shared>(callback); - eventHandlers[type].push_back(delegate); - - return delegate->GetUUID(); + auto delegate = std::make_shared>( + std::move(callback)); + eventHandlers[EventT::GetStaticType()].push_back(delegate); + return delegate->GetID(); } - - // Register a member function handler + /// Registers a member-function handler bound to instance, returning an ID for later unregistration. template static std::string RegisterEventHandler(T* instance, void (T::*memberFunction)(const EventT&)) { - EventType type = EventT::GetStaticType(); - auto callback = [instance, memberFunction](const EventT& event) { (instance->*memberFunction)(event); }; - - auto delegate = std::make_shared>(callback); - eventHandlers[type].push_back(delegate); - - return delegate->GetID(); + return RegisterEventCallback(std::move(callback)); } + /// Like RegisterEventCallback(), but unregisters when the returned + /// token is destroyed. + template + [[nodiscard]] static EventSubscription Subscribe( + std::function callback); + + /// Like RegisterEventHandler(), but unregisters when the returned + /// token is destroyed. + template + [[nodiscard]] static EventSubscription Subscribe( + T* instance, void (T::*memberFunction)(const EventT&)); + /// Removes the single handler with matching id from type's handler list. - static void UnregisterEvent(EventType type, std::string id) { - for(auto it = eventHandlers[type].begin(); it != eventHandlers[type].end(); ++it) { - if((*it)->GetID() == id) { - eventHandlers[type].erase(it); - break; + static void UnregisterEvent(EventType type, const std::string& id) { + uint64_t handle = 0; + auto [end, ec] = + std::from_chars(id.data(), id.data() + id.size(), handle); + if (ec != std::errc() || end != id.data() + id.size()) return; + + auto found = eventHandlers.find(type); + if (found == eventHandlers.end()) return; + + auto& handlers = found->second; + for (size_t i = 0; i < handlers.size(); ++i) { + if (handlers[i]->GetHandle() != handle || + handlers[i]->removed) + continue; + if (dispatchDepth > 0) { + handlers[i]->removed = true; + needsCompaction = true; + } else { + handlers.erase(handlers.begin() + i); } + return; } } /// Drops every handler registered for type. static void UnregisterEvents(EventType type) { - eventHandlers[type].clear(); + auto found = eventHandlers.find(type); + if (found == eventHandlers.end()) return; + if (dispatchDepth > 0) { + MarkRemoved(found->second); + } else { + found->second.clear(); + } } /// Drops every handler for every event type. static void UnregisterAllEvents() { - eventHandlers.clear(); + if (dispatchDepth > 0) { + for (auto& [type, handlers] : eventHandlers) + MarkRemoved(handlers); + } else { + eventHandlers.clear(); + } } - // Dispatch an event to all registered handlers /// Invokes every handler registered for event's type, in registration order. template static void DispatchEvent(const EventT& event) { - EventType type = event.GetEventType(); + auto found = eventHandlers.find(event.GetEventType()); + if (found == eventHandlers.end()) return; - if (eventHandlers.find(type) == eventHandlers.end()) - return; - - // Copy the handler list so that handlers which add/remove entries - // during dispatch don't invalidate the iteration. - auto handlers = eventHandlers[type]; - for (auto& handler : handlers) { - // Try to cast to the right event delegate type - auto typedDelegate = std::dynamic_pointer_cast>(handler); - if (typedDelegate) { - typedDelegate->SetEvent(event); - typedDelegate->Execute(); - } + auto& handlers = found->second; + const size_t count = handlers.size(); + DispatchScope scope; + for (size_t i = 0; i < count && !event.Handled; ++i) { + EventDelegateBase* handler = handlers[i].get(); + if (handler->removed) continue; + if (auto* typed = + dynamic_cast*>(handler)) + typed->Invoke(event); } } - + /// Drops every handler for every event type; equivalent to UnregisterAllEvents(). - static void ClearEventHandlers() { - eventHandlers.clear(); + static void ClearEventHandlers() { UnregisterAllEvents(); } + + private: + using HandlerList = std::vector>; + + // Removals during dispatch are deferred so indices and delegates + // stay valid. + struct DispatchScope { + DispatchScope() { ++dispatchDepth; } + ~DispatchScope() { + if (--dispatchDepth == 0 && needsCompaction) Compact(); + } + }; + + static void MarkRemoved(HandlerList& handlers) { + for (auto& handler : handlers) handler->removed = true; + needsCompaction = true; + } + + static void Compact() { + needsCompaction = false; + for (auto& [type, handlers] : eventHandlers) { + std::erase_if(handlers, + [](const auto& h) { return h->removed; }); + } + } + + static inline std::unordered_map + eventHandlers; + static inline int dispatchDepth = 0; + static inline bool needsCompaction = false; + }; + + /// Move-only owner of one handler registration; unregisters it on + /// destruction or Reset(). + /// @ingroup events + class EventSubscription { + public: + EventSubscription() = default; + EventSubscription(EventType type, std::string id) + : m_type(type), m_id(std::move(id)) {} + + EventSubscription(EventSubscription&& other) noexcept + : m_type(other.m_type), m_id(std::move(other.m_id)) { + other.m_id.clear(); } - private: - static inline std::unordered_map>> eventHandlers; + EventSubscription& operator=(EventSubscription&& other) noexcept { + if (this != &other) { + Reset(); + m_type = other.m_type; + m_id = std::move(other.m_id); + other.m_id.clear(); + } + return *this; + } + + EventSubscription(const EventSubscription&) = delete; + EventSubscription& operator=(const EventSubscription&) = delete; + + ~EventSubscription() { Reset(); } + + /// Unregisters the handler now; safe to call more than once. + void Reset() { + if (m_id.empty()) return; + EventDispatcher::UnregisterEvent(m_type, m_id); + m_id.clear(); + } + + bool IsActive() const { return !m_id.empty(); } + EventType GetType() const { return m_type; } + const std::string& GetID() const { return m_id; } + + private: + EventType m_type = EventType::Unknown; + std::string m_id; }; + template + EventSubscription EventDispatcher::Subscribe( + std::function callback) { + return EventSubscription( + EventT::GetStaticType(), + RegisterEventCallback(std::move(callback))); + } + + template + EventSubscription EventDispatcher::Subscribe( + T* instance, void (T::*memberFunction)(const EventT&)) { + return EventSubscription( + EventT::GetStaticType(), + RegisterEventHandler(instance, memberFunction)); + } // Helper function for easier event dispatching /// Constructs a T from args and dispatches it through EventDispatcher. diff --git a/src/Core/Application.cpp b/src/Core/Application.cpp index 83c0eae5..8385072b 100644 --- a/src/Core/Application.cpp +++ b/src/Core/Application.cpp @@ -93,13 +93,19 @@ namespace Sleak { RenderEngine::RendererType::Vulkan, CoreWindow); } - EventDispatcher::RegisterEventHandler(this,&Application::OnKeyPressed); - EventDispatcher::RegisterEventHandler(this, &Application::OnWindowResize); - EventDispatcher::RegisterEventHandler(this, &Application::OnWindowFullScreen); - + m_keyPressedSub = + EventDispatcher::Subscribe(this, &Application::OnKeyPressed); + m_windowResizeSub = + EventDispatcher::Subscribe(this, &Application::OnWindowResize); + m_windowFullScreenSub = + EventDispatcher::Subscribe(this, &Application::OnWindowFullScreen); } Application::~Application() { + m_keyPressedSub.Reset(); + m_windowResizeSub.Reset(); + m_windowFullScreenSub.Reset(); + // Flush the GPU before tearing down any scene-owned resources (textures, // meshes, materials) — the renderer's descriptor sets still reference // samplers/image views owned by the game's scene objects. Without this From adacdde9cc08bc21e27b847d611fcd4dbc984821 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Sun, 4 Oct 2026 21:00:43 +0300 Subject: [PATCH 2/6] docs: describe the new dispatch rules and EventSubscription --- docs/pages/events_and_input.md | 37 +++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/docs/pages/events_and_input.md b/docs/pages/events_and_input.md index 0050ebbe..90f67160 100644 --- a/docs/pages/events_and_input.md +++ b/docs/pages/events_and_input.md @@ -43,7 +43,7 @@ every handler registered for that event type before returning. The event dispatch hub is `Sleak::EventDispatcher` (`include/public/Events/Event.hpp`), an all-static class backed by an -`unordered_map>>`: +`unordered_map>>`: ```cpp template @@ -52,10 +52,16 @@ static std::string RegisterEventHandler(T* instance, void (T::*memberFunction)(c template static std::string RegisterEventCallback(std::function callback); -static void UnregisterEvent(EventType type, std::string id); +static void UnregisterEvent(EventType type, const std::string& id); static void UnregisterEvents(EventType type); static void UnregisterAllEvents(); +template +static EventSubscription Subscribe(T* instance, void (T::*memberFunction)(const EventT&)); + +template +static EventSubscription Subscribe(std::function callback); + template static void DispatchEvent(const EventT& event); ``` @@ -136,16 +142,23 @@ raised from `Window::Update()` is fully handled before the frame's scene update begins. There is no event queue, no deferred delivery, and no ordering guarantee between handlers beyond registration order. -`DispatchEvent` copies the handler vector before iterating it, so a handler -may register or unregister handlers while it runs without invalidating the -loop. Handlers added during dispatch do not receive the event currently -being delivered. - -`RegisterEventHandler` returns a `std::string` id. Keep it and pass it to -`UnregisterEvent(type, id)` when the registering object dies. A handler -bound to a destroyed `this` is a dangling call, and the dispatcher has no -way to detect it. Unregister in the same place you destroy the object, -typically a scene's destructor or `OnDeactivate`. +A handler may register or unregister handlers while it runs. A handler +unregistered during dispatch is skipped for the rest of that dispatch, and +the list is compacted once the outermost dispatch returns. Handlers added +during dispatch do not receive the event currently being delivered. + +A handler can set `e.Handled = true` (the flag is `mutable`, so this works +through the `const&`) to stop the event from reaching handlers registered +after it. + +`RegisterEventHandler` returns a `std::string` id, the decimal form of a +counter. Keep it and pass it to `UnregisterEvent(type, id)` when the +registering object dies. A handler bound to a destroyed `this` is a +dangling call, and the dispatcher has no way to detect it. Unregister in +the same place you destroy the object, typically a scene's destructor or +`OnDeactivate`. Or use `Subscribe`, which returns a move-only +`EventSubscription` that unregisters itself when it is destroyed or +`Reset()`. --- From d86e3f399c3334ba80b8f61e8dce59f8456cfcca Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Sun, 4 Oct 2026 21:54:48 +0300 Subject: [PATCH 3/6] style: apply clang-format to changed lines --- include/public/Events/Event.hpp | 106 ++++++++++++++++---------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/include/public/Events/Event.hpp b/include/public/Events/Event.hpp index 13a9e46a..c0d50841 100644 --- a/include/public/Events/Event.hpp +++ b/include/public/Events/Event.hpp @@ -270,68 +270,68 @@ namespace Sleak { eventHandlers; static inline int dispatchDepth = 0; static inline bool needsCompaction = false; - }; - - /// Move-only owner of one handler registration; unregisters it on - /// destruction or Reset(). - /// @ingroup events - class EventSubscription { - public: - EventSubscription() = default; - EventSubscription(EventType type, std::string id) - : m_type(type), m_id(std::move(id)) {} - - EventSubscription(EventSubscription&& other) noexcept - : m_type(other.m_type), m_id(std::move(other.m_id)) { + }; + + /// Move-only owner of one handler registration; unregisters it on + /// destruction or Reset(). + /// @ingroup events + class EventSubscription { + public: + EventSubscription() = default; + EventSubscription(EventType type, std::string id) + : m_type(type), m_id(std::move(id)) {} + + EventSubscription(EventSubscription&& other) noexcept + : m_type(other.m_type), m_id(std::move(other.m_id)) { + other.m_id.clear(); + } + + EventSubscription& operator=(EventSubscription&& other) noexcept { + if (this != &other) { + Reset(); + m_type = other.m_type; + m_id = std::move(other.m_id); other.m_id.clear(); } + return *this; + } - EventSubscription& operator=(EventSubscription&& other) noexcept { - if (this != &other) { - Reset(); - m_type = other.m_type; - m_id = std::move(other.m_id); - other.m_id.clear(); - } - return *this; - } + EventSubscription(const EventSubscription&) = delete; + EventSubscription& operator=(const EventSubscription&) = delete; - EventSubscription(const EventSubscription&) = delete; - EventSubscription& operator=(const EventSubscription&) = delete; + ~EventSubscription() { Reset(); } - ~EventSubscription() { Reset(); } + /// Unregisters the handler now; safe to call more than once. + void Reset() { + if (m_id.empty()) return; + EventDispatcher::UnregisterEvent(m_type, m_id); + m_id.clear(); + } - /// Unregisters the handler now; safe to call more than once. - void Reset() { - if (m_id.empty()) return; - EventDispatcher::UnregisterEvent(m_type, m_id); - m_id.clear(); - } + bool IsActive() const { return !m_id.empty(); } + EventType GetType() const { return m_type; } + const std::string& GetID() const { return m_id; } - bool IsActive() const { return !m_id.empty(); } - EventType GetType() const { return m_type; } - const std::string& GetID() const { return m_id; } + private: + EventType m_type = EventType::Unknown; + std::string m_id; + }; - private: - EventType m_type = EventType::Unknown; - std::string m_id; - }; - - template - EventSubscription EventDispatcher::Subscribe( - std::function callback) { - return EventSubscription( - EventT::GetStaticType(), - RegisterEventCallback(std::move(callback))); - } + template + EventSubscription EventDispatcher::Subscribe( + std::function callback) { + return EventSubscription( + EventT::GetStaticType(), + RegisterEventCallback(std::move(callback))); + } - template - EventSubscription EventDispatcher::Subscribe( - T* instance, void (T::*memberFunction)(const EventT&)) { - return EventSubscription( - EventT::GetStaticType(), - RegisterEventHandler(instance, memberFunction)); - } + template + EventSubscription EventDispatcher::Subscribe( + T* instance, void (T::*memberFunction)(const EventT&)) { + return EventSubscription( + EventT::GetStaticType(), + RegisterEventHandler(instance, memberFunction)); + } // Helper function for easier event dispatching /// Constructs a T from args and dispatches it through EventDispatcher. From 449e5693f3202a5ce61fa586520c804ca2e04434 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Sun, 4 Oct 2026 23:06:16 +0300 Subject: [PATCH 4/6] style: apply clang-format to changed lines --- include/public/Events/Delegate.hpp | 3 ++- include/public/Events/Event.hpp | 23 +++++++++++++---------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/include/public/Events/Delegate.hpp b/include/public/Events/Delegate.hpp index 6470b12c..340cac8b 100644 --- a/include/public/Events/Delegate.hpp +++ b/include/public/Events/Delegate.hpp @@ -105,7 +105,8 @@ namespace Sleak { private: FunctionType function; - const EventT* eventPtr = nullptr; // Store a pointer to avoid copying + const EventT* eventPtr = + nullptr; // Store a pointer to avoid copying }; /// Fan-out list of Delegate instances, all invoked together via Broadcast. diff --git a/include/public/Events/Event.hpp b/include/public/Events/Event.hpp index c0d50841..acff1077 100644 --- a/include/public/Events/Event.hpp +++ b/include/public/Events/Event.hpp @@ -143,19 +143,22 @@ namespace Sleak { /// Events::Input::KeyPressedEvent, Events::Input::MouseMovedEvent /// @ingroup events class ENGINE_API EventDispatcher { - public: - /// Registers a free-function/lambda callback for EventT, returning an ID for later unregistration. - template - static std::string RegisterEventCallback(std::function callback) { - auto delegate = std::make_shared>( - std::move(callback)); - eventHandlers[EventT::GetStaticType()].push_back(delegate); - return delegate->GetID(); + public: + /// Registers a free-function/lambda callback for EventT, returning an + /// ID for later unregistration. + template + static std::string RegisterEventCallback( + std::function callback) { + auto delegate = + std::make_shared>(std::move(callback)); + eventHandlers[EventT::GetStaticType()].push_back(delegate); + return delegate->GetID(); } /// Registers a member-function handler bound to instance, returning an ID for later unregistration. - template - static std::string RegisterEventHandler(T* instance, void (T::*memberFunction)(const EventT&)) { + template + static std::string RegisterEventHandler( + T* instance, void (T::*memberFunction)(const EventT&)) { auto callback = [instance, memberFunction](const EventT& event) { (instance->*memberFunction)(event); }; From 0f1bcff15d125856f46418707e14b7c518957e37 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Thu, 8 Oct 2026 03:03:37 +0300 Subject: [PATCH 5/6] fix: share the event delegate handle counter across modules The counter was a function-local static in an inline method of a class that is not exported. Each module that registers handlers (the engine and the game) gets its own copy once symbols are hidden, and always on Windows, so ids collide and UnregisterEvent from the game can remove an engine handler. Keep it in an exported class instead. --- include/public/Events/Delegate.hpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/include/public/Events/Delegate.hpp b/include/public/Events/Delegate.hpp index 340cac8b..4581f4c4 100644 --- a/include/public/Events/Delegate.hpp +++ b/include/public/Events/Delegate.hpp @@ -1,6 +1,7 @@ #ifndef _DELEGATE_H_ #define _DELEGATE_H_ +#include #include #include #include @@ -49,6 +50,13 @@ namespace Sleak { std::tuple storedArgs; // Store by reference using forward_as_tuple }; + /// Process-wide source of delegate handles. Exported so the engine + /// and every module built on it draw from one counter. + /// @ingroup events + struct ENGINE_API DelegateHandleCounter { + static inline std::atomic next{0}; + }; + // Specialized delegate for Event types /// Common base for EventDelegate instantiations, so they can share a handler list. /// @ingroup events @@ -60,8 +68,9 @@ namespace Sleak { /// Assigns a fresh process-unique ID, used to identify this delegate /// for unregistration. std::string GenerateID() { - static std::atomic nextHandle{0}; - handle = nextHandle.fetch_add(1, std::memory_order_relaxed) + 1; + handle = DelegateHandleCounter::next.fetch_add( + 1, std::memory_order_relaxed) + + 1; id = std::to_string(handle); return id; } From 60042691c51b33fd77a26614ce8c7bf8cd73fc36 Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Thu, 8 Oct 2026 03:25:30 +0300 Subject: [PATCH 6/6] style: fix brace indent flagged by the format check --- include/public/Events/Event.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/public/Events/Event.hpp b/include/public/Events/Event.hpp index acff1077..c993c524 100644 --- a/include/public/Events/Event.hpp +++ b/include/public/Events/Event.hpp @@ -153,7 +153,7 @@ namespace Sleak { std::make_shared>(std::move(callback)); eventHandlers[EventT::GetStaticType()].push_back(delegate); return delegate->GetID(); - } + } /// Registers a member-function handler bound to instance, returning an ID for later unregistration. template