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