Skip to content

Commit bdc2c44

Browse files
Copilotlijy91
andauthored
Implement Generic Event Handling System for C++ (#13)
* Initial plan * Implement generic event handling system for C++ Co-authored-by: lijy91 <[email protected]> * Complete generic event system with integration examples and demo Co-authored-by: lijy91 <[email protected]> * Remove legacy event system and update to new event dispatcher - Delete event_system and related demo/test files - Remove common_events and integration examples - Update CMakeLists.txt to require C++17 - Update nativeapi.h to use event_dispatcher/event headers - Implement GetGlobalEventDispatcher in event_dispatcher.cpp/h --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: lijy91 <[email protected]> Co-authored-by: LiJianying <[email protected]>
1 parent 9ec78d6 commit bdc2c44

File tree

7 files changed

+499
-3
lines changed

7 files changed

+499
-3
lines changed

.gitignore

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,11 @@
22
.vs/
33
.vscode/
44
build/
5-
cmake-build-debug/
5+
cmake-build-debug/
6+
7+
# Generated test binaries and object files
8+
*.o
9+
*_test
10+
callback_test
11+
simple_test
12+
event_test

examples/display_example/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.10)
33
project(display_example VERSION 0.0.1 LANGUAGES CXX)
44

55
# Set C++ standard
6-
set(CMAKE_CXX_STANDARD 11)
6+
set(CMAKE_CXX_STANDARD 17)
77
set(CMAKE_CXX_STANDARD_REQUIRED ON)
88

99
# Add example program

examples/window_example/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.10)
33
project(window_example VERSION 0.0.1 LANGUAGES CXX)
44

55
# Set C++ standard
6-
set(CMAKE_CXX_STANDARD 11)
6+
set(CMAKE_CXX_STANDARD 17)
77
set(CMAKE_CXX_STANDARD_REQUIRED ON)
88

99
# Add example program

include/nativeapi.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include "../src/app_runner.h"
55
#include "../src/display.h"
66
#include "../src/display_manager.h"
7+
#include "../src/event.h"
8+
#include "../src/event_dispatcher.h"
79
#include "../src/geometry.h"
810
#include "../src/keyboard_monitor.h"
911
#include "../src/menu.h"

src/event.h

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#pragma once
2+
3+
#include <functional>
4+
#include <memory>
5+
#include <string>
6+
#include <typeindex>
7+
#include <unordered_map>
8+
#include <chrono>
9+
10+
namespace nativeapi {
11+
12+
/**
13+
* Base class for all events in the generic event system.
14+
* Events should inherit from this class and provide their own data.
15+
*/
16+
class Event {
17+
public:
18+
Event() : timestamp_(std::chrono::steady_clock::now()) {}
19+
virtual ~Event() = default;
20+
21+
// Get the time when this event was created
22+
std::chrono::steady_clock::time_point GetTimestamp() const {
23+
return timestamp_;
24+
}
25+
26+
// Get a string representation of the event type (for debugging)
27+
virtual std::string GetTypeName() const = 0;
28+
29+
private:
30+
std::chrono::steady_clock::time_point timestamp_;
31+
};
32+
33+
/**
34+
* Template for typed events. This provides type safety and automatic
35+
* type identification for events.
36+
*/
37+
template<typename T>
38+
class TypedEvent : public Event {
39+
public:
40+
static std::type_index GetStaticType() {
41+
return std::type_index(typeid(T));
42+
}
43+
44+
std::type_index GetType() const {
45+
return GetStaticType();
46+
}
47+
48+
std::string GetTypeName() const override {
49+
return typeid(T).name();
50+
}
51+
};
52+
53+
/**
54+
* Generic event listener interface that can handle any event type.
55+
* This is the base interface for the observer pattern.
56+
*/
57+
class EventListener {
58+
public:
59+
virtual ~EventListener() = default;
60+
61+
/**
62+
* Handle an event. Implementations should check the event type
63+
* and cast appropriately.
64+
*/
65+
virtual void OnEvent(const Event& event) = 0;
66+
};
67+
68+
/**
69+
* Template for typed event listeners. This provides type safety
70+
* by automatically casting events to the correct type.
71+
*/
72+
template<typename EventType>
73+
class TypedEventListener : public EventListener {
74+
public:
75+
virtual ~TypedEventListener() = default;
76+
77+
void OnEvent(const Event& event) override {
78+
// Check if this is the correct event type
79+
if (auto typed_event = dynamic_cast<const EventType*>(&event)) {
80+
OnTypedEvent(*typed_event);
81+
}
82+
}
83+
84+
/**
85+
* Handle a typed event. Subclasses should override this method.
86+
*/
87+
virtual void OnTypedEvent(const EventType& event) = 0;
88+
};
89+
90+
/**
91+
* Callback-based event handler that wraps std::function callbacks.
92+
* This allows using lambda functions or function pointers as event handlers.
93+
*/
94+
template<typename EventType>
95+
class CallbackEventListener : public TypedEventListener<EventType> {
96+
public:
97+
using CallbackType = std::function<void(const EventType&)>;
98+
99+
explicit CallbackEventListener(CallbackType callback)
100+
: callback_(std::move(callback)) {}
101+
102+
void OnTypedEvent(const EventType& event) override {
103+
if (callback_) {
104+
callback_(event);
105+
}
106+
}
107+
108+
private:
109+
CallbackType callback_;
110+
};
111+
112+
} // namespace nativeapi

