one way of doing a shared timer with just one timerId at any given moment in a single timerEvent

svn path=/trunk/KDE/kdebase/workspace/libs/plasma/; revision=792012
This commit is contained in:
Aaron J. Seigo 2008-03-30 22:42:53 +00:00
parent a17a25a3b3
commit a9b7d3361d
7 changed files with 709 additions and 0 deletions

View File

@ -54,6 +54,7 @@ set(plasma_LIB_SRCS
svg.cpp
svgpanel.cpp
theme.cpp
timer.cpp
desktoptoolbox.cpp
uiloader.cpp
view.cpp
@ -132,6 +133,7 @@ set(plasma_LIB_INCLUDES
svg.h
svgpanel.h
theme.h
timer.h
uiloader.h
view.h
delegate.h)

54
sharedtimer_p.h Normal file
View File

@ -0,0 +1,54 @@
/*
* Copyright 2008 Aaron Seigo <aseigo@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program 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 General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef PLASMA_SHAREDTIMER_P_H
#define PLASMA_SHAREDTIMER_P_H
#include <QtCore/QObject>
namespace Plasma
{
class Timer;
class TimerDrive : public QObject
{
Q_OBJECT
public:
static TimerDrive *self();
void registerTimer(const Timer *t, int msec);
void unregisterTimer(const Timer *t, int msec);
protected:
void timerEvent(QTimerEvent* event);
private:
friend class TimerDriveSingleton;
explicit TimerDrive(QObject *parent = 0);
~TimerDrive();
class Private;
Private * const d;
};
} // namespace Plasma
#endif

View File

@ -17,3 +17,7 @@ set(appletbrowser_SRCS appletbrowser.cpp)
kde4_add_executable(plasmaappletbrowser ${appletbrowser_SRCS})
target_link_libraries(plasmaappletbrowser ${KDE4_KDEUI_LIBS} plasma)
set(sharedtimertest_SRCS sharedtimertest.cpp)
kde4_add_executable(sharedtimertest ${sharedtimertest_SRCS})
target_link_libraries(sharedtimertest ${KDE4_KDEUI_LIBS} plasma)

131
tests/sharedtimertest.cpp Normal file
View File

@ -0,0 +1,131 @@
/*
* Copyright 2008 Aaron J. Seigo
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2 or
* (at your option) any later version.
*
* This program 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 General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "sharedtimertest.h"
#include <QCoreApplication>
#include <QTime>
#include <QTimer>
#include <KAboutData>
#include <KApplication>
#include <KCmdLineArgs>
#include <KComponentData>
#include <KConfig>
#include <KDebug>
#include "plasma/timer.h"
static const char description[] = I18N_NOOP("Applet browser dialog");
static const char version[] = "1.0";
Tester::Tester(int rounds)
: QObject(0),
m_count(0),
m_round(0),
m_targetRounds(rounds)
{
Plasma::Timer *t50 = new Plasma::Timer(this);
connect(t50, SIGNAL(timeout()), this, SLOT(timeout()));
t50->start(50);
Plasma::Timer *t70 = new Plasma::Timer(this);
connect(t70, SIGNAL(timeout()), this, SLOT(timeout()));
t70->start(70);
Plasma::Timer *t210 = new Plasma::Timer(this);
connect(t210, SIGNAL(timeout()), this, SLOT(timeout()));
t210->start(210);
Plasma::Timer *t500 = new Plasma::Timer(this);
connect(t500, SIGNAL(timeout()), this, SLOT(timeout()));
t500->start(500);
// QObject::connect(t, SIGNAL(timeout()), &app, SLOT(quit()));
Plasma::Timer *t1000 = new Plasma::Timer(this);
t1000->start(1000);
delete t1000;
Plasma::Timer *t95 = new Plasma::Timer(this);
connect(t95, SIGNAL(timeout()), this, SLOT(timeout()));
t95->start(95);
Plasma::Timer *t200 = new Plasma::Timer(this);
connect(t200, SIGNAL(timeout()), this, SLOT(timeout()));
t200->start(200);
m_order << t50 << t70 << t95
<< t50 << t70 << t95
<< t200 << t210
<< t50 << t70 << t95
<< t50 << t70 << t95
<< t200 << t210
<< t50 << t70
<< t500;
}
void Tester::timeout()
{
Plasma::Timer *t = qobject_cast<Plasma::Timer*>(sender());
if (m_order[m_count] != t) {
kFatal() << "round" << m_round << "call" << m_count << "expected"
<< m_order[m_count]->interval() << "but got" << t->interval();
}
//kDebug() << "round" << m_round << "call" << m_count << "got" << t->interval();
++m_count;
if (m_count == m_order.count()) {
m_count = 0;
++m_round;
//kDebug() << "starting round" << m_round;
}
if (m_round == m_targetRounds) {
qApp->quit();
}
}
int main(int argc, char *argv[])
{
KAboutData aboutData("sharedtimertest", 0, ki18n("Plasma Shared Timer Test"),
version, ki18n( description ), KAboutData::License_GPL,
ki18n("(C) 2008, Aaron Seigo"));
aboutData.addAuthor(ki18n("Aaron Seigo"), ki18n("Original author"), "aseigo@kde.org");
KCmdLineArgs::init(argc, argv, &aboutData);
KApplication app;
Tester t(2);
//QTimer::singleShot(2000, &app, SLOT(quit()));
QTime elapsed;
elapsed.start();
int rv = app.exec();
int totalTime = elapsed.elapsed();
kDebug() << "successful run, took" << totalTime;
return rv;
}
#include "sharedtimertest.moc"

31
tests/sharedtimertest.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef SHAREDTIMERTEST_H
#define SHAREDTIMERTEST_H
#include <QList>
#include <QObject>
namespace Plasma
{
class Timer;
} // namespace Plasma
class Tester : public QObject
{
Q_OBJECT
public:
Tester(int rounds);
private Q_SLOTS:
void timeout();
private:
int m_count;
int m_round;
int m_targetRounds;
QList<Plasma::Timer*> m_order;
};
#endif

427
timer.cpp Normal file
View File

@ -0,0 +1,427 @@
/*
* Copyright 2008 Aaron Seigo <aseigo@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program 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 General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "timer.h"
#include "sharedtimer_p.h"
#include <QtCore/QList>
#include <QtCore/QMap>
#include <QtCore/QTimerEvent>
#include <KDE/KDebug>
#include <KDE/KGlobal>
namespace Plasma
{
class Timer::Private
{
public:
Private()
: interval(0),
active(false),
singleShot(false)
{
}
int interval;
bool active;
bool singleShot;
};
Timer::Timer(QObject *parent)
: QObject(parent),
d(new Private)
{
}
Timer::~Timer()
{
TimerDrive::self()->unregisterTimer(this, d->interval);
delete d;
}
void Timer::setInterval(int msec)
{
if (d->active) {
// unregister with the new time
TimerDrive::self()->unregisterTimer(this, d->interval);
}
d->interval = msec;
if (d->active) {
// register with the new time
TimerDrive::self()->registerTimer(this, d->interval);
}
}
int Timer::interval() const
{
return d->interval;
}
void Timer::setSingleShot(bool singleShot)
{
d->singleShot = singleShot;
}
bool Timer::isSingleShot() const
{
return d->singleShot;
}
bool Timer::isActive() const
{
return d->active;
}
void Timer::start(int msec)
{
if (d->interval != msec) {
setInterval(msec);
}
start();
}
void Timer::start()
{
if (d->active) {
return;
}
d->active = true;
TimerDrive::self()->registerTimer(this, d->interval);
}
void Timer::stop()
{
TimerDrive::self()->unregisterTimer(this, d->interval);
d->active = false;
}
void Timer::activate() const
{
emit timeout();
if (d->singleShot) {
TimerDrive::self()->unregisterTimer(this, d->interval);
d->active = false;
}
}
class TimerBundle
{
public:
TimerBundle()
: multiple(-1),
currentCount(-1)
{
}
void setMultiple(int msec, int nextMsec)
{
if (nextMsec > 0) {
currentCount = multiple = nextMsec / msec;
} else {
currentCount = multiple = -1;
}
//kDebug() << "setting msec of" << msec << "with" << nextMsec << multiple;
}
int multiple;
int currentCount;
QList<const Timer*> timers;
};
class TimerDrive::Private
{
public:
Private()
: interval(0),
timerId(-1)
{
}
// msec -> TimerBundle
QMap<int, TimerBundle> bundles;
int interval;
int timerId;
};
class TimerDriveSingleton
{
public:
TimerDrive self;
};
K_GLOBAL_STATIC(TimerDriveSingleton, privateTimerDriveSelf)
TimerDrive* TimerDrive::self()
{
return &privateTimerDriveSelf->self;
}
TimerDrive::TimerDrive(QObject *parent)
: QObject(parent),
d(new Private)
{
}
TimerDrive::~TimerDrive()
{
delete d;
}
void TimerDrive::registerTimer(const Timer *t, int msec)
{
kDebug() << "registering" << t << "for" << msec;
QMap<int, TimerBundle>::iterator it = d->bundles.find(msec);
if (it == d->bundles.end()) {
kDebug( ) << "creating a new bundle";
it = d->bundles.insert(msec, TimerBundle());
if (it != d->bundles.begin()) {
QMap<int, TimerBundle>::iterator prev = it - 1;
prev.value().setMultiple(prev.key(), msec);
}
QMap<int, TimerBundle>::iterator next = it + 1;
if (next != d->bundles.end()) {
it.value().setMultiple(msec, next.key());
}
if (d->bundles.count() == 1) {
kDebug() << "kickstarting the timer";
d->interval = msec;
if (d->timerId != -1) {
// this should never happen, but better safe than sorry?
killTimer(d->timerId);
}
d->timerId = startTimer(msec);
} else if (d->timerId != -1) {
//TODO: figure out if we're injecting an item between the two current timers
}
} else {
// prevent multiple inclusions
foreach (const Timer *existingTimer, it.value().timers) {
if (t == existingTimer) {
return;
}
}
}
kDebug() << "appending our timer";
it.value().timers.append(t);
}
void TimerDrive::unregisterTimer(const Timer *t, int msec)
{
kDebug() << "unregistering" << t << "for" << msec;
// hash of intervals to timer ids
QMap<int, TimerBundle>::iterator it = d->bundles.find(msec);
if (it != d->bundles.end()) {
TimerBundle &bundle = it.value();
bundle.timers.removeAll(t);
if (bundle.timers.count() == 0) {
kDebug() << "no more timers, removing this bundle";
if (it != d->bundles.begin()) {
// update the previous entries multiple
kDebug() << "reseting previous interval";
int nextMsec = 0;
if (it != d->bundles.end()) {
QMap<int, TimerBundle>::iterator next = it + 1;
if (next != d->bundles.end()) {
nextMsec = next.key();
}
}
QMap<int, TimerBundle>::iterator prev = it - 1;
if (prev != d->bundles.end()) {
prev.value().setMultiple(prev.key(), nextMsec);
}
}
d->bundles.erase(it);
}
}
if (d->bundles.count() == 0) {
if (d->timerId != -1) {
killTimer(d->timerId);
d->timerId = -1;
}
} else if (d->timerId != -1) {
//TODO: figure out if we were going to call this guy next, and if so,
// repair the timings
}
}
void TimerDrive::timerEvent(QTimerEvent *event)
{
int id = event->timerId();
if (d->timerId != -1) {
killTimer(d->timerId);
}
QMap<int, TimerBundle>::iterator it = d->bundles.begin();
QMap<int, TimerBundle>::iterator begin = d->bundles.begin();
QMap<int, TimerBundle>::iterator end = d->bundles.end();
if (it == end) {
return;
}
kDebug() << id << d->timerId << "got interval of" << d->interval;
int effectiveInterval = d->interval;
while (it != end) {
int msec = it.key();
TimerBundle &bundle = it.value();
if (msec <= effectiveInterval && bundle.currentCount != 0) {
kDebug() << " +++ activating" << msec << bundle.currentCount << effectiveInterval;
foreach (const Timer *t, bundle.timers) {
t->activate();
}
if (bundle.currentCount >= 0) {
--bundle.currentCount;
}
if (bundle.currentCount >= 0) {
// we haven't reached our multiple, so don't go further.
break;
}
} else {
//kDebug() << " --- skipping" << msec << bundle.currentCount << effectiveInterval;
bundle.currentCount = bundle.multiple;
}
effectiveInterval = msec * bundle.multiple + d->interval;
++it;
}
d->interval = 0;
int intervalAdjustment = 0;
if (it == end) {
d->interval = begin.key();
} else {
int currentCount = it.value().currentCount;
int prevMsec = it.key() * it.value().multiple;
if (++it == end) {
//kDebug() << " end of the line";
d->interval = begin.key();
// take into consideration time spent in the last moments prior to reset.
if (--it != begin) {
int prevInterval = it.key();
while (--it != begin) {
int gap = prevInterval - it.key();
if (gap + intervalAdjustment > d->interval) {
break;
}
intervalAdjustment += gap;
prevInterval = it.key();
}
}
// ensure all our current counts are reset
while (begin != end) {
begin.value().currentCount = begin.value().multiple;
++begin;
}
} else {
// calculate hop to the next value
int interval = it.key() - prevMsec;
if (currentCount != 0) {
// start at the beginning, because we have to take another lap
// before continuing on to the next item
//kDebug() << " reseting";
d->interval = begin.key();
// take into consideration time spent in the last moments prior to reset.
if (--it != begin) {
int prevInterval = it.key();
while (--it != begin) {
int gap = prevInterval - it.key();
if (gap + intervalAdjustment > d->interval) {
break;
}
intervalAdjustment += gap;
prevInterval = it.key();
}
}
} else if (interval > begin.key()) {
// start at the beginning, because our next interval is longer
// than some of our earlier timers
//kDebug() << " looping back";
d->interval = begin.key();
while (begin != it) {
if (begin.key() < interval) {
begin.value().currentCount = begin.value().multiple;
} else {
begin.value().currentCount = 0;
}
++begin;
}
} else if (it.value().currentCount == 0) {
// in this branch, we've looped back once due to the next step
// interval being larger than our beginning interval
//kDebug() << " loop back detected";
while (begin != it) {
begin.value().currentCount = 0;
++begin;
}
while (it.value().currentCount == 0) {
interval = it.key() * it.value().multiple;
++it;
}
d->interval = it.key() - interval;
} else {
// short hop to the next entry
//kDebug() << " short hop to the next stop";
d->interval = interval;
}
}
}
if (d->interval) {
d->timerId = startTimer(d->interval - intervalAdjustment);
kDebug() << " starting interval of" << d->interval
<< " (really" << d->interval - intervalAdjustment << ")" << d->timerId;
} else {
d->timerId = -1;
}
}
} // namespace Plasma
#include "timer.moc"
#include "sharedtimer_p.moc"

60
timer.h Normal file
View File

@ -0,0 +1,60 @@
/*
* Copyright 2008 Aaron Seigo <aseigo@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program 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 General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef PLASMA_TIMER_H
#define PLASMA_TIMER_H
#include <QtCore/QObject>
#include <plasma/plasma_export.h>
namespace Plasma
{
class PLASMA_EXPORT Timer : public QObject
{
Q_OBJECT
public:
explicit Timer(QObject *parent = 0);
~Timer();
int interval() const;
bool isActive() const;
bool isSingleShot() const;
void setInterval(int msec);
void setSingleShot(bool singleShot);
void activate() const;
public Q_SLOTS:
void start(int msec);
void start();
void stop();
Q_SIGNALS:
void timeout() const;
private:
class Private;
Private * const d;
};
} // namespace Plasma
#endif