mrJean1 / PyCocoa

Basic, partial Python binding to Objective-C Cocao
16 stars 2 forks source link

Event breakdown on M1 #7

Open caffeinepills opened 1 year ago

caffeinepills commented 1 year ago

Using a loop to wait for events is causing issues with M1 processors.

Here are examples:

PyObjC version: https://pastebin.com/11MFFniA

Pycocoa version: https://pastebin.com/t7ud54C9

With the PyCocoa version, after 100 or so mouse clicks, the events start acting up. Sometimes the events start getting out of sync, I don't know if it's missed or duplicate events, but instead of getting 1 mouse down and 1 mouse up for every click it starts doing this:

got mouse down (180, 150)
got mouse up (180, 150)
got mouse down (180, 150)
got mouse down (180, 150)
got mouse up (180, 150)
got mouse down (180, 150)
got mouse down (180, 150)
got mouse up (180, 150)
got mouse down (180, 150)
got mouse up (180, 150)

If you minimize the window then maximize it again, it seems to work for a bit then breaks again. I have confirmed with PyObjC that this issue does not occur. Looking at other windowing libraries, they use this same loop behavior for checking events. For testing purposes we tried GLFW to see if we could reproduce the issues, but the events were stable. So I can confirm the code itself should function properly, it's something else within the library causing this issue. If you use the built in event loop (app.run()), it also seems to function properly.

I've only been able to confirm this behavior happening on M1 processors. On my test machine (x86_64), this issue does not seem to occur.

mrJean1 commented 1 year ago

Alternating mouse down and up events never fail after several hundred clicks with pycocoa 23.2.5, Python 3.11.1 on Apple M1 MacBook Air, arm64, macOS 13.2 with 4 different mice: Apple Bluetooth, Thinkpad Bluetooth Laser, Lenovo USB-dongle and wired Logitech. Attached is a snapshot from a slightly modified script. Same results for Python 3.9.6 on M1 arm64 and for Python 3.8.10, 3.7.6 and 2.7.18 all on M1 x86_64 emulation.

...
got mouse down (164, 111, True, 421)
got mouse up (164, 111, True, 422)
got mouse down (164, 111, True, 423)
got mouse up (164, 111, True, 424)
got mouse down (164, 111, True, 425)
got mouse up (164, 111, True, 426)
got mouse down (164, 111, True, 427)
got mouse up (164, 111, True, 428)
got mouse down (164, 112, True, 429)
got mouse up (164, 112, True, 430)
...

issue7.zip

benmoran56 commented 1 year ago

Just to add, I can confirm that I do have the issue here with my M1 Mac. MacBook Pro 14-inch, 2021 macOS 13.1

It doesn't happen immediately, and not every time. I was able to get it to happen with the following: --> Click a few dozen times or more at a high rate. Wait a few minutes, then quickly click a bunch of times again.

mrJean1 commented 1 year ago

Tried that sequence several times, but there is never a missed click with a mouse, neither with the trackpad of my M1,2020 Macbook Air and macOS 13.2.1.

benmoran56 commented 1 year ago

Thanks for checking. I'll update my Mac to 13.2 and see if there is any change.

mrJean1 commented 1 year ago

It seems to be more unlikely a software issue, because if it were, the issues should be reproducable on other M1 Macs. Perhaps, try wirh a different mouse, wired or wireless to eliminate that part.

Cleptomania commented 1 year ago

For what it's worth I experience the same problem on a 2021 M1 Macbook Pro on OS version 13.2.1, with Python 3.11.2, and pycocoa 23.2.6, using the laptop's built-in trackpad. I don't currently have the ability to test another mouse.

caffeinepills commented 1 year ago

I tried some more troubleshooting on this, but unfortunately was unable to resolve the issues.

Other things tried:

I am not quite sure what else can be done. I did confirm this issue exists with Rubicon-objc as well. My only guess is there is some ctypes issue hiding somewhere.

mrJean1 commented 1 year ago

Certainly, there is overhead due to the ctypes layer resulting in events not being handled in a timely manner. The round trip from macOS to ObjC to ctypes to Python to PyCocoa to the App may be too long.

And if so, that also would explain why PyObjC does not exhibit the issue, since there are at least one, probably more layers less in this case.

All variadic functions in pycocoa.oslibs.py have the first argument defined (as required for arm64), except _csignature_variadic(libobjc.object_setInstanceVariable, Ivar_t). However, function pygeodesy.runtime.set_ivar does set all argtypes before invoking libobjc.object_setInstanceVariable.

caffeinepills commented 1 year ago

That's certainly a possibility. Perhaps arm64 is more sensitive to the calls, or there is something else happening. I am not entirely sure. I did go through those functions and they do seem set properly, but yeah I couldn't find any other clues. Unfortunately I don't really have any more areas or knowledge to investigate further.

The only workaround (if anyone else has this issue) is to instead use the built in NSApplication shared application event loop. I did end up finding that NSTimer.scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: seems to schedule in that same loop. So we are able to at least elapse time in Python, make calls, and dispatch events that way in the same thread. Using this seems to bypass the issue for now.

mrJean1 commented 1 year ago

If using a separate event loop helps avoid the issue, that shows that there is too much overhead.

Perhaps, that event loop just gets the events and only buffers them, does not call any of the higher level handlers to avoid that overhead. The higher-level code simply loops and pops event from the front of the buffer.

The Tcl/Tk event loop operates like that. And even allows events to be appended to the buffer.