Correct way to shutdown SDL2 from code?

What is the correct way to shutdown SDL2 from code? I currently do this:

void Platform::quit()
{
	SDL_Event event = { SDL_QUIT };
	SDL_PushEvent(&event);
}

The above works on the macOS version of my application, but the iOS version never returns from SDL_UIKitRunApp(). Is there another function I should call to initiate a shutdown?

Woops, I hit the wrong button.

Your prompt makes me interested in whether the problem is that IOS is not packing their structures in the same way as MacOS.
(I’m a Linux nerd so that is really just a guess)
If that is the case, then you should have success if you specifically fill in the structure properties rather than trying to use an initialization list:

Edit: Not a packing issue. It’s never lupus.

#include <SDL2/SDL.h>

void pushQuit()
{
	SDL_Event ev;
	ev.type = SDL_QUIT;
	SDL_PushEvent(&ev);
}

int main()
{
	SDL_Init(SDL_INIT_EVERYTHING);
	SDL_Window * win = SDL_CreateWindow("title", 0,0,800, 800, SDL_WINDOW_SHOWN);
	SDL_Renderer * screen = SDL_CreateRenderer(win, -1, 0);

	bool run = true;
	while(run)
	{
		SDL_Event ev;
		while(SDL_PollEvent(&ev))
		{
			switch(ev.type)
			{
				case SDL_QUIT:
					SDL_Log("Quit event detected");
					run = false;
					break;
			}
		}
		SDL_RenderClear(screen);
		// drawing functions go here...

		SDL_RenderPresent(screen);
		SDL_Delay(30);
		int time = SDL_GetTicks();
		if(time > 5000)
		{
			pushQuit();
		}
	}
	SDL_DestroyWindow(win);
	SDL_Quit();

}

… also, check out the remarks about SDL_Quit lower in this page, it is up to you to catch the event and process it correctly (see the example code I provided). On some OS’s it sounds like the OS itself will close down the program automatically on a quit event, but this is not the case on many Operating Systems.

It’s not a structure packing problem. Pasting the contents of your pushQuit() into my Platform::quit() method doesn’t affect my issue (the resulting event data is exactly the same).

I have installed an event filter to watch for SDL_APP_TERMINATING on iOS, but that event is never sent. SDL processes the SDL_QUIT event but does not propagate the termination signal to the underlying UIKit application, so we don’t break away from UIKit’s main loop.

Ah… the correct answer is that an iOS application shouldn’t terminate itself! Discussed extensively in this Stackoverflow question: ios - Proper way to exit iPhone application? - Stack Overflow

I only need to quit from code when my game has encountered an unrecoverable error (which, ideally, never happens…), so I will just rewrite my error handling on iOS to show an undismissable error box instead.

IIRC the SDL_APP_TERMINATING event is for when the OS is going to close your app and you’ve got a very brief amount of time to save whatever you can before that happens.

1 Like

Correct. When I referred to SDL_APP_TERMINATING earlier, I meant that I did not receive the event after pushing SDL_QUIT… which is expected, because on iOS only the OS may decide when your app terminates.

Thanks for your input, my issue is resolved!

Just FYI, you can absolutely stop your own app in iOS, such as by calling exit(). It just isn’t a good idea.

Something to be aware of, the thing with the SDL_APP_* events is that you should handle them in an event filter instead of your regular game loop. While SDL tries to present the iOS app notifications it gets as regular event queue events, the reality is that for stuff like low memory warnings, entering/exiting the background, etc., iOS calls a callback in your app (which SDL implements) and iOS expects that whatever your app needs to do to respond is handled by the time the callback returns.

An event filter essentially allows you to “catch” events before they get put on the event queue, which on iOS means getting the SDL_APP_* events before the callback returns, so you can act on them immediately instead of the next iteration of your game loop. For SDL_APP_LOWMEMORY, SDL_APP_TERMINATING, and SDL_APP_DIDENTERBACKGROUND, your app will be suspended in the background and not running when you get these events.

IIRC, the canonical example is something like:

/* If this returns 0 that means it "eats" the event and it won't get added to the main event queue,
   return 1 to add the event to the main event queue. */
int handle_app_events(void *dontcare, SDL_Event *event)
{
    switch(event->type) {
    case SDL_APP_TERMINATING:
        /* We are getting killed by the OS. Shut down anything that needs any special shutdown
           code before returning from this. (you don't need to bother with SDL_Quit()) */
        return 0;
    case SDL_APP_LOWMEMORY:
        /* You know what to do. Do it before returning. */
        return 0;
    case SDL_APP_WILLENTERBACKGROUND:
        /* Stop the game loop, we're going to be suspended */
        return 0;
    case SDL_APP_DIDENTERBACKGROUND:
        /* Save state etc. We only get this after the app has already entered the background, so without
           an event filter our app's main game loop wouldn't see this event until after we came back to the
           foreground. */
        return 0;
    case SDL_APP_WILLENTERFOREGROUND:
        /* Restore state etc. */
        return 0;
    case SDL_APP_DIDENTERFOREGROUND:
        /* Resume game loop */
        return 0;
    default:
        /* Something else happened, let the main event queue have it */
        return 1;
    }
}

int main(int argc, char **argv)
{
    /* Somewhere before your main game loop */
    SDL_SetEventFilter(handle_app_events, NULL);
1 Like