4
0
mirror of https://github.com/QuasarApp/qca.git synced 2025-04-30 21:44:30 +00:00
qca/src/support/synchronizer.cpp

515 lines
10 KiB
C++

/*
* Copyright (C) 2005 Justin Karneges <justin@affinix.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*
*/
#include "qca_support.h"
#include "qca_safetimer.h"
#include <QAbstractEventDispatcher>
#include <QCoreApplication>
#include <QElapsedTimer>
#include <QEvent>
#include <QMutex>
#include <QPair>
#include <QWaitCondition>
//#define TIMERFIXER_DEBUG
#ifdef TIMERFIXER_DEBUG
#include <stdio.h>
#endif
namespace QCA {
//----------------------------------------------------------------------------
// TimerFixer
//----------------------------------------------------------------------------
class TimerFixer : public QObject
{
Q_OBJECT
public:
struct TimerInfo
{
int id;
int interval;
QElapsedTimer time;
bool fixInterval;
TimerInfo() : fixInterval(false) {}
};
TimerFixer *fixerParent;
QList<TimerFixer*> fixerChildren;
QObject *target;
QAbstractEventDispatcher *ed;
QList<TimerInfo> timers;
static bool haveFixer(QObject *obj)
{
return obj->findChild<TimerFixer *>() ? true : false;
}
TimerFixer(QObject *_target, TimerFixer *_fp = nullptr) : QObject(_target)
{
ed = nullptr;
target = _target;
fixerParent = _fp;
if(fixerParent)
fixerParent->fixerChildren.append(this);
#ifdef TIMERFIXER_DEBUG
printf("TimerFixer[%p] pairing with %p (%s)\n", this, target, target->metaObject()->className());
#endif
edlink();
target->installEventFilter(this);
QObjectList list = target->children();
for(int n = 0; n < list.count(); ++n)
hook(list[n]);
}
~TimerFixer() override
{
if(fixerParent)
fixerParent->fixerChildren.removeAll(this);
QList<TimerFixer*> list = fixerChildren;
for(int n = 0; n < list.count(); ++n)
delete list[n];
list.clear();
updateTimerList(); // do this just to trip debug output
target->removeEventFilter(this);
edunlink();
#ifdef TIMERFIXER_DEBUG
printf("TimerFixer[%p] unpaired with %p (%s)\n", this, target, target->metaObject()->className());
#endif
}
bool event(QEvent *e) override
{
switch(e->type())
{
case QEvent::ThreadChange: // this happens second
//printf("TimerFixer[%p] self changing threads\n", this);
edunlink();
QMetaObject::invokeMethod(this, "fixTimers", Qt::QueuedConnection);
break;
default:
break;
}
return QObject::event(e);
}
bool eventFilter(QObject *, QEvent *e) override
{
switch(e->type())
{
case QEvent::ChildAdded:
hook(((QChildEvent *)e)->child());
break;
case QEvent::ChildRemoved:
unhook(((QChildEvent *)e)->child());
break;
case QEvent::Timer:
handleTimerEvent(((QTimerEvent *)e)->timerId());
break;
case QEvent::ThreadChange: // this happens first
#ifdef TIMERFIXER_DEBUG
printf("TimerFixer[%p] target changing threads\n", this);
#endif
break;
default:
break;
}
return false;
}
private Q_SLOTS:
void edlink()
{
ed = QAbstractEventDispatcher::instance();
//printf("TimerFixer[%p] linking to dispatcher %p\n", this, ed);
connect(ed, &QAbstractEventDispatcher::aboutToBlock, this, &TimerFixer::ed_aboutToBlock);
}
void edunlink()
{
//printf("TimerFixer[%p] unlinking from dispatcher %p\n", this, ed);
if(ed)
{
disconnect(ed, &QAbstractEventDispatcher::aboutToBlock, this, &TimerFixer::ed_aboutToBlock);
ed = nullptr;
}
}
void ed_aboutToBlock()
{
//printf("TimerFixer[%p] aboutToBlock\n", this);
updateTimerList();
}
void fixTimers()
{
updateTimerList();
edlink();
for(int n = 0; n < timers.count(); ++n)
{
TimerInfo &info = timers[n];
QThread *objectThread = target->thread();
QAbstractEventDispatcher *ed = QAbstractEventDispatcher::instance(objectThread);
int timeLeft = qMax(info.interval - static_cast<int>(info.time.elapsed()), 0);
info.fixInterval = true;
ed->unregisterTimer(info.id);
info.id = ed->registerTimer(timeLeft, Qt::CoarseTimer, target);
#ifdef TIMERFIXER_DEBUG
printf("TimerFixer[%p] adjusting [%d] to %d\n", this, info.id, timeLeft);
#endif
}
}
private:
void hook(QObject *obj)
{
// don't watch a fixer or any object that already has one
// SafeTimer has own method to fix timers, skip it too
if(obj == this || qobject_cast<TimerFixer *>(obj) || haveFixer(obj) || qobject_cast<SafeTimer*>(obj))
return;
new TimerFixer(obj, this);
}
void unhook(QObject *obj)
{
TimerFixer *t = nullptr;
for(int n = 0; n < fixerChildren.count(); ++n)
{
if(fixerChildren[n]->target == obj)
t = fixerChildren[n];
}
delete t;
}
void handleTimerEvent(int id)
{
bool found = false;
int n;
for(n = 0; n < timers.count(); ++n)
{
if(timers[n].id == id)
{
found = true;
break;
}
}
if(!found)
{
//printf("*** unrecognized timer [%d] activated ***\n", id);
return;
}
TimerInfo &info = timers[n];
#ifdef TIMERFIXER_DEBUG
printf("TimerFixer[%p] timer [%d] activated!\n", this, info.id);
#endif
if(info.fixInterval)
{
#ifdef TIMERFIXER_DEBUG
printf("restoring correct interval (%d)\n", info.interval);
#endif
info.fixInterval = false;
ed->unregisterTimer(info.id);
info.id = ed->registerTimer(info.interval, Qt::CoarseTimer, target);
}
info.time.start();
}
void updateTimerList()
{
QList<QAbstractEventDispatcher::TimerInfo> edtimers;
if(ed)
edtimers = ed->registeredTimers(target);
// removed?
for(int n = 0; n < timers.count(); ++n)
{
bool found = false;
int id = timers[n].id;
for(int i = 0; i < edtimers.count(); ++i)
{
if(edtimers[i].timerId == id)
{
found = true;
break;
}
}
if(!found)
{
timers.removeAt(n);
--n;
#ifdef TIMERFIXER_DEBUG
printf("TimerFixer[%p] timer [%d] removed\n", this, id);
#endif
}
}
// added?
for(int n = 0; n < edtimers.count(); ++n)
{
int id = edtimers[n].timerId;
bool found = false;
for(int i = 0; i < timers.count(); ++i)
{
if(timers[i].id == id)
{
found = true;
break;
}
}
if(!found)
{
TimerInfo info;
info.id = id;
info.interval = edtimers[n].interval;
info.time.start();
timers += info;
#ifdef TIMERFIXER_DEBUG
printf("TimerFixer[%p] timer [%d] added (interval=%d)\n", this, info.id, info.interval);
#endif
}
}
}
};
//----------------------------------------------------------------------------
// Synchronizer
//----------------------------------------------------------------------------
class SynchronizerAgent : public QObject
{
Q_OBJECT
public:
SynchronizerAgent(QObject *parent = nullptr) : QObject(parent)
{
QMetaObject::invokeMethod(this, "started", Qt::QueuedConnection);
}
Q_SIGNALS:
void started();
};
class Synchronizer::Private : public QThread
{
Q_OBJECT
public:
Synchronizer *q;
bool active;
bool do_quit;
bool cond_met;
QObject *obj;
QEventLoop *loop;
SynchronizerAgent *agent;
TimerFixer *fixer;
QMutex m;
QWaitCondition w;
QThread *orig_thread;
Private(QObject *_obj, Synchronizer *_q)
: QThread(_q)
, q(_q)
, active(false)
, do_quit(false)
, cond_met(false)
, obj(_obj)
, loop(nullptr)
, agent(nullptr)
, fixer(nullptr)
, m(QMutex::NonRecursive)
, w()
, orig_thread(nullptr)
{
// SafeTimer has own method to fix timers, skip it too
if (!qobject_cast<SafeTimer*>(obj))
fixer = new TimerFixer(obj);
}
~Private() override
{
stop();
delete fixer;
}
void start()
{
if(active)
return;
m.lock();
active = true;
do_quit = false;
QThread::start();
w.wait(&m);
m.unlock();
}
void stop()
{
if(!active)
return;
m.lock();
do_quit = true;
w.wakeOne();
m.unlock();
wait();
active = false;
}
bool waitForCondition(int msecs)
{
unsigned long time = ULONG_MAX;
if(msecs != -1)
time = msecs;
// move object to the worker thread
cond_met = false;
orig_thread = QThread::currentThread();
q->setParent(nullptr); // don't follow the object
QObject *orig_parent = obj->parent();
obj->setParent(nullptr); // unparent the target or the move will fail
obj->moveToThread(this);
// tell the worker thread to start, wait for completion
m.lock();
w.wakeOne();
if(!w.wait(&m, time))
{
if(loop)
{
// if we timed out, tell the worker to quit
QMetaObject::invokeMethod(loop, "quit");
w.wait(&m);
}
}
// at this point the worker is done. cleanup and return
m.unlock();
// restore parents
obj->setParent(orig_parent);
q->setParent(obj);
return cond_met;
}
void conditionMet()
{
if(!loop)
return;
loop->quit();
cond_met = true;
}
protected:
void run() override
{
m.lock();
QEventLoop eventLoop;
while(true)
{
// thread now sleeps, waiting for work
w.wakeOne();
w.wait(&m);
if(do_quit)
{
m.unlock();
break;
}
loop = &eventLoop;
agent = new SynchronizerAgent;
connect(agent, &SynchronizerAgent::started, this, &Private::agent_started, Qt::DirectConnection);
// run the event loop
eventLoop.exec();
delete agent;
agent = nullptr;
// eventloop done, flush pending events
QCoreApplication::instance()->sendPostedEvents();
QCoreApplication::instance()->sendPostedEvents(nullptr, QEvent::DeferredDelete);
// and move the object back
obj->moveToThread(orig_thread);
m.lock();
loop = nullptr;
w.wakeOne();
}
}
private Q_SLOTS:
void agent_started()
{
m.unlock();
}
};
Synchronizer::Synchronizer(QObject *parent)
:QObject(parent)
{
d = new Private(parent, this);
}
Synchronizer::~Synchronizer()
{
delete d;
}
bool Synchronizer::waitForCondition(int msecs)
{
d->start();
return d->waitForCondition(msecs);
}
void Synchronizer::conditionMet()
{
d->conditionMet();
}
}
#include "synchronizer.moc"