Atlas - SDL_asyncio.c

Home / ext / SDL / src / io Lines: 1 | Size: 10761 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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 26static 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 43SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode) 44{ 45 CHECK_PARAM(!file) { 46 SDL_InvalidParamError("file"); 47 return NULL; 48 } 49 CHECK_PARAM(!mode) { 50 SDL_InvalidParamError("mode"); 51 return NULL; 52 } 53 54 const char *binary_mode = AsyncFileModeValid(mode); 55 if (!binary_mode) { 56 SDL_SetError("Unsupported file mode"); 57 return NULL; 58 } 59 60 SDL_AsyncIO *asyncio = (SDL_AsyncIO *)SDL_calloc(1, sizeof(*asyncio)); 61 if (!asyncio) { 62 return NULL; 63 } 64 65 asyncio->lock = SDL_CreateMutex(); 66 if (!asyncio->lock) { 67 SDL_free(asyncio); 68 return NULL; 69 } 70 71 if (!SDL_SYS_AsyncIOFromFile(file, binary_mode, asyncio)) { 72 SDL_DestroyMutex(asyncio->lock); 73 SDL_free(asyncio); 74 return NULL; 75 } 76 77 return asyncio; 78} 79 80Sint64 SDL_GetAsyncIOSize(SDL_AsyncIO *asyncio) 81{ 82 CHECK_PARAM(!asyncio) { 83 SDL_InvalidParamError("asyncio"); 84 return -1; 85 } 86 return asyncio->iface.size(asyncio->userdata); 87} 88 89static bool RequestAsyncIO(bool reading, SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata) 90{ 91 CHECK_PARAM(!asyncio) { 92 return SDL_InvalidParamError("asyncio"); 93 } 94 CHECK_PARAM(!ptr) { 95 return SDL_InvalidParamError("ptr"); 96 } 97 CHECK_PARAM(!queue) { 98 return SDL_InvalidParamError("queue"); 99 } 100 101 SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*task)); 102 if (!task) { 103 return false; 104 } 105 106 task->asyncio = asyncio; 107 task->type = reading ? SDL_ASYNCIO_TASK_READ : SDL_ASYNCIO_TASK_WRITE; 108 task->offset = offset; 109 task->buffer = ptr; 110 task->requested_size = size; 111 task->app_userdata = userdata; 112 task->queue = queue; 113 114 SDL_LockMutex(asyncio->lock); 115 if (asyncio->closing) { 116 SDL_free(task); 117 SDL_UnlockMutex(asyncio->lock); 118 return SDL_SetError("SDL_AsyncIO is closing, can't start new tasks"); 119 } 120 LINKED_LIST_PREPEND(task, asyncio->tasks, asyncio); 121 SDL_AddAtomicInt(&queue->tasks_inflight, 1); 122 SDL_UnlockMutex(asyncio->lock); 123 124 const bool queued = reading ? asyncio->iface.read(asyncio->userdata, task) : asyncio->iface.write(asyncio->userdata, task); 125 if (!queued) { 126 SDL_AddAtomicInt(&queue->tasks_inflight, -1); 127 SDL_LockMutex(asyncio->lock); 128 LINKED_LIST_UNLINK(task, asyncio); 129 SDL_UnlockMutex(asyncio->lock); 130 SDL_free(task); 131 task = NULL; 132 } 133 134 return (task != NULL); 135} 136 137bool SDL_ReadAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata) 138{ 139 return RequestAsyncIO(true, asyncio, ptr, offset, size, queue, userdata); 140} 141 142bool SDL_WriteAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata) 143{ 144 return RequestAsyncIO(false, asyncio, ptr, offset, size, queue, userdata); 145} 146 147bool SDL_CloseAsyncIO(SDL_AsyncIO *asyncio, bool flush, SDL_AsyncIOQueue *queue, void *userdata) 148{ 149 CHECK_PARAM(!asyncio) { 150 return SDL_InvalidParamError("asyncio"); 151 } 152 CHECK_PARAM(!queue) { 153 return SDL_InvalidParamError("queue"); 154 } 155 156 SDL_LockMutex(asyncio->lock); 157 if (asyncio->closing) { 158 SDL_UnlockMutex(asyncio->lock); 159 return SDL_SetError("Already closing"); 160 } 161 162 SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*task)); 163 if (task) { 164 task->asyncio = asyncio; 165 task->type = SDL_ASYNCIO_TASK_CLOSE; 166 task->app_userdata = userdata; 167 task->queue = queue; 168 task->flush = flush; 169 170 asyncio->closing = task; 171 172 if (LINKED_LIST_START(asyncio->tasks, asyncio) == NULL) { // no tasks? Queue the close task now. 173 LINKED_LIST_PREPEND(task, asyncio->tasks, asyncio); 174 SDL_AddAtomicInt(&queue->tasks_inflight, 1); 175 if (!asyncio->iface.close(asyncio->userdata, task)) { 176 // uhoh, maybe they can try again later...? 177 SDL_AddAtomicInt(&queue->tasks_inflight, -1); 178 LINKED_LIST_UNLINK(task, asyncio); 179 SDL_free(task); 180 task = asyncio->closing = NULL; 181 } 182 } 183 } 184 185 SDL_UnlockMutex(asyncio->lock); 186 187 return (task != NULL); 188} 189 190SDL_AsyncIOQueue *SDL_CreateAsyncIOQueue(void) 191{ 192 SDL_AsyncIOQueue *queue = SDL_calloc(1, sizeof (*queue)); 193 if (queue) { 194 SDL_SetAtomicInt(&queue->tasks_inflight, 0); 195 if (!SDL_SYS_CreateAsyncIOQueue(queue)) { 196 SDL_free(queue); 197 return NULL; 198 } 199 } 200 return queue; 201} 202 203static bool GetAsyncIOTaskOutcome(SDL_AsyncIOTask *task, SDL_AsyncIOOutcome *outcome) 204{ 205 if (!task || !outcome) { 206 return false; 207 } 208 209 SDL_AsyncIO *asyncio = task->asyncio; 210 211 SDL_zerop(outcome); 212 outcome->asyncio = asyncio->oneshot ? NULL : asyncio; 213 outcome->result = task->result; 214 outcome->type = task->type; 215 outcome->buffer = task->buffer; 216 outcome->offset = task->offset; 217 outcome->bytes_requested = task->requested_size; 218 outcome->bytes_transferred = task->result_size; 219 outcome->userdata = task->app_userdata; 220 221 // Take the completed task out of the SDL_AsyncIO that created it. 222 SDL_Mutex *lock = asyncio->lock; 223 SDL_LockMutex(lock); 224 LINKED_LIST_UNLINK(task, asyncio); 225 // see if it's time to queue a pending close request (close requested and no other pending tasks) 226 SDL_AsyncIOTask *closing = asyncio->closing; 227 if (closing && (task != closing) && (LINKED_LIST_START(asyncio->tasks, asyncio) == NULL)) { 228 LINKED_LIST_PREPEND(closing, asyncio->tasks, asyncio); 229 SDL_AddAtomicInt(&closing->queue->tasks_inflight, 1); 230 const bool async_close_task_was_queued = asyncio->iface.close(asyncio->userdata, closing); 231 SDL_assert(async_close_task_was_queued); // !!! FIXME: if this fails to queue the task, we're leaking resources! 232 if (!async_close_task_was_queued) { 233 SDL_AddAtomicInt(&closing->queue->tasks_inflight, -1); 234 } 235 } 236 SDL_UnlockMutex(lock); 237 238 // was this the result of a closing task? Finally destroy the asyncio. 239 bool retval = true; 240 if (closing && (task == closing)) { 241 if (asyncio->oneshot) { 242 retval = false; // don't send the close task results on to the app, just the read task for these. 243 } 244 asyncio->iface.destroy(asyncio->userdata); 245 SDL_DestroyMutex(asyncio->lock); 246 SDL_free(asyncio); 247 } 248 249 SDL_AddAtomicInt(&task->queue->tasks_inflight, -1); 250 SDL_free(task); 251 252 return retval; 253} 254 255bool SDL_GetAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome) 256{ 257 if (!queue || !outcome) { 258 return false; 259 } 260 return GetAsyncIOTaskOutcome(queue->iface.get_results(queue->userdata), outcome); 261} 262 263bool SDL_WaitAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome, Sint32 timeoutMS) 264{ 265 if (!queue || !outcome) { 266 return false; 267 } 268 return GetAsyncIOTaskOutcome(queue->iface.wait_results(queue->userdata, timeoutMS), outcome); 269} 270 271void SDL_SignalAsyncIOQueue(SDL_AsyncIOQueue *queue) 272{ 273 if (queue) { 274 queue->iface.signal(queue->userdata); 275 } 276} 277 278void SDL_DestroyAsyncIOQueue(SDL_AsyncIOQueue *queue) 279{ 280 if (queue) { 281 // block until any pending tasks complete. 282 while (SDL_GetAtomicInt(&queue->tasks_inflight) > 0) { 283 SDL_AsyncIOTask *task = queue->iface.wait_results(queue->userdata, -1); 284 if (task) { 285 if (task->asyncio->oneshot) { 286 SDL_free(task->buffer); // throw away the buffer from SDL_LoadFileAsync that will never be consumed/freed by app. 287 task->buffer = NULL; 288 } 289 SDL_AsyncIOOutcome outcome; 290 GetAsyncIOTaskOutcome(task, &outcome); // this frees the task, and does other upkeep. 291 } 292 } 293 294 queue->iface.destroy(queue->userdata); 295 SDL_free(queue); 296 } 297} 298 299void SDL_QuitAsyncIO(void) 300{ 301 SDL_SYS_QuitAsyncIO(); 302} 303 304bool SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata) 305{ 306 CHECK_PARAM(!file) { 307 return SDL_InvalidParamError("file"); 308 } 309 CHECK_PARAM(!queue) { 310 return SDL_InvalidParamError("queue"); 311 } 312 313 bool retval = false; 314 SDL_AsyncIO *asyncio = SDL_AsyncIOFromFile(file, "r"); 315 if (asyncio) { 316 asyncio->oneshot = true; 317 318 Uint8 *ptr = NULL; 319 const Sint64 flen = SDL_GetAsyncIOSize(asyncio); 320 if (flen >= 0) { 321 // !!! FIXME: check if flen > address space, since it'll truncate and we'll just end up with an incomplete buffer or a crash. 322 ptr = (Uint8 *) SDL_malloc((size_t) (flen + 1)); // over-allocate by one so we can add a null-terminator. 323 if (ptr) { 324 ptr[flen] = '\0'; 325 retval = SDL_ReadAsyncIO(asyncio, ptr, 0, (Uint64) flen, queue, userdata); 326 if (!retval) { 327 SDL_free(ptr); 328 } 329 } 330 } 331 332 SDL_CloseAsyncIO(asyncio, false, queue, userdata); // if this fails, we'll have a resource leak, but this would already be a dramatic system failure. 333 } 334 335 return retval; 336} 337 338
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.