From 407cf7341e70feeb24ff63ad11bd2e79dac5823f Mon Sep 17 00:00:00 2001 From: Richard Moe Gustavsen Date: Mon, 5 Nov 2012 14:07:47 +0100 Subject: iOS: QIOSEventDispatcher: implement timer support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I1966a64e6535f32005681db37b4fe5d89dafc70c Reviewed-by: Tor Arne Vestbø --- src/plugins/platforms/ios/qioseventdispatcher.h | 15 ++- src/plugins/platforms/ios/qioseventdispatcher.mm | 136 ++++++++++++++++++++--- 2 files changed, 132 insertions(+), 19 deletions(-) (limited to 'src/plugins') diff --git a/src/plugins/platforms/ios/qioseventdispatcher.h b/src/plugins/platforms/ios/qioseventdispatcher.h index db3eb85ffc..da8464f5ee 100644 --- a/src/plugins/platforms/ios/qioseventdispatcher.h +++ b/src/plugins/platforms/ios/qioseventdispatcher.h @@ -77,6 +77,7 @@ #define QEVENTDISPATCHER_IOS_P_H #include +#include #include 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 diff --git a/src/plugins/platforms/ios/qioseventdispatcher.mm b/src/plugins/platforms/ios/qioseventdispatcher.mm index 85854f711e..191e80668d 100644 --- a/src/plugins/platforms/ios/qioseventdispatcher.mm +++ b/src/plugins/platforms/ios/qioseventdispatcher.mm @@ -76,6 +76,7 @@ #include "qioseventdispatcher.h" #include #include +#include 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(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(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(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()); } -- cgit v1.2.3