// Copyright (C) 2017 Ford Motor Company // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QREMOTEOBJECTS_ABSTRACT_ITEM_REPLICA_P_H #define QREMOTEOBJECTS_ABSTRACT_ITEM_REPLICA_P_H // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include "qremoteobjectabstractitemmodeltypes_p.h" #include "qremoteobjectabstractitemmodelreplica.h" #include "qremoteobjectreplica.h" #include "qremoteobjectpendingcall.h" #include #include #include QT_BEGIN_NAMESPACE namespace { const int DefaultNodesCacheSize = 1000; } struct CacheEntry { QHash data; Qt::ItemFlags flags; explicit CacheEntry() : flags(Qt::NoItemFlags) {} }; using CachedRowEntry = QList; template struct LRUCache { typedef std::pair Pair; std::list cachedItems; typedef typename std::list::iterator CacheIterator; std::unordered_map cachedItemsMap; size_t cacheSize; explicit LRUCache() { bool ok; cacheSize = qEnvironmentVariableIntValue("QTRO_NODES_CACHE_SIZE" , &ok); if (!ok) cacheSize = DefaultNodesCacheSize; } ~LRUCache() { clear(); } inline void cleanCache() { Q_ASSERT(cachedItems.size() == cachedItemsMap.size()); auto it = cachedItems.rbegin(); while (cachedItemsMap.size() > cacheSize) { // Do not trash elements with children // Workaround QTreeView bugs which caches the children indexes for very long time while (it->second->hasChildren && it != cachedItems.rend()) ++it; if (it == cachedItems.rend()) break; cachedItemsMap.erase(it->first); delete it->second; cachedItems.erase((++it).base()); } Q_ASSERT(cachedItems.size() == cachedItemsMap.size()); } void setCacheSize(size_t rootCacheSize) { cacheSize = rootCacheSize; cleanCache(); cachedItemsMap.reserve(rootCacheSize); } void changeKeys(Key key, Key delta) { std::vector> changed; auto it = cachedItemsMap.begin(); while (it != cachedItemsMap.end()) { if (it->first >= key) { changed.emplace_back(it->first + delta, it->second); it->second->first += delta; it = cachedItemsMap.erase(it); } else { ++it; } } for (auto pair : changed) cachedItemsMap[pair.first] = pair.second; } void insert(Key key, Value *value) { changeKeys(key, 1); ensure(key, value); Q_ASSERT(cachedItems.size() == cachedItemsMap.size()); } void ensure(Key key, Value *value) { cachedItems.emplace_front(key, value); cachedItemsMap[key] = cachedItems.begin(); cleanCache(); } void remove(Key key) { auto it = cachedItemsMap.find(key); if (it != cachedItemsMap.end()) { delete it->second->second; cachedItems.erase(it->second); cachedItemsMap.erase(it); } changeKeys(key, -1); Q_ASSERT(cachedItems.size() == cachedItemsMap.size()); } Value *get(Key key) { auto it = cachedItemsMap.find(key); if (it == cachedItemsMap.end()) return nullptr; // Move the accessed item to front cachedItems.splice(cachedItems.begin(), cachedItems, it->second); Q_ASSERT(it->second->first == key); return it->second->second; } Key find(Value *val) { for (auto it = cachedItemsMap.begin(); it != cachedItemsMap.end(); ++it) { if (it->second->second == val) return it->first; } Q_ASSERT_X(false, __FUNCTION__, "Value not found"); return Key{}; } bool exists(Value *val) { for (const auto &pair : cachedItems) if (pair.second == val) return true; return false; } bool exists(Key key) { return cachedItemsMap.find(key) != cachedItemsMap.end(); } size_t size() { return cachedItemsMap.size(); } void clear() { for (const auto &pair : cachedItems) delete pair.second; cachedItems.clear(); cachedItemsMap.clear(); } }; class QAbstractItemModelReplicaImplementation; struct CacheData { QAbstractItemModelReplicaImplementation *replicaModel; CacheData *parent; CachedRowEntry cachedRowEntry; bool hasChildren; LRUCache children; int columnCount; int rowCount; explicit CacheData(QAbstractItemModelReplicaImplementation *model, CacheData *parentItem = nullptr); ~CacheData(); void ensureChildren(int start, int end) { for (int i = start; i <= end; ++i) if (!children.exists(i)) children.ensure(i, new CacheData(replicaModel, this)); } void insertChildren(int start, int end) { Q_ASSERT_X(start >= 0 && start <= end, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 <= %2")).arg(start).arg(end))); for (int i = start; i <= end; ++i) { auto cacheData = new CacheData(replicaModel, this); cacheData->columnCount = columnCount; children.insert(i, cacheData); ++rowCount; } if (rowCount) hasChildren = true; } void removeChildren(int start, int end) { Q_ASSERT_X(start >= 0 && start <= end && end < rowCount, __FUNCTION__, qPrintable(QString(QLatin1String("0 <= %1 <= %2 < %3")).arg(start).arg(end).arg(rowCount))); for (int i = end; i >= start; --i) { children.remove(i); --rowCount; } hasChildren = rowCount; } void clear() { cachedRowEntry.clear(); children.clear(); hasChildren = false; columnCount = 0; rowCount = 0; } }; struct RequestedData { QtPrivate::IndexList start; QtPrivate::IndexList end; QList roles; }; struct RequestedHeaderData { int role; int section; Qt::Orientation orientation; }; class SizeWatcher : public QRemoteObjectPendingCallWatcher { Q_OBJECT public: SizeWatcher(QtPrivate::IndexList _parentList, const QRemoteObjectPendingReply &reply) : QRemoteObjectPendingCallWatcher(reply), parentList(_parentList) {} QtPrivate::IndexList parentList; }; class RowWatcher : public QRemoteObjectPendingCallWatcher { Q_OBJECT public: RowWatcher(QtPrivate::IndexList _start, QtPrivate::IndexList _end, QList _roles, const QRemoteObjectPendingReply &reply) : QRemoteObjectPendingCallWatcher(reply), start(_start), end(_end), roles(_roles) {} QtPrivate::IndexList start, end; QList roles; }; class HeaderWatcher : public QRemoteObjectPendingCallWatcher { Q_OBJECT public: HeaderWatcher(QList _orientations, QList _sections, QList _roles, const QRemoteObjectPendingReply &reply) : QRemoteObjectPendingCallWatcher(reply), orientations(_orientations), sections(_sections), roles(_roles) {} QList orientations; QList sections, roles; }; class QAbstractItemModelReplicaImplementation : public QRemoteObjectReplica { Q_OBJECT //TODO Use an input name for the model on the Replica side Q_CLASSINFO(QCLASSINFO_REMOTEOBJECT_TYPE, "ServerModelAdapter") Q_PROPERTY(QList availableRoles READ availableRoles NOTIFY availableRolesChanged) Q_PROPERTY(QIntHash roleNames READ roleNames) public: QAbstractItemModelReplicaImplementation(); QAbstractItemModelReplicaImplementation(QRemoteObjectNode *node, const QString &name); ~QAbstractItemModelReplicaImplementation() override; void initialize() override; static void registerMetatypes(); inline const QList &availableRoles() const { if (m_availableRoles.isEmpty()) m_availableRoles = propAsVariant(0).value>(); return m_availableRoles; } QHash roleNames() const { QIntHash roles = propAsVariant(1).value(); return roles; } void setModel(QAbstractItemModelReplica *model); bool clearCache(const QtPrivate::IndexList &start, const QtPrivate::IndexList &end, const QList &roles); Q_SIGNALS: void availableRolesChanged(); void dataChanged(QtPrivate::IndexList topLeft, QtPrivate::IndexList bottomRight, QList roles); void rowsInserted(QtPrivate::IndexList parent, int first, int last); void rowsRemoved(QtPrivate::IndexList parent, int first, int last); void rowsMoved(QtPrivate::IndexList parent, int start, int end, QtPrivate::IndexList destination, int row); void currentChanged(QtPrivate::IndexList current, QtPrivate::IndexList previous); void modelReset(); void headerDataChanged(Qt::Orientation,int,int); void columnsInserted(QtPrivate::IndexList parent, int first, int last); void layoutChanged(QtPrivate::IndexList parents, QAbstractItemModel::LayoutChangeHint hint); public Q_SLOTS: QRemoteObjectPendingReply replicaSizeRequest(QtPrivate::IndexList parentList) { static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaSizeRequest(QtPrivate::IndexList)"); QVariantList __repc_args; __repc_args << QVariant::fromValue(parentList); return QRemoteObjectPendingReply(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args)); } QRemoteObjectPendingReply replicaRowRequest(QtPrivate::IndexList start, QtPrivate::IndexList end, QList roles) { static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaRowRequest(QtPrivate::IndexList,QtPrivate::IndexList,QList)"); QVariantList __repc_args; __repc_args << QVariant::fromValue(start) << QVariant::fromValue(end) << QVariant::fromValue(roles); return QRemoteObjectPendingReply(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args)); } QRemoteObjectPendingReply replicaHeaderRequest(QList orientations, QList sections, QList roles) { static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaHeaderRequest(QList,QList,QList)"); QVariantList __repc_args; __repc_args << QVariant::fromValue(orientations) << QVariant::fromValue(sections) << QVariant::fromValue(roles); return QRemoteObjectPendingReply(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args)); } void replicaSetCurrentIndex(QtPrivate::IndexList index, QItemSelectionModel::SelectionFlags command) { static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaSetCurrentIndex(QtPrivate::IndexList,QItemSelectionModel::SelectionFlags)"); QVariantList __repc_args; __repc_args << QVariant::fromValue(index) << QVariant::fromValue(command); send(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args); } void replicaSetData(QtPrivate::IndexList index, const QVariant &value, int role) { static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaSetData(QtPrivate::IndexList,QVariant,int)"); QVariantList __repc_args; __repc_args << QVariant::fromValue(index) << QVariant::fromValue(value) << QVariant::fromValue(role); send(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args); } QRemoteObjectPendingReply replicaCacheRequest(size_t size, QList roles) { static int __repc_index = QAbstractItemModelReplicaImplementation::staticMetaObject.indexOfSlot("replicaCacheRequest(size_t,QList)"); QVariantList __repc_args; __repc_args << QVariant::fromValue(size) << QVariant::fromValue(roles); return QRemoteObjectPendingReply(sendWithReply(QMetaObject::InvokeMetaMethod, __repc_index, __repc_args)); } void onHeaderDataChanged(Qt::Orientation orientation, int first, int last); void onDataChanged(const QtPrivate::IndexList &start, const QtPrivate::IndexList &end, const QList &roles); void onRowsInserted(const QtPrivate::IndexList &parent, int start, int end); void onRowsRemoved(const QtPrivate::IndexList &parent, int start, int end); void onColumnsInserted(const QtPrivate::IndexList &parent, int start, int end); void onRowsMoved(QtPrivate::IndexList srcParent, int srcRow, int count, QtPrivate::IndexList destParent, int destRow); void onCurrentChanged(QtPrivate::IndexList current, QtPrivate::IndexList previous); void onModelReset(); void requestedData(QRemoteObjectPendingCallWatcher *); void requestedHeaderData(QRemoteObjectPendingCallWatcher *); void init(); void fetchPendingData(); void fetchPendingHeaderData(); void handleInitDone(QRemoteObjectPendingCallWatcher *watcher); void handleModelResetDone(QRemoteObjectPendingCallWatcher *watcher); void handleSizeDone(QRemoteObjectPendingCallWatcher *watcher); void onReplicaCurrentChanged(const QModelIndex ¤t, const QModelIndex &previous); void fillCache(const QtPrivate::IndexValuePair &pair,const QList &roles); void onLayoutChanged(const QtPrivate::IndexList &parents, QAbstractItemModel::LayoutChangeHint hint); public: QScopedPointer m_selectionModel; QList m_headerData[2]; CacheData m_rootItem; inline CacheData* cacheData(const QModelIndex &index) const { if (!index.isValid()) return const_cast(&m_rootItem); if (index.internalPointer()) { auto parent = static_cast(index.internalPointer()); if (m_activeParents.find(parent) != m_activeParents.end()) return parent->children.get(index.row()); } return nullptr; } inline CacheData* cacheData(const QtPrivate::IndexList &index) const { return cacheData(toQModelIndex(index, q)); } inline CacheData* createCacheData(const QtPrivate::IndexList &index) const { bool ok = false; auto modelIndex = toQModelIndex(index, q, &ok); if (!ok) return nullptr; cacheData(modelIndex.parent())->ensureChildren(modelIndex.row() , modelIndex.row()); return cacheData(modelIndex); } inline CacheEntry* cacheEntry(const QModelIndex &index) const { auto data = cacheData(index); if (!data || index.column() < 0 || index.column() >= data->cachedRowEntry.size()) return nullptr; CacheEntry &entry = data->cachedRowEntry[index.column()]; return &entry; } inline CacheEntry* cacheEntry(const QtPrivate::IndexList &index) const { return cacheEntry(toQModelIndex(index, q)); } QRemoteObjectPendingCallWatcher *doModelReset(); void initializeModelConnections(); bool m_initDone = false; QList m_requestedData; QList m_requestedHeaderData; QList m_pendingRequests; QAbstractItemModelReplica *q; mutable QList m_availableRoles; std::unordered_set m_activeParents; QtRemoteObjects::InitialAction m_initialAction; QList m_initialFetchRolesHint; }; QT_END_NAMESPACE #endif // QREMOTEOBJECTS_ABSTRACT_ITEM_REPLICA_P_H