feat(StatusQ): Faster version of SFPM's ExpressionRole
Provides FastExpressionRole component compatible with SortFilterProxyModel. In comparison to original ExpressionRole it allows to define which role values should be provided to the expression's context. This approach improves performance significantly in most cases. Closes: #13047
This commit is contained in:
parent
480673d8be
commit
1c4a936833
|
@ -89,33 +89,35 @@ endif()
|
|||
add_library(StatusQ SHARED
|
||||
${STATUSQ_QRC_COMPILED}
|
||||
include/StatusQ/QClipboardProxy.h
|
||||
include/StatusQ/aggregator.h
|
||||
include/StatusQ/concatmodel.h
|
||||
include/StatusQ/fastexpressionrole.h
|
||||
include/StatusQ/leftjoinmodel.h
|
||||
include/StatusQ/modelutilsinternal.h
|
||||
include/StatusQ/permissionutilsinternal.h
|
||||
include/StatusQ/rolesrenamingmodel.h
|
||||
include/StatusQ/rxvalidator.h
|
||||
include/StatusQ/singleroleaggregator.h
|
||||
include/StatusQ/statussyntaxhighlighter.h
|
||||
include/StatusQ/statuswindow.h
|
||||
include/StatusQ/stringutilsinternal.h
|
||||
include/StatusQ/submodelproxymodel.h
|
||||
include/StatusQ/aggregator.h
|
||||
include/StatusQ/singleroleaggregator.h
|
||||
include/StatusQ/sumaggregator.h
|
||||
src/QClipboardProxy.cpp
|
||||
src/aggregator.cpp
|
||||
src/concatmodel.cpp
|
||||
src/fastexpressionrole.cpp
|
||||
src/leftjoinmodel.cpp
|
||||
src/modelutilsinternal.cpp
|
||||
src/permissionutilsinternal.cpp
|
||||
src/plugin.cpp
|
||||
src/rolesrenamingmodel.cpp
|
||||
src/rxvalidator.cpp
|
||||
src/singleroleaggregator.cpp
|
||||
src/statussyntaxhighlighter.cpp
|
||||
src/statuswindow.cpp
|
||||
src/stringutilsinternal.cpp
|
||||
src/submodelproxymodel.cpp
|
||||
src/aggregator.cpp
|
||||
src/singleroleaggregator.cpp
|
||||
src/sumaggregator.cpp
|
||||
|
||||
# wallet
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <proxyroles/singlerole.h>
|
||||
|
||||
#include <QQmlScriptString>
|
||||
|
||||
class QQmlExpression;
|
||||
|
||||
class FastExpressionRole : public qqsfpm::SingleRole
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QQmlScriptString expression READ expression WRITE setExpression
|
||||
NOTIFY expressionChanged)
|
||||
|
||||
Q_PROPERTY(QStringList expectedRoles READ expectedRoles
|
||||
WRITE setExpectedRoles NOTIFY expectedRolesChanged)
|
||||
|
||||
public:
|
||||
using SingleRole::SingleRole;
|
||||
|
||||
const QQmlScriptString& expression() const;
|
||||
void setExpression(const QQmlScriptString& scriptString);
|
||||
|
||||
void proxyModelCompleted(
|
||||
const qqsfpm::QQmlSortFilterProxyModel& proxyModel) override;
|
||||
|
||||
void setExpectedRoles(const QStringList& expectedRoles);
|
||||
const QStringList& expectedRoles() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void expressionChanged();
|
||||
void expectedRolesChanged();
|
||||
|
||||
private:
|
||||
QVariant data(const QModelIndex& sourceIndex,
|
||||
const qqsfpm::QQmlSortFilterProxyModel& proxyModel) override;
|
||||
void updateContext(const qqsfpm::QQmlSortFilterProxyModel& proxyModel);
|
||||
void updateExpression();
|
||||
|
||||
QQmlScriptString m_scriptString;
|
||||
QQmlExpression* m_expression = nullptr;
|
||||
QQmlContext* m_context = nullptr;
|
||||
|
||||
QStringList m_expectedRoles;
|
||||
};
|
|
@ -0,0 +1,164 @@
|
|||
#include "StatusQ/fastexpressionrole.h"
|
||||
|
||||
#include "qqmlsortfilterproxymodel.h"
|
||||
|
||||
#include <QQmlContext>
|
||||
#include <QQmlExpression>
|
||||
|
||||
using namespace qqsfpm;
|
||||
|
||||
/*!
|
||||
\qmltype FastExpressionRole
|
||||
\inherits SingleRole
|
||||
\inqmlmodule StatusQ
|
||||
\brief A custom role similar to (and based on) SFPM's ExpressionRole but
|
||||
optimized to access only explicitly indicated roles.
|
||||
|
||||
A FastExpressionRole, similarly as \l ExpressionRole, is a \l ProxyRole
|
||||
allowing to implement a custom role based on a javascript expression.
|
||||
However in FastExpressionRole's expression context there are available only
|
||||
roles explicitly listed in \l expectedRoles property:
|
||||
|
||||
\code
|
||||
SortFilterProxyModel {
|
||||
sourceModel: numberModel
|
||||
proxyRoles: FastExpressionRole {
|
||||
name: "c"
|
||||
expression: model.a + model.b
|
||||
expectedRoles: ["a", "b"]
|
||||
}
|
||||
}
|
||||
\endcode
|
||||
|
||||
By accessing only needed roles, the performance is significantly better in
|
||||
comparison to ExpressionRole, especially when the model has multiple
|
||||
FastExpressionRole's.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\qmlproperty expression FastExpressionRole::expression
|
||||
|
||||
See ExpressionRole::expression for details. Unline the original
|
||||
ExpressionRole only roles explicitly declared via expectedRoles are accessible.
|
||||
*/
|
||||
const QQmlScriptString& FastExpressionRole::expression() const
|
||||
{
|
||||
return m_scriptString;
|
||||
}
|
||||
|
||||
void FastExpressionRole::setExpression(const QQmlScriptString& scriptString)
|
||||
{
|
||||
if (m_scriptString == scriptString)
|
||||
return;
|
||||
|
||||
m_scriptString = scriptString;
|
||||
updateExpression();
|
||||
|
||||
emit expressionChanged();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void FastExpressionRole::proxyModelCompleted(const QQmlSortFilterProxyModel& proxyModel)
|
||||
{
|
||||
updateContext(proxyModel);
|
||||
}
|
||||
|
||||
void FastExpressionRole::setExpectedRoles(const QStringList& expectedRoles)
|
||||
{
|
||||
if (m_expectedRoles == expectedRoles)
|
||||
return;
|
||||
|
||||
m_expectedRoles = expectedRoles;
|
||||
emit expectedRolesChanged();
|
||||
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/*!
|
||||
\qmlproperty list<string> FastExpressionRole::expectedRoles
|
||||
|
||||
List of role names intended to be available in the expression's context.
|
||||
*/
|
||||
const QStringList& FastExpressionRole::expectedRoles() const
|
||||
{
|
||||
return m_expectedRoles;
|
||||
}
|
||||
|
||||
QVariant FastExpressionRole::data(const QModelIndex& sourceIndex,
|
||||
const QQmlSortFilterProxyModel& proxyModel)
|
||||
{
|
||||
if (m_scriptString.isEmpty())
|
||||
return {};
|
||||
|
||||
QVariantMap modelMap;
|
||||
auto roles = proxyModel.roleNames();
|
||||
|
||||
QQmlContext context(qmlContext(this));
|
||||
auto addToContext = [&] (const QString &name, const QVariant& value) {
|
||||
context.setContextProperty(name, value);
|
||||
modelMap.insert(name, value);
|
||||
};
|
||||
|
||||
for (auto it = roles.cbegin(); it != roles.cend(); ++it) {
|
||||
auto name = it.value();
|
||||
|
||||
if (!m_expectedRoles.contains(name))
|
||||
continue;
|
||||
|
||||
addToContext(name, proxyModel.sourceData(sourceIndex, it.key()));
|
||||
}
|
||||
|
||||
addToContext(QStringLiteral("index"), sourceIndex.row());
|
||||
|
||||
context.setContextProperty(QStringLiteral("model"), modelMap);
|
||||
|
||||
QQmlExpression expression(m_scriptString, &context);
|
||||
QVariant result = expression.evaluate();
|
||||
|
||||
if (expression.hasError())
|
||||
qWarning() << expression.error();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void FastExpressionRole::updateContext(const QQmlSortFilterProxyModel& proxyModel)
|
||||
{
|
||||
delete m_context;
|
||||
m_context = new QQmlContext(qmlContext(this), this);
|
||||
|
||||
QVariantMap modelMap;
|
||||
|
||||
auto addToContext = [&] (const QString &name, const QVariant& value) {
|
||||
m_context->setContextProperty(name, value);
|
||||
modelMap.insert(name, value);
|
||||
};
|
||||
|
||||
const auto roles = proxyModel.roleNames();
|
||||
|
||||
for (auto it = roles.cbegin(); it != roles.cend(); ++it) {
|
||||
auto name = it.value();
|
||||
|
||||
if (!m_expectedRoles.contains(name))
|
||||
continue;
|
||||
|
||||
addToContext(name, {});
|
||||
}
|
||||
|
||||
addToContext(QStringLiteral("index"), -1);
|
||||
|
||||
m_context->setContextProperty(QStringLiteral("model"), modelMap);
|
||||
updateExpression();
|
||||
}
|
||||
|
||||
void FastExpressionRole::updateExpression()
|
||||
{
|
||||
if (!m_context)
|
||||
return;
|
||||
|
||||
delete m_expression;
|
||||
m_expression = new QQmlExpression(m_scriptString, m_context, nullptr, this);
|
||||
connect(m_expression, &QQmlExpression::valueChanged, this, &FastExpressionRole::invalidate);
|
||||
m_expression->setNotifyOnValueChanged(true);
|
||||
m_expression->evaluate();
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "StatusQ/QClipboardProxy.h"
|
||||
#include "StatusQ/concatmodel.h"
|
||||
#include "StatusQ/fastexpressionrole.h"
|
||||
#include "StatusQ/leftjoinmodel.h"
|
||||
#include "StatusQ/modelutilsinternal.h"
|
||||
#include "StatusQ/permissionutilsinternal.h"
|
||||
|
@ -16,7 +17,6 @@
|
|||
#include "StatusQ/submodelproxymodel.h"
|
||||
#include "StatusQ/sumaggregator.h"
|
||||
|
||||
|
||||
#include "wallet/managetokenscontroller.h"
|
||||
#include "wallet/managetokensmodel.h"
|
||||
|
||||
|
@ -38,6 +38,9 @@ public:
|
|||
|
||||
qmlRegisterType<SourceModel>("StatusQ", 0, 1, "SourceModel");
|
||||
qmlRegisterType<ConcatModel>("StatusQ", 0, 1, "ConcatModel");
|
||||
|
||||
qmlRegisterType<FastExpressionRole>("StatusQ", 0, 1, "FastExpressionRole");
|
||||
|
||||
qmlRegisterType<LeftJoinModel>("StatusQ", 0, 1, "LeftJoinModel");
|
||||
qmlRegisterType<SubmodelProxyModel>("StatusQ", 0, 1, "SubmodelProxyModel");
|
||||
qmlRegisterType<RoleRename>("StatusQ", 0, 1, "RoleRename");
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
import QtQml 2.15
|
||||
import QtQuick 2.15
|
||||
import QtTest 1.15
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import StatusQ 0.1
|
||||
import StatusQ.Core.Utils 0.1
|
||||
|
||||
import StatusQ.TestHelpers 0.1
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
Component {
|
||||
id: testComponent
|
||||
|
||||
QtObject {
|
||||
property int d: 0
|
||||
|
||||
readonly property ListModel source: ListModel {
|
||||
id: listModel
|
||||
|
||||
ListElement { a: 1; b: 2; c: 3 }
|
||||
}
|
||||
|
||||
readonly property ModelAccessObserverProxy observer: ModelAccessObserverProxy {
|
||||
id: observerProxy
|
||||
|
||||
property int accessCounter: 0
|
||||
|
||||
sourceModel: listModel
|
||||
|
||||
onDataAccessed: accessCounter++
|
||||
}
|
||||
|
||||
readonly property FastExpressionRole expressionRole: expressionRole
|
||||
|
||||
readonly property SortFilterProxyModel model: SortFilterProxyModel {
|
||||
id: testModel
|
||||
|
||||
sourceModel: observerProxy
|
||||
|
||||
proxyRoles: [
|
||||
FastExpressionRole {
|
||||
id: expressionRole
|
||||
|
||||
name: "expressionRole"
|
||||
expression: a + model.b + (model.c ?? 0) + d + index
|
||||
|
||||
expectedRoles: ["a", "b"]
|
||||
},
|
||||
FastExpressionRole {
|
||||
name: "expressionRole2"
|
||||
expression: "staticRole"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
readonly property Instantiator instantiator: Instantiator {
|
||||
model: testModel
|
||||
|
||||
QtObject {
|
||||
property string expressionRole: model.expressionRole
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TestCase {
|
||||
name: "FastExpressionRole"
|
||||
|
||||
function test_expressionRoleValue() {
|
||||
const obj = createTemporaryObject(testComponent, root)
|
||||
|
||||
const instantiator = obj.instantiator
|
||||
const listModel = obj.source
|
||||
|
||||
fuzzyCompare(instantiator.object.expressionRole, 3, 1e-7)
|
||||
listModel.setProperty(0, "b", 9)
|
||||
fuzzyCompare(instantiator.object.expressionRole, 10, 1e-7)
|
||||
obj.d = 42
|
||||
fuzzyCompare(instantiator.object.expressionRole, 52, 1e-7)
|
||||
}
|
||||
|
||||
function test_expressionRoleAccessToSource() {
|
||||
const obj = createTemporaryObject(testComponent, root)
|
||||
|
||||
const testModel = obj.model
|
||||
const observerProxy = obj.observer
|
||||
|
||||
observerProxy.accessCounter = 0
|
||||
|
||||
ModelUtils.get(testModel, 0, "expressionRole")
|
||||
compare(observerProxy.accessCounter, 2)
|
||||
|
||||
ModelUtils.get(testModel, 0, "expressionRole2")
|
||||
compare(observerProxy.accessCounter, 2)
|
||||
}
|
||||
|
||||
function test_expressionRoleAccessToSourceViaContextChange() {
|
||||
const obj = createTemporaryObject(testComponent, root)
|
||||
|
||||
const testModel = obj.model
|
||||
const observerProxy = obj.observer
|
||||
|
||||
const instantiator = obj.instantiator
|
||||
|
||||
observerProxy.accessCounter = 0
|
||||
obj.d = 1
|
||||
|
||||
compare(observerProxy.accessCounter, 4)
|
||||
}
|
||||
|
||||
function test_expressionRoleChangeExpectedRoles() {
|
||||
const obj = createTemporaryObject(testComponent, root)
|
||||
|
||||
const instantiator = obj.instantiator
|
||||
const expressionRole = obj.expressionRole
|
||||
|
||||
fuzzyCompare(instantiator.object.expressionRole, 3, 1e-7)
|
||||
|
||||
expressionRole.expectedRoles = ["a", "b", "c"]
|
||||
fuzzyCompare(instantiator.object.expressionRole, 6, 1e-7)
|
||||
|
||||
expressionRole.expectedRoles = ["a", "b"]
|
||||
fuzzyCompare(instantiator.object.expressionRole, 3, 1e-7)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue