Merge pull request #9968 from nabijaczleweli/shid
Pre-compute sort keys, don't launder getting them through get_data_for_role()
This commit is contained in:
@@ -62,12 +62,18 @@ class CustomModel(QtCore.QAbstractItemModel):
|
|||||||
parent.addChild(self, node)
|
parent.addChild(self, node)
|
||||||
|
|
||||||
def index(self, row, column, _parent=None):
|
def index(self, row, column, _parent=None):
|
||||||
|
# Performance-critical function
|
||||||
|
|
||||||
if not _parent or not _parent.isValid():
|
if not _parent or not _parent.isValid():
|
||||||
parent = self._root
|
parent = self._root
|
||||||
else:
|
else:
|
||||||
parent = _parent.internalPointer()
|
parent = _parent.internalPointer()
|
||||||
|
|
||||||
if not QtCore.QAbstractItemModel.hasIndex(self, row, column, _parent):
|
# Open-coded
|
||||||
|
# if not QtCore.QAbstractItemModel.hasIndex(self, row, column, _parent):
|
||||||
|
# the implementation is equivalent but it's in C++,
|
||||||
|
# so VM entries take up inordinate amounts of time (up to 25% of refresh()):
|
||||||
|
if row < 0 or column < 0 or row >= self.rowCount(_parent) or column >= self._columncount:
|
||||||
return QtCore.QModelIndex()
|
return QtCore.QModelIndex()
|
||||||
|
|
||||||
child = parent.child(row)
|
child = parent.child(row)
|
||||||
|
|||||||
@@ -75,36 +75,70 @@ TX_ICONS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
ROLE_SORT_ORDER = Qt.ItemDataRole.UserRole + 1000
|
|
||||||
|
|
||||||
|
|
||||||
class HistorySortModel(QSortFilterProxyModel):
|
class HistorySortModel(QSortFilterProxyModel):
|
||||||
|
|
||||||
|
def data_for(self, index: QModelIndex):
|
||||||
|
col = index.column()
|
||||||
|
if col == HistoryColumns.STATUS:
|
||||||
|
# respect sort order of self.transactions (wallet.get_full_history)
|
||||||
|
return -index.row()
|
||||||
|
else:
|
||||||
|
node = index.internalPointer()
|
||||||
|
return node.sort_keys[col]
|
||||||
|
|
||||||
def lessThan(self, source_left: QModelIndex, source_right: QModelIndex):
|
def lessThan(self, source_left: QModelIndex, source_right: QModelIndex):
|
||||||
item1 = self.sourceModel().data(source_left, ROLE_SORT_ORDER)
|
return self.data_for(source_left) < self.data_for(source_right)
|
||||||
item2 = self.sourceModel().data(source_right, ROLE_SORT_ORDER)
|
|
||||||
if item1 is None or item2 is None:
|
|
||||||
raise Exception(f'UserRole not set for column {source_left.column()}')
|
|
||||||
v1 = item1.value()
|
|
||||||
v2 = item2.value()
|
|
||||||
if v1 is None or isinstance(v1, Decimal) and v1.is_nan(): v1 = -float("inf")
|
|
||||||
if v2 is None or isinstance(v2, Decimal) and v2.is_nan(): v2 = -float("inf")
|
|
||||||
try:
|
|
||||||
return v1 < v2
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_item_key(tx_item):
|
def get_item_key(tx_item):
|
||||||
return tx_item.get('txid') or tx_item['payment_hash']
|
return tx_item.get('txid') or tx_item['payment_hash']
|
||||||
|
|
||||||
|
def flatten_sort_key(v):
|
||||||
|
if v is None or isinstance(v, Decimal) and v.is_nan():
|
||||||
|
return -float("inf")
|
||||||
|
else:
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
class HistoryNode(CustomNode):
|
class HistoryNode(CustomNode):
|
||||||
|
|
||||||
model: 'HistoryModel'
|
model: 'HistoryModel'
|
||||||
|
|
||||||
|
def __init__(self, model: 'CustomModel', tx_item):
|
||||||
|
super().__init__(model, tx_item)
|
||||||
|
|
||||||
|
if tx_item is None:
|
||||||
|
tx_item = {}
|
||||||
|
is_lightning = tx_item.get('lightning', False)
|
||||||
|
short_id = ""
|
||||||
|
if not is_lightning:
|
||||||
|
txpos_in_block = tx_item.get('txpos_in_block', -1)
|
||||||
|
if txpos_in_block >= 0:
|
||||||
|
short_id = f"{tx_item['height']}x{txpos_in_block}"
|
||||||
|
self.sort_keys = {
|
||||||
|
HistoryColumns.DESCRIPTION: flatten_sort_key(
|
||||||
|
tx_item.get('label')),
|
||||||
|
HistoryColumns.AMOUNT: flatten_sort_key(
|
||||||
|
(tx_item['bc_value'].value if 'bc_value' in tx_item else 0)\
|
||||||
|
+ (tx_item['ln_value'].value if 'ln_value' in tx_item else 0)),
|
||||||
|
HistoryColumns.BALANCE: 0,
|
||||||
|
HistoryColumns.FIAT_VALUE: flatten_sort_key(
|
||||||
|
tx_item['fiat_value'].value if 'fiat_value' in tx_item else None),
|
||||||
|
HistoryColumns.FIAT_ACQ_PRICE: flatten_sort_key(
|
||||||
|
tx_item['acquisition_price'].value if 'acquisition_price' in tx_item else None),
|
||||||
|
HistoryColumns.FIAT_CAP_GAINS: flatten_sort_key(
|
||||||
|
tx_item['capital_gain'].value if 'capital_gain' in tx_item else None),
|
||||||
|
HistoryColumns.TXID: flatten_sort_key(
|
||||||
|
tx_item.get('txid') if not is_lightning else None),
|
||||||
|
HistoryColumns.SHORT_ID:
|
||||||
|
short_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_balance(self, balance):
|
||||||
|
self._data['balance'] = Satoshis(balance)
|
||||||
|
self.sort_keys[HistoryColumns.BALANCE] = balance
|
||||||
|
|
||||||
def get_data_for_role(self, index: QModelIndex, role: Qt.ItemDataRole) -> QVariant:
|
def get_data_for_role(self, index: QModelIndex, role: Qt.ItemDataRole) -> QVariant:
|
||||||
# note: this method is performance-critical.
|
|
||||||
# it is called a lot, and so must run extremely fast.
|
|
||||||
assert index.isValid()
|
assert index.isValid()
|
||||||
col = index.column()
|
col = index.column()
|
||||||
window = self.model.window
|
window = self.model.window
|
||||||
@@ -115,7 +149,6 @@ class HistoryNode(CustomNode):
|
|||||||
# and the group does not have an onchain tx
|
# and the group does not have an onchain tx
|
||||||
is_lightning = True
|
is_lightning = True
|
||||||
timestamp = tx_item['timestamp']
|
timestamp = tx_item['timestamp']
|
||||||
short_id = None
|
|
||||||
if is_lightning:
|
if is_lightning:
|
||||||
status = 0
|
status = 0
|
||||||
if timestamp is None:
|
if timestamp is None:
|
||||||
@@ -124,10 +157,6 @@ class HistoryNode(CustomNode):
|
|||||||
status_str = format_time(int(timestamp))
|
status_str = format_time(int(timestamp))
|
||||||
else:
|
else:
|
||||||
tx_hash = tx_item['txid']
|
tx_hash = tx_item['txid']
|
||||||
if col == HistoryColumns.SHORT_ID:
|
|
||||||
txpos_in_block = tx_item.get('txpos_in_block', -1)
|
|
||||||
if txpos_in_block >= 0:
|
|
||||||
short_id = f"{tx_item['height']}x{txpos_in_block}"
|
|
||||||
conf = tx_item['confirmations']
|
conf = tx_item['confirmations']
|
||||||
try:
|
try:
|
||||||
status, status_str = self.model.tx_status_cache[tx_hash]
|
status, status_str = self.model.tx_status_cache[tx_hash]
|
||||||
@@ -135,28 +164,6 @@ class HistoryNode(CustomNode):
|
|||||||
tx_mined_info = self.model._tx_mined_info_from_tx_item(tx_item)
|
tx_mined_info = self.model._tx_mined_info_from_tx_item(tx_item)
|
||||||
status, status_str = window.wallet.get_tx_status(tx_hash, tx_mined_info)
|
status, status_str = window.wallet.get_tx_status(tx_hash, tx_mined_info)
|
||||||
|
|
||||||
if role == ROLE_SORT_ORDER:
|
|
||||||
d = {
|
|
||||||
HistoryColumns.STATUS:
|
|
||||||
# respect sort order of self.transactions (wallet.get_full_history)
|
|
||||||
-index.row(),
|
|
||||||
HistoryColumns.DESCRIPTION:
|
|
||||||
tx_item['label'] if 'label' in tx_item else None,
|
|
||||||
HistoryColumns.AMOUNT:
|
|
||||||
(tx_item['bc_value'].value if 'bc_value' in tx_item else 0)\
|
|
||||||
+ (tx_item['ln_value'].value if 'ln_value' in tx_item else 0),
|
|
||||||
HistoryColumns.BALANCE:
|
|
||||||
(tx_item['balance'].value if 'balance' in tx_item else 0),
|
|
||||||
HistoryColumns.FIAT_VALUE:
|
|
||||||
tx_item['fiat_value'].value if 'fiat_value' in tx_item else None,
|
|
||||||
HistoryColumns.FIAT_ACQ_PRICE:
|
|
||||||
tx_item['acquisition_price'].value if 'acquisition_price' in tx_item else None,
|
|
||||||
HistoryColumns.FIAT_CAP_GAINS:
|
|
||||||
tx_item['capital_gain'].value if 'capital_gain' in tx_item else None,
|
|
||||||
HistoryColumns.TXID: tx_hash if not is_lightning else None,
|
|
||||||
HistoryColumns.SHORT_ID: short_id,
|
|
||||||
}
|
|
||||||
return QVariant(d[col])
|
|
||||||
if role == MyTreeView.ROLE_EDIT_KEY:
|
if role == MyTreeView.ROLE_EDIT_KEY:
|
||||||
return QVariant(get_item_key(tx_item))
|
return QVariant(get_item_key(tx_item))
|
||||||
if role not in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole, MyTreeView.ROLE_CLIPBOARD_DATA):
|
if role not in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole, MyTreeView.ROLE_CLIPBOARD_DATA):
|
||||||
@@ -227,7 +234,7 @@ class HistoryNode(CustomNode):
|
|||||||
elif col == HistoryColumns.TXID:
|
elif col == HistoryColumns.TXID:
|
||||||
return QVariant(tx_hash) if not is_lightning else QVariant('')
|
return QVariant(tx_hash) if not is_lightning else QVariant('')
|
||||||
elif col == HistoryColumns.SHORT_ID:
|
elif col == HistoryColumns.SHORT_ID:
|
||||||
return QVariant(short_id or "")
|
return QVariant(self.sort_keys[HistoryColumns.SHORT_ID])
|
||||||
return QVariant()
|
return QVariant()
|
||||||
|
|
||||||
|
|
||||||
@@ -295,8 +302,6 @@ class HistoryModel(CustomModel, Logger):
|
|||||||
include_lightning=self.should_include_lightning_payments(),
|
include_lightning=self.should_include_lightning_payments(),
|
||||||
include_fiat=self.should_show_fiat(),
|
include_fiat=self.should_show_fiat(),
|
||||||
)
|
)
|
||||||
if transactions == self.transactions:
|
|
||||||
return
|
|
||||||
old_length = self._root.childCount()
|
old_length = self._root.childCount()
|
||||||
if old_length != 0:
|
if old_length != 0:
|
||||||
self.beginRemoveRows(QModelIndex(), 0, old_length)
|
self.beginRemoveRows(QModelIndex(), 0, old_length)
|
||||||
@@ -316,7 +321,7 @@ class HistoryModel(CustomModel, Logger):
|
|||||||
balance = 0
|
balance = 0
|
||||||
for node in self._root._children:
|
for node in self._root._children:
|
||||||
balance += node._data['value'].value
|
balance += node._data['value'].value
|
||||||
node._data['balance'] = Satoshis(balance)
|
node.set_balance(balance)
|
||||||
|
|
||||||
# update tx_status_cache (before endInsertRows() triggers get_data_for_role() calls)
|
# update tx_status_cache (before endInsertRows() triggers get_data_for_role() calls)
|
||||||
self.tx_status_cache.clear()
|
self.tx_status_cache.clear()
|
||||||
|
|||||||
Reference in New Issue
Block a user