Change QCocoaEventDispatcher timer handling to use QTimerInfoList

This gives us support for the various Qt::TimerTypes.

We only use one CFRunLoopTimer to drive all of the Qt timers. We update
the time-to-fire for this timer as we add/remove/fire Qt timers. The
documentation for the CFRunLoopTimerSetNextFireDate() function says that
this is a valid use case, and is more performant than constantly adding
and removing CFRunLoopTimers. The documentation recommends using a large
interval for this use case (the docs say "several decades", but we use 1
year).

Change-Id: Ie7fd7a845f4254699a5b6a5720e7626f2c5e787f
Reviewed-by: Robin Burchell <robin+qt@viroteck.net>
Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@nokia.com>
This commit is contained in:
Bradley T. Hughes 2012-01-05 14:29:15 +01:00 committed by Qt by Nokia
parent c7755d33db
commit d23322bf1c
2 changed files with 100 additions and 106 deletions

View File

@ -92,6 +92,7 @@
#include <QtCore/qstack.h>
#include <QtGui/qwindowdefs.h>
#include <QtCore/private/qabstracteventdispatcher_p.h>
#include <QtCore/private/qtimerinfo_unix_p.h>
#include <CoreFoundation/CoreFoundation.h>
@ -132,21 +133,6 @@ public:
void flush();
};
struct MacTimerInfo {
QCocoaEventDispatcherPrivate *d_ptr;
int id;
int interval;
Qt::TimerType timerType;
QObject *obj;
bool pending;
CFRunLoopTimerRef runLoopTimer;
bool operator==(const MacTimerInfo &other)
{
return (id == other.id);
}
};
typedef QHash<int, MacTimerInfo *> MacTimerHash;
struct MacSocketInfo {
MacSocketInfo() : socket(0), runloop(0), readNotifier(0), writeNotifier(0) {}
CFSocketRef socket;
@ -163,7 +149,12 @@ class QCocoaEventDispatcherPrivate : public QAbstractEventDispatcherPrivate
public:
QCocoaEventDispatcherPrivate();
MacTimerHash macTimerHash;
// timer handling
QTimerInfoList timerInfoList;
CFRunLoopTimerRef runLoopTimerRef;
void maybeStartCFRunLoopTimer();
void maybeStopCFRunLoopTimer();
static void activateTimer(CFRunLoopTimerRef, void *info);
// Set 'blockSendPostedEvents' to true if you _really_ need
// to make sure that qt events are not posted while calling
@ -196,7 +187,6 @@ public:
static Boolean postedEventSourceEqualCallback(const void *info1, const void *info2);
static void postedEventsSourcePerformCallback(void *info);
static void activateTimer(CFRunLoopTimerRef, void *info);
static void waitingObserverCallback(CFRunLoopObserverRef observer,
CFRunLoopActivity activity, void *info);
static void firstLoopEntry(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info);

View File

@ -111,32 +111,79 @@ static inline CFRunLoopRef mainRunLoop()
/* timer call back */
void QCocoaEventDispatcherPrivate::activateTimer(CFRunLoopTimerRef, void *info)
{
MacTimerInfo *tmr = static_cast<MacTimerInfo *>(info);
QCocoaEventDispatcherPrivate *d = tmr->d_ptr;
int timerID = tmr->id;
if (tmr == 0 || tmr->pending == true)
return; // Can't send another timer event if it's pending.
QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info);
(void) d->timerInfoList.activateTimers();
d->maybeStartCFRunLoopTimer();
}
if (d->blockSendPostedEvents) {
QCoreApplication::postEvent(tmr->obj, new QTimerEvent(tmr->id));
} else {
tmr->pending = true;
QTimerEvent e(tmr->id);
QCoreApplication::sendSpontaneousEvent(tmr->obj, &e);
// Get the value again in case the timer gets unregistered during the sendEvent.
tmr = d->macTimerHash.value(timerID);
if (tmr != 0)
tmr->pending = false;
void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
{
if (timerInfoList.isEmpty()) {
// no active timers, so the CFRunLoopTimerRef should not be active either
Q_ASSERT(runLoopTimerRef == 0);
return;
}
if (runLoopTimerRef == 0) {
// start the CFRunLoopTimer
CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
CFTimeInterval interval;
CFTimeInterval oneyear = CFTimeInterval(3600. * 24. * 365.);
// Q: when should the CFRunLoopTimer fire for the first time?
struct timeval tv;
if (timerInfoList.timerWait(tv)) {
// A: when we have timers to fire, of course
interval = qMax(tv.tv_sec + tv.tv_usec / 1000000., 0.0000001);
} else {
// this shouldn't really happen, but in case it does, set the timer to fire a some point in the distant future
interval = oneyear;
}
ttf += interval;
CFRunLoopTimerContext info = { 0, this, 0, 0, 0 };
// create the timer with a large interval, as recommended by the CFRunLoopTimerSetNextFireDate()
// documentation, since we will adjust the timer's time-to-fire as needed to keep Qt timers working
runLoopTimerRef = CFRunLoopTimerCreate(0, ttf, oneyear, 0, 0, QCocoaEventDispatcherPrivate::activateTimer, &info);
Q_ASSERT(runLoopTimerRef != 0);
CFRunLoopAddTimer(mainRunLoop(), runLoopTimerRef, kCFRunLoopCommonModes);
} else {
// calculate when we need to wake up to process timers again
CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
CFTimeInterval interval;
// Q: when should the timer first next?
struct timeval tv;
if (timerInfoList.timerWait(tv)) {
// A: when we have timers to fire, of course
interval = qMax(tv.tv_sec + tv.tv_usec / 1000000., 0.0000001);
} else {
// no timers can fire, but we cannot stop the CFRunLoopTimer, set the timer to fire at some
// point in the distant future (the timer interval is one year)
interval = CFRunLoopTimerGetInterval(runLoopTimerRef);
}
ttf += interval;
CFRunLoopTimerSetNextFireDate(runLoopTimerRef, ttf);
}
}
void QCocoaEventDispatcherPrivate::maybeStopCFRunLoopTimer()
{
if (runLoopTimerRef == 0)
return;
CFRunLoopTimerInvalidate(runLoopTimerRef);
CFRelease(runLoopTimerRef);
runLoopTimerRef = 0;
}
void QCocoaEventDispatcher::registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *obj)
{
#ifndef QT_NO_DEBUG
if (timerId < 1 || interval < 0 || !obj) {
qWarning("QEventDispatcherMac::registerTimer: invalid arguments");
qWarning("QCocoaEventDispatcher::registerTimer: invalid arguments");
return;
} else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
qWarning("QObject::startTimer: timers cannot be started from another thread");
@ -144,58 +191,37 @@ void QCocoaEventDispatcher::registerTimer(int timerId, int interval, Qt::TimerTy
}
#endif
MacTimerInfo *t = new MacTimerInfo();
t->d_ptr = d_func();
t->id = timerId;
t->interval = interval;
t->timerType = timerType;
t->obj = obj;
t->runLoopTimer = 0;
t->pending = false;
CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent();
CFTimeInterval cfinterval = qMax(CFTimeInterval(interval) / 1000, 0.0000001);
fireDate += cfinterval;
t->d_ptr->macTimerHash.insert(timerId, t);
CFRunLoopTimerContext info = { 0, (void *)t, 0, 0, 0 };
t->runLoopTimer = CFRunLoopTimerCreate(0, fireDate, cfinterval, 0, 0,
QCocoaEventDispatcherPrivate::activateTimer, &info);
if (t->runLoopTimer == 0) {
qFatal("QEventDispatcherMac::registerTimer: Cannot create timer");
}
CFRunLoopAddTimer(mainRunLoop(), t->runLoopTimer, kCFRunLoopCommonModes);
Q_D(QCocoaEventDispatcher);
d->timerInfoList.registerTimer(timerId, interval, timerType, obj);
d->maybeStartCFRunLoopTimer();
}
bool QCocoaEventDispatcher::unregisterTimer(int identifier)
bool QCocoaEventDispatcher::unregisterTimer(int timerId)
{
#ifndef QT_NO_DEBUG
if (identifier < 1) {
qWarning("QEventDispatcherMac::unregisterTimer: invalid argument");
if (timerId < 1) {
qWarning("QCocoaEventDispatcher::unregisterTimer: invalid argument");
return false;
} else if (thread() != QThread::currentThread()) {
qWarning("QObject::killTimer: timers cannot be stopped from another thread");
return false;
}
#endif
if (identifier <= 0)
return false; // not init'd or invalid timer
MacTimerInfo *timerInfo = d_func()->macTimerHash.take(identifier);
if (timerInfo == 0)
return false;
CFRunLoopTimerInvalidate(timerInfo->runLoopTimer);
CFRelease(timerInfo->runLoopTimer);
delete timerInfo;
return true;
Q_D(QCocoaEventDispatcher);
bool returnValue = d->timerInfoList.unregisterTimer(timerId);
if (!d->timerInfoList.isEmpty())
d->maybeStartCFRunLoopTimer();
else
d->maybeStopCFRunLoopTimer();
return returnValue;
}
bool QCocoaEventDispatcher::unregisterTimers(QObject *obj)
{
#ifndef QT_NO_DEBUG
if (!obj) {
qWarning("QEventDispatcherMac::unregisterTimers: invalid argument");
qWarning("QCocoaEventDispatcher::unregisterTimers: invalid argument");
return false;
} else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
qWarning("QObject::killTimers: timers cannot be stopped from another thread");
@ -204,40 +230,26 @@ bool QCocoaEventDispatcher::unregisterTimers(QObject *obj)
#endif
Q_D(QCocoaEventDispatcher);
MacTimerHash::iterator it = d->macTimerHash.begin();
while (it != d->macTimerHash.end()) {
MacTimerInfo *timerInfo = it.value();
if (timerInfo->obj != obj) {
++it;
} else {
CFRunLoopTimerInvalidate(timerInfo->runLoopTimer);
CFRelease(timerInfo->runLoopTimer);
delete timerInfo;
it = d->macTimerHash.erase(it);
}
}
return true;
bool returnValue = d->timerInfoList.unregisterTimers(obj);
if (!d->timerInfoList.isEmpty())
d->maybeStartCFRunLoopTimer();
else
d->maybeStopCFRunLoopTimer();
return returnValue;
}
QList<QCocoaEventDispatcher::TimerInfo>
QCocoaEventDispatcher::registeredTimers(QObject *object) const
{
#ifndef QT_NO_DEBUG
if (!object) {
qWarning("QEventDispatcherMac:registeredTimers: invalid argument");
qWarning("QCocoaEventDispatcher:registeredTimers: invalid argument");
return QList<TimerInfo>();
}
QList<TimerInfo> list;
#endif
Q_D(const QCocoaEventDispatcher);
MacTimerHash::const_iterator it = d->macTimerHash.constBegin();
while (it != d->macTimerHash.constEnd()) {
MacTimerInfo *t = it.value();
if (t->obj == object)
list << TimerInfo(t->id, t->interval, t->timerType);
++it;
}
return list;
return d->timerInfoList.registeredTimers(object);
}
/**************************************************************************
@ -882,7 +894,8 @@ void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window)
}
QCocoaEventDispatcherPrivate::QCocoaEventDispatcherPrivate()
: blockSendPostedEvents(false),
: runLoopTimerRef(0),
blockSendPostedEvents(false),
currentExecIsNSAppRun(false),
nsAppRunCalledByQt(false),
cleanupModalSessionsNeeded(false),
@ -1045,18 +1058,9 @@ void QCocoaEventDispatcher::flush()
QCocoaEventDispatcher::~QCocoaEventDispatcher()
{
Q_D(QCocoaEventDispatcher);
//timer cleanup
MacTimerHash::iterator it = d->macTimerHash.begin();
while (it != d->macTimerHash.end()) {
MacTimerInfo *t = it.value();
if (t->runLoopTimer) {
CFRunLoopTimerInvalidate(t->runLoopTimer);
CFRelease(t->runLoopTimer);
}
delete t;
++it;
}
d->macTimerHash.clear();
qDeleteAll(d->timerInfoList);
d->maybeStopCFRunLoopTimer();
// Remove CFSockets from the runloop.
for (MacSocketHash::ConstIterator it = d->macSockets.constBegin(); it != d->macSockets.constEnd(); ++it) {