mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-04-02 07:06:04 +02:00
asyncio: Added async i/o APIs.
This commit is contained in:
@@ -55,6 +55,7 @@
|
||||
#include "video/SDL_surface_c.h"
|
||||
#include "video/SDL_video_c.h"
|
||||
#include "filesystem/SDL_filesystem_c.h"
|
||||
#include "file/SDL_asyncio_c.h"
|
||||
#ifdef SDL_PLATFORM_ANDROID
|
||||
#include "core/android/SDL_android.h"
|
||||
#endif
|
||||
@@ -625,6 +626,7 @@ void SDL_Quit(void)
|
||||
#endif
|
||||
|
||||
SDL_QuitTimers();
|
||||
SDL_QuitAsyncIO();
|
||||
|
||||
SDL_SetObjectsInvalid();
|
||||
SDL_AssertionsQuit();
|
||||
|
||||
@@ -1189,6 +1189,17 @@ SDL3_0.0.0 {
|
||||
SDL_GetCurrentDirectory;
|
||||
SDL_IsAudioDevicePhysical;
|
||||
SDL_IsAudioDevicePlayback;
|
||||
SDL_AsyncIOFromFile;
|
||||
SDL_GetAsyncIOSize;
|
||||
SDL_ReadAsyncIO;
|
||||
SDL_WriteAsyncIO;
|
||||
SDL_CloseAsyncIO;
|
||||
SDL_CreateAsyncIOQueue;
|
||||
SDL_DestroyAsyncIOQueue;
|
||||
SDL_GetAsyncIOResult;
|
||||
SDL_WaitAsyncIOResult;
|
||||
SDL_SignalAsyncIOQueue;
|
||||
SDL_LoadFileAsync;
|
||||
# extra symbols go here (don't modify this line)
|
||||
local: *;
|
||||
};
|
||||
|
||||
@@ -1214,3 +1214,14 @@
|
||||
#define SDL_GetCurrentDirectory SDL_GetCurrentDirectory_REAL
|
||||
#define SDL_IsAudioDevicePhysical SDL_IsAudioDevicePhysical_REAL
|
||||
#define SDL_IsAudioDevicePlayback SDL_IsAudioDevicePlayback_REAL
|
||||
#define SDL_AsyncIOFromFile SDL_AsyncIOFromFile_REAL
|
||||
#define SDL_GetAsyncIOSize SDL_GetAsyncIOSize_REAL
|
||||
#define SDL_ReadAsyncIO SDL_ReadAsyncIO_REAL
|
||||
#define SDL_WriteAsyncIO SDL_WriteAsyncIO_REAL
|
||||
#define SDL_CloseAsyncIO SDL_CloseAsyncIO_REAL
|
||||
#define SDL_CreateAsyncIOQueue SDL_CreateAsyncIOQueue_REAL
|
||||
#define SDL_DestroyAsyncIOQueue SDL_DestroyAsyncIOQueue_REAL
|
||||
#define SDL_GetAsyncIOResult SDL_GetAsyncIOResult_REAL
|
||||
#define SDL_WaitAsyncIOResult SDL_WaitAsyncIOResult_REAL
|
||||
#define SDL_SignalAsyncIOQueue SDL_SignalAsyncIOQueue_REAL
|
||||
#define SDL_LoadFileAsync SDL_LoadFileAsync_REAL
|
||||
|
||||
@@ -1220,3 +1220,14 @@ SDL_DYNAPI_PROC(bool,SDL_SaveFile,(const char *a,const void *b,size_t c),(a,b,c)
|
||||
SDL_DYNAPI_PROC(char*,SDL_GetCurrentDirectory,(void),(),return)
|
||||
SDL_DYNAPI_PROC(bool,SDL_IsAudioDevicePhysical,(SDL_AudioDeviceID a),(a),return)
|
||||
SDL_DYNAPI_PROC(bool,SDL_IsAudioDevicePlayback,(SDL_AudioDeviceID a),(a),return)
|
||||
SDL_DYNAPI_PROC(SDL_AsyncIO*,SDL_AsyncIOFromFile,(const char *a, const char *b),(a,b),return)
|
||||
SDL_DYNAPI_PROC(Sint64,SDL_GetAsyncIOSize,(SDL_AsyncIO *a),(a),return)
|
||||
SDL_DYNAPI_PROC(SDL_AsyncIOTask*,SDL_ReadAsyncIO,(SDL_AsyncIO *a, void *b, Uint64 c, Uint64 d, SDL_AsyncIOQueue *e, void *f),(a,b,c,d,e,f),return)
|
||||
SDL_DYNAPI_PROC(SDL_AsyncIOTask*,SDL_WriteAsyncIO,(SDL_AsyncIO *a, void *b, Uint64 c, Uint64 d, SDL_AsyncIOQueue *e, void *f),(a,b,c,d,e,f),return)
|
||||
SDL_DYNAPI_PROC(SDL_AsyncIOTask*,SDL_CloseAsyncIO,(SDL_AsyncIO *a, SDL_AsyncIOQueue *b, void *c),(a,b,c),return)
|
||||
SDL_DYNAPI_PROC(SDL_AsyncIOQueue*,SDL_CreateAsyncIOQueue,(void),(),return)
|
||||
SDL_DYNAPI_PROC(void,SDL_DestroyAsyncIOQueue,(SDL_AsyncIOQueue *a),(a),)
|
||||
SDL_DYNAPI_PROC(bool,SDL_GetAsyncIOResult,(SDL_AsyncIOQueue *a, SDL_AsyncIOOutcome *b),(a,b),return)
|
||||
SDL_DYNAPI_PROC(bool,SDL_WaitAsyncIOResult,(SDL_AsyncIOQueue *a, SDL_AsyncIOOutcome *b, Sint32 c),(a,b,c),return)
|
||||
SDL_DYNAPI_PROC(void,SDL_SignalAsyncIOQueue,(SDL_AsyncIOQueue *a),(a),)
|
||||
SDL_DYNAPI_PROC(SDL_AsyncIOTask*,SDL_LoadFileAsync,(const char *a, SDL_AsyncIOQueue *b, void *c),(a,b,c),return)
|
||||
|
||||
335
src/file/SDL_asyncio.c
Normal file
335
src/file/SDL_asyncio.c
Normal file
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
#include "SDL_sysasyncio.h"
|
||||
#include "SDL_asyncio_c.h"
|
||||
|
||||
static const char *AsyncFileModeValid(const char *mode)
|
||||
{
|
||||
static const struct { const char *valid; const char *with_binary; } mode_map[] = {
|
||||
{ "r", "rb" },
|
||||
{ "w", "wb" },
|
||||
{ "r+","r+b" },
|
||||
{ "w+", "w+b" }
|
||||
};
|
||||
|
||||
for (int i = 0; i < SDL_arraysize(mode_map); i++) {
|
||||
if (SDL_strcmp(mode, mode_map[i].valid) == 0) {
|
||||
return mode_map[i].with_binary;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode)
|
||||
{
|
||||
if (!file) {
|
||||
SDL_InvalidParamError("file");
|
||||
return NULL;
|
||||
} else if (!mode) {
|
||||
SDL_InvalidParamError("mode");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *binary_mode = AsyncFileModeValid(mode);
|
||||
if (!binary_mode) {
|
||||
SDL_SetError("Unsupported file mode");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_AsyncIO *asyncio = (SDL_AsyncIO *)SDL_calloc(1, sizeof(*asyncio));
|
||||
if (asyncio) {
|
||||
asyncio->lock = SDL_CreateMutex();
|
||||
if (!asyncio->lock) {
|
||||
SDL_free(asyncio);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SDL_SYS_AsyncIOFromFile(file, binary_mode, asyncio)) {
|
||||
SDL_DestroyMutex(asyncio->lock);
|
||||
SDL_free(asyncio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return asyncio;
|
||||
}
|
||||
|
||||
Sint64 SDL_GetAsyncIOSize(SDL_AsyncIO *asyncio)
|
||||
{
|
||||
if (!asyncio) {
|
||||
SDL_InvalidParamError("asyncio");
|
||||
return -1;
|
||||
}
|
||||
return asyncio->iface.size(asyncio->userdata);
|
||||
}
|
||||
|
||||
static SDL_AsyncIOTask *RequestAsyncIO(bool reading, SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata)
|
||||
{
|
||||
if (!asyncio) {
|
||||
SDL_InvalidParamError("asyncio");
|
||||
return NULL;
|
||||
} else if (!ptr) {
|
||||
SDL_InvalidParamError("ptr");
|
||||
return NULL;
|
||||
} else if (!queue) {
|
||||
SDL_InvalidParamError("queue");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*task));
|
||||
if (!task) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
task->asyncio = asyncio;
|
||||
task->type = reading ? SDL_ASYNCIO_TASK_READ : SDL_ASYNCIO_TASK_WRITE;
|
||||
task->offset = offset;
|
||||
task->buffer = ptr;
|
||||
task->requested_size = size;
|
||||
task->app_userdata = userdata;
|
||||
task->queue = queue;
|
||||
|
||||
SDL_LockMutex(asyncio->lock);
|
||||
if (asyncio->closing) {
|
||||
SDL_free(task);
|
||||
SDL_UnlockMutex(asyncio->lock);
|
||||
SDL_SetError("SDL_AsyncIO is closing, can't start new tasks");
|
||||
return NULL;
|
||||
}
|
||||
LINKED_LIST_PREPEND(task, asyncio->tasks, asyncio);
|
||||
SDL_AddAtomicInt(&queue->tasks_inflight, 1);
|
||||
SDL_UnlockMutex(asyncio->lock);
|
||||
|
||||
const bool queued = reading ? asyncio->iface.read(asyncio->userdata, task) : asyncio->iface.write(asyncio->userdata, task);
|
||||
if (!queued) {
|
||||
SDL_AddAtomicInt(&queue->tasks_inflight, -1);
|
||||
SDL_LockMutex(asyncio->lock);
|
||||
LINKED_LIST_UNLINK(task, asyncio);
|
||||
SDL_UnlockMutex(asyncio->lock);
|
||||
SDL_free(task);
|
||||
task = NULL;
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
SDL_AsyncIOTask *SDL_ReadAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata)
|
||||
{
|
||||
return RequestAsyncIO(true, asyncio, ptr, offset, size, queue, userdata);
|
||||
}
|
||||
|
||||
SDL_AsyncIOTask *SDL_WriteAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata)
|
||||
{
|
||||
return RequestAsyncIO(false, asyncio, ptr, offset, size, queue, userdata);
|
||||
}
|
||||
|
||||
SDL_AsyncIOTask *SDL_CloseAsyncIO(SDL_AsyncIO *asyncio, SDL_AsyncIOQueue *queue, void *userdata)
|
||||
{
|
||||
if (!asyncio) {
|
||||
SDL_InvalidParamError("asyncio");
|
||||
return NULL;
|
||||
} else if (!queue) {
|
||||
SDL_InvalidParamError("queue");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_LockMutex(asyncio->lock);
|
||||
if (asyncio->closing) {
|
||||
SDL_UnlockMutex(asyncio->lock);
|
||||
SDL_SetError("Already closing");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*task));
|
||||
if (task) {
|
||||
task->asyncio = asyncio;
|
||||
task->type = SDL_ASYNCIO_TASK_CLOSE;
|
||||
task->app_userdata = userdata;
|
||||
task->queue = queue;
|
||||
|
||||
asyncio->closing = task;
|
||||
|
||||
if (LINKED_LIST_START(asyncio->tasks, asyncio) == NULL) { // no tasks? Queue the close task now.
|
||||
LINKED_LIST_PREPEND(task, asyncio->tasks, asyncio);
|
||||
SDL_AddAtomicInt(&queue->tasks_inflight, 1);
|
||||
if (!asyncio->iface.close(asyncio->userdata, task)) {
|
||||
// uhoh, maybe they can try again later...?
|
||||
SDL_AddAtomicInt(&queue->tasks_inflight, -1);
|
||||
LINKED_LIST_UNLINK(task, asyncio);
|
||||
SDL_free(task);
|
||||
task = asyncio->closing = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(asyncio->lock);
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
SDL_AsyncIOQueue *SDL_CreateAsyncIOQueue(void)
|
||||
{
|
||||
SDL_AsyncIOQueue *queue = SDL_calloc(1, sizeof (*queue));
|
||||
if (queue) {
|
||||
SDL_SetAtomicInt(&queue->tasks_inflight, 0);
|
||||
if (!SDL_SYS_CreateAsyncIOQueue(queue)) {
|
||||
SDL_free(queue);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return queue;
|
||||
}
|
||||
|
||||
static bool GetAsyncIOTaskOutcome(SDL_AsyncIOTask *task, SDL_AsyncIOOutcome *outcome)
|
||||
{
|
||||
if (!task || !outcome) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_AsyncIO *asyncio = task->asyncio;
|
||||
|
||||
SDL_zerop(outcome);
|
||||
outcome->asyncio = asyncio->oneshot ? NULL : asyncio;
|
||||
outcome->result = task->result;
|
||||
outcome->buffer = task->buffer;
|
||||
outcome->offset = task->offset;
|
||||
outcome->bytes_requested = task->requested_size;
|
||||
outcome->bytes_transferred = task->result_size;
|
||||
outcome->userdata = task->app_userdata;
|
||||
|
||||
// Take the completed task out of the SDL_AsyncIO that created it.
|
||||
SDL_LockMutex(asyncio->lock);
|
||||
LINKED_LIST_UNLINK(task, asyncio);
|
||||
// see if it's time to queue a pending close request (close requested and no other pending tasks)
|
||||
SDL_AsyncIOTask *closing = asyncio->closing;
|
||||
if (closing && (task != closing) && (LINKED_LIST_START(asyncio->tasks, asyncio) == NULL)) {
|
||||
LINKED_LIST_PREPEND(closing, asyncio->tasks, asyncio);
|
||||
SDL_AddAtomicInt(&closing->queue->tasks_inflight, 1);
|
||||
const bool async_close_task_was_queued = asyncio->iface.close(asyncio->userdata, closing);
|
||||
SDL_assert(async_close_task_was_queued); // !!! FIXME: if this fails to queue the task, we're leaking resources!
|
||||
if (!async_close_task_was_queued) {
|
||||
SDL_AddAtomicInt(&closing->queue->tasks_inflight, -1);
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(task->asyncio->lock);
|
||||
|
||||
// was this the result of a closing task? Finally destroy the asyncio.
|
||||
bool retval = true;
|
||||
if (closing && (task == closing)) {
|
||||
if (asyncio->oneshot) {
|
||||
retval = false; // don't send the close task results on to the app, just the read task for these.
|
||||
}
|
||||
asyncio->iface.destroy(asyncio->userdata);
|
||||
SDL_DestroyMutex(asyncio->lock);
|
||||
SDL_free(asyncio);
|
||||
}
|
||||
|
||||
SDL_AddAtomicInt(&task->queue->tasks_inflight, -1);
|
||||
SDL_free(task);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool SDL_GetAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome)
|
||||
{
|
||||
if (!queue || !outcome) {
|
||||
return false;
|
||||
}
|
||||
return GetAsyncIOTaskOutcome(queue->iface.get_results(queue->userdata), outcome);
|
||||
}
|
||||
|
||||
bool SDL_WaitAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome, Sint32 timeoutMS)
|
||||
{
|
||||
if (!queue || !outcome) {
|
||||
return false;
|
||||
}
|
||||
return GetAsyncIOTaskOutcome(queue->iface.wait_results(queue->userdata, timeoutMS), outcome);
|
||||
}
|
||||
|
||||
void SDL_SignalAsyncIOQueue(SDL_AsyncIOQueue *queue)
|
||||
{
|
||||
if (queue) {
|
||||
queue->iface.signal(queue->userdata);
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_DestroyAsyncIOQueue(SDL_AsyncIOQueue *queue)
|
||||
{
|
||||
if (queue) {
|
||||
// block until any pending tasks complete.
|
||||
while (SDL_GetAtomicInt(&queue->tasks_inflight) > 0) {
|
||||
SDL_AsyncIOTask *task = queue->iface.wait_results(queue->userdata, -1);
|
||||
if (task) {
|
||||
if (task->asyncio->oneshot) {
|
||||
SDL_free(task->buffer); // throw away the buffer from SDL_LoadFileAsync that will never be consumed/freed by app.
|
||||
task->buffer = NULL;
|
||||
}
|
||||
SDL_AsyncIOOutcome outcome;
|
||||
GetAsyncIOTaskOutcome(task, &outcome); // this frees the task, and does other upkeep.
|
||||
}
|
||||
}
|
||||
|
||||
queue->iface.destroy(queue->userdata);
|
||||
SDL_free(queue);
|
||||
}
|
||||
}
|
||||
|
||||
void SDL_QuitAsyncIO(void)
|
||||
{
|
||||
SDL_SYS_QuitAsyncIO();
|
||||
}
|
||||
|
||||
SDL_AsyncIOTask *SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata)
|
||||
{
|
||||
if (!file) {
|
||||
SDL_InvalidParamError("file");
|
||||
return NULL;
|
||||
} else if (!queue) {
|
||||
SDL_InvalidParamError("queue");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_AsyncIOTask *task = NULL;
|
||||
SDL_AsyncIO *asyncio = SDL_AsyncIOFromFile(file, "r");
|
||||
if (asyncio) {
|
||||
asyncio->oneshot = true;
|
||||
|
||||
void *ptr = NULL;
|
||||
const Sint64 flen = SDL_GetAsyncIOSize(asyncio);
|
||||
if (flen >= 0) {
|
||||
// !!! FIXME: check if flen > address space, since it'll truncate and we'll just end up with an incomplete buffer or a crash.
|
||||
ptr = SDL_malloc((size_t) (flen + 1)); // over-allocate by one so we can add a null-terminator.
|
||||
if (ptr) {
|
||||
task = SDL_ReadAsyncIO(asyncio, ptr, 0, (Uint64) flen, queue, userdata);
|
||||
}
|
||||
}
|
||||
|
||||
if (!task) {
|
||||
SDL_free(ptr);
|
||||
}
|
||||
|
||||
SDL_CloseAsyncIO(asyncio, queue, userdata); // if this fails, we'll have a resource leak, but this would already be a dramatic system failure.
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
30
src/file/SDL_asyncio_c.h
Normal file
30
src/file/SDL_asyncio_c.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
#include "../SDL_internal.h"
|
||||
|
||||
#ifndef SDL_asyncio_c_h_
|
||||
#define SDL_asyncio_c_h_
|
||||
|
||||
// Shutdown any still-existing Async I/O. Note that there is no Init function, as it inits on-demand!
|
||||
extern void SDL_QuitAsyncIO(void);
|
||||
|
||||
#endif // SDL_asyncio_c_h_
|
||||
|
||||
133
src/file/SDL_sysasyncio.h
Normal file
133
src/file/SDL_sysasyncio.h
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
#include "SDL_internal.h"
|
||||
|
||||
#ifndef SDL_sysasyncio_h_
|
||||
#define SDL_sysasyncio_h_
|
||||
|
||||
// If your platform has an option other than the "generic" code, make sure this
|
||||
// is #defined to 0 instead and implement the SDL_SYS_* functions below in your
|
||||
// backend (having them maybe call into the SDL_SYS_*_Generic versions as a
|
||||
// fallback if the platform has functionality that isn't always available).
|
||||
#define SDL_ASYNCIO_ONLY_HAVE_GENERIC 1
|
||||
|
||||
// this entire thing is just juggling doubly-linked lists, so make some helper macros.
|
||||
#define LINKED_LIST_DECLARE_FIELDS(type, prefix) \
|
||||
type *prefix##prev; \
|
||||
type *prefix##next
|
||||
|
||||
#define LINKED_LIST_PREPEND(item, list, prefix) do { \
|
||||
item->prefix##prev = &list; \
|
||||
item->prefix##next = list.prefix##next; \
|
||||
if (item->prefix##next) { \
|
||||
item->prefix##next->prefix##prev = item; \
|
||||
} \
|
||||
list.prefix##next = item; \
|
||||
} while (false)
|
||||
|
||||
#define LINKED_LIST_UNLINK(item, prefix) do { \
|
||||
if (item->prefix##next) { \
|
||||
item->prefix##next->prefix##prev = item->prefix##prev; \
|
||||
} \
|
||||
item->prefix##prev->prefix##next = task->prefix##next; \
|
||||
item->prefix##prev = item->prefix##next = NULL; \
|
||||
} while (false)
|
||||
|
||||
#define LINKED_LIST_START(list, prefix) (list.prefix##next)
|
||||
#define LINKED_LIST_NEXT(item, prefix) (item->prefix##next)
|
||||
#define LINKED_LIST_PREV(item, prefix) (item->prefix##prev)
|
||||
|
||||
typedef struct SDL_AsyncIOTask SDL_AsyncIOTask;
|
||||
|
||||
struct SDL_AsyncIOTask
|
||||
{
|
||||
SDL_AsyncIO *asyncio;
|
||||
SDL_AsyncIOTaskType type;
|
||||
SDL_AsyncIOQueue *queue;
|
||||
Uint64 offset;
|
||||
void *buffer;
|
||||
char *error;
|
||||
SDL_AsyncIOResult result;
|
||||
Uint64 requested_size;
|
||||
Uint64 result_size;
|
||||
void *app_userdata;
|
||||
LINKED_LIST_DECLARE_FIELDS(struct SDL_AsyncIOTask, asyncio);
|
||||
LINKED_LIST_DECLARE_FIELDS(struct SDL_AsyncIOTask, queue); // the generic backend uses this, so I've added it here to avoid the extra allocation.
|
||||
LINKED_LIST_DECLARE_FIELDS(struct SDL_AsyncIOTask, threadpool); // the generic backend uses this, so I've added it here to avoid the extra allocation.
|
||||
};
|
||||
|
||||
typedef struct SDL_AsyncIOQueueInterface
|
||||
{
|
||||
bool (*queue_task)(void *userdata, SDL_AsyncIOTask *task);
|
||||
void (*cancel_task)(void *userdata, SDL_AsyncIOTask *task);
|
||||
SDL_AsyncIOTask * (*get_results)(void *userdata);
|
||||
SDL_AsyncIOTask * (*wait_results)(void *userdata, Sint32 timeoutMS);
|
||||
void (*signal)(void *userdata);
|
||||
void (*destroy)(void *userdata);
|
||||
} SDL_AsyncIOQueueInterface;
|
||||
|
||||
struct SDL_AsyncIOQueue
|
||||
{
|
||||
SDL_AsyncIOQueueInterface iface;
|
||||
void *userdata;
|
||||
SDL_AtomicInt tasks_inflight;
|
||||
};
|
||||
|
||||
// this interface is kept per-object, even though generally it's going to decide
|
||||
// on a single interface that is the same for the entire process, but I've kept
|
||||
// the abstraction in case we start exposing more types of async i/o, like
|
||||
// sockets, in the future.
|
||||
typedef struct SDL_AsyncIOInterface
|
||||
{
|
||||
Sint64 (*size)(void *userdata);
|
||||
bool (*read)(void *userdata, SDL_AsyncIOTask *task);
|
||||
bool (*write)(void *userdata, SDL_AsyncIOTask *task);
|
||||
bool (*close)(void *userdata, SDL_AsyncIOTask *task);
|
||||
void (*destroy)(void *userdata);
|
||||
} SDL_AsyncIOInterface;
|
||||
|
||||
struct SDL_AsyncIO
|
||||
{
|
||||
SDL_AsyncIOInterface iface;
|
||||
void *userdata;
|
||||
SDL_Mutex *lock;
|
||||
SDL_AsyncIOTask tasks;
|
||||
SDL_AsyncIOTask *closing; // The close task, which isn't queued until all pending work for this file is done.
|
||||
bool oneshot; // true if this is a SDL_LoadFileAsync open.
|
||||
};
|
||||
|
||||
// This is implemented for various platforms; param validation is done before calling this. Open file, fill in iface and userdata.
|
||||
extern bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio);
|
||||
|
||||
// This is implemented for various platforms. Call SDL_OpenAsyncIOQueue from in here.
|
||||
extern bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue);
|
||||
|
||||
// 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.
|
||||
extern void SDL_SYS_QuitAsyncIO(void);
|
||||
|
||||
// the "generic" version is always available, since it is almost always needed as a fallback even on platforms that might offer something better.
|
||||
extern bool SDL_SYS_AsyncIOFromFile_Generic(const char *file, const char *mode, SDL_AsyncIO *asyncio);
|
||||
extern bool SDL_SYS_CreateAsyncIOQueue_Generic(SDL_AsyncIOQueue *queue);
|
||||
extern void SDL_SYS_QuitAsyncIO_Generic(void);
|
||||
|
||||
#endif
|
||||
|
||||
460
src/file/generic/SDL_asyncio_generic.c
Normal file
460
src/file/generic/SDL_asyncio_generic.c
Normal file
@@ -0,0 +1,460 @@
|
||||
/*
|
||||
Simple DirectMedia Layer
|
||||
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
// The generic backend uses a threadpool to block on synchronous i/o.
|
||||
// This is not ideal, it's meant to be used if there isn't a platform-specific
|
||||
// backend that can do something more efficient!
|
||||
|
||||
#include "SDL_internal.h"
|
||||
#include "../SDL_sysasyncio.h"
|
||||
|
||||
// on Emscripten without threads, async i/o is synchronous. Sorry. Almost
|
||||
// everything is MEMFS, so it's just a memcpy anyhow, and the Emscripten
|
||||
// filesystem APIs don't offer async. In theory, directly accessing
|
||||
// persistent storage _does_ offer async APIs at the browser level, but
|
||||
// that's not exposed in Emscripten's filesystem abstraction.
|
||||
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
|
||||
#define SDL_ASYNCIO_USE_THREADPOOL 0
|
||||
#else
|
||||
#define SDL_ASYNCIO_USE_THREADPOOL 1
|
||||
#endif
|
||||
|
||||
typedef struct GenericAsyncIOQueueData
|
||||
{
|
||||
SDL_Mutex *lock;
|
||||
SDL_Condition *condition;
|
||||
SDL_AsyncIOTask completed_tasks;
|
||||
} GenericAsyncIOQueueData;
|
||||
|
||||
typedef struct GenericAsyncIOData
|
||||
{
|
||||
SDL_Mutex *lock; // !!! FIXME: we can skip this lock if we have an equivalent of pread/pwrite
|
||||
SDL_IOStream *io;
|
||||
} GenericAsyncIOData;
|
||||
|
||||
static void AsyncIOTaskComplete(SDL_AsyncIOTask *task)
|
||||
{
|
||||
SDL_assert(task->queue);
|
||||
GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) task->queue->userdata;
|
||||
SDL_LockMutex(data->lock);
|
||||
LINKED_LIST_PREPEND(task, data->completed_tasks, queue);
|
||||
SDL_SignalCondition(data->condition); // wake a thread waiting on the queue.
|
||||
SDL_UnlockMutex(data->lock);
|
||||
}
|
||||
|
||||
// synchronous i/o is offloaded onto the threadpool. This function does the threaded work.
|
||||
// This is called directly, without a threadpool, if !SDL_ASYNCIO_USE_THREADPOOL.
|
||||
static void SynchronousIO(SDL_AsyncIOTask *task)
|
||||
{
|
||||
SDL_assert(task->result != SDL_ASYNCIO_CANCELLED); // shouldn't have gotten in here if cancelled!
|
||||
|
||||
GenericAsyncIOData *data = (GenericAsyncIOData *) task->asyncio->userdata;
|
||||
SDL_IOStream *io = data->io;
|
||||
const size_t size = (size_t) task->requested_size;
|
||||
void *ptr = task->buffer;
|
||||
|
||||
// this seek won't work if two tasks are reading from the same file at the same time,
|
||||
// so we lock here. This makes multiple reads from a single file serialize, but different
|
||||
// files will still run in parallel. An app can also open the same file twice to avoid this.
|
||||
SDL_LockMutex(data->lock);
|
||||
if (task->type == SDL_ASYNCIO_TASK_CLOSE) {
|
||||
task->result = SDL_CloseIO(data->io) ? SDL_ASYNCIO_COMPLETE : SDL_ASYNCIO_FAILURE;
|
||||
} else if (SDL_SeekIO(io, (Sint64) task->offset, SDL_IO_SEEK_SET) < 0) {
|
||||
task->result = SDL_ASYNCIO_FAILURE;
|
||||
} else {
|
||||
const bool writing = (task->type == SDL_ASYNCIO_TASK_WRITE);
|
||||
task->result_size = (Uint64) (writing ? SDL_WriteIO(io, ptr, size) : SDL_ReadIO(io, ptr, size));
|
||||
if (task->result_size == task->requested_size) {
|
||||
task->result = SDL_ASYNCIO_COMPLETE;
|
||||
} else {
|
||||
if (writing) {
|
||||
task->result = SDL_ASYNCIO_FAILURE; // it's always a failure on short writes.
|
||||
} else {
|
||||
const SDL_IOStatus status = SDL_GetIOStatus(io);
|
||||
SDL_assert(status != SDL_IO_STATUS_READY); // this should have either failed or been EOF.
|
||||
SDL_assert(status != SDL_IO_STATUS_NOT_READY); // these should not be non-blocking reads!
|
||||
task->result = (status == SDL_IO_STATUS_EOF) ? SDL_ASYNCIO_COMPLETE : SDL_ASYNCIO_FAILURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
SDL_UnlockMutex(data->lock);
|
||||
|
||||
AsyncIOTaskComplete(task);
|
||||
}
|
||||
|
||||
#if SDL_ASYNCIO_USE_THREADPOOL
|
||||
static SDL_InitState threadpool_init;
|
||||
static SDL_Mutex *threadpool_lock = NULL;
|
||||
static bool stop_threadpool = false;
|
||||
static SDL_AsyncIOTask threadpool_tasks;
|
||||
static SDL_Condition *threadpool_condition = NULL;
|
||||
static int max_threadpool_threads = 0;
|
||||
static int running_threadpool_threads = 0;
|
||||
static int idle_threadpool_threads = 0;
|
||||
static int threadpool_threads_spun = 0;
|
||||
|
||||
static int SDLCALL AsyncIOThreadpoolWorker(void *data)
|
||||
{
|
||||
SDL_LockMutex(threadpool_lock);
|
||||
|
||||
while (!stop_threadpool) {
|
||||
SDL_AsyncIOTask *task = LINKED_LIST_START(threadpool_tasks, threadpool);
|
||||
if (!task) {
|
||||
// if we go 30 seconds without a new task, terminate unless we're the only thread left.
|
||||
idle_threadpool_threads++;
|
||||
const bool rc = SDL_WaitConditionTimeout(threadpool_condition, threadpool_lock, 30000);
|
||||
idle_threadpool_threads--;
|
||||
|
||||
if (!rc) {
|
||||
// decide if we have too many idle threads, and if so, quit to let thread pool shrink when not busy.
|
||||
if (idle_threadpool_threads) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
LINKED_LIST_UNLINK(task, threadpool);
|
||||
|
||||
SDL_UnlockMutex(threadpool_lock);
|
||||
|
||||
// bookkeeping is done, so we drop the mutex and fire the work.
|
||||
SynchronousIO(task);
|
||||
|
||||
SDL_LockMutex(threadpool_lock); // take the lock again and see if there's another task (if not, we'll wait on the Condition).
|
||||
}
|
||||
|
||||
running_threadpool_threads--;
|
||||
|
||||
// this is kind of a hack, but this lets us reuse threadpool_condition to block on shutdown until all threads have exited.
|
||||
if (stop_threadpool) {
|
||||
SDL_BroadcastCondition(threadpool_condition);
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(threadpool_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool MaybeSpinNewWorkerThread(void)
|
||||
{
|
||||
// if all existing threads are busy and the pool of threads isn't maxed out, make a new one.
|
||||
if ((idle_threadpool_threads == 0) && (running_threadpool_threads < max_threadpool_threads)) {
|
||||
char threadname[32];
|
||||
SDL_snprintf(threadname, sizeof (threadname), "SDLasyncio%d", threadpool_threads_spun);
|
||||
SDL_Thread *thread = SDL_CreateThread(AsyncIOThreadpoolWorker, threadname, NULL);
|
||||
if (thread == NULL) {
|
||||
return false;
|
||||
}
|
||||
SDL_DetachThread(thread); // these terminate themselves when idle too long, so we never WaitThread.
|
||||
running_threadpool_threads++;
|
||||
threadpool_threads_spun++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void QueueAsyncIOTask(SDL_AsyncIOTask *task)
|
||||
{
|
||||
SDL_assert(task != NULL);
|
||||
|
||||
SDL_LockMutex(threadpool_lock);
|
||||
|
||||
if (stop_threadpool) { // just in case.
|
||||
task->result = SDL_ASYNCIO_CANCELLED;
|
||||
AsyncIOTaskComplete(task);
|
||||
} else {
|
||||
LINKED_LIST_PREPEND(task, threadpool_tasks, threadpool);
|
||||
MaybeSpinNewWorkerThread(); // okay if this fails or the thread pool is maxed out. Something will get there eventually.
|
||||
|
||||
// tell idle threads to get to work.
|
||||
// This is a broadcast because we want someone from the thread pool to wake up, but
|
||||
// also shutdown might also be blocking on this. One of the threads will grab
|
||||
// it, the others will go back to sleep.
|
||||
SDL_BroadcastCondition(threadpool_condition);
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(threadpool_lock);
|
||||
}
|
||||
|
||||
// We don't initialize async i/o at all until it's used, so
|
||||
// JUST IN CASE two things try to start at the same time,
|
||||
// this will make sure everything gets the same mutex.
|
||||
static bool PrepareThreadpool(void)
|
||||
{
|
||||
bool okay = true;
|
||||
if (SDL_ShouldInit(&threadpool_init)) {
|
||||
max_threadpool_threads = (SDL_GetNumLogicalCPUCores() * 2) + 1; // !!! FIXME: this should probably have a hint to override.
|
||||
max_threadpool_threads = SDL_clamp(max_threadpool_threads, 1, 8); // 8 is probably more than enough.
|
||||
|
||||
okay = (okay && ((threadpool_lock = SDL_CreateMutex()) != NULL));
|
||||
okay = (okay && ((threadpool_condition = SDL_CreateCondition()) != NULL));
|
||||
okay = (okay && MaybeSpinNewWorkerThread()); // make sure at least one thread is going, since we'll need it.
|
||||
|
||||
if (!okay) {
|
||||
if (threadpool_condition) {
|
||||
SDL_DestroyCondition(threadpool_condition);
|
||||
threadpool_condition = NULL;
|
||||
}
|
||||
if (threadpool_lock) {
|
||||
SDL_DestroyMutex(threadpool_lock);
|
||||
threadpool_lock = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_SetInitialized(&threadpool_init, okay);
|
||||
}
|
||||
return okay;
|
||||
}
|
||||
|
||||
static void ShutdownThreadpool(void)
|
||||
{
|
||||
if (SDL_ShouldQuit(&threadpool_init)) {
|
||||
SDL_LockMutex(threadpool_lock);
|
||||
|
||||
// cancel anything that's still pending.
|
||||
SDL_AsyncIOTask *task;
|
||||
while ((task = LINKED_LIST_START(threadpool_tasks, threadpool)) != NULL) {
|
||||
LINKED_LIST_UNLINK(task, threadpool);
|
||||
task->result = SDL_ASYNCIO_CANCELLED;
|
||||
AsyncIOTaskComplete(task);
|
||||
}
|
||||
|
||||
stop_threadpool = true;
|
||||
SDL_BroadcastCondition(threadpool_condition); // tell the whole threadpool to wake up and quit.
|
||||
|
||||
while (running_threadpool_threads > 0) {
|
||||
// each threadpool thread will broadcast this condition before it terminates if stop_threadpool is set.
|
||||
// we can't just join the threads because they are detached, so the thread pool can automatically shrink as necessary.
|
||||
SDL_WaitCondition(threadpool_condition, threadpool_lock);
|
||||
}
|
||||
|
||||
SDL_UnlockMutex(threadpool_lock);
|
||||
|
||||
SDL_DestroyMutex(threadpool_lock);
|
||||
threadpool_lock = NULL;
|
||||
SDL_DestroyCondition(threadpool_condition);
|
||||
threadpool_condition = NULL;
|
||||
|
||||
max_threadpool_threads = running_threadpool_threads = idle_threadpool_threads = threadpool_threads_spun = 0;
|
||||
|
||||
stop_threadpool = false;
|
||||
SDL_SetInitialized(&threadpool_init, false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static Sint64 generic_asyncio_size(void *userdata)
|
||||
{
|
||||
GenericAsyncIOData *data = (GenericAsyncIOData *) userdata;
|
||||
return SDL_GetIOSize(data->io);
|
||||
}
|
||||
|
||||
static bool generic_asyncio_io(void *userdata, SDL_AsyncIOTask *task)
|
||||
{
|
||||
return task->queue->iface.queue_task(task->queue->userdata, task);
|
||||
}
|
||||
|
||||
static void generic_asyncio_destroy(void *userdata)
|
||||
{
|
||||
GenericAsyncIOData *data = (GenericAsyncIOData *) userdata;
|
||||
SDL_DestroyMutex(data->lock);
|
||||
SDL_free(data);
|
||||
}
|
||||
|
||||
|
||||
static bool generic_asyncioqueue_queue_task(void *userdata, SDL_AsyncIOTask *task)
|
||||
{
|
||||
#if SDL_ASYNCIO_USE_THREADPOOL
|
||||
QueueAsyncIOTask(task);
|
||||
#else
|
||||
SynchronousIO(task); // oh well. Get a better platform.
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static void generic_asyncioqueue_cancel_task(void *userdata, SDL_AsyncIOTask *task)
|
||||
{
|
||||
#if !SDL_ASYNCIO_USE_THREADPOOL // in theory, this was all synchronous and should never call this, but just in case.
|
||||
task->result = SDL_ASYNCIO_CANCELLED;
|
||||
AsyncIOTaskComplete(task);
|
||||
#else
|
||||
// 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.
|
||||
SDL_LockMutex(threadpool_lock);
|
||||
if (LINKED_LIST_PREV(task, threadpool) != NULL) { // still in the queue waiting to be run? Take it out.
|
||||
LINKED_LIST_UNLINK(task, threadpool);
|
||||
task->result = SDL_ASYNCIO_CANCELLED;
|
||||
AsyncIOTaskComplete(task);
|
||||
}
|
||||
SDL_UnlockMutex(threadpool_lock);
|
||||
#endif
|
||||
}
|
||||
|
||||
static SDL_AsyncIOTask *generic_asyncioqueue_get_results(void *userdata)
|
||||
{
|
||||
GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata;
|
||||
SDL_LockMutex(data->lock);
|
||||
SDL_AsyncIOTask *task = LINKED_LIST_START(data->completed_tasks, queue);
|
||||
if (task) {
|
||||
LINKED_LIST_UNLINK(task, queue);
|
||||
}
|
||||
SDL_UnlockMutex(data->lock);
|
||||
return task;
|
||||
}
|
||||
|
||||
static SDL_AsyncIOTask *generic_asyncioqueue_wait_results(void *userdata, Sint32 timeoutMS)
|
||||
{
|
||||
GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata;
|
||||
SDL_LockMutex(data->lock);
|
||||
SDL_AsyncIOTask *task = LINKED_LIST_START(data->completed_tasks, queue);
|
||||
if (!task) {
|
||||
SDL_WaitConditionTimeout(data->condition, data->lock, timeoutMS);
|
||||
task = LINKED_LIST_START(data->completed_tasks, queue);
|
||||
}
|
||||
if (task) {
|
||||
LINKED_LIST_UNLINK(task, queue);
|
||||
}
|
||||
SDL_UnlockMutex(data->lock);
|
||||
return task;
|
||||
}
|
||||
|
||||
static void generic_asyncioqueue_signal(void *userdata)
|
||||
{
|
||||
GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata;
|
||||
SDL_LockMutex(data->lock);
|
||||
SDL_BroadcastCondition(data->condition);
|
||||
SDL_UnlockMutex(data->lock);
|
||||
}
|
||||
|
||||
static void generic_asyncioqueue_destroy(void *userdata)
|
||||
{
|
||||
GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata;
|
||||
SDL_DestroyMutex(data->lock);
|
||||
SDL_DestroyCondition(data->condition);
|
||||
SDL_free(data);
|
||||
}
|
||||
|
||||
bool SDL_SYS_CreateAsyncIOQueue_Generic(SDL_AsyncIOQueue *queue)
|
||||
{
|
||||
#if SDL_ASYNCIO_USE_THREADPOOL
|
||||
if (!PrepareThreadpool()) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) SDL_calloc(1, sizeof (*data));
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data->lock = SDL_CreateMutex();
|
||||
if (!data->lock) {
|
||||
SDL_free(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
data->condition = SDL_CreateCondition();
|
||||
if (!data->condition) {
|
||||
SDL_DestroyMutex(data->lock);
|
||||
SDL_free(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
static const SDL_AsyncIOQueueInterface SDL_AsyncIOQueue_Generic = {
|
||||
generic_asyncioqueue_queue_task,
|
||||
generic_asyncioqueue_cancel_task,
|
||||
generic_asyncioqueue_get_results,
|
||||
generic_asyncioqueue_wait_results,
|
||||
generic_asyncioqueue_signal,
|
||||
generic_asyncioqueue_destroy
|
||||
};
|
||||
|
||||
SDL_copyp(&queue->iface, &SDL_AsyncIOQueue_Generic);
|
||||
queue->userdata = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool SDL_SYS_AsyncIOFromFile_Generic(const char *file, const char *mode, SDL_AsyncIO *asyncio)
|
||||
{
|
||||
#if SDL_ASYNCIO_USE_THREADPOOL
|
||||
if (!PrepareThreadpool()) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
GenericAsyncIOData *data = (GenericAsyncIOData *) SDL_calloc(1, sizeof (*data));
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data->lock = SDL_CreateMutex();
|
||||
if (!data->lock) {
|
||||
SDL_free(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
data->io = SDL_IOFromFile(file, mode);
|
||||
if (!data->io) {
|
||||
SDL_DestroyMutex(data->lock);
|
||||
SDL_free(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
static const SDL_AsyncIOInterface SDL_AsyncIOFile_Generic = {
|
||||
generic_asyncio_size,
|
||||
generic_asyncio_io,
|
||||
generic_asyncio_io,
|
||||
generic_asyncio_io,
|
||||
generic_asyncio_destroy
|
||||
};
|
||||
|
||||
SDL_copyp(&asyncio->iface, &SDL_AsyncIOFile_Generic);
|
||||
asyncio->userdata = data;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDL_SYS_QuitAsyncIO_Generic(void)
|
||||
{
|
||||
#if SDL_ASYNCIO_USE_THREADPOOL
|
||||
ShutdownThreadpool();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#if SDL_ASYNCIO_ONLY_HAVE_GENERIC
|
||||
bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio)
|
||||
{
|
||||
return SDL_SYS_AsyncIOFromFile_Generic(file, mode, asyncio);
|
||||
}
|
||||
|
||||
bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue)
|
||||
{
|
||||
return SDL_SYS_CreateAsyncIOQueue_Generic(queue);
|
||||
}
|
||||
|
||||
void SDL_SYS_QuitAsyncIO(void)
|
||||
{
|
||||
SDL_SYS_QuitAsyncIO_Generic();
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user