#include "SDL_internal.h"
#include "SDL_sysasyncio.h"
#include "SDL_asyncio_c.h"
static const char *AsyncFileModeValid(const char *mode, bool *readonly)
{
static const struct { const char *valid; const char *with_binary; bool readonly; } mode_map[] = {
{ "r", "rb", true },
{ "w", "wb", false },
{ "r+","r+b", false },
{ "w+", "w+b", false }
};
for (int i = 0; i < SDL_arraysize(mode_map); i++) {
if (SDL_strcmp(mode, mode_map[i].valid) == 0) {
*readonly = mode_map[i].readonly;
return mode_map[i].with_binary;
}
}
*readonly = false;
return NULL;
}
SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode)
{
CHECK_PARAM(!file) {
SDL_InvalidParamError("file");
return NULL;
}
CHECK_PARAM(!mode) {
SDL_InvalidParamError("mode");
return NULL;
}
bool readonly = false;
const char *binary_mode = AsyncFileModeValid(mode, &readonly);
if (!binary_mode) {
SDL_SetError("Unsupported file mode");
return NULL;
}
SDL_AsyncIO *asyncio = (SDL_AsyncIO *)SDL_calloc(1, sizeof(*asyncio));
if (!asyncio) {
return NULL;
}
asyncio->readonly = readonly;
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)
{
CHECK_PARAM(!asyncio) {
SDL_InvalidParamError("asyncio");
return -1;
}
return asyncio->iface.size(asyncio->userdata);
}
static bool RequestAsyncIO(bool reading, SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata)
{
CHECK_PARAM(!asyncio) {
return SDL_InvalidParamError("asyncio");
}
CHECK_PARAM(!ptr) {
return SDL_InvalidParamError("ptr");
}
CHECK_PARAM(!queue) {
return SDL_InvalidParamError("queue");
}
SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*task));
if (!task) {
return false;
}
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);
return SDL_SetError("SDL_AsyncIO is closing, can't start new tasks");
}
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 != NULL);
}
bool 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);
}
bool 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);
}
bool SDL_CloseAsyncIO(SDL_AsyncIO *asyncio, bool flush, SDL_AsyncIOQueue *queue, void *userdata)
{
CHECK_PARAM(!asyncio) {
return SDL_InvalidParamError("asyncio");
}
CHECK_PARAM(!queue) {
return SDL_InvalidParamError("queue");
}
SDL_LockMutex(asyncio->lock);
if (asyncio->closing) {
SDL_UnlockMutex(asyncio->lock);
return SDL_SetError("Already closing");
}
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;
task->flush = flush;
asyncio->closing = task;
if (LINKED_LIST_START(asyncio->tasks, asyncio) == NULL) { LINKED_LIST_PREPEND(task, asyncio->tasks, asyncio);
SDL_AddAtomicInt(&queue->tasks_inflight, 1);
if (!asyncio->iface.close(asyncio->userdata, task)) {
SDL_AddAtomicInt(&queue->tasks_inflight, -1);
LINKED_LIST_UNLINK(task, asyncio);
SDL_free(task);
task = asyncio->closing = NULL;
}
}
}
SDL_UnlockMutex(asyncio->lock);
return (task != NULL);
}
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->type = task->type;
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;
SDL_Mutex *lock = asyncio->lock;
SDL_LockMutex(lock);
LINKED_LIST_UNLINK(task, asyncio);
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); if (!async_close_task_was_queued) {
SDL_AddAtomicInt(&closing->queue->tasks_inflight, -1);
}
}
SDL_UnlockMutex(lock);
bool retval = true;
if (closing && (task == closing)) {
if (asyncio->oneshot) {
retval = false; }
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) {
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); task->buffer = NULL;
}
SDL_AsyncIOOutcome outcome;
GetAsyncIOTaskOutcome(task, &outcome); }
}
queue->iface.destroy(queue->userdata);
SDL_free(queue);
}
}
void SDL_QuitAsyncIO(void)
{
SDL_SYS_QuitAsyncIO();
}
bool SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata)
{
CHECK_PARAM(!file) {
return SDL_InvalidParamError("file");
}
CHECK_PARAM(!queue) {
return SDL_InvalidParamError("queue");
}
bool retval = false;
SDL_AsyncIO *asyncio = SDL_AsyncIOFromFile(file, "r");
if (asyncio) {
asyncio->oneshot = true;
Uint8 *ptr = NULL;
const Sint64 flen = SDL_GetAsyncIOSize(asyncio);
if (flen >= 0) {
ptr = (Uint8 *) SDL_malloc((size_t) (flen + 1)); if (ptr) {
ptr[flen] = '\0';
retval = SDL_ReadAsyncIO(asyncio, ptr, 0, (Uint64) flen, queue, userdata);
if (!retval) {
SDL_free(ptr);
}
}
}
SDL_CloseAsyncIO(asyncio, false, queue, userdata); }
return retval;
}