| 1 | // Copyright (C) 2022 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #include <private/qv4referenceobject_p.h> |
| 5 | |
| 6 | QT_BEGIN_NAMESPACE |
| 7 | |
| 8 | DEFINE_OBJECT_VTABLE(QV4::ReferenceObject); |
| 9 | |
| 10 | /*! |
| 11 | \class QV4::ReferenceObject |
| 12 | \brief An object that keeps track of the provenance of its owned |
| 13 | value, allowing to reflect mutations on the original instance. |
| 14 | |
| 15 | \internal |
| 16 | |
| 17 | \section1 Copied Types and Mutations |
| 18 | |
| 19 | In QML, certain types are conceptually passed by value. |
| 20 | Instances of those types are always copied when they are accessed |
| 21 | or passed around. |
| 22 | |
| 23 | Let those be "Copied Type"s. |
| 24 | |
| 25 | For example, suppose that \c{foo} is an instance of a Copied Type |
| 26 | that has a member \c{bar} that has some value \c{X}. |
| 27 | |
| 28 | Consider the following example: |
| 29 | |
| 30 | \qml |
| 31 | import QtQuick |
| 32 | |
| 33 | Item { |
| 34 | Component.onCompleted: { |
| 35 | foo.bar = Y |
| 36 | console.log(foo.bar) |
| 37 | } |
| 38 | } |
| 39 | \endqml |
| 40 | |
| 41 | Where \c{Y} is some value that can inhabit \c{foo.bar} and whose |
| 42 | stringified representation is distinguishable from \c{X}. |
| 43 | |
| 44 | One might expect that a stringified representation of \c{Y} should be logged. |
| 45 | Nonetheless, as \c{foo} is a Copied Type, accessing it creates a copy. |
| 46 | The access to the \c{bar} member and its further mutation is |
| 47 | performed on the copy that was created, and thus is not retained by |
| 48 | the object that \c{foo} refers to. |
| 49 | |
| 50 | If \c{copy} is an operation that performs a deep-copy of an object |
| 51 | and returns it, the above snippet can be considered implicitly |
| 52 | equivalent to the following: |
| 53 | |
| 54 | \qml |
| 55 | import QtQuick |
| 56 | |
| 57 | Item { |
| 58 | Component.onCompleted: { |
| 59 | copy(foo).bar = Y |
| 60 | console.log(copy(foo).bar) |
| 61 | } |
| 62 | } |
| 63 | \endqml |
| 64 | |
| 65 | This can generally be surprising as it stands in contrast to the |
| 66 | effect that the above assignment would have if \c{foo} wasn't a |
| 67 | Copied Type. Similarly, it stands in contrast to what one could |
| 68 | expect from the outcome of the same assignment in a Javascript |
| 69 | environment, where the mutation might be expected to generally be |
| 70 | visible in later steps no matter the type of \c{foo}. |
| 71 | |
| 72 | A ReferenceObject can be used to avoid this inconsistency by |
| 73 | wrapping a value and providing a "write-back" mechanism that can |
| 74 | reflect mutations back on the original value. |
| 75 | |
| 76 | Furthermore, a ReferenceObject can be used to load the data from |
| 77 | the original value to ensure that the two values remain in sync, as |
| 78 | the value might have been mutated while the copy is still alive, |
| 79 | conceptually allowing for an "inverted write-back". |
| 80 | |
| 81 | \section1 Setting Up a ReferenceObject |
| 82 | |
| 83 | ReferenceObject is intended to be extended by inheritance. |
| 84 | |
| 85 | An object that is used to wrap a value that is copied around but |
| 86 | has a provenance that requires a write-back, can inherit from |
| 87 | ReferenceObject to plug into the write-back behavior. |
| 88 | |
| 89 | The heap part of the object should subclass |
| 90 | QV4::Heap::ReferenceObject while the object part should subclass |
| 91 | QV4::ReferenceObject. |
| 92 | |
| 93 | When initializing the heap part of the subclass, |
| 94 | QV4::Heap::ReferenceObject::init should be called to set up the |
| 95 | write-back mechanism. |
| 96 | |
| 97 | The write-back mechanism stores a reference to an object and, |
| 98 | potentially, a property index to write-back at. |
| 99 | |
| 100 | Furthermore, a series of flags can be used to condition the |
| 101 | behavior of the write-back. |
| 102 | |
| 103 | For example: |
| 104 | |
| 105 | \code |
| 106 | void QV4::Heap::Foo::init(Heap::Object *object) { |
| 107 | ReferenceObject::init(object, 1, ReferenceObject::Flag::CanWriteBack); |
| 108 | // Some further initialization code |
| 109 | ... |
| 110 | } |
| 111 | \endcode |
| 112 | |
| 113 | The snippet is an example of some sub-class \c{Foo} of |
| 114 | ReferenceObject setting up for a write-back to the property at index |
| 115 | 1 of some object \c{object}. |
| 116 | |
| 117 | Generally, a sub-class of ReferenceObject will be used to wrap one |
| 118 | or more Copied Types and provide a certain behavior. |
| 119 | |
| 120 | In certain situations there is no need to setup a write-back. |
| 121 | For example, we might have certain cases where there is no original |
| 122 | value to be wrapped while still in need of providing an object of |
| 123 | the sub-classing type. |
| 124 | |
| 125 | One example of such a behavior is that of returning an instance from |
| 126 | a C++ method to QML. |
| 127 | |
| 128 | When that is the case, the following initialization can be provided: |
| 129 | |
| 130 | \code |
| 131 | void QV4::Heap::Foo::init(Heap::Object *object) { |
| 132 | ReferenceObject::init(nullptr, -1, NoFlag); |
| 133 | // Some further initialization code |
| 134 | ... |
| 135 | } |
| 136 | \endcode |
| 137 | |
| 138 | \section2 Intitialization and the IsDirty flag |
| 139 | |
| 140 | In certain cases, we try to avoid read-backs when we know that we |
| 141 | have the latest data available already, see \l{Limiting reads on a |
| 142 | QObject property}. |
| 143 | |
| 144 | Certain implementation of ReferenceObject, might want to lazily load |
| 145 | the data on the first read, rather than on the initialization of the |
| 146 | reference. One such example would be `QQmlValueTypeWrapper`. |
| 147 | |
| 148 | When that is the case, the IsDirty flag should be passed at |
| 149 | initialiazation time. |
| 150 | For example: |
| 151 | |
| 152 | \code |
| 153 | void QV4::Heap::Foo::init(Heap::Object *object) { |
| 154 | ReferenceObject::init(object, 1, ReferenceObject::Flag::CanWriteBack | ReferenceObject::Flag::IsDirty); |
| 155 | // Some further initialization code |
| 156 | ... |
| 157 | } |
| 158 | \endcode |
| 159 | |
| 160 | If the flag is not passed there the first read might be elided, |
| 161 | leaving the object in an incorrect state. |
| 162 | |
| 163 | \section1 Providing the Required Infrastructure for a Default Write-back |
| 164 | |
| 165 | Generally, to use the base implementation of write and read backs, |
| 166 | as provided by QV4::ReferenceObject::readReference and |
| 167 | QV4::ReferenceObject::writeBack, a sub-class should provide the |
| 168 | following interface: |
| 169 | |
| 170 | \code |
| 171 | void *storagePointer(); |
| 172 | const void *storagePointer(); |
| 173 | |
| 174 | |
| 175 | QVariant toVariant() const; |
| 176 | bool setVariant(const QVariant &variant); |
| 177 | \endcode |
| 178 | |
| 179 | The two overloads of \c{storagePointer} should provide access to |
| 180 | the internal backing storage of the ReferenceObject. |
| 181 | |
| 182 | Generally, a ReferenceObject will be wrapping some instance of a C++ |
| 183 | type, owning a copy of the original instance used as a storage for |
| 184 | writes and reads. |
| 185 | An implementation of \c{storagePointer} should give access to this |
| 186 | backing storage, which is used to know what value to write-back and |
| 187 | where to write when reading back from the original value. |
| 188 | |
| 189 | For example, a Sequence type that wraps a QVariantList will own its |
| 190 | own instance of a QVariantList. The implementation for |
| 191 | \c{storagePointer} should give access to that owned instance. |
| 192 | |
| 193 | \c{toVariant} should provide a QVariant wrapped representation of |
| 194 | the internal storage that the ReferenceObject uses. |
| 195 | This is used during the write-back of a ReferenceObject whose |
| 196 | original value was a QVariant backed instance. |
| 197 | |
| 198 | Do remember that instances of a ReferenceObject that are backing |
| 199 | QVariant values should further pass the |
| 200 | QV4::Heap::ReferenceObject::Flag::IsVariant flag at initialization |
| 201 | time. |
| 202 | |
| 203 | On the opposite side, \c{setVariant} should switch the value that |
| 204 | the ReferenceObject stores with the provided variant. |
| 205 | This is used when a QVariant backed ReferenceObject performs a read |
| 206 | of its original value, to allow for synchronization. |
| 207 | |
| 208 | It is still possible to use ReferenceObject without necessarily |
| 209 | providing the above interface. |
| 210 | QV4::DateObject is an example of a ReferenceObject sub-class that |
| 211 | provides its own writeBack implementation and doesn't abide by the |
| 212 | above. |
| 213 | |
| 214 | \section1 Performing a Write-back |
| 215 | |
| 216 | With a sub-class of ReferenceObject that was set-up as above, a |
| 217 | write-back can be performed by calling |
| 218 | QV4::ReferenceObject::writeBack. |
| 219 | |
| 220 | For example, some ReferenceObject subclass \c{Foo} might be |
| 221 | backing, say, some map-like object. |
| 222 | |
| 223 | Internally, insertion of an element might pass by some method, say |
| 224 | \c{insertElement}. An implementation might, for example, look like |
| 225 | the following: |
| 226 | |
| 227 | \code |
| 228 | bool Foo::insertElement(const QString& key, const Value& value) { |
| 229 | // Insert the element if possible |
| 230 | ... |
| 231 | QV4::ReferenceObject::writeBack(d()); |
| 232 | ... |
| 233 | // Some further handling |
| 234 | } |
| 235 | \endcode |
| 236 | |
| 237 | The call to writeBack will ensure that the newly inserted element |
| 238 | will be reflected on the original value where the object originates |
| 239 | from. |
| 240 | |
| 241 | Here \c{d()}, with \c{Foo} being a sub-class of ReferenceObject and |
| 242 | thus of Object, is the heap part of \c{Foo} that should provide the |
| 243 | interface specificed above and owns the actual storage of the stored |
| 244 | value. |
| 245 | |
| 246 | \section1 Synchronizing with the Original Value |
| 247 | |
| 248 | QV4::ReferenceObject::readReference provides a way to obtain the |
| 249 | current state of the value that the ReferenceObject refers to. |
| 250 | |
| 251 | When this read is performed, the obtained value will be stored back |
| 252 | into the backing storage for the ReferenceObject. |
| 253 | |
| 254 | This allows the ReferenceObject to lazily load the latest data on |
| 255 | demand and correctly reflect the original value. |
| 256 | |
| 257 | For example, say that \c{Foo} is a sub-class of ReferenceObject that |
| 258 | is used to back array-like values. |
| 259 | |
| 260 | \c{Foo} should provide the usual \c{virtualGetLength} method to |
| 261 | support the \c{length} property that we expect an array to have. |
| 262 | |
| 263 | In a possible implementation of \c{virtualGetLength}, \c{Foo} should |
| 264 | ensure that readReference is called before providing a value for the |
| 265 | length, to ensure that the latest data is available. |
| 266 | |
| 267 | For example: |
| 268 | |
| 269 | \code |
| 270 | qint64 Foo::virtualGetLength(const Managed *m) |
| 271 | { |
| 272 | const Foo *foo = static_cast<const Foo *>(m); |
| 273 | foo->readReference(d()); |
| 274 | // Return the length from the owned storage |
| 275 | } |
| 276 | \endcode |
| 277 | |
| 278 | \section2 Limiting reads on a QObject property |
| 279 | |
| 280 | In most cases we cannot know whether the original data was modified between |
| 281 | read accesses. This generally forces a read to be performed each time we |
| 282 | require the latest data, even if we might have it already. |
| 283 | |
| 284 | This can have surprising results, as certain procedure might require reading |
| 285 | the data multiple times to be performed, which sometimes can be very |
| 286 | expensive. |
| 287 | |
| 288 | When the original data comes from the property of a \c{QObject}, and the |
| 289 | property has a \tt{NOTIFY} signal or is \tt{BINDABLE}, we can subscribe to the |
| 290 | signal to know when the data is actually modified outside our control, so that |
| 291 | we need to fetch it again. |
| 292 | |
| 293 | A ReferenceObject can take advantage of this to reduce the number of reads |
| 294 | that are required when dealing with a \c{QObject}'s property provening data. |
| 295 | |
| 296 | When a property has a \tt{NOTIFY} signal and is a \tt{BINDABLE} at |
| 297 | the same time, we only need to use one such connection. |
| 298 | Currently, the \tt{BINDABLE} subscription will take predecedence. |
| 299 | |
| 300 | ReferenceObjects that are part of a \l{Reference object chains}{chain}, will |
| 301 | traverse the chain up until a QOjbect holding root is found, and connect based |
| 302 | on that object. |
| 303 | As read/write backs in a chain are always propagated up the chain, this allow |
| 304 | ReferenceObjects that are not directly parented to relevant element to still |
| 305 | avoid unnecesary reads. |
| 306 | |
| 307 | For example, the property of a value type exposed by a Q_GADGET, cannot have a |
| 308 | \tt{NOTIFY} signal. |
| 309 | Nonetheless, if a change were to occur to the parent value type or the |
| 310 | property itself, that change would be propagated up the chain, possibly |
| 311 | triggering a \tt{NOTIFY} signal that is part of the chain. |
| 312 | Thus, by connecting to that upper \tt{NOTIFY} signal, we can still reliably know |
| 313 | if a change was performed on the property itself and thus avoid reduce the |
| 314 | number of reads. |
| 315 | |
| 316 | As changes in the chain that do not really invalidate the data of that |
| 317 | property will still trigger that same \tt{NOTIFY} signal, sometimes we will |
| 318 | perform a read that is unnecessary due to granularity at which we are working. |
| 319 | This is the case, returning to the example above, when a different |
| 320 | property of that same value type will be changed. |
| 321 | |
| 322 | This should still be a win, as we still expect to cut off multiple reads that |
| 323 | would be performed without the optimization. |
| 324 | |
| 325 | The default implementation for QV4::ReferenceObject::readReference will take |
| 326 | care of performing this optimization already. |
| 327 | |
| 328 | Derived objects that provide their own readReference implementation can plug |
| 329 | into QV4::Heap::ReferenceObject::isDirty, QV4::Heap::ReferenceObject::setDirty |
| 330 | and QV4::Heap::ReferenceObject::isConnected to provide the same optimization. |
| 331 | |
| 332 | A ReferenceObject uses a "dirty" flag to track whether the data should be read |
| 333 | again. |
| 334 | If the ReferenceObject refers to a \c{QObject}'s property that has a |
| 335 | \tt{NOTIFY} signal or is \tt{BINDABLE}, it will set the flag each time the |
| 336 | \tt{NOTIFY} signal is emitted or the \tt{BINDABLE} is changed. |
| 337 | |
| 338 | isDirty returns whether the flag is set and, thus, a readReference |
| 339 | implementation should avoid performing the read itself when the method |
| 340 | returns true. |
| 341 | |
| 342 | After a read is performed, the "dirty" flag should be set again if the read |
| 343 | was unsuccessful. |
| 344 | The flag can be modified by usages of `setDirty`. |
| 345 | |
| 346 | Generally, this only applies to instances of ReferenceObject that provene from |
| 347 | a \c{QObject}'s property that has a notify signal, as that is the case that |
| 348 | allows us to know when a read is required. |
| 349 | |
| 350 | In all other cases, a ReferenceObject should always be "dirty" and perform a |
| 351 | read, as it cannot know if the data was modified since its last read. |
| 352 | This case will initially be managed by the base constructor for |
| 353 | ReferenceObject, nonetheless derived objects with a custom readReference |
| 354 | implementation need to take it into accoutn when setting the "dirty" flag |
| 355 | after a read. |
| 356 | |
| 357 | isConnected can be used to discern between the two cases, as it will only |
| 358 | return true when the ReferenceObject is connected to a NOTIFY signal that can |
| 359 | modify the "dirty" flag. |
| 360 | When isConnected is false, a read implementation should always keep the |
| 361 | ReferenceObject in a permanent "dirty" state, to ensure that the correct data |
| 362 | is fetched when required. |
| 363 | |
| 364 | \section1 Limiting Write-backs Based on Source Location |
| 365 | |
| 366 | \note We generally consider location-aware write-backs to be a |
| 367 | mistake and expect to generally avoid further uses of them. |
| 368 | Due to backward compatibility promises they cannot be universally |
| 369 | enforced, possibly creating discrepancies in certain behaviors. |
| 370 | If at some point possible, the feature might be backtracked on and |
| 371 | removed, albeit this has shown to be difficult due to certain |
| 372 | existing cross-dependencies. |
| 373 | |
| 374 | Consider the following code: |
| 375 | |
| 376 | \qml |
| 377 | import QtQuick |
| 378 | |
| 379 | Text { |
| 380 | Component.onCompleted: { |
| 381 | var f = font // font is a value type and f is a value type reference |
| 382 | f.bold = true; |
| 383 | otherText.font = f |
| 384 | } |
| 385 | } |
| 386 | \endqml |
| 387 | |
| 388 | Remembering that \c{font} is a Copied Type in QtQuick, \c{f} will be a |
| 389 | Copied Type reference, internally backed by a ReferenceObject. |
| 390 | Changing the \c{bold} property of \c{f} would internally perform a |
| 391 | write-back which will reflect on the original \c{font} property of |
| 392 | the \c{Text} component, as we would expect from the definition we |
| 393 | gave above. |
| 394 | |
| 395 | Nonetheless, we could generally expect that the intention behind the |
| 396 | code wasn't to update the original \c{font} property, but to pass a |
| 397 | slightly modified version of its value to \c{otherText}. |
| 398 | |
| 399 | To avoid introducing this kind of possibly unintended behavior while |
| 400 | still supporting a solution to the original problem, ReferenceObject |
| 401 | allows limiting the availability of write-backs to a specific source |
| 402 | location. |
| 403 | |
| 404 | For example, by limiting the availability of write-backs to the |
| 405 | single statement where a Copied Type reference is created, we can |
| 406 | address the most common cases of the original issue while avoiding |
| 407 | the unintuitive behavior of the above. |
| 408 | |
| 409 | To enable source location enforcement, |
| 410 | QV4::Heap::ReferenceObject::Flag::EnforcesLocation should be set |
| 411 | when the ReferenceObject is initialized. |
| 412 | |
| 413 | A reference location should be set by calling |
| 414 | QV4::Heap::ReferenceObject::setLocation. |
| 415 | For example, during initialization, one might be set to limit |
| 416 | write-backs to the currently processed statement, where the |
| 417 | ReferenceObject was created, as follows: |
| 418 | |
| 419 | \code |
| 420 | void Heap::Sequence::Foo::init(..., Heap::ReferenceObject::Flags flags) { |
| 421 | ... |
| 422 | ReferenceObject::init(..., flags & ReferenceObject::Flag::EnforcesLocation); |
| 423 | ... |
| 424 | if (CppStackFrame *frame = internalClass->engine->currentStackFrame) |
| 425 | setLocation(frame->v4Function, frame->statementNumber()); |
| 426 | ... |
| 427 | } |
| 428 | \endcode |
| 429 | |
| 430 | Do note that calls to QV4::ReferenceObject::writeBack and |
| 431 | QV4::ReferenceObject::readReference do not directly take into |
| 432 | account location enforcement. |
| 433 | |
| 434 | This should generally be handled by the sub-class. |
| 435 | QV4::Heap::ReferenceObject::isAttachedToProperty can be used to |
| 436 | recognize whether the reference is still suitable for write-backs in |
| 437 | a location-enforcement-aware way. |
| 438 | |
| 439 | \section1 Reference object chains |
| 440 | |
| 441 | ReferenceObject can be nested. |
| 442 | |
| 443 | For example, consider: |
| 444 | |
| 445 | \code |
| 446 | a.b.c |
| 447 | \endcode |
| 448 | |
| 449 | 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}. |
| 450 | |
| 451 | Based on what each of \c{a}, \c{b} and \c{c} is, multiple ReferenceObject |
| 452 | instances, parented to one another, might be introduced. |
| 453 | |
| 454 | For example, if \c{a} is a Q_OBJECT, \c{b} is a value type and \c{c} is a type |
| 455 | that will be converted to a QV4::Sequence, \c{a} will be wrapped by a |
| 456 | QObjectWrapper, \c{b} will be wrapped by a QQmlValueTypeWrapper which is parented |
| 457 | to the QObjectWrapper wrapping \c{a} and \c{c} will be a Sequence that is |
| 458 | parented to the QQmlValueTypeWrapper wrapping \c{b}. |
| 459 | |
| 460 | This parenting chain is used to enable recursive read/write backs, ensuring |
| 461 | that a read/write back travels up the chain as required so that the latest |
| 462 | data is available on every relevant element. |
| 463 | |
| 464 | At certain points in the chain, it is possible that a non-reference object is |
| 465 | introduced. |
| 466 | |
| 467 | For example, this is always the case when a Q_OBJECT is retrieved, as it will |
| 468 | be wrapped in a QObjectWrapper which is not a reference object. |
| 469 | |
| 470 | This breaks the chain of parenting and introduces the start of a new chain. |
| 471 | As a QObjectWrapper directly stores a pointer to the original object, it |
| 472 | doesn't need to perform the same read/write backs that reference objects do. |
| 473 | Similarly, child reference objects only need to read up to the innermost |
| 474 | QObjectWrapper in a chain to obtain the latest data. |
| 475 | |
| 476 | Returning to the example above, if \c{b} is a Q_OBJECT instead of a value |
| 477 | type, then it will be the root of the reference chain that has \c{c} has its |
| 478 | child, without the need to be related to the QObjectWrapper that has been |
| 479 | built by accessing \c{a}. |
| 480 | |
| 481 | QQmlTypeWrapper, that can wrap a QObject pointer that represents a |
| 482 | singleton or an attached property, behaves as chain root in the |
| 483 | exact same way that QObjectWrapper does. |
| 484 | */ |
| 485 | |
| 486 | void QQmlDirtyReferenceObject_callback(QQmlNotifierEndpoint *e, void **) { |
| 487 | static_cast<QV4::Heap::ReferenceObjectEndpoint*>(e)->reference->setDirty(true); |
| 488 | } |
| 489 | |
| 490 | QT_END_NAMESPACE |
| 491 | |