wxWidgets / Phoenix

wxPython's Project Phoenix. A new implementation of wxPython, better, stronger, faster than he was before.
http://wxpython.org/
2.23k stars 510 forks source link

Performance Slowdown with wxPy4.1 #1499

Closed Metallicow closed 1 year ago

Metallicow commented 4 years ago

I'm not exactly sure what the problem is as I've noticed this with my app and startup and switching tabs.

I'm guessing the issue is with a wxWidgets change or maybe something related to drawing. I don't think Scintilla or loading textfiles has anything to do with this slowdown.

Testing with wxPy4.0 and 4.1 with agw AUI wxPy demo sample Open the agw AUI demo and resize the frame or move a sash to force redrawing. Also change back and forth from one tab to another. Notice the performance/speed(time it takes to get there). Everything seems fine with the default tab art theme and the chrome tab theme on wxPython4.0 ... On the other hand open the agw AUI demo up with wxPython4.1 and do the same thing. Performance is worse(especially with chrome tabs performance is real bad)

@RobinD42 @infinity77 Any Idea why the performance went to hell...? Might this be related to something wxWidgets might have changed as far as dc drawing stuff...? idunno...

Testing was done on Win7

Metallicow commented 4 years ago

I managed to use print statements in my OnActuallyOpenTheFile method and did a couple benchmarks with SnakeViz. It appears STC seems to have around the same performance, but agw AuiNotebook takes way more.

Can someone explain why there are 10 from . import framemanager lines in methods in agw.aui.auibook.py that are all in methods that get called repeatedly for example on startup? Isn't this known to cause performance issues with python...? Even still, the slowdown only is happening on wxPy4.1....???

SnakeViz open and load 20 files on startup benchmark Screenies benchmarkwxPy407_1

benchmarkwxPy41x_1

The last thing under tabart.py (DrawTab) method is ~:0(<built-in method DrawBitmap>) which really makes me wonder...

infinity77 commented 4 years ago

The multiple module import is irrelevant: once framemanager is imported the first time Python caches the module, so subsequent calls to import the same module are pretty much instantaneous. And I doubt that changing the wxPython version has any effect on that. That said, if DrawTab went from 0.8 seconds to 8 seconds there is definitely something weird going on in the underlying drawing stuff. Since the code has not changed (as far as I can see) then I guess wxWidgets must be doing something different under the hood. Does the AGW demo show the same behavior?

Metallicow commented 4 years ago

Yea, sorta, It lags a little bit on tab change(just barely noticable in the agw.aui demo), but I use a custom copy of agw.aui, mostly for drawing my own custom bitmaps(plus mine has optimizations in places for drawing operations), But I'd lay a guess if someone whipped up a sample to do basically the same as a basic code editor would, it pretty much lags the same when using wxPy4.1. My test was done with actual files(all different, not generated ones, so as to show a real load).

