Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,11 @@
.vs/
.vscode/
build/
cmake-build-debug/
cmake-build-debug/

# Generated test binaries and object files
*.o
*_test
callback_test
simple_test
event_test
2 changes: 1 addition & 1 deletion examples/display_example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion examples/window_example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions include/nativeapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
112 changes: 112 additions & 0 deletions src/event.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#pragma once

#include <functional>
#include <memory>
#include <string>
#include <typeindex>
#include <unordered_map>
#include <chrono>

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<typename T>
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<typename EventType>
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<const EventType*>(&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<typename EventType>
class CallbackEventListener : public TypedEventListener<EventType> {
public:
using CallbackType = std::function<void(const EventType&)>;

explicit CallbackEventListener(CallbackType callback)
: callback_(std::move(callback)) {}

void OnTypedEvent(const EventType& event) override {
if (callback_) {
callback_(event);
}
}

private:
CallbackType callback_;
};

} // namespace nativeapi
188 changes: 188 additions & 0 deletions src/event_dispatcher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#include "event_dispatcher.h"

#include <algorithm>
#include <iostream>

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<std::mutex> 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<std::mutex> 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<std::mutex> lock(listeners_mutex_);
listeners_[event_type].clear();
}

void EventDispatcher::RemoveAllListeners() {
std::lock_guard<std::mutex> lock(listeners_mutex_);
listeners_.clear();
callback_listeners_.clear();
}

void EventDispatcher::DispatchSync(const Event& event) {
std::type_index event_type = typeid(event);
std::vector<EventListener*> listeners_copy;

// Copy listeners to avoid holding the lock during dispatch
{
std::lock_guard<std::mutex> 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> event) {
if (!event) {
return;
}

// Start the worker thread if not already running
if (!running_.load()) {
Start();
}

{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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> event;

// Wait for an event or stop signal
{
std::unique_lock<std::mutex> 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
Loading
Loading