src/event_dispatcher.cpp

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#include "event_dispatcher.h"
2+
3+
#include <algorithm>
4+
#include <iostream>
5+
6+
namespace nativeapi {
7+
8+
EventDispatcher::EventDispatcher()
9+
: running_(false), stop_requested_(false), next_listener_id_(1) {}
10+
11+
EventDispatcher::~EventDispatcher() {
12+
Stop();
13+
}
14+
15+
size_t EventDispatcher::AddListener(std::type_index event_type,
16+
EventListener* listener) {
17+
if (!listener) {
18+
return 0; // Invalid listener
19+
}
20+
21+
std::lock_guard<std::mutex> lock(listeners_mutex_);
22+
size_t listener_id = next_listener_id_.fetch_add(1);
23+
24+
listeners_[event_type].push_back({listener, listener_id});
25+
26+
return listener_id;
27+
}
28+
29+
bool EventDispatcher::RemoveListener(size_t listener_id) {
30+
std::lock_guard<std::mutex> lock(listeners_mutex_);
31+
32+
for (auto& [event_type, listener_list] : listeners_) {
33+
auto it = std::find_if(listener_list.begin(), listener_list.end(),
34+
[listener_id](const ListenerInfo& info) {
35+
return info.id == listener_id;
36+
});
37+
38+
if (it != listener_list.end()) {
39+
listener_list.erase(it);
40+
return true;
41+
}
42+
}
43+
44+
return false;
45+
}
46+
47+
void EventDispatcher::RemoveAllListeners(std::type_index event_type) {
48+
std::lock_guard<std::mutex> lock(listeners_mutex_);
49+
listeners_[event_type].clear();
50+
}
51+
52+
void EventDispatcher::RemoveAllListeners() {
53+
std::lock_guard<std::mutex> lock(listeners_mutex_);
54+
listeners_.clear();
55+
callback_listeners_.clear();
56+
}
57+
58+
void EventDispatcher::DispatchSync(const Event& event) {
59+
std::type_index event_type = typeid(event);
60+
std::vector<EventListener*> listeners_copy;
61+
62+
// Copy listeners to avoid holding the lock during dispatch
63+
{
64+
std::lock_guard<std::mutex> lock(listeners_mutex_);
65+
auto it = listeners_.find(event_type);
66+
if (it != listeners_.end()) {
67+
listeners_copy.reserve(it->second.size());
68+
for (const auto& info : it->second) {
69+
listeners_copy.push_back(info.listener);
70+
}
71+
}
72+
}
73+
74+
// Dispatch to all listeners
75+
for (auto* listener : listeners_copy) {
76+
try {
77+
listener->OnEvent(event);
78+
} catch (const std::exception& e) {
79+
std::cerr << "Exception in event listener: " << e.what() << std::endl;
80+
} catch (...) {
81+
std::cerr << "Unknown exception in event listener" << std::endl;
82+
}
83+
}
84+
}
85+
86+
void EventDispatcher::DispatchAsync(std::unique_ptr<Event> event) {
87+
if (!event) {
88+
return;
89+
}
90+
91+
// Start the worker thread if not already running
92+
if (!running_.load()) {
93+
Start();
94+
}
95+
96+
{
97+
std::lock_guard<std::mutex> lock(queue_mutex_);
98+
event_queue_.push(std::move(event));
99+
}
100+
101+
queue_condition_.notify_one();
102+
}
103+
104+
void EventDispatcher::Start() {
105+
if (running_.load()) {
106+
return; // Already running
107+
}
108+
109+
stop_requested_.store(false);
110+
running_.store(true);
111+
112+
worker_thread_ = std::thread(&EventDispatcher::ProcessAsyncEvents, this);
113+
}
114+
115+
void EventDispatcher::Stop() {
116+
if (!running_.load()) {
117+
return; // Not running
118+
}
119+
120+
stop_requested_.store(true);
121+
queue_condition_.notify_all();
122+
123+
if (worker_thread_.joinable()) {
124+
worker_thread_.join();
125+
}
126+
127+
running_.store(false);
128+
129+
// Clear any remaining events in the queue
130+
std::lock_guard<std::mutex> lock(queue_mutex_);
131+
while (!event_queue_.empty()) {
132+
event_queue_.pop();
133+
}
134+
}
135+
136+
bool EventDispatcher::IsRunning() const {
137+
return running_.load();
138+
}
139+
140+
size_t EventDispatcher::GetListenerCount(std::type_index event_type) const {
141+
std::lock_guard<std::mutex> lock(listeners_mutex_);
142+
auto it = listeners_.find(event_type);
143+
return (it != listeners_.end()) ? it->second.size() : 0;
144+
}
145+
146+
size_t EventDispatcher::GetTotalListenerCount() const {
147+
std::lock_guard<std::mutex> lock(listeners_mutex_);
148+
size_t total = 0;
149+
for (const auto& [event_type, listener_list] : listeners_) {
150+
total += listener_list.size();
151+
}
152+
return total;
153+
}
154+
155+
void EventDispatcher::ProcessAsyncEvents() {
156+
while (running_.load()) {
157+
std::unique_ptr<Event> event;
158+
159+
// Wait for an event or stop signal
160+
{
161+
std::unique_lock<std::mutex> lock(queue_mutex_);
162+
queue_condition_.wait(lock, [this] {
163+
return !event_queue_.empty() || stop_requested_.load();
164+
});
165+
166+
if (stop_requested_.load()) {
167+
break;
168+
}
169+
170+
if (!event_queue_.empty()) {
171+
event = std::move(event_queue_.front());
172+
event_queue_.pop();
173+
}
174+
}
175+
176+
// Dispatch the event if we have one
177+
if (event) {
178+
DispatchSync(*event);
179+
}
180+
}
181+
}
182+
183+
EventDispatcher& GetGlobalEventDispatcher() {
184+
static EventDispatcher global_dispatcher;
185+
return global_dispatcher;
186+
}
187+
188+
} // namespace nativeapi

0 commit comments

Comments
 (0)