diff options
Diffstat (limited to 'contrib/SDL-3.2.8/src/io')
| -rw-r--r-- | contrib/SDL-3.2.8/src/io/SDL_asyncio.c | 330 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/io/SDL_asyncio_c.h | 30 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/io/SDL_iostream.c | 1657 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/io/SDL_iostream_c.h | 35 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/io/SDL_sysasyncio.h | 144 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/io/generic/SDL_asyncio_generic.c | 465 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c | 551 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.c | 88 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.h | 28 | ||||
| -rw-r--r-- | contrib/SDL-3.2.8/src/io/windows/SDL_asyncio_windows_ioring.c | 550 |
10 files changed, 3878 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/io/SDL_asyncio.c b/contrib/SDL-3.2.8/src/io/SDL_asyncio.c new file mode 100644 index 0000000..aa5ba87 --- /dev/null +++ b/contrib/SDL-3.2.8/src/io/SDL_asyncio.c | |||
| @@ -0,0 +1,330 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | #include "SDL_internal.h" | ||
| 23 | #include "SDL_sysasyncio.h" | ||
| 24 | #include "SDL_asyncio_c.h" | ||
| 25 | |||
| 26 | static const char *AsyncFileModeValid(const char *mode) | ||
| 27 | { | ||
| 28 | static const struct { const char *valid; const char *with_binary; } mode_map[] = { | ||
| 29 | { "r", "rb" }, | ||
| 30 | { "w", "wb" }, | ||
| 31 | { "r+","r+b" }, | ||
| 32 | { "w+", "w+b" } | ||
| 33 | }; | ||
| 34 | |||
| 35 | for (int i = 0; i < SDL_arraysize(mode_map); i++) { | ||
| 36 | if (SDL_strcmp(mode, mode_map[i].valid) == 0) { | ||
| 37 | return mode_map[i].with_binary; | ||
| 38 | } | ||
| 39 | } | ||
| 40 | return NULL; | ||
| 41 | } | ||
| 42 | |||
| 43 | SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode) | ||
| 44 | { | ||
| 45 | if (!file) { | ||
| 46 | SDL_InvalidParamError("file"); | ||
| 47 | return NULL; | ||
| 48 | } else if (!mode) { | ||
| 49 | SDL_InvalidParamError("mode"); | ||
| 50 | return NULL; | ||
| 51 | } | ||
| 52 | |||
| 53 | const char *binary_mode = AsyncFileModeValid(mode); | ||
| 54 | if (!binary_mode) { | ||
| 55 | SDL_SetError("Unsupported file mode"); | ||
| 56 | return NULL; | ||
| 57 | } | ||
| 58 | |||
| 59 | SDL_AsyncIO *asyncio = (SDL_AsyncIO *)SDL_calloc(1, sizeof(*asyncio)); | ||
| 60 | if (!asyncio) { | ||
| 61 | return NULL; | ||
| 62 | } | ||
| 63 | |||
| 64 | asyncio->lock = SDL_CreateMutex(); | ||
| 65 | if (!asyncio->lock) { | ||
| 66 | SDL_free(asyncio); | ||
| 67 | return NULL; | ||
| 68 | } | ||
| 69 | |||
| 70 | if (!SDL_SYS_AsyncIOFromFile(file, binary_mode, asyncio)) { | ||
| 71 | SDL_DestroyMutex(asyncio->lock); | ||
| 72 | SDL_free(asyncio); | ||
| 73 | return NULL; | ||
| 74 | } | ||
| 75 | |||
| 76 | return asyncio; | ||
| 77 | } | ||
| 78 | |||
| 79 | Sint64 SDL_GetAsyncIOSize(SDL_AsyncIO *asyncio) | ||
| 80 | { | ||
| 81 | if (!asyncio) { | ||
| 82 | SDL_InvalidParamError("asyncio"); | ||
| 83 | return -1; | ||
| 84 | } | ||
| 85 | return asyncio->iface.size(asyncio->userdata); | ||
| 86 | } | ||
| 87 | |||
| 88 | static bool RequestAsyncIO(bool reading, SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata) | ||
| 89 | { | ||
| 90 | if (!asyncio) { | ||
| 91 | return SDL_InvalidParamError("asyncio"); | ||
| 92 | } else if (!ptr) { | ||
| 93 | return SDL_InvalidParamError("ptr"); | ||
| 94 | } else if (!queue) { | ||
| 95 | return SDL_InvalidParamError("queue"); | ||
| 96 | } | ||
| 97 | |||
| 98 | SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*task)); | ||
| 99 | if (!task) { | ||
| 100 | return false; | ||
| 101 | } | ||
| 102 | |||
| 103 | task->asyncio = asyncio; | ||
| 104 | task->type = reading ? SDL_ASYNCIO_TASK_READ : SDL_ASYNCIO_TASK_WRITE; | ||
| 105 | task->offset = offset; | ||
| 106 | task->buffer = ptr; | ||
| 107 | task->requested_size = size; | ||
| 108 | task->app_userdata = userdata; | ||
| 109 | task->queue = queue; | ||
| 110 | |||
| 111 | SDL_LockMutex(asyncio->lock); | ||
| 112 | if (asyncio->closing) { | ||
| 113 | SDL_free(task); | ||
| 114 | SDL_UnlockMutex(asyncio->lock); | ||
| 115 | return SDL_SetError("SDL_AsyncIO is closing, can't start new tasks"); | ||
| 116 | } | ||
| 117 | LINKED_LIST_PREPEND(task, asyncio->tasks, asyncio); | ||
| 118 | SDL_AddAtomicInt(&queue->tasks_inflight, 1); | ||
| 119 | SDL_UnlockMutex(asyncio->lock); | ||
| 120 | |||
| 121 | const bool queued = reading ? asyncio->iface.read(asyncio->userdata, task) : asyncio->iface.write(asyncio->userdata, task); | ||
| 122 | if (!queued) { | ||
| 123 | SDL_AddAtomicInt(&queue->tasks_inflight, -1); | ||
| 124 | SDL_LockMutex(asyncio->lock); | ||
| 125 | LINKED_LIST_UNLINK(task, asyncio); | ||
| 126 | SDL_UnlockMutex(asyncio->lock); | ||
| 127 | SDL_free(task); | ||
| 128 | task = NULL; | ||
| 129 | } | ||
| 130 | |||
| 131 | return (task != NULL); | ||
| 132 | } | ||
| 133 | |||
| 134 | bool SDL_ReadAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata) | ||
| 135 | { | ||
| 136 | return RequestAsyncIO(true, asyncio, ptr, offset, size, queue, userdata); | ||
| 137 | } | ||
| 138 | |||
| 139 | bool SDL_WriteAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata) | ||
| 140 | { | ||
| 141 | return RequestAsyncIO(false, asyncio, ptr, offset, size, queue, userdata); | ||
| 142 | } | ||
| 143 | |||
| 144 | bool SDL_CloseAsyncIO(SDL_AsyncIO *asyncio, bool flush, SDL_AsyncIOQueue *queue, void *userdata) | ||
| 145 | { | ||
| 146 | if (!asyncio) { | ||
| 147 | return SDL_InvalidParamError("asyncio"); | ||
| 148 | } else if (!queue) { | ||
| 149 | return SDL_InvalidParamError("queue"); | ||
| 150 | } | ||
| 151 | |||
| 152 | SDL_LockMutex(asyncio->lock); | ||
| 153 | if (asyncio->closing) { | ||
| 154 | SDL_UnlockMutex(asyncio->lock); | ||
| 155 | return SDL_SetError("Already closing"); | ||
| 156 | } | ||
| 157 | |||
| 158 | SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*task)); | ||
| 159 | if (task) { | ||
| 160 | task->asyncio = asyncio; | ||
| 161 | task->type = SDL_ASYNCIO_TASK_CLOSE; | ||
| 162 | task->app_userdata = userdata; | ||
| 163 | task->queue = queue; | ||
| 164 | task->flush = flush; | ||
| 165 | |||
| 166 | asyncio->closing = task; | ||
| 167 | |||
| 168 | if (LINKED_LIST_START(asyncio->tasks, asyncio) == NULL) { // no tasks? Queue the close task now. | ||
| 169 | LINKED_LIST_PREPEND(task, asyncio->tasks, asyncio); | ||
| 170 | SDL_AddAtomicInt(&queue->tasks_inflight, 1); | ||
| 171 | if (!asyncio->iface.close(asyncio->userdata, task)) { | ||
| 172 | // uhoh, maybe they can try again later...? | ||
| 173 | SDL_AddAtomicInt(&queue->tasks_inflight, -1); | ||
| 174 | LINKED_LIST_UNLINK(task, asyncio); | ||
| 175 | SDL_free(task); | ||
| 176 | task = asyncio->closing = NULL; | ||
| 177 | } | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | SDL_UnlockMutex(asyncio->lock); | ||
| 182 | |||
| 183 | return (task != NULL); | ||
| 184 | } | ||
| 185 | |||
| 186 | SDL_AsyncIOQueue *SDL_CreateAsyncIOQueue(void) | ||
| 187 | { | ||
| 188 | SDL_AsyncIOQueue *queue = SDL_calloc(1, sizeof (*queue)); | ||
| 189 | if (queue) { | ||
| 190 | SDL_SetAtomicInt(&queue->tasks_inflight, 0); | ||
| 191 | if (!SDL_SYS_CreateAsyncIOQueue(queue)) { | ||
| 192 | SDL_free(queue); | ||
| 193 | return NULL; | ||
| 194 | } | ||
| 195 | } | ||
| 196 | return queue; | ||
| 197 | } | ||
| 198 | |||
| 199 | static bool GetAsyncIOTaskOutcome(SDL_AsyncIOTask *task, SDL_AsyncIOOutcome *outcome) | ||
| 200 | { | ||
| 201 | if (!task || !outcome) { | ||
| 202 | return false; | ||
| 203 | } | ||
| 204 | |||
| 205 | SDL_AsyncIO *asyncio = task->asyncio; | ||
| 206 | |||
| 207 | SDL_zerop(outcome); | ||
| 208 | outcome->asyncio = asyncio->oneshot ? NULL : asyncio; | ||
| 209 | outcome->result = task->result; | ||
| 210 | outcome->type = task->type; | ||
| 211 | outcome->buffer = task->buffer; | ||
| 212 | outcome->offset = task->offset; | ||
| 213 | outcome->bytes_requested = task->requested_size; | ||
| 214 | outcome->bytes_transferred = task->result_size; | ||
| 215 | outcome->userdata = task->app_userdata; | ||
| 216 | |||
| 217 | // Take the completed task out of the SDL_AsyncIO that created it. | ||
| 218 | SDL_LockMutex(asyncio->lock); | ||
| 219 | LINKED_LIST_UNLINK(task, asyncio); | ||
| 220 | // see if it's time to queue a pending close request (close requested and no other pending tasks) | ||
| 221 | SDL_AsyncIOTask *closing = asyncio->closing; | ||
| 222 | if (closing && (task != closing) && (LINKED_LIST_START(asyncio->tasks, asyncio) == NULL)) { | ||
| 223 | LINKED_LIST_PREPEND(closing, asyncio->tasks, asyncio); | ||
| 224 | SDL_AddAtomicInt(&closing->queue->tasks_inflight, 1); | ||
| 225 | const bool async_close_task_was_queued = asyncio->iface.close(asyncio->userdata, closing); | ||
| 226 | SDL_assert(async_close_task_was_queued); // !!! FIXME: if this fails to queue the task, we're leaking resources! | ||
| 227 | if (!async_close_task_was_queued) { | ||
| 228 | SDL_AddAtomicInt(&closing->queue->tasks_inflight, -1); | ||
| 229 | } | ||
| 230 | } | ||
| 231 | SDL_UnlockMutex(task->asyncio->lock); | ||
| 232 | |||
| 233 | // was this the result of a closing task? Finally destroy the asyncio. | ||
| 234 | bool retval = true; | ||
| 235 | if (closing && (task == closing)) { | ||
| 236 | if (asyncio->oneshot) { | ||
| 237 | retval = false; // don't send the close task results on to the app, just the read task for these. | ||
| 238 | } | ||
| 239 | asyncio->iface.destroy(asyncio->userdata); | ||
| 240 | SDL_DestroyMutex(asyncio->lock); | ||
| 241 | SDL_free(asyncio); | ||
| 242 | } | ||
| 243 | |||
| 244 | SDL_AddAtomicInt(&task->queue->tasks_inflight, -1); | ||
| 245 | SDL_free(task); | ||
| 246 | |||
| 247 | return retval; | ||
| 248 | } | ||
| 249 | |||
| 250 | bool SDL_GetAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome) | ||
| 251 | { | ||
| 252 | if (!queue || !outcome) { | ||
| 253 | return false; | ||
| 254 | } | ||
| 255 | return GetAsyncIOTaskOutcome(queue->iface.get_results(queue->userdata), outcome); | ||
| 256 | } | ||
| 257 | |||
| 258 | bool SDL_WaitAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome, Sint32 timeoutMS) | ||
| 259 | { | ||
| 260 | if (!queue || !outcome) { | ||
| 261 | return false; | ||
| 262 | } | ||
| 263 | return GetAsyncIOTaskOutcome(queue->iface.wait_results(queue->userdata, timeoutMS), outcome); | ||
| 264 | } | ||
| 265 | |||
| 266 | void SDL_SignalAsyncIOQueue(SDL_AsyncIOQueue *queue) | ||
| 267 | { | ||
| 268 | if (queue) { | ||
| 269 | queue->iface.signal(queue->userdata); | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | void SDL_DestroyAsyncIOQueue(SDL_AsyncIOQueue *queue) | ||
| 274 | { | ||
| 275 | if (queue) { | ||
| 276 | // block until any pending tasks complete. | ||
| 277 | while (SDL_GetAtomicInt(&queue->tasks_inflight) > 0) { | ||
| 278 | SDL_AsyncIOTask *task = queue->iface.wait_results(queue->userdata, -1); | ||
| 279 | if (task) { | ||
| 280 | if (task->asyncio->oneshot) { | ||
| 281 | SDL_free(task->buffer); // throw away the buffer from SDL_LoadFileAsync that will never be consumed/freed by app. | ||
| 282 | task->buffer = NULL; | ||
| 283 | } | ||
| 284 | SDL_AsyncIOOutcome outcome; | ||
| 285 | GetAsyncIOTaskOutcome(task, &outcome); // this frees the task, and does other upkeep. | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | queue->iface.destroy(queue->userdata); | ||
| 290 | SDL_free(queue); | ||
| 291 | } | ||
| 292 | } | ||
| 293 | |||
| 294 | void SDL_QuitAsyncIO(void) | ||
| 295 | { | ||
| 296 | SDL_SYS_QuitAsyncIO(); | ||
| 297 | } | ||
| 298 | |||
| 299 | bool SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata) | ||
| 300 | { | ||
| 301 | if (!file) { | ||
| 302 | return SDL_InvalidParamError("file"); | ||
| 303 | } else if (!queue) { | ||
| 304 | return SDL_InvalidParamError("queue"); | ||
| 305 | } | ||
| 306 | |||
| 307 | bool retval = false; | ||
| 308 | SDL_AsyncIO *asyncio = SDL_AsyncIOFromFile(file, "r"); | ||
| 309 | if (asyncio) { | ||
| 310 | asyncio->oneshot = true; | ||
| 311 | |||
| 312 | void *ptr = NULL; | ||
| 313 | const Sint64 flen = SDL_GetAsyncIOSize(asyncio); | ||
| 314 | if (flen >= 0) { | ||
| 315 | // !!! FIXME: check if flen > address space, since it'll truncate and we'll just end up with an incomplete buffer or a crash. | ||
| 316 | ptr = SDL_malloc((size_t) (flen + 1)); // over-allocate by one so we can add a null-terminator. | ||
| 317 | if (ptr) { | ||
| 318 | retval = SDL_ReadAsyncIO(asyncio, ptr, 0, (Uint64) flen, queue, userdata); | ||
| 319 | if (!retval) { | ||
| 320 | SDL_free(ptr); | ||
| 321 | } | ||
| 322 | } | ||
| 323 | } | ||
| 324 | |||
| 325 | SDL_CloseAsyncIO(asyncio, false, queue, userdata); // if this fails, we'll have a resource leak, but this would already be a dramatic system failure. | ||
| 326 | } | ||
| 327 | |||
| 328 | return retval; | ||
| 329 | } | ||
| 330 | |||
diff --git a/contrib/SDL-3.2.8/src/io/SDL_asyncio_c.h b/contrib/SDL-3.2.8/src/io/SDL_asyncio_c.h new file mode 100644 index 0000000..861ba1a --- /dev/null +++ b/contrib/SDL-3.2.8/src/io/SDL_asyncio_c.h | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "../SDL_internal.h" | ||
| 22 | |||
| 23 | #ifndef SDL_asyncio_c_h_ | ||
| 24 | #define SDL_asyncio_c_h_ | ||
| 25 | |||
| 26 | // Shutdown any still-existing Async I/O. Note that there is no Init function, as it inits on-demand! | ||
| 27 | extern void SDL_QuitAsyncIO(void); | ||
| 28 | |||
| 29 | #endif // SDL_asyncio_c_h_ | ||
| 30 | |||
diff --git a/contrib/SDL-3.2.8/src/io/SDL_iostream.c b/contrib/SDL-3.2.8/src/io/SDL_iostream.c new file mode 100644 index 0000000..989f3b9 --- /dev/null +++ b/contrib/SDL-3.2.8/src/io/SDL_iostream.c | |||
| @@ -0,0 +1,1657 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #if defined(SDL_PLATFORM_WINDOWS) | ||
| 24 | #include "../core/windows/SDL_windows.h" | ||
| 25 | #else | ||
| 26 | #include <unistd.h> | ||
| 27 | #endif | ||
| 28 | |||
| 29 | #ifdef HAVE_STDIO_H | ||
| 30 | #include <stdio.h> | ||
| 31 | #include <errno.h> | ||
| 32 | #include <sys/stat.h> | ||
| 33 | #endif | ||
| 34 | #ifdef HAVE_LIMITS_H | ||
| 35 | #include <limits.h> | ||
| 36 | #endif | ||
| 37 | |||
| 38 | #ifdef SDL_PLATFORM_APPLE | ||
| 39 | #include <fcntl.h> | ||
| 40 | #endif | ||
| 41 | |||
| 42 | #include "SDL_iostream_c.h" | ||
| 43 | |||
| 44 | /* This file provides a general interface for SDL to read and write | ||
| 45 | data sources. It can easily be extended to files, memory, etc. | ||
| 46 | */ | ||
| 47 | |||
| 48 | struct SDL_IOStream | ||
| 49 | { | ||
| 50 | SDL_IOStreamInterface iface; | ||
| 51 | void *userdata; | ||
| 52 | SDL_IOStatus status; | ||
| 53 | SDL_PropertiesID props; | ||
| 54 | }; | ||
| 55 | |||
| 56 | #ifdef SDL_PLATFORM_3DS | ||
| 57 | #include "n3ds/SDL_iostreamromfs.h" | ||
| 58 | #endif // SDL_PLATFORM_3DS | ||
| 59 | |||
| 60 | #ifdef SDL_PLATFORM_ANDROID | ||
| 61 | #include <unistd.h> | ||
| 62 | #include "../core/android/SDL_android.h" | ||
| 63 | #endif | ||
| 64 | |||
| 65 | #if defined(SDL_PLATFORM_WINDOWS) | ||
| 66 | |||
| 67 | typedef struct IOStreamWindowsData | ||
| 68 | { | ||
| 69 | HANDLE h; | ||
| 70 | void *data; | ||
| 71 | size_t size; | ||
| 72 | size_t left; | ||
| 73 | bool append; | ||
| 74 | bool autoclose; | ||
| 75 | } IOStreamWindowsData; | ||
| 76 | |||
| 77 | |||
| 78 | // Functions to read/write Win32 API file pointers | ||
| 79 | #ifndef INVALID_SET_FILE_POINTER | ||
| 80 | #define INVALID_SET_FILE_POINTER 0xFFFFFFFF | ||
| 81 | #endif | ||
| 82 | |||
| 83 | #define READAHEAD_BUFFER_SIZE 1024 | ||
| 84 | |||
| 85 | static HANDLE SDLCALL windows_file_open(const char *filename, const char *mode) | ||
| 86 | { | ||
| 87 | #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) | ||
| 88 | UINT old_error_mode; | ||
| 89 | #endif | ||
| 90 | HANDLE h; | ||
| 91 | DWORD r_right, w_right; | ||
| 92 | DWORD must_exist, truncate; | ||
| 93 | int a_mode; | ||
| 94 | |||
| 95 | // "r" = reading, file must exist | ||
| 96 | // "w" = writing, truncate existing, file may not exist | ||
| 97 | // "r+"= reading or writing, file must exist | ||
| 98 | // "a" = writing, append file may not exist | ||
| 99 | // "a+"= append + read, file may not exist | ||
| 100 | // "w+" = read, write, truncate. file may not exist | ||
| 101 | |||
| 102 | must_exist = (SDL_strchr(mode, 'r') != NULL) ? OPEN_EXISTING : 0; | ||
| 103 | truncate = (SDL_strchr(mode, 'w') != NULL) ? CREATE_ALWAYS : 0; | ||
| 104 | r_right = (SDL_strchr(mode, '+') != NULL || must_exist) ? GENERIC_READ : 0; | ||
| 105 | a_mode = (SDL_strchr(mode, 'a') != NULL) ? OPEN_ALWAYS : 0; | ||
| 106 | w_right = (a_mode || SDL_strchr(mode, '+') || truncate) ? GENERIC_WRITE : 0; | ||
| 107 | |||
| 108 | if (!r_right && !w_right) { | ||
| 109 | return INVALID_HANDLE_VALUE; // inconsistent mode | ||
| 110 | } | ||
| 111 | // failed (invalid call) | ||
| 112 | |||
| 113 | #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) | ||
| 114 | // Do not open a dialog box if failure | ||
| 115 | old_error_mode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); | ||
| 116 | #endif | ||
| 117 | |||
| 118 | { | ||
| 119 | LPWSTR str = WIN_UTF8ToStringW(filename); | ||
| 120 | h = CreateFileW(str, | ||
| 121 | (w_right | r_right), | ||
| 122 | (w_right) ? 0 : FILE_SHARE_READ, | ||
| 123 | NULL, | ||
| 124 | (must_exist | truncate | a_mode), | ||
| 125 | FILE_ATTRIBUTE_NORMAL, | ||
| 126 | NULL); | ||
| 127 | SDL_free(str); | ||
| 128 | } | ||
| 129 | |||
| 130 | #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES) | ||
| 131 | // restore old behavior | ||
| 132 | SetErrorMode(old_error_mode); | ||
| 133 | #endif | ||
| 134 | |||
| 135 | if (h == INVALID_HANDLE_VALUE) { | ||
| 136 | char *error; | ||
| 137 | if (SDL_asprintf(&error, "Couldn't open %s", filename) > 0) { | ||
| 138 | WIN_SetError(error); | ||
| 139 | SDL_free(error); | ||
| 140 | } else { | ||
| 141 | SDL_SetError("Couldn't open %s", filename); | ||
| 142 | } | ||
| 143 | } | ||
| 144 | return h; | ||
| 145 | } | ||
| 146 | |||
| 147 | static Sint64 SDLCALL windows_file_size(void *userdata) | ||
| 148 | { | ||
| 149 | IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata; | ||
| 150 | LARGE_INTEGER size; | ||
| 151 | |||
| 152 | if (!GetFileSizeEx(iodata->h, &size)) { | ||
| 153 | return WIN_SetError("windows_file_size"); | ||
| 154 | } | ||
| 155 | |||
| 156 | return size.QuadPart; | ||
| 157 | } | ||
| 158 | |||
| 159 | static Sint64 SDLCALL windows_file_seek(void *userdata, Sint64 offset, SDL_IOWhence whence) | ||
| 160 | { | ||
| 161 | IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata; | ||
| 162 | DWORD windowswhence; | ||
| 163 | LARGE_INTEGER windowsoffset; | ||
| 164 | |||
| 165 | // FIXME: We may be able to satisfy the seek within buffered data | ||
| 166 | if ((whence == SDL_IO_SEEK_CUR) && (iodata->left)) { | ||
| 167 | offset -= iodata->left; | ||
| 168 | } | ||
| 169 | iodata->left = 0; | ||
| 170 | |||
| 171 | switch (whence) { | ||
| 172 | case SDL_IO_SEEK_SET: | ||
| 173 | windowswhence = FILE_BEGIN; | ||
| 174 | break; | ||
| 175 | case SDL_IO_SEEK_CUR: | ||
| 176 | windowswhence = FILE_CURRENT; | ||
| 177 | break; | ||
| 178 | case SDL_IO_SEEK_END: | ||
| 179 | windowswhence = FILE_END; | ||
| 180 | break; | ||
| 181 | default: | ||
| 182 | SDL_SetError("windows_file_seek: Unknown value for 'whence'"); | ||
| 183 | return -1; | ||
| 184 | } | ||
| 185 | |||
| 186 | windowsoffset.QuadPart = offset; | ||
| 187 | if (!SetFilePointerEx(iodata->h, windowsoffset, &windowsoffset, windowswhence)) { | ||
| 188 | return WIN_SetError("Error seeking in datastream"); | ||
| 189 | } | ||
| 190 | return windowsoffset.QuadPart; | ||
| 191 | } | ||
| 192 | |||
| 193 | static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) | ||
| 194 | { | ||
| 195 | IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata; | ||
| 196 | size_t total_need = size; | ||
| 197 | size_t total_read = 0; | ||
| 198 | size_t read_ahead; | ||
| 199 | DWORD bytes; | ||
| 200 | |||
| 201 | if (iodata->left > 0) { | ||
| 202 | void *data = (char *)iodata->data + | ||
| 203 | iodata->size - | ||
| 204 | iodata->left; | ||
| 205 | read_ahead = SDL_min(total_need, iodata->left); | ||
| 206 | SDL_memcpy(ptr, data, read_ahead); | ||
| 207 | iodata->left -= read_ahead; | ||
| 208 | |||
| 209 | if (read_ahead == total_need) { | ||
| 210 | return size; | ||
| 211 | } | ||
| 212 | ptr = (char *)ptr + read_ahead; | ||
| 213 | total_need -= read_ahead; | ||
| 214 | total_read += read_ahead; | ||
| 215 | } | ||
| 216 | |||
| 217 | if (total_need < READAHEAD_BUFFER_SIZE) { | ||
| 218 | if (!ReadFile(iodata->h, iodata->data, READAHEAD_BUFFER_SIZE, &bytes, NULL)) { | ||
| 219 | DWORD error = GetLastError(); | ||
| 220 | switch (error) { | ||
| 221 | case ERROR_BROKEN_PIPE: | ||
| 222 | case ERROR_HANDLE_EOF: | ||
| 223 | break; | ||
| 224 | case ERROR_NO_DATA: | ||
| 225 | *status = SDL_IO_STATUS_NOT_READY; | ||
| 226 | break; | ||
| 227 | default: | ||
| 228 | WIN_SetError("Error reading from datastream"); | ||
| 229 | break; | ||
| 230 | } | ||
| 231 | return 0; | ||
| 232 | } | ||
| 233 | read_ahead = SDL_min(total_need, bytes); | ||
| 234 | SDL_memcpy(ptr, iodata->data, read_ahead); | ||
| 235 | iodata->size = bytes; | ||
| 236 | iodata->left = bytes - read_ahead; | ||
| 237 | total_read += read_ahead; | ||
| 238 | } else { | ||
| 239 | if (!ReadFile(iodata->h, ptr, (DWORD)total_need, &bytes, NULL)) { | ||
| 240 | DWORD error = GetLastError(); | ||
| 241 | switch (error) { | ||
| 242 | case ERROR_BROKEN_PIPE: | ||
| 243 | case ERROR_HANDLE_EOF: | ||
| 244 | break; | ||
| 245 | case ERROR_NO_DATA: | ||
| 246 | *status = SDL_IO_STATUS_NOT_READY; | ||
| 247 | break; | ||
| 248 | default: | ||
| 249 | WIN_SetError("Error reading from datastream"); | ||
| 250 | break; | ||
| 251 | } | ||
| 252 | return 0; | ||
| 253 | } | ||
| 254 | total_read += bytes; | ||
| 255 | } | ||
| 256 | return total_read; | ||
| 257 | } | ||
| 258 | |||
| 259 | static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status) | ||
| 260 | { | ||
| 261 | IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata; | ||
| 262 | DWORD bytes; | ||
| 263 | |||
| 264 | if (iodata->left) { | ||
| 265 | if (!SetFilePointer(iodata->h, -(LONG)iodata->left, NULL, FILE_CURRENT)) { | ||
| 266 | WIN_SetError("Error seeking in datastream"); | ||
| 267 | return 0; | ||
| 268 | } | ||
| 269 | iodata->left = 0; | ||
| 270 | } | ||
| 271 | |||
| 272 | // if in append mode, we must go to the EOF before write | ||
| 273 | if (iodata->append) { | ||
| 274 | LARGE_INTEGER windowsoffset; | ||
| 275 | windowsoffset.QuadPart = 0; | ||
| 276 | if (!SetFilePointerEx(iodata->h, windowsoffset, &windowsoffset, FILE_END)) { | ||
| 277 | WIN_SetError("Error seeking in datastream"); | ||
| 278 | return 0; | ||
| 279 | } | ||
| 280 | } | ||
| 281 | |||
| 282 | if (!WriteFile(iodata->h, ptr, (DWORD)size, &bytes, NULL)) { | ||
| 283 | WIN_SetError("Error writing to datastream"); | ||
| 284 | return 0; | ||
| 285 | } | ||
| 286 | if (bytes == 0 && size > 0) { | ||
| 287 | *status = SDL_IO_STATUS_NOT_READY; | ||
| 288 | } | ||
| 289 | return bytes; | ||
| 290 | } | ||
| 291 | |||
| 292 | static bool SDLCALL windows_file_flush(void *userdata, SDL_IOStatus *status) | ||
| 293 | { | ||
| 294 | IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata; | ||
| 295 | if (!FlushFileBuffers(iodata->h)) { | ||
| 296 | return WIN_SetError("Error flushing datastream"); | ||
| 297 | } | ||
| 298 | return true; | ||
| 299 | } | ||
| 300 | |||
| 301 | static bool SDLCALL windows_file_close(void *userdata) | ||
| 302 | { | ||
| 303 | IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata; | ||
| 304 | if (iodata->h != INVALID_HANDLE_VALUE) { | ||
| 305 | if (iodata->autoclose) { | ||
| 306 | CloseHandle(iodata->h); | ||
| 307 | } | ||
| 308 | iodata->h = INVALID_HANDLE_VALUE; // to be sure | ||
| 309 | } | ||
| 310 | SDL_free(iodata->data); | ||
| 311 | SDL_free(iodata); | ||
| 312 | return true; | ||
| 313 | } | ||
| 314 | |||
| 315 | SDL_IOStream *SDL_IOFromHandle(HANDLE handle, const char *mode, bool autoclose) | ||
| 316 | { | ||
| 317 | IOStreamWindowsData *iodata = (IOStreamWindowsData *) SDL_calloc(1, sizeof (*iodata)); | ||
| 318 | if (!iodata) { | ||
| 319 | if (autoclose) { | ||
| 320 | CloseHandle(handle); | ||
| 321 | } | ||
| 322 | return NULL; | ||
| 323 | } | ||
| 324 | |||
| 325 | SDL_IOStreamInterface iface; | ||
| 326 | SDL_INIT_INTERFACE(&iface); | ||
| 327 | if (GetFileType(handle) == FILE_TYPE_DISK) { | ||
| 328 | iface.size = windows_file_size; | ||
| 329 | iface.seek = windows_file_seek; | ||
| 330 | } | ||
| 331 | iface.read = windows_file_read; | ||
| 332 | iface.write = windows_file_write; | ||
| 333 | iface.flush = windows_file_flush; | ||
| 334 | iface.close = windows_file_close; | ||
| 335 | |||
| 336 | iodata->h = handle; | ||
| 337 | iodata->append = (SDL_strchr(mode, 'a') != NULL); | ||
| 338 | iodata->autoclose = autoclose; | ||
| 339 | |||
| 340 | iodata->data = (char *)SDL_malloc(READAHEAD_BUFFER_SIZE); | ||
| 341 | if (!iodata->data) { | ||
| 342 | iface.close(iodata); | ||
| 343 | return NULL; | ||
| 344 | } | ||
| 345 | |||
| 346 | SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata); | ||
| 347 | if (!iostr) { | ||
| 348 | iface.close(iodata); | ||
| 349 | } else { | ||
| 350 | const SDL_PropertiesID props = SDL_GetIOProperties(iostr); | ||
| 351 | if (props) { | ||
| 352 | SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER, iodata->h); | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | return iostr; | ||
| 357 | } | ||
| 358 | #endif // defined(SDL_PLATFORM_WINDOWS) | ||
| 359 | |||
| 360 | #if !defined(SDL_PLATFORM_WINDOWS) | ||
| 361 | |||
| 362 | // Functions to read/write file descriptors. Not used for windows. | ||
| 363 | |||
| 364 | typedef struct IOStreamFDData | ||
| 365 | { | ||
| 366 | int fd; | ||
| 367 | bool autoclose; | ||
| 368 | bool regular_file; | ||
| 369 | } IOStreamFDData; | ||
| 370 | |||
| 371 | static int SDL_fdatasync(int fd) | ||
| 372 | { | ||
| 373 | int result = 0; | ||
| 374 | |||
| 375 | #if defined(SDL_PLATFORM_APPLE) // Apple doesn't have fdatasync (rather, the symbol exists as an incompatible system call). | ||
| 376 | result = fcntl(fd, F_FULLFSYNC); | ||
| 377 | #elif defined(SDL_PLATFORM_HAIKU) | ||
| 378 | result = fsync(fd); | ||
| 379 | #elif defined(HAVE_FDATASYNC) | ||
| 380 | result = fdatasync(fd); | ||
| 381 | #endif | ||
| 382 | return result; | ||
| 383 | } | ||
| 384 | |||
| 385 | static Sint64 SDLCALL fd_seek(void *userdata, Sint64 offset, SDL_IOWhence whence) | ||
| 386 | { | ||
| 387 | IOStreamFDData *iodata = (IOStreamFDData *) userdata; | ||
| 388 | int fdwhence; | ||
| 389 | |||
| 390 | switch (whence) { | ||
| 391 | case SDL_IO_SEEK_SET: | ||
| 392 | fdwhence = SEEK_SET; | ||
| 393 | break; | ||
| 394 | case SDL_IO_SEEK_CUR: | ||
| 395 | fdwhence = SEEK_CUR; | ||
| 396 | break; | ||
| 397 | case SDL_IO_SEEK_END: | ||
| 398 | fdwhence = SEEK_END; | ||
| 399 | break; | ||
| 400 | default: | ||
| 401 | SDL_SetError("Unknown value for 'whence'"); | ||
| 402 | return -1; | ||
| 403 | } | ||
| 404 | |||
| 405 | off_t result = lseek(iodata->fd, (off_t)offset, fdwhence); | ||
| 406 | if (result < 0) { | ||
| 407 | SDL_SetError("Couldn't get stream offset: %s", strerror(errno)); | ||
| 408 | } | ||
| 409 | return result; | ||
| 410 | } | ||
| 411 | |||
| 412 | static size_t SDLCALL fd_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) | ||
| 413 | { | ||
| 414 | IOStreamFDData *iodata = (IOStreamFDData *) userdata; | ||
| 415 | ssize_t bytes; | ||
| 416 | do { | ||
| 417 | bytes = read(iodata->fd, ptr, size); | ||
| 418 | } while (bytes < 0 && errno == EINTR); | ||
| 419 | |||
| 420 | if (bytes < 0) { | ||
| 421 | if (errno == EAGAIN) { | ||
| 422 | *status = SDL_IO_STATUS_NOT_READY; | ||
| 423 | } else { | ||
| 424 | SDL_SetError("Error reading from datastream: %s", strerror(errno)); | ||
| 425 | } | ||
| 426 | bytes = 0; | ||
| 427 | } | ||
| 428 | return (size_t)bytes; | ||
| 429 | } | ||
| 430 | |||
| 431 | static size_t SDLCALL fd_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status) | ||
| 432 | { | ||
| 433 | IOStreamFDData *iodata = (IOStreamFDData *) userdata; | ||
| 434 | ssize_t bytes; | ||
| 435 | do { | ||
| 436 | bytes = write(iodata->fd, ptr, size); | ||
| 437 | } while (bytes < 0 && errno == EINTR); | ||
| 438 | |||
| 439 | if (bytes < 0) { | ||
| 440 | if (errno == EAGAIN) { | ||
| 441 | *status = SDL_IO_STATUS_NOT_READY; | ||
| 442 | } else { | ||
| 443 | SDL_SetError("Error writing to datastream: %s", strerror(errno)); | ||
| 444 | } | ||
| 445 | bytes = 0; | ||
| 446 | } | ||
| 447 | return (size_t)bytes; | ||
| 448 | } | ||
| 449 | |||
| 450 | static bool SDLCALL fd_flush(void *userdata, SDL_IOStatus *status) | ||
| 451 | { | ||
| 452 | IOStreamFDData *iodata = (IOStreamFDData *) userdata; | ||
| 453 | int result; | ||
| 454 | do { | ||
| 455 | result = SDL_fdatasync(iodata->fd); | ||
| 456 | } while (result < 0 && errno == EINTR); | ||
| 457 | |||
| 458 | if (result < 0) { | ||
| 459 | return SDL_SetError("Error flushing datastream: %s", strerror(errno)); | ||
| 460 | } | ||
| 461 | return true; | ||
| 462 | } | ||
| 463 | |||
| 464 | static bool SDLCALL fd_close(void *userdata) | ||
| 465 | { | ||
| 466 | IOStreamFDData *iodata = (IOStreamFDData *) userdata; | ||
| 467 | bool status = true; | ||
| 468 | if (iodata->autoclose) { | ||
| 469 | if (close(iodata->fd) < 0) { | ||
| 470 | status = SDL_SetError("Error closing datastream: %s", strerror(errno)); | ||
| 471 | } | ||
| 472 | } | ||
| 473 | SDL_free(iodata); | ||
| 474 | return status; | ||
| 475 | } | ||
| 476 | |||
| 477 | SDL_IOStream *SDL_IOFromFD(int fd, bool autoclose) | ||
| 478 | { | ||
| 479 | IOStreamFDData *iodata = (IOStreamFDData *) SDL_calloc(1, sizeof (*iodata)); | ||
| 480 | if (!iodata) { | ||
| 481 | if (autoclose) { | ||
| 482 | close(fd); | ||
| 483 | } | ||
| 484 | return NULL; | ||
| 485 | } | ||
| 486 | |||
| 487 | SDL_IOStreamInterface iface; | ||
| 488 | SDL_INIT_INTERFACE(&iface); | ||
| 489 | // There's no fd_size because SDL_GetIOSize emulates it the same way we'd do it for fd anyhow. | ||
| 490 | iface.seek = fd_seek; | ||
| 491 | iface.read = fd_read; | ||
| 492 | iface.write = fd_write; | ||
| 493 | iface.flush = fd_flush; | ||
| 494 | iface.close = fd_close; | ||
| 495 | |||
| 496 | iodata->fd = fd; | ||
| 497 | iodata->autoclose = autoclose; | ||
| 498 | |||
| 499 | struct stat st; | ||
| 500 | iodata->regular_file = ((fstat(fd, &st) == 0) && S_ISREG(st.st_mode)); | ||
| 501 | |||
| 502 | SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata); | ||
| 503 | if (!iostr) { | ||
| 504 | iface.close(iodata); | ||
| 505 | } else { | ||
| 506 | const SDL_PropertiesID props = SDL_GetIOProperties(iostr); | ||
| 507 | if (props) { | ||
| 508 | SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER, fd); | ||
| 509 | } | ||
| 510 | } | ||
| 511 | |||
| 512 | return iostr; | ||
| 513 | } | ||
| 514 | #endif // !defined(SDL_PLATFORM_WINDOWS) | ||
| 515 | |||
| 516 | #if defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINDOWS) | ||
| 517 | |||
| 518 | // Functions to read/write stdio file pointers. Not used for windows. | ||
| 519 | |||
| 520 | typedef struct IOStreamStdioData | ||
| 521 | { | ||
| 522 | FILE *fp; | ||
| 523 | bool autoclose; | ||
| 524 | bool regular_file; | ||
| 525 | } IOStreamStdioData; | ||
| 526 | |||
| 527 | #ifdef HAVE_FOPEN64 | ||
| 528 | #define fopen fopen64 | ||
| 529 | #endif | ||
| 530 | #ifdef HAVE_FSEEKO64 | ||
| 531 | #define fseek_off_t off64_t | ||
| 532 | #define fseek fseeko64 | ||
| 533 | #define ftell ftello64 | ||
| 534 | #elif defined(HAVE_FSEEKO) | ||
| 535 | #if defined(OFF_MIN) && defined(OFF_MAX) | ||
| 536 | #define FSEEK_OFF_MIN OFF_MIN | ||
| 537 | #define FSEEK_OFF_MAX OFF_MAX | ||
| 538 | #elif defined(HAVE_LIMITS_H) | ||
| 539 | /* POSIX doesn't specify the minimum and maximum macros for off_t so | ||
| 540 | * we have to improvise and dance around implementation-defined | ||
| 541 | * behavior. This may fail if the off_t type has padding bits or | ||
| 542 | * is not a two's-complement representation. The compilers will detect | ||
| 543 | * and eliminate the dead code if off_t has 64 bits. | ||
| 544 | */ | ||
| 545 | #define FSEEK_OFF_MAX (((((off_t)1 << (sizeof(off_t) * CHAR_BIT - 2)) - 1) << 1) + 1) | ||
| 546 | #define FSEEK_OFF_MIN (-(FSEEK_OFF_MAX)-1) | ||
| 547 | #endif | ||
| 548 | #define fseek_off_t off_t | ||
| 549 | #define fseek fseeko | ||
| 550 | #define ftell ftello | ||
| 551 | #elif defined(HAVE__FSEEKI64) | ||
| 552 | #define fseek_off_t __int64 | ||
| 553 | #define fseek _fseeki64 | ||
| 554 | #define ftell _ftelli64 | ||
| 555 | #else | ||
| 556 | #ifdef HAVE_LIMITS_H | ||
| 557 | #define FSEEK_OFF_MIN LONG_MIN | ||
| 558 | #define FSEEK_OFF_MAX LONG_MAX | ||
| 559 | #endif | ||
| 560 | #define fseek_off_t long | ||
| 561 | #endif | ||
| 562 | |||
| 563 | static Sint64 SDLCALL stdio_seek(void *userdata, Sint64 offset, SDL_IOWhence whence) | ||
| 564 | { | ||
| 565 | IOStreamStdioData *iodata = (IOStreamStdioData *) userdata; | ||
| 566 | int stdiowhence; | ||
| 567 | |||
| 568 | switch (whence) { | ||
| 569 | case SDL_IO_SEEK_SET: | ||
| 570 | stdiowhence = SEEK_SET; | ||
| 571 | break; | ||
| 572 | case SDL_IO_SEEK_CUR: | ||
| 573 | stdiowhence = SEEK_CUR; | ||
| 574 | break; | ||
| 575 | case SDL_IO_SEEK_END: | ||
| 576 | stdiowhence = SEEK_END; | ||
| 577 | break; | ||
| 578 | default: | ||
| 579 | SDL_SetError("Unknown value for 'whence'"); | ||
| 580 | return -1; | ||
| 581 | } | ||
| 582 | |||
| 583 | #if defined(FSEEK_OFF_MIN) && defined(FSEEK_OFF_MAX) | ||
| 584 | if (offset < (Sint64)(FSEEK_OFF_MIN) || offset > (Sint64)(FSEEK_OFF_MAX)) { | ||
| 585 | SDL_SetError("Seek offset out of range"); | ||
| 586 | return -1; | ||
| 587 | } | ||
| 588 | #endif | ||
| 589 | |||
| 590 | // don't make a possibly-costly API call for the noop seek from SDL_TellIO | ||
| 591 | const bool is_noop = (whence == SDL_IO_SEEK_CUR) && (offset == 0); | ||
| 592 | |||
| 593 | if (is_noop || fseek(iodata->fp, (fseek_off_t)offset, stdiowhence) == 0) { | ||
| 594 | const Sint64 pos = ftell(iodata->fp); | ||
| 595 | if (pos < 0) { | ||
| 596 | SDL_SetError("Couldn't get stream offset: %s", strerror(errno)); | ||
| 597 | return -1; | ||
| 598 | } | ||
| 599 | return pos; | ||
| 600 | } | ||
| 601 | SDL_SetError("Error seeking in datastream: %s", strerror(errno)); | ||
| 602 | return -1; | ||
| 603 | } | ||
| 604 | |||
| 605 | static size_t SDLCALL stdio_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) | ||
| 606 | { | ||
| 607 | IOStreamStdioData *iodata = (IOStreamStdioData *) userdata; | ||
| 608 | const size_t bytes = fread(ptr, 1, size, iodata->fp); | ||
| 609 | if (bytes == 0 && ferror(iodata->fp)) { | ||
| 610 | if (errno == EAGAIN) { | ||
| 611 | *status = SDL_IO_STATUS_NOT_READY; | ||
| 612 | clearerr(iodata->fp); | ||
| 613 | } else { | ||
| 614 | SDL_SetError("Error reading from datastream: %s", strerror(errno)); | ||
| 615 | } | ||
| 616 | } | ||
| 617 | return bytes; | ||
| 618 | } | ||
| 619 | |||
| 620 | static size_t SDLCALL stdio_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status) | ||
| 621 | { | ||
| 622 | IOStreamStdioData *iodata = (IOStreamStdioData *) userdata; | ||
| 623 | const size_t bytes = fwrite(ptr, 1, size, iodata->fp); | ||
| 624 | if (bytes == 0 && ferror(iodata->fp)) { | ||
| 625 | if (errno == EAGAIN) { | ||
| 626 | *status = SDL_IO_STATUS_NOT_READY; | ||
| 627 | clearerr(iodata->fp); | ||
| 628 | } else { | ||
| 629 | SDL_SetError("Error writing to datastream: %s", strerror(errno)); | ||
| 630 | } | ||
| 631 | } | ||
| 632 | return bytes; | ||
| 633 | } | ||
| 634 | |||
| 635 | static bool SDLCALL stdio_flush(void *userdata, SDL_IOStatus *status) | ||
| 636 | { | ||
| 637 | IOStreamStdioData *iodata = (IOStreamStdioData *) userdata; | ||
| 638 | if (fflush(iodata->fp) != 0) { | ||
| 639 | if (errno == EAGAIN) { | ||
| 640 | *status = SDL_IO_STATUS_NOT_READY; | ||
| 641 | return false; | ||
| 642 | } else { | ||
| 643 | return SDL_SetError("Error flushing datastream: %s", strerror(errno)); | ||
| 644 | } | ||
| 645 | } | ||
| 646 | |||
| 647 | int result; | ||
| 648 | int fd = fileno(iodata->fp); | ||
| 649 | do { | ||
| 650 | result = SDL_fdatasync(fd); | ||
| 651 | } while (result < 0 && errno == EINTR); | ||
| 652 | |||
| 653 | if (result < 0) { | ||
| 654 | return SDL_SetError("Error flushing datastream: %s", strerror(errno)); | ||
| 655 | } | ||
| 656 | return true; | ||
| 657 | } | ||
| 658 | |||
| 659 | static bool SDLCALL stdio_close(void *userdata) | ||
| 660 | { | ||
| 661 | IOStreamStdioData *iodata = (IOStreamStdioData *) userdata; | ||
| 662 | bool status = true; | ||
| 663 | if (iodata->autoclose) { | ||
| 664 | if (fclose(iodata->fp) != 0) { | ||
| 665 | status = SDL_SetError("Error closing datastream: %s", strerror(errno)); | ||
| 666 | } | ||
| 667 | } | ||
| 668 | SDL_free(iodata); | ||
| 669 | return status; | ||
| 670 | } | ||
| 671 | |||
| 672 | SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose) | ||
| 673 | { | ||
| 674 | IOStreamStdioData *iodata = (IOStreamStdioData *) SDL_calloc(1, sizeof (*iodata)); | ||
| 675 | if (!iodata) { | ||
| 676 | if (autoclose) { | ||
| 677 | fclose(fp); | ||
| 678 | } | ||
| 679 | return NULL; | ||
| 680 | } | ||
| 681 | |||
| 682 | SDL_IOStreamInterface iface; | ||
| 683 | SDL_INIT_INTERFACE(&iface); | ||
| 684 | // There's no stdio_size because SDL_GetIOSize emulates it the same way we'd do it for stdio anyhow. | ||
| 685 | iface.seek = stdio_seek; | ||
| 686 | iface.read = stdio_read; | ||
| 687 | iface.write = stdio_write; | ||
| 688 | iface.flush = stdio_flush; | ||
| 689 | iface.close = stdio_close; | ||
| 690 | |||
| 691 | iodata->fp = fp; | ||
| 692 | iodata->autoclose = autoclose; | ||
| 693 | |||
| 694 | struct stat st; | ||
| 695 | iodata->regular_file = ((fstat(fileno(fp), &st) == 0) && S_ISREG(st.st_mode)); | ||
| 696 | |||
| 697 | SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata); | ||
| 698 | if (!iostr) { | ||
| 699 | iface.close(iodata); | ||
| 700 | } else { | ||
| 701 | const SDL_PropertiesID props = SDL_GetIOProperties(iostr); | ||
| 702 | if (props) { | ||
| 703 | SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_STDIO_FILE_POINTER, fp); | ||
| 704 | SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER, fileno(fp)); | ||
| 705 | } | ||
| 706 | } | ||
| 707 | |||
| 708 | return iostr; | ||
| 709 | } | ||
| 710 | #endif // !HAVE_STDIO_H && !defined(SDL_PLATFORM_WINDOWS) | ||
| 711 | |||
| 712 | // Functions to read/write memory pointers | ||
| 713 | |||
| 714 | typedef struct IOStreamMemData | ||
| 715 | { | ||
| 716 | Uint8 *base; | ||
| 717 | Uint8 *here; | ||
| 718 | Uint8 *stop; | ||
| 719 | } IOStreamMemData; | ||
| 720 | |||
| 721 | static Sint64 SDLCALL mem_size(void *userdata) | ||
| 722 | { | ||
| 723 | const IOStreamMemData *iodata = (IOStreamMemData *) userdata; | ||
| 724 | return (iodata->stop - iodata->base); | ||
| 725 | } | ||
| 726 | |||
| 727 | static Sint64 SDLCALL mem_seek(void *userdata, Sint64 offset, SDL_IOWhence whence) | ||
| 728 | { | ||
| 729 | IOStreamMemData *iodata = (IOStreamMemData *) userdata; | ||
| 730 | Uint8 *newpos; | ||
| 731 | |||
| 732 | switch (whence) { | ||
| 733 | case SDL_IO_SEEK_SET: | ||
| 734 | newpos = iodata->base + offset; | ||
| 735 | break; | ||
| 736 | case SDL_IO_SEEK_CUR: | ||
| 737 | newpos = iodata->here + offset; | ||
| 738 | break; | ||
| 739 | case SDL_IO_SEEK_END: | ||
| 740 | newpos = iodata->stop + offset; | ||
| 741 | break; | ||
| 742 | default: | ||
| 743 | SDL_SetError("Unknown value for 'whence'"); | ||
| 744 | return -1; | ||
| 745 | } | ||
| 746 | if (newpos < iodata->base) { | ||
| 747 | newpos = iodata->base; | ||
| 748 | } | ||
| 749 | if (newpos > iodata->stop) { | ||
| 750 | newpos = iodata->stop; | ||
| 751 | } | ||
| 752 | iodata->here = newpos; | ||
| 753 | return (Sint64)(iodata->here - iodata->base); | ||
| 754 | } | ||
| 755 | |||
| 756 | static size_t mem_io(void *userdata, void *dst, const void *src, size_t size) | ||
| 757 | { | ||
| 758 | IOStreamMemData *iodata = (IOStreamMemData *) userdata; | ||
| 759 | const size_t mem_available = (iodata->stop - iodata->here); | ||
| 760 | if (size > mem_available) { | ||
| 761 | size = mem_available; | ||
| 762 | } | ||
| 763 | SDL_memcpy(dst, src, size); | ||
| 764 | iodata->here += size; | ||
| 765 | return size; | ||
| 766 | } | ||
| 767 | |||
| 768 | static size_t SDLCALL mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) | ||
| 769 | { | ||
| 770 | IOStreamMemData *iodata = (IOStreamMemData *) userdata; | ||
| 771 | return mem_io(userdata, ptr, iodata->here, size); | ||
| 772 | } | ||
| 773 | |||
| 774 | static size_t SDLCALL mem_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status) | ||
| 775 | { | ||
| 776 | IOStreamMemData *iodata = (IOStreamMemData *) userdata; | ||
| 777 | return mem_io(userdata, iodata->here, ptr, size); | ||
| 778 | } | ||
| 779 | |||
| 780 | static bool SDLCALL mem_close(void *userdata) | ||
| 781 | { | ||
| 782 | SDL_free(userdata); | ||
| 783 | return true; | ||
| 784 | } | ||
| 785 | |||
| 786 | // Functions to create SDL_IOStream structures from various data sources | ||
| 787 | |||
| 788 | #if defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINDOWS) | ||
| 789 | static bool IsRegularFileOrPipe(FILE *f) | ||
| 790 | { | ||
| 791 | #ifndef SDL_PLATFORM_EMSCRIPTEN | ||
| 792 | struct stat st; | ||
| 793 | if (fstat(fileno(f), &st) < 0 || !(S_ISREG(st.st_mode) || S_ISFIFO(st.st_mode))) { | ||
| 794 | return false; | ||
| 795 | } | ||
| 796 | #endif // !SDL_PLATFORM_EMSCRIPTEN | ||
| 797 | |||
| 798 | return true; | ||
| 799 | } | ||
| 800 | #endif | ||
| 801 | |||
| 802 | SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode) | ||
| 803 | { | ||
| 804 | SDL_IOStream *iostr = NULL; | ||
| 805 | |||
| 806 | if (!file || !*file) { | ||
| 807 | SDL_InvalidParamError("file"); | ||
| 808 | return NULL; | ||
| 809 | } | ||
| 810 | if (!mode || !*mode) { | ||
| 811 | SDL_InvalidParamError("mode"); | ||
| 812 | return NULL; | ||
| 813 | } | ||
| 814 | |||
| 815 | #ifdef SDL_PLATFORM_ANDROID | ||
| 816 | #ifdef HAVE_STDIO_H | ||
| 817 | // Try to open the file on the filesystem first | ||
| 818 | if (*file == '/') { | ||
| 819 | FILE *fp = fopen(file, mode); | ||
| 820 | if (fp) { | ||
| 821 | if (!IsRegularFileOrPipe(fp)) { | ||
| 822 | fclose(fp); | ||
| 823 | SDL_SetError("%s is not a regular file or pipe", file); | ||
| 824 | return NULL; | ||
| 825 | } | ||
| 826 | return SDL_IOFromFP(fp, true); | ||
| 827 | } | ||
| 828 | } else if (SDL_strncmp(file, "content://", 10) == 0) { | ||
| 829 | // Try opening content:// URI | ||
| 830 | int fd = Android_JNI_OpenFileDescriptor(file, mode); | ||
| 831 | if (fd == -1) { | ||
| 832 | // SDL error is already set. | ||
| 833 | return NULL; | ||
| 834 | } | ||
| 835 | |||
| 836 | FILE *fp = fdopen(fd, mode); | ||
| 837 | if (!fp) { | ||
| 838 | close(fd); | ||
| 839 | SDL_SetError("Unable to open file descriptor (%d) from URI %s: %s", fd, file, strerror(errno)); | ||
| 840 | return NULL; | ||
| 841 | } | ||
| 842 | |||
| 843 | return SDL_IOFromFP(fp, true); | ||
| 844 | } else { | ||
| 845 | // Try opening it from internal storage if it's a relative path | ||
| 846 | char *path = NULL; | ||
| 847 | SDL_asprintf(&path, "%s/%s", SDL_GetAndroidInternalStoragePath(), file); | ||
| 848 | if (path) { | ||
| 849 | FILE *fp = fopen(path, mode); | ||
| 850 | SDL_free(path); | ||
| 851 | if (fp) { | ||
| 852 | if (!IsRegularFileOrPipe(fp)) { | ||
| 853 | fclose(fp); | ||
| 854 | SDL_SetError("%s is not a regular file or pipe", path); | ||
| 855 | return NULL; | ||
| 856 | } | ||
| 857 | return SDL_IOFromFP(fp, true); | ||
| 858 | } | ||
| 859 | } | ||
| 860 | } | ||
| 861 | #endif // HAVE_STDIO_H | ||
| 862 | |||
| 863 | // Try to open the file from the asset system | ||
| 864 | |||
| 865 | void *iodata = NULL; | ||
| 866 | if (!Android_JNI_FileOpen(&iodata, file, mode)) { | ||
| 867 | return NULL; | ||
| 868 | } | ||
| 869 | |||
| 870 | SDL_IOStreamInterface iface; | ||
| 871 | SDL_INIT_INTERFACE(&iface); | ||
| 872 | iface.size = Android_JNI_FileSize; | ||
| 873 | iface.seek = Android_JNI_FileSeek; | ||
| 874 | iface.read = Android_JNI_FileRead; | ||
| 875 | iface.write = Android_JNI_FileWrite; | ||
| 876 | iface.close = Android_JNI_FileClose; | ||
| 877 | |||
| 878 | iostr = SDL_OpenIO(&iface, iodata); | ||
| 879 | if (!iostr) { | ||
| 880 | iface.close(iodata); | ||
| 881 | } else { | ||
| 882 | const SDL_PropertiesID props = SDL_GetIOProperties(iostr); | ||
| 883 | if (props) { | ||
| 884 | SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER, iodata); | ||
| 885 | } | ||
| 886 | } | ||
| 887 | |||
| 888 | #elif defined(SDL_PLATFORM_WINDOWS) | ||
| 889 | HANDLE handle = windows_file_open(file, mode); | ||
| 890 | if (handle != INVALID_HANDLE_VALUE) { | ||
| 891 | iostr = SDL_IOFromHandle(handle, mode, true); | ||
| 892 | } | ||
| 893 | |||
| 894 | #elif defined(HAVE_STDIO_H) | ||
| 895 | { | ||
| 896 | #if defined(SDL_PLATFORM_3DS) | ||
| 897 | FILE *fp = N3DS_FileOpen(file, mode); | ||
| 898 | #else | ||
| 899 | FILE *fp = fopen(file, mode); | ||
| 900 | #endif | ||
| 901 | |||
| 902 | if (!fp) { | ||
| 903 | SDL_SetError("Couldn't open %s: %s", file, strerror(errno)); | ||
| 904 | } else if (!IsRegularFileOrPipe(fp)) { | ||
| 905 | fclose(fp); | ||
| 906 | fp = NULL; | ||
| 907 | SDL_SetError("%s is not a regular file or pipe", file); | ||
| 908 | } else { | ||
| 909 | iostr = SDL_IOFromFP(fp, true); | ||
| 910 | } | ||
| 911 | } | ||
| 912 | |||
| 913 | #else | ||
| 914 | SDL_SetError("SDL not compiled with stdio support"); | ||
| 915 | #endif // !HAVE_STDIO_H | ||
| 916 | |||
| 917 | return iostr; | ||
| 918 | } | ||
| 919 | |||
| 920 | SDL_IOStream *SDL_IOFromMem(void *mem, size_t size) | ||
| 921 | { | ||
| 922 | if (!mem) { | ||
| 923 | SDL_InvalidParamError("mem"); | ||
| 924 | return NULL; | ||
| 925 | } else if (!size) { | ||
| 926 | SDL_InvalidParamError("size"); | ||
| 927 | return NULL; | ||
| 928 | } | ||
| 929 | |||
| 930 | IOStreamMemData *iodata = (IOStreamMemData *) SDL_calloc(1, sizeof (*iodata)); | ||
| 931 | if (!iodata) { | ||
| 932 | return NULL; | ||
| 933 | } | ||
| 934 | |||
| 935 | SDL_IOStreamInterface iface; | ||
| 936 | SDL_INIT_INTERFACE(&iface); | ||
| 937 | iface.size = mem_size; | ||
| 938 | iface.seek = mem_seek; | ||
| 939 | iface.read = mem_read; | ||
| 940 | iface.write = mem_write; | ||
| 941 | iface.close = mem_close; | ||
| 942 | |||
| 943 | iodata->base = (Uint8 *)mem; | ||
| 944 | iodata->here = iodata->base; | ||
| 945 | iodata->stop = iodata->base + size; | ||
| 946 | |||
| 947 | SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata); | ||
| 948 | if (!iostr) { | ||
| 949 | SDL_free(iodata); | ||
| 950 | } else { | ||
| 951 | const SDL_PropertiesID props = SDL_GetIOProperties(iostr); | ||
| 952 | if (props) { | ||
| 953 | SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER, mem); | ||
| 954 | SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER, size); | ||
| 955 | } | ||
| 956 | } | ||
| 957 | return iostr; | ||
| 958 | } | ||
| 959 | |||
| 960 | SDL_IOStream *SDL_IOFromConstMem(const void *mem, size_t size) | ||
| 961 | { | ||
| 962 | if (!mem) { | ||
| 963 | SDL_InvalidParamError("mem"); | ||
| 964 | return NULL; | ||
| 965 | } else if (!size) { | ||
| 966 | SDL_InvalidParamError("size"); | ||
| 967 | return NULL; | ||
| 968 | } | ||
| 969 | |||
| 970 | IOStreamMemData *iodata = (IOStreamMemData *) SDL_calloc(1, sizeof (*iodata)); | ||
| 971 | if (!iodata) { | ||
| 972 | return NULL; | ||
| 973 | } | ||
| 974 | |||
| 975 | SDL_IOStreamInterface iface; | ||
| 976 | SDL_INIT_INTERFACE(&iface); | ||
| 977 | iface.size = mem_size; | ||
| 978 | iface.seek = mem_seek; | ||
| 979 | iface.read = mem_read; | ||
| 980 | // leave iface.write as NULL. | ||
| 981 | iface.close = mem_close; | ||
| 982 | |||
| 983 | iodata->base = (Uint8 *)mem; | ||
| 984 | iodata->here = iodata->base; | ||
| 985 | iodata->stop = iodata->base + size; | ||
| 986 | |||
| 987 | SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata); | ||
| 988 | if (!iostr) { | ||
| 989 | SDL_free(iodata); | ||
| 990 | } else { | ||
| 991 | const SDL_PropertiesID props = SDL_GetIOProperties(iostr); | ||
| 992 | if (props) { | ||
| 993 | SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER, (void *)mem); | ||
| 994 | SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER, size); | ||
| 995 | } | ||
| 996 | } | ||
| 997 | return iostr; | ||
| 998 | } | ||
| 999 | |||
| 1000 | typedef struct IOStreamDynamicMemData | ||
| 1001 | { | ||
| 1002 | SDL_IOStream *stream; | ||
| 1003 | IOStreamMemData data; | ||
| 1004 | Uint8 *end; | ||
| 1005 | } IOStreamDynamicMemData; | ||
| 1006 | |||
| 1007 | static Sint64 SDLCALL dynamic_mem_size(void *userdata) | ||
| 1008 | { | ||
| 1009 | IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; | ||
| 1010 | return mem_size(&iodata->data); | ||
| 1011 | } | ||
| 1012 | |||
| 1013 | static Sint64 SDLCALL dynamic_mem_seek(void *userdata, Sint64 offset, SDL_IOWhence whence) | ||
| 1014 | { | ||
| 1015 | IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; | ||
| 1016 | return mem_seek(&iodata->data, offset, whence); | ||
| 1017 | } | ||
| 1018 | |||
| 1019 | static size_t SDLCALL dynamic_mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) | ||
| 1020 | { | ||
| 1021 | IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; | ||
| 1022 | return mem_io(&iodata->data, ptr, iodata->data.here, size); | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | static bool dynamic_mem_realloc(IOStreamDynamicMemData *iodata, size_t size) | ||
| 1026 | { | ||
| 1027 | size_t chunksize = (size_t)SDL_GetNumberProperty(SDL_GetIOProperties(iodata->stream), SDL_PROP_IOSTREAM_DYNAMIC_CHUNKSIZE_NUMBER, 0); | ||
| 1028 | if (!chunksize) { | ||
| 1029 | chunksize = 1024; | ||
| 1030 | } | ||
| 1031 | |||
| 1032 | // We're intentionally allocating more memory than needed so it can be null terminated | ||
| 1033 | size_t chunks = (((iodata->end - iodata->data.base) + size) / chunksize) + 1; | ||
| 1034 | size_t length = (chunks * chunksize); | ||
| 1035 | Uint8 *base = (Uint8 *)SDL_realloc(iodata->data.base, length); | ||
| 1036 | if (!base) { | ||
| 1037 | return false; | ||
| 1038 | } | ||
| 1039 | |||
| 1040 | size_t here_offset = (iodata->data.here - iodata->data.base); | ||
| 1041 | size_t stop_offset = (iodata->data.stop - iodata->data.base); | ||
| 1042 | iodata->data.base = base; | ||
| 1043 | iodata->data.here = base + here_offset; | ||
| 1044 | iodata->data.stop = base + stop_offset; | ||
| 1045 | iodata->end = base + length; | ||
| 1046 | return SDL_SetPointerProperty(SDL_GetIOProperties(iodata->stream), SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, base); | ||
| 1047 | } | ||
| 1048 | |||
| 1049 | static size_t SDLCALL dynamic_mem_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status) | ||
| 1050 | { | ||
| 1051 | IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; | ||
| 1052 | if (size > (size_t)(iodata->data.stop - iodata->data.here)) { | ||
| 1053 | if (size > (size_t)(iodata->end - iodata->data.here)) { | ||
| 1054 | if (!dynamic_mem_realloc(iodata, size)) { | ||
| 1055 | return 0; | ||
| 1056 | } | ||
| 1057 | } | ||
| 1058 | iodata->data.stop = iodata->data.here + size; | ||
| 1059 | } | ||
| 1060 | return mem_io(&iodata->data, iodata->data.here, ptr, size); | ||
| 1061 | } | ||
| 1062 | |||
| 1063 | static bool SDLCALL dynamic_mem_close(void *userdata) | ||
| 1064 | { | ||
| 1065 | const IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; | ||
| 1066 | void *mem = SDL_GetPointerProperty(SDL_GetIOProperties(iodata->stream), SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL); | ||
| 1067 | if (mem) { | ||
| 1068 | SDL_free(mem); | ||
| 1069 | } | ||
| 1070 | SDL_free(userdata); | ||
| 1071 | return true; | ||
| 1072 | } | ||
| 1073 | |||
| 1074 | SDL_IOStream *SDL_IOFromDynamicMem(void) | ||
| 1075 | { | ||
| 1076 | IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) SDL_calloc(1, sizeof (*iodata)); | ||
| 1077 | if (!iodata) { | ||
| 1078 | return NULL; | ||
| 1079 | } | ||
| 1080 | |||
| 1081 | SDL_IOStreamInterface iface; | ||
| 1082 | SDL_INIT_INTERFACE(&iface); | ||
| 1083 | iface.size = dynamic_mem_size; | ||
| 1084 | iface.seek = dynamic_mem_seek; | ||
| 1085 | iface.read = dynamic_mem_read; | ||
| 1086 | iface.write = dynamic_mem_write; | ||
| 1087 | iface.close = dynamic_mem_close; | ||
| 1088 | |||
| 1089 | SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata); | ||
| 1090 | if (iostr) { | ||
| 1091 | iodata->stream = iostr; | ||
| 1092 | } else { | ||
| 1093 | SDL_free(iodata); | ||
| 1094 | } | ||
| 1095 | return iostr; | ||
| 1096 | } | ||
| 1097 | |||
| 1098 | SDL_IOStatus SDL_GetIOStatus(SDL_IOStream *context) | ||
| 1099 | { | ||
| 1100 | if (!context) { | ||
| 1101 | SDL_InvalidParamError("context"); | ||
| 1102 | return SDL_IO_STATUS_ERROR; | ||
| 1103 | } | ||
| 1104 | return context->status; | ||
| 1105 | } | ||
| 1106 | |||
| 1107 | SDL_IOStream *SDL_OpenIO(const SDL_IOStreamInterface *iface, void *userdata) | ||
| 1108 | { | ||
| 1109 | if (!iface) { | ||
| 1110 | SDL_InvalidParamError("iface"); | ||
| 1111 | return NULL; | ||
| 1112 | } | ||
| 1113 | if (iface->version < sizeof(*iface)) { | ||
| 1114 | // Update this to handle older versions of this interface | ||
| 1115 | SDL_SetError("Invalid interface, should be initialized with SDL_INIT_INTERFACE()"); | ||
| 1116 | return NULL; | ||
| 1117 | } | ||
| 1118 | |||
| 1119 | SDL_IOStream *iostr = (SDL_IOStream *)SDL_calloc(1, sizeof(*iostr)); | ||
| 1120 | if (iostr) { | ||
| 1121 | SDL_copyp(&iostr->iface, iface); | ||
| 1122 | iostr->userdata = userdata; | ||
| 1123 | } | ||
| 1124 | return iostr; | ||
| 1125 | } | ||
| 1126 | |||
| 1127 | bool SDL_CloseIO(SDL_IOStream *iostr) | ||
| 1128 | { | ||
| 1129 | bool result = true; | ||
| 1130 | if (iostr) { | ||
| 1131 | if (iostr->iface.close) { | ||
| 1132 | result = iostr->iface.close(iostr->userdata); | ||
| 1133 | } | ||
| 1134 | SDL_DestroyProperties(iostr->props); | ||
| 1135 | SDL_free(iostr); | ||
| 1136 | } | ||
| 1137 | return result; | ||
| 1138 | } | ||
| 1139 | |||
| 1140 | // Load all the data from an SDL data stream | ||
| 1141 | void *SDL_LoadFile_IO(SDL_IOStream *src, size_t *datasize, bool closeio) | ||
| 1142 | { | ||
| 1143 | const int FILE_CHUNK_SIZE = 1024; | ||
| 1144 | Sint64 size, size_total = 0; | ||
| 1145 | size_t size_read; | ||
| 1146 | char *data = NULL, *newdata; | ||
| 1147 | bool loading_chunks = false; | ||
| 1148 | |||
| 1149 | if (!src) { | ||
| 1150 | SDL_InvalidParamError("src"); | ||
| 1151 | goto done; | ||
| 1152 | } | ||
| 1153 | |||
| 1154 | size = SDL_GetIOSize(src); | ||
| 1155 | if (size < 0) { | ||
| 1156 | size = FILE_CHUNK_SIZE; | ||
| 1157 | loading_chunks = true; | ||
| 1158 | } | ||
| 1159 | if (size >= SDL_SIZE_MAX - 1) { | ||
| 1160 | goto done; | ||
| 1161 | } | ||
| 1162 | data = (char *)SDL_malloc((size_t)(size + 1)); | ||
| 1163 | if (!data) { | ||
| 1164 | goto done; | ||
| 1165 | } | ||
| 1166 | |||
| 1167 | size_total = 0; | ||
| 1168 | for (;;) { | ||
| 1169 | if (loading_chunks) { | ||
| 1170 | if ((size_total + FILE_CHUNK_SIZE) > size) { | ||
| 1171 | size = (size_total + FILE_CHUNK_SIZE); | ||
| 1172 | if (size >= SDL_SIZE_MAX - 1) { | ||
| 1173 | newdata = NULL; | ||
| 1174 | } else { | ||
| 1175 | newdata = (char *)SDL_realloc(data, (size_t)(size + 1)); | ||
| 1176 | } | ||
| 1177 | if (!newdata) { | ||
| 1178 | SDL_free(data); | ||
| 1179 | data = NULL; | ||
| 1180 | goto done; | ||
| 1181 | } | ||
| 1182 | data = newdata; | ||
| 1183 | } | ||
| 1184 | } | ||
| 1185 | |||
| 1186 | size_read = SDL_ReadIO(src, data + size_total, (size_t)(size - size_total)); | ||
| 1187 | if (size_read > 0) { | ||
| 1188 | size_total += size_read; | ||
| 1189 | continue; | ||
| 1190 | } else if (SDL_GetIOStatus(src) == SDL_IO_STATUS_NOT_READY) { | ||
| 1191 | // Wait for the stream to be ready | ||
| 1192 | SDL_Delay(1); | ||
| 1193 | continue; | ||
| 1194 | } | ||
| 1195 | |||
| 1196 | // The stream status will remain set for the caller to check | ||
| 1197 | break; | ||
| 1198 | } | ||
| 1199 | |||
| 1200 | data[size_total] = '\0'; | ||
| 1201 | |||
| 1202 | done: | ||
| 1203 | if (datasize) { | ||
| 1204 | *datasize = (size_t)size_total; | ||
| 1205 | } | ||
| 1206 | if (closeio && src) { | ||
| 1207 | SDL_CloseIO(src); | ||
| 1208 | } | ||
| 1209 | return data; | ||
| 1210 | } | ||
| 1211 | |||
| 1212 | void *SDL_LoadFile(const char *file, size_t *datasize) | ||
| 1213 | { | ||
| 1214 | SDL_IOStream *stream = SDL_IOFromFile(file, "rb"); | ||
| 1215 | if (!stream) { | ||
| 1216 | if (datasize) { | ||
| 1217 | *datasize = 0; | ||
| 1218 | } | ||
| 1219 | return NULL; | ||
| 1220 | } | ||
| 1221 | return SDL_LoadFile_IO(stream, datasize, true); | ||
| 1222 | } | ||
| 1223 | |||
| 1224 | bool SDL_SaveFile_IO(SDL_IOStream *src, const void *data, size_t datasize, bool closeio) | ||
| 1225 | { | ||
| 1226 | size_t size_written = 0; | ||
| 1227 | size_t size_total = 0; | ||
| 1228 | bool success = true; | ||
| 1229 | |||
| 1230 | if (!src) { | ||
| 1231 | SDL_InvalidParamError("src"); | ||
| 1232 | goto done; | ||
| 1233 | } | ||
| 1234 | |||
| 1235 | if (!data && datasize > 0) { | ||
| 1236 | SDL_InvalidParamError("data"); | ||
| 1237 | goto done; | ||
| 1238 | } | ||
| 1239 | |||
| 1240 | if (datasize > 0) { | ||
| 1241 | while (size_total < datasize) { | ||
| 1242 | size_written = SDL_WriteIO(src, ((const char *) data) + size_written, datasize - size_written); | ||
| 1243 | |||
| 1244 | if (size_written <= 0) { | ||
| 1245 | if (SDL_GetIOStatus(src) == SDL_IO_STATUS_NOT_READY) { | ||
| 1246 | // Wait for the stream to be ready | ||
| 1247 | SDL_Delay(1); | ||
| 1248 | continue; | ||
| 1249 | } else { | ||
| 1250 | success = false; | ||
| 1251 | goto done; | ||
| 1252 | } | ||
| 1253 | } | ||
| 1254 | |||
| 1255 | size_total += size_written; | ||
| 1256 | } | ||
| 1257 | } | ||
| 1258 | |||
| 1259 | done: | ||
| 1260 | if (closeio && src) { | ||
| 1261 | SDL_CloseIO(src); | ||
| 1262 | } | ||
| 1263 | |||
| 1264 | return success; | ||
| 1265 | } | ||
| 1266 | |||
| 1267 | bool SDL_SaveFile(const char *file, const void *data, size_t datasize) | ||
| 1268 | { | ||
| 1269 | SDL_IOStream *stream = SDL_IOFromFile(file, "wb"); | ||
| 1270 | if (!stream) { | ||
| 1271 | return false; | ||
| 1272 | } | ||
| 1273 | return SDL_SaveFile_IO(stream, data, datasize, true); | ||
| 1274 | } | ||
| 1275 | |||
| 1276 | SDL_PropertiesID SDL_GetIOProperties(SDL_IOStream *context) | ||
| 1277 | { | ||
| 1278 | if (!context) { | ||
| 1279 | SDL_InvalidParamError("context"); | ||
| 1280 | return 0; | ||
| 1281 | } | ||
| 1282 | |||
| 1283 | if (context->props == 0) { | ||
| 1284 | context->props = SDL_CreateProperties(); | ||
| 1285 | } | ||
| 1286 | return context->props; | ||
| 1287 | } | ||
| 1288 | |||
| 1289 | Sint64 SDL_GetIOSize(SDL_IOStream *context) | ||
| 1290 | { | ||
| 1291 | if (!context) { | ||
| 1292 | return SDL_InvalidParamError("context"); | ||
| 1293 | } | ||
| 1294 | if (!context->iface.size) { | ||
| 1295 | Sint64 pos, size; | ||
| 1296 | |||
| 1297 | pos = SDL_SeekIO(context, 0, SDL_IO_SEEK_CUR); | ||
| 1298 | if (pos < 0) { | ||
| 1299 | return -1; | ||
| 1300 | } | ||
| 1301 | size = SDL_SeekIO(context, 0, SDL_IO_SEEK_END); | ||
| 1302 | |||
| 1303 | SDL_SeekIO(context, pos, SDL_IO_SEEK_SET); | ||
| 1304 | return size; | ||
| 1305 | } | ||
| 1306 | return context->iface.size(context->userdata); | ||
| 1307 | } | ||
| 1308 | |||
| 1309 | Sint64 SDL_SeekIO(SDL_IOStream *context, Sint64 offset, SDL_IOWhence whence) | ||
| 1310 | { | ||
| 1311 | if (!context) { | ||
| 1312 | SDL_InvalidParamError("context"); | ||
| 1313 | return -1; | ||
| 1314 | } else if (!context->iface.seek) { | ||
| 1315 | SDL_Unsupported(); | ||
| 1316 | return -1; | ||
| 1317 | } | ||
| 1318 | return context->iface.seek(context->userdata, offset, whence); | ||
| 1319 | } | ||
| 1320 | |||
| 1321 | Sint64 SDL_TellIO(SDL_IOStream *context) | ||
| 1322 | { | ||
| 1323 | return SDL_SeekIO(context, 0, SDL_IO_SEEK_CUR); | ||
| 1324 | } | ||
| 1325 | |||
| 1326 | size_t SDL_ReadIO(SDL_IOStream *context, void *ptr, size_t size) | ||
| 1327 | { | ||
| 1328 | size_t bytes; | ||
| 1329 | |||
| 1330 | if (!context) { | ||
| 1331 | SDL_InvalidParamError("context"); | ||
| 1332 | return 0; | ||
| 1333 | } else if (!context->iface.read) { | ||
| 1334 | context->status = SDL_IO_STATUS_WRITEONLY; | ||
| 1335 | SDL_Unsupported(); | ||
| 1336 | return 0; | ||
| 1337 | } | ||
| 1338 | |||
| 1339 | context->status = SDL_IO_STATUS_READY; | ||
| 1340 | SDL_ClearError(); | ||
| 1341 | |||
| 1342 | if (size == 0) { | ||
| 1343 | return 0; | ||
| 1344 | } | ||
| 1345 | |||
| 1346 | bytes = context->iface.read(context->userdata, ptr, size, &context->status); | ||
| 1347 | if (bytes == 0 && context->status == SDL_IO_STATUS_READY) { | ||
| 1348 | if (*SDL_GetError()) { | ||
| 1349 | context->status = SDL_IO_STATUS_ERROR; | ||
| 1350 | } else { | ||
| 1351 | context->status = SDL_IO_STATUS_EOF; | ||
| 1352 | } | ||
| 1353 | } | ||
| 1354 | return bytes; | ||
| 1355 | } | ||
| 1356 | |||
| 1357 | size_t SDL_WriteIO(SDL_IOStream *context, const void *ptr, size_t size) | ||
| 1358 | { | ||
| 1359 | size_t bytes; | ||
| 1360 | |||
| 1361 | if (!context) { | ||
| 1362 | SDL_InvalidParamError("context"); | ||
| 1363 | return 0; | ||
| 1364 | } else if (!context->iface.write) { | ||
| 1365 | context->status = SDL_IO_STATUS_READONLY; | ||
| 1366 | SDL_Unsupported(); | ||
| 1367 | return 0; | ||
| 1368 | } | ||
| 1369 | |||
| 1370 | context->status = SDL_IO_STATUS_READY; | ||
| 1371 | SDL_ClearError(); | ||
| 1372 | |||
| 1373 | if (size == 0) { | ||
| 1374 | return 0; | ||
| 1375 | } | ||
| 1376 | |||
| 1377 | bytes = context->iface.write(context->userdata, ptr, size, &context->status); | ||
| 1378 | if ((bytes == 0) && (context->status == SDL_IO_STATUS_READY)) { | ||
| 1379 | context->status = SDL_IO_STATUS_ERROR; | ||
| 1380 | } | ||
| 1381 | return bytes; | ||
| 1382 | } | ||
| 1383 | |||
| 1384 | size_t SDL_IOprintf(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...) | ||
| 1385 | { | ||
| 1386 | va_list ap; | ||
| 1387 | int size; | ||
| 1388 | char *string; | ||
| 1389 | size_t bytes; | ||
| 1390 | |||
| 1391 | va_start(ap, fmt); | ||
| 1392 | size = SDL_vasprintf(&string, fmt, ap); | ||
| 1393 | va_end(ap); | ||
| 1394 | if (size < 0) { | ||
| 1395 | return 0; | ||
| 1396 | } | ||
| 1397 | |||
| 1398 | bytes = SDL_WriteIO(context, string, (size_t)size); | ||
| 1399 | SDL_free(string); | ||
| 1400 | return bytes; | ||
| 1401 | } | ||
| 1402 | |||
| 1403 | size_t SDL_IOvprintf(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) | ||
| 1404 | { | ||
| 1405 | int size; | ||
| 1406 | char *string; | ||
| 1407 | size_t bytes; | ||
| 1408 | |||
| 1409 | size = SDL_vasprintf(&string, fmt, ap); | ||
| 1410 | if (size < 0) { | ||
| 1411 | return 0; | ||
| 1412 | } | ||
| 1413 | |||
| 1414 | bytes = SDL_WriteIO(context, string, (size_t)size); | ||
| 1415 | SDL_free(string); | ||
| 1416 | return bytes; | ||
| 1417 | } | ||
| 1418 | |||
| 1419 | bool SDL_FlushIO(SDL_IOStream *context) | ||
| 1420 | { | ||
| 1421 | bool result = true; | ||
| 1422 | |||
| 1423 | if (!context) { | ||
| 1424 | return SDL_InvalidParamError("context"); | ||
| 1425 | } | ||
| 1426 | |||
| 1427 | context->status = SDL_IO_STATUS_READY; | ||
| 1428 | SDL_ClearError(); | ||
| 1429 | |||
| 1430 | if (context->iface.flush) { | ||
| 1431 | result = context->iface.flush(context->userdata, &context->status); | ||
| 1432 | } | ||
| 1433 | if (!result && (context->status == SDL_IO_STATUS_READY)) { | ||
| 1434 | context->status = SDL_IO_STATUS_ERROR; | ||
| 1435 | } | ||
| 1436 | return result; | ||
| 1437 | } | ||
| 1438 | |||
| 1439 | // Functions for dynamically reading and writing endian-specific values | ||
| 1440 | |||
| 1441 | bool SDL_ReadU8(SDL_IOStream *src, Uint8 *value) | ||
| 1442 | { | ||
| 1443 | Uint8 data = 0; | ||
| 1444 | bool result = false; | ||
| 1445 | |||
| 1446 | if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) { | ||
| 1447 | result = true; | ||
| 1448 | } | ||
| 1449 | if (value) { | ||
| 1450 | *value = data; | ||
| 1451 | } | ||
| 1452 | return result; | ||
| 1453 | } | ||
| 1454 | |||
| 1455 | bool SDL_ReadS8(SDL_IOStream *src, Sint8 *value) | ||
| 1456 | { | ||
| 1457 | Sint8 data = 0; | ||
| 1458 | bool result = false; | ||
| 1459 | |||
| 1460 | if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) { | ||
| 1461 | result = true; | ||
| 1462 | } | ||
| 1463 | if (value) { | ||
| 1464 | *value = data; | ||
| 1465 | } | ||
| 1466 | return result; | ||
| 1467 | } | ||
| 1468 | |||
| 1469 | bool SDL_ReadU16LE(SDL_IOStream *src, Uint16 *value) | ||
| 1470 | { | ||
| 1471 | Uint16 data = 0; | ||
| 1472 | bool result = false; | ||
| 1473 | |||
| 1474 | if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) { | ||
| 1475 | result = true; | ||
| 1476 | } | ||
| 1477 | if (value) { | ||
| 1478 | *value = SDL_Swap16LE(data); | ||
| 1479 | } | ||
| 1480 | return result; | ||
| 1481 | } | ||
| 1482 | |||
| 1483 | bool SDL_ReadS16LE(SDL_IOStream *src, Sint16 *value) | ||
| 1484 | { | ||
| 1485 | return SDL_ReadU16LE(src, (Uint16 *)value); | ||
| 1486 | } | ||
| 1487 | |||
| 1488 | bool SDL_ReadU16BE(SDL_IOStream *src, Uint16 *value) | ||
| 1489 | { | ||
| 1490 | Uint16 data = 0; | ||
| 1491 | bool result = false; | ||
| 1492 | |||
| 1493 | if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) { | ||
| 1494 | result = true; | ||
| 1495 | } | ||
| 1496 | if (value) { | ||
| 1497 | *value = SDL_Swap16BE(data); | ||
| 1498 | } | ||
| 1499 | return result; | ||
| 1500 | } | ||
| 1501 | |||
| 1502 | bool SDL_ReadS16BE(SDL_IOStream *src, Sint16 *value) | ||
| 1503 | { | ||
| 1504 | return SDL_ReadU16BE(src, (Uint16 *)value); | ||
| 1505 | } | ||
| 1506 | |||
| 1507 | bool SDL_ReadU32LE(SDL_IOStream *src, Uint32 *value) | ||
| 1508 | { | ||
| 1509 | Uint32 data = 0; | ||
| 1510 | bool result = false; | ||
| 1511 | |||
| 1512 | if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) { | ||
| 1513 | result = true; | ||
| 1514 | } | ||
| 1515 | if (value) { | ||
| 1516 | *value = SDL_Swap32LE(data); | ||
| 1517 | } | ||
| 1518 | return result; | ||
| 1519 | } | ||
| 1520 | |||
| 1521 | bool SDL_ReadS32LE(SDL_IOStream *src, Sint32 *value) | ||
| 1522 | { | ||
| 1523 | return SDL_ReadU32LE(src, (Uint32 *)value); | ||
| 1524 | } | ||
| 1525 | |||
| 1526 | bool SDL_ReadU32BE(SDL_IOStream *src, Uint32 *value) | ||
| 1527 | { | ||
| 1528 | Uint32 data = 0; | ||
| 1529 | bool result = false; | ||
| 1530 | |||
| 1531 | if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) { | ||
| 1532 | result = true; | ||
| 1533 | } | ||
| 1534 | if (value) { | ||
| 1535 | *value = SDL_Swap32BE(data); | ||
| 1536 | } | ||
| 1537 | return result; | ||
| 1538 | } | ||
| 1539 | |||
| 1540 | bool SDL_ReadS32BE(SDL_IOStream *src, Sint32 *value) | ||
| 1541 | { | ||
| 1542 | return SDL_ReadU32BE(src, (Uint32 *)value); | ||
| 1543 | } | ||
| 1544 | |||
| 1545 | bool SDL_ReadU64LE(SDL_IOStream *src, Uint64 *value) | ||
| 1546 | { | ||
| 1547 | Uint64 data = 0; | ||
| 1548 | bool result = false; | ||
| 1549 | |||
| 1550 | if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) { | ||
| 1551 | result = true; | ||
| 1552 | } | ||
| 1553 | if (value) { | ||
| 1554 | *value = SDL_Swap64LE(data); | ||
| 1555 | } | ||
| 1556 | return result; | ||
| 1557 | } | ||
| 1558 | |||
| 1559 | bool SDL_ReadS64LE(SDL_IOStream *src, Sint64 *value) | ||
| 1560 | { | ||
| 1561 | return SDL_ReadU64LE(src, (Uint64 *)value); | ||
| 1562 | } | ||
| 1563 | |||
| 1564 | bool SDL_ReadU64BE(SDL_IOStream *src, Uint64 *value) | ||
| 1565 | { | ||
| 1566 | Uint64 data = 0; | ||
| 1567 | bool result = false; | ||
| 1568 | |||
| 1569 | if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) { | ||
| 1570 | result = true; | ||
| 1571 | } | ||
| 1572 | if (value) { | ||
| 1573 | *value = SDL_Swap64BE(data); | ||
| 1574 | } | ||
| 1575 | return result; | ||
| 1576 | } | ||
| 1577 | |||
| 1578 | bool SDL_ReadS64BE(SDL_IOStream *src, Sint64 *value) | ||
| 1579 | { | ||
| 1580 | return SDL_ReadU64BE(src, (Uint64 *)value); | ||
| 1581 | } | ||
| 1582 | |||
| 1583 | bool SDL_WriteU8(SDL_IOStream *dst, Uint8 value) | ||
| 1584 | { | ||
| 1585 | return (SDL_WriteIO(dst, &value, sizeof(value)) == sizeof(value)); | ||
| 1586 | } | ||
| 1587 | |||
| 1588 | bool SDL_WriteS8(SDL_IOStream *dst, Sint8 value) | ||
| 1589 | { | ||
| 1590 | return (SDL_WriteIO(dst, &value, sizeof(value)) == sizeof(value)); | ||
| 1591 | } | ||
| 1592 | |||
| 1593 | bool SDL_WriteU16LE(SDL_IOStream *dst, Uint16 value) | ||
| 1594 | { | ||
| 1595 | const Uint16 swapped = SDL_Swap16LE(value); | ||
| 1596 | return (SDL_WriteIO(dst, &swapped, sizeof(swapped)) == sizeof(swapped)); | ||
| 1597 | } | ||
| 1598 | |||
| 1599 | bool SDL_WriteS16LE(SDL_IOStream *dst, Sint16 value) | ||
| 1600 | { | ||
| 1601 | return SDL_WriteU16LE(dst, (Uint16)value); | ||
| 1602 | } | ||
| 1603 | |||
| 1604 | bool SDL_WriteU16BE(SDL_IOStream *dst, Uint16 value) | ||
| 1605 | { | ||
| 1606 | const Uint16 swapped = SDL_Swap16BE(value); | ||
| 1607 | return (SDL_WriteIO(dst, &swapped, sizeof(swapped)) == sizeof(swapped)); | ||
| 1608 | } | ||
| 1609 | |||
| 1610 | bool SDL_WriteS16BE(SDL_IOStream *dst, Sint16 value) | ||
| 1611 | { | ||
| 1612 | return SDL_WriteU16BE(dst, (Uint16)value); | ||
| 1613 | } | ||
| 1614 | |||
| 1615 | bool SDL_WriteU32LE(SDL_IOStream *dst, Uint32 value) | ||
| 1616 | { | ||
| 1617 | const Uint32 swapped = SDL_Swap32LE(value); | ||
| 1618 | return (SDL_WriteIO(dst, &swapped, sizeof(swapped)) == sizeof(swapped)); | ||
| 1619 | } | ||
| 1620 | |||
| 1621 | bool SDL_WriteS32LE(SDL_IOStream *dst, Sint32 value) | ||
| 1622 | { | ||
| 1623 | return SDL_WriteU32LE(dst, (Uint32)value); | ||
| 1624 | } | ||
| 1625 | |||
| 1626 | bool SDL_WriteU32BE(SDL_IOStream *dst, Uint32 value) | ||
| 1627 | { | ||
| 1628 | const Uint32 swapped = SDL_Swap32BE(value); | ||
| 1629 | return (SDL_WriteIO(dst, &swapped, sizeof(swapped)) == sizeof(swapped)); | ||
| 1630 | } | ||
| 1631 | |||
| 1632 | bool SDL_WriteS32BE(SDL_IOStream *dst, Sint32 value) | ||
| 1633 | { | ||
| 1634 | return SDL_WriteU32BE(dst, (Uint32)value); | ||
| 1635 | } | ||
| 1636 | |||
| 1637 | bool SDL_WriteU64LE(SDL_IOStream *dst, Uint64 value) | ||
| 1638 | { | ||
| 1639 | const Uint64 swapped = SDL_Swap64LE(value); | ||
| 1640 | return (SDL_WriteIO(dst, &swapped, sizeof(swapped)) == sizeof(swapped)); | ||
| 1641 | } | ||
| 1642 | |||
| 1643 | bool SDL_WriteS64LE(SDL_IOStream *dst, Sint64 value) | ||
| 1644 | { | ||
| 1645 | return SDL_WriteU64LE(dst, (Uint64)value); | ||
| 1646 | } | ||
| 1647 | |||
| 1648 | bool SDL_WriteU64BE(SDL_IOStream *dst, Uint64 value) | ||
| 1649 | { | ||
| 1650 | const Uint64 swapped = SDL_Swap64BE(value); | ||
| 1651 | return (SDL_WriteIO(dst, &swapped, sizeof(swapped)) == sizeof(swapped)); | ||
| 1652 | } | ||
| 1653 | |||
| 1654 | bool SDL_WriteS64BE(SDL_IOStream *dst, Sint64 value) | ||
| 1655 | { | ||
| 1656 | return SDL_WriteU64BE(dst, (Uint64)value); | ||
| 1657 | } | ||
diff --git a/contrib/SDL-3.2.8/src/io/SDL_iostream_c.h b/contrib/SDL-3.2.8/src/io/SDL_iostream_c.h new file mode 100644 index 0000000..c04fe20 --- /dev/null +++ b/contrib/SDL-3.2.8/src/io/SDL_iostream_c.h | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifndef SDL_iostream_c_h_ | ||
| 24 | #define SDL_iostream_c_h_ | ||
| 25 | |||
| 26 | #if defined(SDL_PLATFORM_WINDOWS) | ||
| 27 | SDL_IOStream *SDL_IOFromHandle(HANDLE handle, const char *mode, bool autoclose); | ||
| 28 | #else | ||
| 29 | #if defined(HAVE_STDIO_H) | ||
| 30 | extern SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose); | ||
| 31 | #endif | ||
| 32 | extern SDL_IOStream *SDL_IOFromFD(int fd, bool autoclose); | ||
| 33 | #endif | ||
| 34 | |||
| 35 | #endif // SDL_iostream_c_h_ | ||
diff --git a/contrib/SDL-3.2.8/src/io/SDL_sysasyncio.h b/contrib/SDL-3.2.8/src/io/SDL_sysasyncio.h new file mode 100644 index 0000000..fbe41d1 --- /dev/null +++ b/contrib/SDL-3.2.8/src/io/SDL_sysasyncio.h | |||
| @@ -0,0 +1,144 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | #include "SDL_internal.h" | ||
| 23 | |||
| 24 | #ifndef SDL_sysasyncio_h_ | ||
| 25 | #define SDL_sysasyncio_h_ | ||
| 26 | |||
| 27 | #if defined(SDL_PLATFORM_WINDOWS) && defined(NTDDI_WIN10_NI) | ||
| 28 | #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && NTDDI_VERSION >= NTDDI_WIN10_NI | ||
| 29 | #define HAVE_IORINGAPI_H | ||
| 30 | #endif | ||
| 31 | #endif | ||
| 32 | |||
| 33 | // If your platform has an option other than the "generic" code, make sure this | ||
| 34 | // is #defined to 0 instead and implement the SDL_SYS_* functions below in your | ||
| 35 | // backend (having them maybe call into the SDL_SYS_*_Generic versions as a | ||
| 36 | // fallback if the platform has functionality that isn't always available). | ||
| 37 | #if defined(HAVE_LIBURING_H) || defined(HAVE_IORINGAPI_H) | ||
| 38 | #define SDL_ASYNCIO_ONLY_HAVE_GENERIC 0 | ||
| 39 | #else | ||
| 40 | #define SDL_ASYNCIO_ONLY_HAVE_GENERIC 1 | ||
| 41 | #endif | ||
| 42 | |||
| 43 | // this entire thing is just juggling doubly-linked lists, so make some helper macros. | ||
| 44 | #define LINKED_LIST_DECLARE_FIELDS(type, prefix) \ | ||
| 45 | type *prefix##prev; \ | ||
| 46 | type *prefix##next | ||
| 47 | |||
| 48 | #define LINKED_LIST_PREPEND(item, list, prefix) do { \ | ||
| 49 | item->prefix##prev = &list; \ | ||
| 50 | item->prefix##next = list.prefix##next; \ | ||
| 51 | if (item->prefix##next) { \ | ||
| 52 | item->prefix##next->prefix##prev = item; \ | ||
| 53 | } \ | ||
| 54 | list.prefix##next = item; \ | ||
| 55 | } while (false) | ||
| 56 | |||
| 57 | #define LINKED_LIST_UNLINK(item, prefix) do { \ | ||
| 58 | if (item->prefix##next) { \ | ||
| 59 | item->prefix##next->prefix##prev = item->prefix##prev; \ | ||
| 60 | } \ | ||
| 61 | item->prefix##prev->prefix##next = task->prefix##next; \ | ||
| 62 | item->prefix##prev = item->prefix##next = NULL; \ | ||
| 63 | } while (false) | ||
| 64 | |||
| 65 | #define LINKED_LIST_START(list, prefix) (list.prefix##next) | ||
| 66 | #define LINKED_LIST_NEXT(item, prefix) (item->prefix##next) | ||
| 67 | #define LINKED_LIST_PREV(item, prefix) (item->prefix##prev) | ||
| 68 | |||
| 69 | typedef struct SDL_AsyncIOTask SDL_AsyncIOTask; | ||
| 70 | |||
| 71 | struct SDL_AsyncIOTask | ||
| 72 | { | ||
| 73 | SDL_AsyncIO *asyncio; | ||
| 74 | SDL_AsyncIOTaskType type; | ||
| 75 | SDL_AsyncIOQueue *queue; | ||
| 76 | Uint64 offset; | ||
| 77 | bool flush; | ||
| 78 | void *buffer; | ||
| 79 | char *error; | ||
| 80 | SDL_AsyncIOResult result; | ||
| 81 | Uint64 requested_size; | ||
| 82 | Uint64 result_size; | ||
| 83 | void *app_userdata; | ||
| 84 | LINKED_LIST_DECLARE_FIELDS(struct SDL_AsyncIOTask, asyncio); | ||
| 85 | LINKED_LIST_DECLARE_FIELDS(struct SDL_AsyncIOTask, queue); // the generic backend uses this, so I've added it here to avoid the extra allocation. | ||
| 86 | LINKED_LIST_DECLARE_FIELDS(struct SDL_AsyncIOTask, threadpool); // the generic backend uses this, so I've added it here to avoid the extra allocation. | ||
| 87 | }; | ||
| 88 | |||
| 89 | typedef struct SDL_AsyncIOQueueInterface | ||
| 90 | { | ||
| 91 | bool (*queue_task)(void *userdata, SDL_AsyncIOTask *task); | ||
| 92 | void (*cancel_task)(void *userdata, SDL_AsyncIOTask *task); | ||
| 93 | SDL_AsyncIOTask * (*get_results)(void *userdata); | ||
| 94 | SDL_AsyncIOTask * (*wait_results)(void *userdata, Sint32 timeoutMS); | ||
| 95 | void (*signal)(void *userdata); | ||
| 96 | void (*destroy)(void *userdata); | ||
| 97 | } SDL_AsyncIOQueueInterface; | ||
| 98 | |||
| 99 | struct SDL_AsyncIOQueue | ||
| 100 | { | ||
| 101 | SDL_AsyncIOQueueInterface iface; | ||
| 102 | void *userdata; | ||
| 103 | SDL_AtomicInt tasks_inflight; | ||
| 104 | }; | ||
| 105 | |||
| 106 | // this interface is kept per-object, even though generally it's going to decide | ||
| 107 | // on a single interface that is the same for the entire process, but I've kept | ||
| 108 | // the abstraction in case we start exposing more types of async i/o, like | ||
| 109 | // sockets, in the future. | ||
| 110 | typedef struct SDL_AsyncIOInterface | ||
| 111 | { | ||
| 112 | Sint64 (*size)(void *userdata); | ||
| 113 | bool (*read)(void *userdata, SDL_AsyncIOTask *task); | ||
| 114 | bool (*write)(void *userdata, SDL_AsyncIOTask *task); | ||
| 115 | bool (*close)(void *userdata, SDL_AsyncIOTask *task); | ||
| 116 | void (*destroy)(void *userdata); | ||
| 117 | } SDL_AsyncIOInterface; | ||
| 118 | |||
| 119 | struct SDL_AsyncIO | ||
| 120 | { | ||
| 121 | SDL_AsyncIOInterface iface; | ||
| 122 | void *userdata; | ||
| 123 | SDL_Mutex *lock; | ||
| 124 | SDL_AsyncIOTask tasks; | ||
| 125 | SDL_AsyncIOTask *closing; // The close task, which isn't queued until all pending work for this file is done. | ||
| 126 | bool oneshot; // true if this is a SDL_LoadFileAsync open. | ||
| 127 | }; | ||
| 128 | |||
| 129 | // This is implemented for various platforms; param validation is done before calling this. Open file, fill in iface and userdata. | ||
| 130 | extern bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio); | ||
| 131 | |||
| 132 | // This is implemented for various platforms. Call SDL_OpenAsyncIOQueue from in here. | ||
| 133 | extern bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue); | ||
| 134 | |||
| 135 | // This is called during SDL_QuitAsyncIO, after all tasks have completed and all files are closed, to let the platform clean up global backend details. | ||
| 136 | extern void SDL_SYS_QuitAsyncIO(void); | ||
| 137 | |||
| 138 | // the "generic" version is always available, since it is almost always needed as a fallback even on platforms that might offer something better. | ||
| 139 | extern bool SDL_SYS_AsyncIOFromFile_Generic(const char *file, const char *mode, SDL_AsyncIO *asyncio); | ||
| 140 | extern bool SDL_SYS_CreateAsyncIOQueue_Generic(SDL_AsyncIOQueue *queue); | ||
| 141 | extern void SDL_SYS_QuitAsyncIO_Generic(void); | ||
| 142 | |||
| 143 | #endif | ||
| 144 | |||
diff --git a/contrib/SDL-3.2.8/src/io/generic/SDL_asyncio_generic.c b/contrib/SDL-3.2.8/src/io/generic/SDL_asyncio_generic.c new file mode 100644 index 0000000..4c2a562 --- /dev/null +++ b/contrib/SDL-3.2.8/src/io/generic/SDL_asyncio_generic.c | |||
| @@ -0,0 +1,465 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | // The generic backend uses a threadpool to block on synchronous i/o. | ||
| 23 | // This is not ideal, it's meant to be used if there isn't a platform-specific | ||
| 24 | // backend that can do something more efficient! | ||
| 25 | |||
| 26 | #include "SDL_internal.h" | ||
| 27 | #include "../SDL_sysasyncio.h" | ||
| 28 | |||
| 29 | // on Emscripten without threads, async i/o is synchronous. Sorry. Almost | ||
| 30 | // everything is MEMFS, so it's just a memcpy anyhow, and the Emscripten | ||
| 31 | // filesystem APIs don't offer async. In theory, directly accessing | ||
| 32 | // persistent storage _does_ offer async APIs at the browser level, but | ||
| 33 | // that's not exposed in Emscripten's filesystem abstraction. | ||
| 34 | #if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__) | ||
| 35 | #define SDL_ASYNCIO_USE_THREADPOOL 0 | ||
| 36 | #else | ||
| 37 | #define SDL_ASYNCIO_USE_THREADPOOL 1 | ||
| 38 | #endif | ||
| 39 | |||
| 40 | typedef struct GenericAsyncIOQueueData | ||
| 41 | { | ||
| 42 | SDL_Mutex *lock; | ||
| 43 | SDL_Condition *condition; | ||
| 44 | SDL_AsyncIOTask completed_tasks; | ||
| 45 | } GenericAsyncIOQueueData; | ||
| 46 | |||
| 47 | typedef struct GenericAsyncIOData | ||
| 48 | { | ||
| 49 | SDL_Mutex *lock; // !!! FIXME: we can skip this lock if we have an equivalent of pread/pwrite | ||
| 50 | SDL_IOStream *io; | ||
| 51 | } GenericAsyncIOData; | ||
| 52 | |||
| 53 | static void AsyncIOTaskComplete(SDL_AsyncIOTask *task) | ||
| 54 | { | ||
| 55 | SDL_assert(task->queue); | ||
| 56 | GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) task->queue->userdata; | ||
| 57 | SDL_LockMutex(data->lock); | ||
| 58 | LINKED_LIST_PREPEND(task, data->completed_tasks, queue); | ||
| 59 | SDL_SignalCondition(data->condition); // wake a thread waiting on the queue. | ||
| 60 | SDL_UnlockMutex(data->lock); | ||
| 61 | } | ||
| 62 | |||
| 63 | // synchronous i/o is offloaded onto the threadpool. This function does the threaded work. | ||
| 64 | // This is called directly, without a threadpool, if !SDL_ASYNCIO_USE_THREADPOOL. | ||
| 65 | static void SynchronousIO(SDL_AsyncIOTask *task) | ||
| 66 | { | ||
| 67 | SDL_assert(task->result != SDL_ASYNCIO_CANCELED); // shouldn't have gotten in here if canceled! | ||
| 68 | |||
| 69 | GenericAsyncIOData *data = (GenericAsyncIOData *) task->asyncio->userdata; | ||
| 70 | SDL_IOStream *io = data->io; | ||
| 71 | const size_t size = (size_t) task->requested_size; | ||
| 72 | void *ptr = task->buffer; | ||
| 73 | |||
| 74 | // this seek won't work if two tasks are reading from the same file at the same time, | ||
| 75 | // so we lock here. This makes multiple reads from a single file serialize, but different | ||
| 76 | // files will still run in parallel. An app can also open the same file twice to avoid this. | ||
| 77 | SDL_LockMutex(data->lock); | ||
| 78 | if (task->type == SDL_ASYNCIO_TASK_CLOSE) { | ||
| 79 | bool okay = true; | ||
| 80 | if (task->flush) { | ||
| 81 | okay = SDL_FlushIO(data->io); | ||
| 82 | } | ||
| 83 | okay = SDL_CloseIO(data->io) && okay; | ||
| 84 | task->result = okay ? SDL_ASYNCIO_COMPLETE : SDL_ASYNCIO_FAILURE; | ||
| 85 | } else if (SDL_SeekIO(io, (Sint64) task->offset, SDL_IO_SEEK_SET) < 0) { | ||
| 86 | task->result = SDL_ASYNCIO_FAILURE; | ||
| 87 | } else { | ||
| 88 | const bool writing = (task->type == SDL_ASYNCIO_TASK_WRITE); | ||
| 89 | task->result_size = (Uint64) (writing ? SDL_WriteIO(io, ptr, size) : SDL_ReadIO(io, ptr, size)); | ||
| 90 | if (task->result_size == task->requested_size) { | ||
| 91 | task->result = SDL_ASYNCIO_COMPLETE; | ||
| 92 | } else { | ||
| 93 | if (writing) { | ||
| 94 | task->result = SDL_ASYNCIO_FAILURE; // it's always a failure on short writes. | ||
| 95 | } else { | ||
| 96 | const SDL_IOStatus status = SDL_GetIOStatus(io); | ||
| 97 | SDL_assert(status != SDL_IO_STATUS_READY); // this should have either failed or been EOF. | ||
| 98 | SDL_assert(status != SDL_IO_STATUS_NOT_READY); // these should not be non-blocking reads! | ||
| 99 | task->result = (status == SDL_IO_STATUS_EOF) ? SDL_ASYNCIO_COMPLETE : SDL_ASYNCIO_FAILURE; | ||
| 100 | } | ||
| 101 | } | ||
| 102 | } | ||
| 103 | SDL_UnlockMutex(data->lock); | ||
| 104 | |||
| 105 | AsyncIOTaskComplete(task); | ||
| 106 | } | ||
| 107 | |||
| 108 | #if SDL_ASYNCIO_USE_THREADPOOL | ||
| 109 | static SDL_InitState threadpool_init; | ||
| 110 | static SDL_Mutex *threadpool_lock = NULL; | ||
| 111 | static bool stop_threadpool = false; | ||
| 112 | static SDL_AsyncIOTask threadpool_tasks; | ||
| 113 | static SDL_Condition *threadpool_condition = NULL; | ||
| 114 | static int max_threadpool_threads = 0; | ||
| 115 | static int running_threadpool_threads = 0; | ||
| 116 | static int idle_threadpool_threads = 0; | ||
| 117 | static int threadpool_threads_spun = 0; | ||
| 118 | |||
| 119 | static int SDLCALL AsyncIOThreadpoolWorker(void *data) | ||
| 120 | { | ||
| 121 | SDL_LockMutex(threadpool_lock); | ||
| 122 | |||
| 123 | while (!stop_threadpool) { | ||
| 124 | SDL_AsyncIOTask *task = LINKED_LIST_START(threadpool_tasks, threadpool); | ||
| 125 | if (!task) { | ||
| 126 | // if we go 30 seconds without a new task, terminate unless we're the only thread left. | ||
| 127 | idle_threadpool_threads++; | ||
| 128 | const bool rc = SDL_WaitConditionTimeout(threadpool_condition, threadpool_lock, 30000); | ||
| 129 | idle_threadpool_threads--; | ||
| 130 | |||
| 131 | if (!rc) { | ||
| 132 | // decide if we have too many idle threads, and if so, quit to let thread pool shrink when not busy. | ||
| 133 | if (idle_threadpool_threads) { | ||
| 134 | break; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | continue; | ||
| 139 | } | ||
| 140 | |||
| 141 | LINKED_LIST_UNLINK(task, threadpool); | ||
| 142 | |||
| 143 | SDL_UnlockMutex(threadpool_lock); | ||
| 144 | |||
| 145 | // bookkeeping is done, so we drop the mutex and fire the work. | ||
| 146 | SynchronousIO(task); | ||
| 147 | |||
| 148 | SDL_LockMutex(threadpool_lock); // take the lock again and see if there's another task (if not, we'll wait on the Condition). | ||
| 149 | } | ||
| 150 | |||
| 151 | running_threadpool_threads--; | ||
| 152 | |||
| 153 | // this is kind of a hack, but this lets us reuse threadpool_condition to block on shutdown until all threads have exited. | ||
| 154 | if (stop_threadpool) { | ||
| 155 | SDL_BroadcastCondition(threadpool_condition); | ||
| 156 | } | ||
| 157 | |||
| 158 | SDL_UnlockMutex(threadpool_lock); | ||
| 159 | |||
| 160 | return 0; | ||
| 161 | } | ||
| 162 | |||
| 163 | static bool MaybeSpinNewWorkerThread(void) | ||
| 164 | { | ||
| 165 | // if all existing threads are busy and the pool of threads isn't maxed out, make a new one. | ||
| 166 | if ((idle_threadpool_threads == 0) && (running_threadpool_threads < max_threadpool_threads)) { | ||
| 167 | char threadname[32]; | ||
| 168 | SDL_snprintf(threadname, sizeof (threadname), "SDLasyncio%d", threadpool_threads_spun); | ||
| 169 | SDL_Thread *thread = SDL_CreateThread(AsyncIOThreadpoolWorker, threadname, NULL); | ||
| 170 | if (thread == NULL) { | ||
| 171 | return false; | ||
| 172 | } | ||
| 173 | SDL_DetachThread(thread); // these terminate themselves when idle too long, so we never WaitThread. | ||
| 174 | running_threadpool_threads++; | ||
| 175 | threadpool_threads_spun++; | ||
| 176 | } | ||
| 177 | return true; | ||
| 178 | } | ||
| 179 | |||
| 180 | static void QueueAsyncIOTask(SDL_AsyncIOTask *task) | ||
| 181 | { | ||
| 182 | SDL_assert(task != NULL); | ||
| 183 | |||
| 184 | SDL_LockMutex(threadpool_lock); | ||
| 185 | |||
| 186 | if (stop_threadpool) { // just in case. | ||
| 187 | task->result = SDL_ASYNCIO_CANCELED; | ||
| 188 | AsyncIOTaskComplete(task); | ||
| 189 | } else { | ||
| 190 | LINKED_LIST_PREPEND(task, threadpool_tasks, threadpool); | ||
| 191 | MaybeSpinNewWorkerThread(); // okay if this fails or the thread pool is maxed out. Something will get there eventually. | ||
| 192 | |||
| 193 | // tell idle threads to get to work. | ||
| 194 | // This is a broadcast because we want someone from the thread pool to wake up, but | ||
| 195 | // also shutdown might also be blocking on this. One of the threads will grab | ||
| 196 | // it, the others will go back to sleep. | ||
| 197 | SDL_BroadcastCondition(threadpool_condition); | ||
| 198 | } | ||
| 199 | |||
| 200 | SDL_UnlockMutex(threadpool_lock); | ||
| 201 | } | ||
| 202 | |||
| 203 | // We don't initialize async i/o at all until it's used, so | ||
| 204 | // JUST IN CASE two things try to start at the same time, | ||
| 205 | // this will make sure everything gets the same mutex. | ||
| 206 | static bool PrepareThreadpool(void) | ||
| 207 | { | ||
| 208 | bool okay = true; | ||
| 209 | if (SDL_ShouldInit(&threadpool_init)) { | ||
| 210 | max_threadpool_threads = (SDL_GetNumLogicalCPUCores() * 2) + 1; // !!! FIXME: this should probably have a hint to override. | ||
| 211 | max_threadpool_threads = SDL_clamp(max_threadpool_threads, 1, 8); // 8 is probably more than enough. | ||
| 212 | |||
| 213 | okay = (okay && ((threadpool_lock = SDL_CreateMutex()) != NULL)); | ||
| 214 | okay = (okay && ((threadpool_condition = SDL_CreateCondition()) != NULL)); | ||
| 215 | okay = (okay && MaybeSpinNewWorkerThread()); // make sure at least one thread is going, since we'll need it. | ||
| 216 | |||
| 217 | if (!okay) { | ||
| 218 | if (threadpool_condition) { | ||
| 219 | SDL_DestroyCondition(threadpool_condition); | ||
| 220 | threadpool_condition = NULL; | ||
| 221 | } | ||
| 222 | if (threadpool_lock) { | ||
| 223 | SDL_DestroyMutex(threadpool_lock); | ||
| 224 | threadpool_lock = NULL; | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | SDL_SetInitialized(&threadpool_init, okay); | ||
| 229 | } | ||
| 230 | return okay; | ||
| 231 | } | ||
| 232 | |||
| 233 | static void ShutdownThreadpool(void) | ||
| 234 | { | ||
| 235 | if (SDL_ShouldQuit(&threadpool_init)) { | ||
| 236 | SDL_LockMutex(threadpool_lock); | ||
| 237 | |||
| 238 | // cancel anything that's still pending. | ||
| 239 | SDL_AsyncIOTask *task; | ||
| 240 | while ((task = LINKED_LIST_START(threadpool_tasks, threadpool)) != NULL) { | ||
| 241 | LINKED_LIST_UNLINK(task, threadpool); | ||
| 242 | task->result = SDL_ASYNCIO_CANCELED; | ||
| 243 | AsyncIOTaskComplete(task); | ||
| 244 | } | ||
| 245 | |||
| 246 | stop_threadpool = true; | ||
| 247 | SDL_BroadcastCondition(threadpool_condition); // tell the whole threadpool to wake up and quit. | ||
| 248 | |||
| 249 | while (running_threadpool_threads > 0) { | ||
| 250 | // each threadpool thread will broadcast this condition before it terminates if stop_threadpool is set. | ||
| 251 | // we can't just join the threads because they are detached, so the thread pool can automatically shrink as necessary. | ||
| 252 | SDL_WaitCondition(threadpool_condition, threadpool_lock); | ||
| 253 | } | ||
| 254 | |||
| 255 | SDL_UnlockMutex(threadpool_lock); | ||
| 256 | |||
| 257 | SDL_DestroyMutex(threadpool_lock); | ||
| 258 | threadpool_lock = NULL; | ||
| 259 | SDL_DestroyCondition(threadpool_condition); | ||
| 260 | threadpool_condition = NULL; | ||
| 261 | |||
| 262 | max_threadpool_threads = running_threadpool_threads = idle_threadpool_threads = threadpool_threads_spun = 0; | ||
| 263 | |||
| 264 | stop_threadpool = false; | ||
| 265 | SDL_SetInitialized(&threadpool_init, false); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | #endif | ||
| 269 | |||
| 270 | |||
| 271 | static Sint64 generic_asyncio_size(void *userdata) | ||
| 272 | { | ||
| 273 | GenericAsyncIOData *data = (GenericAsyncIOData *) userdata; | ||
| 274 | return SDL_GetIOSize(data->io); | ||
| 275 | } | ||
| 276 | |||
| 277 | static bool generic_asyncio_io(void *userdata, SDL_AsyncIOTask *task) | ||
| 278 | { | ||
| 279 | return task->queue->iface.queue_task(task->queue->userdata, task); | ||
| 280 | } | ||
| 281 | |||
| 282 | static void generic_asyncio_destroy(void *userdata) | ||
| 283 | { | ||
| 284 | GenericAsyncIOData *data = (GenericAsyncIOData *) userdata; | ||
| 285 | SDL_DestroyMutex(data->lock); | ||
| 286 | SDL_free(data); | ||
| 287 | } | ||
| 288 | |||
| 289 | |||
| 290 | static bool generic_asyncioqueue_queue_task(void *userdata, SDL_AsyncIOTask *task) | ||
| 291 | { | ||
| 292 | #if SDL_ASYNCIO_USE_THREADPOOL | ||
| 293 | QueueAsyncIOTask(task); | ||
| 294 | #else | ||
| 295 | SynchronousIO(task); // oh well. Get a better platform. | ||
| 296 | #endif | ||
| 297 | return true; | ||
| 298 | } | ||
| 299 | |||
| 300 | static void generic_asyncioqueue_cancel_task(void *userdata, SDL_AsyncIOTask *task) | ||
| 301 | { | ||
| 302 | #if !SDL_ASYNCIO_USE_THREADPOOL // in theory, this was all synchronous and should never call this, but just in case. | ||
| 303 | task->result = SDL_ASYNCIO_CANCELED; | ||
| 304 | AsyncIOTaskComplete(task); | ||
| 305 | #else | ||
| 306 | // we can't stop i/o that's in-flight, but we _can_ just refuse to start it if the threadpool hadn't picked it up yet. | ||
| 307 | SDL_LockMutex(threadpool_lock); | ||
| 308 | if (LINKED_LIST_PREV(task, threadpool) != NULL) { // still in the queue waiting to be run? Take it out. | ||
| 309 | LINKED_LIST_UNLINK(task, threadpool); | ||
| 310 | task->result = SDL_ASYNCIO_CANCELED; | ||
| 311 | AsyncIOTaskComplete(task); | ||
| 312 | } | ||
| 313 | SDL_UnlockMutex(threadpool_lock); | ||
| 314 | #endif | ||
| 315 | } | ||
| 316 | |||
| 317 | static SDL_AsyncIOTask *generic_asyncioqueue_get_results(void *userdata) | ||
| 318 | { | ||
| 319 | GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata; | ||
| 320 | SDL_LockMutex(data->lock); | ||
| 321 | SDL_AsyncIOTask *task = LINKED_LIST_START(data->completed_tasks, queue); | ||
| 322 | if (task) { | ||
| 323 | LINKED_LIST_UNLINK(task, queue); | ||
| 324 | } | ||
| 325 | SDL_UnlockMutex(data->lock); | ||
| 326 | return task; | ||
| 327 | } | ||
| 328 | |||
| 329 | static SDL_AsyncIOTask *generic_asyncioqueue_wait_results(void *userdata, Sint32 timeoutMS) | ||
| 330 | { | ||
| 331 | GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata; | ||
| 332 | SDL_LockMutex(data->lock); | ||
| 333 | SDL_AsyncIOTask *task = LINKED_LIST_START(data->completed_tasks, queue); | ||
| 334 | if (!task) { | ||
| 335 | SDL_WaitConditionTimeout(data->condition, data->lock, timeoutMS); | ||
| 336 | task = LINKED_LIST_START(data->completed_tasks, queue); | ||
| 337 | } | ||
| 338 | if (task) { | ||
| 339 | LINKED_LIST_UNLINK(task, queue); | ||
| 340 | } | ||
| 341 | SDL_UnlockMutex(data->lock); | ||
| 342 | return task; | ||
| 343 | } | ||
| 344 | |||
| 345 | static void generic_asyncioqueue_signal(void *userdata) | ||
| 346 | { | ||
| 347 | GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata; | ||
| 348 | SDL_LockMutex(data->lock); | ||
| 349 | SDL_BroadcastCondition(data->condition); | ||
| 350 | SDL_UnlockMutex(data->lock); | ||
| 351 | } | ||
| 352 | |||
| 353 | static void generic_asyncioqueue_destroy(void *userdata) | ||
| 354 | { | ||
| 355 | GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata; | ||
| 356 | SDL_DestroyMutex(data->lock); | ||
| 357 | SDL_DestroyCondition(data->condition); | ||
| 358 | SDL_free(data); | ||
| 359 | } | ||
| 360 | |||
| 361 | bool SDL_SYS_CreateAsyncIOQueue_Generic(SDL_AsyncIOQueue *queue) | ||
| 362 | { | ||
| 363 | #if SDL_ASYNCIO_USE_THREADPOOL | ||
| 364 | if (!PrepareThreadpool()) { | ||
| 365 | return false; | ||
| 366 | } | ||
| 367 | #endif | ||
| 368 | |||
| 369 | GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) SDL_calloc(1, sizeof (*data)); | ||
| 370 | if (!data) { | ||
| 371 | return false; | ||
| 372 | } | ||
| 373 | |||
| 374 | data->lock = SDL_CreateMutex(); | ||
| 375 | if (!data->lock) { | ||
| 376 | SDL_free(data); | ||
| 377 | return false; | ||
| 378 | } | ||
| 379 | |||
| 380 | data->condition = SDL_CreateCondition(); | ||
| 381 | if (!data->condition) { | ||
| 382 | SDL_DestroyMutex(data->lock); | ||
| 383 | SDL_free(data); | ||
| 384 | return false; | ||
| 385 | } | ||
| 386 | |||
| 387 | static const SDL_AsyncIOQueueInterface SDL_AsyncIOQueue_Generic = { | ||
| 388 | generic_asyncioqueue_queue_task, | ||
| 389 | generic_asyncioqueue_cancel_task, | ||
| 390 | generic_asyncioqueue_get_results, | ||
| 391 | generic_asyncioqueue_wait_results, | ||
| 392 | generic_asyncioqueue_signal, | ||
| 393 | generic_asyncioqueue_destroy | ||
| 394 | }; | ||
| 395 | |||
| 396 | SDL_copyp(&queue->iface, &SDL_AsyncIOQueue_Generic); | ||
| 397 | queue->userdata = data; | ||
| 398 | return true; | ||
| 399 | } | ||
| 400 | |||
| 401 | |||
| 402 | bool SDL_SYS_AsyncIOFromFile_Generic(const char *file, const char *mode, SDL_AsyncIO *asyncio) | ||
| 403 | { | ||
| 404 | #if SDL_ASYNCIO_USE_THREADPOOL | ||
| 405 | if (!PrepareThreadpool()) { | ||
| 406 | return false; | ||
| 407 | } | ||
| 408 | #endif | ||
| 409 | |||
| 410 | GenericAsyncIOData *data = (GenericAsyncIOData *) SDL_calloc(1, sizeof (*data)); | ||
| 411 | if (!data) { | ||
| 412 | return false; | ||
| 413 | } | ||
| 414 | |||
| 415 | data->lock = SDL_CreateMutex(); | ||
| 416 | if (!data->lock) { | ||
| 417 | SDL_free(data); | ||
| 418 | return false; | ||
| 419 | } | ||
| 420 | |||
| 421 | data->io = SDL_IOFromFile(file, mode); | ||
| 422 | if (!data->io) { | ||
| 423 | SDL_DestroyMutex(data->lock); | ||
| 424 | SDL_free(data); | ||
| 425 | return false; | ||
| 426 | } | ||
| 427 | |||
| 428 | static const SDL_AsyncIOInterface SDL_AsyncIOFile_Generic = { | ||
| 429 | generic_asyncio_size, | ||
| 430 | generic_asyncio_io, | ||
| 431 | generic_asyncio_io, | ||
| 432 | generic_asyncio_io, | ||
| 433 | generic_asyncio_destroy | ||
| 434 | }; | ||
| 435 | |||
| 436 | SDL_copyp(&asyncio->iface, &SDL_AsyncIOFile_Generic); | ||
| 437 | asyncio->userdata = data; | ||
| 438 | return true; | ||
| 439 | } | ||
| 440 | |||
| 441 | void SDL_SYS_QuitAsyncIO_Generic(void) | ||
| 442 | { | ||
| 443 | #if SDL_ASYNCIO_USE_THREADPOOL | ||
| 444 | ShutdownThreadpool(); | ||
| 445 | #endif | ||
| 446 | } | ||
| 447 | |||
| 448 | |||
| 449 | #if SDL_ASYNCIO_ONLY_HAVE_GENERIC | ||
| 450 | bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio) | ||
| 451 | { | ||
| 452 | return SDL_SYS_AsyncIOFromFile_Generic(file, mode, asyncio); | ||
| 453 | } | ||
| 454 | |||
| 455 | bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue) | ||
| 456 | { | ||
| 457 | return SDL_SYS_CreateAsyncIOQueue_Generic(queue); | ||
| 458 | } | ||
| 459 | |||
| 460 | void SDL_SYS_QuitAsyncIO(void) | ||
| 461 | { | ||
| 462 | SDL_SYS_QuitAsyncIO_Generic(); | ||
| 463 | } | ||
| 464 | #endif | ||
| 465 | |||
diff --git a/contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c b/contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c new file mode 100644 index 0000000..07e8c24 --- /dev/null +++ b/contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c | |||
| @@ -0,0 +1,551 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | // The Linux backend uses io_uring for asynchronous i/o, and falls back to | ||
| 23 | // the "generic" threadpool implementation if liburing isn't available or | ||
| 24 | // fails for some other reason. | ||
| 25 | |||
| 26 | #include "SDL_internal.h" | ||
| 27 | |||
| 28 | #ifdef HAVE_LIBURING_H | ||
| 29 | |||
| 30 | #include "../SDL_sysasyncio.h" | ||
| 31 | |||
| 32 | #include <liburing.h> | ||
| 33 | #include <errno.h> | ||
| 34 | #include <fcntl.h> | ||
| 35 | #include <string.h> // for strerror() | ||
| 36 | |||
| 37 | static SDL_InitState liburing_init; | ||
| 38 | |||
| 39 | // We could add a whole bootstrap thing like the audio/video/etc subsystems use, but let's keep this simple for now. | ||
| 40 | static bool (*CreateAsyncIOQueue)(SDL_AsyncIOQueue *queue); | ||
| 41 | static void (*QuitAsyncIO)(void); | ||
| 42 | static bool (*AsyncIOFromFile)(const char *file, const char *mode, SDL_AsyncIO *asyncio); | ||
| 43 | |||
| 44 | // we never link directly to liburing. | ||
| 45 | // (this says "-ffi" which sounds like a scripting language binding thing, but the non-ffi version | ||
| 46 | // is static-inline code we can't lookup with dlsym. This is by design.) | ||
| 47 | static const char *liburing_library = "liburing-ffi.so.2"; | ||
| 48 | static void *liburing_handle = NULL; | ||
| 49 | |||
| 50 | #define SDL_LIBURING_FUNCS \ | ||
| 51 | SDL_LIBURING_FUNC(int, io_uring_queue_init, (unsigned entries, struct io_uring *ring, unsigned flags)) \ | ||
| 52 | SDL_LIBURING_FUNC(struct io_uring_probe *,io_uring_get_probe,(void)) \ | ||
| 53 | SDL_LIBURING_FUNC(void, io_uring_free_probe, (struct io_uring_probe *probe)) \ | ||
| 54 | SDL_LIBURING_FUNC(int, io_uring_opcode_supported, (const struct io_uring_probe *p, int op)) \ | ||
| 55 | SDL_LIBURING_FUNC(struct io_uring_sqe *, io_uring_get_sqe, (struct io_uring *ring)) \ | ||
| 56 | SDL_LIBURING_FUNC(void, io_uring_prep_read,(struct io_uring_sqe *sqe, int fd, void *buf, unsigned nbytes, __u64 offset)) \ | ||
| 57 | SDL_LIBURING_FUNC(void, io_uring_prep_write,(struct io_uring_sqe *sqe, int fd, const void *buf, unsigned nbytes, __u64 offset)) \ | ||
| 58 | SDL_LIBURING_FUNC(void, io_uring_prep_close, (struct io_uring_sqe *sqe, int fd)) \ | ||
| 59 | SDL_LIBURING_FUNC(void, io_uring_prep_fsync, (struct io_uring_sqe *sqe, int fd, unsigned fsync_flags)) \ | ||
| 60 | SDL_LIBURING_FUNC(void, io_uring_prep_cancel, (struct io_uring_sqe *sqe, void *user_data, int flags)) \ | ||
| 61 | SDL_LIBURING_FUNC(void, io_uring_prep_timeout, (struct io_uring_sqe *sqe, struct __kernel_timespec *ts, unsigned count, unsigned flags)) \ | ||
| 62 | SDL_LIBURING_FUNC(void, io_uring_prep_nop, (struct io_uring_sqe *sqe)) \ | ||
| 63 | SDL_LIBURING_FUNC(void, io_uring_sqe_set_data, (struct io_uring_sqe *sqe, void *data)) \ | ||
| 64 | SDL_LIBURING_FUNC(void, io_uring_sqe_set_flags, (struct io_uring_sqe *sqe, unsigned flags)) \ | ||
| 65 | SDL_LIBURING_FUNC(int, io_uring_submit, (struct io_uring *ring)) \ | ||
| 66 | SDL_LIBURING_FUNC(int, io_uring_peek_cqe, (struct io_uring *ring, struct io_uring_cqe **cqe_ptr)) \ | ||
| 67 | SDL_LIBURING_FUNC(int, io_uring_wait_cqe, (struct io_uring *ring, struct io_uring_cqe **cqe_ptr)) \ | ||
| 68 | SDL_LIBURING_FUNC(int, io_uring_wait_cqe_timeout, (struct io_uring *ring, struct io_uring_cqe **cqe_ptr, struct __kernel_timespec *ts)) \ | ||
| 69 | SDL_LIBURING_FUNC(void, io_uring_cqe_seen, (struct io_uring *ring, struct io_uring_cqe *cqe)) \ | ||
| 70 | SDL_LIBURING_FUNC(void, io_uring_queue_exit, (struct io_uring *ring)) \ | ||
| 71 | |||
| 72 | |||
| 73 | #define SDL_LIBURING_FUNC(ret, fn, args) typedef ret (*SDL_fntype_##fn) args; | ||
| 74 | SDL_LIBURING_FUNCS | ||
| 75 | #undef SDL_LIBURING_FUNC | ||
| 76 | |||
| 77 | typedef struct SDL_LibUringFunctions | ||
| 78 | { | ||
| 79 | #define SDL_LIBURING_FUNC(ret, fn, args) SDL_fntype_##fn fn; | ||
| 80 | SDL_LIBURING_FUNCS | ||
| 81 | #undef SDL_LIBURING_FUNC | ||
| 82 | } SDL_LibUringFunctions; | ||
| 83 | |||
| 84 | static SDL_LibUringFunctions liburing; | ||
| 85 | |||
| 86 | |||
| 87 | typedef struct LibUringAsyncIOQueueData | ||
| 88 | { | ||
| 89 | SDL_Mutex *sqe_lock; | ||
| 90 | SDL_Mutex *cqe_lock; | ||
| 91 | struct io_uring ring; | ||
| 92 | SDL_AtomicInt num_waiting; | ||
| 93 | } LibUringAsyncIOQueueData; | ||
| 94 | |||
| 95 | |||
| 96 | static void UnloadLibUringLibrary(void) | ||
| 97 | { | ||
| 98 | if (liburing_library) { | ||
| 99 | SDL_UnloadObject(liburing_handle); | ||
| 100 | liburing_library = NULL; | ||
| 101 | } | ||
| 102 | SDL_zero(liburing); | ||
| 103 | } | ||
| 104 | |||
| 105 | static bool LoadLibUringSyms(void) | ||
| 106 | { | ||
| 107 | #define SDL_LIBURING_FUNC(ret, fn, args) { \ | ||
| 108 | liburing.fn = (SDL_fntype_##fn) SDL_LoadFunction(liburing_handle, #fn); \ | ||
| 109 | if (!liburing.fn) { \ | ||
| 110 | return false; \ | ||
| 111 | } \ | ||
| 112 | } | ||
| 113 | SDL_LIBURING_FUNCS | ||
| 114 | #undef SDL_LIBURING_FUNC | ||
| 115 | return true; | ||
| 116 | } | ||
| 117 | |||
| 118 | // we rely on the presence of liburing to handle io_uring for us. The alternative is making | ||
| 119 | // direct syscalls into the kernel, which is undesirable. liburing both shields us from this, | ||
| 120 | // but also smooths over some kernel version differences, etc. | ||
| 121 | static bool LoadLibUring(void) | ||
| 122 | { | ||
| 123 | bool result = true; | ||
| 124 | |||
| 125 | if (!liburing_handle) { | ||
| 126 | liburing_handle = SDL_LoadObject(liburing_library); | ||
| 127 | if (!liburing_handle) { | ||
| 128 | result = false; | ||
| 129 | // Don't call SDL_SetError(): SDL_LoadObject already did. | ||
| 130 | } else { | ||
| 131 | result = LoadLibUringSyms(); | ||
| 132 | if (result) { | ||
| 133 | static const int needed_ops[] = { | ||
| 134 | IORING_OP_NOP, | ||
| 135 | IORING_OP_FSYNC, | ||
| 136 | IORING_OP_TIMEOUT, | ||
| 137 | IORING_OP_CLOSE, | ||
| 138 | IORING_OP_READ, | ||
| 139 | IORING_OP_WRITE, | ||
| 140 | IORING_OP_ASYNC_CANCEL | ||
| 141 | }; | ||
| 142 | |||
| 143 | struct io_uring_probe *probe = liburing.io_uring_get_probe(); | ||
| 144 | if (!probe) { | ||
| 145 | result = false; | ||
| 146 | } else { | ||
| 147 | for (int i = 0; i < SDL_arraysize(needed_ops); i++) { | ||
| 148 | if (!io_uring_opcode_supported(probe, needed_ops[i])) { | ||
| 149 | result = false; | ||
| 150 | break; | ||
| 151 | } | ||
| 152 | } | ||
| 153 | liburing.io_uring_free_probe(probe); | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | if (!result) { | ||
| 158 | UnloadLibUringLibrary(); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | } | ||
| 162 | return result; | ||
| 163 | } | ||
| 164 | |||
| 165 | static bool liburing_SetError(const char *what, int err) | ||
| 166 | { | ||
| 167 | SDL_assert(err <= 0); | ||
| 168 | return SDL_SetError("%s failed: %s", what, strerror(-err)); | ||
| 169 | } | ||
| 170 | |||
| 171 | static Sint64 liburing_asyncio_size(void *userdata) | ||
| 172 | { | ||
| 173 | const int fd = (int) (intptr_t) userdata; | ||
| 174 | struct stat statbuf; | ||
| 175 | if (fstat(fd, &statbuf) < 0) { | ||
| 176 | SDL_SetError("fstat failed: %s", strerror(errno)); | ||
| 177 | return -1; | ||
| 178 | } | ||
| 179 | return ((Sint64) statbuf.st_size); | ||
| 180 | } | ||
| 181 | |||
| 182 | // you must hold sqe_lock when calling this! | ||
| 183 | static bool liburing_asyncioqueue_queue_task(void *userdata, SDL_AsyncIOTask *task) | ||
| 184 | { | ||
| 185 | LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata; | ||
| 186 | const int rc = liburing.io_uring_submit(&queuedata->ring); | ||
| 187 | return (rc < 0) ? liburing_SetError("io_uring_submit", rc) : true; | ||
| 188 | } | ||
| 189 | |||
| 190 | static void liburing_asyncioqueue_cancel_task(void *userdata, SDL_AsyncIOTask *task) | ||
| 191 | { | ||
| 192 | SDL_AsyncIOTask *cancel_task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*cancel_task)); | ||
| 193 | if (!cancel_task) { | ||
| 194 | return; // oh well, the task can just finish on its own. | ||
| 195 | } | ||
| 196 | |||
| 197 | LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata; | ||
| 198 | |||
| 199 | // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up. | ||
| 200 | SDL_LockMutex(queuedata->sqe_lock); | ||
| 201 | struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring); | ||
| 202 | if (!sqe) { | ||
| 203 | SDL_UnlockMutex(queuedata->sqe_lock); | ||
| 204 | SDL_free(cancel_task); // oh well, the task can just finish on its own. | ||
| 205 | return; | ||
| 206 | } | ||
| 207 | |||
| 208 | cancel_task->app_userdata = task; | ||
| 209 | liburing.io_uring_prep_cancel(sqe, task, 0); | ||
| 210 | liburing.io_uring_sqe_set_data(sqe, cancel_task); | ||
| 211 | liburing_asyncioqueue_queue_task(userdata, task); | ||
| 212 | SDL_UnlockMutex(queuedata->sqe_lock); | ||
| 213 | } | ||
| 214 | |||
| 215 | static SDL_AsyncIOTask *ProcessCQE(LibUringAsyncIOQueueData *queuedata, struct io_uring_cqe *cqe) | ||
| 216 | { | ||
| 217 | if (!cqe) { | ||
| 218 | return NULL; | ||
| 219 | } | ||
| 220 | |||
| 221 | SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) io_uring_cqe_get_data(cqe); | ||
| 222 | if (task) { // can be NULL if this was just a wakeup message, a NOP, etc. | ||
| 223 | if (!task->queue) { // We leave `queue` blank to signify this was a task cancellation. | ||
| 224 | SDL_AsyncIOTask *cancel_task = task; | ||
| 225 | task = (SDL_AsyncIOTask *) cancel_task->app_userdata; | ||
| 226 | SDL_free(cancel_task); | ||
| 227 | if (cqe->res >= 0) { // cancel was successful? | ||
| 228 | task->result = SDL_ASYNCIO_CANCELED; | ||
| 229 | } else { | ||
| 230 | task = NULL; // it already finished or was too far along to cancel, so we'll pick up the actual results later. | ||
| 231 | } | ||
| 232 | } else if (cqe->res < 0) { | ||
| 233 | task->result = SDL_ASYNCIO_FAILURE; | ||
| 234 | // !!! FIXME: fill in task->error. | ||
| 235 | } else { | ||
| 236 | if ((task->type == SDL_ASYNCIO_TASK_WRITE) && (((Uint64) cqe->res) < task->requested_size)) { | ||
| 237 | task->result = SDL_ASYNCIO_FAILURE; // it's always a failure on short writes. | ||
| 238 | } | ||
| 239 | |||
| 240 | // don't explicitly mark it as COMPLETE; that's the default value and a linked task might have failed in an earlier operation and this would overwrite it. | ||
| 241 | |||
| 242 | if ((task->type == SDL_ASYNCIO_TASK_READ) || (task->type == SDL_ASYNCIO_TASK_WRITE)) { | ||
| 243 | task->result_size = (Uint64) cqe->res; | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | if ((task->type == SDL_ASYNCIO_TASK_CLOSE) && task->flush) { | ||
| 248 | task->flush = false; | ||
| 249 | task = NULL; // don't return this one, it's a linked task, so it'll arrive in a later CQE. | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | return task; | ||
| 254 | } | ||
| 255 | |||
| 256 | static SDL_AsyncIOTask *liburing_asyncioqueue_get_results(void *userdata) | ||
| 257 | { | ||
| 258 | LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata; | ||
| 259 | |||
| 260 | // have to hold a lock because otherwise two threads will get the same cqe until we mark it "seen". Copy and mark it right away, then process further. | ||
| 261 | SDL_LockMutex(queuedata->cqe_lock); | ||
| 262 | struct io_uring_cqe *cqe = NULL; | ||
| 263 | const int rc = liburing.io_uring_peek_cqe(&queuedata->ring, &cqe); | ||
| 264 | if (rc != 0) { | ||
| 265 | SDL_assert(rc == -EAGAIN); // should only fail because nothing is available at the moment. | ||
| 266 | SDL_UnlockMutex(queuedata->cqe_lock); | ||
| 267 | return NULL; | ||
| 268 | } | ||
| 269 | |||
| 270 | struct io_uring_cqe cqe_copy; | ||
| 271 | SDL_copyp(&cqe_copy, cqe); // this is only a few bytes. | ||
| 272 | liburing.io_uring_cqe_seen(&queuedata->ring, cqe); // let io_uring use this slot again. | ||
| 273 | SDL_UnlockMutex(queuedata->cqe_lock); | ||
| 274 | |||
| 275 | return ProcessCQE(queuedata, &cqe_copy); | ||
| 276 | } | ||
| 277 | |||
| 278 | static SDL_AsyncIOTask *liburing_asyncioqueue_wait_results(void *userdata, Sint32 timeoutMS) | ||
| 279 | { | ||
| 280 | LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata; | ||
| 281 | struct io_uring_cqe *cqe = NULL; | ||
| 282 | |||
| 283 | SDL_AddAtomicInt(&queuedata->num_waiting, 1); | ||
| 284 | if (timeoutMS < 0) { | ||
| 285 | liburing.io_uring_wait_cqe(&queuedata->ring, &cqe); | ||
| 286 | } else { | ||
| 287 | struct __kernel_timespec ts = { (Sint64) timeoutMS / SDL_MS_PER_SECOND, (Sint64) SDL_MS_TO_NS(timeoutMS % SDL_MS_PER_SECOND) }; | ||
| 288 | liburing.io_uring_wait_cqe_timeout(&queuedata->ring, &cqe, &ts); | ||
| 289 | } | ||
| 290 | SDL_AddAtomicInt(&queuedata->num_waiting, -1); | ||
| 291 | |||
| 292 | // (we don't care if the wait failed for any reason, as the upcoming peek_cqe will report valid information. We just wanted the wait operation to block.) | ||
| 293 | |||
| 294 | // each thing that peeks or waits for a completion _gets the same cqe_ until we mark it as seen. So when we wake up from the wait, lock the mutex and | ||
| 295 | // then use peek to make sure we have a unique cqe, and other competing threads either get their own or nothing. | ||
| 296 | return liburing_asyncioqueue_get_results(userdata); // this just happens to do all those things. | ||
| 297 | } | ||
| 298 | |||
| 299 | static void liburing_asyncioqueue_signal(void *userdata) | ||
| 300 | { | ||
| 301 | LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata; | ||
| 302 | const int num_waiting = SDL_GetAtomicInt(&queuedata->num_waiting); | ||
| 303 | |||
| 304 | SDL_LockMutex(queuedata->sqe_lock); | ||
| 305 | for (int i = 0; i < num_waiting; i++) { // !!! FIXME: is there a better way to do this than pushing a zero-timeout request for everything waiting? | ||
| 306 | struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring); | ||
| 307 | if (sqe) { | ||
| 308 | static struct __kernel_timespec ts; // no wait, just wake a thread as fast as this can land in the completion queue. | ||
| 309 | liburing.io_uring_prep_timeout(sqe, &ts, 0, 0); | ||
| 310 | liburing.io_uring_sqe_set_data(sqe, NULL); | ||
| 311 | } | ||
| 312 | } | ||
| 313 | liburing.io_uring_submit(&queuedata->ring); | ||
| 314 | |||
| 315 | SDL_UnlockMutex(queuedata->sqe_lock); | ||
| 316 | } | ||
| 317 | |||
| 318 | static void liburing_asyncioqueue_destroy(void *userdata) | ||
| 319 | { | ||
| 320 | LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata; | ||
| 321 | liburing.io_uring_queue_exit(&queuedata->ring); | ||
| 322 | SDL_DestroyMutex(queuedata->sqe_lock); | ||
| 323 | SDL_DestroyMutex(queuedata->cqe_lock); | ||
| 324 | SDL_free(queuedata); | ||
| 325 | } | ||
| 326 | |||
| 327 | static bool SDL_SYS_CreateAsyncIOQueue_liburing(SDL_AsyncIOQueue *queue) | ||
| 328 | { | ||
| 329 | LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) SDL_calloc(1, sizeof (*queuedata)); | ||
| 330 | if (!queuedata) { | ||
| 331 | return false; | ||
| 332 | } | ||
| 333 | |||
| 334 | SDL_SetAtomicInt(&queuedata->num_waiting, 0); | ||
| 335 | |||
| 336 | queuedata->sqe_lock = SDL_CreateMutex(); | ||
| 337 | if (!queuedata->sqe_lock) { | ||
| 338 | SDL_free(queuedata); | ||
| 339 | return false; | ||
| 340 | } | ||
| 341 | |||
| 342 | queuedata->cqe_lock = SDL_CreateMutex(); | ||
| 343 | if (!queuedata->cqe_lock) { | ||
| 344 | SDL_DestroyMutex(queuedata->sqe_lock); | ||
| 345 | SDL_free(queuedata); | ||
| 346 | return false; | ||
| 347 | } | ||
| 348 | |||
| 349 | // !!! FIXME: no idea how large the queue should be. Is 128 overkill or too small? | ||
| 350 | const int rc = liburing.io_uring_queue_init(128, &queuedata->ring, 0); | ||
| 351 | if (rc != 0) { | ||
| 352 | SDL_DestroyMutex(queuedata->sqe_lock); | ||
| 353 | SDL_DestroyMutex(queuedata->cqe_lock); | ||
| 354 | SDL_free(queuedata); | ||
| 355 | return liburing_SetError("io_uring_queue_init", rc); | ||
| 356 | } | ||
| 357 | |||
| 358 | static const SDL_AsyncIOQueueInterface SDL_AsyncIOQueue_liburing = { | ||
| 359 | liburing_asyncioqueue_queue_task, | ||
| 360 | liburing_asyncioqueue_cancel_task, | ||
| 361 | liburing_asyncioqueue_get_results, | ||
| 362 | liburing_asyncioqueue_wait_results, | ||
| 363 | liburing_asyncioqueue_signal, | ||
| 364 | liburing_asyncioqueue_destroy | ||
| 365 | }; | ||
| 366 | |||
| 367 | SDL_copyp(&queue->iface, &SDL_AsyncIOQueue_liburing); | ||
| 368 | queue->userdata = queuedata; | ||
| 369 | return true; | ||
| 370 | } | ||
| 371 | |||
| 372 | |||
| 373 | static bool liburing_asyncio_read(void *userdata, SDL_AsyncIOTask *task) | ||
| 374 | { | ||
| 375 | LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) task->queue->userdata; | ||
| 376 | const int fd = (int) (intptr_t) userdata; | ||
| 377 | |||
| 378 | // !!! FIXME: `unsigned` is likely smaller than requested_size's Uint64. If we overflow it, we could try submitting multiple SQEs | ||
| 379 | // !!! FIXME: and make a note in the task that there are several in sequence. | ||
| 380 | if (task->requested_size > ((Uint64) ~((unsigned) 0))) { | ||
| 381 | return SDL_SetError("io_uring: i/o task is too large"); | ||
| 382 | } | ||
| 383 | |||
| 384 | // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up. | ||
| 385 | SDL_LockMutex(queuedata->sqe_lock); | ||
| 386 | bool retval; | ||
| 387 | struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring); | ||
| 388 | if (!sqe) { | ||
| 389 | retval = SDL_SetError("io_uring: submission queue is full"); | ||
| 390 | } else { | ||
| 391 | liburing.io_uring_prep_read(sqe, fd, task->buffer, (unsigned) task->requested_size, task->offset); | ||
| 392 | liburing.io_uring_sqe_set_data(sqe, task); | ||
| 393 | retval = task->queue->iface.queue_task(task->queue->userdata, task); | ||
| 394 | } | ||
| 395 | SDL_UnlockMutex(queuedata->sqe_lock); | ||
| 396 | return retval; | ||
| 397 | } | ||
| 398 | |||
| 399 | static bool liburing_asyncio_write(void *userdata, SDL_AsyncIOTask *task) | ||
| 400 | { | ||
| 401 | LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) task->queue->userdata; | ||
| 402 | const int fd = (int) (intptr_t) userdata; | ||
| 403 | |||
| 404 | // !!! FIXME: `unsigned` is likely smaller than requested_size's Uint64. If we overflow it, we could try submitting multiple SQEs | ||
| 405 | // !!! FIXME: and make a note in the task that there are several in sequence. | ||
| 406 | if (task->requested_size > ((Uint64) ~((unsigned) 0))) { | ||
| 407 | return SDL_SetError("io_uring: i/o task is too large"); | ||
| 408 | } | ||
| 409 | |||
| 410 | // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up. | ||
| 411 | SDL_LockMutex(queuedata->sqe_lock); | ||
| 412 | bool retval; | ||
| 413 | struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring); | ||
| 414 | if (!sqe) { | ||
| 415 | retval = SDL_SetError("io_uring: submission queue is full"); | ||
| 416 | } else { | ||
| 417 | liburing.io_uring_prep_write(sqe, fd, task->buffer, (unsigned) task->requested_size, task->offset); | ||
| 418 | liburing.io_uring_sqe_set_data(sqe, task); | ||
| 419 | retval = task->queue->iface.queue_task(task->queue->userdata, task); | ||
| 420 | } | ||
| 421 | SDL_UnlockMutex(queuedata->sqe_lock); | ||
| 422 | return retval; | ||
| 423 | } | ||
| 424 | |||
| 425 | static bool liburing_asyncio_close(void *userdata, SDL_AsyncIOTask *task) | ||
| 426 | { | ||
| 427 | LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) task->queue->userdata; | ||
| 428 | const int fd = (int) (intptr_t) userdata; | ||
| 429 | |||
| 430 | // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up. | ||
| 431 | SDL_LockMutex(queuedata->sqe_lock); | ||
| 432 | bool retval; | ||
| 433 | struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring); | ||
| 434 | if (!sqe) { | ||
| 435 | retval = SDL_SetError("io_uring: submission queue is full"); | ||
| 436 | } else { | ||
| 437 | if (task->flush) { | ||
| 438 | struct io_uring_sqe *flush_sqe = sqe; | ||
| 439 | sqe = liburing.io_uring_get_sqe(&queuedata->ring); // this will be our actual close task. | ||
| 440 | if (!sqe) { | ||
| 441 | liburing.io_uring_prep_nop(flush_sqe); // we already have the first sqe, just make it a NOP. | ||
| 442 | liburing.io_uring_sqe_set_data(flush_sqe, NULL); | ||
| 443 | task->queue->iface.queue_task(task->queue->userdata, task); | ||
| 444 | SDL_UnlockMutex(queuedata->sqe_lock); | ||
| 445 | return SDL_SetError("io_uring: submission queue is full"); | ||
| 446 | } | ||
| 447 | liburing.io_uring_prep_fsync(flush_sqe, fd, IORING_FSYNC_DATASYNC); | ||
| 448 | liburing.io_uring_sqe_set_data(flush_sqe, task); | ||
| 449 | liburing.io_uring_sqe_set_flags(flush_sqe, IOSQE_IO_HARDLINK); // must complete before next sqe starts, and next sqe should run even if this fails. | ||
| 450 | } | ||
| 451 | |||
| 452 | liburing.io_uring_prep_close(sqe, fd); | ||
| 453 | liburing.io_uring_sqe_set_data(sqe, task); | ||
| 454 | |||
| 455 | retval = task->queue->iface.queue_task(task->queue->userdata, task); | ||
| 456 | } | ||
| 457 | SDL_UnlockMutex(queuedata->sqe_lock); | ||
| 458 | return retval; | ||
| 459 | } | ||
| 460 | |||
| 461 | static void liburing_asyncio_destroy(void *userdata) | ||
| 462 | { | ||
| 463 | // this is only a Unix file descriptor, should have been closed elsewhere. | ||
| 464 | } | ||
| 465 | |||
| 466 | static int PosixOpenModeFromString(const char *mode) | ||
| 467 | { | ||
| 468 | // this is exactly the set of strings that SDL_AsyncIOFromFile promises will work. | ||
| 469 | static const struct { const char *str; int flags; } mappings[] = { | ||
| 470 | { "rb", O_RDONLY }, | ||
| 471 | { "wb", O_WRONLY | O_CREAT | O_TRUNC }, | ||
| 472 | { "r+b", O_RDWR }, | ||
| 473 | { "w+b", O_RDWR | O_CREAT | O_TRUNC } | ||
| 474 | }; | ||
| 475 | |||
| 476 | for (int i = 0; i < SDL_arraysize(mappings); i++) { | ||
| 477 | if (SDL_strcmp(mappings[i].str, mode) == 0) { | ||
| 478 | return mappings[i].flags; | ||
| 479 | } | ||
| 480 | } | ||
| 481 | |||
| 482 | SDL_assert(!"Shouldn't have reached this code"); | ||
| 483 | return 0; | ||
| 484 | } | ||
| 485 | |||
| 486 | static bool SDL_SYS_AsyncIOFromFile_liburing(const char *file, const char *mode, SDL_AsyncIO *asyncio) | ||
| 487 | { | ||
| 488 | const int fd = open(file, PosixOpenModeFromString(mode), 0644); | ||
| 489 | if (fd == -1) { | ||
| 490 | return SDL_SetError("open failed: %s", strerror(errno)); | ||
| 491 | } | ||
| 492 | |||
| 493 | static const SDL_AsyncIOInterface SDL_AsyncIOFile_liburing = { | ||
| 494 | liburing_asyncio_size, | ||
| 495 | liburing_asyncio_read, | ||
| 496 | liburing_asyncio_write, | ||
| 497 | liburing_asyncio_close, | ||
| 498 | liburing_asyncio_destroy | ||
| 499 | }; | ||
| 500 | |||
| 501 | SDL_copyp(&asyncio->iface, &SDL_AsyncIOFile_liburing); | ||
| 502 | asyncio->userdata = (void *) (intptr_t) fd; | ||
| 503 | return true; | ||
| 504 | } | ||
| 505 | |||
| 506 | static void SDL_SYS_QuitAsyncIO_liburing(void) | ||
| 507 | { | ||
| 508 | UnloadLibUringLibrary(); | ||
| 509 | } | ||
| 510 | |||
| 511 | static void MaybeInitializeLibUring(void) | ||
| 512 | { | ||
| 513 | if (SDL_ShouldInit(&liburing_init)) { | ||
| 514 | if (LoadLibUring()) { | ||
| 515 | CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_liburing; | ||
| 516 | QuitAsyncIO = SDL_SYS_QuitAsyncIO_liburing; | ||
| 517 | AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_liburing; | ||
| 518 | } else { // can't use liburing? Use the "generic" threadpool implementation instead. | ||
| 519 | CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_Generic; | ||
| 520 | QuitAsyncIO = SDL_SYS_QuitAsyncIO_Generic; | ||
| 521 | AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_Generic; | ||
| 522 | } | ||
| 523 | SDL_SetInitialized(&liburing_init, true); | ||
| 524 | } | ||
| 525 | } | ||
| 526 | |||
| 527 | bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue) | ||
| 528 | { | ||
| 529 | MaybeInitializeLibUring(); | ||
| 530 | return CreateAsyncIOQueue(queue); | ||
| 531 | } | ||
| 532 | |||
| 533 | bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio) | ||
| 534 | { | ||
| 535 | MaybeInitializeLibUring(); | ||
| 536 | return AsyncIOFromFile(file, mode, asyncio); | ||
| 537 | } | ||
| 538 | |||
| 539 | void SDL_SYS_QuitAsyncIO(void) | ||
| 540 | { | ||
| 541 | if (SDL_ShouldQuit(&liburing_init)) { | ||
| 542 | QuitAsyncIO(); | ||
| 543 | CreateAsyncIOQueue = NULL; | ||
| 544 | QuitAsyncIO = NULL; | ||
| 545 | AsyncIOFromFile = NULL; | ||
| 546 | SDL_SetInitialized(&liburing_init, false); | ||
| 547 | } | ||
| 548 | } | ||
| 549 | |||
| 550 | #endif // defined HAVE_LIBURING_H | ||
| 551 | |||
diff --git a/contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.c b/contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.c new file mode 100644 index 0000000..b42f539 --- /dev/null +++ b/contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.c | |||
| @@ -0,0 +1,88 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | #include "SDL_iostreamromfs.h" | ||
| 23 | |||
| 24 | // Checks if the mode is a kind of reading | ||
| 25 | static bool IsReadMode(const char *mode); | ||
| 26 | |||
| 27 | // Checks if the file starts with the given prefix | ||
| 28 | static bool HasPrefix(const char *file, const char *prefix); | ||
| 29 | |||
| 30 | static FILE *TryOpenFile(const char *file, const char *mode); | ||
| 31 | static FILE *TryOpenInRomfs(const char *file, const char *mode); | ||
| 32 | |||
| 33 | /* Nintendo 3DS applications may embed resources in the executable. The | ||
| 34 | resources are stored in a special read-only partition prefixed with | ||
| 35 | 'romfs:/'. As such, when opening a file, we should first try the romfs | ||
| 36 | unless sdmc is specifically mentioned. | ||
| 37 | */ | ||
| 38 | FILE *N3DS_FileOpen(const char *file, const char *mode) | ||
| 39 | { | ||
| 40 | // romfs are read-only | ||
| 41 | if (!IsReadMode(mode)) { | ||
| 42 | return fopen(file, mode); | ||
| 43 | } | ||
| 44 | |||
| 45 | // If the path has an explicit prefix, we skip the guess work | ||
| 46 | if (HasPrefix(file, "romfs:/") || HasPrefix(file, "sdmc:/")) { | ||
| 47 | return fopen(file, mode); | ||
| 48 | } | ||
| 49 | |||
| 50 | return TryOpenFile(file, mode); | ||
| 51 | } | ||
| 52 | |||
| 53 | static bool IsReadMode(const char *mode) | ||
| 54 | { | ||
| 55 | return SDL_strchr(mode, 'r') != NULL; | ||
| 56 | } | ||
| 57 | |||
| 58 | static bool HasPrefix(const char *file, const char *prefix) | ||
| 59 | { | ||
| 60 | return SDL_strncmp(prefix, file, SDL_strlen(prefix)) == 0; | ||
| 61 | } | ||
| 62 | |||
| 63 | static FILE *TryOpenFile(const char *file, const char *mode) | ||
| 64 | { | ||
| 65 | FILE *fp = NULL; | ||
| 66 | |||
| 67 | fp = TryOpenInRomfs(file, mode); | ||
| 68 | if (!fp) { | ||
| 69 | fp = fopen(file, mode); | ||
| 70 | } | ||
| 71 | |||
| 72 | return fp; | ||
| 73 | } | ||
| 74 | |||
| 75 | FILE *TryOpenInRomfs(const char *file, const char *mode) | ||
| 76 | { | ||
| 77 | FILE *fp = NULL; | ||
| 78 | char *prefixed_filepath = NULL; | ||
| 79 | |||
| 80 | if (SDL_asprintf(&prefixed_filepath, "romfs:/%s", file) < 0) { | ||
| 81 | return NULL; | ||
| 82 | } | ||
| 83 | |||
| 84 | fp = fopen(prefixed_filepath, mode); | ||
| 85 | |||
| 86 | SDL_free(prefixed_filepath); | ||
| 87 | return fp; | ||
| 88 | } | ||
diff --git a/contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.h b/contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.h new file mode 100644 index 0000000..b931390 --- /dev/null +++ b/contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.h | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | #include "SDL_internal.h" | ||
| 22 | |||
| 23 | #ifndef SDL_iostreamromfs_h_ | ||
| 24 | #define SDL_iostreamromfs_h_ | ||
| 25 | |||
| 26 | FILE *N3DS_FileOpen(const char *file, const char *mode); | ||
| 27 | |||
| 28 | #endif // SDL_iostreamromfs_h_ | ||
diff --git a/contrib/SDL-3.2.8/src/io/windows/SDL_asyncio_windows_ioring.c b/contrib/SDL-3.2.8/src/io/windows/SDL_asyncio_windows_ioring.c new file mode 100644 index 0000000..2d38efc --- /dev/null +++ b/contrib/SDL-3.2.8/src/io/windows/SDL_asyncio_windows_ioring.c | |||
| @@ -0,0 +1,550 @@ | |||
| 1 | /* | ||
| 2 | Simple DirectMedia Layer | ||
| 3 | Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org> | ||
| 4 | |||
| 5 | This software is provided 'as-is', without any express or implied | ||
| 6 | warranty. In no event will the authors be held liable for any damages | ||
| 7 | arising from the use of this software. | ||
| 8 | |||
| 9 | Permission is granted to anyone to use this software for any purpose, | ||
| 10 | including commercial applications, and to alter it and redistribute it | ||
| 11 | freely, subject to the following restrictions: | ||
| 12 | |||
| 13 | 1. The origin of this software must not be misrepresented; you must not | ||
| 14 | claim that you wrote the original software. If you use this software | ||
| 15 | in a product, an acknowledgment in the product documentation would be | ||
| 16 | appreciated but is not required. | ||
| 17 | 2. Altered source versions must be plainly marked as such, and must not be | ||
| 18 | misrepresented as being the original software. | ||
| 19 | 3. This notice may not be removed or altered from any source distribution. | ||
| 20 | */ | ||
| 21 | |||
| 22 | // The Windows backend uses IoRing for asynchronous i/o, and falls back to | ||
| 23 | // the "generic" threadpool implementation if it isn't available or | ||
| 24 | // fails for some other reason. IoRing was introduced in Windows 11. | ||
| 25 | |||
| 26 | #include "SDL_internal.h" | ||
| 27 | #include "../SDL_sysasyncio.h" | ||
| 28 | |||
| 29 | #ifdef HAVE_IORINGAPI_H | ||
| 30 | |||
| 31 | #include "../../core/windows/SDL_windows.h" | ||
| 32 | #include <ioringapi.h> | ||
| 33 | |||
| 34 | // Don't know what the lowest usable version is, but this seems safe. | ||
| 35 | #define SDL_REQUIRED_IORING_VERSION IORING_VERSION_3 | ||
| 36 | |||
| 37 | static SDL_InitState ioring_init; | ||
| 38 | |||
| 39 | // We could add a whole bootstrap thing like the audio/video/etc subsystems use, but let's keep this simple for now. | ||
| 40 | static bool (*CreateAsyncIOQueue)(SDL_AsyncIOQueue *queue); | ||
| 41 | static void (*QuitAsyncIO)(void); | ||
| 42 | static bool (*AsyncIOFromFile)(const char *file, const char *mode, SDL_AsyncIO *asyncio); | ||
| 43 | |||
| 44 | // we never link directly to ioring. | ||
| 45 | static const char *ioring_library = "KernelBase.dll"; | ||
| 46 | static void *ioring_handle = NULL; | ||
| 47 | |||
| 48 | #define SDL_IORING_FUNCS \ | ||
| 49 | SDL_IORING_FUNC(HRESULT, QueryIoRingCapabilities, (IORING_CAPABILITIES *capabilities)) \ | ||
| 50 | SDL_IORING_FUNC(BOOL, IsIoRingOpSupported, (HIORING ioRing, IORING_OP_CODE op)) \ | ||
| 51 | SDL_IORING_FUNC(HRESULT, CreateIoRing, (IORING_VERSION ioringVersion, IORING_CREATE_FLAGS flags, UINT32 submissionQueueSize, UINT32 completionQueueSize, HIORING* h)) \ | ||
| 52 | SDL_IORING_FUNC(HRESULT, GetIoRingInfo, (HIORING ioRing, IORING_INFO* info)) \ | ||
| 53 | SDL_IORING_FUNC(HRESULT, SubmitIoRing, (HIORING ioRing, UINT32 waitOperations, UINT32 milliseconds, UINT32* submittedEntries)) \ | ||
| 54 | SDL_IORING_FUNC(HRESULT, CloseIoRing, (HIORING ioRing)) \ | ||
| 55 | SDL_IORING_FUNC(HRESULT, PopIoRingCompletion, (HIORING ioRing, IORING_CQE* cqe)) \ | ||
| 56 | SDL_IORING_FUNC(HRESULT, SetIoRingCompletionEvent, (HIORING ioRing, HANDLE hEvent)) \ | ||
| 57 | SDL_IORING_FUNC(HRESULT, BuildIoRingCancelRequest, (HIORING ioRing, IORING_HANDLE_REF file, UINT_PTR opToCancel, UINT_PTR userData)) \ | ||
| 58 | SDL_IORING_FUNC(HRESULT, BuildIoRingReadFile, (HIORING ioRing, IORING_HANDLE_REF fileRef, IORING_BUFFER_REF dataRef, UINT32 numberOfBytesToRead, UINT64 fileOffset, UINT_PTR userData, IORING_SQE_FLAGS sqeFlags)) \ | ||
| 59 | SDL_IORING_FUNC(HRESULT, BuildIoRingWriteFile, (HIORING ioRing, IORING_HANDLE_REF fileRef, IORING_BUFFER_REF bufferRef, UINT32 numberOfBytesToWrite, UINT64 fileOffset, FILE_WRITE_FLAGS writeFlags, UINT_PTR userData, IORING_SQE_FLAGS sqeFlags)) \ | ||
| 60 | SDL_IORING_FUNC(HRESULT, BuildIoRingFlushFile, (HIORING ioRing, IORING_HANDLE_REF fileRef, FILE_FLUSH_MODE flushMode, UINT_PTR userData, IORING_SQE_FLAGS sqeFlags)) \ | ||
| 61 | |||
| 62 | #define SDL_IORING_FUNC(ret, fn, args) typedef ret (WINAPI *SDL_fntype_##fn) args; | ||
| 63 | SDL_IORING_FUNCS | ||
| 64 | #undef SDL_IORING_FUNC | ||
| 65 | |||
| 66 | typedef struct SDL_WinIoRingFunctions | ||
| 67 | { | ||
| 68 | #define SDL_IORING_FUNC(ret, fn, args) SDL_fntype_##fn fn; | ||
| 69 | SDL_IORING_FUNCS | ||
| 70 | #undef SDL_IORING_FUNC | ||
| 71 | } SDL_WinIoRingFunctions; | ||
| 72 | |||
| 73 | static SDL_WinIoRingFunctions ioring; | ||
| 74 | |||
| 75 | |||
| 76 | typedef struct WinIoRingAsyncIOQueueData | ||
| 77 | { | ||
| 78 | SDL_Mutex *sqe_lock; | ||
| 79 | SDL_Mutex *cqe_lock; | ||
| 80 | HANDLE event; | ||
| 81 | HIORING ring; | ||
| 82 | SDL_AtomicInt num_waiting; | ||
| 83 | } WinIoRingAsyncIOQueueData; | ||
| 84 | |||
| 85 | |||
| 86 | static void UnloadWinIoRingLibrary(void) | ||
| 87 | { | ||
| 88 | if (ioring_library) { | ||
| 89 | SDL_UnloadObject(ioring_handle); | ||
| 90 | ioring_library = NULL; | ||
| 91 | } | ||
| 92 | SDL_zero(ioring); | ||
| 93 | } | ||
| 94 | |||
| 95 | static bool LoadWinIoRingSyms(void) | ||
| 96 | { | ||
| 97 | #define SDL_IORING_FUNC(ret, fn, args) { \ | ||
| 98 | ioring.fn = (SDL_fntype_##fn) SDL_LoadFunction(ioring_handle, #fn); \ | ||
| 99 | if (!ioring.fn) { \ | ||
| 100 | return false; \ | ||
| 101 | } \ | ||
| 102 | } | ||
| 103 | SDL_IORING_FUNCS | ||
| 104 | #undef SDL_IORING_FUNC | ||
| 105 | return true; | ||
| 106 | } | ||
| 107 | |||
| 108 | static bool LoadWinIoRing(void) | ||
| 109 | { | ||
| 110 | bool result = true; | ||
| 111 | |||
| 112 | if (!ioring_handle) { | ||
| 113 | ioring_handle = SDL_LoadObject(ioring_library); | ||
| 114 | if (!ioring_handle) { | ||
| 115 | result = false; | ||
| 116 | // Don't call SDL_SetError(): SDL_LoadObject already did. | ||
| 117 | } else { | ||
| 118 | result = LoadWinIoRingSyms(); | ||
| 119 | if (result) { | ||
| 120 | IORING_CAPABILITIES caps; | ||
| 121 | HRESULT hr = ioring.QueryIoRingCapabilities(&caps); | ||
| 122 | if (FAILED(hr)) { | ||
| 123 | result = false; | ||
| 124 | } else if (caps.MaxVersion < SDL_REQUIRED_IORING_VERSION) { | ||
| 125 | result = false; | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | if (!result) { | ||
| 130 | UnloadWinIoRingLibrary(); | ||
| 131 | } | ||
| 132 | } | ||
| 133 | } | ||
| 134 | return result; | ||
| 135 | } | ||
| 136 | |||
| 137 | static Sint64 ioring_asyncio_size(void *userdata) | ||
| 138 | { | ||
| 139 | HANDLE handle = (HANDLE) userdata; | ||
| 140 | LARGE_INTEGER size; | ||
| 141 | if (!GetFileSizeEx(handle, &size)) { | ||
| 142 | WIN_SetError("GetFileSizeEx"); | ||
| 143 | return -1; | ||
| 144 | } | ||
| 145 | return (Sint64) size.QuadPart; | ||
| 146 | } | ||
| 147 | |||
| 148 | // you must hold sqe_lock when calling this! | ||
| 149 | static bool ioring_asyncioqueue_queue_task(void *userdata, SDL_AsyncIOTask *task) | ||
| 150 | { | ||
| 151 | WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata; | ||
| 152 | const HRESULT hr = ioring.SubmitIoRing(queuedata->ring, 0, 0, NULL); | ||
| 153 | return (FAILED(hr) ? WIN_SetErrorFromHRESULT("SubmitIoRing", hr) : true); | ||
| 154 | } | ||
| 155 | |||
| 156 | static void ioring_asyncioqueue_cancel_task(void *userdata, SDL_AsyncIOTask *task) | ||
| 157 | { | ||
| 158 | if (!task->asyncio || !task->asyncio->userdata) { | ||
| 159 | return; // Windows IoRing needs the file handle in question, so we'll just have to let it complete if unknown. | ||
| 160 | } | ||
| 161 | |||
| 162 | SDL_AsyncIOTask *cancel_task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*cancel_task)); | ||
| 163 | if (!cancel_task) { | ||
| 164 | return; // oh well, the task can just finish on its own. | ||
| 165 | } | ||
| 166 | |||
| 167 | WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata; | ||
| 168 | HANDLE handle = (HANDLE) task->asyncio->userdata; | ||
| 169 | IORING_HANDLE_REF href = IoRingHandleRefFromHandle(handle); | ||
| 170 | |||
| 171 | // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up. | ||
| 172 | SDL_LockMutex(queuedata->sqe_lock); | ||
| 173 | const HRESULT hr = ioring.BuildIoRingCancelRequest(queuedata->ring, href, (UINT_PTR) task, (UINT_PTR) cancel_task); | ||
| 174 | if (FAILED(hr)) { | ||
| 175 | SDL_UnlockMutex(queuedata->sqe_lock); | ||
| 176 | SDL_free(cancel_task); // oh well, the task can just finish on its own. | ||
| 177 | return; | ||
| 178 | } | ||
| 179 | |||
| 180 | cancel_task->app_userdata = task; | ||
| 181 | ioring_asyncioqueue_queue_task(userdata, task); | ||
| 182 | SDL_UnlockMutex(queuedata->sqe_lock); | ||
| 183 | } | ||
| 184 | |||
| 185 | static SDL_AsyncIOTask *ProcessCQE(WinIoRingAsyncIOQueueData *queuedata, IORING_CQE *cqe) | ||
| 186 | { | ||
| 187 | if (!cqe) { | ||
| 188 | return NULL; | ||
| 189 | } | ||
| 190 | |||
| 191 | SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) cqe->UserData; | ||
| 192 | if (task) { // can be NULL if this was just a wakeup message, a NOP, etc. | ||
| 193 | if (!task->queue) { // We leave `queue` blank to signify this was a task cancellation. | ||
| 194 | SDL_AsyncIOTask *cancel_task = task; | ||
| 195 | task = (SDL_AsyncIOTask *) cancel_task->app_userdata; | ||
| 196 | SDL_free(cancel_task); | ||
| 197 | if (SUCCEEDED(cqe->ResultCode)) { // cancel was successful? | ||
| 198 | task->result = SDL_ASYNCIO_CANCELED; | ||
| 199 | } else { | ||
| 200 | task = NULL; // it already finished or was too far along to cancel, so we'll pick up the actual results later. | ||
| 201 | } | ||
| 202 | } else if (FAILED(cqe->ResultCode)) { | ||
| 203 | task->result = SDL_ASYNCIO_FAILURE; | ||
| 204 | // !!! FIXME: fill in task->error. | ||
| 205 | } else { | ||
| 206 | if ((task->type == SDL_ASYNCIO_TASK_WRITE) && (((Uint64) cqe->Information) < task->requested_size)) { | ||
| 207 | task->result = SDL_ASYNCIO_FAILURE; // it's always a failure on short writes. | ||
| 208 | } | ||
| 209 | |||
| 210 | // don't explicitly mark it as COMPLETE; that's the default value and a linked task might have failed in an earlier operation and this would overwrite it. | ||
| 211 | |||
| 212 | if ((task->type == SDL_ASYNCIO_TASK_READ) || (task->type == SDL_ASYNCIO_TASK_WRITE)) { | ||
| 213 | task->result_size = (Uint64) cqe->Information; | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | // we currently send all close operations through as flushes, requested or not, so the actually closing is (in theory) fast. We do that here. | ||
| 218 | // if a later IoRing interface version offers an asynchronous close operation, revisit this to only flush if requested, like we do in the Linux io_uring code. | ||
| 219 | if (task->type == SDL_ASYNCIO_TASK_CLOSE) { | ||
| 220 | SDL_assert(task->asyncio != NULL); | ||
| 221 | SDL_assert(task->asyncio->userdata != NULL); | ||
| 222 | HANDLE handle = (HANDLE) task->asyncio->userdata; | ||
| 223 | if (!CloseHandle(handle)) { | ||
| 224 | task->result = SDL_ASYNCIO_FAILURE; // shrug. | ||
| 225 | } | ||
| 226 | } | ||
| 227 | } | ||
| 228 | |||
| 229 | return task; | ||
| 230 | } | ||
| 231 | |||
| 232 | static SDL_AsyncIOTask *ioring_asyncioqueue_get_results(void *userdata) | ||
| 233 | { | ||
| 234 | WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata; | ||
| 235 | |||
| 236 | // unlike liburing's io_uring_peek_cqe(), it's possible PopIoRingCompletion() is thread safe, but for now we wrap it in a mutex just in case. | ||
| 237 | SDL_LockMutex(queuedata->cqe_lock); | ||
| 238 | IORING_CQE cqe; | ||
| 239 | const HRESULT hr = ioring.PopIoRingCompletion(queuedata->ring, &cqe); | ||
| 240 | SDL_UnlockMutex(queuedata->cqe_lock); | ||
| 241 | |||
| 242 | if ((hr == S_FALSE) || FAILED(hr)) { | ||
| 243 | return NULL; // nothing available at the moment. | ||
| 244 | } | ||
| 245 | |||
| 246 | return ProcessCQE(queuedata, &cqe); | ||
| 247 | } | ||
| 248 | |||
| 249 | static SDL_AsyncIOTask *ioring_asyncioqueue_wait_results(void *userdata, Sint32 timeoutMS) | ||
| 250 | { | ||
| 251 | WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata; | ||
| 252 | |||
| 253 | // the event only signals when the IoRing moves from empty to non-empty, so you have to try a (non-blocking) get_results first or risk eternal hangs. | ||
| 254 | SDL_AsyncIOTask *task = ioring_asyncioqueue_get_results(userdata); | ||
| 255 | if (!task) { | ||
| 256 | SDL_AddAtomicInt(&queuedata->num_waiting, 1); | ||
| 257 | WaitForSingleObject(queuedata->event, (timeoutMS < 0) ? INFINITE : (DWORD) timeoutMS); | ||
| 258 | SDL_AddAtomicInt(&queuedata->num_waiting, -1); | ||
| 259 | |||
| 260 | // (we don't care if the wait failed for any reason, as the upcoming get_results will report valid information. We just wanted the wait operation to block.) | ||
| 261 | task = ioring_asyncioqueue_get_results(userdata); | ||
| 262 | } | ||
| 263 | |||
| 264 | return task; | ||
| 265 | } | ||
| 266 | |||
| 267 | static void ioring_asyncioqueue_signal(void *userdata) | ||
| 268 | { | ||
| 269 | WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata; | ||
| 270 | const int num_waiting = SDL_GetAtomicInt(&queuedata->num_waiting); | ||
| 271 | for (int i = 0; i < num_waiting; i++) { | ||
| 272 | SetEvent(queuedata->event); | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | static void ioring_asyncioqueue_destroy(void *userdata) | ||
| 277 | { | ||
| 278 | WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata; | ||
| 279 | ioring.CloseIoRing(queuedata->ring); | ||
| 280 | CloseHandle(queuedata->event); | ||
| 281 | SDL_DestroyMutex(queuedata->sqe_lock); | ||
| 282 | SDL_DestroyMutex(queuedata->cqe_lock); | ||
| 283 | SDL_free(queuedata); | ||
| 284 | } | ||
| 285 | |||
| 286 | static bool SDL_SYS_CreateAsyncIOQueue_ioring(SDL_AsyncIOQueue *queue) | ||
| 287 | { | ||
| 288 | WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) SDL_calloc(1, sizeof (*queuedata)); | ||
| 289 | if (!queuedata) { | ||
| 290 | return false; | ||
| 291 | } | ||
| 292 | |||
| 293 | HRESULT hr; | ||
| 294 | IORING_CREATE_FLAGS flags; | ||
| 295 | |||
| 296 | SDL_SetAtomicInt(&queuedata->num_waiting, 0); | ||
| 297 | |||
| 298 | queuedata->sqe_lock = SDL_CreateMutex(); | ||
| 299 | if (!queuedata->sqe_lock) { | ||
| 300 | goto failed; | ||
| 301 | } | ||
| 302 | |||
| 303 | queuedata->cqe_lock = SDL_CreateMutex(); | ||
| 304 | if (!queuedata->cqe_lock) { | ||
| 305 | goto failed; | ||
| 306 | } | ||
| 307 | |||
| 308 | queuedata->event = CreateEventW(NULL, FALSE, FALSE, NULL); | ||
| 309 | if (!queuedata->event) { | ||
| 310 | WIN_SetError("CreateEventW"); | ||
| 311 | goto failed; | ||
| 312 | } | ||
| 313 | |||
| 314 | // !!! FIXME: no idea how large the queue should be. Is 128 overkill or too small? | ||
| 315 | flags.Required = IORING_CREATE_REQUIRED_FLAGS_NONE; | ||
| 316 | flags.Advisory = IORING_CREATE_ADVISORY_FLAGS_NONE; | ||
| 317 | hr = ioring.CreateIoRing(SDL_REQUIRED_IORING_VERSION, flags, 128, 128, &queuedata->ring); | ||
| 318 | if (FAILED(hr)) { | ||
| 319 | WIN_SetErrorFromHRESULT("CreateIoRing", hr); | ||
| 320 | goto failed; | ||
| 321 | } | ||
| 322 | |||
| 323 | hr = ioring.SetIoRingCompletionEvent(queuedata->ring, queuedata->event); | ||
| 324 | if (FAILED(hr)) { | ||
| 325 | WIN_SetErrorFromHRESULT("SetIoRingCompletionEvent", hr); | ||
| 326 | goto failed; | ||
| 327 | } | ||
| 328 | |||
| 329 | static const IORING_OP_CODE needed_ops[] = { | ||
| 330 | IORING_OP_NOP, | ||
| 331 | IORING_OP_FLUSH, | ||
| 332 | IORING_OP_READ, | ||
| 333 | IORING_OP_WRITE, | ||
| 334 | IORING_OP_CANCEL | ||
| 335 | }; | ||
| 336 | |||
| 337 | for (int i = 0; i < SDL_arraysize(needed_ops); i++) { | ||
| 338 | if (!ioring.IsIoRingOpSupported(queuedata->ring, needed_ops[i])) { | ||
| 339 | SDL_SetError("Created IoRing doesn't support op %u", (unsigned int) needed_ops[i]); | ||
| 340 | goto failed; | ||
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | static const SDL_AsyncIOQueueInterface SDL_AsyncIOQueue_ioring = { | ||
| 345 | ioring_asyncioqueue_queue_task, | ||
| 346 | ioring_asyncioqueue_cancel_task, | ||
| 347 | ioring_asyncioqueue_get_results, | ||
| 348 | ioring_asyncioqueue_wait_results, | ||
| 349 | ioring_asyncioqueue_signal, | ||
| 350 | ioring_asyncioqueue_destroy | ||
| 351 | }; | ||
| 352 | |||
| 353 | SDL_copyp(&queue->iface, &SDL_AsyncIOQueue_ioring); | ||
| 354 | queue->userdata = queuedata; | ||
| 355 | return true; | ||
| 356 | |||
| 357 | failed: | ||
| 358 | if (queuedata->ring) { | ||
| 359 | ioring.CloseIoRing(queuedata->ring); | ||
| 360 | } | ||
| 361 | if (queuedata->event) { | ||
| 362 | CloseHandle(queuedata->event); | ||
| 363 | } | ||
| 364 | if (queuedata->sqe_lock) { | ||
| 365 | SDL_DestroyMutex(queuedata->sqe_lock); | ||
| 366 | } | ||
| 367 | if (queuedata->cqe_lock) { | ||
| 368 | SDL_DestroyMutex(queuedata->cqe_lock); | ||
| 369 | } | ||
| 370 | SDL_free(queuedata); | ||
| 371 | return false; | ||
| 372 | } | ||
| 373 | |||
| 374 | static bool ioring_asyncio_read(void *userdata, SDL_AsyncIOTask *task) | ||
| 375 | { | ||
| 376 | // !!! FIXME: UINT32 smaller than requested_size's Uint64. If we overflow it, we could try submitting multiple SQEs | ||
| 377 | // !!! FIXME: and make a note in the task that there are several in sequence. | ||
| 378 | if (task->requested_size > 0xFFFFFFFF) { | ||
| 379 | return SDL_SetError("ioring: i/o task is too large"); | ||
| 380 | } | ||
| 381 | |||
| 382 | HANDLE handle = (HANDLE) userdata; | ||
| 383 | WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) task->queue->userdata; | ||
| 384 | IORING_HANDLE_REF href = IoRingHandleRefFromHandle(handle); | ||
| 385 | IORING_BUFFER_REF bref = IoRingBufferRefFromPointer(task->buffer); | ||
| 386 | |||
| 387 | // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up. | ||
| 388 | SDL_LockMutex(queuedata->sqe_lock); | ||
| 389 | bool retval; | ||
| 390 | const HRESULT hr = ioring.BuildIoRingReadFile(queuedata->ring, href, bref, (UINT32) task->requested_size, task->offset, (UINT_PTR) task, IOSQE_FLAGS_NONE); | ||
| 391 | if (FAILED(hr)) { | ||
| 392 | retval = WIN_SetErrorFromHRESULT("BuildIoRingReadFile", hr); | ||
| 393 | } else { | ||
| 394 | retval = task->queue->iface.queue_task(task->queue->userdata, task); | ||
| 395 | } | ||
| 396 | SDL_UnlockMutex(queuedata->sqe_lock); | ||
| 397 | return retval; | ||
| 398 | } | ||
| 399 | |||
| 400 | static bool ioring_asyncio_write(void *userdata, SDL_AsyncIOTask *task) | ||
| 401 | { | ||
| 402 | // !!! FIXME: UINT32 smaller than requested_size's Uint64. If we overflow it, we could try submitting multiple SQEs | ||
| 403 | // !!! FIXME: and make a note in the task that there are several in sequence. | ||
| 404 | if (task->requested_size > 0xFFFFFFFF) { | ||
| 405 | return SDL_SetError("ioring: i/o task is too large"); | ||
| 406 | } | ||
| 407 | |||
| 408 | HANDLE handle = (HANDLE) userdata; | ||
| 409 | WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) task->queue->userdata; | ||
| 410 | IORING_HANDLE_REF href = IoRingHandleRefFromHandle(handle); | ||
| 411 | IORING_BUFFER_REF bref = IoRingBufferRefFromPointer(task->buffer); | ||
| 412 | |||
| 413 | // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up. | ||
| 414 | SDL_LockMutex(queuedata->sqe_lock); | ||
| 415 | bool retval; | ||
| 416 | const HRESULT hr = ioring.BuildIoRingWriteFile(queuedata->ring, href, bref, (UINT32) task->requested_size, task->offset, 0 /*FILE_WRITE_FLAGS_NONE*/, (UINT_PTR) task, IOSQE_FLAGS_NONE); | ||
| 417 | if (FAILED(hr)) { | ||
| 418 | retval = WIN_SetErrorFromHRESULT("BuildIoRingWriteFile", hr); | ||
| 419 | } else { | ||
| 420 | retval = task->queue->iface.queue_task(task->queue->userdata, task); | ||
| 421 | } | ||
| 422 | SDL_UnlockMutex(queuedata->sqe_lock); | ||
| 423 | return retval; | ||
| 424 | } | ||
| 425 | |||
| 426 | static bool ioring_asyncio_close(void *userdata, SDL_AsyncIOTask *task) | ||
| 427 | { | ||
| 428 | // current IoRing operations don't offer asynchronous closing, but let's assume most of the potential work is flushing to disk, so just do it for everything, explicit flush or not. We'll close when it finishes. | ||
| 429 | HANDLE handle = (HANDLE) userdata; | ||
| 430 | WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *)task->queue->userdata; | ||
| 431 | IORING_HANDLE_REF href = IoRingHandleRefFromHandle(handle); | ||
| 432 | |||
| 433 | // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up. | ||
| 434 | SDL_LockMutex(queuedata->sqe_lock); | ||
| 435 | bool retval; | ||
| 436 | const HRESULT hr = ioring.BuildIoRingFlushFile(queuedata->ring, href, FILE_FLUSH_DEFAULT, (UINT_PTR) task, IOSQE_FLAGS_NONE); | ||
| 437 | if (FAILED(hr)) { | ||
| 438 | retval = WIN_SetErrorFromHRESULT("BuildIoRingFlushFile", hr); | ||
| 439 | } else { | ||
| 440 | retval = task->queue->iface.queue_task(task->queue->userdata, task); | ||
| 441 | } | ||
| 442 | SDL_UnlockMutex(queuedata->sqe_lock); | ||
| 443 | return retval; | ||
| 444 | } | ||
| 445 | |||
| 446 | static void ioring_asyncio_destroy(void *userdata) | ||
| 447 | { | ||
| 448 | // this is only a Win32 file HANDLE, should have been closed elsewhere. | ||
| 449 | } | ||
| 450 | |||
| 451 | static bool Win32OpenModeFromString(const char *mode, DWORD *access_mode, DWORD *create_mode) | ||
| 452 | { | ||
| 453 | // this is exactly the set of strings that SDL_AsyncIOFromFile promises will work. | ||
| 454 | static const struct { const char *str; DWORD amode; WORD cmode; } mappings[] = { | ||
| 455 | { "rb", GENERIC_READ, OPEN_EXISTING }, | ||
| 456 | { "wb", GENERIC_WRITE, CREATE_ALWAYS }, | ||
| 457 | { "r+b", GENERIC_READ | GENERIC_WRITE, OPEN_EXISTING }, | ||
| 458 | { "w+b", GENERIC_READ | GENERIC_WRITE, CREATE_ALWAYS } | ||
| 459 | }; | ||
| 460 | |||
| 461 | for (int i = 0; i < SDL_arraysize(mappings); i++) { | ||
| 462 | if (SDL_strcmp(mappings[i].str, mode) == 0) { | ||
| 463 | *access_mode = mappings[i].amode; | ||
| 464 | *create_mode = mappings[i].cmode; | ||
| 465 | return true; | ||
| 466 | } | ||
| 467 | } | ||
| 468 | |||
| 469 | SDL_assert(!"Shouldn't have reached this code"); | ||
| 470 | return SDL_SetError("Invalid file open mode"); | ||
| 471 | } | ||
| 472 | |||
| 473 | static bool SDL_SYS_AsyncIOFromFile_ioring(const char *file, const char *mode, SDL_AsyncIO *asyncio) | ||
| 474 | { | ||
| 475 | DWORD access_mode, create_mode; | ||
| 476 | if (!Win32OpenModeFromString(mode, &access_mode, &create_mode)) { | ||
| 477 | return false; | ||
| 478 | } | ||
| 479 | |||
| 480 | LPWSTR wstr = WIN_UTF8ToStringW(file); | ||
| 481 | if (!wstr) { | ||
| 482 | return false; | ||
| 483 | } | ||
| 484 | |||
| 485 | HANDLE handle = CreateFileW(wstr, access_mode, FILE_SHARE_READ, NULL, create_mode, FILE_ATTRIBUTE_NORMAL, NULL); | ||
| 486 | SDL_free(wstr); | ||
| 487 | if (!handle) { | ||
| 488 | return WIN_SetError("CreateFileW"); | ||
| 489 | } | ||
| 490 | |||
| 491 | static const SDL_AsyncIOInterface SDL_AsyncIOFile_ioring = { | ||
| 492 | ioring_asyncio_size, | ||
| 493 | ioring_asyncio_read, | ||
| 494 | ioring_asyncio_write, | ||
| 495 | ioring_asyncio_close, | ||
| 496 | ioring_asyncio_destroy | ||
| 497 | }; | ||
| 498 | |||
| 499 | SDL_copyp(&asyncio->iface, &SDL_AsyncIOFile_ioring); | ||
| 500 | |||
| 501 | asyncio->userdata = (void *) handle; | ||
| 502 | return true; | ||
| 503 | } | ||
| 504 | |||
| 505 | static void SDL_SYS_QuitAsyncIO_ioring(void) | ||
| 506 | { | ||
| 507 | UnloadWinIoRingLibrary(); | ||
| 508 | } | ||
| 509 | |||
| 510 | static void MaybeInitializeWinIoRing(void) | ||
| 511 | { | ||
| 512 | if (SDL_ShouldInit(&ioring_init)) { | ||
| 513 | if (LoadWinIoRing()) { | ||
| 514 | CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_ioring; | ||
| 515 | QuitAsyncIO = SDL_SYS_QuitAsyncIO_ioring; | ||
| 516 | AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_ioring; | ||
| 517 | } else { // can't use ioring? Use the "generic" threadpool implementation instead. | ||
| 518 | CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_Generic; | ||
| 519 | QuitAsyncIO = SDL_SYS_QuitAsyncIO_Generic; | ||
| 520 | AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_Generic; | ||
| 521 | } | ||
| 522 | SDL_SetInitialized(&ioring_init, true); | ||
| 523 | } | ||
| 524 | } | ||
| 525 | |||
| 526 | bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue) | ||
| 527 | { | ||
| 528 | MaybeInitializeWinIoRing(); | ||
| 529 | return CreateAsyncIOQueue(queue); | ||
| 530 | } | ||
| 531 | |||
| 532 | bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio) | ||
| 533 | { | ||
| 534 | MaybeInitializeWinIoRing(); | ||
| 535 | return AsyncIOFromFile(file, mode, asyncio); | ||
| 536 | } | ||
| 537 | |||
| 538 | void SDL_SYS_QuitAsyncIO(void) | ||
| 539 | { | ||
| 540 | if (SDL_ShouldQuit(&ioring_init)) { | ||
| 541 | QuitAsyncIO(); | ||
| 542 | CreateAsyncIOQueue = NULL; | ||
| 543 | QuitAsyncIO = NULL; | ||
| 544 | AsyncIOFromFile = NULL; | ||
| 545 | SDL_SetInitialized(&ioring_init, false); | ||
| 546 | } | ||
| 547 | } | ||
| 548 | |||
| 549 | #endif // defined HAVE_IORINGAPI_H | ||
| 550 | |||
