-
Notifications
You must be signed in to change notification settings - Fork 2
feat: add StaticEventBus #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,182 @@ | ||||||||||||||
| #pragma once | ||||||||||||||
|
|
||||||||||||||
| #include <algorithm> | ||||||||||||||
| #include <concepts> | ||||||||||||||
| #include <functional> | ||||||||||||||
| #include <memory> | ||||||||||||||
| #include <typeindex> | ||||||||||||||
| #include <unordered_map> | ||||||||||||||
| #include <vector> | ||||||||||||||
|
|
||||||||||||||
| #include "MaaUtils/Conf.h" | ||||||||||||||
| #include "MaaUtils/Port.h" | ||||||||||||||
|
|
||||||||||||||
| MAA_NS_BEGIN | ||||||||||||||
|
|
||||||||||||||
| class Event | ||||||||||||||
| { | ||||||||||||||
| public: | ||||||||||||||
| virtual ~Event() = default; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| class CancellableEvent : public Event | ||||||||||||||
| { | ||||||||||||||
| public: | ||||||||||||||
| void cancel() { cancelled_ = true; } | ||||||||||||||
|
|
||||||||||||||
| bool is_cancelled() const { return cancelled_; } | ||||||||||||||
|
|
||||||||||||||
| private: | ||||||||||||||
| bool cancelled_ = false; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| template <typename T> | ||||||||||||||
| concept IsEvent = std::derived_from<T, Event>; | ||||||||||||||
|
|
||||||||||||||
| class EventStorageBase | ||||||||||||||
| { | ||||||||||||||
| public: | ||||||||||||||
| virtual ~EventStorageBase() = default; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| template <IsEvent EventT> | ||||||||||||||
| struct EventStorage : public EventStorageBase | ||||||||||||||
| { | ||||||||||||||
| struct Subscription | ||||||||||||||
| { | ||||||||||||||
| int priority; | ||||||||||||||
| bool has_owner; | ||||||||||||||
| std::function<void(EventT&)> callback; | ||||||||||||||
| std::weak_ptr<void> owner; | ||||||||||||||
|
|
||||||||||||||
| bool operator<(const Subscription& other) const | ||||||||||||||
| { | ||||||||||||||
| // Higher priority subscriptions come first | ||||||||||||||
| return priority > other.priority; | ||||||||||||||
| } | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| std::vector<Subscription> subscriptions; | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| class EventStorageRegistry | ||||||||||||||
| { | ||||||||||||||
| private: | ||||||||||||||
| std::unordered_map<std::type_index, std::unique_ptr<EventStorageBase>> storages_; | ||||||||||||||
|
|
||||||||||||||
| public: | ||||||||||||||
| template <IsEvent EventT> | ||||||||||||||
| EventStorage<EventT>& get_storage() | ||||||||||||||
| { | ||||||||||||||
| std::type_index type_idx(typeid(EventT)); | ||||||||||||||
| auto it = storages_.find(type_idx); | ||||||||||||||
| if (it == storages_.end()) { | ||||||||||||||
| auto storage = std::make_unique<EventStorage<EventT>>(); | ||||||||||||||
| auto* storage_ptr = storage.get(); | ||||||||||||||
| storages_[type_idx] = std::move(storage); | ||||||||||||||
| return *storage_ptr; | ||||||||||||||
| } | ||||||||||||||
| else { | ||||||||||||||
| return *static_cast<EventStorage<EventT>*>(it->second.get()); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| }; | ||||||||||||||
|
|
||||||||||||||
| MAA_UTILS_API EventStorageRegistry& get_event_storage_registry(); | ||||||||||||||
|
|
||||||||||||||
| class StaticEventManager | ||||||||||||||
| { | ||||||||||||||
| private: | ||||||||||||||
| template <IsEvent EventT> | ||||||||||||||
| inline static EventStorage<EventT>& get_event_storage() | ||||||||||||||
| { | ||||||||||||||
| static auto* cached_storage = &get_event_storage_registry().get_storage<EventT>(); | ||||||||||||||
| return *cached_storage; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| public: | ||||||||||||||
| // Free function subscription | ||||||||||||||
| template <IsEvent EventT, typename Callable> | ||||||||||||||
| static void subscribe(Callable&& callback, int priority = 0) | ||||||||||||||
| { | ||||||||||||||
| auto& storage = get_event_storage<EventT>(); | ||||||||||||||
|
|
||||||||||||||
| auto wrapper = [callback = std::forward<Callable>(callback)](EventT& event) mutable { | ||||||||||||||
| if constexpr (std::is_invocable_v<Callable&, EventT&>) { | ||||||||||||||
| callback(event); | ||||||||||||||
| } | ||||||||||||||
| else if constexpr (std::is_invocable_v<Callable&, const EventT&>) { | ||||||||||||||
| callback(event); | ||||||||||||||
| } | ||||||||||||||
| else { | ||||||||||||||
| static_assert(false, "Callback must be invocable with EventT& or const EventT&"); | ||||||||||||||
|
||||||||||||||
| static_assert(false, "Callback must be invocable with EventT& or const EventT&"); | |
| static_assert( | |
| !std::is_invocable_v<Callable&, EventT&> && | |
| !std::is_invocable_v<Callable&, const EventT&>, | |
| "Callback must be invocable with EventT& or const EventT&" | |
| ); |
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The subscribe() method modifies storage.subscriptions without synchronization. Concurrent calls to subscribe() or simultaneous publish() operations on the same event type can cause data races. Consider adding mutex protection for subscription vector modifications.
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using static_assert(false, ...) is ill-formed in C++. It will always fail even when not instantiated. Use a type-dependent expression like static_assert(sizeof(Callable) == 0, ...) or static_assert(!sizeof(Callable*), ...) to ensure it only fires when the template is actually instantiated with an invalid type.
| static_assert(false, "Callback must be invocable with EventT& or const EventT&"); | |
| static_assert(sizeof(Callable) == 0, "Callback must be invocable with EventT& or const EventT&"); |
Copilot
AI
Nov 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The publish() method iterates over and potentially modifies storage.subscriptions without synchronization. If another thread calls subscribe() during publication, this creates a data race. The iteration and cleanup operations need to be protected with the same mutex used in subscribe().
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| #include "MaaUtils/StaticEventBus.h" | ||
|
|
||
| MAA_NS_BEGIN | ||
|
|
||
| EventStorageRegistry& get_event_storage_registry() { | ||
| static EventStorageRegistry registry; | ||
| return registry; | ||
| } | ||
|
|
||
| MAA_NS_END |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| set(CMAKE_CXX_STANDARD 20) | ||
|
|
||
| add_executable(StaticEventBusTest StaticEventBusTest.cpp ../source/StaticEventBus/StaticEventBus.cpp) | ||
|
|
||
| target_include_directories(StaticEventBusTest PRIVATE ../include) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
get_storage()method modifiesstorages_without synchronization. If multiple threads callsubscribe()orpublish()concurrently with different event types, this can lead to race conditions during map modification. Consider adding mutex protection aroundstorages_access.