diff --git a/include/window/event.hpp b/include/window/event.hpp new file mode 100644 index 0000000..7bade5e --- /dev/null +++ b/include/window/event.hpp @@ -0,0 +1,71 @@ +#ifndef EVENT_H_ +#define EVENT_H_ + +#include +#include +#include +#include + +class EventBus { + using Type = std::type_index; + using RawFn = std::function; + + struct Slot { std::size_t id; RawFn fn; }; + + std::unordered_map> subs_; + std::size_t next_id_ = 1; + +public: + struct Handle { + std::type_index type{typeid(void)}; + std::size_t id{0}; + explicit operator bool() const { return id != 0; } + }; + + template + Handle subscribe(F&& f) { + auto& vec = subs_[Type(typeid(E))]; + Handle h{ Type(typeid(E)), next_id_++ }; + // Wrap strongly typed callback into type-erased RawFn + RawFn wrapper = [fn = std::function(std::forward(f))](const void* p){ + fn(*static_cast(p)); + }; + vec.push_back(Slot{h.id, std::move(wrapper)}); + return h; + } + + // Unsubscribe with handle + void unsubscribe(const Handle& h) { + auto it = subs_.find(h.type); + if (it == subs_.end()) return; + auto& vec = it->second; + vec.erase(std::remove_if(vec.begin(), vec.end(), + [&](const Slot& s){ return s.id == h.id; }), + vec.end()); + } + + // Publish immediately + template + void publish(const E& e) const { + auto it = subs_.find(Type(typeid(E))); + if (it == subs_.end()) return; + for (auto& slot : it->second) slot.fn(&e); + } +}; + +// Optional RAII helper +struct ScopedSub { + EventBus* bus{}; + EventBus::Handle h{}; + ScopedSub() = default; + ScopedSub(EventBus& b, EventBus::Handle hh) : bus(&b), h(hh) {} + ScopedSub(ScopedSub&& o) noexcept { *this = std::move(o); } + ScopedSub& operator=(ScopedSub&& o) noexcept { + if (this != &o) { reset(); bus = o.bus; h = o.h; o.bus = nullptr; } + return *this; + } + ~ScopedSub(){ reset(); } + void reset(){ if (bus && h) bus->unsubscribe(h); bus=nullptr; h={}; } +}; + +#endif // EVENT_H_ \ No newline at end of file