SDL_PeekEvents/SDL_PollEvent weird interaction

SDL 2.28.5

For each keypress (and mouse action too) I get the following sequence:

  1. SDL_PeepEvents( &event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT ) returns true, event is a proper event that happened.
  2. SDL_PollEvent returns FALSE
  3. SDL_PollEvent ran immedately again behaves as expected (returns the key event and true).

The application is single threaded, no event filters are used. SDL_PumpEvents is called before SDL_PeepEvents. I’ve just ported the same code from SDL 1.2 where it worked as I would expect it to work.

What could be causing this?

Did you mean SDL_PeepEvents?
Could be a window focus problem, but it’s strange that the peep returns the right event. Could you share a minimal reproducible example?

Yes, PeepEvents, sorry for the typo!

I could try to make a minimal reproducible example, but this is FreePascal, custom SDL bindings and a fairly complex custom engine loop - it would take a while to decouple all, and in the end I feel I’d have a properly working code whereas still not having any clue where the error in the engine could be :/.

Even switching to “official” bindings would take at least a day of work due to how things are set up, and it would be work wasted, as the official version doesn’t do what I need it to do (run-time loading).

I hoped someone had experience with a situation similar to this and has some insight where to look for potential problems.

I’m sorry but I can’t replicate this problem, tried to reproduce the behavior you described in C:

#include <SDL.h>
#include <stdio.h>

void print_status(int rc, const SDL_Event *event) {
	printf("return: %d, type: %u\n", rc, event->type);
}

int filter(void *userdata, SDL_Event *event) {
	switch (event->type) {
		case SDL_KEYDOWN:
		case SDL_KEYUP:
		case SDL_MOUSEMOTION:
		case SDL_MOUSEBUTTONDOWN:
		case SDL_MOUSEBUTTONUP:
		case SDL_MOUSEWHEEL:
			return 1;
		default:
			return 0;
	}
}

int main(int argc, char **argv) {
	SDL_Init(SDL_INIT_EVERYTHING);

	SDL_SetEventFilter(&filter, NULL);

	SDL_Window *window = SDL_CreateWindow("test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, 0);

	SDL_Event event;
	int rc = 0;

	for (;;) {
		SDL_PumpEvents();
		rc = SDL_PeepEvents(&event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
		
		if (rc != 0) {
			puts("");

			printf("PEEP: ");
			print_status(rc, &event);

			rc = SDL_PollEvent(&event);
			printf("POLL: ");
			print_status(rc, &event);
		}

		SDL_Delay(100);
	}

	SDL_DestroyWindow(window);
	SDL_Quit();

	return 0;
}

For me the output of program is (pressed the spacebar key):

$ ./test.exe 

PEEP: return: 1, type: 768
POLL: return: 1, type: 768

PEEP: return: 1, type: 769
POLL: return: 1, type: 769

This actually helps a bit. The program as written works perfectly fine for me as well. But when I turn off filtering, weird things happen:


PEEP: return: 1, type: 768
POLL: return: 0, type: 32512

PEEP: return: 1, type: 768
POLL: return: 1, type: 768

When setting up a test what gets passed to Filter, we learn that a 32512 event is indeed passed through. Unfortunately this event is undocumented anywhere and isn’t picked up by anything.

Is this normal behaviour? Is it required to have a filter function that filters out unknown events?

With return code 0 (nothing polled) sure it’s just a garbage value.

No, I just didn’t checked return of poll explicitly in code to be able to see the issue.

32512 (0x7F00) is the code for the SDL_POLLSENTINEL event type.

2 Likes

Isn’t a garbage value because the same value gets passed to the filter function too!

In particular this is the function I tested to catch that value:

int filter(void *userdata, SDL_Event *event) {
	switch (event->type) {
		case SDL_KEYDOWN: 
			return 1;
		case SDL_KEYUP:
		case SDL_MOUSEMOTION:
		case SDL_MOUSEBUTTONDOWN:
		case SDL_MOUSEBUTTONUP:
		case SDL_MOUSEWHEEL:
			return 0;
	}
	printf("caught passthrough: %u\n", event->type);
	return 1;
}

(though in Pascal)

Yeah I’ve mistaken this one, check the Peter’s post!

Yeah, now this makes sense! This is a major difference between SDL 1.2 and 2.0 though.

What is the official suggested approach? Filtering out all the unknown events or accepting that HasEvent can result in a failed PollEvent?

You just shouldn’t touch the event if the return value is 0!
Your polling should look like this:

while(SDL_PollEvent(&event)) {
  // touch the event
}

Anyways I understood what’s causing your problem. Basically, it’s not a problem at all! Events are separated by SDL_POLLSENTINEL’s to create an event “groups” that you have to process in your event loop. Your event is in the next “group”, so you didn’t get in your first poll sequence. In other hand SDL_PeepEvents will show you this event no matter what.

Can you try to replace poll by peep with SDL_GETEVENT? Does it solve your problem?

Yeah, ask yourself if you really need SDL_PeepEvents. Usually SDL_PollEvent is all that you need.

Note that it’s possible to pass NULL as argument to SDL_PollEvent to check if there is another event without removing it from the queue. That doesn’t allow you to see the type of the next event but it’ll probably behave more consistent with your other calls to SDL_PollEvent.

If you want it to work like in SDL 1.2 and ignore the sentinel I think you just have to call SDL_PollEvent one extra time when it returns 0 to make sure there truly isn’t any events left.

while (SDL_PollEvent(&event) || SDL_PollEvent(&event)) {
  // handle event
}

I haven’t actually tested this but I don’t see why it wouldn’t work. Whether it’s a good idea or not, I don’t know. The sentinel must have been added for a reason but I don’t know the details…

Thanks everyone for the help. Instead of the double Poll (which works BTW!) I went with filtering out any event that isn’t managed through a filter function and this works well too.

I need EventPending in this particular project, as parts of the game render in a loop until something happens, but have no idea what to do with the actual event itself - would require too much refactoring for barely any gain.

It seems like one of the reasons for the sentinel is to avoid having to call SDL_PumpEvents internally each time SDL_PollEvent is called. See https://github.com/libsdl-org/SDL/pull/4794. I suspect filtering out the sentinel will essentially undo this optimization.