aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLuca Di Sera <luca.disera@qt.io>2024-12-16 13:35:13 +0100
committerLuca Di Sera <luca.disera@qt.io>2025-02-05 18:39:26 +0100
commita7349e6433d092398d76cafa5408753f06892cd7 (patch)
treea364aba708ab4d7c657a17dd6e0589d4c59bb53b /src
parent063ab57c0d20da54a3fb7ecbeac103bac9aee7da (diff)
Avoid unnecessary read-backs on ReferenceObjects
When certain types are passed over from C++ to QML, QML converts them to a QML usable representation that keeps track of the original data. To do so, the QML representation of the object performs read/write-backs when required to ensure that the representation is kept in sync with the original data, by reading the latest data from the source or writing back changes that are performed on the QML side. Generally, the representation on the QML side cannot know when the original data was modified, so that it has to read the data again each time it accesses the representation to ensure that the latest data is available. The reads can be very expensive. For example, the following snippet: ``` JSON.stringify(foo) ``` Where `foo` is, for example, a `QStringList`, might need to read the data multiple times for each element in the list. For representations that provene from a QObject's property that has a NOTIFY signal, it is possible to cut down on the number of reads by reading only when the data was effectively changed since the last read, that is, the NOTIFY signal of the property was emitted. Similarly, if the property is a BINDABLE, we can subscribe to the BINDABLE itself to track whether a change was performed or not. Thus, to reduce the number of reads when possible, `ReferenceObject`, the base representation for objects that perform read/write-backs, was modified to take into account the NOTIFY signal or BINDABLE status of a property when referencing data that originally provenes from a `QObject`'s property on the C++ side. `QObjectWrapper`, `QQmlTypeWrapper`, `ReferenceObject` and `QMLValueTYpeWrapper` were modified with the usual `Q_MANAGED_TYPE` macro, to allow identifying instances of those objects, which is used to enable the new implementation. The intializer for `ReferenceObject` was modified to behave differently when the referenced data comes from a `QObjectWrapper`, which wraps a `QObject` provening from C++, or a `QQmlTypeWrapper`, which can wrap a `QObject` that is a singleton or an attached property. When it is so, and the part of the wrapped `QObject` that is referenced is a property that has a NOTIFY signal or is a BINDABLE, the `ReferenceObject` instance will now connect to the signal or subscribe to the BINDABLE. A newly added flag, `isDirty`, will be set when the signal is emitted and is used to track whether the data has changed since our last read. `QV4::ReferenceObject::readReference`, the base implementation for read-backs, was modified to take into account the new "isDirty" flag. When the flag is not set, we expect to already have the latest data, and thus do not actually perform the read anymore. Furthermore, the same implementation was modified to take care of setting the `isDirty` flag to a correct state after a read is performed. The connection to the NOTIFY signal is performed through the existing `QQmlNotifier/Endpoint` infrastructure, which allows, between others, to connect to signal emitted by a `QObject`, and should be more performant than a naked connection. Similarly, a BINDABLE is subscribed to by using its usual interface. A new callback was added to be used by `ReferenceObject` to allow setting the `isDirty` flag on the signal emission. `ReferenceObject` will now store a `QQmlNotifierEndpoint` or a `QPropertyNotifier`, that will take care of listening to upstream changes and set the dirty flag as required. A few bug-provening test cases where added to `tst_qqmllanguage` to test that the number of unnecessary reads was indeed reduced. Additional test cases were added to inspect certain aspects of the new implementation to ensure that it works in certain common or edge cases. The internal documentation for `ReferenceObject` was modified to mention this new behavior. Fixes: QTBUG-118847 Fixes: QTBUG-127322 Change-Id: Id62979ae4e03910e1165c293837e5d884727dddc Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'src')
-rw-r--r--src/qml/jsruntime/qv4dateobject_p.h19
-rw-r--r--src/qml/jsruntime/qv4managed.cpp12
-rw-r--r--src/qml/jsruntime/qv4managed_p.h6
-rw-r--r--src/qml/jsruntime/qv4qobjectwrapper_p.h1
-rw-r--r--src/qml/jsruntime/qv4referenceobject.cpp157
-rw-r--r--src/qml/jsruntime/qv4referenceobject_p.h217
-rw-r--r--src/qml/jsruntime/qv4sequenceobject.cpp2
-rw-r--r--src/qml/qml/qqmlnotifier.cpp2
-rw-r--r--src/qml/qml/qqmlnotifier_p.h1
-rw-r--r--src/qml/qml/qqmltypewrapper_p.h1
-rw-r--r--src/qml/qml/qqmlvaluetypewrapper_p.h3
11 files changed, 378 insertions, 43 deletions
diff --git a/src/qml/jsruntime/qv4dateobject_p.h b/src/qml/jsruntime/qv4dateobject_p.h
index 6b4d07f740..248c549744 100644
--- a/src/qml/jsruntime/qv4dateobject_p.h
+++ b/src/qml/jsruntime/qv4dateobject_p.h
@@ -217,20 +217,27 @@ DECLARE_HEAP_OBJECT(DateObject, ReferenceObject) {
if (!object())
return false;
+ if (!isDirty())
+ return true;
+
QV4::Scope scope(internalClass->engine);
QV4::ScopedObject o(scope, object());
+ bool wasRead = false;
if (isVariant()) {
QVariant variant;
void *a[] = { &variant };
- return o->metacall(QMetaObject::ReadProperty, property(), a)
- && setVariant(variant);
+ wasRead = o->metacall(QMetaObject::ReadProperty, property(), a)
+ && setVariant(variant);
+ } else {
+ wasRead = m_date.withWriteonlyStoragePointer([&](void *storagePointer) {
+ void *a[] = { storagePointer };
+ return o->metacall(QMetaObject::ReadProperty, property(), a);
+ }, scope.engine);
}
- return m_date.withWriteonlyStoragePointer([&](void *storagePointer) {
- void *a[] = { storagePointer };
- return o->metacall(QMetaObject::ReadProperty, property(), a);
- }, scope.engine);
+ setDirty(!isConnected() || !wasRead);
+ return wasRead;
}
bool writeBack(int internalIndex = QV4::ReferenceObject::AllProperties)
diff --git a/src/qml/jsruntime/qv4managed.cpp b/src/qml/jsruntime/qv4managed.cpp
index 3524a1adcb..9101d6a177 100644
--- a/src/qml/jsruntime/qv4managed.cpp
+++ b/src/qml/jsruntime/qv4managed.cpp
@@ -106,6 +106,18 @@ QString Managed::className() const
case Type_QmlListProperty:
s = "QML List";
break;
+ case Type_V4QObjectWrapper:
+ s = "V4QObjectWrapper";
+ break;
+ case Type_QMLTypeWrapper:
+ s = "QMLTypeWrapper";
+ break;
+ case Type_V4ReferenceObject:
+ s = "V4ReferenceObject";
+ break;
+ case Type_QMLValueTypeWrapper:
+ s = "QMLValueTypeWrapper";
+ break;
}
return QString::fromLatin1(s);
}
diff --git a/src/qml/jsruntime/qv4managed_p.h b/src/qml/jsruntime/qv4managed_p.h
index aeac8c4914..0a5012ec6a 100644
--- a/src/qml/jsruntime/qv4managed_p.h
+++ b/src/qml/jsruntime/qv4managed_p.h
@@ -122,7 +122,10 @@ public:
Type_V4Sequence,
Type_QmlListProperty,
-
+ Type_V4QObjectWrapper,
+ Type_QMLTypeWrapper,
+ Type_V4ReferenceObject,
+ Type_QMLValueTypeWrapper,
};
Q_MANAGED_TYPE(Invalid)
@@ -131,6 +134,7 @@ public:
inline ExecutionEngine *engine() const { return internalClass()->engine; }
bool isV4SequenceType() const { return d()->internalClass->vtable->type == Type_V4Sequence; }
+ bool isV4QObjectWrapper() const { return d()->internalClass->vtable->type == Type_V4QObjectWrapper; }
bool isQmlListPropertyType() const { return d()->internalClass->vtable->type == Type_QmlListProperty; }
bool isArrayLike() const { return isArrayObject() || isV4SequenceType() || isQmlListPropertyType(); }
diff --git a/src/qml/jsruntime/qv4qobjectwrapper_p.h b/src/qml/jsruntime/qv4qobjectwrapper_p.h
index c467b55a45..7432217f48 100644
--- a/src/qml/jsruntime/qv4qobjectwrapper_p.h
+++ b/src/qml/jsruntime/qv4qobjectwrapper_p.h
@@ -120,6 +120,7 @@ private:
struct Q_QML_EXPORT QObjectWrapper : public Object
{
V4_OBJECT2(QObjectWrapper, Object)
+ Q_MANAGED_TYPE(V4QObjectWrapper)
V4_NEEDS_DESTROY
enum Flag {
diff --git a/src/qml/jsruntime/qv4referenceobject.cpp b/src/qml/jsruntime/qv4referenceobject.cpp
index 941333b59f..c054976868 100644
--- a/src/qml/jsruntime/qv4referenceobject.cpp
+++ b/src/qml/jsruntime/qv4referenceobject.cpp
@@ -135,6 +135,31 @@ DEFINE_OBJECT_VTABLE(QV4::ReferenceObject);
}
\endcode
+ \section2 Intitialization and the IsDirty flag
+
+ In certain cases, we try to avoid read-backs when we know that we
+ have the latest data available already, see \l{Limiting reads on a
+ QObject property}.
+
+ Certain implementation of ReferenceObject, might want to lazily load
+ the data on the first read, rather than on the initialization of the
+ reference. One such example would be `QQmlValueTypeWrapper`.
+
+ When that is the case, the IsDirty flag should be passed at
+ initialiazation time.
+ For example:
+
+ \code
+ void QV4::Heap::Foo::init(Heap::Object *object) {
+ ReferenceObject::init(object, 1, ReferenceObject::Flag::CanWriteBack | ReferenceObject::Flag::IsDirty);
+ // Some further initialization code
+ ...
+ }
+ \endcode
+
+ If the flag is not passed there the first read might be elided,
+ leaving the object in an incorrect state.
+
\section1 Providing the Required Infrastructure for a Default Write-back
Generally, to use the base implementation of write and read backs,
@@ -250,6 +275,88 @@ DEFINE_OBJECT_VTABLE(QV4::ReferenceObject);
}
\endcode
+ \section2 Limiting reads on a QObject property
+
+ In most cases we cannot know whether the original data was modified between
+ read accesses. This generally forces a read to be performed each time we
+ require the latest data, even if we might have it already.
+
+ This can have surprising results, as certain procedure might require reading
+ the data multiple times to be performed, which sometimes can be very
+ expensive.
+
+ When the original data comes from the property of a \c{QObject}, and the
+ property has a \tt{NOTIFY} signal or is \tt{BINDABLE}, we can subscribe to the
+ signal to know when the data is actually modified outside our control, so that
+ we need to fetch it again.
+
+ A ReferenceObject can take advantage of this to reduce the number of reads
+ that are required when dealing with a \c{QObject}'s property provening data.
+
+ ReferenceObjects that are part of a \l{Reference object chains}{chain}, will
+ traverse the chain up until a QOjbect holding root is found, and connect based
+ on that object.
+ As read/write backs in a chain are always propagated up the chain, this allow
+ ReferenceObjects that are not directly parented to relevant element to still
+ avoid unnecesary reads.
+
+ For example, the property of a value type exposed by a Q_GADGET, cannot have a
+ \tt{NOTIFY} signal.
+ Nonetheless, if a change were to occur to the parent value type or the
+ property itself, that change would be propagated up the chain, possibly
+ triggering a \tt{NOTIFY} signal that is part of the chain.
+ Thus, by connecting to that upper \tt{NOTIFY} signal, we can still reliably know
+ if a change was performed on the property itself and thus avoid reduce the
+ number of reads.
+
+ As changes in the chain that do not really invalidate the data of that
+ property will still trigger that same \tt{NOTIFY} signal, sometimes we will
+ perform a read that is unnecessary due to granularity at which we are working.
+ This is the case, returning to the example above, when a different
+ property of that same value type will be changed.
+
+ This should still be a win, as we still expect to cut off multiple reads that
+ would be performed without the optimization.
+
+ The default implementation for QV4::ReferenceObject::readReference will take
+ care of performing this optimization already.
+
+ Derived objects that provide their own readReference implementation can plug
+ into QV4::Heap::ReferenceObject::isDirty, QV4::Heap::ReferenceObject::setDirty
+ and QV4::Heap::ReferenceObject::isConnected to provide the same optimization.
+
+ A ReferenceObject uses a "dirty" flag to track whether the data should be read
+ again.
+ If the ReferenceObject refers to a \c{QObject}'s property that has a
+ \tt{NOTIFY} signal or is \tt{BINDABLE}, it will set the flag each time the
+ \tt{NOTIFY} signal is emitted or the \tt{BINDABLE} is changed.
+
+ isDirty returns whether the flag is set and, thus, a readReference
+ implementation should avoid performing the read itself when the method
+ returns true.
+
+ After a read is performed, the "dirty" flag should be set again if the read
+ was unsuccessful.
+ The flag can be modified by usages of `setDirty`.
+
+ Generally, this only applies to instances of ReferenceObject that provene from
+ a \c{QObject}'s property that has a notify signal, as that is the case that
+ allows us to know when a read is required.
+
+ In all other cases, a ReferenceObject should always be "dirty" and perform a
+ read, as it cannot know if the data was modified since its last read.
+ This case will initially be managed by the base constructor for
+ ReferenceObject, nonetheless derived objects with a custom readReference
+ implementation need to take it into accoutn when setting the "dirty" flag
+ after a read.
+
+ isConnected can be used to discern between the two cases, as it will only
+ return true when the ReferenceObject is connected to a NOTIFY signal that can
+ modify the "dirty" flag.
+ When isConnected is false, a read implementation should always keep the
+ ReferenceObject in a permanent "dirty" state, to ensure that the correct data
+ is fetched when required.
+
\section1 Limiting Write-backs Based on Source Location
\note We generally consider location-aware write-backs to be a
@@ -324,6 +431,56 @@ DEFINE_OBJECT_VTABLE(QV4::ReferenceObject);
QV4::Heap::ReferenceObject::isAttachedToProperty can be used to
recognize whether the reference is still suitable for write-backs in
a location-enforcement-aware way.
+
+ \section1 Reference object chains
+
+ ReferenceObject can be nested.
+
+ For example, consider:
+
+ \code
+ a.b.c
+ \endcode
+
+ Where \c{a} is some object exposed to QML, \c{b} is a property of \c{a} and \c{c} is a property of \c{b}.
+
+ Based on what each of \c{a}, \c{b} and \c{c} is, multiple ReferenceObject
+ instances, parented to one another, might be introduced.
+
+ For example, if \c{a} is a Q_OBJECT, \c{b} is a value type and \c{c} is a type
+ that will be converted to a QV4::Sequence, \c{a} will be wrapped by a
+ QObjectWrapper, \c{b} will be wrapped by a QQmlValueTypeWrapper which is parented
+ to the QObjectWrapper wrapping \c{a} and \c{c} will be a Sequence that is
+ parented to the QQmlValueTypeWrapper wrapping \c{b}.
+
+ This parenting chain is used to enable recursive read/write backs, ensuring
+ that a read/write back travels up the chain as required so that the latest
+ data is available on every relevant element.
+
+ At certain points in the chain, it is possible that a non-reference object is
+ introduced.
+
+ For example, this is always the case when a Q_OBJECT is retrieved, as it will
+ be wrapped in a QObjectWrapper which is not a reference object.
+
+ This breaks the chain of parenting and introduces the start of a new chain.
+ As a QObjectWrapper directly stores a pointer to the original object, it
+ doesn't need to perform the same read/write backs that reference objects do.
+ Similarly, child reference objects only need to read up to the innermost
+ QObjectWrapper in a chain to obtain the latest data.
+
+ Returning to the example above, if \c{b} is a Q_OBJECT instead of a value
+ type, then it will be the root of the reference chain that has \c{c} has its
+ child, without the need to be related to the QObjectWrapper that has been
+ built by accessing \c{a}.
+
+ QQmlTypeWrapper, that can wrap a QObject pointer that represents a
+ singleton or an attached property, behaves as chain root in the
+ exact same way that QObjectWrapper does.
*/
+void QQmlDirtyReferenceObject_callback(QQmlNotifierEndpoint *e, void **) {
+ static_cast<QV4::Heap::ReferenceObjectEndpoint*>(e)->reference->setDirty(true);
+}
+
QT_END_NAMESPACE
diff --git a/src/qml/jsruntime/qv4referenceobject_p.h b/src/qml/jsruntime/qv4referenceobject_p.h
index 094749ce6e..0dbde5bb76 100644
--- a/src/qml/jsruntime/qv4referenceobject_p.h
+++ b/src/qml/jsruntime/qv4referenceobject_p.h
@@ -17,12 +17,23 @@
#include <private/qv4object_p.h>
#include <private/qv4stackframe_p.h>
+#include <private/qqmlnotifier_p.h>
+#include <private/qv4qobjectwrapper_p.h>
QT_BEGIN_NAMESPACE
namespace QV4 {
namespace Heap {
+struct ReferenceObject;
+struct ReferenceObjectEndpoint : QQmlNotifierEndpoint {
+ ReferenceObjectEndpoint(ReferenceObject* reference)
+ : QQmlNotifierEndpoint(QQmlDirtyReferenceObject),
+ reference(reference)
+ {}
+
+ ReferenceObject* reference;
+};
#define ReferenceObjectMembers(class, Member) \
Member(class, Pointer, Object *, m_object)
@@ -35,14 +46,122 @@ DECLARE_HEAP_OBJECT(ReferenceObject, Object) {
CanWriteBack = 1 << 0,
IsVariant = 1 << 1,
EnforcesLocation = 1 << 2,
+ IsDirty = 1 << 3,
};
Q_DECLARE_FLAGS(Flags, Flag);
void init(Object *object, int property, Flags flags)
{
+ auto connectToNotifySignal = [this](QObject* obj, int property, QQmlEngine* engine) {
+ Q_ASSERT(obj);
+ Q_ASSERT(engine);
+
+ referenceEndpoint = new ReferenceObjectEndpoint(this);
+ referenceEndpoint->connect(
+ obj,
+ // Connect and signal emission work on "signal
+ // indexe"s. Those are different from "method
+ // indexes".
+ // The public MetaObject interface can, generally,
+ // give us the "method index" of the notify
+ // signal.
+ // Quite unintuitively, this is true for
+ // "notifySignalIndex".
+ // As the "method index" and the "signal index"
+ // can be different, connecting the "method index"
+ // of the notify signal can incur in issues when
+ // the signal is being emitted and checking for
+ // connected endpoints.
+ // For example, we might be connected to the
+ // "method index" of the notify signal for the
+ // property and end up checking for the
+ // subscribers of a different signal when the
+ // notify signal is emitted, due to the different
+ // meaning of the same index.
+ // Thus we pass by the private interface to ensure
+ // that we are connecting based on the "signal
+ // index" instead.
+ QMetaObjectPrivate::signalIndex(obj->metaObject()->property(property).notifySignal()),
+ engine);
+ // When the object that is being referenced is destroyed, we
+ // need to ensure that one additional read is performed to
+ // invalidate the data we hold.
+ // As the object might be destroyed in a way that doesn't
+ // trigger the notify signal for the relevant property, we react
+ // directly to the destruction itself.
+ // We use a plain connection instead of a QQmlNotifierEndpoint
+ // based connection as, currently, declarative-side signals are
+ // always discarded during destruction (see
+ // QQmlData::signalEmitted).
+ // In theory it should be possible to relax that condition for
+ // the destroy signal specifically, which should allow a more
+ // optimized way of connecting.
+ // Nonetheless this seems to be the only place where we have
+ // this kind of need, and thus go for the simpler solution,
+ // which can be changed later if the need arise.
+ onDelete = new QMetaObject::Connection(QObject::connect(obj, &QObject::destroyed, [this](){ setDirty(true); }));
+ };
+
+ auto connectToBindable = [this](QObject* obj, int property, QQmlEngine* engine) {
+ Q_ASSERT(obj);
+ Q_ASSERT(engine);
+
+ bindableNotifier = new QPropertyNotifier(obj->metaObject()->property(property).bindable(obj).addNotifier([this](){ setDirty(true); }));
+ onDelete = new QMetaObject::Connection(QObject::connect(obj, &QObject::destroyed, [this](){ setDirty(true); }));
+ };
+
setObject(object);
m_property = property;
m_flags = flags;
+
+ while (object &&
+ object->internalClass->vtable->type != Managed::Type_V4QObjectWrapper &&
+ object->internalClass->vtable->type != Managed::Type_QMLTypeWrapper)
+ {
+ if (!(object->internalClass->vtable->type == Managed::Type_V4ReferenceObject) &&
+ !(object->internalClass->vtable->type == Managed::Type_V4Sequence) &&
+ !(object->internalClass->vtable->type == Managed::Type_DateObject) &&
+ !(object->internalClass->vtable->type == Managed::Type_QMLValueTypeWrapper))
+ {
+ break;
+ }
+
+ property = static_cast<QV4::Heap::ReferenceObject*>(object)->property();
+ object = static_cast<QV4::Heap::ReferenceObject*>(object)->object();
+ }
+
+ if (object && object->internalClass->vtable->type == Managed::Type_V4QObjectWrapper)
+ {
+ auto wrapper = static_cast<QV4::Heap::QObjectWrapper*>(object);
+ QObject* obj = wrapper->object();
+
+ if (obj->metaObject()->property(property).hasNotifySignal() && internalClass->engine->qmlEngine())
+ connectToNotifySignal(obj, property, internalClass->engine->qmlEngine());
+
+ if (obj->metaObject()->property(property).isBindable() && internalClass->engine->qmlEngine())
+ connectToBindable(obj, property, internalClass->engine->qmlEngine());
+ }
+
+ if (object && object->internalClass->vtable->type == Managed::Type_QMLTypeWrapper) {
+ auto wrapper = static_cast<QV4::Heap::QQmlTypeWrapper*>(object);
+
+ Scope scope(internalClass->engine);
+ Scoped<QV4::QQmlTypeWrapper> scopedWrapper(scope, wrapper);
+ QObject* obj = scopedWrapper->object();
+
+ if (obj->metaObject()->property(property).hasNotifySignal() && internalClass->engine->qmlEngine())
+ connectToNotifySignal(obj, property, internalClass->engine->qmlEngine());
+
+ if (obj->metaObject()->property(property).isBindable() && internalClass->engine->qmlEngine())
+ connectToBindable(obj, property, internalClass->engine->qmlEngine());
+ }
+
+ // If we could not connect to anything we don't have a way to
+ // dirty on-demand and thus should be in an always dirty state
+ // to ensure that reads go through.
+ if (!isConnected())
+ setDirty(true);
+
Object::init();
}
@@ -82,55 +201,85 @@ DECLARE_HEAP_OBJECT(ReferenceObject, Object) {
bool isReference() const { return m_object; }
-private:
-
- bool hasFlag(Flag flag) const
- {
- return m_flags & quint8(flag);
+ bool isDirty() const { return hasFlag(IsDirty); }
+ void setDirty(bool dirty) { setFlag(IsDirty, dirty); }
+ bool isConnected() {
+ return (referenceEndpoint && referenceEndpoint->isConnected()) || bindableNotifier;
}
- void setFlag(Flag flag, bool set)
- {
- m_flags = set ? (m_flags | quint8(flag)) : (m_flags & ~quint8(flag));
+ void destroy() {
+ if (referenceEndpoint)
+ delete referenceEndpoint;
+
+ if (bindableNotifier)
+ delete bindableNotifier;
+
+ if (onDelete) {
+ QObject::disconnect(*onDelete);
+ delete onDelete;
+ }
}
- const Function *m_function;
- int m_property;
- quint16 m_statementIndex;
- quint8 m_flags;
-};
+ private:
+
+ bool hasFlag(Flag flag) const
+ {
+ return m_flags & quint8(flag);
+ }
-Q_DECLARE_OPERATORS_FOR_FLAGS(ReferenceObject::Flags)
+ void setFlag(Flag flag, bool set)
+ {
+ m_flags = set ? (m_flags | quint8(flag)) : (m_flags & ~quint8(flag));
+ }
-} // namespace Heap
+ const Function *m_function;
+ int m_property;
+ quint16 m_statementIndex;
+ quint8 m_flags;
+ ReferenceObjectEndpoint* referenceEndpoint;
+ QPropertyNotifier* bindableNotifier;
+ QMetaObject::Connection* onDelete;
+ };
+ Q_DECLARE_OPERATORS_FOR_FLAGS(ReferenceObject::Flags)
-struct ReferenceObject : public Object
-{
- V4_OBJECT2(ReferenceObject, Object)
- V4_NEEDS_DESTROY
+ } // namespace Heap
-public:
- static constexpr const int AllProperties = -1;
- template<typename HeapObject>
- static bool readReference(HeapObject *ref)
+ struct ReferenceObject : public Object
{
- if (!ref->object())
- return false;
+ V4_OBJECT2(ReferenceObject, Object)
+ Q_MANAGED_TYPE(V4ReferenceObject)
+ V4_NEEDS_DESTROY
- QV4::Scope scope(ref->internalClass->engine);
- QV4::ScopedObject object(scope, ref->object());
+ public:
+ static constexpr const int AllProperties = -1;
- if (ref->isVariant()) {
- QVariant variant;
- void *a[] = { &variant };
- return object->metacall(QMetaObject::ReadProperty, ref->property(), a)
+ template<typename HeapObject>
+ static bool readReference(HeapObject *ref)
+ {
+ if (!ref->object())
+ return false;
+
+ if (!ref->isDirty())
+ return true;
+
+ QV4::Scope scope(ref->internalClass->engine);
+ QV4::ScopedObject object(scope, ref->object());
+
+ bool wasRead = false;
+ if (ref->isVariant()) {
+ QVariant variant;
+ void *a[] = { &variant };
+ wasRead = object->metacall(QMetaObject::ReadProperty, ref->property(), a)
&& ref->setVariant(variant);
- }
+ } else {
+ void *a[] = { ref->storagePointer() };
+ wasRead = object->metacall(QMetaObject::ReadProperty, ref->property(), a);
+ }
- void *a[] = { ref->storagePointer() };
- return object->metacall(QMetaObject::ReadProperty, ref->property(), a);
+ ref->setDirty(!ref->isConnected() || !wasRead);
+ return wasRead;
}
template<typename HeapObject>
diff --git a/src/qml/jsruntime/qv4sequenceobject.cpp b/src/qml/jsruntime/qv4sequenceobject.cpp
index 416ed959cc..e487b17b25 100644
--- a/src/qml/jsruntime/qv4sequenceobject.cpp
+++ b/src/qml/jsruntime/qv4sequenceobject.cpp
@@ -111,7 +111,7 @@ void Heap::Sequence::init(
QMetaType listType, QMetaSequence metaSequence, const void *container,
Heap::Object *object, int propertyIndex, Heap::ReferenceObject::Flags flags)
{
- ReferenceObject::init(object, propertyIndex, flags);
+ ReferenceObject::init(object, propertyIndex, flags | IsDirty);
initTypes(listType, metaSequence);
if (CppStackFrame *frame = internalClass->engine->currentStackFrame)
diff --git a/src/qml/qml/qqmlnotifier.cpp b/src/qml/qml/qqmlnotifier.cpp
index 23aa32bc3f..b234df6b41 100644
--- a/src/qml/qml/qqmlnotifier.cpp
+++ b/src/qml/qml/qqmlnotifier.cpp
@@ -15,6 +15,7 @@ void QQmlJavaScriptExpressionGuard_callback(QQmlNotifierEndpoint *, void **);
void QQmlVMEMetaObjectEndpoint_callback(QQmlNotifierEndpoint *, void **);
void QQmlUnbindableToUnbindableGuard_callback(QQmlNotifierEndpoint *, void **);
void QQmlUnbindableToBindableGuard_callback(QQmlNotifierEndpoint *, void **);
+void QQmlDirtyReferenceObject_callback(QQmlNotifierEndpoint *, void **);
static Callback QQmlNotifier_callbacks[] = {
nullptr,
@@ -23,6 +24,7 @@ static Callback QQmlNotifier_callbacks[] = {
QQmlVMEMetaObjectEndpoint_callback,
QQmlUnbindableToUnbindableGuard_callback,
QQmlUnbindableToBindableGuard_callback,
+ QQmlDirtyReferenceObject_callback,
};
namespace {
diff --git a/src/qml/qml/qqmlnotifier_p.h b/src/qml/qml/qqmlnotifier_p.h
index 4d2a349334..d46f5b9ff9 100644
--- a/src/qml/qml/qqmlnotifier_p.h
+++ b/src/qml/qml/qqmlnotifier_p.h
@@ -58,6 +58,7 @@ public:
QQmlVMEMetaObjectEndpoint = 3,
QQmlUnbindableToUnbindableGuard = 4,
QQmlUnbindableToBindableGuard = 5,
+ QQmlDirtyReferenceObject = 6,
};
inline QQmlNotifierEndpoint(Callback callback);
diff --git a/src/qml/qml/qqmltypewrapper_p.h b/src/qml/qml/qqmltypewrapper_p.h
index fa859dd118..73007b05f6 100644
--- a/src/qml/qml/qqmltypewrapper_p.h
+++ b/src/qml/qml/qqmltypewrapper_p.h
@@ -106,6 +106,7 @@ struct Q_QML_EXPORT QQmlTypeWrapper : FunctionObject
{
V4_OBJECT2(QQmlTypeWrapper, FunctionObject)
V4_PROTOTYPE(typeWrapperPrototype)
+ Q_MANAGED_TYPE(QMLTypeWrapper);
V4_NEEDS_DESTROY
bool isSingleton() const;
diff --git a/src/qml/qml/qqmlvaluetypewrapper_p.h b/src/qml/qml/qqmlvaluetypewrapper_p.h
index 703c63bdab..2c075be5b4 100644
--- a/src/qml/qml/qqmlvaluetypewrapper_p.h
+++ b/src/qml/qml/qqmlvaluetypewrapper_p.h
@@ -37,7 +37,7 @@ DECLARE_HEAP_OBJECT(QQmlValueTypeWrapper, ReferenceObject) {
const void *data, QMetaType metaType, const QMetaObject *metaObject,
Object *object, int property, Flags flags)
{
- ReferenceObject::init(object, property, flags);
+ ReferenceObject::init(object, property, flags | IsDirty);
setMetaType(metaType);
setMetaObject(metaObject);
if (data)
@@ -99,6 +99,7 @@ struct Q_QML_EXPORT QQmlValueTypeWrapper : public ReferenceObject
{
V4_OBJECT2(QQmlValueTypeWrapper, ReferenceObject)
V4_PROTOTYPE(valueTypeWrapperPrototype)
+ Q_MANAGED_TYPE(QMLValueTypeWrapper)
V4_NEEDS_DESTROY
public: