iOS: QIOSEventDispatcher: implement timer support

Change-Id: I1966a64e6535f32005681db37b4fe5d89dafc70c
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@digia.com>
This commit is contained in:
Richard Moe Gustavsen 2012-11-05 14:07:47 +01:00 committed by Tor Arne Vestbø
parent 145abdc442
commit 407cf7341e
2 changed files with 132 additions and 19 deletions

View File

@ -77,6 +77,7 @@
#define QEVENTDISPATCHER_IOS_P_H
#include <QtCore/qabstracteventdispatcher.h>
#include <QtCore/private/qtimerinfo_unix_p.h>
#include <CoreFoundation/CoreFoundation.h>
QT_BEGIN_NAMESPACE
@ -107,9 +108,19 @@ public:
void flush();
private:
CFRunLoopSourceRef m_postedEventsSource;
static void postedEventsSourceCallback(void *info);
CFRunLoopSourceRef m_postedEventsRunLoopSource;
CFRunLoopSourceRef m_blockingTimerRunLoopSource;
QTimerInfoList m_timerInfoList;
CFRunLoopTimerRef m_runLoopTimerRef;
void processPostedEvents();
void maybeStartCFRunLoopTimer();
void maybeStopCFRunLoopTimer();
static void postedEventsRunLoopCallback(void *info);
static void nonBlockingTimerRunLoopCallback(CFRunLoopTimerRef, void *info);
static void blockingTimerRunLoopCallback(void *info);
};
QT_END_NAMESPACE

View File