The benchmark shows that 20 files load in 1.24 seconds on wxPy40 and 8.72 seconds on wxPy41. SnakeViz is kinda weird cause it subtracts when it goes down, but yea I verified the startup/regular load function does actually reflect that. (Pain in the ass using all those print statements wrapping functions, but yep that's the ball of wax)

Metallicow commented 4 years ago

One thing I did notice is that the creation time for the auinotebook page keeps increasing the number of pages like a curve. That I'm sure could be optimized probably in the lib file... But the drawing seems to be the biggest chuck of time blowup.

wxPy41 bench results - Click to expand ```python OnOpen File01.py DEBUG Timeit CreateSTC time: 0.0160000324249 DEBUG Timeit AddPage time: 0.0269999504089 DEBUG Timeit File01.py time: 0.0490000247955 OnOpen File02.py DEBUG Timeit CreateSTC time: 0.0160000324249 DEBUG Timeit AddPage time: 0.0289998054504 DEBUG Timeit File02.py time: 0.0499999523163 OnOpen File03.py DEBUG Timeit CreateSTC time: 0.0149998664856 DEBUG Timeit AddPage time: 0.0360000133514 DEBUG Timeit File03.py time: 0.055999994278 OnOpen File04.py DEBUG Timeit CreateSTC time: 0.0130000114441 DEBUG Timeit AddPage time: 0.0449998378754 DEBUG Timeit File04.py time: 0.0629999637604 OnOpen File05.py DEBUG Timeit CreateSTC time: 0.0150001049042 DEBUG Timeit AddPage time: 0.0540001392365 DEBUG Timeit File05.py time: 0.0739998817444 OnOpen File06.py DEBUG Timeit CreateSTC time: 0.0139999389648 DEBUG Timeit AddPage time: 0.0599999427795 DEBUG Timeit File06.py time: 0.0799999237061 OnOpen File07.py DEBUG Timeit CreateSTC time: 0.0160000324249 DEBUG Timeit AddPage time: 0.0710000991821 DEBUG Timeit File07.py time: 0.0930001735687 OnOpen File08.py DEBUG Timeit CreateSTC time: 0.0160000324249 DEBUG Timeit AddPage time: 0.219000101089 DEBUG Timeit File08.py time: 0.241000175476 OnOpen File09.py DEBUG Timeit CreateSTC time: 0.0170001983643 DEBUG Timeit AddPage time: 0.319000005722 DEBUG Timeit File09.py time: 0.342000007629 OnOpen File10.py DEBUG Timeit CreateSTC time: 0.0199999809265 DEBUG Timeit AddPage time: 0.461999893188 DEBUG Timeit File10.py time: 0.486999988556 OnOpen File11.py DEBUG Timeit CreateSTC time: 0.0160000324249 DEBUG Timeit AddPage time: 0.535000085831 DEBUG Timeit File11.py time: 0.556999921799 OnOpen File12.py DEBUG Timeit CreateSTC time: 0.0160000324249 DEBUG Timeit AddPage time: 0.621999979019 DEBUG Timeit File12.py time: 0.644000053406 OnOpen File13.py DEBUG Timeit CreateSTC time: 0.0569999217987 DEBUG Timeit AddPage time: 0.661999940872 DEBUG Timeit File13.py time: 0.724999904633 OnOpen File14.py DEBUG Timeit CreateSTC time: 0.0209999084473 DEBUG Timeit AddPage time: 0.763000011444 DEBUG Timeit File14.py time: 0.790999889374 OnOpen File15.py DEBUG Timeit CreateSTC time: 0.0139999389648 DEBUG Timeit AddPage time: 0.0799999237061 DEBUG Timeit File15.py time: 0.0980000495911 OnOpen File16.py DEBUG Timeit CreateSTC time: 0.0199999809265 DEBUG Timeit AddPage time: 0.861999988556 DEBUG Timeit File16.py time: 0.887000083923 OnOpen File17.py DEBUG Timeit CreateSTC time: 0.0160000324249 DEBUG Timeit AddPage time: 1.04499983788 DEBUG Timeit File17.py time: 1.06500005722 OnOpen File18.py DEBUG Timeit CreateSTC time: 0.0160000324249 DEBUG Timeit AddPage time: 0.0820000171661 DEBUG Timeit File18.py time: 0.102999925613 OnOpen File19.py DEBUG Timeit CreateSTC time: 0.0160000324249 DEBUG Timeit AddPage time: 1.12199997902 DEBUG Timeit File19.py time: 1.14199995995 OnOpen File20.py DEBUG Timeit CreateSTC time: 0.0130000114441 DEBUG Timeit AddPage time: 1.16499996185 DEBUG Timeit File20.py time: 1.1819999218 DEBUG Timeit LoadSessionXML total time: 8.77999997139 DEBUG Timeit gMainWin startup time: 9.32800006866 ```

I seem to keep optimizing my code line by line(still getting faster), but either I'm missing the problem or this appears to be a wxWidgets issue with something that changed internally...

Metallicow commented 4 years ago

I'm just making some general questions here....

  1. @vadz Might there be anything that changed in wxWidgets drawing code inbetween 4.0 and 4.1?

  2. @RobinD42 Might this be related to SIP changes? or maybe someone made a PulReqz that borked things....

  3. @infinity77 You originally ported the thing, so beyond python stuff, is there anything that looks weird to you?

  4. @everybody Have you experienced the slowdown in your projects, and if so, what area?

vadz commented 4 years ago

FWIW I'm not aware of any performance regressions in the drawing code, but I'm not sure which wx versions are wxPy 4.0 and 4.1 based on exactly.

From a quick look at the previous comments it seems like most of the time is spent in pure Python code, so I'm tempted to say that for once this is not our fault...

infinity77 commented 4 years ago

If you look at the commit history of the pure-Python AUI you will see that nothing has changed for years. So you “just” have to track down what other change(s) caused the slowdown. I have a few suggestions in mind, in increasing order of pain. You may want to try each of them separately and record your timeit results:

  1. Try Freeze()-ing and Thaw()-ing your application or the notebook between the first and the last page.

  2. Replace wx.lib.agw.aui with wx.aui

  3. Build a simple frame that displays around 20 bitmaps drawn with wx.PaintDC and test the drawing times for wxPython 4.0 vs. wxPython 4.1

  4. Convert this patch: https://github.com/wxWidgets/wxWidgets/commit/dca84c823c78d8f686d23a8a8efa189d17cc7d0c#diff-66a3101c96333a8f0030dab2da9b49ef to Python and add this piece of code to AuiNotebook.DoSizing (appropriately converted to Python):

    if (m_tabs->IsFrozen() || m_tabs->GetParent()->IsFrozen())
        return;

And see if anything changes.

  1. Build Phoenix with the lowest possible version of Python that it allows and compare it with your current one.

I don’t know which wxWidgets version are tracked by the two wxPython releases, but seeing that the python code hasn’t changed in a long time then the usual suspect are always drawing stuff, measuring text and sizers.

Andrea.

Metallicow commented 4 years ago

Freeze Thaw didn't seem to make a difference by wrapping the function. Also until wxWidgets incorporates more of the agw aui stuff/features, I don't see C++ aui as being an option for me as of this time. Tho I did stumble upon part of the issue. In my OnActuallyOpenTheFile method, I changed the select keyword from True to False

gStcNB.AddPage(stctrl, '%s' % os.path.basename(filepath), True, self.GetTabFileBmp('%s' % filepath))
DEBUG Timeit LoadSessionXML time: 0.608000040054
DEBUG Timeit startup time: 1.16900014877
OnAuiNotebookPageChanged
OnAuiNotebookPageChanged
OnSetFocus testapng.py

By doing that I shaved those 8 seconds off in the print statements anyhow(obviously if Im loading multiple files I shouldn't have to select every one), but there is still like 7-8 seconds of lag after that and it seems creating a new tab still takes a bit of time, as well as changing tabs. I thinking maybe my breadcrumb bar implementation might be part of it. I whipped one up as a AuiToolbar and it tends to do a bit of os.listdir each time tab changes.

Re:3 I think this might be the next step to create a benchmark sample to try and find the biggest time gap. Hopefully it might give enough info to be able to point out what is taking all this extra time.

Re:4 I think I'm going to have to write a method for agw aui notebook that would accommodate loading multiple pages, that way some of those methods that don't need called multiple times will run faster. Ill have to look into that patch also to see if I can figure out something there also.

And maybe one other thing that comes to mind is that wxWidgets might have fixed one of the Focus bugs, and for some reason when I set focus with my fix it might being called multiple times.

infinity77 commented 4 years ago

Ah, yes, if you’re passing select=True any time you add a page you will get page changing/changed events, and if you’re doing stuff inside these events then it’s not a fair benchmark anymore. To be honest I don’t think there is any way you can track down what’s going on using a complex app. Even the AUI demo is way too complicated to do this analysis. My opinion is that you should try a simple app with AuiNotebook inside - with bitmaps - and compare the two Phoenix versions. If there’s any difference there then we may start talking...

Metallicow commented 3 years ago

Ok. It seems I'm at a point in testing this where I may have to be more consistent than I have been ever before in my source code. The idea here is that wxWidgets changed something in the events and that is what may be triggering this... Since every BlaBlaModule.py is also a standalone app, when using them as a whole(imported and called), there was before issues with things not being in the spot they should be at startup. Hence I would do a forced SizeEvent in the initialize method. My Initialize method for all basically runs along these lines.

    def Initialize(self):
        #... All the good stuff and checking for SourceCoder...
        #...
        #... then at the end of all of them is...
        self.SendSizeEvent()  # Force sizer to refresh.
        #... some modules have a wx.CallLater line or so
        self._isInitialized = True

The thing I'm going to try and test here is that since the agw.aui caused hell with startuptime, is that to check if the size events are stacking now or not...? Because if they are stacking, then that would explain why agw.aui performance has went to hell and also why the application as a whole is dropping off a cliff performance wise. While SourceCoder isn't 'exactly' plugin based, it would make a point if 100 plugins/modules structured all the same way would stack for example... 100 size event calls, which force redrawing. testing... so called theory.

Metallicow commented 3 years ago

@infinity77 In case you was wondering, this is a butchered version of the auibook InsertPage method, that I used to speedup and bypass the focus events triggering/stacking. This method or something similar should be added to the main distribution as a public method, but I recall I didn't accommodate for selecting a page(I do locally in my code right after) or handled controls list either. Does this look OK for a start..? ..or should it be written a bit different... ??? All arguments should be equal length lists/tuples as I have it written now... ...also... does the method absolutely need a return bool...?

Collapsible Content - Click to expand ```python def AddMultiplePages(self, pages, captions, selects, bitmaps, disabled_bitmaps, controls, tooltips): """ Adds multiple pages. Only one `selects` parameter should be ``True``, calling this should generate a single page change event. All argument lists should be of equal length. :param wx.Window `pages`: the pages to be added; :param string `captions`: specifies the texts for the new pages; :param bool `selects`: specifies whether the page should be selected; :param wx.Bitmap `bitmaps`: the bitmaps to display in the enabled tabs; :param wx.Bitmap `disabled_bitmaps`: the bitmaps to displayed in the disabled tabs; :param wx.Window `controls`: almost any :class:`wx.Window` -derived instance to be located inside a tab; :param string `tooltips`: the tooltips to display when the mouse hovers over the tab. """ page_idx = self.GetPageCount() from . import framemanager # local opts. framemanager_GetManager = framemanager.GetManager _tabs = self._tabs _tabs_GetPageCount = _tabs.GetPageCount _tabs_InsertPage = _tabs.InsertPage active_tabctrl = self.GetActiveTabCtrl() active_tabctrl_GetPageCount = active_tabctrl.GetPageCount active_tabctrl_AddPage = active_tabctrl.AddPage active_tabctrl_InsertPage = active_tabctrl.InsertPage force = False for i in range(len(pages)): ##page = pages[i] ##caption = captions[i] ### select = selects[i] ##bitmap = bitmaps[i] ##control = controls[i] ##tooltip = tooltips[i] page, caption, select, bitmap, disabled_bitmap, control, tooltip = pages[i], captions[i], selects[i], bitmaps[i], disabled_bitmaps[i], controls[i], tooltips[i] # if not page: # return False page.Reparent(self) info = AuiNotebookPage() info.window = page info.caption = caption info.bitmap = bitmap info.active = False info.control = control info.tooltip = tooltip originalPaneMgr = framemanager_GetManager(page) if originalPaneMgr: originalPane = originalPaneMgr.GetPane(page) if originalPane: info.hasCloseButton = originalPane.HasCloseButton() if bitmap.IsOk() and not disabled_bitmap.IsOk(): disabled_bitmap = bitmap.ConvertToDisabled() info.dis_bitmap = disabled_bitmap # if there are currently no tabs, the first added # tab must be active if _tabs_GetPageCount() == 0: info.active = True _tabs_InsertPage(page, info, page_idx) # if that was the first page added, even if # select is False, it must become the "current page" # (though no select events will be fired) ##if not select and _tabs_GetPageCount() == 1: ## select = True if page_idx >= active_tabctrl_GetPageCount(): active_tabctrl_AddPage(page, info) else: active_tabctrl_InsertPage(page, info, page_idx) if control: force = True control.Reparent(active_tabctrl) control.Show() ## self.UpdateTabCtrlHeight(force=force) ## self.DoSizing() ## active_tabctrl.DoShowHide() # adjust selected index if self._curpage >= page_idx: self._curpage += 1 ##if select: ## self.SetSelectionToWindow(page) page_idx += 1 ## return True self.UpdateTabCtrlHeight(force=force) #self.DoSizing() active_tabctrl.DoShowHide() ```
Metallicow commented 3 years ago

:book: NOTE: to anyone reading and using wxPy4.1 coming from wxPy4.0 gMyAuiNotebook.SetSelection(new_page=index, force=True) is almost plain evil and will kill your performance if not careful. Be very cautious where you place a force=True in your code.

... Some places it is hard to bypass this, just because of how agw.aui is coded.

Ex. If you have a split notebook... agw.aui never accomodated for clicking from one to another and would handle for example(Setting focus, Tab bitmaps, Coloring tab text bold, Title heading, etc) and this stuff (has to be/probably should be) done in an event handler on the fly normally. These issues may or may not exist in wx.aui, but remember wx.aui isnt as moddable and fixable as the python version is.

vadz commented 3 years ago

Sorry, I didn't follow this closely, but I'd just like to say that in most cases SendSizeEvent() really shouldn't be necessary. Ideal is to set the window sizes correctly while they're still hidden and then just show them directly at the right place. I'd like to know if there is some reason you have to call it in your code?

Metallicow commented 3 years ago

@vadz I recall when I started putting the SendSizeEvent lines in there it was because when I would switch OS to test on another sometimes the sizers was not setting up right on startup on whatever OS and grabbing the frame gripper is a PITA to do everytime. Not sure if that is an actual issue anymore since that stuff has been in my Initialize snippet since the buggy wxPy2.8 era and a lot of stuff has been fixed/changed since then. I'll have to revisit this next time I test various win/linux flavors and if it is fixed, then it is probably safe for me to go ahead and remove all them.

HelioGuilherme66 commented 3 years ago

@Metallicow Regarding item 4 in this comment. The project I am working is also from wxPython 2.8 ancient times, and was migrated to Python 3 and wxPython 4. I am currently experiencing unbearable lag, in agw.ui, Notebooks. I am changing colors and fonts of elements, because need to have Dark/Light modes (specially on MacOS). I was hoping that the look of applications would match the system settings (from wx side), but only observe this on MacOS, not on Fedora + Plasma 5 nor Windows 10, (application menu is gray). I anyone wants to take a look I have this PR (but still with some other problems).

I will now see the recommendation you done on this comment.

infinity77 commented 3 years ago

I am not sure I follow anymore: is the slowdown you’re experiencing happening only on Mac? And in what way the setting of custom colors is affecting performance? As far as I can see in this thread, no one has produced a simple example reproducing the problem nor done benchmarks on a simple application.

Metallicow commented 3 years ago

Well I have managed to identify some more of the hard hitters on my performance.

  1. The first was my breadcrumb bar, disabling its 3 initialize/update lines in my code dropped startup from 10sec to 6ish. So 4 seconds basically. This again runs like butter on wxPy4.0 but it lodged inside a agw.aui.AuiToolBar, so Im guessing this wxWidgets event changes that caused it also.

  2. The second is directly tied in with agw.Aui/AuiNoteBook

try:  # Modded agw/aui library.
    import src.lib.xwx.agw.aui as aui
    from src.lib.xwx.agw.aui import aui_switcherdialog as ASD
    import src.lib.xwx.mcow.aui_src as aui_src  # commenting out gains 3 seconds at startup
except ImportError as exc:  # if it's not there locally, try the wxPython lib.
    import wx.lib.agw.aui as aui
    from wx.lib.agw.aui import aui_switcherdialog as ASD

basically I got sick and tired of updating my forked copy and merging changes manually, so at least I was smart and made them overrides instead of always modifying the base agw.aui code. if aui_src imports it will throw my custom arts in and override default ones

from .aui_constants_src import *
from .dockart_src import *
from .tabart_src import *

Now here is where it chokes for 3 seconds. Basically anywhere I set my art mods... So it appears SetArtProvider is the cause of the second one.

        #... in Initializing AuiNotebook ... 
        # Setup and Extend default agw aui tab arts with SourceCoder tab arts.
        try:
            if hasattr(aui_src, 'SourceCoderTabArt'):
                extendTabArts = [
                    aui_src.SourceCoderTabArt,
                    aui_src.CustomTabArt]
                tabArtIndex = gGlobalsDict['iMainAuiNotebookTabArt']
                self.SetupAuiNotebookTabArts(tabArtIndex, extendTabArts)
        except NameError:  # global name --- not defined.
            self.SetupAuiNotebookTabArts()

    def SetupAuiNotebookTabArts(self, tabArtIndex=0, extendTabArts=[]):
        """
        Setup the AuiNotebook Tab Art.

        Default Tab Arts are:

            tab_arts = [aui.AuiDefaultTabArt,
                        aui.AuiSimpleTabArt,
                        aui.VC71TabArt,
                        aui.FF2TabArt,
                        aui.VC8TabArt,
                        aui.ChromeTabArt
                        ]

        :param `tabArtIndex`: Tab Art index to setup notebook for.
        :param `tabArtIndex`: int
        :param `extendTabArts`: List of TabArt classes
        :param `extendTabArts`: list
        """
        self.tab_arts = [aui.AuiDefaultTabArt,
                         aui.AuiSimpleTabArt,
                         aui.VC71TabArt,
                         aui.FF2TabArt,
                         aui.VC8TabArt,
                         aui.ChromeTabArt
                         ] + extendTabArts
        self._notebook_theme = tabArtIndex
        self.SetArtProvider(self.tab_arts[tabArtIndex]())

Aftermath Benchmark: with splashscreen t1 and t2 at end splash everything is initialized. Changed splash timer to 100 so I could benchmark accurately with my normal load of files. t2 - t1 = time Py2x86 wxPy4.0 basically <2-3 seconds most times. With or without scandir breadcrumb bar and arts Py3x64 wxPy4.1 6.56 seconds with aui_src art mods loaded. No breadcrumb bar Py3x64 wxPy4.1 3.93 with aui_src art mods NOT loaded. No breadcrumb bar

My conclusion is that I'm probably just gonna REALLY TRY TO take a crack at ripping every piece of agw.aui apart and making each piece benchmarkable and try to identify event changes or art provider stuff. This is really sad because of all the work Andrea did originally and fixed stuff and the rest of us in the community beat into a decently working version on wxPy4.0, and then this wxPy4.1 major lag came along.

Also I guess Im gonna have to rip up his supertooltip up and rewrite it also because my lightning fast autocomplete anything cannot even pop up a supercalltip without hard crashing. I tried my best to work around timer stuff but to no avail. a calltip really doesn't need timers.

Also Its kinda strange that I have a dialog pop up that hard crashes also on wxPy4.1 when doubleclicking on wx.WHITE and provides other color CONSTANT alternatives. This one doesn't have anything to do with agw.aui... Haven't fully investigated this one. No lag tho, just crash.

Metallicow commented 3 years ago

@infinity77 I guess this is as simple application as you can get as far as drawing arts is concerned. Grab you favorite seamless texture and whatever embedded tab images ya might want. Mine are optimized also before embedding. In the past, I added code at the end of agw.aui constants if anyone needs to extract the images to play around with.

# -Python Imports.
import os
import sys

# -wxPython Imports.
import wx

from wx.lib.embeddedimage import PyEmbeddedImage

# from wx.lib.agw.aui import AuiDefaultTabArt, ChromeTabArt
# from wx.lib.agw.aui.aui_constants import (AUI_BUTTON_CLOSE,
    # AUI_BUTTON_STATE_NORMAL, AUI_BUTTON_STATE_HOVER, AUI_BUTTON_STATE_PRESSED)

# -Local Imports.
from src.lib.xwx.agw.aui import AuiDefaultTabArt, ChromeTabArt
from src.lib.xwx.agw.aui.aui_constants import (AUI_BUTTON_CLOSE,
    AUI_BUTTON_STATE_NORMAL, AUI_BUTTON_STATE_HOVER, AUI_BUTTON_STATE_PRESSED)

class SourceCoderTabArt(ChromeTabArt):
    """
    A class to draw tabs using the SourceCoder style.
    It uses custom bitmaps to render the tabs.

    Inheritance
    AuiDefaultTabArt->ChromeTabArt->SourceCoderTabArt
    """
    def __init__(self):
        """ Default class constructor. """
        AuiDefaultTabArt.__init__(self)
        self._leftActiveBmp = tab_active_left_src.GetBitmap()
        self._centerActiveBmp = tab_active_center_src.GetBitmap()
        self._rightActiveBmp = tab_active_right_src.GetBitmap()
        self._leftInactiveBmp = tab_inactive_left_src.GetBitmap()
        self._centerInactiveBmp = tab_inactive_center_src.GetBitmap()
        self._rightInactiveBmp = tab_inactive_right_src.GetBitmap()

        closeBmp = tab_x_16.GetBitmap()
        closeHBmp = tab_x_hover16.GetBitmap()
        closePBmp = tab_x_pressed16.GetBitmap()

        self.SetCustomButton(AUI_BUTTON_CLOSE, AUI_BUTTON_STATE_NORMAL, closeBmp)
        self.SetCustomButton(AUI_BUTTON_CLOSE, AUI_BUTTON_STATE_HOVER, closeHBmp)
        self.SetCustomButton(AUI_BUTTON_CLOSE, AUI_BUTTON_STATE_PRESSED, closePBmp)
# -wxPython Imports.
import wx
# from wx.lib.agw.aui import AuiDefaultDockArt
from src.lib.xwx.agw.aui import AuiDefaultDockArt

class TiledBackgroundDockArt(AuiDefaultDockArt):

    def __init__(self, bitmap):
        AuiDefaultDockArt.__init__(self)

        self._bitmapBrush = wx.Brush(bitmap)

    def SetBrushBitmap(self, bitmap):
        self._bitmapBrush = wx.Brush(bitmap)

    def DrawBackground(self, dc, window, orient, rect):  # Seamless Tiled Background Method
        """
        Draws a background.

        :param `dc`: a :class:`DC` device context;
        :param `window`: an instance of :class:`Window`;
        :param integer `orient`: the gradient (if any) orientation;
        :param Rect `rect`: the background rectangle.
        """
        dc.SetPen(wx.TRANSPARENT_PEN)
        dc.SetBrush(self._bitmapBrush)
        dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height)
Metallicow commented 3 years ago

@HelioGuilherme66 I took a quick look at you ginormous PR but didnt see anything that really stood out. I did take a quick look here tho... https://github.com/robotframework/RIDE/blob/master/src/robotide/ui/notebook.py

    def OnTabChanging(self, event):
        ...
        self.GetPage(event.GetSelection()).SetFocus()
        RideNotebookTabChanging(oldtab=oldtitle, newtab=newtitle).publish()

    def OnTabChanged(self, event):
        ...
        RideNotebookTabChanged().publish()

I would be very cautious of these lines at first glance. Not sure what the Ride things do... appears Pypubsub is involved. but whatever is in the notebook might have a Focus handler you have implemented which might offer you a very ugly hackish way I implemented that cures the major tab changing lag for me but the OnSetFocus event fires off like crazy #N times instead of once. This is the bass-ackwards logic at play.


    def OnSetFocus(self, event):
        event.Skip()

        gMainWin.activeSTC = self
        ## print('ACTIVE CHILD: ', gMainWin.activeSTC)
        gMainWin.filepath = self.filepath
        ## print('ONSETFOCUS, filepath: ', self.filepath)

        stc_page_idx = gStcNB.GetPageIndex(self)
        # gStcNB.SetSelection(new_page=stc_page_idx, force=True)  # Fix stuff when notebook is split.
        gStcNB.SetSelection(new_page=stc_page_idx, force=False)  # Fix stuff when notebook is split. Dont Force, Handle setting activeStc and filepath in AuiNotebookModule.OnAuiNotebookPageChanged
        if not self.HasFocus():
            self.SetFocus()  # This is just plain ugly/stupid/hackish to have to do to avoid forcing gStcNB selection on wxPy4.1.

        ... a bunch of other maintenance calls ...

        def_name('%s' % self.filename)  # print the name of the called def to console/terminal
```.
HelioGuilherme66 commented 3 years ago

@Metallicow (sorry for that ugly PR) That OnTabChanging was an evolution from a hack for problems before wxPython 4. I was getting repeated aui.EVT_AUINOTEBOOK_PAGE_CHANGED (or original aui.wx.lib.agw.aui.auibook) events.

RIDE is an IDE, with usually three NoteBooks tabs created: an Editor which is a Grid with custom cell editor, a Text Editor which is a stc with code colorization, and a Run tab which have some arguments text fields and two text areas with output only (logs). There is also an detachable pane for the Project exploring (very important to select items as arguments for Run tab, and Editor).

We use PyPubSub for the messaging between the components. The normal wx events are very important because of the focus and update of the editor tabs. We had (and still have) some problems with this. For example pressing Ctrl-F on Grid Editor, was being captured by Text Editor (find field), when we change the document model on Grid Editor, we must have it updated in Text Editor (and vice versa). When we leave Text Editor we whant to save the caret position to restore when coming back. See below, the related code in Text Editor (triggered by PyPubSub messages):

    def OnTabChange(self, message):
        if message.newtab == self.title:
            self._register_shortcuts()
            self._open()
            self._editor.set_editor_caret_position()
            self._set_read_only(self._editor._editor.readonly)
        elif message.oldtab == self.title:
            self._editor.remove_and_store_state()
            self.unregister_actions()
            self._editor_component.save()

    def OnTabChanged(self, event):
        self._show_editor()

    def OnTabChanging(self, message):
        if 'Edit' in message.oldtab:
            self._editor.save()

@infinity77 About the performance degradation it happens across all operating systems, but is more noticeable in Linux. Less in Windows. We really should have a benchmarking template for wxPython, and I most interested in doing it, also the automated tests for the demo. Unfortunately, I get so many different code challenges and surprises in wxPython evolution, that I can't have my project stable enough to dedicate my time to make them. I am also limited in my programming knowledge, and sometimes get lost in simple things not working, when they should. For example, in this goal of having the background color changed for all elements I have a dialog with color selection, the background color on that dialog does no change, even if I force Refresh. The color would only be set at new dialog call.

In short, there was not performance degradation on RIDE 2.0, pre-release install (Windows as reference): python3 -m pip install -U --pre robotframework-ride

But when using SetBackgroundColor there was in my branch colours.

I'll try to make a simple application based on auinotebook + Grid with custom cell editor.

Metallicow commented 3 years ago

@HelioGuilherme66 I'll take a look at how I theme my whole app's modules and get back with a simpler solution. I haven't exposed it in my wxPython Demo framework because it is always a item of benchmarking. It is similar The widget inspectors loop code with a bunch of if/elif stuff for each and every widget like I have done in My sample wxPy Demo framework but only added it at app level for MouseScroll events in Main.py. https://github.com/Metallicow/wxPython-Sample-Apps-and-Demos The single file demo framework is still largely unfinished, but it works. The subdemos here are all standalone programs and should work. Just create a folder and name it benchmarks and ummm... do whatever you think you need to do to make a sample.

@infinity77 @RobinD42 Yea, yea, I know.... Robin says that it isn't designed for games. I'm stubborn. I made a benchmark. If there is any drawing regressions, it doesn't seem to show in my "Game" lol. So that is why I think this is wxWidgets event based changes, and agw.aui... well still isnt perfect by any means. [Benchmark]

Metallicow commented 3 years ago

@HelioGuilherme66 according to what you have said, then I would guess that these two lines will probably cause agw.aui to redraw/refresh/update immediately or reiterate its whole mindless loop to figure out what is supposed to be what and finally after 10-#### iterations it finally gives up and says "wow we finally got to where you told me to go"

            self._editor.remove_and_store_state()
        self._show_editor()
Metallicow commented 3 years ago

The best way to handle this situation is to listen sometimes: Not all folks know you are alive when you type, but we all know probably 5 of them are "respected" They might have forced you to listen to this. And you held their word. https://www.youtube.com/c/johnnycashofficial/featured https://www.youtube.com/watch?v=8AHCfZTRGiI

infinity77 commented 3 years ago

@HelioGuilherme66 : it seems to me you are having a general performance issue with setting colors overall - is that the case? Or is it only because of wx.lib.agw.aui? I’m unclear on that.

@Metallicow : I’m not sure we understand each other, and I suggest we stay on point instead of wandering into unrelated issues. I have quite a few major applications that use wx.lib.agw.aui, and since they use wxPython Classic and previous versions of Phoenix I can’t see any performance hit on those.

So, let’s see if I can summarize this issue correctly:

Then let’s turn to where the issue may be:

  1. Maybe some changes in SIP or the way the calls to wxWidgets methods through SIP are made has changed? Unlikely, but to keep in mind
  2. The move from wxPython 4.0 to 4.1 has a very big jump in the wxWidgets version: did something change there? Very many things did. How about the continuous and very frequent checks of screen DPI stuff? Or the text measuring routines? Any change on sizers?

One thing is clear: you cannot possibly benchmark this issue by using RIDE or SourceCoder. I don’t use wxPython 4.1 so it doesn’t concern me that much, but to get to the bottom of this I am afraid you will have to sit down, write a simple app and benchmark that one using the two different wxPython versions to find out where the slowdown is. Randomly posting unrunnable snippet of code in this thread is not going to get us anywhere.

If and when the bottlenecks are identified someone will probably have to create a wxWidgets app showing the issue - assuming the problem is in wxWidgets - and see if the wxWidgets team is willing to change stuff.

Metallicow commented 3 years ago

The way I see it is that the aui manager will have to give up rights to the notebook tab container events and at that point the user would be in control of whether the "Game" or in this case we could relate the sprites to be tabs and the on and off functions(like pause) would have to be left up to the user to implement. Most of it could be done relatively easily but even if you rip that part out and replace it, users would still have to go into their own code and update it because of the focus issue, if they had implemented a focus fix workaround in the past. At least by doing it this way when a user would set focus in an event handler the game(tab) engine should be able to recognize this and either stop because it has lost focus or could be overridden with something like event.Skip(). Tho in order to have one that could be a drop in replacement for the aui notebook, it would have to be developed as a tab game in mind first and tested, then convert def names to keep all the named functions the same.

Ex: AddPage is essentially AddSprite, at which point you would need to redraw just once and then stop the drawloop(pause) Most of this would be matrix math but at the 2D level, which that is why events cannot be allowed otherwise it one or the other would have to wait on the other which would cause an immediate slowdown.

So in the end basically aui manager would not be allowed to handle its draw loop, but this would make it much easier for the user to manipulate the splitting of the tabs. And of course developing all that would take a bit of time, so it wouldn't happen overnight. And there would be need of the threewaysplitter to handle splitting at a level where each split would have its own tab game instance.

The long drawn out answer to this is that while performance is unbearable at the moment, at least it didn't break anything.

Metallicow commented 3 years ago

@HelioGuilherme66 This should help with your theming problem. Of course if your framework is plugin based each plugin will have to call it upon itself when it is initialized one way or the other. And of course the amount of kwargs will increase depending on each widget and how many colors might be applied to it... so yea, there has to be some sort of order to the colors. Hope this helps.

    def _ApplyThemeToWidget(self, widget,
                            foreColor=wx.RED, backColor=wx.BLACK):
        if isinstance(widget, wx.lib.agw.aui.AuiToolBar):
            auiDefaultToolBarArt = wx.lib.agw.aui.AuiDefaultToolBarArt()
            auiDefaultToolBarArt.SetDefaultColours(wx.BLACK)
        elif isinstance(widget, wx.Control):
            if not isinstance(widget, (wx.Button, wx.BitmapButton)):
                widget.SetForegroundColour(foreColor)
                widget.SetBackgroundColour(backColor)
        elif etc...
        else:
            raise UnthemableWidgetError('HELP! I have no clue how to theme this.')

    def _WalkWidgets(self, widget, indent=0, indentLevel=2):
        ## print(' ' * indent + widget.__class__.__name__)
        widget.Freeze()
        self._ApplyThemeToWidget(widget)
        for child in widget.GetChildren():
            if (not child.IsTopLevel() or isinstance(child, wx.PopupWindow)):
                indent += indentLevel
                self._WalkWidgets(child, indent, indentLevel)
            indent -= indentLevel
        widget.Thaw()
Metallicow commented 1 year ago

This can be closed, as the focus bug appears to be fixed.