Keep a {id: item} cache for ModelStore models

Accelerates the ModelStore ListView find() function,
which now just has to get an object key instead of looping through the
whole model.
This commit is contained in:
miruka 2020-09-02 13:38:11 -04:00
parent b1398e5dbf
commit f5cb3ecaa0
5 changed files with 40 additions and 41 deletions

View File

@ -44,7 +44,8 @@ class Model(MutableMapping):
self.take_items_ownership: bool = True self.take_items_ownership: bool = True
self._active_batch_remove_indice: Optional[List[int]] = None # [(index, item.id), ...]
self._active_batch_removed: Optional[List[Tuple[int, Any]]] = None
if self.sync_id: if self.sync_id:
self.instances[self.sync_id] = self self.instances[self.sync_id] = self
@ -148,10 +149,10 @@ class Model(MutableMapping):
proxy.source_item_deleted(self, key) proxy.source_item_deleted(self, key)
if self.sync_id: if self.sync_id:
if self._active_batch_remove_indice is None: if self._active_batch_removed is None:
ModelItemDeleted(self.sync_id, index) ModelItemDeleted(self.sync_id, index, 1, (item.id,))
else: else:
self._active_batch_remove_indice.append(index) self._active_batch_removed.append((index, item.id))
def __iter__(self) -> Iterator: def __iter__(self) -> Iterator:
@ -189,17 +190,21 @@ class Model(MutableMapping):
with self.write_lock: with self.write_lock:
try: try:
self._active_batch_remove_indice = [] self._active_batch_removed = []
yield None yield None
finally: finally:
indice = self._active_batch_remove_indice batch = self._active_batch_removed
groups = [ groups = [
list(group) for item, group in itertools.groupby(indice) list(group) for item, group in
itertools.groupby(batch, key=lambda x: x[0])
] ]
for grp in groups: for group in groups:
ModelItemDeleted( ModelItemDeleted(
self.sync_id, index=grp[0], count=len(grp), self.sync_id,
index = group[0][0],
count = len(group),
ids = [item[1] for item in group],
) )
self._active_batch_remove_indice = None self._active_batch_removed = None

View File

@ -1,7 +1,7 @@
# SPDX-License-Identifier: LGPL-3.0-or-later # SPDX-License-Identifier: LGPL-3.0-or-later
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Dict, Optional from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence
import pyotherside import pyotherside
@ -85,6 +85,7 @@ class ModelItemDeleted(ModelEvent):
index: int = field() index: int = field()
count: int = 1 count: int = 1
ids: Sequence[Any] = ()
@dataclass @dataclass

View File

@ -13,6 +13,7 @@ QtObject {
readonly property Component model: Component { readonly property Component model: Component {
ListModel { ListModel {
property var modelId property var modelId
property var idToItems: ({})
// Used by HFilterModel // Used by HFilterModel
signal fieldsChanged(int index, var changes) signal fieldsChanged(int index, var changes)
@ -25,10 +26,7 @@ QtObject {
} }
function find(id, default_=null) { function find(id, default_=null) {
for (let i = 0; i < count; i++) return idToItems[id] || default_
if (get(i).id === id) return get(i)
return default_
} }
} }
} }

View File

@ -12,8 +12,8 @@ Item {
property string userId property string userId
property string roomId property string roomId
property QtObject userInfo: null property QtObject userInfo: ModelStore.get("accounts").find(userId)
property QtObject roomInfo: null property QtObject roomInfo: ModelStore.get(userId, "rooms").find(roomId)
property bool ready: Boolean(userInfo && roomInfo) property bool ready: Boolean(userInfo && roomInfo)
property bool longLoading: false property bool longLoading: false
@ -58,22 +58,6 @@ Item {
) )
} }
Timer {
interval: 100
running: ! userInfo
repeat: true
triggeredOnStart: true
onTriggered: userInfo = ModelStore.get("accounts").find(userId)
}
Timer {
interval: 100
running: ! roomInfo
repeat: true
triggeredOnStart: true
onTriggered: roomInfo = ModelStore.get(userId, "rooms").find(roomId)
}
Timer { Timer {
interval: 300 interval: 300
running: ! ready running: ! ready

View File

@ -52,15 +52,18 @@ QtObject {
} }
function onModelItemSet(syncId, indexThen, indexNow, changedFields) { function onModelItemSet(syncId, indexThen, indexNow, changedFields) {
const model = ModelStore.get(syncId)
if (indexThen === undefined) { if (indexThen === undefined) {
// print("insert", syncId, indexThen, indexNow, // print("insert", syncId, indexThen, indexNow,
// JSON.stringify(changedFields)) // JSON.stringify(changedFields))
ModelStore.get(syncId).insert(indexNow, changedFields) model.insert(indexNow, changedFields)
model.idToItems[changedFields.id] = model.get(indexNow)
model.idToItemsChanged()
} else { } else {
// print("set", syncId, indexThen, indexNow, // print("set", syncId, indexThen, indexNow,
// JSON.stringify(changedFields)) // JSON.stringify(changedFields))
const model = ModelStore.get(syncId)
model.set(indexThen, changedFields) model.set(indexThen, changedFields)
if (indexThen !== indexNow) model.move(indexThen, indexNow, 1) if (indexThen !== indexNow) model.move(indexThen, indexNow, 1)
@ -69,14 +72,22 @@ QtObject {
} }
} }
function onModelItemDeleted(syncId, index, count=1) { function onModelItemDeleted(syncId, index, count=1, ids=[]) {
// print("delete", syncId, index, count) // print("delete", syncId, index, count, ids)
ModelStore.get(syncId).remove(index, count) const model = ModelStore.get(syncId)
model.remove(index, count)
for (let i = 0; i < ids.length; i++) {
delete model.idToItems[ids[i]]
}
if (ids.length) model.idToItemsChanged()
} }
function onModelCleared(syncId) { function onModelCleared(syncId) {
// print("clear", syncId) // print("clear", syncId)
ModelStore.get(syncId).clear() ModelStore.get(syncId).clear()
model.idToItems = {}
} }
function onDevicesUpdated(forAccount) { function onDevicesUpdated(forAccount) {