#pragma once #include #include "common.hpp" #include "components/bar.hpp" #include "components/logger.hpp" #include "modules/meta.hpp" #include "utils/command.hpp" #include "utils/string.hpp" LEMONBUDDY_NS using module_t = unique_ptr; using modulemap_t = map>; enum class event_type { NONE = 0, UPDATE, CHECK, INPUT, QUIT }; struct event { int type; char data[256]{'\0'}; }; class eventloop { public: /** * Queue type */ using entry_t = event; using queue_t = moodycamel::BlockingConcurrentQueue; /** * Construct eventloop */ explicit eventloop(const logger& logger) : m_log(logger) {} /** * Deconstruct eventloop */ ~eventloop() noexcept { for (auto&& block : m_modules) { for (auto&& module : block.second) { auto module_name = module->name(); auto cleanup_ms = time_execution([&module] { module->stop(); module.reset(); }); m_log.trace("eventloop: Deconstruction of %s took %lu ms.", module_name, cleanup_ms); } } } /** * Set callback handler for UPDATE events */ void set_update_cb(callback<>&& cb) { m_update_cb = forward(cb); } /** * Set callback handler for raw INPUT events */ void set_input_db(callback&& cb) { m_unrecognized_input_cb = forward(cb); } /** * Get reference to module map */ modulemap_t& modules() { return m_modules; } /** * Add module to alignment block */ void add_module(const alignment pos, module_t&& module) { modulemap_t::iterator it = m_modules.lower_bound(pos); if (it != m_modules.end() && !(m_modules.key_comp()(pos, it->first))) { it->second.emplace_back(forward(module)); } else { vector vec; vec.emplace_back(forward(module)); m_modules.insert(it, modulemap_t::value_type(pos, move(vec))); } } /** * Enqueue event */ bool enqueue(const entry_t& i) { bool enqueued; if ((enqueued = m_queue.enqueue(i)) == false) { m_log.warn("Failed to queue event (%d)", i.type); } return enqueued; } /** * Start module threads and wait for events on the queue * * @param timeframe Time to wait for subsequent events * @param limit Maximum amount of subsequent events to swallow within timeframe */ template void run(chrono::duration timeframe, int limit) { m_log.info("Starting event loop"); m_running = true; // Start module threads for (auto&& block : m_modules) { for (auto&& module : block.second) { try { m_log.info("Starting %s", module->name()); module->start(); } catch (const application_error& err) { m_log.err("Failed to start '%s' (reason: %s)", module->name(), err.what()); } } } m_log.trace("eventloop: Enter loop"); while (m_running) { entry_t evt, next{static_cast(event_type::NONE)}; m_queue.wait_dequeue(evt); if (!m_running) { break; } if (match_event(evt, event_type::UPDATE)) { int swallowed = 0; while (swallowed++ < limit && m_queue.wait_dequeue_timed(next, timeframe)) { if (match_event(next, event_type::QUIT)) { evt = next; break; } else if (compare_events(evt, next)) { m_log.trace("eventloop: Swallowing event within timeframe"); evt = next; } else { break; } } } forward_event(evt); if (match_event(next, event_type::NONE)) continue; if (compare_events(evt, next)) continue; forward_event(next); } m_log.trace("eventloop: Loop ended"); } /** * Stop main loop by enqueuing a QUIT event */ void stop() { m_log.info("Stopping event loop"); m_running = false; enqueue({static_cast(event_type::QUIT)}); } protected: /** * Test if event matches given type */ bool match_event(entry_t evt, event_type type) { return static_cast(type) == evt.type; } /** * Compare given events */ bool compare_events(entry_t evt, entry_t evt2) { return evt.type == evt2.type; } /** * Forward event to handler based on type */ void forward_event(entry_t evt) { if (evt.type == static_cast(event_type::UPDATE)) { on_update(); } else if (evt.type == static_cast(event_type::INPUT)) { on_input(string{evt.data}); } else if (evt.type == static_cast(event_type::CHECK)) { on_check(); } else if (evt.type == static_cast(event_type::QUIT)) { on_quit(); } else { m_log.warn("Unknown event type for enqueued event (%d)", evt.type); } } /** * Handler for enqueued UPDATE events */ void on_update() { m_log.trace("eventloop: Received UPDATE event"); if (m_update_cb) { m_update_cb(); } else { m_log.warn("No callback to handle update"); } } /** * Handler for enqueued INPUT events */ void on_input(string input) { m_log.trace("eventloop: Received INPUT event"); for (auto&& block : m_modules) { for (auto&& module : block.second) { if (!module->receive_events()) continue; if (module->handle_event(input)) { return; } } } if (m_unrecognized_input_cb) { m_unrecognized_input_cb(input); } else { m_log.warn("No callback to handle unrecognized input"); } } /** * Handler for enqueued CHECK events */ void on_check() { if (!m_running) { return; } for (auto&& block : m_modules) { for (auto&& module : block.second) { if (module->running()) return; } } m_log.warn("No running modules..."); stop(); } /** * Handler for enqueued QUIT events */ void on_quit() { m_log.trace("eventloop: Received QUIT event"); m_running = false; } private: const logger& m_log; queue_t m_queue; modulemap_t m_modules; stateflag m_running; callback<> m_update_cb; callback m_unrecognized_input_cb; }; namespace { /** * Configure injection module */ template > di::injector configure_eventloop() { return di::make_injector(configure_logger()); } } LEMONBUDDY_NS_END