Skip to content

Update documentation to reflect the recommended way SDL_StorageReady() should be used #12400

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 218 additions & 56 deletions include/SDL3/SDL_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,41 +48,54 @@
* Consider the following example:
*
* ```c
* void ReadGameData(void)
* bool ReadGameData(void)
* {
* extern char** fileNames;
* extern size_t numFiles;
* for (size_t i = 0; i < numFiles; i += 1) {
* FILE *data = fopen(fileNames[i], "rwb");
* if (data == NULL) {
* FILE *file = fopen(fileNames[i], "rwb");
* if (file == NULL) {
* // Something bad happened!
* return false;
* } else {
* // A bunch of stuff happens here
* fclose(data);
* fclose(file);
* }
* }
* return true;
* }
*
* void ReadSave(void)
* typedef struct SaveData_T
* {
* FILE *save = fopen("saves/save0.sav", "rb");
* if (save == NULL) {
* // Data for your saves here
* } SaveData_T;
*
* bool ReadSaveData(SaveData_T *saveData)
* {
* FILE *saveFile = fopen("saves/save0.sav", "rb");
* if (saveFile == NULL) {
* // Something bad happened!
* return false;
* } else {
* // A bunch of stuff happens here
* fclose(save);
* // A bunch of stuff reading saveFile, and putting data into
* // saveData, happens here
* fclose(saveFile);
* }
* return true;
* }
*
* void WriteSave(void)
* bool WriteSaveData(SaveData_T *saveData)
* {
* FILE *save = fopen("saves/save0.sav", "wb");
* if (save == NULL) {
* FILE *saveFile = fopen("saves/save0.sav", "wb");
* if (saveFile == NULL) {
* // Something bad happened!
* return false;
* } else {
* // A bunch of stuff happens here
* fclose(save);
* // A bunch of stuff looking at saveData, and writing to saveFile,
* // happens here
* fclose(saveFile);
* }
* return true;
* }
* ```
*
Expand Down Expand Up @@ -116,92 +129,241 @@
* trip over:
*
* ```c
* void ReadGameData(void)
* bool ReadGameData(SDL_Storage *storage)
* {
* extern char** fileNames;
* extern size_t numFiles;
*
* SDL_Storage *title = SDL_OpenTitleStorage(NULL, 0);
* if (title == NULL) {
* // Something bad happened!
* }
* while (!SDL_StorageReady(title)) {
* SDL_Delay(1);
* }
*
* for (size_t i = 0; i < numFiles; i += 1) {
* void* dst;
* Uint64 dstLen = 0;
*
* if (SDL_GetStorageFileSize(title, fileNames[i], &dstLen) && dstLen > 0) {
* if (SDL_GetStorageFileSize(storage, fileNames[i], &dstLen) && dstLen > 0) {
* dst = SDL_malloc(dstLen);
* if (SDL_ReadStorageFile(title, fileNames[i], dst, dstLen)) {
* if (!dst) {
* // Something bad happened!
* return false;
* }
* if (SDL_ReadStorageFile(storage, fileNames[i], dst, dstLen)) {
* // A bunch of stuff happens here
* } else {
* // Something bad happened!
* return false;
* }
* SDL_free(dst);
* } else {
* // Something bad happened!
* return false;
* }
* }
*
* SDL_CloseStorage(title);
* return true;
* }
*
* void ReadSave(void)
* bool ReadSaveData(SDL_Storage *storage, SaveData_T *saveData)
* {
* SDL_Storage *user = SDL_OpenUserStorage("libsdl", "Storage Example", 0);
* if (user == NULL) {
* // Something bad happened!
* }
* while (!SDL_StorageReady(user)) {
* SDL_Delay(1);
* }
*
* Uint64 saveLen = 0;
* if (SDL_GetStorageFileSize(user, "save0.sav", &saveLen) && saveLen > 0) {
* void* dst = SDL_malloc(saveLen);
* if (SDL_ReadStorageFile(user, "save0.sav", dst, saveLen)) {
* // A bunch of stuff happens here
* Uint64 dstLen = 0;
* if (SDL_GetStorageFileSize(storage, "save0.sav", &dstLen) && dstLen > 0) {
* void* dst = SDL_malloc(dstLen);
* if (!dst) {
* // Something bad happened!
* return false;
* }
* if (SDL_ReadStorageFile(storage, "save0.sav", dst, dstLen)) {
* // A bunch of stuff happens here, putting data into saveData
* } else {
* // Something bad happened!
* SDL_free(dst);
* return false;
* }
* SDL_free(dst);
* } else {
* // Something bad happened!
* return false;
* }
*
* SDL_CloseStorage(user);
* }
*
* void WriteSave(void)
* bool WriteSaveData(SDL_Storage *storage, SaveData_T *saveData)
* {
* SDL_Storage *user = SDL_OpenUserStorage("libsdl", "Storage Example", 0);
* if (user == NULL) {
* void *dst;
* Uint64 dstLen;
*
* // A bunch of stuff happens here looking at saveData, setting up dst and
* // dstLen.
*
* if (!SDL_WriteStorageFile(storage, "save0.sav", dst, dstLen)) {
* // Something bad happened!
* return false;
* } else {
* return true;
* }
* while (!SDL_StorageReady(user)) {
* SDL_Delay(1);
* }
*
* // You probably want to only read or only write save data at a time, not
* // both, so you could use an enum to control that.
* typedef enum SaveDataOp_T
* {
* SAVE_DATA_OP_NONE,
* SAVE_DATA_OP_READ,
* SAVE_DATA_OP_WRITE
* } SaveDataOp_T;
*
* // main() function style
* int main(int argc, char **argv)
* {
* const char * const userOrg = "libsdl";
* const char * const userApp = "Storage Example";
* bool readGameDataNow = false;
* SaveDataOp_T saveDataOp;
* SaveData_T saveData;
* bool quit = false;
* while (!quit) {
* // This part is key: You must keep polling events when waiting on
* // storage to be ready (SDL_StorageReady() returns true). Events
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* // storage to be ready (SDL_StorageReady() returns true). Events
* // storage to be ready (SDL_StorageReady() returns true). Event

* // polling can be required on some platforms for storage to become
* // ready at all!
* SDL_Event event;
* while (SDL_PollEvent(&event)) {
* switch (event.type) {
* case SDL_EVENT_QUIT:
* quit = true;
* break;
* // Other events processed here...
* }
* }
*
* // Game/graphics/sound/etc. logic here, only using data known to
* // already be valid right now. Logic would set readGameDataNow and/or
* // saveDataOp when needed, then could wait for them every logic tick
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's a tick?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A single step of updating the game logic. In classical 60 FPS games, it'd be a single frame.

* // until they indicate the requested storage operation is done;
* // readGameDataNow switching from true last tick to false this tick
* // indicates game data is done reading, saveDataOp switching from
* // SAVE_DATA_OP_READ/SAVE_DATA_OP_WRITE last tick to SAVE_DATA_OP_NONE
* // this tick indicates the save operation is done.
*
* // Remember, these operations won't complete if the storage they use
* // isn't yet ready, so your game logic should be able to wait multiple
* // ticks in case the storage operation(s) didn't complete the same tick
* // the operation(s) were requested!
* if (saveDataOp == SAVE_DATA_OP_READ) {
* SDL_Storage *user = SDL_OpenUserStorage(userOrg, userApp, 0);
* if (!user) {
* // Something bad happened!
* return 1;
* } else if (SDL_StorageReady(user)) {
* if (!ReadSaveData(user, &saveData)) {
* // Something bad happened!
* return 1;
* }
* saveDataOp = SAVE_DATA_OP_NONE;
* }
* SDL_CloseStorage(user);
* } else if (saveDataOp == SAVE_DATA_OP_WRITE) {
* SDL_Storage *user = SDL_OpenUserStorage(userOrg, userApp, 0);
* if (!user) {
* // Something bad happened!
* return 1;
* } else if (SDL_StorageReady(user)) {
* if (!WriteSaveData(user, &saveData)) {
* // Something bad happened!
* return 1;
* }
* saveDataOp = SAVE_DATA_OP_NONE;
* }
* SDL_CloseStorage(user);
* }
* if (readGameDataNow) {
* SDL_Storage *title = SDL_OpenTitleStorage(NULL, 0);
* if (!title) {
* // Something bad happened!
* return 1;
* } else if (SDL_StorageReady(title)) {
* if (!ReadGameData(title)) {
* // Something bad happened!
* return 1;
* }
* readGameDataNow = false;
* }
* SDL_CloseStorage(title);
* }
* }
* return 0;
* }
*
* extern void *saveData; // A bunch of stuff happened here...
* extern Uint64 saveLen;
* if (!SDL_WriteStorageFile(user, "save0.sav", saveData, saveLen)) {
* // Something bad happened!
* // App callbacks style
* // SDL takes care of polling events for you with app callbacks, so you're
* // good there already.
* const char * const userOrg = "libsdl";
* const char * const userApp = "Storage Example";
* bool readGameDataNow = false;
* SaveDataOp_T saveDataOp = SAVE_DATA_OP_NONE;
* SaveData_T saveData;
* SDL_AppResult SDL_AppIterate(void *appstate)
* {
* // Game/graphics/sound/etc. logic here, only using data known to already be
* // valid right now. Logic would set readGameDataNow and/or saveDataOp when
* // needed, then could wait for them every logic tick until they indicate
* // the requested storage operation is done; readGameDataNow switching from
* // true last tick to false this tick indicates game data is done reading,
* // saveDataOp switching from SAVE_DATA_OP_READ/SAVE_DATA_OP_WRITE last tick
* // to SAVE_DATA_OP_NONE this tick indicates the save operation is done.
*
* // Remember, these operations won't complete if the storage they use isn't
* // yet ready, so your game logic should be able to wait multiple ticks in
* // case the storage operation(s) didn't complete the same tick the
* // operation(s) were requested!
* if (saveDataOp == SAVE_DATA_OP_READ) {
* SDL_Storage *user = SDL_OpenUserStorage(userOrg, userApp, 0);
* if (!user) {
* // Something bad happened!
* return SDL_APP_FAILURE;
* } else if (SDL_StorageReady(user)) {
* if (!ReadSaveData(user, &saveData)) {
* // Something bad happened!
* return SDL_APP_FAILURE;
* }
* saveDataOp = SAVE_DATA_OP_NONE;
* }
* SDL_CloseStorage(user);
* } else if (saveDataOp == SAVE_DATA_OP_WRITE) {
* SDL_Storage *user = SDL_OpenUserStorage(userOrg, userApp, 0);
* if (!user) {
* // Something bad happened!
* return SDL_APP_FAILURE;
* } else if (SDL_StorageReady(user)) {
* if (!WriteSaveData(user, &saveData)) {
* // Something bad happened!
* return SDL_APP_FAILURE;
* }
* saveDataOp = SAVE_DATA_OP_NONE;
* }
* SDL_CloseStorage(user);
* }
* if (readGameDataNow) {
* SDL_Storage *title = SDL_OpenTitleStorage(NULL, 0);
* if (!title) {
* // Something bad happened!
* return SDL_APP_FAILURE;
* } else if (SDL_StorageReady(title)) {
* if (!ReadGameData(title)) {
* // Something bad happened!
* return SDL_APP_FAILURE;
* }
* readGameDataNow = false;
* }
* SDL_CloseStorage(title);
* }
*
* SDL_CloseStorage(user);
* return SDL_APP_CONTINUE;
* }
* ```
*
* Note the improvements that SDL_Storage makes:
*
* 1. **What to Access:** This code explicitly reads from a title or user
* storage device based on the context of the function.
* storage device based on the context of the operation.
*
* 2. **How to Access:** This code explicitly uses either a read or write
* function based on the context of the function.
* function based on the context of the operation.
*
* 3. **When to Access:** This code explicitly opens the device when it needs
* to, and closes it when it is finished working with the filesystem.
Expand Down Expand Up @@ -450,7 +612,7 @@ extern SDL_DECLSPEC bool SDLCALL SDL_CloseStorage(SDL_Storage *storage);
*
* This function should be called in regular intervals until it returns true -
* however, it is not recommended to spinwait on this call, as the backend may
* depend on a synchronous message loop. You might instead poll this in your
* depend on a synchronous message loop. You should instead poll this in your
* game's main loop while processing events and drawing a loading screen.
*
* \param storage a storage container to query.
Expand Down
Loading