diff --git a/LibCore/CEvent.h b/LibCore/CEvent.h index d9fbb74d70..4010afe607 100644 --- a/LibCore/CEvent.h +++ b/LibCore/CEvent.h @@ -30,7 +30,7 @@ private: }; class CDeferredInvocationEvent : public CEvent { - friend class GEventLoop; + friend class CEventLoop; public: CDeferredInvocationEvent(Function invokee) : CEvent(CEvent::Type::DeferredInvoke) diff --git a/LibCore/CEventLoop.cpp b/LibCore/CEventLoop.cpp new file mode 100644 index 0000000000..d7b474d276 --- /dev/null +++ b/LibCore/CEventLoop.cpp @@ -0,0 +1,261 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define CEVENTLOOP_DEBUG + +static CEventLoop* s_main_event_loop; +static Vector* s_event_loop_stack; +HashMap>* CEventLoop::s_timers; +HashTable* CEventLoop::s_notifiers; +int CEventLoop::s_next_timer_id = 1; + +CEventLoop::CEventLoop() +{ + if (!s_event_loop_stack) { + s_event_loop_stack = new Vector; + s_timers = new HashMap>; + s_notifiers = new HashTable; + } + + if (!s_main_event_loop) { + s_main_event_loop = this; + s_event_loop_stack->append(this); + } + +#ifdef CEVENTLOOP_DEBUG + dbgprintf("(%u) CEventLoop constructed :)\n", getpid()); +#endif +} + +CEventLoop::~CEventLoop() +{ +} + +CEventLoop& CEventLoop::main() +{ + ASSERT(s_main_event_loop); + return *s_main_event_loop; +} + +CEventLoop& CEventLoop::current() +{ + return *s_event_loop_stack->last(); +} + +void CEventLoop::quit(int code) +{ + m_exit_requested = true; + m_exit_code = code; +} + +struct CEventLoopPusher { +public: + CEventLoopPusher(CEventLoop& event_loop) : m_event_loop(event_loop) + { + if (&m_event_loop != s_main_event_loop) { + m_event_loop.take_pending_events_from(CEventLoop::current()); + s_event_loop_stack->append(&event_loop); + } + } + ~CEventLoopPusher() + { + if (&m_event_loop != s_main_event_loop) { + s_event_loop_stack->take_last(); + CEventLoop::current().take_pending_events_from(m_event_loop); + } + } +private: + CEventLoop& m_event_loop; +}; + +int CEventLoop::exec() +{ + CEventLoopPusher pusher(*this); + + m_running = true; + for (;;) { + if (m_exit_requested) + return m_exit_code; + do_processing(); + if (m_queued_events.is_empty()) { + wait_for_event(); + do_processing(); + } + Vector events = move(m_queued_events); + for (auto& queued_event : events) { + auto* receiver = queued_event.receiver.ptr(); + auto& event = *queued_event.event; +#ifdef CEVENTLOOP_DEBUG + dbgprintf("CEventLoop: %s{%p} event %u\n", receiver->class_name(), receiver, (unsigned)event.type()); +#endif + if (!receiver) { + switch (event.type()) { + case CEvent::Quit: + ASSERT_NOT_REACHED(); + return 0; + default: + dbgprintf("Event type %u with no receiver :(\n", event.type()); + } + } else if (event.type() == CEvent::Type::DeferredInvoke) { + printf("DeferredInvoke: receiver=%s{%p}\n", receiver->class_name(), receiver); + static_cast(event).m_invokee(*receiver); + } else { + receiver->event(event); + } + + if (m_exit_requested) { + auto rejigged_event_queue = move(events); + rejigged_event_queue.append(move(m_queued_events)); + m_queued_events = move(rejigged_event_queue); + return m_exit_code; + } + } + } + ASSERT_NOT_REACHED(); +} + +void CEventLoop::post_event(CObject& receiver, OwnPtr&& event) +{ +#ifdef CEVENTLOOP_DEBUG + dbgprintf("CEventLoop::post_event: {%u} << receiver=%p, event=%p\n", m_queued_events.size(), &receiver, event.ptr()); +#endif + m_queued_events.append({ receiver.make_weak_ptr(), move(event) }); +} + +void CEventLoop::wait_for_event() +{ + fd_set rfds; + fd_set wfds; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + + int max_fd = 0; + auto add_fd_to_set = [&max_fd] (int fd, fd_set& set){ + FD_SET(fd, &set); + if (fd > max_fd) + max_fd = fd; + }; + + int max_fd_added = -1; + add_file_descriptors_for_select(rfds, max_fd_added); + max_fd = max(max_fd, max_fd_added); + for (auto& notifier : *s_notifiers) { + if (notifier->event_mask() & GNotifier::Read) + add_fd_to_set(notifier->fd(), rfds); + if (notifier->event_mask() & GNotifier::Write) + add_fd_to_set(notifier->fd(), wfds); + if (notifier->event_mask() & GNotifier::Exceptional) + ASSERT_NOT_REACHED(); + } + + struct timeval timeout = { 0, 0 }; + if (!s_timers->is_empty() && m_queued_events.is_empty()) + get_next_timer_expiration(timeout); + + int rc = select(max_fd + 1, &rfds, &wfds, nullptr, (m_queued_events.is_empty() && s_timers->is_empty()) ? nullptr : &timeout); + if (rc < 0) { + ASSERT_NOT_REACHED(); + } + + for (auto& it : *s_timers) { + auto& timer = *it.value; + if (!timer.has_expired()) + continue; +#ifdef CEVENTLOOP_DEBUG + dbgprintf("CEventLoop: Timer %d has expired, sending CTimerEvent to %p\n", timer.timer_id, timer.owner); +#endif + post_event(*timer.owner, make(timer.timer_id)); + if (timer.should_reload) { + timer.reload(); + } else { + // FIXME: Support removing expired timers that don't want to reload. + ASSERT_NOT_REACHED(); + } + } + + for (auto& notifier : *s_notifiers) { + if (FD_ISSET(notifier->fd(), &rfds)) { + if (notifier->on_ready_to_read) + notifier->on_ready_to_read(*notifier); + } + if (FD_ISSET(notifier->fd(), &wfds)) { + if (notifier->on_ready_to_write) + notifier->on_ready_to_write(*notifier); + } + } + + process_file_descriptors_after_select(rfds); +} + +bool CEventLoop::EventLoopTimer::has_expired() const +{ + timeval now; + gettimeofday(&now, nullptr); + return now.tv_sec > fire_time.tv_sec || (now.tv_sec == fire_time.tv_sec && now.tv_usec >= fire_time.tv_usec); +} + +void CEventLoop::EventLoopTimer::reload() +{ + gettimeofday(&fire_time, nullptr); + fire_time.tv_sec += interval / 1000; + fire_time.tv_usec += (interval % 1000) * 1000; +} + +void CEventLoop::get_next_timer_expiration(timeval& soonest) +{ + ASSERT(!s_timers->is_empty()); + bool has_checked_any = false; + for (auto& it : *s_timers) { + auto& fire_time = it.value->fire_time; + if (!has_checked_any || fire_time.tv_sec < soonest.tv_sec || (fire_time.tv_sec == soonest.tv_sec && fire_time.tv_usec < soonest.tv_usec)) + soonest = fire_time; + has_checked_any = true; + } +} + +int CEventLoop::register_timer(CObject& object, int milliseconds, bool should_reload) +{ + ASSERT(milliseconds >= 0); + auto timer = make(); + timer->owner = object.make_weak_ptr(); + timer->interval = milliseconds; + timer->reload(); + timer->should_reload = should_reload; + int timer_id = ++s_next_timer_id; // FIXME: This will eventually wrap around. + ASSERT(timer_id); // FIXME: Aforementioned wraparound. + timer->timer_id = timer_id; + s_timers->set(timer->timer_id, move(timer)); + return timer_id; +} + +bool CEventLoop::unregister_timer(int timer_id) +{ + auto it = s_timers->find(timer_id); + if (it == s_timers->end()) + return false; + s_timers->remove(it); + return true; +} + +void CEventLoop::register_notifier(Badge, GNotifier& notifier) +{ + s_notifiers->set(¬ifier); +} + +void CEventLoop::unregister_notifier(Badge, GNotifier& notifier) +{ + s_notifiers->remove(¬ifier); +} diff --git a/LibCore/CEventLoop.h b/LibCore/CEventLoop.h new file mode 100644 index 0000000000..2de219c5cd --- /dev/null +++ b/LibCore/CEventLoop.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +class CEvent; +class CObject; +class GNotifier; + +class CEventLoop { +public: + CEventLoop(); + virtual ~CEventLoop(); + + int exec(); + + void post_event(CObject& receiver, OwnPtr&&); + + static CEventLoop& main(); + static CEventLoop& current(); + + bool running() const { return m_running; } + + static int register_timer(CObject&, int milliseconds, bool should_reload); + static bool unregister_timer(int timer_id); + + static void register_notifier(Badge, GNotifier&); + static void unregister_notifier(Badge, GNotifier&); + + void quit(int); + + virtual void take_pending_events_from(CEventLoop& other) + { + m_queued_events.append(move(other.m_queued_events)); + } + +protected: + virtual void add_file_descriptors_for_select(fd_set&, int& max_fd) { UNUSED_PARAM(max_fd); } + virtual void process_file_descriptors_after_select(const fd_set&) { } + virtual void do_processing() { } + +private: + void wait_for_event(); + void get_next_timer_expiration(timeval&); + + struct QueuedEvent { + WeakPtr receiver; + OwnPtr event; + }; + Vector m_queued_events; + + bool m_running { false }; + bool m_exit_requested { false }; + int m_exit_code { 0 }; + + struct EventLoopTimer { + int timer_id { 0 }; + int interval { 0 }; + timeval fire_time; + bool should_reload { false }; + WeakPtr owner; + + void reload(); + bool has_expired() const; + }; + + static HashMap>* s_timers; + static int s_next_timer_id; + + static HashTable* s_notifiers; +}; diff --git a/LibCore/Makefile b/LibCore/Makefile index 1a7d41a678..2d90c6b16b 100644 --- a/LibCore/Makefile +++ b/LibCore/Makefile @@ -1,6 +1,7 @@ OBJS = \ CElapsedTimer.o \ CObject.o \ + CEventLoop.o \ CEvent.o LIBRARY = libcore.a diff --git a/LibGUI/GEventLoop.cpp b/LibGUI/GEventLoop.cpp index dd234b6397..8e1bc69bc0 100644 --- a/LibGUI/GEventLoop.cpp +++ b/LibGUI/GEventLoop.cpp @@ -23,13 +23,8 @@ //#define COALESCING_DEBUG static HashMap* g_actions; -static GEventLoop* s_main_event_loop; -static Vector* s_event_loop_stack; int GEventLoop::s_event_fd = -1; pid_t GEventLoop::s_server_pid = -1; -HashMap>* GEventLoop::s_timers; -HashTable* GEventLoop::s_notifiers; -int GEventLoop::s_next_timer_id = 1; void GEventLoop::connect_to_server() { @@ -70,16 +65,10 @@ void GEventLoop::connect_to_server() GEventLoop::GEventLoop() { - if (!s_event_loop_stack) { - s_event_loop_stack = new Vector; - s_timers = new HashMap>; - s_notifiers = new HashTable; - } - - if (!s_main_event_loop) { - s_main_event_loop = this; - s_event_loop_stack->append(this); + static bool connected = false; + if (!connected) { connect_to_server(); + connected = true; } if (!g_actions) @@ -94,97 +83,6 @@ GEventLoop::~GEventLoop() { } -GEventLoop& GEventLoop::main() -{ - ASSERT(s_main_event_loop); - return *s_main_event_loop; -} - -GEventLoop& GEventLoop::current() -{ - return *s_event_loop_stack->last(); -} - -void GEventLoop::quit(int code) -{ - m_exit_requested = true; - m_exit_code = code; -} - -struct GEventLoopPusher { -public: - GEventLoopPusher(GEventLoop& event_loop) : m_event_loop(event_loop) - { - if (&m_event_loop != s_main_event_loop) { - m_event_loop.take_pending_events_from(GEventLoop::current()); - s_event_loop_stack->append(&event_loop); - } - } - ~GEventLoopPusher() - { - if (&m_event_loop != s_main_event_loop) { - s_event_loop_stack->take_last(); - GEventLoop::current().take_pending_events_from(m_event_loop); - } - } -private: - GEventLoop& m_event_loop; -}; - -int GEventLoop::exec() -{ - GEventLoopPusher pusher(*this); - - m_running = true; - for (;;) { - if (m_exit_requested) - return m_exit_code; - process_unprocessed_messages(); - if (m_queued_events.is_empty()) { - wait_for_event(); - process_unprocessed_messages(); - } - Vector events = move(m_queued_events); - for (auto& queued_event : events) { - auto* receiver = queued_event.receiver.ptr(); - auto& event = *queued_event.event; -#ifdef GEVENTLOOP_DEBUG - dbgprintf("GEventLoop: %s{%p} event %u\n", receiver->class_name(), receiver, (unsigned)event.type()); -#endif - if (!receiver) { - switch (event.type()) { - case CEvent::Quit: - ASSERT_NOT_REACHED(); - return 0; - default: - dbgprintf("Event type %u with no receiver :(\n", event.type()); - } - } else if (event.type() == CEvent::Type::DeferredInvoke) { - printf("DeferredInvoke: receiver=%s{%p}\n", receiver->class_name(), receiver); - static_cast(event).m_invokee(*receiver); - } else { - receiver->event(event); - } - - if (m_exit_requested) { - auto rejigged_event_queue = move(events); - rejigged_event_queue.append(move(m_queued_events)); - m_queued_events = move(rejigged_event_queue); - return m_exit_code; - } - } - } - ASSERT_NOT_REACHED(); -} - -void GEventLoop::post_event(CObject& receiver, OwnPtr&& event) -{ -#ifdef GEVENTLOOP_DEBUG - dbgprintf("GEventLoop::post_event: {%u} << receiver=%p, event=%p\n", m_queued_events.size(), &receiver, event.ptr()); -#endif - m_queued_events.append({ receiver.make_weak_ptr(), move(event) }); -} - void GEventLoop::handle_paint_event(const WSAPI_ServerMessage& event, GWindow& window) { #ifdef GEVENTLOOP_DEBUG @@ -284,73 +182,6 @@ void GEventLoop::handle_wm_event(const WSAPI_ServerMessage& event, GWindow& wind ASSERT_NOT_REACHED(); } -void GEventLoop::wait_for_event() -{ - fd_set rfds; - fd_set wfds; - FD_ZERO(&rfds); - FD_ZERO(&wfds); - - int max_fd = 0; - auto add_fd_to_set = [&max_fd] (int fd, fd_set& set){ - FD_SET(fd, &set); - if (fd > max_fd) - max_fd = fd; - }; - - add_fd_to_set(s_event_fd, rfds); - for (auto& notifier : *s_notifiers) { - if (notifier->event_mask() & GNotifier::Read) - add_fd_to_set(notifier->fd(), rfds); - if (notifier->event_mask() & GNotifier::Write) - add_fd_to_set(notifier->fd(), wfds); - if (notifier->event_mask() & GNotifier::Exceptional) - ASSERT_NOT_REACHED(); - } - - struct timeval timeout = { 0, 0 }; - if (!s_timers->is_empty() && m_queued_events.is_empty()) - get_next_timer_expiration(timeout); - ASSERT(m_unprocessed_messages.is_empty()); - int rc = select(max_fd + 1, &rfds, &wfds, nullptr, (m_queued_events.is_empty() && s_timers->is_empty()) ? nullptr : &timeout); - if (rc < 0) { - ASSERT_NOT_REACHED(); - } - - for (auto& it : *s_timers) { - auto& timer = *it.value; - if (!timer.has_expired()) - continue; -#ifdef GEVENTLOOP_DEBUG - dbgprintf("GEventLoop: Timer %d has expired, sending CTimerEvent to %p\n", timer.timer_id, timer.owner); -#endif - post_event(*timer.owner, make(timer.timer_id)); - if (timer.should_reload) { - timer.reload(); - } else { - // FIXME: Support removing expired timers that don't want to reload. - ASSERT_NOT_REACHED(); - } - } - - for (auto& notifier : *s_notifiers) { - if (FD_ISSET(notifier->fd(), &rfds)) { - if (notifier->on_ready_to_read) - notifier->on_ready_to_read(*notifier); - } - if (FD_ISSET(notifier->fd(), &wfds)) { - if (notifier->on_ready_to_write) - notifier->on_ready_to_write(*notifier); - } - } - - if (!FD_ISSET(s_event_fd, &rfds)) - return; - - bool success = drain_messages_from_server(); - ASSERT(success); -} - void GEventLoop::process_unprocessed_messages() { int coalesced_paints = 0; @@ -491,66 +322,6 @@ bool GEventLoop::drain_messages_from_server() } } -bool GEventLoop::EventLoopTimer::has_expired() const -{ - timeval now; - gettimeofday(&now, nullptr); - return now.tv_sec > fire_time.tv_sec || (now.tv_sec == fire_time.tv_sec && now.tv_usec >= fire_time.tv_usec); -} - -void GEventLoop::EventLoopTimer::reload() -{ - gettimeofday(&fire_time, nullptr); - fire_time.tv_sec += interval / 1000; - fire_time.tv_usec += (interval % 1000) * 1000; -} - -void GEventLoop::get_next_timer_expiration(timeval& soonest) -{ - ASSERT(!s_timers->is_empty()); - bool has_checked_any = false; - for (auto& it : *s_timers) { - auto& fire_time = it.value->fire_time; - if (!has_checked_any || fire_time.tv_sec < soonest.tv_sec || (fire_time.tv_sec == soonest.tv_sec && fire_time.tv_usec < soonest.tv_usec)) - soonest = fire_time; - has_checked_any = true; - } -} - -int GEventLoop::register_timer(CObject& object, int milliseconds, bool should_reload) -{ - ASSERT(milliseconds >= 0); - auto timer = make(); - timer->owner = object.make_weak_ptr(); - timer->interval = milliseconds; - timer->reload(); - timer->should_reload = should_reload; - int timer_id = ++s_next_timer_id; // FIXME: This will eventually wrap around. - ASSERT(timer_id); // FIXME: Aforementioned wraparound. - timer->timer_id = timer_id; - s_timers->set(timer->timer_id, move(timer)); - return timer_id; -} - -bool GEventLoop::unregister_timer(int timer_id) -{ - auto it = s_timers->find(timer_id); - if (it == s_timers->end()) - return false; - s_timers->remove(it); - return true; -} - -void GEventLoop::register_notifier(Badge, GNotifier& notifier) -{ - s_notifiers->set(¬ifier); -} - -void GEventLoop::unregister_notifier(Badge, GNotifier& notifier) -{ - s_notifiers->remove(¬ifier); -} - bool GEventLoop::post_message_to_server(const WSAPI_ClientMessage& message) { int nwritten = write(s_event_fd, &message, sizeof(WSAPI_ClientMessage)); @@ -559,7 +330,6 @@ bool GEventLoop::post_message_to_server(const WSAPI_ClientMessage& message) bool GEventLoop::wait_for_specific_event(WSAPI_ServerMessage::Type type, WSAPI_ServerMessage& event) { - for (;;) { fd_set rfds; FD_ZERO(&rfds); diff --git a/LibGUI/GEventLoop.h b/LibGUI/GEventLoop.h index e26bc7ff5b..bf09c4d401 100644 --- a/LibGUI/GEventLoop.h +++ b/LibGUI/GEventLoop.h @@ -1,10 +1,6 @@ #pragma once -#include -#include -#include -#include -#include +#include #include #include @@ -13,42 +9,43 @@ class CObject; class GNotifier; class GWindow; -class GEventLoop { +class GEventLoop final : public CEventLoop { public: GEventLoop(); - ~GEventLoop(); + virtual ~GEventLoop() override; - int exec(); - - void post_event(CObject& receiver, OwnPtr&&); - - static GEventLoop& main(); - static GEventLoop& current(); - - bool running() const { return m_running; } - - static int register_timer(CObject&, int milliseconds, bool should_reload); - static bool unregister_timer(int timer_id); - - static void register_notifier(Badge, GNotifier&); - static void unregister_notifier(Badge, GNotifier&); - - void quit(int); + static GEventLoop& current() { return static_cast(CEventLoop::current()); } static bool post_message_to_server(const WSAPI_ClientMessage&); bool wait_for_specific_event(WSAPI_ServerMessage::Type, WSAPI_ServerMessage&); - WSAPI_ServerMessage sync_request(const WSAPI_ClientMessage& request, WSAPI_ServerMessage::Type response_type); static pid_t server_pid() { return s_server_pid; } - void take_pending_events_from(GEventLoop& other) + virtual void take_pending_events_from(CEventLoop& other) override { - m_queued_events.append(move(other.m_queued_events)); - m_unprocessed_messages.append(move(other.m_unprocessed_messages)); + CEventLoop::take_pending_events_from(other); + m_unprocessed_messages.append(move(static_cast(other).m_unprocessed_messages)); } private: + virtual void add_file_descriptors_for_select(fd_set& fds, int& max_fd_added) override + { + FD_SET(s_event_fd, &fds); + max_fd_added = s_event_fd; + } + + virtual void process_file_descriptors_after_select(const fd_set& fds) override + { + if (FD_ISSET(s_event_fd, &fds)) + drain_messages_from_server(); + } + + virtual void do_processing() override + { + process_unprocessed_messages(); + } + void wait_for_event(); bool drain_messages_from_server(); void process_unprocessed_messages(); @@ -61,37 +58,9 @@ private: void handle_menu_event(const WSAPI_ServerMessage&); void handle_window_entered_or_left_event(const WSAPI_ServerMessage&, GWindow&); void handle_wm_event(const WSAPI_ServerMessage&, GWindow&); - void get_next_timer_expiration(timeval&); void connect_to_server(); - struct QueuedEvent { - WeakPtr receiver; - OwnPtr event; - }; - Vector m_queued_events; - Vector m_unprocessed_messages; - - bool m_running { false }; - bool m_exit_requested { false }; - int m_exit_code { 0 }; - static pid_t s_server_pid; static pid_t s_event_fd; - - struct EventLoopTimer { - int timer_id { 0 }; - int interval { 0 }; - timeval fire_time; - bool should_reload { false }; - WeakPtr owner; - - void reload(); - bool has_expired() const; - }; - - static HashMap>* s_timers; - static int s_next_timer_id; - - static HashTable* s_notifiers; };