SDL: Sync SDL3 wiki -> header (18fc1)

From 18fc13c20dc16a1dd1f3fd05df399925751b79d5 Mon Sep 17 00:00:00 2001
From: SDL Wiki Bot <[EMAIL REDACTED]>
Date: Tue, 31 Dec 2024 18:00:37 +0000
Subject: [PATCH] Sync SDL3 wiki -> header

[ci skip]
---
 include/SDL3/SDL_storage.h | 193 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 192 insertions(+), 1 deletion(-)

diff --git a/include/SDL3/SDL_storage.h b/include/SDL3/SDL_storage.h
index cdd31b471c5d4..5f0e26e4d91f5 100644
--- a/include/SDL3/SDL_storage.h
+++ b/include/SDL3/SDL_storage.h
@@ -22,7 +22,198 @@
 /**
  * # CategoryStorage
  *
- * SDL storage container management.
+ * The storage API is a high-level API designed to abstract away the
+ * portability issues that come up when using something lower-level (in SDL's
+ * case, this sits on top of SDL_filesystem). It is significantly more
+ * restrictive than a typical filesystem API, for a number of reasons:
+ *
+ * 1. **What to Access:** A common pitfall with existing filesystem APIs is
+ * the assumption that all storage is monolithic. However, many other
+ * platforms (game consoles in particular) are more strict about what _type_
+ * of filesystem is being accessed; for example, game content and user data
+ * are usually two separate storage devices with entirely different
+ * characteristics (and possibly different low-level APIs altogether!). 2.
+ * **How to Access:** Another common mistake is applications assuming that all
+ * storage is universally writeable - again, many platforms treat game content
+ * and user data as two separate storage devices, and only user data is
+ * writeable while game content is read-only. 3. **When to Access:** The most
+ * common portability issue with filesystem access is _timing_ - you cannot
+ * always assume that the storage device is always accessible all of the time,
+ * nor can you assume that there are no limits to how long you have access to
+ * a particular device.
+ *
+ * Consider the following example:
+ *
+ * ```
+ * void 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) {
+ *             // Something bad happened!
+ *         } else {
+ *             // A bunch of stuff happens here
+ *             fclose(data);
+ *         }
+ *     }
+ * }
+ *
+ * void ReadSave(void)
+ * {
+ *     FILE *save = fopen("saves/save0.sav", "rb");
+ *     if (save == NULL) {
+ *         // Something bad happened!
+ *     } else {
+ *         // A bunch of stuff happens here
+ *         fclose(save);
+ *     }
+ * }
+ *
+ * void WriteSave(void)
+ * {
+ *     FILE *save = fopen("saves/save0.sav", "wb");
+ *     if (save == NULL) {
+ *         // Something bad happened!
+ *     } else {
+ *         // A bunch of stuff happens here
+ *         fclose(save);
+ *     }
+ * }
+ * ```
+ *
+ * Going over the bullet points again:
+ *
+ * 1. **What to Access:** This code accesses a global filesystem; game data
+ * and saves are all presumed to be in the current working directory (which
+ * may or may not be the game's installation folder!). 2. **How to Access:**
+ * This code assumes that content paths are writeable, and that save data is
+ * also writeable despite being in the same location as the game data. 3.
+ * **When to Access:** This code assumes that they can be called at any time,
+ * since the filesystem is always accessible and has no limits on how long the
+ * filesystem is being accessed.
+ *
+ * Due to these assumptions, the filesystem code is not portable and will fail
+ * under these common scenarios:
+ *
+ * - The game is installed on a device that is read-only, both content loading
+ *   and game saves will fail or crash outright
+ * - Game/User storage is not implicitly mounted, so no files will be found
+ *   for either scenario when a platform requires explicitly mounting
+ *   filesystems
+ * - Save data may not be safe since the I/O is not being flushed or
+ *   validated, so an error occurring elsewhere in the program may result in
+ *   missing/corrupted save data
+ *
+ * When using, SDL_Storage, these types of problems are virtually impossible
+ * to trip over:
+ *
+ * ```
+ * void ReadGameData(void)
+ * {
+ *     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) {
+ *             dst = SDL_malloc(dstLen);
+ *             if (SDL_ReadStorageFile(title, fileNames[i], dst, dstLen)) {
+ *                 // A bunch of stuff happens here
+ *             } else {
+ *                 // Something bad happened!
+ *             }
+ *             SDL_free(dst);
+ *         } else {
+ *             // Something bad happened!
+ *         }
+ *     }
+ *
+ *     SDL_CloseStorage(title);
+ * }
+ *
+ * void ReadSave(void)
+ * {
+ *     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
+ *         } else {
+ *             // Something bad happened!
+ *         }
+ *         SDL_free(dst);
+ *     } else {
+ *         // Something bad happened!
+ *     }
+ *
+ *     SDL_CloseStorage(user);
+ * }
+ *
+ * void WriteSave(void)
+ * {
+ *     SDL_Storage *user = SDL_OpenUserStorage("libsdl", "Storage Example", 0);
+ *     if (user == NULL) {
+ *         // Something bad happened!
+ *     }
+ *     while (!SDL_StorageReady(user)) {
+ *         SDL_Delay(1);
+ *     }
+ *
+ *     extern void *saveData; // A bunch of stuff happened here...
+ *     extern Uint64 saveLen;
+ *     if (!SDL_WriteStorageFile(user, "save0.sav", saveData, saveLen)) {
+ *         // Something bad happened!
+ *     }
+ *
+ *     SDL_CloseStorage(user);
+ * }
+ * ```
+ *
+ * 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. 2. **How to Access:**
+ * This code explicitly uses either a read or write function based on the
+ * context of the function. 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.
+ *
+ * The result is an application that is significantly more robust against the
+ * increasing demands of platforms and their filesystems!
+ *
+ * A publicly available example of an SDL_Storage backend is the
+ * [Steam Cloud](https://partner.steamgames.com/doc/features/cloud)
+ * backend - you can initialize Steamworks when starting the program, and then
+ * SDL will recognize that Steamworks is initialized and automatically use
+ * ISteamRemoteStorage when the application opens user storage. More
+ * importantly, when you _open_ storage it knows to begin a "batch" of
+ * filesystem operations, and when you _close_ storage it knows to end and
+ * flush the batch. This is used by Steam to support
+ * [Dynamic Cloud Sync](https://steamcommunity.com/groups/steamworks/announcements/detail/3142949576401813670)
+ * ; users can save data on one PC, put the device to sleep, and then continue
+ * playing on another PC (and vice versa) with the save data fully
+ * synchronized across all devices, allowing for a seamless experience without
+ * having to do full restarts of the program.
  */
 
 #ifndef SDL_storage_h_