1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-19 00:25:07 +00:00

Implement event loop timers.

GObjects can now register a timer with the GEventLoop. This will eventually
cause GTimerEvents to be dispatched to the GObject.

This needed a few supporting changes in the kernel:

- The PIT now ticks 1000 times/sec.
- select() now supports an arbitrary timeout.
- gettimeofday() now returns something in the tv_usec field.

With these changes, the clock window in guitest2 finally ticks on its own.
This commit is contained in:
Andreas Kling 2019-02-01 03:50:06 +01:00
parent 9153666e72
commit 95c3442d59
10 changed files with 140 additions and 7 deletions

View file

@ -6,6 +6,7 @@
#include <LibC/stdio.h>
#include <LibC/fcntl.h>
#include <LibC/string.h>
#include <LibC/time.h>
#include <LibC/sys/select.h>
#include <LibC/gui.h>
@ -137,11 +138,29 @@ void GEventLoop::wait_for_event()
FD_ZERO(&rfds);
FD_SET(m_event_fd, &rfds);
struct timeval timeout = { 0, 0 };
int rc = select(m_event_fd + 1, &rfds, nullptr, nullptr, m_queued_events.is_empty() ? nullptr : &timeout);
if (!m_timers.is_empty())
get_next_timer_expiration(timeout);
int rc = select(m_event_fd + 1, &rfds, nullptr, nullptr, (m_queued_events.is_empty() && m_timers.is_empty()) ? nullptr : &timeout);
if (rc < 0) {
ASSERT_NOT_REACHED();
}
for (auto& it : m_timers) {
auto& timer = *it.value;
if (!timer.has_expired())
continue;
#ifdef GEVENTLOOP_DEBUG
dbgprintf("GEventLoop: Timer %d has expired, sending GTimerEvent to %p\n", timer.timer_id, timer.owner);
#endif
post_event(timer.owner, make<GTimerEvent>(timer.timer_id));
if (timer.should_reload) {
timer.reload();
} else {
// FIXME: Support removing expired timers that don't want to reload.
ASSERT_NOT_REACHED();
}
}
if (!FD_ISSET(m_event_fd, &rfds))
return;
@ -180,3 +199,53 @@ void GEventLoop::wait_for_event()
}
}
}
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;
}
void GEventLoop::get_next_timer_expiration(timeval& soonest)
{
ASSERT(!m_timers.is_empty());
bool has_checked_any = false;
for (auto& it : m_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(GObject& object, int milliseconds, bool should_reload)
{
ASSERT(milliseconds >= 0);
auto timer = make<EventLoopTimer>();
timer->owner = &object;
timer->interval = milliseconds;
timer->reload();
timer->should_reload = should_reload;
int timer_id = ++m_next_timer_id; // FIXME: This will eventually wrap around.
ASSERT(timer_id); // FIXME: Aforementioned wraparound.
timer->timer_id = timer_id;
m_timers.set(timer->timer_id, move(timer));
return timer_id;
}
bool GEventLoop::unregister_timer(int timer_id)
{
auto it = m_timers.find(timer_id);
if (it == m_timers.end())
return false;
m_timers.remove(it);
return true;
}