diff --git a/.gitignore b/.gitignore index 987c968..c354476 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,11 @@ .vs/ .vscode/ build/ -cmake-build-debug/ \ No newline at end of file +cmake-build-debug/ + +# Generated test binaries and object files +*.o +*_test +callback_test +simple_test +event_test \ No newline at end of file diff --git a/examples/display_example/CMakeLists.txt b/examples/display_example/CMakeLists.txt index 45f6a65..9a4f0f3 100644 --- a/examples/display_example/CMakeLists.txt +++ b/examples/display_example/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.10) project(display_example VERSION 0.0.1 LANGUAGES CXX) # Set C++ standard -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Add example program diff --git a/examples/window_example/CMakeLists.txt b/examples/window_example/CMakeLists.txt index 2953452..8797260 100644 --- a/examples/window_example/CMakeLists.txt +++ b/examples/window_example/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.10) project(window_example VERSION 0.0.1 LANGUAGES CXX) # Set C++ standard -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Add example program diff --git a/include/nativeapi.h b/include/nativeapi.h index ecc15ad..f16704c 100644 --- a/include/nativeapi.h +++ b/include/nativeapi.h @@ -4,6 +4,8 @@ #include "../src/app_runner.h" #include "../src/display.h" #include "../src/display_manager.h" +#include "../src/event.h" +#include "../src/event_dispatcher.h" #include "../src/geometry.h" #include "../src/keyboard_monitor.h" #include "../src/menu.h" diff --git a/src/event.h b/src/event.h new file mode 100644 index 0000000..1c74fda --- /dev/null +++ b/src/event.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace nativeapi { + +/** + * Base class for all events in the generic event system. + * Events should inherit from this class and provide their own data. + */ +class Event { + public: + Event() : timestamp_(std::chrono::steady_clock::now()) {} + virtual ~Event() = default; + + // Get the time when this event was created + std::chrono::steady_clock::time_point GetTimestamp() const { + return timestamp_; + } + + // Get a string representation of the event type (for debugging) + virtual std::string GetTypeName() const = 0; + + private: + std::chrono::steady_clock::time_point timestamp_; +}; + +/** + * Template for typed events. This provides type safety and automatic + * type identification for events. + */ +template +class TypedEvent : public Event { + public: + static std::type_index GetStaticType() { + return std::type_index(typeid(T)); + } + + std::type_index GetType() const { + return GetStaticType(); + } + + std::string GetTypeName() const override { + return typeid(T).name(); + } +}; + +/** + * Generic event listener interface that can handle any event type. + * This is the base interface for the observer pattern. + */ +class EventListener { + public: + virtual ~EventListener() = default; + + /** + * Handle an event. Implementations should check the event type + * and cast appropriately. + */ + virtual void OnEvent(const Event& event) = 0; +}; + +/** + * Template for typed event listeners. This provides type safety + * by automatically casting events to the correct type. + */ +template +class TypedEventListener : public EventListener { + public: + virtual ~TypedEventListener() = default; + + void OnEvent(const Event& event) override { + // Check if this is the correct event type + if (auto typed_event = dynamic_cast(&event)) { + OnTypedEvent(*typed_event); + } + } + + /** + * Handle a typed event. Subclasses should override this method. + */ + virtual void OnTypedEvent(const EventType& event) = 0; +}; + +/** + * Callback-based event handler that wraps std::function callbacks. + * This allows using lambda functions or function pointers as event handlers. + */ +template +class CallbackEventListener : public TypedEventListener { + public: + using CallbackType = std::function; + + explicit CallbackEventListener(CallbackType callback) + : callback_(std::move(callback)) {} + + void OnTypedEvent(const EventType& event) override { + if (callback_) { + callback_(event); + } + } + + private: + CallbackType callback_; +}; + +} // namespace nativeapi \ No newline at end of file diff --git a/src/event_dispatcher.cpp b/src/event_dispatcher.cpp new file mode 100644 index 0000000..11f1cf9 --- /dev/null +++ b/src/event_dispatcher.cpp @@ -0,0 +1,188 @@ +#include "event_dispatcher.h" + +#include +#include + +namespace nativeapi { + +EventDispatcher::EventDispatcher() + : running_(false), stop_requested_(false), next_listener_id_(1) {} + +EventDispatcher::~EventDispatcher() { + Stop(); +} + +size_t EventDispatcher::AddListener(std::type_index event_type, + EventListener* listener) { + if (!listener) { + return 0; // Invalid listener + } + + std::lock_guard lock(listeners_mutex_); + size_t listener_id = next_listener_id_.fetch_add(1); + + listeners_[event_type].push_back({listener, listener_id}); + + return listener_id; +} + +bool EventDispatcher::RemoveListener(size_t listener_id) { + std::lock_guard lock(listeners_mutex_); + + for (auto& [event_type, listener_list] : listeners_) { + auto it = std::find_if(listener_list.begin(), listener_list.end(), + [listener_id](const ListenerInfo& info) { + return info.id == listener_id; + }); + + if (it != listener_list.end()) { + listener_list.erase(it); + return true; + } + } + + return false; +} + +void EventDispatcher::RemoveAllListeners(std::type_index event_type) { + std::lock_guard lock(listeners_mutex_); + listeners_[event_type].clear(); +} + +void EventDispatcher::RemoveAllListeners() { + std::lock_guard lock(listeners_mutex_); + listeners_.clear(); + callback_listeners_.clear(); +} + +void EventDispatcher::DispatchSync(const Event& event) { + std::type_index event_type = typeid(event); + std::vector listeners_copy; + + // Copy listeners to avoid holding the lock during dispatch + { + std::lock_guard lock(listeners_mutex_); + auto it = listeners_.find(event_type); + if (it != listeners_.end()) { + listeners_copy.reserve(it->second.size()); + for (const auto& info : it->second) { + listeners_copy.push_back(info.listener); + } + } + } + + // Dispatch to all listeners + for (auto* listener : listeners_copy) { + try { + listener->OnEvent(event); + } catch (const std::exception& e) { + std::cerr << "Exception in event listener: " << e.what() << std::endl; + } catch (...) { + std::cerr << "Unknown exception in event listener" << std::endl; + } + } +} + +void EventDispatcher::DispatchAsync(std::unique_ptr event) { + if (!event) { + return; + } + + // Start the worker thread if not already running + if (!running_.load()) { + Start(); + } + + { + std::lock_guard lock(queue_mutex_); + event_queue_.push(std::move(event)); + } + + queue_condition_.notify_one(); +} + +void EventDispatcher::Start() { + if (running_.load()) { + return; // Already running + } + + stop_requested_.store(false); + running_.store(true); + + worker_thread_ = std::thread(&EventDispatcher::ProcessAsyncEvents, this); +} + +void EventDispatcher::Stop() { + if (!running_.load()) { + return; // Not running + } + + stop_requested_.store(true); + queue_condition_.notify_all(); + + if (worker_thread_.joinable()) { + worker_thread_.join(); + } + + running_.store(false); + + // Clear any remaining events in the queue + std::lock_guard lock(queue_mutex_); + while (!event_queue_.empty()) { + event_queue_.pop(); + } +} + +bool EventDispatcher::IsRunning() const { + return running_.load(); +} + +size_t EventDispatcher::GetListenerCount(std::type_index event_type) const { + std::lock_guard lock(listeners_mutex_); + auto it = listeners_.find(event_type); + return (it != listeners_.end()) ? it->second.size() : 0; +} + +size_t EventDispatcher::GetTotalListenerCount() const { + std::lock_guard lock(listeners_mutex_); + size_t total = 0; + for (const auto& [event_type, listener_list] : listeners_) { + total += listener_list.size(); + } + return total; +} + +void EventDispatcher::ProcessAsyncEvents() { + while (running_.load()) { + std::unique_ptr event; + + // Wait for an event or stop signal + { + std::unique_lock lock(queue_mutex_); + queue_condition_.wait(lock, [this] { + return !event_queue_.empty() || stop_requested_.load(); + }); + + if (stop_requested_.load()) { + break; + } + + if (!event_queue_.empty()) { + event = std::move(event_queue_.front()); + event_queue_.pop(); + } + } + + // Dispatch the event if we have one + if (event) { + DispatchSync(*event); + } + } +} + +EventDispatcher& GetGlobalEventDispatcher() { + static EventDispatcher global_dispatcher; + return global_dispatcher; +} + +} // namespace nativeapi diff --git a/src/event_dispatcher.h b/src/event_dispatcher.h new file mode 100644 index 0000000..4f5eec8 --- /dev/null +++ b/src/event_dispatcher.h @@ -0,0 +1,187 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "event.h" + +namespace nativeapi { + +/** + * Generic event dispatcher that supports both synchronous and asynchronous + * event dispatching. It maintains listeners for different event types and + * can dispatch events either immediately or queue them for later processing. + */ +class EventDispatcher { + public: + EventDispatcher(); + ~EventDispatcher(); + + /** + * Add a listener for a specific event type. + * The listener will be called whenever an event of that type is dispatched. + * + * @param listener Pointer to the event listener (must remain valid until + * removed) + * @return A unique listener ID that can be used to remove the listener + */ + template + size_t AddListener(TypedEventListener* listener) { + return AddListener(TypedEvent::GetStaticType(), listener); + } + + /** + * Add a callback function as a listener for a specific event type. + * + * @param callback Function to call when the event occurs + * @return A unique listener ID that can be used to remove the listener + */ + template + size_t AddListener(std::function callback) { + auto callback_listener = + std::make_unique>(std::move(callback)); + auto listener_ptr = callback_listener.get(); + + // Store the callback listener first, then add it + { + std::lock_guard lock(listeners_mutex_); + callback_listeners_.emplace_back(std::move(callback_listener)); + } + + // Use the type-erased method to avoid infinite recursion + return AddListener(TypedEvent::GetStaticType(), listener_ptr); + } + + /** + * Remove a listener by its ID. + * + * @param listener_id The ID returned by AddListener + * @return true if the listener was found and removed, false otherwise + */ + bool RemoveListener(size_t listener_id); + + /** + * Remove all listeners for a specific event type. + */ + template + void RemoveAllListeners() { + RemoveAllListeners(TypedEvent::GetStaticType()); + } + + /** + * Remove all listeners. + */ + void RemoveAllListeners(); + + /** + * Dispatch an event synchronously to all registered listeners. + * This will call all listeners immediately on the current thread. + * + * @param event The event to dispatch + */ + void DispatchSync(const Event& event); + + /** + * Dispatch an event synchronously using perfect forwarding. + * This creates the event object and dispatches it immediately. + */ + template + void DispatchSync(Args&&... args) { + EventType event(std::forward(args)...); + DispatchSync(event); + } + + /** + * Queue an event for asynchronous dispatch. + * The event will be dispatched on the background thread. + * + * @param event The event to queue (will be copied) + */ + void DispatchAsync(std::unique_ptr event); + + /** + * Queue an event for asynchronous dispatch using perfect forwarding. + */ + template + void DispatchAsync(Args&&... args) { + auto event = std::make_unique(std::forward(args)...); + DispatchAsync(std::move(event)); + } + + /** + * Start the background thread for asynchronous event processing. + * This is called automatically when needed, but can be called explicitly. + */ + void Start(); + + /** + * Stop the background thread and clear the event queue. + */ + void Stop(); + + /** + * Check if the background thread is running. + */ + bool IsRunning() const; + + /** + * Get the number of listeners registered for a specific event type. + */ + template + size_t GetListenerCount() const { + return GetListenerCount(TypedEvent::GetStaticType()); + } + + /** + * Get the total number of registered listeners. + */ + size_t GetTotalListenerCount() const; + + private: + struct ListenerInfo { + EventListener* listener; + size_t id; + }; + + // Type-erased methods for internal use + size_t AddListener(std::type_index event_type, EventListener* listener); + void RemoveAllListeners(std::type_index event_type); + size_t GetListenerCount(std::type_index event_type) const; + + // Background thread function for processing async events + void ProcessAsyncEvents(); + + // Member variables + mutable std::mutex listeners_mutex_; + std::unordered_map> listeners_; + + // Storage for callback listeners to manage their lifetime + std::vector> callback_listeners_; + + // Async event processing + std::mutex queue_mutex_; + std::queue> event_queue_; + std::condition_variable queue_condition_; + std::thread worker_thread_; + std::atomic running_; + std::atomic stop_requested_; + + // Listener ID generation + std::atomic next_listener_id_; +}; + +/** + * Convenience function to create a global event dispatcher instance. + * This is useful for applications that need a single, shared event system. + */ +EventDispatcher& GetGlobalEventDispatcher(); +} // namespace nativeapi