@ -76,6 +76,7 @@
#include "qioseventdispatcher.h"
#include <qdebug.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtCore/QThread>
QT_BEGIN_NAMESPACE
QT_USE_NAMESPACE
@ -85,40 +86,134 @@ static Boolean runLoopSourceEqualCallback(const void *info1, const void *info2)
return info1 == info2;
}
void QIOSEventDispatcher::postedEventsSourceCallback(void *info)
void QIOSEventDispatcher::postedEventsRunLoopCallback(void *info)
{
QIOSEventDispatcher *self = static_cast<QIOSEventDispatcher *>(info);
self->processPostedEvents();
}
void QIOSEventDispatcher::nonBlockingTimerRunLoopCallback(CFRunLoopTimerRef, void *info)
{
// The (one and only) CFRunLoopTimer has fired, which means that at least
// one QTimer should now fire as well. Note that CFRunLoopTimer's callback will
// never recurse. So if the app starts a new QEventLoop within this callback, other
// timers will stop working. The work-around is to forward the callback to a
// dedicated CFRunLoopSource that can recurse:
QIOSEventDispatcher *self = static_cast<QIOSEventDispatcher *>(info);
CFRunLoopSourceSignal(self->m_blockingTimerRunLoopSource);
}
void QIOSEventDispatcher::blockingTimerRunLoopCallback(void *info)
{
// TODO:
// We also need to block this new timer source
// along with the posted event source when calling processEvents()
// "manually" to prevent livelock deep in CFRunLoop.
QIOSEventDispatcher *self = static_cast<QIOSEventDispatcher *>(info);
self->m_timerInfoList.activateTimers();
self->maybeStartCFRunLoopTimer();
}
void QIOSEventDispatcher::maybeStartCFRunLoopTimer()
{
// Find out when the next registered timer should fire, and schedule
// runLoopTimer accordingly. If the runLoopTimer does not yet exist, and
// at least one timer is registered, start by creating the timer:
if (m_timerInfoList.isEmpty()) {
Q_ASSERT(m_runLoopTimerRef == 0);
return;
}
CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
CFTimeInterval interval;
if (m_runLoopTimerRef == 0) {
// start the CFRunLoopTimer
CFTimeInterval oneyear = CFTimeInterval(3600. * 24. * 365.);
// calculate when the next timer should fire:
struct timespec tv;
if (m_timerInfoList.timerWait(tv)) {
interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 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
m_runLoopTimerRef = CFRunLoopTimerCreate(0, ttf, oneyear, 0, 0, QIOSEventDispatcher::nonBlockingTimerRunLoopCallback, &info);
Q_ASSERT(m_runLoopTimerRef != 0);
CFRunLoopAddTimer(CFRunLoopGetMain(), m_runLoopTimerRef, kCFRunLoopCommonModes);
} else {
struct timespec tv;
// Calculate when the next timer should fire:
if (m_timerInfoList.timerWait(tv)) {
interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 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(m_runLoopTimerRef);
}
ttf += interval;
CFRunLoopTimerSetNextFireDate(m_runLoopTimerRef, ttf);
}
}
void QIOSEventDispatcher::maybeStopCFRunLoopTimer()
{
if (m_runLoopTimerRef == 0)
return;
CFRunLoopTimerInvalidate(m_runLoopTimerRef);
CFRelease(m_runLoopTimerRef);
m_runLoopTimerRef = 0;
}
void QIOSEventDispatcher::processPostedEvents()
{
qDebug() << __FUNCTION__ << "called";
QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::AllEvents);
}
QIOSEventDispatcher::QIOSEventDispatcher(QObject *parent)
: QAbstractEventDispatcher(parent)
, m_runLoopTimerRef(0)
{
CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
CFRunLoopSourceContext context;
bzero(&context, sizeof(CFRunLoopSourceContext));
context.equal = runLoopSourceEqualCallback;
context.info = this;
// source used to send posted events:
context.perform = QIOSEventDispatcher::postedEventsSourceCallback;
m_postedEventsSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
Q_ASSERT(m_postedEventsSource);
CFRunLoopAddSource(mainRunLoop, m_postedEventsSource, kCFRunLoopCommonModes);
// source used to handle timers:
context.perform = QIOSEventDispatcher::blockingTimerRunLoopCallback;
m_blockingTimerRunLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
Q_ASSERT(m_blockingTimerRunLoopSource);
CFRunLoopAddSource(mainRunLoop, m_blockingTimerRunLoopSource, kCFRunLoopCommonModes);
// source used to handle posted events:
context.perform = QIOSEventDispatcher::postedEventsRunLoopCallback;
m_postedEventsRunLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
Q_ASSERT(m_postedEventsRunLoopSource);
CFRunLoopAddSource(mainRunLoop, m_postedEventsRunLoopSource, kCFRunLoopCommonModes);
}
QIOSEventDispatcher::~QIOSEventDispatcher()
{
CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
CFRunLoopRemoveSource(mainRunLoop, m_postedEventsSource, kCFRunLoopCommonModes);
CFRelease(m_postedEventsSource);
CFRunLoopRemoveSource(mainRunLoop, m_postedEventsRunLoopSource, kCFRunLoopCommonModes);
CFRelease(m_postedEventsRunLoopSource);
qDeleteAll(m_timerInfoList);
maybeStopCFRunLoopTimer();
CFRunLoopRemoveSource(CFRunLoopGetMain(), m_blockingTimerRunLoopSource, kCFRunLoopCommonModes);
CFRelease(m_blockingTimerRunLoopSource);
}
bool QIOSEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
@ -146,13 +241,20 @@ void QIOSEventDispatcher::unregisterSocketNotifier(QSocketNotifier *notifier)
Q_UNUSED(notifier);
}
void QIOSEventDispatcher::registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *object)
void QIOSEventDispatcher::registerTimer(int timerId, int interval, Qt::TimerType timerType, QObject *obj)
{
qDebug() << __FUNCTION__ << "not implemented";
Q_UNUSED(timerId);
Q_UNUSED(interval);
Q_UNUSED(timerType);
Q_UNUSED(object);
#ifndef QT_NO_DEBUG
if (timerId < 1 || interval < 0 || !obj) {
qWarning("QIOSEventDispatcher::registerTimer: invalid arguments");
return;
} else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
qWarning("QIOSEventDispatcher: timers cannot be started from another thread");
return;
}
#endif
m_timerInfoList.registerTimer(timerId, interval, timerType, obj);
maybeStartCFRunLoopTimer();
}
bool QIOSEventDispatcher::unregisterTimer(int timerId)
@ -185,7 +287,7 @@ int QIOSEventDispatcher::remainingTime(int timerId)
void QIOSEventDispatcher::wakeUp()
{
CFRunLoopSourceSignal(m_postedEventsSource);
CFRunLoopSourceSignal(m_postedEventsRunLoopSource);
CFRunLoopWakeUp(CFRunLoopGetMain());
}