user.c 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /*
  2. * This example code creates an SDL window and renderer, and waits for the user
  3. * to click on the window. By default, the window color is blue; When storage
  4. * succeeds, the window turns green, if it fails the window turns red and the
  5. * error message is logged. Left clicking will save a game, all other clicks
  6. * will load a game.
  7. *
  8. * The primary goal is to show how to handle save data without blocking the main
  9. * thread, while also making sure to keep the storage handle open for as little
  10. * as possible; many platforms do not allow keeping user storage open for long
  11. * periods of time so you _must_ be sure to only have user storage open when you
  12. * are absolutely 100% ready to interact with the storage handle.
  13. *
  14. * This code is public domain. Feel free to use it for any purpose!
  15. */
  16. #define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */
  17. #include <SDL3/SDL.h>
  18. #include <SDL3/SDL_main.h>
  19. /* This is the list of steps that will occur as part of saving or loading a game */
  20. typedef enum savestate_t
  21. {
  22. SAVE_STATE_UNSTARTED, /* blue */
  23. SAVE_STATE_PROCESSING_GAME_WORLD, /* yellow */
  24. SAVE_STATE_PREPARING_STORAGE, /* cyan */
  25. SAVE_STATE_PROCESSING_STORAGE_FILE, /* magenta */
  26. SAVE_STATE_FINAL_CHECK /* green if success, red if failed */
  27. } savestate_t;
  28. SDL_AtomicInt current_save_state;
  29. /* During the final check, this indicates success or failure */
  30. static int save_result = -1;
  31. /* This is the thread that handles the majority of save operations */
  32. static SDL_Thread *save_thread = NULL;
  33. /* Opening storage is itself an async operation, so the thread will have some waiting to do */
  34. static SDL_Semaphore *storage_ready = NULL;
  35. /* This is the handle for the user's filesystem */
  36. static SDL_Storage *save_storage = NULL;
  37. #define SAVE_FILE_NAME "save.sav"
  38. /* This function pretends to serialize a fictional game world, then starts
  39. * opening the filesystem to write the serialized data
  40. */
  41. static int SDLCALL WriteSaveData(void *data)
  42. {
  43. Uint64 game_world; /* to keep things simple, let's just pretend that an entire game fits in 64-bits */
  44. bool write_result;
  45. SDL_SetAtomicInt(&current_save_state, SAVE_STATE_PROCESSING_GAME_WORLD);
  46. /* again, let's just pretend that an entire game fits in 64-bits */
  47. game_world = SDL_GetPerformanceCounter();
  48. /* now that save data is ready to go, we can start opening the filesystem */
  49. save_storage = SDL_OpenUserStorage("libsdl", "User Storage Example", 0);
  50. if (save_storage == NULL) {
  51. SDL_SetAtomicInt(&current_save_state, SAVE_STATE_FINAL_CHECK);
  52. return -1;
  53. }
  54. SDL_SetAtomicInt(&current_save_state, SAVE_STATE_PREPARING_STORAGE);
  55. /* the main thread will eventually signal to us that storage is ready */
  56. SDL_WaitSemaphore(storage_ready);
  57. /* the save data can now be written to the storage device */
  58. write_result = SDL_WriteStorageFile(save_storage, SAVE_FILE_NAME, &game_world, sizeof(game_world));
  59. /* regardless of what happened above, we've reached the end of the routine */
  60. SDL_CloseStorage(save_storage);
  61. SDL_SetAtomicInt(&current_save_state, SAVE_STATE_FINAL_CHECK);
  62. if (!write_result) {
  63. return -1;
  64. }
  65. return 0;
  66. }
  67. /* This function opens the filesystem to read a save file, then deserializes the
  68. * data into fictional game world data
  69. */
  70. static int SDLCALL ReadSaveData(void *data)
  71. {
  72. Uint64 game_world; /* to keep things simple, let's just pretend that an entire game fits in 64-bits */
  73. Uint64 save_len;
  74. bool read_result;
  75. /* start by preparing the filesystem for reading */
  76. save_storage = SDL_OpenUserStorage("libsdl", "User Storage Example", 0);
  77. if (save_storage == NULL) {
  78. SDL_SetAtomicInt(&current_save_state, SAVE_STATE_FINAL_CHECK);
  79. return -1;
  80. }
  81. SDL_SetAtomicInt(&current_save_state, SAVE_STATE_PREPARING_STORAGE);
  82. /* the main thread will eventually signal to us that storage is ready */
  83. SDL_WaitSemaphore(storage_ready);
  84. read_result = SDL_GetStorageFileSize(save_storage, SAVE_FILE_NAME, &save_len);
  85. if (!read_result) {
  86. SDL_CloseStorage(save_storage);
  87. SDL_SetAtomicInt(&current_save_state, SAVE_STATE_FINAL_CHECK);
  88. SDL_Log("Save data was not found");
  89. return -1;
  90. } else if (save_len != sizeof(game_world)) {
  91. SDL_CloseStorage(save_storage);
  92. SDL_SetAtomicInt(&current_save_state, SAVE_STATE_FINAL_CHECK);
  93. SDL_Log("Save data size is incorrect, was the file corrupted?");
  94. return -1;
  95. }
  96. /* once we've read the file in, the storage handle is no longer needed */
  97. read_result = SDL_ReadStorageFile(save_storage, SAVE_FILE_NAME, &game_world, sizeof(game_world));
  98. SDL_CloseStorage(save_storage);
  99. SDL_SetAtomicInt(&current_save_state, SAVE_STATE_PROCESSING_GAME_WORLD);
  100. if (read_result) {
  101. /* again, let's just pretend that an entire game fits in 64-bits */
  102. SDL_Log("Game World loaded, value was %" SDL_PRIu64, game_world);
  103. }
  104. /* regardless of what happened above, we've reached the end of the routine */
  105. SDL_SetAtomicInt(&current_save_state, SAVE_STATE_FINAL_CHECK);
  106. if (!read_result) {
  107. return -1;
  108. }
  109. return 0;
  110. }
  111. /* We will use this renderer to draw into this window every frame. */
  112. static SDL_Window *window = NULL;
  113. static SDL_Renderer *renderer = NULL;
  114. /* This function runs once at startup. */
  115. SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
  116. {
  117. SDL_SetAppMetadata("User Storage Example", "1.0", "com.example.storage-user");
  118. if (!SDL_Init(SDL_INIT_VIDEO)) {
  119. SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
  120. return SDL_APP_FAILURE;
  121. }
  122. if (!SDL_CreateWindowAndRenderer("examples/storage/user", 640, 480, 0, &window, &renderer)) {
  123. SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
  124. return SDL_APP_FAILURE;
  125. }
  126. /* initialize the default save state */
  127. SDL_SetAtomicInt(&current_save_state, SAVE_STATE_UNSTARTED);
  128. storage_ready = SDL_CreateSemaphore(0);
  129. return SDL_APP_CONTINUE; /* carry on with the program! */
  130. }
  131. /* This function runs when a new event (mouse input, keypresses, etc) occurs. */
  132. SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
  133. {
  134. if (event->type == SDL_EVENT_QUIT) {
  135. return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */
  136. } else if (event->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
  137. if (save_thread != NULL) {
  138. SDL_Log("Ignoring interaction, save/load is in progress");
  139. } else {
  140. /* once the thread starts, it will update this to the first "real" state */
  141. SDL_SetAtomicInt(&current_save_state, SAVE_STATE_UNSTARTED);
  142. if (event->button.button == 1) {
  143. save_thread = SDL_CreateThread(WriteSaveData, "Save Write Thread", NULL);
  144. } else {
  145. save_thread = SDL_CreateThread(ReadSaveData, "Save Read Thread", NULL);
  146. }
  147. }
  148. }
  149. return SDL_APP_CONTINUE; /* carry on with the program! */
  150. }
  151. /* This function runs once per frame, and is the heart of the program. */
  152. SDL_AppResult SDL_AppIterate(void *appstate)
  153. {
  154. float red, green, blue;
  155. int save_state = SDL_GetAtomicInt(&current_save_state);
  156. /* the main thread does not have to do much other than help the thread wait
  157. * for storage to be ready and read the result when the thread is finished
  158. */
  159. if (save_state == SAVE_STATE_PREPARING_STORAGE) {
  160. if (SDL_StorageReady(save_storage)) {
  161. SDL_SetAtomicInt(&current_save_state, SAVE_STATE_PROCESSING_STORAGE_FILE);
  162. SDL_SignalSemaphore(storage_ready);
  163. }
  164. } else if (save_state == SAVE_STATE_FINAL_CHECK) {
  165. if (save_thread != NULL) {
  166. SDL_WaitThread(save_thread, &save_result);
  167. save_thread = NULL;
  168. if (save_result == 0) {
  169. SDL_Log("Save/Load complete!");
  170. } else {
  171. SDL_Log("Save/Load failed: %s", SDL_GetError());
  172. }
  173. }
  174. }
  175. /* set the draw color based on the state of the save system */
  176. switch (save_state)
  177. {
  178. case SAVE_STATE_UNSTARTED:
  179. red = 0.0f;
  180. green = 0.0f;
  181. blue = 1.0f;
  182. break;
  183. case SAVE_STATE_PROCESSING_GAME_WORLD:
  184. red = 1.0f;
  185. green = 1.0f;
  186. blue = 0.0f;
  187. break;
  188. case SAVE_STATE_PREPARING_STORAGE:
  189. red = 0.0f;
  190. green = 1.0f;
  191. blue = 1.0f;
  192. break;
  193. case SAVE_STATE_PROCESSING_STORAGE_FILE:
  194. red = 1.0f;
  195. green = 0.0f;
  196. blue = 1.0f;
  197. break;
  198. case SAVE_STATE_FINAL_CHECK:
  199. if (save_result == 0) {
  200. red = 0.0f;
  201. green = 1.0f;
  202. } else {
  203. red = 1.0f;
  204. green = 0.0f;
  205. }
  206. blue = 0.0f;
  207. break;
  208. default:
  209. red = 0.0f;
  210. green = 0.0f;
  211. blue = 0.0f;
  212. SDL_assert(!"Unrecognized save state");
  213. break;
  214. }
  215. SDL_SetRenderDrawColorFloat(renderer, red, green, blue, SDL_ALPHA_OPAQUE_FLOAT); /* new color, full alpha. */
  216. /* clear the window to the draw color. */
  217. SDL_RenderClear(renderer);
  218. /* put the newly-cleared rendering on the screen. */
  219. SDL_RenderPresent(renderer);
  220. return SDL_APP_CONTINUE; /* carry on with the program! */
  221. }
  222. /* This function runs once at shutdown. */
  223. void SDL_AppQuit(void *appstate, SDL_AppResult result)
  224. {
  225. /* If saving/loading is still in progress, force the thread not to wait */
  226. SDL_SignalSemaphore(storage_ready);
  227. SDL_WaitThread(save_thread, NULL);
  228. SDL_DestroySemaphore(storage_ready);
  229. /* SDL will clean up the window/renderer for us. */
  230. }