asyncio: Added async i/o APIs.

Fixes #1374.
This commit is contained in:
Ryan C. Gordon 2024-08-27 21:57:27 -04:00
parent 7a4178bcb9
commit 3744c5b66f
No known key found for this signature in database
GPG Key ID: FA148B892AB48044
22 changed files with 1886 additions and 4 deletions

View File

@ -35,6 +35,7 @@ LOCAL_SRC_FILES := \
$(wildcard $(LOCAL_PATH)/src/dynapi/*.c) \
$(wildcard $(LOCAL_PATH)/src/events/*.c) \
$(wildcard $(LOCAL_PATH)/src/file/*.c) \
$(wildcard $(LOCAL_PATH)/src/file/generic/*.c) \
$(wildcard $(LOCAL_PATH)/src/gpu/*.c) \
$(wildcard $(LOCAL_PATH)/src/gpu/vulkan/*.c) \
$(wildcard $(LOCAL_PATH)/src/haptic/*.c) \

View File

@ -1120,6 +1120,7 @@ sdl_glob_sources(
"${SDL3_SOURCE_DIR}/src/dynapi/*.c"
"${SDL3_SOURCE_DIR}/src/events/*.c"
"${SDL3_SOURCE_DIR}/src/file/*.c"
"${SDL3_SOURCE_DIR}/src/file/generic/*.c"
"${SDL3_SOURCE_DIR}/src/filesystem/*.c"
"${SDL3_SOURCE_DIR}/src/gpu/*.c"
"${SDL3_SOURCE_DIR}/src/joystick/*.c"
@ -2105,8 +2106,6 @@ elseif(APPLE)
set(HAVE_SDL_MAIN_CALLBACKS TRUE)
endif()
sdl_glob_sources("${SDL3_SOURCE_DIR}/src/file/cocoa/*.m")
if(SDL_CAMERA)
if(MACOS OR IOS)
set(SDL_CAMERA_DRIVER_COREMEDIA 1)

View File

@ -339,6 +339,7 @@
<ClInclude Include="..\..\include\SDL3\SDL_haptic.h" />
<ClInclude Include="..\..\include\SDL3\SDL_hints.h" />
<ClInclude Include="..\..\include\SDL3\SDL_hidapi.h" />
<ClInclude Include="..\..\include\SDL3\SDL_asyncio.h" />
<ClInclude Include="..\..\include\SDL3\SDL_joystick.h" />
<ClInclude Include="..\..\include\SDL3\SDL_keyboard.h" />
<ClInclude Include="..\..\include\SDL3\SDL_keycode.h" />
@ -432,6 +433,8 @@
<ClInclude Include="..\..\src\events\SDL_windowevents_c.h" />
<ClInclude Include="..\..\src\filesystem\SDL_sysfilesystem.h" />
<ClInclude Include="..\..\src\gpu\SDL_sysgpu.h" />
<ClInclude Include="..\..\src\file\SDL_asyncio_c.h" />
<ClInclude Include="..\..\src\file\SDL_sysasyncio.h" />
<ClInclude Include="..\..\src\haptic\SDL_haptic_c.h" />
<ClInclude Include="..\..\src\haptic\SDL_syshaptic.h" />
<ClInclude Include="..\..\src\haptic\windows\SDL_dinputhaptic_c.h" />
@ -517,6 +520,8 @@
<ClCompile Include="..\..\src\dialog\SDL_dialog_utils.c" />
<ClCompile Include="..\..\src\filesystem\SDL_filesystem.c" />
<ClCompile Include="..\..\src\filesystem\windows\SDL_sysfsops.c" />
<ClCompile Include="..\..\src\file\generic\SDL_asyncio_generic.c" />
<ClCompile Include="..\..\src\file\SDL_asyncio.c" />
<ClCompile Include="..\..\src\main\gdk\SDL_sysmain_runapp.cpp" />
<ClCompile Include="..\..\src\main\generic\SDL_sysmain_callbacks.c" />
<ClCompile Include="..\..\src\main\SDL_main_callbacks.c" />

View File

@ -13,6 +13,12 @@
<ClCompile Include="..\..\src\filesystem\windows\SDL_sysfsops.c">
<Filter>filesystem\windows</Filter>
</ClCompile>
<ClCompile Include="..\..\src\file\generic\SDL_asyncio_generic.c">
<Filter>file\generic</Filter>
</ClCompile>
<ClCompile Include="..\..\src\file\SDL_asyncio.c">
<Filter>file</Filter>
</ClCompile>
<ClCompile Include="..\..\src\render\direct3d12\SDL_render_d3d12_xbox.cpp" />
<ClCompile Include="..\..\src\render\direct3d12\SDL_shaders_d3d12_xboxone.cpp" />
<ClCompile Include="..\..\src\render\direct3d12\SDL_shaders_d3d12_xboxseries.cpp" />
@ -261,6 +267,9 @@
<ClInclude Include="..\..\include\SDL3\SDL_haptic.h" />
<ClInclude Include="..\..\include\SDL3\SDL_hints.h" />
<ClInclude Include="..\..\include\SDL3\SDL_hidapi.h" />
<ClInclude Include="..\..\include\SDL3\SDL_asyncio.h">
<Filter>API Headers</Filter>
</ClInclude>
<ClInclude Include="..\..\include\SDL3\SDL_joystick.h" />
<ClInclude Include="..\..\include\SDL3\SDL_keyboard.h" />
<ClInclude Include="..\..\include\SDL3\SDL_keycode.h" />
@ -352,6 +361,12 @@
<Filter>filesystem</Filter>
</ClInclude>
<ClInclude Include="..\..\src\gpu\SDL_sysgpu.h" />
<ClInclude Include="..\..\src\file\SDL_asyncio_c.h">
<Filter>file</Filter>
</ClInclude>
<ClInclude Include="..\..\src\file\SDL_sysasyncio.h">
<Filter>file</Filter>
</ClInclude>
<ClInclude Include="..\..\src\haptic\SDL_haptic_c.h" />
<ClInclude Include="..\..\src\haptic\SDL_syshaptic.h" />
<ClInclude Include="..\..\src\haptic\windows\SDL_dinputhaptic_c.h" />

View File

@ -259,6 +259,7 @@
<ClInclude Include="..\..\include\SDL3\SDL_haptic.h" />
<ClInclude Include="..\..\include\SDL3\SDL_hints.h" />
<ClInclude Include="..\..\include\SDL3\SDL_hidapi.h" />
<ClInclude Include="..\..\include\SDL3\SDL_asyncio.h" />
<ClInclude Include="..\..\include\SDL3\SDL_joystick.h" />
<ClInclude Include="..\..\include\SDL3\SDL_keyboard.h" />
<ClInclude Include="..\..\include\SDL3\SDL_keycode.h" />
@ -352,6 +353,8 @@
<ClInclude Include="..\..\src\filesystem\SDL_sysfilesystem.h" />
<ClInclude Include="..\..\src\gpu\SDL_sysgpu.h" />
<ClInclude Include="..\..\src\gpu\vulkan\SDL_gpu_vulkan_vkfuncs.h" />
<ClInclude Include="..\..\src\file\SDL_asyncio_c.h" />
<ClInclude Include="..\..\src\file\SDL_sysasyncio.h" />
<ClInclude Include="..\..\src\haptic\SDL_haptic_c.h" />
<ClInclude Include="..\..\src\haptic\SDL_syshaptic.h" />
<ClInclude Include="..\..\src\haptic\windows\SDL_dinputhaptic_c.h" />
@ -416,6 +419,8 @@
<ClCompile Include="..\..\src\gpu\d3d11\SDL_gpu_d3d11.c" />
<ClCompile Include="..\..\src\gpu\d3d12\SDL_gpu_d3d12.c" />
<ClCompile Include="..\..\src\gpu\vulkan\SDL_gpu_vulkan.c" />
<ClCompile Include="..\..\src\file\generic\SDL_asyncio_generic.c" />
<ClCompile Include="..\..\src\file\SDL_asyncio.c" />
<ClCompile Include="..\..\src\main\generic\SDL_sysmain_callbacks.c" />
<ClCompile Include="..\..\src\main\SDL_main_callbacks.c" />
<ClCompile Include="..\..\src\main\SDL_runapp.c" />

View File

@ -211,6 +211,9 @@
<Filter Include="main\windows">
<UniqueIdentifier>{00009d5ded166cc6c6680ec771a30000}</UniqueIdentifier>
</Filter>
<Filter Include="file\generic">
<UniqueIdentifier>{00004d6806b6238cae0ed62db5440000}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\include\SDL3\SDL_begin_code.h">
@ -279,6 +282,9 @@
<ClInclude Include="..\..\include\SDL3\SDL_hidapi.h">
<Filter>API Headers</Filter>
</ClInclude>
<ClInclude Include="..\..\include\SDL3\SDL_asyncio.h">
<Filter>API Headers</Filter>
</ClInclude>
<ClInclude Include="..\..\include\SDL3\SDL_joystick.h">
<Filter>API Headers</Filter>
</ClInclude>
@ -438,6 +444,12 @@
<ClInclude Include="..\..\src\filesystem\SDL_sysfilesystem.h">
<Filter>filesystem</Filter>
</ClInclude>
<ClInclude Include="..\..\src\file\SDL_asyncio_c.h">
<Filter>file</Filter>
</ClInclude>
<ClInclude Include="..\..\src\file\SDL_sysasyncio.h">
<Filter>file</Filter>
</ClInclude>
<ClInclude Include="..\..\src\main\SDL_main_callbacks.h">
<Filter>main</Filter>
</ClInclude>
@ -944,6 +956,12 @@
<ClCompile Include="..\..\src\filesystem\windows\SDL_sysfsops.c">
<Filter>filesystem\windows</Filter>
</ClCompile>
<ClCompile Include="..\..\src\file\generic\SDL_asyncio_generic.c">
<Filter>file\generic</Filter>
</ClCompile>
<ClCompile Include="..\..\src\file\SDL_asyncio.c">
<Filter>file</Filter>
</ClCompile>
<ClCompile Include="..\..\src\main\generic\SDL_sysmain_callbacks.c">
<Filter>main\generic</Filter>
</ClCompile>

View File

@ -545,6 +545,13 @@
F3FA5A242B59ACE000FEAD97 /* yuv_rgb_lsx.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FA5A1B2B59ACE000FEAD97 /* yuv_rgb_lsx.h */; };
F3FA5A252B59ACE000FEAD97 /* yuv_rgb_common.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FA5A1C2B59ACE000FEAD97 /* yuv_rgb_common.h */; };
FA73671D19A540EF004122E4 /* CoreVideo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA73671C19A540EF004122E4 /* CoreVideo.framework */; platformFilters = (ios, maccatalyst, macos, tvos, watchos, ); };
0000E5D7110DFF81FF660000 /* SDL_cocoapen.h in Headers */ = {isa = PBXBuildFile; fileRef = 00002F2F5496FA184A0F0000 /* SDL_cocoapen.h */; };
0000D5B526B85DE7AB1C0000 /* SDL_cocoapen.m in Sources */ = {isa = PBXBuildFile; fileRef = 0000CCA310B73A7B59910000 /* SDL_cocoapen.m */; };
0000AEB9AE90228CA2D60000 /* SDL_asyncio.c in Sources */ = {isa = PBXBuildFile; fileRef = 00003928A612EC33D42C0000 /* SDL_asyncio.c */; };
000062F9C843687F50F70000 /* SDL_asyncio_c.h in Headers */ = {isa = PBXBuildFile; fileRef = 0000919399B1A908267F0000 /* SDL_asyncio_c.h */; };
00005081394CCF8322BE0000 /* SDL_sysasyncio.h in Headers */ = {isa = PBXBuildFile; fileRef = 0000585B2CAB450B40540000 /* SDL_sysasyncio.h */; };
000018AF97C08F2DAFFD0000 /* SDL_asyncio.h in Headers */ = {isa = PBXBuildFile; fileRef = 00004945A946DF5B1AED0000 /* SDL_asyncio.h */; settings = {ATTRIBUTES = (Public, ); }; };
00004D0B73767647AD550000 /* SDL_asyncio_generic.c in Sources */ = {isa = PBXBuildFile; fileRef = 0000FB02CDE4BE34A87E0000 /* SDL_asyncio_generic.c */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -1119,6 +1126,13 @@
F59C710600D5CB5801000001 /* SDL.info */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; path = SDL.info; sourceTree = "<group>"; };
F5A2EF3900C6A39A01000001 /* BUGS.txt */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text; name = BUGS.txt; path = ../../BUGS.txt; sourceTree = SOURCE_ROOT; };
FA73671C19A540EF004122E4 /* CoreVideo.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreVideo.framework; path = System/Library/Frameworks/CoreVideo.framework; sourceTree = SDKROOT; };
00002F2F5496FA184A0F0000 /* SDL_cocoapen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_cocoapen.h; path = SDL_cocoapen.h; sourceTree = "<group>"; };
0000CCA310B73A7B59910000 /* SDL_cocoapen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDL_cocoapen.m; path = SDL_cocoapen.m; sourceTree = "<group>"; };
00003928A612EC33D42C0000 /* SDL_asyncio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_asyncio.c; path = SDL_asyncio.c; sourceTree = "<group>"; };
0000919399B1A908267F0000 /* SDL_asyncio_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_asyncio_c.h; path = SDL_asyncio_c.h; sourceTree = "<group>"; };
0000585B2CAB450B40540000 /* SDL_sysasyncio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_sysasyncio.h; path = SDL_sysasyncio.h; sourceTree = "<group>"; };
00004945A946DF5B1AED0000 /* SDL_asyncio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_asyncio.h; path = SDL3/SDL_asyncio.h; sourceTree = "<group>"; };
0000FB02CDE4BE34A87E0000 /* SDL_asyncio_generic.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_asyncio_generic.c; path = SDL_asyncio_generic.c; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -1294,6 +1308,7 @@
F3F7D8C52933074B00816151 /* SDL_video.h */,
F3F7D8D42933074C00816151 /* SDL_vulkan.h */,
F3F7D8CF2933074C00816151 /* SDL.h */,
00004945A946DF5B1AED0000 /* SDL_asyncio.h */,
);
name = "Public Headers";
path = ../../include;
@ -1934,6 +1949,10 @@
isa = PBXGroup;
children = (
A7D8A7DB23E2513F00DCD162 /* SDL_iostream.c */,
00003928A612EC33D42C0000 /* SDL_asyncio.c */,
0000919399B1A908267F0000 /* SDL_asyncio_c.h */,
0000585B2CAB450B40540000 /* SDL_sysasyncio.h */,
000013C0F2EADC24ADC10000 /* generic */,
);
path = file;
sourceTree = "<group>";
@ -2426,6 +2445,14 @@
path = resources;
sourceTree = "<group>";
};
000013C0F2EADC24ADC10000 /* generic */ = {
isa = PBXGroup;
children = (
0000FB02CDE4BE34A87E0000 /* SDL_asyncio_generic.c */,
);
path = generic;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
@ -3050,6 +3077,8 @@
000095FA1BDE436CF3AF0000 /* SDL_time.c in Sources */,
0000140640E77F73F1DF0000 /* SDL_dialog_utils.c in Sources */,
0000D5B526B85DE7AB1C0000 /* SDL_cocoapen.m in Sources */,
0000AEB9AE90228CA2D60000 /* SDL_asyncio.c in Sources */,
00004D0B73767647AD550000 /* SDL_asyncio_generic.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -128,7 +128,7 @@ add_sdl_example_executable(audio-load-wav SOURCES audio/03-load-wav/load-wav.c D
add_sdl_example_executable(camera-read-and-draw SOURCES camera/01-read-and-draw/read-and-draw.c)
add_sdl_example_executable(pen-drawing-lines SOURCES pen/01-drawing-lines/drawing-lines.c)
add_sdl_example_executable(game-snake SOURCES game/01-snake/snake.c)
add_sdl_example_executable(asyncio-load-bitmaps SOURCES asyncio/01-load-bitmaps/load-bitmaps.c DATAFILES ${CMAKE_CURRENT_SOURCE_DIR}/../test/sample.bmp ${CMAKE_CURRENT_SOURCE_DIR}/../test/gamepad_front.bmp ${CMAKE_CURRENT_SOURCE_DIR}/../test/speaker.bmp ${CMAKE_CURRENT_SOURCE_DIR}/../test/icon2x.bmp)
if(PSP)
# Build EBOOT files if building for PSP

View File

@ -0,0 +1,6 @@
This example code loads a few bitmap files from disk using the asynchronous
i/o, and then draws it to the window. It uses a task group to watch multiple
reads and deal with them in whatever order they finish.
Note that for a single tiny file like this, you'd probably not want to bother
with async i/o in real life, but this is just an example of how to do it.

View File

@ -0,0 +1,125 @@
/*
* This example code loads a bitmap with asynchronous i/o and renders it.
*
* This code is public domain. Feel free to use it for any purpose!
*/
#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
/* We will use this renderer to draw into this window every frame. */
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
static SDL_AsyncIOQueue *queue = NULL;
#define TOTAL_TEXTURES 4
static const char * const bmps[TOTAL_TEXTURES] = { "sample.bmp", "gamepad_front.bmp", "speaker.bmp", "icon2x.bmp" };
static SDL_Texture *textures[TOTAL_TEXTURES];
static const SDL_FRect texture_rects[TOTAL_TEXTURES] = {
{ 116, 156, 408, 167 },
{ 20, 200, 96, 60 },
{ 525, 180, 96, 96 },
{ 288, 375, 64, 64 }
};
/* This function runs once at startup. */
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
int i;
if (!SDL_Init(SDL_INIT_VIDEO)) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't initialize SDL!", SDL_GetError(), NULL);
return SDL_APP_FAILURE;
}
if (!SDL_CreateWindowAndRenderer("examples/asyncio/load-bitmaps", 640, 480, 0, &window, &renderer)) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create window/renderer!", SDL_GetError(), NULL);
return SDL_APP_FAILURE;
}
queue = SDL_CreateAsyncIOQueue();
if (!queue) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create async i/o queue!", SDL_GetError(), NULL);
return SDL_APP_FAILURE;
}
/* Load some .bmp files asynchronously from wherever the app is being run from, put them in the same queue. */
for (i = 0; i < SDL_arraysize(bmps); i++) {
char *path = NULL;
SDL_asprintf(&path, "%s%s", SDL_GetBasePath(), bmps[i]); /* allocate a string of the full file path */
/* you _should) check for failure, but we'll just go on without files here. */
SDL_LoadFileAsync(path, queue, (void *) bmps[i]); /* attach the filename as app-specific data, so we can see it later. */
SDL_free(path);
}
return SDL_APP_CONTINUE; /* carry on with the program! */
}
/* This function runs when a new event (mouse input, keypresses, etc) occurs. */
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
if (event->type == SDL_EVENT_QUIT) {
return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */
}
return SDL_APP_CONTINUE; /* carry on with the program! */
}
/* This function runs once per frame, and is the heart of the program. */
SDL_AppResult SDL_AppIterate(void *appstate)
{
SDL_AsyncIOOutcome outcome;
int i;
if (SDL_GetAsyncIOResult(queue, &outcome)) { /* a .bmp file load has finished? */
if (outcome.result == SDL_ASYNCIO_COMPLETE) {
/* this might be _any_ of the bmps; they might finish loading in any order. */
for (i = 0; i < SDL_arraysize(bmps); i++) {
/* this doesn't need a strcmp because we gave the pointer from this array to SDL_LoadFileAsync */
if (outcome.userdata == bmps[i]) {
break;
}
}
if (i < SDL_arraysize(bmps)) { /* (just in case.) */
SDL_Surface *surface = SDL_LoadBMP_IO(SDL_IOFromConstMem(outcome.buffer, (size_t) outcome.bytes_transferred), SDL_TRUE);
if (surface) { /* the renderer is not multithreaded, so create the texture here once the data loads. */
textures[i] = SDL_CreateTextureFromSurface(renderer, surface);
if (!textures[i]) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create texture!", SDL_GetError(), NULL);
return SDL_APP_FAILURE;
}
SDL_DestroySurface(surface);
}
}
}
SDL_free(outcome.buffer);
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
for (i = 0; i < SDL_arraysize(textures); i++) {
SDL_RenderTexture(renderer, textures[i], NULL, &texture_rects[i]);
}
SDL_RenderPresent(renderer);
return SDL_APP_CONTINUE; /* carry on with the program! */
}
/* This function runs once at shutdown. */
void SDL_AppQuit(void *appstate)
{
int i;
SDL_DestroyAsyncIOQueue(queue);
for (i = 0; i < SDL_arraysize(textures); i++) {
SDL_DestroyTexture(textures[i]);
}
/* SDL will clean up the window/renderer for us. */
}

