'How do I use SDL_PeepEvents properly in multiple event loops?
I would like to do a simple thing — in the unit with the logic of handling the window, I want to update the window (the first event loop), and then in another unit with the logic of e.g. keyboard handling, update its state (second event loop). Window events must be put back in the SDL queue to be visible when the keyboard is updated (to be able to check if the window is in focus or not).
In short, there is one event queue (internal to SDL), but there are many loops that read events. If an event needs to be visible in several loops, it must return to the queue after handling it. To make this possible, each loop should iterate over the contents of the queue in such a way as not to process the events that was pushed back to the queue after handling.
For this, I am using SDL_PeepEvents
which returns the number of queued events and iterates through the for
loop this many times. The test program code looks like this:
uses
SDL2;
procedure UpdateWindow();
var
Event: TSDL_Event;
Index: Integer;
begin
for Index := 0 to SDL_PeepEvents(nil, -1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) - 1 do
begin
SDL_PollEvent(@Event);
if Event.Type_ = SDL_WINDOWEVENT then
case Event.Window.Event of
SDL_WINDOWEVENT_FOCUS_GAINED: WriteLn('focus gain - window');
SDL_WINDOWEVENT_FOCUS_LOST: WriteLn('focus lost - window');
end;
// put all events back in the queue to be seen in other event loops
SDL_PushEvent(@Event);
end;
end;
procedure UpdateKeyboard();
var
Event: TSDL_Event;
Index: Integer;
begin
for Index := 0 to SDL_PeepEvents(nil, -1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) - 1 do
begin
SDL_PollEvent(@Event);
// reading window events a second time
if Event.Type_ = SDL_WINDOWEVENT then
case Event.Window.Event of
SDL_WINDOWEVENT_FOCUS_GAINED: WriteLn('focus gain - keyboard');
SDL_WINDOWEVENT_FOCUS_LOST: WriteLn('focus lost - keyboard');
end;
SDL_PushEvent(@Event);
end;
end;
var
Window: PSDL_Window;
Done: Boolean = False;
begin
SDL_Init(SDL_INIT_EVERYTHING);
Window := SDL_CreateWindow('Events test', SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
while not Done do
begin
SDL_PumpEvents();
UpdateWindow();
UpdateKeyboard();
// clear events queue
SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);
SDL_Delay(20);
end;
SDL_DestroyWindow(Window);
SDL_Quit();
end.
The above code works, but only sometimes. When the program is started and the window appears, sometimes it says that the window and keyboard are in focus, and sometimes it's just that the window has focus only. This means that the SDL_WINDOWEVENT_FOCUS_GAINED
event was handled in the first loop but not seen in the second loop (even though it was added back to the queue).
With the console window and the SDL window visible, I click alternately to both, activating and deactivating the SDL window. Each time the SDL window is activated, two lines should be added to the console (one for the event loop) and the same when deactivating the window. Unfortunately, the content of the console when clicking looks like this:
focus gain - window
focus gain - keyboard
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus gain - keyboard
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
focus gain - window
focus lost - window
focus lost - keyboard
{...}
As you can see, sometimes when activating a window, the second loop doesn't see the SDL_WINDOWEVENT_FOCUS_GAINED
event, so it doesn't add the focus gained - keyboard
line to the console. This event is lost somewhere even though every event in the first loop is queued back using SDL_PushEvent
.
Question: how to properly handle SDL events in multiple loops and how to put handled events back in the queue so that they are visible to multiple loops?
Lazarus 2.2.0, FPC 3.2.2, SDL2-for-Pascal headers and SDL 2.0.22.0 — all 64-bit.
Solution 1:[1]
If possible, don't do that at all. Best solution is to fetch events only once. If you want to process events multiple times, nothing prevents you from fetching all events you're interested in into single array and iterating this array as many times as you want. You can fetch all events into big array with single SDL_PeepEvents(SDL_GETEVENT)
call.
The problem you have is based on two factors: SDL_PollEvent
's return value is 0 when queue is drained; but nowhere in documentation it is stated that SDL_PushEvent
puts event into the same processing frame - i.e. PollEvent
may indicate queue is drained and return your newly pushed events on next frame. If that happens, in your second loop you process one event incorrectly (as PollEvent
said 0 and no data is filled into Event
structure), and skip one last event.
But, leaving higher level logic as it is, you have two options:
Use
SDL_PeepEvent
to fetch event, notSDL_PollEvent
. I.e. replaceSDL_PollEvent(@Event)
withSDL_PeepEvent(@Event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT)
.Check return value of
SDL_PollEvent
and if it returns 0 - you'll have to call it again, as it means no event is popped from queue. This solution is prone to race conditions and you can't guarantee it wouldn't callSDL_PumpEvents
under the hood, and your lastSDL_FlushEvents
may kill some unprocessed events.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | keltar |