| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- /*
- * This example code creates an SDL window and renderer, and waits for the user
- * to click on the window. By default, the window color is blue; When storage
- * succeeds, the window turns green, if it fails the window turns red and the
- * error message is logged. Left clicking will save a game, all other clicks
- * will load a game.
- *
- * The primary goal is to show how to handle save data without blocking the main
- * thread, while also making sure to keep the storage handle open for as little
- * as possible; many platforms do not allow keeping user storage open for long
- * periods of time so you _must_ be sure to only have user storage open when you
- * are absolutely 100% ready to interact with the storage handle.
- *
- * This code is public domain. Feel free to use it for any purpose!
- */
- #define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */
- #include <SDL3/SDL.h>
- #include <SDL3/SDL_main.h>
- /* This is the list of steps that will occur as part of saving or loading a game */
- typedef enum savestate_t
- {
- SAVE_STATE_UNSTARTED, /* blue */
- SAVE_STATE_PROCESSING_GAME_WORLD, /* yellow */
- SAVE_STATE_PREPARING_STORAGE, /* cyan */
- SAVE_STATE_PROCESSING_STORAGE_FILE, /* magenta */
- SAVE_STATE_FINAL_CHECK /* green if success, red if failed */
- } savestate_t;
- SDL_AtomicInt current_save_state;
- /* During the final check, this indicates success or failure */
- static int save_result = -1;
- /* This is the thread that handles the majority of save operations */
- static SDL_Thread *save_thread = NULL;
- /* Opening storage is itself an async operation, so the thread will have some waiting to do */
- static SDL_Semaphore *storage_ready = NULL;
- /* This is the handle for the user's filesystem */
- static SDL_Storage *save_storage = NULL;
- #define SAVE_FILE_NAME "save.sav"
- /* This function pretends to serialize a fictional game world, then starts
- * opening the filesystem to write the serialized data
- */
- static int SDLCALL WriteSaveData(void *data)
- {
- Uint64 game_world; /* to keep things simple, let's just pretend that an entire game fits in 64-bits */
- bool write_result;
-
- SDL_SetAtomicInt(¤t_save_state, SAVE_STATE_PROCESSING_GAME_WORLD);
- /* again, let's just pretend that an entire game fits in 64-bits */
- game_world = SDL_GetPerformanceCounter();
- /* now that save data is ready to go, we can start opening the filesystem */
- save_storage = SDL_OpenUserStorage("libsdl", "User Storage Example", 0);
- if (save_storage == NULL) {
- SDL_SetAtomicInt(¤t_save_state, SAVE_STATE_FINAL_CHECK);
- return -1;
- }
- SDL_SetAtomicInt(¤t_save_state, SAVE_STATE_PREPARING_STORAGE);
- /* the main thread will eventually signal to us that storage is ready */
- SDL_WaitSemaphore(storage_ready);
- /* the save data can now be written to the storage device */
- write_result = SDL_WriteStorageFile(save_storage, SAVE_FILE_NAME, &game_world, sizeof(game_world));
- /* regardless of what happened above, we've reached the end of the routine */
- SDL_CloseStorage(save_storage);
- SDL_SetAtomicInt(¤t_save_state, SAVE_STATE_FINAL_CHECK);
- if (!write_result) {
- return -1;
- }
- return 0;
- }
- /* This function opens the filesystem to read a save file, then deserializes the
- * data into fictional game world data
- */
- static int SDLCALL ReadSaveData(void *data)
- {
- Uint64 game_world; /* to keep things simple, let's just pretend that an entire game fits in 64-bits */
- Uint64 save_len;
- bool read_result;
- /* start by preparing the filesystem for reading */
- save_storage = SDL_OpenUserStorage("libsdl", "User Storage Example", 0);
- if (save_storage == NULL) {
- SDL_SetAtomicInt(¤t_save_state, SAVE_STATE_FINAL_CHECK);
- return -1;
- }
- SDL_SetAtomicInt(¤t_save_state, SAVE_STATE_PREPARING_STORAGE);
- /* the main thread will eventually signal to us that storage is ready */
- SDL_WaitSemaphore(storage_ready);
- read_result = SDL_GetStorageFileSize(save_storage, SAVE_FILE_NAME, &save_len);
- if (!read_result) {
- SDL_CloseStorage(save_storage);
- SDL_SetAtomicInt(¤t_save_state, SAVE_STATE_FINAL_CHECK);
- SDL_Log("Save data was not found");
- return -1;
- } else if (save_len != sizeof(game_world)) {
- SDL_CloseStorage(save_storage);
- SDL_SetAtomicInt(¤t_save_state, SAVE_STATE_FINAL_CHECK);
- SDL_Log("Save data size is incorrect, was the file corrupted?");
- return -1;
- }
- /* once we've read the file in, the storage handle is no longer needed */
- read_result = SDL_ReadStorageFile(save_storage, SAVE_FILE_NAME, &game_world, sizeof(game_world));
- SDL_CloseStorage(save_storage);
- SDL_SetAtomicInt(¤t_save_state, SAVE_STATE_PROCESSING_GAME_WORLD);
- if (read_result) {
- /* again, let's just pretend that an entire game fits in 64-bits */
- SDL_Log("Game World loaded, value was %" SDL_PRIu64, game_world);
- }
- /* regardless of what happened above, we've reached the end of the routine */
- SDL_SetAtomicInt(¤t_save_state, SAVE_STATE_FINAL_CHECK);
- if (!read_result) {
- return -1;
- }
- return 0;
- }
- /* We will use this renderer to draw into this window every frame. */
- static SDL_Window *window = NULL;
- static SDL_Renderer *renderer = NULL;
- /* This function runs once at startup. */
- SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
- {
- SDL_SetAppMetadata("User Storage Example", "1.0", "com.example.storage-user");
- if (!SDL_Init(SDL_INIT_VIDEO)) {
- SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
- return SDL_APP_FAILURE;
- }
- if (!SDL_CreateWindowAndRenderer("examples/storage/user", 640, 480, 0, &window, &renderer)) {
- SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
- return SDL_APP_FAILURE;
- }
- /* initialize the default save state */
- SDL_SetAtomicInt(¤t_save_state, SAVE_STATE_UNSTARTED);
- storage_ready = SDL_CreateSemaphore(0);
- return SDL_APP_CONTINUE; /* carry on with the program! */
- }
- /* This function runs when a new event (mouse input, keypresses, etc) occurs. */
- SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
- {
- if (event->type == SDL_EVENT_QUIT) {
- return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */
- } else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
- if (save_thread != NULL) {
- SDL_Log("Ignoring interaction, save/load is in progress");
- } else {
- /* once the thread starts, it will update this to the first "real" state */
- SDL_SetAtomicInt(¤t_save_state, SAVE_STATE_UNSTARTED);
- if (event->button.button == 1) {
- save_thread = SDL_CreateThread(WriteSaveData, "Save Write Thread", NULL);
- } else {
- save_thread = SDL_CreateThread(ReadSaveData, "Save Read Thread", NULL);
- }
- }
- }
- return SDL_APP_CONTINUE; /* carry on with the program! */
- }
- /* This function runs once per frame, and is the heart of the program. */
- SDL_AppResult SDL_AppIterate(void *appstate)
- {
- float red, green, blue;
- int save_state = SDL_GetAtomicInt(¤t_save_state);
- /* the main thread does not have to do much other than help the thread wait
- * for storage to be ready and read the result when the thread is finished
- */
- if (save_state == SAVE_STATE_PREPARING_STORAGE) {
- if (SDL_StorageReady(save_storage)) {
- SDL_SetAtomicInt(¤t_save_state, SAVE_STATE_PROCESSING_STORAGE_FILE);
- SDL_SignalSemaphore(storage_ready);
- }
- } else if (save_state == SAVE_STATE_FINAL_CHECK) {
- if (save_thread != NULL) {
- SDL_WaitThread(save_thread, &save_result);
- save_thread = NULL;
- if (save_result == 0) {
- SDL_Log("Save/Load complete!");
- } else {
- SDL_Log("Save/Load failed: %s", SDL_GetError());
- }
- }
- }
- /* set the draw color based on the state of the save system */
- switch (save_state)
- {
- case SAVE_STATE_UNSTARTED:
- red = 0.0f;
- green = 0.0f;
- blue = 1.0f;
- break;
- case SAVE_STATE_PROCESSING_GAME_WORLD:
- red = 1.0f;
- green = 1.0f;
- blue = 0.0f;
- break;
- case SAVE_STATE_PREPARING_STORAGE:
- red = 0.0f;
- green = 1.0f;
- blue = 1.0f;
- break;
- case SAVE_STATE_PROCESSING_STORAGE_FILE:
- red = 1.0f;
- green = 0.0f;
- blue = 1.0f;
- break;
- case SAVE_STATE_FINAL_CHECK:
- if (save_result == 0) {
- red = 0.0f;
- green = 1.0f;
- } else {
- red = 1.0f;
- green = 0.0f;
- }
- blue = 0.0f;
- break;
- default:
- red = 0.0f;
- green = 0.0f;
- blue = 0.0f;
- SDL_assert(!"Unrecognized save state");
- break;
- }
- SDL_SetRenderDrawColorFloat(renderer, red, green, blue, SDL_ALPHA_OPAQUE_FLOAT); /* new color, full alpha. */
- /* clear the window to the draw color. */
- SDL_RenderClear(renderer);
- /* put the newly-cleared rendering on the screen. */
- SDL_RenderPresent(renderer);
- return SDL_APP_CONTINUE; /* carry on with the program! */
- }
- /* This function runs once at shutdown. */
- void SDL_AppQuit(void *appstate, SDL_AppResult result)
- {
- /* If saving/loading is still in progress, force the thread not to wait */
- SDL_SignalSemaphore(storage_ready);
- SDL_WaitThread(save_thread, NULL);
- SDL_DestroySemaphore(storage_ready);
- /* SDL will clean up the window/renderer for us. */
- }
|