View File

@ -30,6 +30,7 @@
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_assert.h>
#include <SDL3/SDL_asyncio.h>
#include <SDL3/SDL_atomic.h>
#include <SDL3/SDL_audio.h>
#include <SDL3/SDL_bits.h>

506
include/SDL3/SDL_asyncio.h Normal file
View File

@ -0,0 +1,506 @@
/*
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.
*/
/* WIKI CATEGORY: AsyncIO */
/**
* # CategoryAsyncIO
*
* SDL offers a way to perform I/O asynchronously. This allows an app to
* read or write files without waiting for data to actually transfer; the
* functions that request I/O never block while the request is fulfilled.
*
* Instead, the data moves in the background and the app can check for
* results at their leisure.
*
* This is more complicated that just reading and writing files in a
* synchronous way, but it can allow for more efficiency, and never having
* framerate drops as the hard drive catches up, etc.
*
* The general usage pattern for async I/O is:
*
* - Create one or more SDL_AsyncIOQueue objects.
* - Open files with SDL_AsyncIOFromFile.
* - Start I/O tasks to the files with SDL_ReadAsyncIO or SDL_WriteAsyncIO,
* putting those tasks into one of the queues.
* - Later on, use SDL_GetAsyncIOResult on a queue to see if any task
* is finished without blocking. Tasks might finish in any order with
* success or failure.
* - When all your tasks are done, close the file with SDL_CloseAsyncIO.
* This also generates a task, since it might flush data to disk!
*
* This all works, without blocking, in a single thread, but one can also
* wait on a queue in a background thread, sleeping until new results
* have arrived:
*
* - Call SDL_WaitAsyncIOResult from one or more threads to efficiently block
* until new tasks complete.
* - When shutting down, call SDL_SignalAsyncIOQueue to unblock any sleeping
* threads despite there being no new tasks completed.
*
* And, of course, to match the synchronous SDL_LoadFile, we offer
* SDL_LoadFileAsync as a convenience function. This will handle allocating
* a buffer, slurping in the file data, and null-terminating it; you still
* get a task handle to check later.
*/
#ifndef SDL_asyncio_h_
#define SDL_asyncio_h_
#include <SDL3/SDL_stdinc.h>
#include <SDL3/SDL_begin_code.h>
/* Set up for C function definitions, even when using C++ */
#ifdef __cplusplus
extern "C" {
#endif
/**
* The asynchronous I/O operation structure.
*
* This operates as an opaque handle. One can then request read or write
* operations on it.
*
* \since This struct is available since SDL 3.0.0.
*
* \sa SDL_AsyncIOFromFile
*/
typedef struct SDL_AsyncIO SDL_AsyncIO;
/**
* Types of asynchronous I/O tasks.
*
* \since This enum is available since SDL 3.0.0.
*/
typedef enum SDL_AsyncIOTaskType
{
SDL_ASYNCIO_TASK_READ, /**< A read operation. */
SDL_ASYNCIO_TASK_WRITE, /**< A write operation. */
SDL_ASYNCIO_TASK_CLOSE /**< A close operation. */
} SDL_AsyncIOTaskType;
/**
* Possible outcomes of an asynchronous I/O task.
*
* \since This enum is available since SDL 3.0.0.
*/
typedef enum SDL_AsyncIOResult
{
SDL_ASYNCIO_COMPLETE, /**< request was completed without error */
SDL_ASYNCIO_FAILURE, /**< request failed for some reason; check SDL_GetError()! */
SDL_ASYNCIO_CANCELLED /**< request was cancelled before completing. */
} SDL_AsyncIOResult;
/**
* Information about a completed asynchronous I/O request.
*
* \since This struct is available since SDL 3.0.0.
*/
typedef struct SDL_AsyncIOOutcome
{
SDL_AsyncIO *asyncio; /**< what generated this task. This pointer will be invalid if it was closed! */
SDL_AsyncIOTaskType type; /**< What sort of task was this? Read, write, etc? */
SDL_AsyncIOResult result; /**< the result of the work (success, failure, cancellation). */
void *buffer; /**< buffer where data was read/written. */
Uint64 offset; /**< offset in the SDL_AsyncIO where data was read/written. */
Uint64 bytes_requested; /**< number of bytes the task was to read/write. */
Uint64 bytes_transferred; /**< actual number of bytes that were read/written. */
void *userdata; /**< pointer provided by the app when starting the task */
} SDL_AsyncIOOutcome;
/**
* An opaque handle for asynchronous I/O tasks.
*
* Each asynchronous read or write operation generates a task, which will
* complete at some time in the future. This handle is used to track the
* progress of that task.
*
* Tasks are added to an SDL_AsyncIOQueue, where they can be queried for
* completion later.
*
* \since This struct is available since SDL 3.0.0.
*
* \sa SDL_ReadAsyncIO
* \sa SDL_WriteAsyncIO
*/
typedef struct SDL_AsyncIOTask SDL_AsyncIOTask;
/**
* A queue of completed asynchronous I/O tasks.
*
* When starting an asynchronous operation, you specify a queue for the new
* task. A queue can be asked later if any tasks in it have completed,
* allowing an app to manage multiple pending tasks in one place, in
* whatever order they complete.
*
* \since This struct is available since SDL 3.0.0.
*
* \sa SDL_CreateAsyncIOQueue
* \sa SDL_ReadAsyncIO
* \sa SDL_WriteAsyncIO
* \sa SDL_GetAsyncIOResult
* \sa SDL_WaitAsyncIOResult
*/
typedef struct SDL_AsyncIOQueue SDL_AsyncIOQueue;
/**
* Use this function to create a new SDL_AsyncIO object for reading from
* and/or writing to a named file.
*
* The `mode` string understands the following values:
*
* - "r": Open a file for reading only. It must exist.
* - "w": Open a file for writing only. It will create missing files or truncate existing ones.
* - "r+": Open a file for update both reading and writing. The file must
* exist.
* - "w+": Create an empty file for both reading and writing. If a file with
* the same name already exists its content is erased and the file is
* treated as a new empty file.
*
* There is no "b" mode, as there is only "binary" style I/O, and no "a" mode
* for appending, since you specify the position when starting a task.
*
* This function supports Unicode filenames, but they must be encoded in UTF-8
* format, regardless of the underlying operating system.
*
* This call is _not_ asynchronous; it will open the file before returning,
* under the assumption that doing so is generally a fast operation. Future
* reads and writes to the opened file will be async, however.
*
* \param file a UTF-8 string representing the filename to open.
* \param mode an ASCII string representing the mode to be used for opening
* the file.
* \returns a pointer to the SDL_AsyncIO structure that is created or NULL on
* failure; call SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_CloseAsyncIO
* \sa SDL_ReadAsyncIO
* \sa SDL_WriteAsyncIO
*/
extern SDL_DECLSPEC SDL_AsyncIO * SDLCALL SDL_AsyncIOFromFile(const char *file, const char *mode);
/**
* Use this function to get the size of the data stream in an SDL_AsyncIO.
*
* This call is _not_ asynchronous; it assumes that obtaining this info
* is a non-blocking operation in most reasonable cases.
*
* \param asyncio the SDL_AsyncIO to get the size of the data stream from.
* \returns the size of the data stream in the SDL_IOStream on success or a
* negative error code on failure; call SDL_GetError() for more
* information.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.0.0.
*/
extern SDL_DECLSPEC Sint64 SDLCALL SDL_GetAsyncIOSize(SDL_AsyncIO *asyncio);
/**
* Start an async read.
*
* This function reads up to `size` bytes from `offset` position in the data
* source to the area pointed at by `ptr`. This function may read less bytes
* than requested.
*
* This function returns as quickly as possible; it does not wait for the
* read to complete. On a successful return, this work will continue in the
* background. If the work begins, even failure is asynchronous: a failing
* return value from this function only means the work couldn't start at all.
*
* `ptr` must remain available until the work is done, and may be accessed by
* the system at any time until then. Do not allocate it on the stack, as this
* might take longer than the life of the calling function to complete!
*
* An SDL_AsyncIOQueue must be specified. The newly-created SDL_AsyncIOTask
* will be added to it when it completes its work.
*
* \param asyncio a pointer to an SDL_AsyncIO structure.
* \param ptr a pointer to a buffer to read data into.
* \param offset the position to start reading in the data source.
* \param size the number of bytes to read from the data source.
* \param queue a queue to add the new SDL_AsyncIO to.
* \param userdata an app-defined pointer that will be provided with the task results.
* \returns A new task handle if a task was started, NULL on complete failure;
* call SDL_GetError() for more information.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_WriteAsyncIO
* \sa SDL_CreateAsyncIOQueue
* \sa SDL_GetAsyncIOTaskResult
*/
extern SDL_DECLSPEC SDL_AsyncIOTask * SDLCALL SDL_ReadAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata);
/**
* Start an async write.
*
* This function writes `size` bytes from `offset` position in the data
* source to the area pointed at by `ptr`.
*
* This function returns as quickly as possible; it does not wait for the
* write to complete. On a successful return, this work will continue in the
* background. If the work begins, even failure is asynchronous: a failing
* return value from this function only means the work couldn't start at all.
*
* `ptr` must remain available until the work is done, and may be accessed by
* the system at any time until then. Do not allocate it on the stack, as this
* might take longer than the life of the calling function to complete!
*
* An SDL_AsyncIOQueue must be specified. The newly-created SDL_AsyncIOTask
* will be added to it when it completes its work.
*
* \param asyncio a pointer to an SDL_AsyncIO structure.
* \param ptr a pointer to a buffer to write data from.
* \param offset the position to start writing to the data source.
* \param size the number of bytes to write to the data source.
* \param queue a queue to add the new SDL_AsyncIO to.
* \param userdata an app-defined pointer that will be provided with the task results.
* \returns A new task handle if a task was started, NULL on complete failure;
* call SDL_GetError() for more information.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_ReadAsyncIO
* \sa SDL_CreateAsyncIOQueue
* \sa SDL_GetAsyncIOTaskResult
*/
extern SDL_DECLSPEC SDL_AsyncIOTask * SDLCALL SDL_WriteAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata);
/**
* Close and free any allocated resources for an async I/O object.
*
* Closing a file is _also_ an asynchronous task! If a write failure
* were to happen during the closing process, for example, the
* task results will report it as usual.
*
* This function guarantees that the close will happen after any other
* pending tasks to `asyncio`, so it's safe to open a file, start
* several operations, close the file immediately, then check for all
* results later. This function will not block until the tasks have
* completed.
*
* Once this function returns non-NULL, `asyncio` is no longer valid,
* regardless of any future outcomes. Any completed tasks might still
* contain this pointer in their SDL_AsyncIOOutcome data, in case the
* app was using this value to track information, but it should not
* be used again.
*
* If this function returns NULL, the close wasn't started at all, and
* it's safe to attempt to close again later.
*
* \param asyncio a pointer to an SDL_AsyncIO structure to close.
* \param queue a queue to add the new SDL_AsyncIO to.
* \param userdata an app-defined pointer that will be provided with the task results.
* \returns A new task handle if a task was started, NULL on complete failure;
* call SDL_GetError() for more information.
*
* \threadsafety It is safe to call this function from any thread, but
* two threads should not attempt to close the same object.
*
* \since This function is available since SDL 3.0.0.
*/
extern SDL_DECLSPEC SDL_AsyncIOTask * SDLCALL SDL_CloseAsyncIO(SDL_AsyncIO *asyncio, SDL_AsyncIOQueue *queue, void *userdata);
/**
* Create a task queue for tracking multiple I/O operations.
*
* Async I/O operations are assigned to a queue when started. The
* queue can be checked for completed tasks thereafter.
*
* \returns a new task queue object or NULL if there was an error; call
* SDL_GetError() for more information.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_DestroyAsyncIOQueue
* \sa SDL_GetAsyncIOResult
* \sa SDL_WaitAsyncIOResult
*/
extern SDL_DECLSPEC SDL_AsyncIOQueue * SDLCALL SDL_CreateAsyncIOQueue(void);
/**
* Destroy a previously-created async I/O task queue.
*
* If there are still tasks pending for this queue, this call will block until
* those tasks are finished. All those tasks will be deallocated. Their results
* will be lost to the app.
*
* Any pending reads from SDL_LoadFileAsync() that are still in this queue
* will have their buffers deallocated by this function, to prevent a memory
* leak.
*
* Once this function is called, the queue is no longer valid and should not
* be used, including by other threads that might access it while destruction
* is blocking on pending tasks.
*
* Do not destroy a queue that still has threads waiting on it through
* SDL_WaitAsyncIOResult(). You can call SDL_SignalAsyncIOQueue() first to
* unblock those threads, and take measures (such as SDL_WaitThread()) to make sure
* they have finished their wait and won't wait on the queue again.
*
* \param queue the task queue to destroy.
*
* \threadsafety It is safe to call this function from any thread, so long as
* no other thread is waiting on the queue with SDL_WaitAsyncIOResult.
*
* \since This function is available since SDL 3.0.0.
*/
extern SDL_DECLSPEC void SDLCALL SDL_DestroyAsyncIOQueue(SDL_AsyncIOQueue *queue);
/**
* Query an async I/O task queue for completed tasks.
*
* If a task assigned to this queue has finished, this will return SDL_TRUE and fill in
* `outcome` with the details of the task. If no task in the queue has finished,
* this function will return SDL_FALSE. This function does not block.
*
* If a task has completed, this function will free its resources and the task
* pointer will no longer be valid. The task will be removed from the queue.
*
* It is safe for multiple threads to call this function on the same queue at
* once; a completed task will only go to one of the threads.
*
* \param queue the async I/O task queue to query.
* \param outcome details of a finished task will be written here. May not be NULL.
* \returns SDL_TRUE if task has completed, SDL_FALSE otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_WaitAsyncIOResult
*/
extern SDL_DECLSPEC SDL_bool SDLCALL SDL_GetAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome);
/**
* Block until an async I/O task queue has a completed task.
*
* This function puts the calling thread to sleep until there a task assigned to
* the queue that has finished.
*
* If a task assigned to the queue has finished, this will return SDL_TRUE and
* fill in `outcome` with the details of the task. If no task in the queue has
* finished, this function will return SDL_FALSE.
*
* If a task has completed, this function will free its resources and the task
* pointer will no longer be valid. The task will be removed from the queue.
*
* It is safe for multiple threads to call this function on the same queue at
* once; a completed task will only go to one of the threads.
*
* Note that by the nature of various platforms, more than one waiting
* thread may wake to handle a single task, but only one will obtain it,
* so `timeoutMS` is a _maximum_ wait time, and this function may return
* SDL_FALSE sooner.
*
* This function may return SDL_FALSE if there was a system error, the OS
* inadvertently awoke multiple threads, or if SDL_SignalAsyncIOQueue() was
* called to wake up all waiting threads without a finished task.
*
* A timeout can be used to specify a maximum wait time, but rather than polling,
* it is possible to have a timeout of -1 to wait forever, and use
* SDL_SignalAsyncIOQueue() to wake up the waiting threads later.
*
* \param queue the async I/O task queue to wait on.
* \param outcome details of a finished task will be written here. May not be NULL.
* \param timeoutMS the maximum time to wait, in milliseconds, or -1 to wait
* indefinitely.
* \returns SDL_TRUE if task has completed, SDL_FALSE otherwise.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_SignalAsyncIOQueue
*/
extern SDL_DECLSPEC SDL_bool SDLCALL SDL_WaitAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome, Sint32 timeoutMS);
/**
* Wake up any threads that are blocking in SDL_WaitAsyncIOResult().
*
* This will unblock any threads that are sleeping in a call to
* SDL_WaitAsyncIOResult for the specified queue, and cause them to
* return from that function.
*
* This can be useful when destroying a queue to make sure nothing is
* touching it indefinitely. In this case, once this call completes, the
* caller should take measures to make sure any previously-blocked threads
* have returned from their wait and will not touch the queue again (perhaps
* by setting a flag to tell the threads to terminate and then using
* SDL_WaitThread() to make sure they've done so).
*
* \param queue the async I/O task queue to signal.
*
* \threadsafety It is safe to call this function from any thread.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_WaitAsyncIOResult
*/
extern SDL_DECLSPEC void SDLCALL SDL_SignalAsyncIOQueue(SDL_AsyncIOQueue *queue);
/**
* Load all the data from a file path, asynchronously.
*
* This function returns as quickly as possible; it does not wait for the
* read to complete. On a successful return, this work will continue in the
* background. If the work begins, even failure is asynchronous: a failing
* return value from this function only means the work couldn't start at all.
*
* The data is allocated with a zero byte at the end (null terminated) for
* convenience. This extra byte is not included in SDL_AsyncIOOutcome's
* bytes_transferred value.
*
* This function will allocate the buffer to contain the file. It must be
* deallocated by calling SDL_free() on SDL_AsyncIOOutcome's buffer field
* after completion.
*
* An SDL_AsyncIOQueue must be specified. The newly-created SDL_AsyncIOTask
* will be added to it when it completes its work.
*
* \param file the path to read all available data from.
* \param queue a queue to add the new SDL_AsyncIO to.
* \param userdata an app-defined pointer that will be provided with the task results.
* \returns an async task, to be queried for results later.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_LoadFile_IO
*/
extern SDL_DECLSPEC SDL_AsyncIOTask * SDLCALL SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata);
/* Ends C function definitions when using C++ */
#ifdef __cplusplus
}
#endif
#include <SDL3/SDL_close_code.h>
#endif /* SDL_asyncio_h_ */

