-
Notifications
You must be signed in to change notification settings - Fork 71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix resizing issues, remove multi-threaded message handling from WinGUI
variant
#240
Fix resizing issues, remove multi-threaded message handling from WinGUI
variant
#240
Conversation
…e/move" This reverts commit dd59e4c. Note: there were conflicts that had to be resolved, so this wasn't a pure revert.
1. WM_ERASEBKGND should return `1` and not `0`, otherwise the runtime will attempt to repain the background automatically, which can lead to flickering. 2. Update wndclass.style to include CS_HREDRAW (in addition to already specified CS_VREDRAW) to ensure horizontal resizing also triggers a WM_PAINT message. 3. Within `HandlePaint` draw all contents to a back buffer, then blit the entire thing to the screen when done. This prevents flicking while resizing/repainting the entire viewport.
That is most reasonable for me. Just out of interest @clangen:
|
Not sure I'd jump to that conclusion. The dual-threaded solution has been a real annoyance, and really solves only two minor problems : (1) while a window is resized, you can't process anything; (2) the background of the window can get mangled during the resizing (but becomes okay once you stop resizing and the program gets a As you note, the two-thread solution causes problems in accessing the main window. It also makes the code a tangled mess. I've made fixes (see commit b84cf0c, issue #197 and issue #212) where mysterious bugs cropped up. They were the stereotypical sort of bugs you get with multithead solutions : hard to reproduce, hard to diagnose. I was extremely pleased when William made the X11 port run in one process, for similar reasons. Forgive me if I'm overlooking something here, but from your description and the video, it looks as if the callback function enables us to evade both of the above issues. True, it's WinGUI specific. But you can ignore it, and the only consequence would be that when resizing in WinGUI, you''d see problems (1) and (2) above. Is that a fair summary? If so, the WinGUI specific nature of your fix is not particularly troubling to me. I expect that 99% of people will ignore it. Frankly, we had (1) and (2) for nine years without complaint. But your solution would mean that in those 1% of cases where resizing must look good, you can set up a callback function and be okay. |
...And in the Department of Really Minor Issues : in static void PrepareBackBuffer(HDC hdc, RECT rect)
{
memset(&backBuffer, 0, sizeof(backBuffer));
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
backBuffer.rect = rect; causing compilation failures on OpenWATCOM, which is a ISO C89 (maybe C90?) compiler... anyway, it uses the C standard in which the |
Sure, here's what it looks like with the current multi-threaded solution: resize_with_multi-threaded_code.movThe wincon and vt ports are completely unaffected by this change, so they will continue to work however they do today.
Yep, that's correct -- you're given an escape hatch to perform computations if necessary while the callback runs, including performing layout operations/transformations with your
Ah, right. That should be easy to fix. Thanks for pointing it out!
|
Alright, made a couple additional changes and CI looks happy. Let me know if there are any other changes you'd like me to take a look at. :) |
One additional thing that may be nice: also double buffer the repaint from However, that doesn't seem to be easy to cleanly because the implementation lives in the shared Definitely not strictly a requirement, as I don't observe any flicker from |
I've been sidetracked by day-job stuff, but this is looking quite good. I see that adding the callback function costs us about ten lines of entirely readable and understandable code. Can't argue with that. I gave a try to a single-thread version on my machine, running on Wine in Linux, nothing except the reversion and updating, and... everything looked good. Then I remembered that we never had the problem under Wine to begin with; Wine, oddly enough, handles these resizing issues "correctly", with no blocking. Unlike, say, Microsoft's product. (Really, the ideal fix here would be for MS to handle resizing without locking everything up. But there are probably programs out there that rely on that mis-feature.) I do still have a "real" Win1 0 machine, and will give it a go on that... with luck, I'll get a shot at it this weekend, then merge it. Agreed on the double buffering involving uglier code than we'd really like to have. I'd suggest holding off on that for at least a while; maybe something cleaner will occur to us. |
@clangen - I've now tried this out on both Linux/Wine and Win1 0, and it all looks good to me. Shall I click on 'Merge pull request' now, or do you still have tweaks in mind? The only regression I saw was that in Wine, the removal of |
I don't have any more tweaks in mind, and can even add the |
Overview
Some time ago a change was merged into
PDCursesMod
to offload Window message handling to a background thread to make resizing happen more fluidly: dd59e4cUnfortunately, this caused some issues with my apps. Specifically, I augment existing
PDCursesMod
functionality to do things like change the app icon and support minimizing to the notification tray. To do this, I get a reference to the main Window Handle (HWND
), and call various Win32 API methods against it. Unfortunately, I am making these calls from the main app thread, not the thread running the message pump, so it leads to weird/undefined behavior that usually manifests as the app freezing at random times during operation, and very often during startup and shutdown when I'm making these API calls.Why does
PDCursesMod
use a non-main Window thread?This had me scratching my head for a while, but after a couple hours of debugging I figured it out. When
getch()
or one of its variants are called, pending key events are returned to the caller, but only after all pending Windows messages have been processed by the internal message pump.The main message pump in
PDCursesMod
(and most Win32) apps looks like this, inPDC_napms
:Basically: it runs a tight loop until the message queue is completely drained.
However, when a resize event starts (i.e the user clicks the resize handle), the message pump remains active, as the Windows runtime is using it to monitor and process mouse moves, converting them to
WM_SIZE
andWM_SIZING
messages. That means that the curses KEY_RESIZE event is not returned to the caller until the message pump is drained, and all sizing has finished. So, while the native window itself updates and changes size, the client app does not have an opportunity to update its cursesWINDOW
and/orPANEL
instances until it's too late (i.e. sizing has finished).Offloading the message processing to a separate thread, along with some careful synchronization via
CRITICAL_SECTION
addresses this immediate issue, but it introduces other problems as described above (i.e. whenever the user needs to do something with the mainHWND
directly).A potential solution
On UNIX platforms we can handle the
SIGWINCH
signal to be notified when the controlling terminal changes size, then update ourWINDOW
andPANEL
instances accordingly. Unfortunately we don't have this luxury on Windows.However, we could add a
WinGui
specific callback to thePDC
API that fires duringWM_SIZE
, whenever the Window has changed dimensions. The calling app could then use this the same way it handlesSIGWINCH
on UNIX platforms to update layout, and then we're good to go.Clearly the downside is this introduces a non-standard, bespoke API for only
WinGui
, but it would do away with the goofyCRITICAL_SECTION
management stuff, and also generally make things more stable.Code changes
Unfortunately, I realize that the introduction of a custom API just for
WinGui
is undesirable and will likely result in this change not being merged. However, I wanted to share my findings and a sample implementation, as it may be useful for coming up with a more robust solution.This proof-of-concept implementation spans 4 commits; it's probably easiest to view this commit-by-commit with the following commentary:
WM_PAINT
handling to ensure there is no/minimal flicker when aggressively resizing. Windows isn't very good about batching draw commands to an on-screen DC, so we accumulate all updates to an offscreen buffer, then blit it back to the screen all at once. (This is an old Win32 trick to deal with this sort of issue).Demo
Here's what it looks like, said and done:
2022-07-24.17-22-52.mov