From a293ee570153735b816aa092b365dae9d4021cc8 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Fri, 22 Sep 2023 20:18:50 +0100 Subject: [PATCH] Improved tree and addwidgets performances --- TermTk/TTkCore/helper.py | 5 +- TermTk/TTkLayouts/layout.py | 12 ++- TermTk/TTkWidgets/TTkModelView/treewidget.py | 9 ++- .../TTkWidgets/TTkModelView/treewidgetitem.py | 48 ++++++++--- tests/stress/02.many.tree.nodes.py | 8 +- tests/timeit/16.event.vs.set.py | 81 +++++++++++++++++++ 6 files changed, 134 insertions(+), 29 deletions(-) create mode 100755 tests/timeit/16.event.vs.set.py diff --git a/TermTk/TTkCore/helper.py b/TermTk/TTkCore/helper.py index 4ac70cf8..e1f88bda 100644 --- a/TermTk/TTkCore/helper.py +++ b/TermTk/TTkCore/helper.py @@ -88,8 +88,9 @@ class TTkHelper: @staticmethod def addUpdateWidget(widget): # if not widget.isVisibleAndParent(): return - TTkHelper._updateWidget.add(widget) - TTkHelper.unlockPaint() + if widget not in TTkHelper._updateWidget: + TTkHelper._updateWidget.add(widget) + TTkHelper.unlockPaint() @staticmethod def addUpdateBuffer(canvas): diff --git a/TermTk/TTkLayouts/layout.py b/TermTk/TTkLayouts/layout.py index 0fcea121..b648d4fc 100644 --- a/TermTk/TTkLayouts/layout.py +++ b/TermTk/TTkLayouts/layout.py @@ -312,7 +312,7 @@ class TTkLayout(TTkLayoutItem): :param widgets: the widget to be removed :type widgets: list of :class:`~TermTk.TTkWidgets` ''' - for item in self._items: + for item in reversed(self._items): if item._layoutItemType == TTkK.WidgetItem and \ item.widget() in widgets: self.removeItem(item) @@ -366,13 +366,11 @@ class TTkLayout(TTkLayoutItem): def update(self, *args, **kwargs): ret = False - for i in self.children(): - if i._layoutItemType == TTkK.WidgetItem and not i.isEmpty(): - ret = ret or i.widget().update(*args, **kwargs) - # TODO: Have a look at this: - # i.getCanvas().top() + for i in self._items: + if i._layoutItemType == TTkK.WidgetItem and (_wid:=i._widget): + ret = ret or _wid.update(*args, **kwargs) elif i._layoutItemType == TTkK.LayoutItem: - ret= ret or i.update(*args, **kwargs) + ret = ret or i.update(*args, **kwargs) return ret class TTkWidgetItem(TTkLayoutItem): diff --git a/TermTk/TTkWidgets/TTkModelView/treewidget.py b/TermTk/TTkWidgets/TTkModelView/treewidget.py index 8ca0079b..ef0d8b6c 100644 --- a/TermTk/TTkWidgets/TTkModelView/treewidget.py +++ b/TermTk/TTkWidgets/TTkModelView/treewidget.py @@ -109,7 +109,7 @@ class TTkTreeWidget(TTkAbstractScrollView): def clear(self): # Remove all the widgets for ri in self._rootItem.children(): - ri.setParent(None) + ri.setTreeItemParent(None) if self._rootItem: self._rootItem.dataChanged.disconnect(self._refreshCache) self._rootItem = TTkTreeWidgetItem(expanded=True) @@ -121,15 +121,16 @@ class TTkTreeWidget(TTkAbstractScrollView): def addTopLevelItem(self, item): self._rootItem.addChild(item) - item.setParent(self) + item.setTreeItemParent(self) self._refreshCache() self.viewChanged.emit() self.update() def addTopLevelItems(self, items): self._rootItem.addChildren(items) - for item in items: - item.setParent(self) + self._rootItem.setTreeItemParent(self) + #for item in items: + # item.setTreeItemParent(self) self._refreshCache() self.viewChanged.emit() self.update() diff --git a/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py b/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py index ba44fb93..cad6035d 100644 --- a/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py +++ b/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py @@ -75,7 +75,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): widget.sizeChanged.connect(self._widgetSizeChanged) self._height = max(self._height,widget.height()) if self._parentWidget: - widget.setParent(self._parentWidget) + widget.setTreeItemParent(self._parentWidget) if hasattr(widget, 'text'): ret = widget.text() if hasattr(widget,'textChanged'): @@ -129,18 +129,40 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): def height(self): return self._height - def setParent(self, parent): + def _clearTreeItemParent(self): + widgets = [] + if self._hasWidgets: + widgets += [w for w in self._widgets if w and w.parentWidget()] + # for widget in widgets: + # if pw := widget.parentWidget(): + # pw.rootLayout().removeWidgets([w for w in self._widgets if w]) + if self._parentWidget: + self._parentWidget.rootLayout().removeWidgets(widgets) + self._parentWidget = None + for c in self._children: + widgets += c._clearTreeItemParent() + return widgets + + def _setTreeItemParent(self, parent): self._parentWidget = parent + widgets = [] if self._hasWidgets: - widgets = [w for w in self._widgets if w] - if parent: - parent.layout().addWidgets(widgets) - else: - for widget in widgets: - if pw := widget.parentWidget(): - pw.rootLayout().removeWidgets([w for w in self._widgets if w]) + widgets += [w for w in self._widgets if w] + # parent.layout().addWidgets(widgets) for c in self._children: - c.setParent(parent) + widgets += c._setTreeItemParent(parent) + return widgets + + def setTreeItemParent(self, parent): + if parent: + widgets = self._setTreeItemParent(parent) + parent.rootLayout().addWidgets(widgets) + else: + # pw = self._parentWidget + widgets = self._clearTreeItemParent() + # pw.rootLayout().removeWidgets(widgets) + + def hasWidgets(self): return self._hasWidgets @@ -168,7 +190,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): self._setDefaultIcon() self._sort(children=False) if self._parentWidget: - child.setParent(self._parentWidget) + child.setTreeItemParent(self._parentWidget) child.dataChanged.connect(self.emitDataChanged) def addChild(self, child): @@ -189,7 +211,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): return None child = self._children.pop(index) child.dataChanged.disconnect(self.emitDataChanged) - child.setParent(None) + child.setTreeItemParent(None) self.dataChanged.emit() return child @@ -197,7 +219,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): children = self._children for child in children: child.dataChanged.disconnect(self.emitDataChanged) - child.setParent(None) + child.setTreeItemParent(None) self._children = [] self.dataChanged.emit() return children diff --git a/tests/stress/02.many.tree.nodes.py b/tests/stress/02.many.tree.nodes.py index 358b9517..bf0d97dd 100755 --- a/tests/stress/02.many.tree.nodes.py +++ b/tests/stress/02.many.tree.nodes.py @@ -72,7 +72,8 @@ ttk.TTkLog.use_default_file_logging() root = ttk.TTk(layout=ttk.TTkGridLayout()) -root.layout().addWidget(btn:=ttk.TTkButton(text="Add Buttons",border=True,maxHeight=3),0,0) +root.layout().addWidget(btnadd:=ttk.TTkButton(text="Add Nodes",border=True,maxHeight=3),0,0) +root.layout().addWidget(btndel:=ttk.TTkButton(text="Clean",border=True,maxHeight=3),0,1) root.layout().addWidget(tw:=ttk.TTkTree(),1,0,1,2) root.layout().addWidget(ttk.TTkLogViewer(maxHeight=10),2,0,1,2) @@ -83,7 +84,7 @@ types = ("Fatal", "Error", "Mil", "Warning", "Info", "Entry", "Exit") def _addMany(): ttk.TTkLog.info("Start Stress!!!") items = [] - for i in range(300): + for i in range(500): _item_elements = [f"n: {i=}", 'console'] for _t in types: _btn_t = _OnOff(checkable=True, checked=(i%0x07==0)) @@ -94,6 +95,7 @@ def _addMany(): tw.addTopLevelItems(items) ttk.TTkLog.info("End Stress!!!") -btn.clicked.connect(_addMany) +btnadd.clicked.connect(_addMany) +btndel.clicked.connect(tw.clear) root.mainloop() diff --git a/tests/timeit/16.event.vs.set.py b/tests/timeit/16.event.vs.set.py new file mode 100755 index 00000000..2d90616d --- /dev/null +++ b/tests/timeit/16.event.vs.set.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2023 Eugenio Parodi +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import sys, os + +import timeit +import threading, random +from functools import reduce + +data = [random.randint(0,10) for _ in range(500)] +pe = threading.Event() +pe.set() + +def test1(d=data, p=pe): + s = set() + for x in d: + s.add(x) + p.set() + return sum(s) + +def test2(d=data, p=pe): + s = set() + for x in d: + if x not in s: + p.set() + s.add(x) + return sum(s) + +def test3(d=data, p=pe): + s = set() + for x in d: + if x not in s: + s.add(x) + p.set() + return sum(s) + +def test4(d=data, p=pe): + s = set() + for x in d: + if x not in s: + s.add(x) + return sum(s) + +def test5(d=data, p=pe): + s = set() + for x in d: + s.add(x) + return sum(s) + +loop = 10000 + +a={} + +iii = 1 +while (testName := f'test{iii}') and (testName in globals()): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"test{iii:02}) | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}") + iii+=1 +