View File

@ -54,6 +54,7 @@
#include "video/SDL_pixels_c.h"
#include "video/SDL_video_c.h"
#include "filesystem/SDL_filesystem_c.h"
#include "file/SDL_asyncio_c.h"
#define SDL_INIT_EVERYTHING ~0U
@ -604,7 +605,7 @@ void SDL_Quit(void)
SDL_DBus_Quit();
#endif
SDL_QuitTimers();
SDL_QuitAsyncIO();
SDL_SetObjectsInvalid();
SDL_AssertionsQuit();

View File

@ -15,6 +15,7 @@ SDL3_0.0.0 {
SDL_AddTimer;
SDL_AddTimerNS;
SDL_AddVulkanRenderSemaphores;
SDL_AsyncIOFromFile;
SDL_AttachVirtualJoystick;
SDL_AudioDevicePaused;
SDL_BeginGPUComputePass;
@ -47,6 +48,7 @@ SDL3_0.0.0 {
SDL_CaptureMouse;
SDL_ClaimWindowForGPUDevice;
SDL_CleanupTLS;
SDL_CloseAsyncIO;
SDL_ClearAudioStream;
SDL_ClearClipboardData;
SDL_ClearComposition;
@ -76,6 +78,7 @@ SDL3_0.0.0 {
SDL_CopyGPUTextureToTexture;
SDL_CopyProperties;
SDL_CopyStorageFile;
SDL_CreateAsyncIOQueue;
SDL_CreateAudioStream;
SDL_CreateColorCursor;
SDL_CreateCondition;
@ -120,6 +123,7 @@ SDL3_0.0.0 {
SDL_DateTimeToTime;
SDL_Delay;
SDL_DelayNS;
SDL_DestroyAsyncIOQueue;
SDL_DestroyAudioStream;
SDL_DestroyCondition;
SDL_DestroyCursor;
@ -214,6 +218,8 @@ SDL3_0.0.0 {
SDL_GetAppMetadataProperty;
SDL_GetAssertionHandler;
SDL_GetAssertionReport;
SDL_GetAsyncIOResult;
SDL_GetAsyncIOSize;
SDL_GetAtomicInt;
SDL_GetAtomicPointer;
SDL_GetAtomicU32;
@ -607,6 +613,7 @@ SDL3_0.0.0 {
SDL_LoadBMP;
SDL_LoadBMP_IO;
SDL_LoadFile;
SDL_LoadFileAsync;
SDL_LoadFile_IO;
SDL_LoadFunction;
SDL_LoadObject;
@ -688,6 +695,7 @@ SDL3_0.0.0 {
SDL_Quit;
SDL_QuitSubSystem;
SDL_RaiseWindow;
SDL_ReadAsyncIO;
SDL_ReadIO;
SDL_ReadProcess;
SDL_ReadS16BE;
@ -904,6 +912,7 @@ SDL3_0.0.0 {
SDL_ShowSimpleMessageBox;
SDL_ShowWindow;
SDL_ShowWindowSystemMenu;
SDL_SignalAsyncIOQueue;
SDL_SignalCondition;
SDL_SignalSemaphore;
SDL_StartTextInput;
@ -964,6 +973,7 @@ SDL3_0.0.0 {
SDL_Vulkan_GetVkGetInstanceProcAddr;
SDL_Vulkan_LoadLibrary;
SDL_Vulkan_UnloadLibrary;
SDL_WaitAsyncIOResult;
SDL_WaitCondition;
SDL_WaitConditionTimeout;
SDL_WaitEvent;
@ -980,6 +990,7 @@ SDL3_0.0.0 {
SDL_WindowHasSurface;
SDL_WindowSupportsGPUPresentMode;
SDL_WindowSupportsGPUSwapchainComposition;
SDL_WriteAsyncIO;
SDL_WriteIO;
SDL_WriteS16BE;
SDL_WriteS16LE;

View File

@ -40,6 +40,7 @@
#define SDL_AddTimer SDL_AddTimer_REAL
#define SDL_AddTimerNS SDL_AddTimerNS_REAL
#define SDL_AddVulkanRenderSemaphores SDL_AddVulkanRenderSemaphores_REAL
#define SDL_AsyncIOFromFile SDL_AsyncIOFromFile_REAL
#define SDL_AttachVirtualJoystick SDL_AttachVirtualJoystick_REAL
#define SDL_AudioDevicePaused SDL_AudioDevicePaused_REAL
#define SDL_BeginGPUComputePass SDL_BeginGPUComputePass_REAL
@ -78,6 +79,7 @@
#define SDL_ClearError SDL_ClearError_REAL
#define SDL_ClearProperty SDL_ClearProperty_REAL
#define SDL_ClearSurface SDL_ClearSurface_REAL
#define SDL_CloseAsyncIO SDL_CloseAsyncIO_REAL
#define SDL_CloseAudioDevice SDL_CloseAudioDevice_REAL
#define SDL_CloseCamera SDL_CloseCamera_REAL
#define SDL_CloseGamepad SDL_CloseGamepad_REAL
@ -101,6 +103,7 @@
#define SDL_CopyGPUTextureToTexture SDL_CopyGPUTextureToTexture_REAL
#define SDL_CopyProperties SDL_CopyProperties_REAL
#define SDL_CopyStorageFile SDL_CopyStorageFile_REAL
#define SDL_CreateAsyncIOQueue SDL_CreateAsyncIOQueue_REAL
#define SDL_CreateAudioStream SDL_CreateAudioStream_REAL
#define SDL_CreateColorCursor SDL_CreateColorCursor_REAL
#define SDL_CreateCondition SDL_CreateCondition_REAL
@ -145,6 +148,7 @@
#define SDL_DateTimeToTime SDL_DateTimeToTime_REAL
#define SDL_Delay SDL_Delay_REAL
#define SDL_DelayNS SDL_DelayNS_REAL
#define SDL_DestroyAsyncIOQueue SDL_DestroyAsyncIOQueue_REAL
#define SDL_DestroyAudioStream SDL_DestroyAudioStream_REAL
#define SDL_DestroyCondition SDL_DestroyCondition_REAL
#define SDL_DestroyCursor SDL_DestroyCursor_REAL
@ -239,6 +243,8 @@
#define SDL_GetAppMetadataProperty SDL_GetAppMetadataProperty_REAL
#define SDL_GetAssertionHandler SDL_GetAssertionHandler_REAL
#define SDL_GetAssertionReport SDL_GetAssertionReport_REAL
#define SDL_GetAsyncIOResult SDL_GetAsyncIOResult_REAL
#define SDL_GetAsyncIOSize SDL_GetAsyncIOSize_REAL
#define SDL_GetAtomicInt SDL_GetAtomicInt_REAL
#define SDL_GetAtomicPointer SDL_GetAtomicPointer_REAL
#define SDL_GetAtomicU32 SDL_GetAtomicU32_REAL
@ -632,6 +638,7 @@
#define SDL_LoadBMP SDL_LoadBMP_REAL
#define SDL_LoadBMP_IO SDL_LoadBMP_IO_REAL
#define SDL_LoadFile SDL_LoadFile_REAL
#define SDL_LoadFileAsync SDL_LoadFileAsync_REAL
#define SDL_LoadFile_IO SDL_LoadFile_IO_REAL
#define SDL_LoadFunction SDL_LoadFunction_REAL
#define SDL_LoadObject SDL_LoadObject_REAL
@ -713,6 +720,7 @@
#define SDL_Quit SDL_Quit_REAL
#define SDL_QuitSubSystem SDL_QuitSubSystem_REAL
#define SDL_RaiseWindow SDL_RaiseWindow_REAL
#define SDL_ReadAsyncIO SDL_ReadAsyncIO_REAL
#define SDL_ReadIO SDL_ReadIO_REAL
#define SDL_ReadProcess SDL_ReadProcess_REAL
#define SDL_ReadS16BE SDL_ReadS16BE_REAL
@ -929,6 +937,7 @@
#define SDL_ShowSimpleMessageBox SDL_ShowSimpleMessageBox_REAL
#define SDL_ShowWindow SDL_ShowWindow_REAL
#define SDL_ShowWindowSystemMenu SDL_ShowWindowSystemMenu_REAL
#define SDL_SignalAsyncIOQueue SDL_SignalAsyncIOQueue_REAL
#define SDL_SignalCondition SDL_SignalCondition_REAL
#define SDL_SignalSemaphore SDL_SignalSemaphore_REAL
#define SDL_StartTextInput SDL_StartTextInput_REAL
@ -989,6 +998,7 @@
#define SDL_Vulkan_GetVkGetInstanceProcAddr SDL_Vulkan_GetVkGetInstanceProcAddr_REAL
#define SDL_Vulkan_LoadLibrary SDL_Vulkan_LoadLibrary_REAL
#define SDL_Vulkan_UnloadLibrary SDL_Vulkan_UnloadLibrary_REAL
#define SDL_WaitAsyncIOResult SDL_WaitAsyncIOResult_REAL
#define SDL_WaitCondition SDL_WaitCondition_REAL
#define SDL_WaitConditionTimeout SDL_WaitConditionTimeout_REAL
#define SDL_WaitEvent SDL_WaitEvent_REAL
@ -1005,6 +1015,7 @@
#define SDL_WindowHasSurface SDL_WindowHasSurface_REAL
#define SDL_WindowSupportsGPUPresentMode SDL_WindowSupportsGPUPresentMode_REAL
#define SDL_WindowSupportsGPUSwapchainComposition SDL_WindowSupportsGPUSwapchainComposition_REAL
#define SDL_WriteAsyncIO SDL_WriteAsyncIO_REAL
#define SDL_WriteIO SDL_WriteIO_REAL
#define SDL_WriteS16BE SDL_WriteS16BE_REAL
#define SDL_WriteS16LE SDL_WriteS16LE_REAL

View File

@ -61,6 +61,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_AddSurfaceAlternateImage,(SDL_Surface *a, SDL_Surfa
SDL_DYNAPI_PROC(SDL_TimerID,SDL_AddTimer,(Uint32 a, SDL_TimerCallback b, void *c),(a,b,c),return)
SDL_DYNAPI_PROC(SDL_TimerID,SDL_AddTimerNS,(Uint64 a, SDL_NSTimerCallback b, void *c),(a,b,c),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_AddVulkanRenderSemaphores,(SDL_Renderer *a, Uint32 b, Sint64 c, Sint64 d),(a,b,c,d),return)
SDL_DYNAPI_PROC(SDL_AsyncIO*,SDL_AsyncIOFromFile,(const char *a, const char *b),(a,b),return)
SDL_DYNAPI_PROC(SDL_JoystickID,SDL_AttachVirtualJoystick,(const SDL_VirtualJoystickDesc *a),(a),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_AudioDevicePaused,(SDL_AudioDeviceID a),(a),return)
SDL_DYNAPI_PROC(SDL_GPUComputePass*,SDL_BeginGPUComputePass,(SDL_GPUCommandBuffer *a, const SDL_GPUStorageTextureWriteOnlyBinding *b, Uint32 c, const SDL_GPUStorageBufferWriteOnlyBinding *d, Uint32 e),(a,b,c,d,e),return)
@ -99,6 +100,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_ClearComposition,(SDL_Window *a),(a),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_ClearError,(void),(),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_ClearProperty,(SDL_PropertiesID a, const char *b),(a,b),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_ClearSurface,(SDL_Surface *a, float b, float c, float d, float e),(a,b,c,d,e),return)
SDL_DYNAPI_PROC(SDL_AsyncIOTask*,SDL_CloseAsyncIO,(SDL_AsyncIO *a, SDL_AsyncIOQueue *b, void *c),(a,b,c),return)
SDL_DYNAPI_PROC(void,SDL_CloseAudioDevice,(SDL_AudioDeviceID a),(a),)
SDL_DYNAPI_PROC(void,SDL_CloseCamera,(SDL_Camera *a),(a),)
SDL_DYNAPI_PROC(void,SDL_CloseGamepad,(SDL_Gamepad *a),(a),)
@ -122,6 +124,7 @@ SDL_DYNAPI_PROC(void,SDL_CopyGPUBufferToBuffer,(SDL_GPUCopyPass *a, const SDL_GP
SDL_DYNAPI_PROC(void,SDL_CopyGPUTextureToTexture,(SDL_GPUCopyPass *a, const SDL_GPUTextureLocation *b, const SDL_GPUTextureLocation *c, Uint32 d, Uint32 e, Uint32 f, SDL_bool g),(a,b,c,d,e,f,g),)
SDL_DYNAPI_PROC(SDL_bool,SDL_CopyProperties,(SDL_PropertiesID a, SDL_PropertiesID b),(a,b),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_CopyStorageFile,(SDL_Storage *a, const char *b, const char *c),(a,b,c),return)
SDL_DYNAPI_PROC(SDL_AsyncIOQueue*,SDL_CreateAsyncIOQueue,(void),(),return)
SDL_DYNAPI_PROC(SDL_AudioStream*,SDL_CreateAudioStream,(const SDL_AudioSpec *a, const SDL_AudioSpec *b),(a,b),return)
SDL_DYNAPI_PROC(SDL_Cursor*,SDL_CreateColorCursor,(SDL_Surface *a, int b, int c),(a,b,c),return)
SDL_DYNAPI_PROC(SDL_Condition*,SDL_CreateCondition,(void),(),return)
@ -166,6 +169,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_CursorVisible,(void),(),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_DateTimeToTime,(const SDL_DateTime *a, SDL_Time *b),(a,b),return)
SDL_DYNAPI_PROC(void,SDL_Delay,(Uint32 a),(a),)
SDL_DYNAPI_PROC(void,SDL_DelayNS,(Uint64 a),(a),)
SDL_DYNAPI_PROC(void,SDL_DestroyAsyncIOQueue,(SDL_AsyncIOQueue *a),(a),)
SDL_DYNAPI_PROC(void,SDL_DestroyAudioStream,(SDL_AudioStream *a),(a),)
SDL_DYNAPI_PROC(void,SDL_DestroyCondition,(SDL_Condition *a),(a),)
SDL_DYNAPI_PROC(void,SDL_DestroyCursor,(SDL_Cursor *a),(a),)
@ -260,6 +264,8 @@ SDL_DYNAPI_PROC(int,SDL_GetAndroidSDKVersion,(void),(),return)
SDL_DYNAPI_PROC(const char*,SDL_GetAppMetadataProperty,(const char *a),(a),return)
SDL_DYNAPI_PROC(SDL_AssertionHandler,SDL_GetAssertionHandler,(void **a),(a),return)
SDL_DYNAPI_PROC(const SDL_AssertData*,SDL_GetAssertionReport,(void),(),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_GetAsyncIOResult,(SDL_AsyncIOQueue *a, SDL_AsyncIOOutcome *b),(a,b),return)
SDL_DYNAPI_PROC(Sint64,SDL_GetAsyncIOSize,(SDL_AsyncIO *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_GetAtomicInt,(SDL_AtomicInt *a),(a),return)
SDL_DYNAPI_PROC(void*,SDL_GetAtomicPointer,(void **a),(a),return)
SDL_DYNAPI_PROC(Uint32,SDL_GetAtomicU32,(SDL_AtomicU32 *a),(a),return)
@ -652,6 +658,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_KillProcess,(SDL_Process *a, SDL_bool b),(a,b),retu
SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadBMP,(const char *a),(a),return)
SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadBMP_IO,(SDL_IOStream *a, SDL_bool b),(a,b),return)
SDL_DYNAPI_PROC(void*,SDL_LoadFile,(const char *a, size_t *b),(a,b),return)
SDL_DYNAPI_PROC(SDL_AsyncIOTask*,SDL_LoadFileAsync,(const char *a, SDL_AsyncIOQueue *b, void *c),(a,b,c),return)
SDL_DYNAPI_PROC(void*,SDL_LoadFile_IO,(SDL_IOStream *a, size_t *b, SDL_bool c),(a,b,c),return)
SDL_DYNAPI_PROC(SDL_FunctionPointer,SDL_LoadFunction,(void *a, const char *b),(a,b),return)
SDL_DYNAPI_PROC(void*,SDL_LoadObject,(const char *a),(a),return)
@ -724,6 +731,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_QueryGPUFence,(SDL_GPUDevice *a, SDL_GPUFence *b),(
SDL_DYNAPI_PROC(void,SDL_Quit,(void),(),)
SDL_DYNAPI_PROC(void,SDL_QuitSubSystem,(SDL_InitFlags a),(a),)
SDL_DYNAPI_PROC(SDL_bool,SDL_RaiseWindow,(SDL_Window *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(size_t,SDL_ReadIO,(SDL_IOStream *a, void *b, size_t c),(a,b,c),return)
SDL_DYNAPI_PROC(void*,SDL_ReadProcess,(SDL_Process *a, size_t *b, int *c),(a,b,c),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_ReadS16BE,(SDL_IOStream *a, Sint16 *b),(a,b),return)
@ -939,6 +947,7 @@ SDL_DYNAPI_PROC(void,SDL_ShowSaveFileDialog,(SDL_DialogFileCallback a, void *b,
SDL_DYNAPI_PROC(SDL_bool,SDL_ShowSimpleMessageBox,(SDL_MessageBoxFlags a, const char *b, const char *c, SDL_Window *d),(a,b,c,d),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_ShowWindow,(SDL_Window *a),(a),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_ShowWindowSystemMenu,(SDL_Window *a, int b, int c),(a,b,c),return)
SDL_DYNAPI_PROC(void,SDL_SignalAsyncIOQueue,(SDL_AsyncIOQueue *a),(a),)
SDL_DYNAPI_PROC(void,SDL_SignalCondition,(SDL_Condition *a),(a),)
SDL_DYNAPI_PROC(void,SDL_SignalSemaphore,(SDL_Semaphore *a),(a),)
SDL_DYNAPI_PROC(SDL_bool,SDL_StartTextInput,(SDL_Window *a),(a),return)
@ -999,6 +1008,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_Vulkan_GetPresentationSupport,(VkInstance a, VkPhys
SDL_DYNAPI_PROC(SDL_FunctionPointer,SDL_Vulkan_GetVkGetInstanceProcAddr,(void),(),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_Vulkan_LoadLibrary,(const char *a),(a),return)
SDL_DYNAPI_PROC(void,SDL_Vulkan_UnloadLibrary,(void),(),)
SDL_DYNAPI_PROC(SDL_bool,SDL_WaitAsyncIOResult,(SDL_AsyncIOQueue *a, SDL_AsyncIOOutcome *b, Sint32 c),(a,b,c),return)
SDL_DYNAPI_PROC(void,SDL_WaitCondition,(SDL_Condition *a, SDL_Mutex *b),(a,b),)
SDL_DYNAPI_PROC(SDL_bool,SDL_WaitConditionTimeout,(SDL_Condition *a, SDL_Mutex *b, Sint32 c),(a,b,c),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_WaitEvent,(SDL_Event *a),(a),return)
@ -1015,6 +1025,7 @@ SDL_DYNAPI_PROC(SDL_InitFlags,SDL_WasInit,(SDL_InitFlags a),(a),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_WindowHasSurface,(SDL_Window *a),(a),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_WindowSupportsGPUPresentMode,(SDL_GPUDevice *a, SDL_Window *b, SDL_GPUPresentMode c),(a,b,c),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_WindowSupportsGPUSwapchainComposition,(SDL_GPUDevice *a, SDL_Window *b, SDL_GPUSwapchainComposition c),(a,b,c),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(size_t,SDL_WriteIO,(SDL_IOStream *a, const void *b, size_t c),(a,b,c),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_WriteS16BE,(SDL_IOStream *a, Sint16 b),(a,b),return)
SDL_DYNAPI_PROC(SDL_bool,SDL_WriteS16LE,(SDL_IOStream *a, Sint16 b),(a,b),return)

335
src/file/SDL_asyncio.c Normal file
View 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;
}
SDL_bool SDL_GetAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome)
{
if (!queue || !outcome) {
return SDL_FALSE;
}
return GetAsyncIOTaskOutcome(queue->iface.get_results(queue->userdata), outcome);
}
SDL_bool SDL_WaitAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome, Sint32 timeoutMS)
{
if (!queue || !outcome) {
return SDL_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
View 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_

135
src/file/SDL_sysasyncio.h Normal file
View File

@ -0,0 +1,135 @@
/*
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;
bool reading;
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_Mutex *iolock; // the generic backend uses this, so I've added it here for laziness's sake.
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

View File

@ -0,0 +1,461 @@
/*
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 SDL_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)
{
// !!! FIXME: remove/free completed tasks? Refuse to destroy a queue that has remaining tasks?
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

View File

@ -326,6 +326,7 @@ files2headers(gamepad_image_headers
files2headers(icon_bmp_header icon.bmp)
files2headers(glass_bmp_header glass.bmp)
add_sdl_test_executable(testasyncio MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testasyncio.c)
add_sdl_test_executable(testaudio MAIN_CALLBACKS NEEDS_RESOURCES TESTUTILS SOURCES testaudio.c)
add_sdl_test_executable(testcolorspace SOURCES testcolorspace.c)
add_sdl_test_executable(testfile NONINTERACTIVE SOURCES testfile.c)

176
test/testasyncio.c Normal file
View File

@ -0,0 +1,176 @@
/*
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.
*/
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_test.h>
#include <SDL3/SDL_test_common.h>
static SDL_Renderer *renderer = NULL;
static SDL_Texture *texture = NULL;
static SDL_AsyncIOQueue *queue = NULL;
static SDLTest_CommonState *state = NULL;
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
const char *base = NULL;
char **bmps = NULL;
int bmpcount = 0;
int i;
SDL_srand(0);
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO);
if (!state) {
return SDL_APP_FAILURE;
}
/* Enable standard application logging */
SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
/* Parse commandline */
for (i = 1; i < argc;) {
int consumed = SDLTest_CommonArg(state, i);
if (consumed <= 0) {
static const char *options[] = {
NULL,
};
SDLTest_CommonLogUsage(state, argv[0], options);
SDL_Quit();
SDLTest_CommonDestroyState(state);
return 1;
}
i += consumed;
}
state->num_windows = 1;
/* Load the SDL library */
if (!SDLTest_CommonInit(state)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't initialize SDL: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE);
renderer = state->renderers[0];
if (!renderer) {
/* SDL_Log("Couldn't create renderer: %s", SDL_GetError()); */
return SDL_APP_FAILURE;
}
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, 512, 512);
if (!texture) {
SDL_Log("Couldn't create texture: %s", SDL_GetError());
return SDL_APP_FAILURE;
} else {
static const Uint32 blank[512 * 512];
const SDL_Rect rect = { 0, 0, 512, 512 };
SDL_UpdateTexture(texture, &rect, blank, 512 * sizeof (Uint32));
}
queue = SDL_CreateAsyncIOQueue();
if (!queue) {
SDL_Log("Couldn't create async i/o queue: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
base = SDL_GetBasePath();
bmps = SDL_GlobDirectory(base, "*.bmp", SDL_GLOB_CASEINSENSITIVE, &bmpcount);
if (!bmps || (bmpcount == 0)) {
SDL_Log("No BMP files found.");
return SDL_APP_FAILURE;
}
for (i = 0; i < bmpcount; i++) {
char *path = NULL;
if (SDL_asprintf(&path, "%s%s", base, bmps[i]) < 0) {
SDL_free(path);
} else {
SDL_Log("Loading %s...", path);
SDL_LoadFileAsync(path, queue, path);
}
}
SDL_free(bmps);
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
switch (event->type) {
case SDL_EVENT_QUIT:
return SDL_APP_SUCCESS;
default:
break;
}
return SDLTest_CommonEventMainCallbacks(state, event);
}
static void async_io_task_complete(const SDL_AsyncIOOutcome *outcome)
{
const char *fname = (const char *) outcome->userdata;
const char *resultstr = "[unknown result]";
switch (outcome->result) {
#define RESCASE(x) case x: resultstr = #x; break
RESCASE(SDL_ASYNCIO_COMPLETE);
RESCASE(SDL_ASYNCIO_FAILURE);
RESCASE(SDL_ASYNCIO_CANCELLED);
#undef RESCASE
}
SDL_Log("File '%s' async results: %s", fname, resultstr);
if (outcome->result == SDL_ASYNCIO_COMPLETE) {
SDL_Surface *surface = SDL_LoadBMP_IO(SDL_IOFromConstMem(outcome->buffer, (size_t) outcome->bytes_transferred), SDL_TRUE);
if (surface) {
SDL_Surface *converted = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA8888);
SDL_DestroySurface(surface);
if (converted) {
const SDL_Rect rect = { 50 + SDL_rand(512 - 100), 50 + SDL_rand(512 - 100), converted->w, converted->h };
SDL_UpdateTexture(texture, &rect, converted->pixels, converted->pitch);
SDL_DestroySurface(converted);
}
}
}
SDL_free(outcome->userdata);
SDL_free(outcome->buffer);
}
SDL_AppResult SDL_AppIterate(void *appstate)
{
SDL_AsyncIOOutcome outcome;
if (SDL_GetAsyncIOResult(queue, &outcome)) {
async_io_task_complete(&outcome);
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
SDL_RenderTexture(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
return SDL_APP_CONTINUE;
}
void SDL_AppQuit(void *appstate)
{
SDL_DestroyAsyncIOQueue(queue);
SDL_DestroyTexture(texture);
SDLTest_CommonQuit(state);
}