asyncio: Added async i/o APIs.

This commit is contained in:
Ryan C. Gordon
2024-08-27 21:57:27 -04:00
parent 7293c18314
commit e79ce2a200
22 changed files with 1883 additions and 2 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

@@ -1119,6 +1119,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"
@@ -2111,8 +2112,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" />
@@ -262,6 +268,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" />
@@ -353,6 +362,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" />
@@ -415,6 +418,8 @@
<ClCompile Include="..\..\src\gpu\SDL_gpu.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 @@
F3FD042E2C9B755700824C4C /* SDL_hidapi_nintendo.h in Headers */ = {isa = PBXBuildFile; fileRef = F3FD042C2C9B755700824C4C /* SDL_hidapi_nintendo.h */; };
F3FD042F2C9B755700824C4C /* SDL_hidapi_steam_hori.c in Sources */ = {isa = PBXBuildFile; fileRef = F3FD042D2C9B755700824C4C /* SDL_hidapi_steam_hori.c */; };
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 */
@@ -1120,6 +1127,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 */
@@ -1295,6 +1309,7 @@
F3F7D8C52933074B00816151 /* SDL_video.h */,
F3F7D8D42933074C00816151 /* SDL_vulkan.h */,
F3F7D8CF2933074C00816151 /* SDL.h */,
00004945A946DF5B1AED0000 /* SDL_asyncio.h */,
);
name = "Public Headers";
path = ../../include;
@@ -1928,6 +1943,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>";
@@ -2420,6 +2439,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 */
@@ -3044,6 +3071,8 @@
0000140640E77F73F1DF0000 /* SDL_dialog_utils.c in Sources */,
0000D5B526B85DE7AB1C0000 /* SDL_cocoapen.m in Sources */,
6312C66D2B42341400A7BB00 /* SDL_murmur3.c in Sources */,
0000AEB9AE90228CA2D60000 /* SDL_asyncio.c in Sources */,
00004D0B73767647AD550000 /* SDL_asyncio_generic.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -144,6 +144,7 @@ add_sdl_example_executable(input-joystick-polling SOURCES input/01-joystick-poll
add_sdl_example_executable(input-joystick-events SOURCES input/02-joystick-events/joystick-events.c)
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(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)
add_sdl_example_executable(demo-snake SOURCES demo/01-snake/snake.c)
add_sdl_example_executable(demo-woodeneye-008 SOURCES demo/02-woodeneye-008/woodeneye-008.c)
add_sdl_example_executable(demo-infinite-monkeys SOURCES demo/03-infinite-monkeys/infinite-monkeys.c)

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), 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, SDL_AppResult result)
{
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

File diff suppressed because it is too large Load Diff

View File

@@ -55,6 +55,7 @@
#include "video/SDL_surface_c.h"
#include "video/SDL_video_c.h"
#include "filesystem/SDL_filesystem_c.h"
#include "file/SDL_asyncio_c.h"
#ifdef SDL_PLATFORM_ANDROID
#include "core/android/SDL_android.h"
#endif
@@ -625,6 +626,7 @@ void SDL_Quit(void)
#endif
SDL_QuitTimers();
SDL_QuitAsyncIO();
SDL_SetObjectsInvalid();
SDL_AssertionsQuit();

View File

@@ -1189,6 +1189,17 @@ SDL3_0.0.0 {
SDL_GetCurrentDirectory;
SDL_IsAudioDevicePhysical;
SDL_IsAudioDevicePlayback;
SDL_AsyncIOFromFile;
SDL_GetAsyncIOSize;
SDL_ReadAsyncIO;
SDL_WriteAsyncIO;
SDL_CloseAsyncIO;
SDL_CreateAsyncIOQueue;
SDL_DestroyAsyncIOQueue;
SDL_GetAsyncIOResult;
SDL_WaitAsyncIOResult;
SDL_SignalAsyncIOQueue;
SDL_LoadFileAsync;
# extra symbols go here (don't modify this line)
local: *;
};

View File

@@ -1214,3 +1214,14 @@
#define SDL_GetCurrentDirectory SDL_GetCurrentDirectory_REAL
#define SDL_IsAudioDevicePhysical SDL_IsAudioDevicePhysical_REAL
#define SDL_IsAudioDevicePlayback SDL_IsAudioDevicePlayback_REAL
#define SDL_AsyncIOFromFile SDL_AsyncIOFromFile_REAL
#define SDL_GetAsyncIOSize SDL_GetAsyncIOSize_REAL
#define SDL_ReadAsyncIO SDL_ReadAsyncIO_REAL
#define SDL_WriteAsyncIO SDL_WriteAsyncIO_REAL
#define SDL_CloseAsyncIO SDL_CloseAsyncIO_REAL
#define SDL_CreateAsyncIOQueue SDL_CreateAsyncIOQueue_REAL
#define SDL_DestroyAsyncIOQueue SDL_DestroyAsyncIOQueue_REAL
#define SDL_GetAsyncIOResult SDL_GetAsyncIOResult_REAL
#define SDL_WaitAsyncIOResult SDL_WaitAsyncIOResult_REAL
#define SDL_SignalAsyncIOQueue SDL_SignalAsyncIOQueue_REAL
#define SDL_LoadFileAsync SDL_LoadFileAsync_REAL

View File

@@ -1220,3 +1220,14 @@ SDL_DYNAPI_PROC(bool,SDL_SaveFile,(const char *a,const void *b,size_t c),(a,b,c)
SDL_DYNAPI_PROC(char*,SDL_GetCurrentDirectory,(void),(),return)
SDL_DYNAPI_PROC(bool,SDL_IsAudioDevicePhysical,(SDL_AudioDeviceID a),(a),return)
SDL_DYNAPI_PROC(bool,SDL_IsAudioDevicePlayback,(SDL_AudioDeviceID a),(a),return)
SDL_DYNAPI_PROC(SDL_AsyncIO*,SDL_AsyncIOFromFile,(const char *a, const char *b),(a,b),return)
SDL_DYNAPI_PROC(Sint64,SDL_GetAsyncIOSize,(SDL_AsyncIO *a),(a),return)
SDL_DYNAPI_PROC(SDL_AsyncIOTask*,SDL_ReadAsyncIO,(SDL_AsyncIO *a, void *b, Uint64 c, Uint64 d, SDL_AsyncIOQueue *e, void *f),(a,b,c,d,e,f),return)
SDL_DYNAPI_PROC(SDL_AsyncIOTask*,SDL_WriteAsyncIO,(SDL_AsyncIO *a, void *b, Uint64 c, Uint64 d, SDL_AsyncIOQueue *e, void *f),(a,b,c,d,e,f),return)
SDL_DYNAPI_PROC(SDL_AsyncIOTask*,SDL_CloseAsyncIO,(SDL_AsyncIO *a, SDL_AsyncIOQueue *b, void *c),(a,b,c),return)
SDL_DYNAPI_PROC(SDL_AsyncIOQueue*,SDL_CreateAsyncIOQueue,(void),(),return)
SDL_DYNAPI_PROC(void,SDL_DestroyAsyncIOQueue,(SDL_AsyncIOQueue *a),(a),)
SDL_DYNAPI_PROC(bool,SDL_GetAsyncIOResult,(SDL_AsyncIOQueue *a, SDL_AsyncIOOutcome *b),(a,b),return)
SDL_DYNAPI_PROC(bool,SDL_WaitAsyncIOResult,(SDL_AsyncIOQueue *a, SDL_AsyncIOOutcome *b, Sint32 c),(a,b,c),return)
SDL_DYNAPI_PROC(void,SDL_SignalAsyncIOQueue,(SDL_AsyncIOQueue *a),(a),)
SDL_DYNAPI_PROC(SDL_AsyncIOTask*,SDL_LoadFileAsync,(const char *a, SDL_AsyncIOQueue *b, void *c),(a,b,c),return)

335
src/file/SDL_asyncio.c Normal file
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;
}
bool SDL_GetAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome)
{
if (!queue || !outcome) {
return false;
}
return GetAsyncIOTaskOutcome(queue->iface.get_results(queue->userdata), outcome);
}
bool SDL_WaitAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome, Sint32 timeoutMS)
{
if (!queue || !outcome) {
return false;
}
return GetAsyncIOTaskOutcome(queue->iface.wait_results(queue->userdata, timeoutMS), outcome);
}
void SDL_SignalAsyncIOQueue(SDL_AsyncIOQueue *queue)
{
if (queue) {
queue->iface.signal(queue->userdata);
}
}
void SDL_DestroyAsyncIOQueue(SDL_AsyncIOQueue *queue)
{
if (queue) {
// block until any pending tasks complete.
while (SDL_GetAtomicInt(&queue->tasks_inflight) > 0) {
SDL_AsyncIOTask *task = queue->iface.wait_results(queue->userdata, -1);
if (task) {
if (task->asyncio->oneshot) {
SDL_free(task->buffer); // throw away the buffer from SDL_LoadFileAsync that will never be consumed/freed by app.
task->buffer = NULL;
}
SDL_AsyncIOOutcome outcome;
GetAsyncIOTaskOutcome(task, &outcome); // this frees the task, and does other upkeep.
}
}
queue->iface.destroy(queue->userdata);
SDL_free(queue);
}
}
void SDL_QuitAsyncIO(void)
{
SDL_SYS_QuitAsyncIO();
}
SDL_AsyncIOTask *SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata)
{
if (!file) {
SDL_InvalidParamError("file");
return NULL;
} else if (!queue) {
SDL_InvalidParamError("queue");
return NULL;
}
SDL_AsyncIOTask *task = NULL;
SDL_AsyncIO *asyncio = SDL_AsyncIOFromFile(file, "r");
if (asyncio) {
asyncio->oneshot = true;
void *ptr = NULL;
const Sint64 flen = SDL_GetAsyncIOSize(asyncio);
if (flen >= 0) {
// !!! FIXME: check if flen > address space, since it'll truncate and we'll just end up with an incomplete buffer or a crash.
ptr = SDL_malloc((size_t) (flen + 1)); // over-allocate by one so we can add a null-terminator.
if (ptr) {
task = SDL_ReadAsyncIO(asyncio, ptr, 0, (Uint64) flen, queue, userdata);
}
}
if (!task) {
SDL_free(ptr);
}
SDL_CloseAsyncIO(asyncio, queue, userdata); // if this fails, we'll have a resource leak, but this would already be a dramatic system failure.
}
return task;
}

30
src/file/SDL_asyncio_c.h Normal file
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_

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

@@ -0,0 +1,133 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_sysasyncio_h_
#define SDL_sysasyncio_h_
// If your platform has an option other than the "generic" code, make sure this
// is #defined to 0 instead and implement the SDL_SYS_* functions below in your
// backend (having them maybe call into the SDL_SYS_*_Generic versions as a
// fallback if the platform has functionality that isn't always available).
#define SDL_ASYNCIO_ONLY_HAVE_GENERIC 1
// this entire thing is just juggling doubly-linked lists, so make some helper macros.
#define LINKED_LIST_DECLARE_FIELDS(type, prefix) \
type *prefix##prev; \
type *prefix##next
#define LINKED_LIST_PREPEND(item, list, prefix) do { \
item->prefix##prev = &list; \
item->prefix##next = list.prefix##next; \
if (item->prefix##next) { \
item->prefix##next->prefix##prev = item; \
} \
list.prefix##next = item; \
} while (false)
#define LINKED_LIST_UNLINK(item, prefix) do { \
if (item->prefix##next) { \
item->prefix##next->prefix##prev = item->prefix##prev; \
} \
item->prefix##prev->prefix##next = task->prefix##next; \
item->prefix##prev = item->prefix##next = NULL; \
} while (false)
#define LINKED_LIST_START(list, prefix) (list.prefix##next)
#define LINKED_LIST_NEXT(item, prefix) (item->prefix##next)
#define LINKED_LIST_PREV(item, prefix) (item->prefix##prev)
typedef struct SDL_AsyncIOTask SDL_AsyncIOTask;
struct SDL_AsyncIOTask
{
SDL_AsyncIO *asyncio;
SDL_AsyncIOTaskType type;
SDL_AsyncIOQueue *queue;
Uint64 offset;
void *buffer;
char *error;
SDL_AsyncIOResult result;
Uint64 requested_size;
Uint64 result_size;
void *app_userdata;
LINKED_LIST_DECLARE_FIELDS(struct SDL_AsyncIOTask, asyncio);
LINKED_LIST_DECLARE_FIELDS(struct SDL_AsyncIOTask, queue); // the generic backend uses this, so I've added it here to avoid the extra allocation.
LINKED_LIST_DECLARE_FIELDS(struct SDL_AsyncIOTask, threadpool); // the generic backend uses this, so I've added it here to avoid the extra allocation.
};
typedef struct SDL_AsyncIOQueueInterface
{
bool (*queue_task)(void *userdata, SDL_AsyncIOTask *task);
void (*cancel_task)(void *userdata, SDL_AsyncIOTask *task);
SDL_AsyncIOTask * (*get_results)(void *userdata);
SDL_AsyncIOTask * (*wait_results)(void *userdata, Sint32 timeoutMS);
void (*signal)(void *userdata);
void (*destroy)(void *userdata);
} SDL_AsyncIOQueueInterface;
struct SDL_AsyncIOQueue
{
SDL_AsyncIOQueueInterface iface;
void *userdata;
SDL_AtomicInt tasks_inflight;
};
// this interface is kept per-object, even though generally it's going to decide
// on a single interface that is the same for the entire process, but I've kept
// the abstraction in case we start exposing more types of async i/o, like
// sockets, in the future.
typedef struct SDL_AsyncIOInterface
{
Sint64 (*size)(void *userdata);
bool (*read)(void *userdata, SDL_AsyncIOTask *task);
bool (*write)(void *userdata, SDL_AsyncIOTask *task);
bool (*close)(void *userdata, SDL_AsyncIOTask *task);
void (*destroy)(void *userdata);
} SDL_AsyncIOInterface;
struct SDL_AsyncIO
{
SDL_AsyncIOInterface iface;
void *userdata;
SDL_Mutex *lock;
SDL_AsyncIOTask tasks;
SDL_AsyncIOTask *closing; // The close task, which isn't queued until all pending work for this file is done.
bool oneshot; // true if this is a SDL_LoadFileAsync open.
};
// This is implemented for various platforms; param validation is done before calling this. Open file, fill in iface and userdata.
extern bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio);
// This is implemented for various platforms. Call SDL_OpenAsyncIOQueue from in here.
extern bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue);
// This is called during SDL_QuitAsyncIO, after all tasks have completed and all files are closed, to let the platform clean up global backend details.
extern void SDL_SYS_QuitAsyncIO(void);
// the "generic" version is always available, since it is almost always needed as a fallback even on platforms that might offer something better.
extern bool SDL_SYS_AsyncIOFromFile_Generic(const char *file, const char *mode, SDL_AsyncIO *asyncio);
extern bool SDL_SYS_CreateAsyncIOQueue_Generic(SDL_AsyncIOQueue *queue);
extern void SDL_SYS_QuitAsyncIO_Generic(void);
#endif

View File

@@ -0,0 +1,460 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
// The generic backend uses a threadpool to block on synchronous i/o.
// This is not ideal, it's meant to be used if there isn't a platform-specific
// backend that can do something more efficient!
#include "SDL_internal.h"
#include "../SDL_sysasyncio.h"
// on Emscripten without threads, async i/o is synchronous. Sorry. Almost
// everything is MEMFS, so it's just a memcpy anyhow, and the Emscripten
// filesystem APIs don't offer async. In theory, directly accessing
// persistent storage _does_ offer async APIs at the browser level, but
// that's not exposed in Emscripten's filesystem abstraction.
#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
#define SDL_ASYNCIO_USE_THREADPOOL 0
#else
#define SDL_ASYNCIO_USE_THREADPOOL 1
#endif
typedef struct GenericAsyncIOQueueData
{
SDL_Mutex *lock;
SDL_Condition *condition;
SDL_AsyncIOTask completed_tasks;
} GenericAsyncIOQueueData;
typedef struct GenericAsyncIOData
{
SDL_Mutex *lock; // !!! FIXME: we can skip this lock if we have an equivalent of pread/pwrite
SDL_IOStream *io;
} GenericAsyncIOData;
static void AsyncIOTaskComplete(SDL_AsyncIOTask *task)
{
SDL_assert(task->queue);
GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) task->queue->userdata;
SDL_LockMutex(data->lock);
LINKED_LIST_PREPEND(task, data->completed_tasks, queue);
SDL_SignalCondition(data->condition); // wake a thread waiting on the queue.
SDL_UnlockMutex(data->lock);
}
// synchronous i/o is offloaded onto the threadpool. This function does the threaded work.
// This is called directly, without a threadpool, if !SDL_ASYNCIO_USE_THREADPOOL.
static void SynchronousIO(SDL_AsyncIOTask *task)
{
SDL_assert(task->result != SDL_ASYNCIO_CANCELLED); // shouldn't have gotten in here if cancelled!
GenericAsyncIOData *data = (GenericAsyncIOData *) task->asyncio->userdata;
SDL_IOStream *io = data->io;
const size_t size = (size_t) task->requested_size;
void *ptr = task->buffer;
// this seek won't work if two tasks are reading from the same file at the same time,
// so we lock here. This makes multiple reads from a single file serialize, but different
// files will still run in parallel. An app can also open the same file twice to avoid this.
SDL_LockMutex(data->lock);
if (task->type == SDL_ASYNCIO_TASK_CLOSE) {
task->result = SDL_CloseIO(data->io) ? SDL_ASYNCIO_COMPLETE : SDL_ASYNCIO_FAILURE;
} else if (SDL_SeekIO(io, (Sint64) task->offset, SDL_IO_SEEK_SET) < 0) {
task->result = SDL_ASYNCIO_FAILURE;
} else {
const bool writing = (task->type == SDL_ASYNCIO_TASK_WRITE);
task->result_size = (Uint64) (writing ? SDL_WriteIO(io, ptr, size) : SDL_ReadIO(io, ptr, size));
if (task->result_size == task->requested_size) {
task->result = SDL_ASYNCIO_COMPLETE;
} else {
if (writing) {
task->result = SDL_ASYNCIO_FAILURE; // it's always a failure on short writes.
} else {
const SDL_IOStatus status = SDL_GetIOStatus(io);
SDL_assert(status != SDL_IO_STATUS_READY); // this should have either failed or been EOF.
SDL_assert(status != SDL_IO_STATUS_NOT_READY); // these should not be non-blocking reads!
task->result = (status == SDL_IO_STATUS_EOF) ? SDL_ASYNCIO_COMPLETE : SDL_ASYNCIO_FAILURE;
}
}
}
SDL_UnlockMutex(data->lock);
AsyncIOTaskComplete(task);
}
#if SDL_ASYNCIO_USE_THREADPOOL
static SDL_InitState threadpool_init;
static SDL_Mutex *threadpool_lock = NULL;
static bool stop_threadpool = false;
static SDL_AsyncIOTask threadpool_tasks;
static SDL_Condition *threadpool_condition = NULL;
static int max_threadpool_threads = 0;
static int running_threadpool_threads = 0;
static int idle_threadpool_threads = 0;
static int threadpool_threads_spun = 0;
static int SDLCALL AsyncIOThreadpoolWorker(void *data)
{
SDL_LockMutex(threadpool_lock);
while (!stop_threadpool) {
SDL_AsyncIOTask *task = LINKED_LIST_START(threadpool_tasks, threadpool);
if (!task) {
// if we go 30 seconds without a new task, terminate unless we're the only thread left.
idle_threadpool_threads++;
const bool rc = SDL_WaitConditionTimeout(threadpool_condition, threadpool_lock, 30000);
idle_threadpool_threads--;
if (!rc) {
// decide if we have too many idle threads, and if so, quit to let thread pool shrink when not busy.
if (idle_threadpool_threads) {
break;
}
}
continue;
}
LINKED_LIST_UNLINK(task, threadpool);
SDL_UnlockMutex(threadpool_lock);
// bookkeeping is done, so we drop the mutex and fire the work.
SynchronousIO(task);
SDL_LockMutex(threadpool_lock); // take the lock again and see if there's another task (if not, we'll wait on the Condition).
}
running_threadpool_threads--;
// this is kind of a hack, but this lets us reuse threadpool_condition to block on shutdown until all threads have exited.
if (stop_threadpool) {
SDL_BroadcastCondition(threadpool_condition);
}
SDL_UnlockMutex(threadpool_lock);
return 0;
}
static bool MaybeSpinNewWorkerThread(void)
{
// if all existing threads are busy and the pool of threads isn't maxed out, make a new one.
if ((idle_threadpool_threads == 0) && (running_threadpool_threads < max_threadpool_threads)) {
char threadname[32];
SDL_snprintf(threadname, sizeof (threadname), "SDLasyncio%d", threadpool_threads_spun);
SDL_Thread *thread = SDL_CreateThread(AsyncIOThreadpoolWorker, threadname, NULL);
if (thread == NULL) {
return false;
}
SDL_DetachThread(thread); // these terminate themselves when idle too long, so we never WaitThread.
running_threadpool_threads++;
threadpool_threads_spun++;
}
return true;
}
static void QueueAsyncIOTask(SDL_AsyncIOTask *task)
{
SDL_assert(task != NULL);
SDL_LockMutex(threadpool_lock);
if (stop_threadpool) { // just in case.
task->result = SDL_ASYNCIO_CANCELLED;
AsyncIOTaskComplete(task);
} else {
LINKED_LIST_PREPEND(task, threadpool_tasks, threadpool);
MaybeSpinNewWorkerThread(); // okay if this fails or the thread pool is maxed out. Something will get there eventually.
// tell idle threads to get to work.
// This is a broadcast because we want someone from the thread pool to wake up, but
// also shutdown might also be blocking on this. One of the threads will grab
// it, the others will go back to sleep.
SDL_BroadcastCondition(threadpool_condition);
}
SDL_UnlockMutex(threadpool_lock);
}
// We don't initialize async i/o at all until it's used, so
// JUST IN CASE two things try to start at the same time,
// this will make sure everything gets the same mutex.
static bool PrepareThreadpool(void)
{
bool okay = true;
if (SDL_ShouldInit(&threadpool_init)) {
max_threadpool_threads = (SDL_GetNumLogicalCPUCores() * 2) + 1; // !!! FIXME: this should probably have a hint to override.
max_threadpool_threads = SDL_clamp(max_threadpool_threads, 1, 8); // 8 is probably more than enough.
okay = (okay && ((threadpool_lock = SDL_CreateMutex()) != NULL));
okay = (okay && ((threadpool_condition = SDL_CreateCondition()) != NULL));
okay = (okay && MaybeSpinNewWorkerThread()); // make sure at least one thread is going, since we'll need it.
if (!okay) {
if (threadpool_condition) {
SDL_DestroyCondition(threadpool_condition);
threadpool_condition = NULL;
}
if (threadpool_lock) {
SDL_DestroyMutex(threadpool_lock);
threadpool_lock = NULL;
}
}
SDL_SetInitialized(&threadpool_init, okay);
}
return okay;
}
static void ShutdownThreadpool(void)
{
if (SDL_ShouldQuit(&threadpool_init)) {
SDL_LockMutex(threadpool_lock);
// cancel anything that's still pending.
SDL_AsyncIOTask *task;
while ((task = LINKED_LIST_START(threadpool_tasks, threadpool)) != NULL) {
LINKED_LIST_UNLINK(task, threadpool);
task->result = SDL_ASYNCIO_CANCELLED;
AsyncIOTaskComplete(task);
}
stop_threadpool = true;
SDL_BroadcastCondition(threadpool_condition); // tell the whole threadpool to wake up and quit.
while (running_threadpool_threads > 0) {
// each threadpool thread will broadcast this condition before it terminates if stop_threadpool is set.
// we can't just join the threads because they are detached, so the thread pool can automatically shrink as necessary.
SDL_WaitCondition(threadpool_condition, threadpool_lock);
}
SDL_UnlockMutex(threadpool_lock);
SDL_DestroyMutex(threadpool_lock);
threadpool_lock = NULL;
SDL_DestroyCondition(threadpool_condition);
threadpool_condition = NULL;
max_threadpool_threads = running_threadpool_threads = idle_threadpool_threads = threadpool_threads_spun = 0;
stop_threadpool = false;
SDL_SetInitialized(&threadpool_init, false);
}
}
#endif
static Sint64 generic_asyncio_size(void *userdata)
{
GenericAsyncIOData *data = (GenericAsyncIOData *) userdata;
return SDL_GetIOSize(data->io);
}
static bool generic_asyncio_io(void *userdata, SDL_AsyncIOTask *task)
{
return task->queue->iface.queue_task(task->queue->userdata, task);
}
static void generic_asyncio_destroy(void *userdata)
{
GenericAsyncIOData *data = (GenericAsyncIOData *) userdata;
SDL_DestroyMutex(data->lock);
SDL_free(data);
}
static bool generic_asyncioqueue_queue_task(void *userdata, SDL_AsyncIOTask *task)
{
#if SDL_ASYNCIO_USE_THREADPOOL
QueueAsyncIOTask(task);
#else
SynchronousIO(task); // oh well. Get a better platform.
#endif
return true;
}
static void generic_asyncioqueue_cancel_task(void *userdata, SDL_AsyncIOTask *task)
{
#if !SDL_ASYNCIO_USE_THREADPOOL // in theory, this was all synchronous and should never call this, but just in case.
task->result = SDL_ASYNCIO_CANCELLED;
AsyncIOTaskComplete(task);
#else
// we can't stop i/o that's in-flight, but we _can_ just refuse to start it if the threadpool hadn't picked it up yet.
SDL_LockMutex(threadpool_lock);
if (LINKED_LIST_PREV(task, threadpool) != NULL) { // still in the queue waiting to be run? Take it out.
LINKED_LIST_UNLINK(task, threadpool);
task->result = SDL_ASYNCIO_CANCELLED;
AsyncIOTaskComplete(task);
}
SDL_UnlockMutex(threadpool_lock);
#endif
}
static SDL_AsyncIOTask *generic_asyncioqueue_get_results(void *userdata)
{
GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata;
SDL_LockMutex(data->lock);
SDL_AsyncIOTask *task = LINKED_LIST_START(data->completed_tasks, queue);
if (task) {
LINKED_LIST_UNLINK(task, queue);
}
SDL_UnlockMutex(data->lock);
return task;
}
static SDL_AsyncIOTask *generic_asyncioqueue_wait_results(void *userdata, Sint32 timeoutMS)
{
GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata;
SDL_LockMutex(data->lock);
SDL_AsyncIOTask *task = LINKED_LIST_START(data->completed_tasks, queue);
if (!task) {
SDL_WaitConditionTimeout(data->condition, data->lock, timeoutMS);
task = LINKED_LIST_START(data->completed_tasks, queue);
}
if (task) {
LINKED_LIST_UNLINK(task, queue);
}
SDL_UnlockMutex(data->lock);
return task;
}
static void generic_asyncioqueue_signal(void *userdata)
{
GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata;
SDL_LockMutex(data->lock);
SDL_BroadcastCondition(data->condition);
SDL_UnlockMutex(data->lock);
}
static void generic_asyncioqueue_destroy(void *userdata)
{
GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata;
SDL_DestroyMutex(data->lock);
SDL_DestroyCondition(data->condition);
SDL_free(data);
}
bool SDL_SYS_CreateAsyncIOQueue_Generic(SDL_AsyncIOQueue *queue)
{
#if SDL_ASYNCIO_USE_THREADPOOL
if (!PrepareThreadpool()) {
return false;
}
#endif
GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) SDL_calloc(1, sizeof (*data));
if (!data) {
return false;
}
data->lock = SDL_CreateMutex();
if (!data->lock) {
SDL_free(data);
return false;
}
data->condition = SDL_CreateCondition();
if (!data->condition) {
SDL_DestroyMutex(data->lock);
SDL_free(data);
return false;
}
static const SDL_AsyncIOQueueInterface SDL_AsyncIOQueue_Generic = {
generic_asyncioqueue_queue_task,
generic_asyncioqueue_cancel_task,
generic_asyncioqueue_get_results,
generic_asyncioqueue_wait_results,
generic_asyncioqueue_signal,
generic_asyncioqueue_destroy
};
SDL_copyp(&queue->iface, &SDL_AsyncIOQueue_Generic);
queue->userdata = data;
return true;
}
bool SDL_SYS_AsyncIOFromFile_Generic(const char *file, const char *mode, SDL_AsyncIO *asyncio)
{
#if SDL_ASYNCIO_USE_THREADPOOL
if (!PrepareThreadpool()) {
return false;
}
#endif
GenericAsyncIOData *data = (GenericAsyncIOData *) SDL_calloc(1, sizeof (*data));
if (!data) {
return false;
}
data->lock = SDL_CreateMutex();
if (!data->lock) {
SDL_free(data);
return false;
}
data->io = SDL_IOFromFile(file, mode);
if (!data->io) {
SDL_DestroyMutex(data->lock);
SDL_free(data);
return false;
}
static const SDL_AsyncIOInterface SDL_AsyncIOFile_Generic = {
generic_asyncio_size,
generic_asyncio_io,
generic_asyncio_io,
generic_asyncio_io,
generic_asyncio_destroy
};
SDL_copyp(&asyncio->iface, &SDL_AsyncIOFile_Generic);
asyncio->userdata = data;
return true;
}
void SDL_SYS_QuitAsyncIO_Generic(void)
{
#if SDL_ASYNCIO_USE_THREADPOOL
ShutdownThreadpool();
#endif
}
#if SDL_ASYNCIO_ONLY_HAVE_GENERIC
bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio)
{
return SDL_SYS_AsyncIOFromFile_Generic(file, mode, asyncio);
}
bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue)
{
return SDL_SYS_CreateAsyncIOQueue_Generic(queue);
}
void SDL_SYS_QuitAsyncIO(void)
{
SDL_SYS_QuitAsyncIO_Generic();
}
#endif

View File

@@ -331,6 +331,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), 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_AppResult result)
{
SDL_DestroyAsyncIOQueue(queue);
SDL_DestroyTexture(texture);
SDLTest_CommonQuit(state);
}