I am trying to write a simple game loop for MacOS. The event polling solution I found on libraries like GLFW, SDL, and MacOS game ports for DOOM, Quake, Handmade Hero is to use the nextEventMatchingMask
API. I am interested in the latency of event polling:
t0 = mach_absolute_time();
NSEvent *event = [NSApp nextEventMatchingMask:NSEventMaskAny
untilDate:nil
inMode:NSDefaultRunLoopMode
dequeue:YES];
t1 = mach_absolute_time();
latency=t1-t0
The experiment I run (source code) consists of opening a Cocoa MacOS window and "randomly" generating mouse and keyboard events. The typical latency numbers I get for the first 1000 events in such experiment looks like this
# latency percentiles in milliseconds
: 0% 5% 10% 15% 20% 25% 30% 35%
: 0.033830 0.047655 0.060302 0.071263 0.073450 0.075871 0.079204 0.083330
: 40% 45% 50% 55% 60% 65% 70% 75%
: 0.093038 0.107813 0.134466 0.143095 0.180434 0.334789 0.448111 0.500118
: 80% 85% 90% 95% 100%
: 0.524535 0.583159 0.648065 0.719931 36.567810
Note that the top 25% latencies are more that half a millisecond. Even when there is no event available (zeros on the chart) we can get a latency of more than a millisecond (see zero floating around near the 600th event). To make things worse, this is the typical case I observe when my Macbook has not much going on: just a Terminal and the test program. It gets worse when more applications are running.
I wonder if there is a more efficient way to the get the next (mouse/keyboard) event if available in a MacOS application. Is there a trick I am missing that makes nextEventMatchingMask
calls more efficient?
The source code to run this test and generate a plot like the one above can be found here: https://github.com/laurolins/cocoa_poll_events_latency
(update) Following an idea Dad suggested in the comments, I ran the test without any mouse/keyboard movement. Here are the latency percentiles:
# latency without any keyboard/mouse event in milliseconds
: 0% 5% 10% 15% 20% 25%
: 0.04029500 0.07617675 0.07908070 0.08556100 0.10188940 0.12956500
: 30% 35% 40% 45% 50% 55%
: 0.13832200 0.14023150 0.14802140 0.15003550 0.15045250 0.15088950
: 60% 65% 70% 75% 80% 85%
: 0.15125000 0.15162535 0.15190300 0.15235550 0.15313200 0.15972645
: 90% 95% 100%
: 0.16802250 0.21194335 35.93352900
The idea of waiting .15 milliseconds to find out that no event was generated does not feel right!
(update 2) Just timed all SDL_EventPoll
and MessagePump
calls on gzdoom running on ubuntu and the latency distribution I am getting looks much better:
# 0% 5% 10% 15% 20% 25% 30%
:0.00095100 0.00186075 0.00215500 0.00226400 0.00234180 0.00241500 0.00255900
: 35% 40% 45% 50% 55% 60% 65%
:0.00289495 0.00652280 0.00712510 0.00749900 0.00770290 0.00807660 0.00888870
: 70% 75% 80% 85% 90% 95% 100%
:0.00897200 0.00905000 0.00931060 0.01374225 0.01728800 0.03256290 0.23016000
while on MacOS gzdoom also struggles with the slowness of nextEventMatchingMask
(latency numbers on the hundreds of microseconds). Conclusion: it is not only my test code that is slow to get the next keyboard/mouse event on MacOS.