forked from SerenityOS/serenity
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
LibCore: Add CEventLoop and make LibGUI/GEventLoop inherit from it.
This is shaping up to be quite nice.
- Loading branch information
1 parent
2f1f51b
commit b254241
Showing
6 changed files
with
366 additions
and
289 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
OBJS = \ | ||
CElapsedTimer.o \ | ||
CObject.o \ | ||
CEventLoop.o \ | ||
CEvent.o | ||
|
||
LIBRARY = libcore.a | ||
|
Oops, something went wrong.