Commit 18bf7113 authored by Ulf Hermann's avatar Ulf Hermann

QmlProfiler: Remove template/virtual magic from SortedTimelineModel

We actually don't have to save the actual data in the
SortedTimelineModel if we keep the data synchronized to the ranges. This
is easy to do by just keeping track of the indices when new ranges are
inserted. Like that we can eliminate the virtual function calls from
AbstractTimelineModelPrivate and simplify the type hierarchy.

Change-Id: Ia7aa02df57380932b689ddfe9a50ff2031198a7d
Reviewed-by: default avatarKai Koehne <kai.koehne@digia.com>
parent e91c79cb
......@@ -35,7 +35,7 @@ namespace QmlProfiler {
AbstractTimelineModel::AbstractTimelineModel(AbstractTimelineModelPrivate *dd,
const QString &displayName, QmlDebug::Message message, QmlDebug::RangeType rangeType,
QObject *parent) :
QObject(parent), d_ptr(dd)
SortedTimelineModel(parent), d_ptr(dd)
{
Q_D(AbstractTimelineModel);
d->q_ptr = this;
......@@ -61,48 +61,6 @@ void AbstractTimelineModel::setModelManager(QmlProfilerModelManager *modelManage
d->modelId = d->modelManager->registerModelProxy();
}
int AbstractTimelineModel::count() const
{
Q_D(const AbstractTimelineModel);
return d->count();
}
int AbstractTimelineModel::firstIndex(qint64 startTime) const
{
Q_D(const AbstractTimelineModel);
return d->firstIndex(startTime);
}
int AbstractTimelineModel::firstIndexNoParents(qint64 startTime) const
{
Q_D(const AbstractTimelineModel);
return d->firstIndexNoParents(startTime);
}
int AbstractTimelineModel::lastIndex(qint64 endTime) const
{
Q_D(const AbstractTimelineModel);
return d->lastIndex(endTime);
}
qint64 AbstractTimelineModel::duration(int index) const
{
Q_D(const AbstractTimelineModel);
return d->duration(index);
}
qint64 AbstractTimelineModel::startTime(int index) const
{
Q_D(const AbstractTimelineModel);
return d->startTime(index);
}
qint64 AbstractTimelineModel::endTime(int index) const
{
Q_D(const AbstractTimelineModel);
return d->startTime(index) + d->duration(index);
}
bool AbstractTimelineModel::isEmpty() const
{
return count() == 0;
......@@ -241,8 +199,6 @@ void AbstractTimelineModel::dataChanged()
default:
break;
}
d->rowOffsets.clear();
}
bool AbstractTimelineModel::accepted(const QmlProfilerDataModel::QmlEventTypeData &event) const
......@@ -272,4 +228,19 @@ QString AbstractTimelineModel::displayName() const
return d->displayName;
}
void AbstractTimelineModel::clear()
{
Q_D(AbstractTimelineModel);
bool wasExpanded = d->expanded;
bool hadRowHeights = !d->rowOffsets.empty();
d->rowOffsets.clear();
d->expanded = false;
SortedTimelineModel::clear();
if (hadRowHeights)
emit rowHeightChanged();
if (wasExpanded)
emit expandedChanged();
d->modelManager->modelProxyCountUpdated(d->modelId, 0, 1);
}
}
......@@ -34,13 +34,13 @@
#include "qmlprofiler_global.h"
#include "qmlprofilermodelmanager.h"
#include "qmlprofilerdatamodel.h"
#include <QObject>
#include "sortedtimelinemodel.h"
#include <QVariant>
#include <QColor>
namespace QmlProfiler {
class QMLPROFILER_EXPORT AbstractTimelineModel : public QObject
class QMLPROFILER_EXPORT AbstractTimelineModel : public SortedTimelineModel
{
Q_OBJECT
......@@ -61,13 +61,6 @@ public:
qint64 traceStartTime() const;
qint64 traceEndTime() const;
qint64 traceDuration() const;
qint64 duration(int index) const;
qint64 startTime(int index) const;
qint64 endTime(int index) const;
int firstIndex(qint64 startTime) const;
int firstIndexNoParents(qint64 startTime) const;
int lastIndex(qint64 endTime) const;
int count() const;
bool accepted(const QmlProfilerDataModel::QmlEventTypeData &event) const;
bool expanded() const;
void setExpanded(bool expanded);
......@@ -81,7 +74,6 @@ public:
virtual QVariantMap details(int index) const = 0;
virtual int row(int index) const = 0;
virtual void loadData() = 0;
virtual void clear() = 0;
// Methods which can optionally be implemented by child models.
// returned map should contain "file", "line", "column" properties, or be empty
......@@ -92,6 +84,7 @@ public:
virtual float height(int index) const;
virtual int rowMinValue(int rowNumber) const;
virtual int rowMaxValue(int rowNumber) const;
virtual void clear();
signals:
void expandedChanged();
......
......@@ -36,17 +36,6 @@ namespace QmlProfiler {
class QMLPROFILER_EXPORT AbstractTimelineModel::AbstractTimelineModelPrivate {
public:
virtual ~AbstractTimelineModelPrivate() {}
virtual int count() const = 0;
virtual qint64 duration(int index) const = 0;
virtual qint64 startTime(int index) const = 0;
virtual qint64 lastEndTime() const = 0;
virtual qint64 firstStartTime() const = 0;
virtual int firstIndex(qint64 startTime) const = 0;
virtual int firstIndexNoParents(qint64 startTime) const = 0;
virtual int lastIndex(qint64 endTime) const = 0;
QVector<int> rowOffsets;
QmlProfilerModelManager *modelManager;
int modelId;
......
......@@ -30,7 +30,6 @@
#include "qmlprofilerpainteventsmodelproxy.h"
#include "qmlprofilermodelmanager.h"
#include "qmlprofilerdatamodel.h"
#include "sortedtimelinemodel.h"
#include "abstracttimelinemodel_p.h"
#include <utils/qtcassert.h>
#include <QCoreApplication>
......@@ -46,12 +45,10 @@
namespace QmlProfiler {
namespace Internal {
class PaintEventsModelProxy::PaintEventsModelProxyPrivate :
public SortedTimelineModel<QmlPaintEventData,
AbstractTimelineModel::AbstractTimelineModelPrivate>
class PaintEventsModelProxy::PaintEventsModelProxyPrivate : public AbstractTimelineModelPrivate
{
public:
QVector<PaintEventsModelProxy::QmlPaintEventData> data;
int maxGuiThreadAnimations;
int maxRenderThreadAnimations;
int rowFromThreadId(QmlDebug::AnimationThread threadId) const;
......@@ -72,10 +69,9 @@ PaintEventsModelProxy::PaintEventsModelProxy(QObject *parent)
void PaintEventsModelProxy::clear()
{
Q_D(PaintEventsModelProxy);
d->clear();
d->maxGuiThreadAnimations = d->maxRenderThreadAnimations = 0;
d->expanded = false;
d->modelManager->modelProxyCountUpdated(d->modelId, 0, 1);
d->data.clear();
AbstractTimelineModel::clear();
}
bool PaintEventsModelProxy::accepted(const QmlProfilerDataModel::QmlEventTypeData &event) const
......@@ -125,7 +121,7 @@ void PaintEventsModelProxy::loadData()
lastEvent.animationcount = (int)event.numericData2;
QTC_ASSERT(lastEvent.animationcount > 0, continue);
d->insert(realStartTime, realEndTime - realStartTime, lastEvent);
d->data.insert(insert(realStartTime, realEndTime - realStartTime), lastEvent);
if (lastEvent.threadId == QmlDebug::GuiThread)
d->maxGuiThreadAnimations = qMax(lastEvent.animationcount, d->maxGuiThreadAnimations);
......@@ -135,10 +131,10 @@ void PaintEventsModelProxy::loadData()
minNextStartTimes[lastEvent.threadId] = event.startTime + 1;
d->modelManager->modelProxyCountUpdated(d->modelId, d->count(), referenceList.count());
d->modelManager->modelProxyCountUpdated(d->modelId, count(), referenceList.count());
}
d->computeNesting();
computeNesting();
d->modelManager->modelProxyCountUpdated(d->modelId, 1, 1);
}
......@@ -163,7 +159,7 @@ int PaintEventsModelProxy::PaintEventsModelProxyPrivate::rowFromThreadId(
int PaintEventsModelProxy::row(int index) const
{
Q_D(const PaintEventsModelProxy);
return d->rowFromThreadId(d->range(index).threadId);
return d->rowFromThreadId(d->data[index].threadId);
}
int PaintEventsModelProxy::rowMaxValue(int rowNumber) const
......@@ -183,13 +179,13 @@ int PaintEventsModelProxy::rowMaxValue(int rowNumber) const
int PaintEventsModelProxy::eventId(int index) const
{
Q_D(const PaintEventsModelProxy);
return d->range(index).threadId;
return d->data[index].threadId;
}
QColor PaintEventsModelProxy::color(int index) const
{
Q_D(const PaintEventsModelProxy);
double fpsFraction = d->range(index).framerate / 60.0;
double fpsFraction = d->data[index].framerate / 60.0;
if (fpsFraction > 1.0)
fpsFraction = 1.0;
if (fpsFraction < 0.0)
......@@ -200,16 +196,16 @@ QColor PaintEventsModelProxy::color(int index) const
float PaintEventsModelProxy::height(int index) const
{
Q_D(const PaintEventsModelProxy);
const PaintEventsModelProxyPrivate::Range &range = d->range(index);
const QmlPaintEventData &data = d->data[index];
// Add some height to the events if we're far from the scale threshold of 2 * DefaultRowHeight.
// Like that you can see the smaller events more easily.
int scaleThreshold = 2 * DefaultRowHeight - rowHeight(d->rowFromThreadId(range.threadId));
int scaleThreshold = 2 * DefaultRowHeight - rowHeight(d->rowFromThreadId(data.threadId));
float boost = scaleThreshold > 0 ? (0.15 * scaleThreshold / DefaultRowHeight) : 0;
return boost + (1.0 - boost) * (float)range.animationcount /
(float)(range.threadId == QmlDebug::GuiThread ? d->maxGuiThreadAnimations :
d->maxRenderThreadAnimations);
return boost + (1.0 - boost) * (float)data.animationcount /
(float)(data.threadId == QmlDebug::GuiThread ? d->maxGuiThreadAnimations :
d->maxRenderThreadAnimations);
}
QVariantList PaintEventsModelProxy::labels() const
......@@ -242,10 +238,10 @@ QVariantMap PaintEventsModelProxy::details(int index) const
QVariantMap result;
result.insert(QStringLiteral("displayName"), displayName());
result.insert(tr("Duration"), QmlProfilerBaseModel::formatTime(d->range(index).duration));
result.insert(tr("Framerate"), QString::fromLatin1("%1 FPS").arg(d->range(index).framerate));
result.insert(tr("Animations"), QString::fromLatin1("%1").arg(d->range(index).animationcount));
result.insert(tr("Context"), tr(d->range(index).threadId == QmlDebug::GuiThread ?
result.insert(tr("Duration"), QmlProfilerBaseModel::formatTime(range(index).duration));
result.insert(tr("Framerate"), QString::fromLatin1("%1 FPS").arg(d->data[index].framerate));
result.insert(tr("Animations"), QString::fromLatin1("%1").arg(d->data[index].animationcount));
result.insert(tr("Context"), tr(d->data[index].threadId == QmlDebug::GuiThread ?
"GUI Thread" : "Render Thread"));
return result;
}
......
......@@ -30,7 +30,6 @@
#include "qmlprofilertimelinemodelproxy.h"
#include "qmlprofilermodelmanager.h"
#include "qmlprofilerdatamodel.h"
#include "sortedtimelinemodel.h"
#include "abstracttimelinemodel_p.h"
#include <QCoreApplication>
......@@ -45,9 +44,7 @@
namespace QmlProfiler {
namespace Internal {
class RangeTimelineModel::RangeTimelineModelPrivate :
public SortedTimelineModel<RangeTimelineModel::QmlRangeEventStartInstance,
AbstractTimelineModel::AbstractTimelineModelPrivate>
class RangeTimelineModel::RangeTimelineModelPrivate : public AbstractTimelineModelPrivate
{
public:
// convenience functions
......@@ -55,6 +52,7 @@ public:
void computeExpandedLevels();
void findBindingLoops();
QVector<QmlRangeEventStartInstance> data;
QVector<int> expandedRowTypes;
int contractedRows;
bool seenPaintEvent;
......@@ -68,7 +66,6 @@ RangeTimelineModel::RangeTimelineModel(QmlDebug::RangeType rangeType, QObject *p
{
Q_D(RangeTimelineModel);
d->seenPaintEvent = false;
d->expandedRowTypes.clear();
d->expandedRowTypes << -1;
d->contractedRows = 1;
}
......@@ -76,14 +73,12 @@ RangeTimelineModel::RangeTimelineModel(QmlDebug::RangeType rangeType, QObject *p
void RangeTimelineModel::clear()
{
Q_D(RangeTimelineModel);
d->clear();
d->expandedRowTypes.clear();
d->expandedRowTypes << -1;
d->contractedRows = 1;
d->seenPaintEvent = false;
d->expanded = false;
d->modelManager->modelProxyCountUpdated(d->modelId, 0, 1);
d->data.clear();
AbstractTimelineModel::clear();
}
void RangeTimelineModel::loadData()
......@@ -105,15 +100,15 @@ void RangeTimelineModel::loadData()
d->seenPaintEvent = true;
// store starttime-based instance
d->insert(event.startTime, event.duration, QmlRangeEventStartInstance(event.typeIndex));
d->modelManager->modelProxyCountUpdated(d->modelId, d->count(), eventList.count() * 6);
d->data.insert(insert(event.startTime, event.duration),
QmlRangeEventStartInstance(event.typeIndex));
d->modelManager->modelProxyCountUpdated(d->modelId, count(), eventList.count() * 6);
}
d->modelManager->modelProxyCountUpdated(d->modelId, 2, 6);
// compute range nesting
d->computeNesting();
computeNesting();
// compute nestingLevel - nonexpanded
d->computeNestingContracted();
......@@ -134,8 +129,9 @@ void RangeTimelineModel::loadData()
void RangeTimelineModel::RangeTimelineModelPrivate::computeNestingContracted()
{
Q_Q(RangeTimelineModel);
int i;
int eventCount = count();
int eventCount = q->count();
int nestingLevels = QmlDebug::Constants::QML_MIN_LEVEL;
contractedRows = nestingLevels + 1;
......@@ -143,7 +139,7 @@ void RangeTimelineModel::RangeTimelineModelPrivate::computeNestingContracted()
nestingEndTimes.fill(0, nestingLevels + 1);
for (i = 0; i < eventCount; i++) {
qint64 st = ranges[i].start;
qint64 st = q->ranges[i].start;
// per type
if (nestingEndTimes[nestingLevels] > st) {
......@@ -156,57 +152,57 @@ void RangeTimelineModel::RangeTimelineModelPrivate::computeNestingContracted()
nestingEndTimes[nestingLevels-1] <= st)
nestingLevels--;
}
nestingEndTimes[nestingLevels] = st + ranges[i].duration;
nestingEndTimes[nestingLevels] = st + q->ranges[i].duration;
ranges[i].displayRowCollapsed = nestingLevels;
data[i].displayRowCollapsed = nestingLevels;
}
}
void RangeTimelineModel::RangeTimelineModelPrivate::computeExpandedLevels()
{
Q_Q(RangeTimelineModel);
QHash<int, int> eventRow;
int eventCount = count();
int eventCount = q->count();
for (int i = 0; i < eventCount; i++) {
int eventId = ranges[i].eventId;
int eventId = data[i].eventId;
if (!eventRow.contains(eventId)) {
eventRow[eventId] = expandedRowTypes.size();
expandedRowTypes << eventId;
}
ranges[i].displayRowExpanded = eventRow[eventId];
data[i].displayRowExpanded = eventRow[eventId];
}
}
void RangeTimelineModel::RangeTimelineModelPrivate::findBindingLoops()
{
Q_Q(RangeTimelineModel);
if (rangeType != QmlDebug::Binding && rangeType != QmlDebug::HandlingSignal)
return;
typedef QPair<int, int> CallStackEntry;
QStack<CallStackEntry> callStack;
for (int i = 0; i < count(); ++i) {
Range *event = &ranges[i];
for (int i = 0; i < q->count(); ++i) {
const Range *potentialParent = callStack.isEmpty()
? 0 : &ranges[callStack.top().second];
? 0 : &q->ranges[callStack.top().second];
while (potentialParent
&& !(potentialParent->start + potentialParent->duration > event->start)) {
&& !(potentialParent->start + potentialParent->duration > q->ranges[i].start)) {
callStack.pop();
potentialParent = callStack.isEmpty() ? 0
: &ranges[callStack.top().second];
: &q->ranges[callStack.top().second];
}
// check whether event is already in stack
for (int ii = 0; ii < callStack.size(); ++ii) {
if (callStack.at(ii).first == event->eventId) {
event->bindingLoopHead = callStack.at(ii).second;
if (callStack.at(ii).first == data[i].eventId) {
data[i].bindingLoopHead = callStack.at(ii).second;
break;
}
}
CallStackEntry newEntry(event->eventId, i);
CallStackEntry newEntry(data[i].eventId, i);
callStack.push(newEntry);
}
......@@ -243,21 +239,21 @@ int RangeTimelineModel::row(int index) const
{
Q_D(const RangeTimelineModel);
if (d->expanded)
return d->range(index).displayRowExpanded;
return d->data[index].displayRowExpanded;
else
return d->range(index).displayRowCollapsed;
return d->data[index].displayRowCollapsed;
}
int RangeTimelineModel::eventId(int index) const
{
Q_D(const RangeTimelineModel);
return d->range(index).eventId;
return d->data[index].eventId;
}
int RangeTimelineModel::bindingLoopDest(int index) const
{
Q_D(const RangeTimelineModel);
return d->range(index).bindingLoopHead;
return d->data[index].bindingLoopHead;
}
QColor RangeTimelineModel::color(int index) const
......@@ -296,7 +292,7 @@ QVariantMap RangeTimelineModel::details(int index) const
d->modelManager->qmlModel()->getEventTypes();
result.insert(QStringLiteral("displayName"), categoryLabel(d->rangeType));
result.insert(tr("Duration"), QmlProfilerBaseModel::formatTime(d->range(index).duration));
result.insert(tr("Duration"), QmlProfilerBaseModel::formatTime(range(index).duration));
QString detailsString = types[id].data;
if (detailsString.length() > 40)
......
......@@ -27,6 +27,9 @@
**
****************************************************************************/
#include "sortedtimelinemodel.h"
namespace QmlProfiler {
/*!
\class QmlProfiler::SortedTimelineModel
\brief The SortedTimelineModel class provides a sorted model for timeline data.
......@@ -49,6 +52,11 @@
\fn SortedTimelineModel::clear()
Clears the ranges and their end times.
*/
void SortedTimelineModel::clear()
{
ranges.clear();
endTimes.clear();
}
/*!
\fn int SortedTimelineModel::count() const
......@@ -66,18 +74,13 @@
*/
/*!
\fn const SortedTimelineModel<Data>::Range &SortedTimelineModel::range(int index) const
\fn const SortedTimelineModel::Range &SortedTimelineModel::range(int index) const
Returns the range data at the specified index.
*/
/*!
\fn Data &SortedTimelineModel::data(int index)
Returns modifiable user data for the range at the specified index.
*/
/*!
\fn int SortedTimelineModel::insert(qint64 startTime, qint64 duration, const Data &item)
Inserts the given data at the given time position and returns its index.
\fn int SortedTimelineModel::insert(qint64 startTime, qint64 duration)
Inserts a range at the given time position and returns its index.
*/
/*!
......@@ -114,7 +117,48 @@
*/
/*!
\fn void computeNesting()
\fn void SortedTimelineModel::computeNesting()
Compute all ranges' parents.
\sa findFirstIndex
*/
void SortedTimelineModel::computeNesting()
{
QLinkedList<int> parents;
for (int range = 0; range != count(); ++range) {
Range &current = ranges[range];
for (QLinkedList<int>::iterator parentIt = parents.begin();;) {
Range &parent = ranges[*parentIt];
qint64 parentEnd = parent.start + parent.duration;
if (parentEnd < current.start) {
if (parent.start == current.start) {
if (parent.parent == -1) {
parent.parent = range;
} else {
Range &ancestor = ranges[parent.parent];
if (ancestor.start == current.start &&
ancestor.duration < current.duration)
parent.parent = range;
}
// Just switch the old parent range for the new, larger one
*parentIt = range;
break;
} else {
parentIt = parents.erase(parentIt);
}
} else if (parentEnd >= current.start + current.duration) {
// no need to insert
current.parent = *parentIt;
break;
} else {
++parentIt;
}
if (parentIt == parents.end()) {
parents.append(range);
break;
}
}
}
}
}
......@@ -30,22 +30,21 @@
#ifndef SORTEDTIMELINEMODEL_H
#define SORTEDTIMELINEMODEL_H
#include "abstracttimelinemodel_p.h"
#include "qmlprofiler_global.h"
#include <QObject>
#include <QVector>
#include <QLinkedList>
namespace QmlProfiler {
// The template has to be inserted into the hierarchy of public/private classes when Data is known.
// Otherwise we'd have to add implementation details to the public headers. This is why the class to
// be derived from is given as template parameter.
template<class Data, class Base = AbstractTimelineModel::AbstractTimelineModelPrivate>
class SortedTimelineModel : public Base {
class QMLPROFILER_EXPORT SortedTimelineModel : public QObject {
Q_OBJECT
public:
struct Range : public Data {
Range() : Data(), start(-1), duration(-1), parent(-1) {}
Range(qint64 start, qint64 duration, const Data &item) :
Data(item), start(start), duration(duration), parent(-1) {}
struct Range {
Range() : start(-1), duration(-1), parent(-1) {}
Range(qint64 start, qint64 duration) :
start(start), duration(duration), parent(-1) {}
qint64 start;
qint64 duration;
int parent;
......@@ -61,37 +60,35 @@ public:
inline qint64 timestamp() const {return end;}
};
void clear()
{
ranges.clear();
endTimes.clear();