mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 21:52:45 +00:00 
			
		
		
		
	LibCore: Add CEventLoop and make LibGUI/GEventLoop inherit from it.
This is shaping up to be quite nice.
This commit is contained in:
		
							parent
							
								
									2f1f51b8ab
								
							
						
					
					
						commit
						b2542414d7
					
				
					 6 changed files with 366 additions and 289 deletions
				
			
		|  | @ -30,7 +30,7 @@ private: | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class CDeferredInvocationEvent : public CEvent { | class CDeferredInvocationEvent : public CEvent { | ||||||
|     friend class GEventLoop; |     friend class CEventLoop; | ||||||
| public: | public: | ||||||
|     CDeferredInvocationEvent(Function<void(CObject&)> invokee) |     CDeferredInvocationEvent(Function<void(CObject&)> invokee) | ||||||
|         : CEvent(CEvent::Type::DeferredInvoke) |         : CEvent(CEvent::Type::DeferredInvoke) | ||||||
|  |  | ||||||
							
								
								
									
										261
									
								
								LibCore/CEventLoop.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										261
									
								
								LibCore/CEventLoop.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,261 @@ | ||||||
|  | #include <LibCore/CObject.h> | ||||||
|  | #include <LibCore/CEventLoop.h> | ||||||
|  | #include <LibCore/CEvent.h> | ||||||
|  | #include <LibGUI/GNotifier.h> | ||||||
|  | #include <LibC/unistd.h> | ||||||
|  | #include <LibC/stdio.h> | ||||||
|  | #include <LibC/fcntl.h> | ||||||
|  | #include <LibC/string.h> | ||||||
|  | #include <LibC/time.h> | ||||||
|  | #include <LibC/sys/select.h> | ||||||
|  | #include <LibC/sys/socket.h> | ||||||
|  | #include <LibC/sys/time.h> | ||||||
|  | #include <LibC/errno.h> | ||||||
|  | #include <LibC/string.h> | ||||||
|  | #include <LibC/stdlib.h> | ||||||
|  | 
 | ||||||
|  | //#define CEVENTLOOP_DEBUG
 | ||||||
|  | 
 | ||||||
|  | static CEventLoop* s_main_event_loop; | ||||||
|  | static Vector<CEventLoop*>* s_event_loop_stack; | ||||||
|  | HashMap<int, OwnPtr<CEventLoop::EventLoopTimer>>* CEventLoop::s_timers; | ||||||
|  | HashTable<GNotifier*>* CEventLoop::s_notifiers; | ||||||
|  | int CEventLoop::s_next_timer_id = 1; | ||||||
|  | 
 | ||||||
|  | CEventLoop::CEventLoop() | ||||||
|  | { | ||||||
|  |     if (!s_event_loop_stack) { | ||||||
|  |         s_event_loop_stack = new Vector<CEventLoop*>; | ||||||
|  |         s_timers = new HashMap<int, OwnPtr<CEventLoop::EventLoopTimer>>; | ||||||
|  |         s_notifiers = new HashTable<GNotifier*>; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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<QueuedEvent> 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<CDeferredInvocationEvent&>(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<CEvent>&& 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<CTimerEvent>(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<EventLoopTimer>(); | ||||||
|  |     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>, GNotifier& notifier) | ||||||
|  | { | ||||||
|  |     s_notifiers->set(¬ifier); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void CEventLoop::unregister_notifier(Badge<GNotifier>, GNotifier& notifier) | ||||||
|  | { | ||||||
|  |     s_notifiers->remove(¬ifier); | ||||||
|  | } | ||||||
							
								
								
									
										76
									
								
								LibCore/CEventLoop.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								LibCore/CEventLoop.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,76 @@ | ||||||
|  | #pragma once | ||||||
|  | 
 | ||||||
|  | #include <AK/Badge.h> | ||||||
|  | #include <AK/HashMap.h> | ||||||
|  | #include <AK/OwnPtr.h> | ||||||
|  | #include <AK/Vector.h> | ||||||
|  | #include <AK/WeakPtr.h> | ||||||
|  | #include <sys/select.h> | ||||||
|  | #include <time.h> | ||||||
|  | 
 | ||||||
|  | class CEvent; | ||||||
|  | class CObject; | ||||||
|  | class GNotifier; | ||||||
|  | 
 | ||||||
|  | class CEventLoop { | ||||||
|  | public: | ||||||
|  |     CEventLoop(); | ||||||
|  |     virtual ~CEventLoop(); | ||||||
|  | 
 | ||||||
|  |     int exec(); | ||||||
|  | 
 | ||||||
|  |     void post_event(CObject& receiver, OwnPtr<CEvent>&&); | ||||||
|  | 
 | ||||||
|  |     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>, GNotifier&); | ||||||
|  |     static void unregister_notifier(Badge<GNotifier>, 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<CObject> receiver; | ||||||
|  |         OwnPtr<CEvent> event; | ||||||
|  |     }; | ||||||
|  |     Vector<QueuedEvent> 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<CObject> owner; | ||||||
|  | 
 | ||||||
|  |         void reload(); | ||||||
|  |         bool has_expired() const; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     static HashMap<int, OwnPtr<EventLoopTimer>>* s_timers; | ||||||
|  |     static int s_next_timer_id; | ||||||
|  | 
 | ||||||
|  |     static HashTable<GNotifier*>* s_notifiers; | ||||||
|  | }; | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| OBJS = \
 | OBJS = \
 | ||||||
|     CElapsedTimer.o \
 |     CElapsedTimer.o \
 | ||||||
|     CObject.o \
 |     CObject.o \
 | ||||||
|  |     CEventLoop.o \
 | ||||||
|     CEvent.o |     CEvent.o | ||||||
| 
 | 
 | ||||||
| LIBRARY = libcore.a | LIBRARY = libcore.a | ||||||
|  |  | ||||||
|  | @ -23,13 +23,8 @@ | ||||||
| //#define COALESCING_DEBUG
 | //#define COALESCING_DEBUG
 | ||||||
| 
 | 
 | ||||||
| static HashMap<GShortcut, GAction*>* g_actions; | static HashMap<GShortcut, GAction*>* g_actions; | ||||||
| static GEventLoop* s_main_event_loop; |  | ||||||
| static Vector<GEventLoop*>* s_event_loop_stack; |  | ||||||
| int GEventLoop::s_event_fd = -1; | int GEventLoop::s_event_fd = -1; | ||||||
| pid_t GEventLoop::s_server_pid = -1; | pid_t GEventLoop::s_server_pid = -1; | ||||||
| HashMap<int, OwnPtr<GEventLoop::EventLoopTimer>>* GEventLoop::s_timers; |  | ||||||
| HashTable<GNotifier*>* GEventLoop::s_notifiers; |  | ||||||
| int GEventLoop::s_next_timer_id = 1; |  | ||||||
| 
 | 
 | ||||||
| void GEventLoop::connect_to_server() | void GEventLoop::connect_to_server() | ||||||
| { | { | ||||||
|  | @ -70,16 +65,10 @@ void GEventLoop::connect_to_server() | ||||||
| 
 | 
 | ||||||
| GEventLoop::GEventLoop() | GEventLoop::GEventLoop() | ||||||
| { | { | ||||||
|     if (!s_event_loop_stack) { |     static bool connected = false; | ||||||
|         s_event_loop_stack = new Vector<GEventLoop*>; |     if (!connected) { | ||||||
|         s_timers = new HashMap<int, OwnPtr<GEventLoop::EventLoopTimer>>; |  | ||||||
|         s_notifiers = new HashTable<GNotifier*>; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (!s_main_event_loop) { |  | ||||||
|         s_main_event_loop = this; |  | ||||||
|         s_event_loop_stack->append(this); |  | ||||||
|         connect_to_server(); |         connect_to_server(); | ||||||
|  |         connected = true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!g_actions) |     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<QueuedEvent> 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<CDeferredInvocationEvent&>(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<CEvent>&& 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) | void GEventLoop::handle_paint_event(const WSAPI_ServerMessage& event, GWindow& window) | ||||||
| { | { | ||||||
| #ifdef GEVENTLOOP_DEBUG | #ifdef GEVENTLOOP_DEBUG | ||||||
|  | @ -284,73 +182,6 @@ void GEventLoop::handle_wm_event(const WSAPI_ServerMessage& event, GWindow& wind | ||||||
|     ASSERT_NOT_REACHED(); |     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<CTimerEvent>(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() | void GEventLoop::process_unprocessed_messages() | ||||||
| { | { | ||||||
|     int coalesced_paints = 0; |     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<EventLoopTimer>(); |  | ||||||
|     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>, GNotifier& notifier) |  | ||||||
| { |  | ||||||
|     s_notifiers->set(¬ifier); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void GEventLoop::unregister_notifier(Badge<GNotifier>, GNotifier& notifier) |  | ||||||
| { |  | ||||||
|     s_notifiers->remove(¬ifier); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool GEventLoop::post_message_to_server(const WSAPI_ClientMessage& message) | bool GEventLoop::post_message_to_server(const WSAPI_ClientMessage& message) | ||||||
| { | { | ||||||
|     int nwritten = write(s_event_fd, &message, sizeof(WSAPI_ClientMessage)); |     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) | bool GEventLoop::wait_for_specific_event(WSAPI_ServerMessage::Type type, WSAPI_ServerMessage& event) | ||||||
| { | { | ||||||
| 
 |  | ||||||
|     for (;;) { |     for (;;) { | ||||||
|         fd_set rfds; |         fd_set rfds; | ||||||
|         FD_ZERO(&rfds); |         FD_ZERO(&rfds); | ||||||
|  |  | ||||||
|  | @ -1,10 +1,6 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <AK/Badge.h> | #include <LibCore/CEventLoop.h> | ||||||
| #include <AK/HashMap.h> |  | ||||||
| #include <AK/OwnPtr.h> |  | ||||||
| #include <AK/Vector.h> |  | ||||||
| #include <AK/WeakPtr.h> |  | ||||||
| #include <WindowServer/WSAPITypes.h> | #include <WindowServer/WSAPITypes.h> | ||||||
| #include <LibGUI/GEvent.h> | #include <LibGUI/GEvent.h> | ||||||
| 
 | 
 | ||||||
|  | @ -13,42 +9,43 @@ class CObject; | ||||||
| class GNotifier; | class GNotifier; | ||||||
| class GWindow; | class GWindow; | ||||||
| 
 | 
 | ||||||
| class GEventLoop { | class GEventLoop final : public CEventLoop { | ||||||
| public: | public: | ||||||
|     GEventLoop(); |     GEventLoop(); | ||||||
|     ~GEventLoop(); |     virtual ~GEventLoop() override; | ||||||
| 
 | 
 | ||||||
|     int exec(); |     static GEventLoop& current() { return static_cast<GEventLoop&>(CEventLoop::current()); } | ||||||
| 
 |  | ||||||
|     void post_event(CObject& receiver, OwnPtr<CEvent>&&); |  | ||||||
| 
 |  | ||||||
|     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>, GNotifier&); |  | ||||||
|     static void unregister_notifier(Badge<GNotifier>, GNotifier&); |  | ||||||
| 
 |  | ||||||
|     void quit(int); |  | ||||||
| 
 | 
 | ||||||
|     static bool post_message_to_server(const WSAPI_ClientMessage&); |     static bool post_message_to_server(const WSAPI_ClientMessage&); | ||||||
|     bool wait_for_specific_event(WSAPI_ServerMessage::Type, WSAPI_ServerMessage&); |     bool wait_for_specific_event(WSAPI_ServerMessage::Type, WSAPI_ServerMessage&); | ||||||
| 
 |  | ||||||
|     WSAPI_ServerMessage sync_request(const WSAPI_ClientMessage& request, WSAPI_ServerMessage::Type response_type); |     WSAPI_ServerMessage sync_request(const WSAPI_ClientMessage& request, WSAPI_ServerMessage::Type response_type); | ||||||
| 
 | 
 | ||||||
|     static pid_t server_pid() { return s_server_pid; } |     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)); |         CEventLoop::take_pending_events_from(other); | ||||||
|         m_unprocessed_messages.append(move(other.m_unprocessed_messages)); |         m_unprocessed_messages.append(move(static_cast<GEventLoop&>(other).m_unprocessed_messages)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| private: | 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(); |     void wait_for_event(); | ||||||
|     bool drain_messages_from_server(); |     bool drain_messages_from_server(); | ||||||
|     void process_unprocessed_messages(); |     void process_unprocessed_messages(); | ||||||
|  | @ -61,37 +58,9 @@ private: | ||||||
|     void handle_menu_event(const WSAPI_ServerMessage&); |     void handle_menu_event(const WSAPI_ServerMessage&); | ||||||
|     void handle_window_entered_or_left_event(const WSAPI_ServerMessage&, GWindow&); |     void handle_window_entered_or_left_event(const WSAPI_ServerMessage&, GWindow&); | ||||||
|     void handle_wm_event(const WSAPI_ServerMessage&, GWindow&); |     void handle_wm_event(const WSAPI_ServerMessage&, GWindow&); | ||||||
|     void get_next_timer_expiration(timeval&); |  | ||||||
|     void connect_to_server(); |     void connect_to_server(); | ||||||
| 
 | 
 | ||||||
|     struct QueuedEvent { |  | ||||||
|         WeakPtr<CObject> receiver; |  | ||||||
|         OwnPtr<CEvent> event; |  | ||||||
|     }; |  | ||||||
|     Vector<QueuedEvent> m_queued_events; |  | ||||||
| 
 |  | ||||||
|     Vector<WSAPI_ServerMessage> m_unprocessed_messages; |     Vector<WSAPI_ServerMessage> 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_server_pid; | ||||||
|     static pid_t s_event_fd; |     static pid_t s_event_fd; | ||||||
| 
 |  | ||||||
|     struct EventLoopTimer { |  | ||||||
|         int timer_id { 0 }; |  | ||||||
|         int interval { 0 }; |  | ||||||
|         timeval fire_time; |  | ||||||
|         bool should_reload { false }; |  | ||||||
|         WeakPtr<CObject> owner; |  | ||||||
| 
 |  | ||||||
|         void reload(); |  | ||||||
|         bool has_expired() const; |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     static HashMap<int, OwnPtr<EventLoopTimer>>* s_timers; |  | ||||||
|     static int s_next_timer_id; |  | ||||||
| 
 |  | ||||||
|     static HashTable<GNotifier*>* s_notifiers; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Andreas Kling
						Andreas Kling