Skip to content

Commit 2ba258b

Browse files
committed
feat: add StaticEventBus
1 parent 3b5f986 commit 2ba258b

5 files changed

Lines changed: 466 additions & 1 deletion

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ FodyWeavers.xsd
419419
# VS Code files for those working on multiple tools
420420
**/.vscode/*
421421
.vscode/*
422-
!.vscode/settings.json
422+
# !.vscode/settings.json
423423
# !.vscode/tasks.json
424424
# !.vscode/launch.json
425425
# !.vscode/extensions.json

include/MaaUtils/StaticEventBus.h

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#pragma once
2+
3+
#include <concepts>
4+
#include <functional>
5+
#include <memory>
6+
#include <compare>
7+
#include <vector>
8+
#include <algorithm>
9+
#include <optional>
10+
#include <unordered_map>
11+
#include <typeindex>
12+
13+
#include "MaaUtils/Conf.h"
14+
#include "MaaUtils/Port.h"
15+
16+
MAA_NS_BEGIN
17+
18+
class Event {
19+
public:
20+
virtual ~Event() = default;
21+
};
22+
23+
class CancellableEvent : public Event {
24+
public:
25+
void cancel() { cancelled_ = true; }
26+
bool is_cancelled() const { return cancelled_; }
27+
28+
private:
29+
bool cancelled_ = false;
30+
};
31+
32+
template<typename T>
33+
concept IsEvent = std::derived_from<T, Event>;
34+
35+
class EventStorageBase {
36+
public:
37+
virtual ~EventStorageBase() = default;
38+
};
39+
40+
template<IsEvent EventT>
41+
struct EventStorage : public EventStorageBase {
42+
struct Subscription {
43+
int priority;
44+
bool has_owner;
45+
std::function<void(EventT&)> callback;
46+
std::weak_ptr<void> owner;
47+
48+
bool operator<(const Subscription& other) const {
49+
// Higher priority subscriptions come first
50+
return priority > other.priority;
51+
}
52+
};
53+
54+
std::vector<Subscription> subscriptions;
55+
};
56+
57+
class EventStorageRegistry {
58+
private:
59+
std::unordered_map<std::type_index, std::unique_ptr<EventStorageBase>> storages_;
60+
public:
61+
template<IsEvent EventT>
62+
EventStorage<EventT>& get_storage() {
63+
std::type_index type_idx(typeid(EventT));
64+
auto it = storages_.find(type_idx);
65+
if (it == storages_.end()) {
66+
auto storage = std::make_unique<EventStorage<EventT>>();
67+
auto* storage_ptr = storage.get();
68+
storages_[type_idx] = std::move(storage);
69+
return *storage_ptr;
70+
} else {
71+
return *static_cast<EventStorage<EventT>*>(it->second.get());
72+
}
73+
}
74+
};
75+
76+
MAA_UTILS_API EventStorageRegistry& get_event_storage_registry();
77+
78+
class StaticEventManager {
79+
private:
80+
template<IsEvent EventT>
81+
inline static EventStorage<EventT>& get_event_storage() {
82+
static auto* cached_storage = &get_event_storage_registry().get_storage<EventT>();
83+
return *cached_storage;
84+
}
85+
86+
public:
87+
// Free function subscription
88+
template<IsEvent EventT, typename Callable>
89+
static void subscribe(Callable&& callback, int priority = 0) {
90+
auto& storage = get_event_storage<EventT>();
91+
92+
auto wrapper = [callback = std::forward<Callable>(callback)](EventT& event) mutable {
93+
if constexpr (std::is_invocable_v<Callable&, EventT&>) {
94+
callback(event);
95+
} else if constexpr (std::is_invocable_v<Callable&, const EventT&>) {
96+
callback(event);
97+
} else {
98+
static_assert(false, "Callback must be invocable with EventT& or const EventT&");
99+
}
100+
};
101+
102+
storage.subscriptions.insert(
103+
std::lower_bound(
104+
storage.subscriptions.begin(),
105+
storage.subscriptions.end(),
106+
typename EventStorage<EventT>::Subscription{priority, false, {}, {}}
107+
),
108+
{priority, false, std::move(wrapper), {}}
109+
);
110+
}
111+
112+
// Member function subscription
113+
template<IsEvent EventT, typename T, typename Callable>
114+
static void subscribe(
115+
const std::shared_ptr<T>& owner,
116+
Callable&& callback,
117+
int priority = 0
118+
) {
119+
auto weak_owner = std::weak_ptr<T>(owner);
120+
auto wrapper = [weak_owner, callback = std::forward<Callable>(callback)](EventT& event) mutable {
121+
if (auto shared_owner = weak_owner.lock()) {
122+
if constexpr (std::is_invocable_v<Callable&, EventT&>) {
123+
callback(event);
124+
} else if constexpr (std::is_invocable_v<Callable&, const EventT&>) {
125+
callback(event);
126+
} else if constexpr (std::is_invocable_v<Callable&, T*, EventT&>) {
127+
(shared_owner.get()->*callback)(event);
128+
} else if constexpr (std::is_invocable_v<Callable&, T*, const EventT&>) {
129+
(shared_owner.get()->*callback)(event);
130+
} else {
131+
static_assert(false, "Callback must be invocable with EventT& or const EventT&");
132+
}
133+
}
134+
};
135+
auto& storage = get_event_storage<EventT>();
136+
storage.subscriptions.insert(
137+
std::lower_bound(
138+
storage.subscriptions.begin(),
139+
storage.subscriptions.end(),
140+
typename EventStorage<EventT>::Subscription{priority, true, {}, {}}
141+
),
142+
{priority, true, std::move(wrapper), weak_owner}
143+
);
144+
}
145+
146+
template<IsEvent EventT>
147+
static void publish(EventT& event) {
148+
auto& storage = get_event_storage<EventT>();
149+
bool need_erase = false;
150+
for (const auto& subscription : storage.subscriptions) {
151+
if (subscription.has_owner && subscription.owner.expired()) {
152+
need_erase = true;
153+
continue;
154+
}
155+
subscription.callback(event);
156+
if constexpr (std::derived_from<EventT, CancellableEvent>) {
157+
if (event.is_cancelled()) {
158+
break;
159+
}
160+
}
161+
}
162+
if (need_erase) {
163+
std::erase_if(
164+
storage.subscriptions,
165+
[](const auto& sub) {
166+
return sub.has_owner && sub.owner.expired();
167+
}
168+
);
169+
}
170+
}
171+
172+
StaticEventManager() = delete;
173+
};
174+
175+
MAA_NS_END
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include "MaaUtils/StaticEventBus.h"
2+
3+
MAA_NS_BEGIN
4+
5+
EventStorageRegistry& get_event_storage_registry() {
6+
static EventStorageRegistry registry;
7+
return registry;
8+
}
9+
10+
MAA_NS_END

test/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
set(CMAKE_CXX_STANDARD 20)
2+
3+
add_executable(StaticEventBusTest StaticEventBusTest.cpp ../source/StaticEventBus/StaticEventBus.cpp)
4+
5+
target_include_directories(StaticEventBusTest PRIVATE ../include)

0 commit comments

Comments
 (0)