From df0f5deddfd6ebb94a6a0ed828c809d5f5dcdb61 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sun, 17 Mar 2024 17:11:20 -0700 Subject: [PATCH] Added SDL_IOFromDynamicMem() --- include/SDL3/SDL_iostream.h | 31 +++++++-- src/dynapi/SDL_dynapi.sym | 1 + src/dynapi/SDL_dynapi_overrides.h | 1 + src/dynapi/SDL_dynapi_procs.h | 1 + src/file/SDL_iostream.c | 111 ++++++++++++++++++++++++++++-- test/testautomation_iostream.c | 63 +++++++++++++++-- 6 files changed, 192 insertions(+), 16 deletions(-) diff --git a/include/SDL3/SDL_iostream.h b/include/SDL3/SDL_iostream.h index 675d5f79c..0e64e8893 100644 --- a/include/SDL3/SDL_iostream.h +++ b/include/SDL3/SDL_iostream.h @@ -189,8 +189,6 @@ typedef struct SDL_IOStream SDL_IOStream; * * \since This function is available since SDL 3.0.0. * - * \sa SDL_IOFromConstMem - * \sa SDL_IOFromMem * \sa SDL_CloseIO * \sa SDL_ReadIO * \sa SDL_SeekIO @@ -201,7 +199,7 @@ extern DECLSPEC SDL_IOStream *SDLCALL SDL_IOFromFile(const char *file, const cha #define SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER "SDL.iostream.windows.handle" #define SDL_PROP_IOSTREAM_STDIO_HANDLE_POINTER "SDL.iostream.stdio.handle" -#define SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER "SDL.opstream.android.aasset" +#define SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER "SDL.iostream.android.aasset" /** * Use this function to prepare a read-write memory buffer for use with @@ -226,8 +224,6 @@ extern DECLSPEC SDL_IOStream *SDLCALL SDL_IOFromFile(const char *file, const cha * \since This function is available since SDL 3.0.0. * * \sa SDL_IOFromConstMem - * \sa SDL_IOFromFile - * \sa SDL_IOFromMem * \sa SDL_CloseIO * \sa SDL_ReadIO * \sa SDL_SeekIO @@ -260,8 +256,6 @@ extern DECLSPEC SDL_IOStream *SDLCALL SDL_IOFromMem(void *mem, size_t size); * * \since This function is available since SDL 3.0.0. * - * \sa SDL_IOFromConstMem - * \sa SDL_IOFromFile * \sa SDL_IOFromMem * \sa SDL_CloseIO * \sa SDL_ReadIO @@ -270,6 +264,29 @@ extern DECLSPEC SDL_IOStream *SDLCALL SDL_IOFromMem(void *mem, size_t size); */ extern DECLSPEC SDL_IOStream *SDLCALL SDL_IOFromConstMem(const void *mem, size_t size); +/** + * Use this function to create an SDL_IOStream that is backed by dynamically allocated memory. + * + * This supports the following properties to provide access to the memory and control over allocations: + * - `SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER`: a pointer to the internal memory of the stream. This can be set to NULL to transfer ownership of the memory to the application, which should free the memory with SDL_free(). If this is done, the next operation on the stream must be SDL_CloseIO(). + * - `SDL_PROP_IOSTREAM_DYNAMIC_CHUNKSIZE_NUMBER`: memory will be allocated in multiples of this size, defaulting to 1024. + * + * \returns a pointer to a new SDL_IOStream structure, or NULL if it fails; + * call SDL_GetError() for more information. + * + * \since This function is available since SDL 3.0.0. + * + * \sa SDL_CloseIO + * \sa SDL_ReadIO + * \sa SDL_SeekIO + * \sa SDL_TellIO + * \sa SDL_WriteIO + */ +extern DECLSPEC SDL_IOStream *SDLCALL SDL_IOFromDynamicMem(void); + +#define SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER "SDL.iostream.dynamic.memory" +#define SDL_PROP_IOSTREAM_DYNAMIC_CHUNKSIZE_NUMBER "SDL.iostream.dynamic.chunksize" + /* @} *//* IOFrom functions */ diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index eb41c61db..9da8e0c8c 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -1000,6 +1000,7 @@ SDL3_0.0.0 { SDL_RenameStoragePath; SDL_GetStoragePathInfo; SDL_FileTimeFromWindows; + SDL_IOFromDynamicMem; # extra symbols go here (don't modify this line) local: *; }; diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h index 7d69981bf..c0dbb212f 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -1025,3 +1025,4 @@ #define SDL_RenameStoragePath SDL_RenameStoragePath_REAL #define SDL_GetStoragePathInfo SDL_GetStoragePathInfo_REAL #define SDL_FileTimeFromWindows SDL_FileTimeFromWindows_REAL +#define SDL_IOFromDynamicMem SDL_IOFromDynamicMem_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index 4266fea33..ca2434ad0 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1050,3 +1050,4 @@ SDL_DYNAPI_PROC(int,SDL_RemoveStoragePath,(SDL_Storage *a, const char *b),(a,b), SDL_DYNAPI_PROC(int,SDL_RenameStoragePath,(SDL_Storage *a, const char *b, const char *c),(a,b,c),return) SDL_DYNAPI_PROC(int,SDL_GetStoragePathInfo,(SDL_Storage *a, const char *b, SDL_PathInfo *c),(a,b,c),return) SDL_DYNAPI_PROC(SDL_FileTime,SDL_FileTimeFromWindows,(Uint32 a, Uint32 b),(a,b),return) +SDL_DYNAPI_PROC(SDL_IOStream*,SDL_IOFromDynamicMem,(void),(),return) diff --git a/src/file/SDL_iostream.c b/src/file/SDL_iostream.c index 050157efa..5f726ca78 100644 --- a/src/file/SDL_iostream.c +++ b/src/file/SDL_iostream.c @@ -495,17 +495,18 @@ static size_t mem_io(void *userdata, void *dst, const void *src, size_t size) static size_t SDLCALL mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) { - const IOStreamMemData *iodata = (IOStreamMemData *) userdata; + IOStreamMemData *iodata = (IOStreamMemData *) userdata; return mem_io(userdata, ptr, iodata->here, size); } static size_t SDLCALL mem_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status) { - const IOStreamMemData *iodata = (IOStreamMemData *) userdata; + IOStreamMemData *iodata = (IOStreamMemData *) userdata; return mem_io(userdata, iodata->here, ptr, size); } -static int SDLCALL mem_close(void *userdata) { +static int SDLCALL mem_close(void *userdata) +{ SDL_free(userdata); return 0; } @@ -731,6 +732,109 @@ SDL_IOStream *SDL_IOFromConstMem(const void *mem, size_t size) return iostr; } +typedef struct IOStreamDynamicMemData +{ + SDL_IOStream *stream; + IOStreamMemData data; + Uint8 *end; +} IOStreamDynamicMemData; + +static Sint64 SDLCALL dynamic_mem_size(void *userdata) +{ + IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; + return mem_size(&iodata->data); +} + +static Sint64 SDLCALL dynamic_mem_seek(void *userdata, Sint64 offset, int whence) +{ + IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; + return mem_seek(&iodata->data, offset, whence); +} + +static size_t SDLCALL dynamic_mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) +{ + IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; + return mem_io(&iodata->data, ptr, iodata->data.here, size); +} + +static int dynamic_mem_realloc(IOStreamDynamicMemData *iodata, size_t size) +{ + size_t chunksize = (size_t)SDL_GetNumberProperty(SDL_GetIOProperties(iodata->stream), SDL_PROP_IOSTREAM_DYNAMIC_CHUNKSIZE_NUMBER, 0); + if (!chunksize) { + chunksize = 1024; + } + + // We're intentionally allocating more memory than needed so it can be null terminated + size_t chunks = (((iodata->end - iodata->data.base) + size) / chunksize) + 1; + size_t length = (chunks * chunksize); + Uint8 *base = (Uint8 *)SDL_realloc(iodata->data.base, length); + if (!base) { + return -1; + } + + size_t here_offset = (iodata->data.here - iodata->data.base); + size_t stop_offset = (iodata->data.stop - iodata->data.base); + iodata->data.base = base; + iodata->data.here = base + here_offset; + iodata->data.stop = base + stop_offset; + iodata->end = base + length; + return SDL_SetProperty(SDL_GetIOProperties(iodata->stream), SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, base); +} + +static size_t SDLCALL dynamic_mem_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status) +{ + IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; + if (size > (iodata->data.stop - iodata->data.here)) { + if (size > (iodata->end - iodata->data.here)) { + if (dynamic_mem_realloc(iodata, size) < 0) { + return 0; + } + } + iodata->data.stop = iodata->data.here + size; + } + return mem_io(&iodata->data, iodata->data.here, ptr, size); +} + +static int SDLCALL dynamic_mem_close(void *userdata) +{ + const IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; + void *mem = SDL_GetProperty(SDL_GetIOProperties(iodata->stream), SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL); + if (mem) { + SDL_free(mem); + } + SDL_free(userdata); + return 0; +} + +SDL_IOStream *SDL_IOFromDynamicMem(void) +{ + IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) SDL_malloc(sizeof (*iodata)); + if (!iodata) { + return NULL; + } + + SDL_IOStreamInterface iface; + SDL_zero(iface); + iface.size = dynamic_mem_size; + iface.seek = dynamic_mem_seek; + iface.read = dynamic_mem_read; + iface.write = dynamic_mem_write; + iface.close = dynamic_mem_close; + + iodata->data.base = NULL; + iodata->data.here = NULL; + iodata->data.stop = NULL; + iodata->end = NULL; + + SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata); + if (iostr) { + iodata->stream = iostr; + } else { + SDL_free(iodata); + } + return iostr; +} + SDL_IOStatus SDL_GetIOStatus(SDL_IOStream *context) { if (!context) { @@ -740,7 +844,6 @@ SDL_IOStatus SDL_GetIOStatus(SDL_IOStream *context) return context->status; } - SDL_IOStream *SDL_OpenIO(const SDL_IOStreamInterface *iface, void *userdata) { if (!iface) { diff --git a/test/testautomation_iostream.c b/test/testautomation_iostream.c index 8ff135c31..0fe3f8bf5 100644 --- a/test/testautomation_iostream.c +++ b/test/testautomation_iostream.c @@ -312,6 +312,55 @@ static int iostrm_testConstMem(void *arg) return TEST_COMPLETED; } +/** + * Tests dynamic memory + * + * \sa SDL_IOFromDynamicMem + * \sa SDL_CloseIO + */ +static int iostrm_testDynamicMem(void *arg) +{ + SDL_IOStream *rw; + SDL_PropertiesID props; + char *mem; + int result; + + /* Open */ + rw = SDL_IOFromDynamicMem(); + SDLTest_AssertPass("Call to SDL_IOFromDynamicMem() succeeded"); + SDLTest_AssertCheck(rw != NULL, "Verify opening memory with SDL_IOFromDynamicMem does not return NULL"); + + /* Bail out if NULL */ + if (rw == NULL) { + return TEST_ABORTED; + } + + /* Set the chunk size to 1 byte */ + props = SDL_GetIOProperties(rw); + SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_DYNAMIC_CHUNKSIZE_NUMBER, 1); + + /* Run generic tests */ + testGenericIOStreamValidations(rw, SDL_TRUE); + + /* Get the dynamic memory and verify it */ + mem = (char *)SDL_GetProperty(props, SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL); + SDLTest_AssertPass("Call to SDL_GetProperty(props, SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL) succeeded"); + SDLTest_AssertCheck(mem != NULL, "Verify memory value is not NULL"); + mem[SDL_SizeIO(rw)] = '\0'; + SDLTest_AssertCheck(SDL_strcmp(mem, IOStreamHelloWorldTestString) == 0, "Verify memory value is correct"); + + /* Take the memory and free it ourselves */ + SDL_SetProperty(props, SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL); + SDL_free(mem); + + /* Close */ + result = SDL_CloseIO(rw); + SDLTest_AssertPass("Call to SDL_CloseIO() succeeded"); + SDLTest_AssertCheck(result == 0, "Verify result value is 0; got: %d", result); + + return TEST_COMPLETED; +} + /** * Tests reading from file. * @@ -614,29 +663,33 @@ static const SDLTest_TestCaseReference iostrmTest3 = { }; static const SDLTest_TestCaseReference iostrmTest4 = { - (SDLTest_TestCaseFp)iostrm_testFileRead, "iostrm_testFileRead", "Tests reading from a file", TEST_ENABLED + (SDLTest_TestCaseFp)iostrm_testDynamicMem, "iostrm_testDynamicMem", "Tests opening dynamic memory", TEST_ENABLED }; static const SDLTest_TestCaseReference iostrmTest5 = { - (SDLTest_TestCaseFp)iostrm_testFileWrite, "iostrm_testFileWrite", "Test writing to a file", TEST_ENABLED + (SDLTest_TestCaseFp)iostrm_testFileRead, "iostrm_testFileRead", "Tests reading from a file", TEST_ENABLED }; static const SDLTest_TestCaseReference iostrmTest6 = { - (SDLTest_TestCaseFp)iostrm_testAllocFree, "iostrm_testAllocFree", "Test alloc and free of RW context", TEST_ENABLED + (SDLTest_TestCaseFp)iostrm_testFileWrite, "iostrm_testFileWrite", "Test writing to a file", TEST_ENABLED }; static const SDLTest_TestCaseReference iostrmTest7 = { - (SDLTest_TestCaseFp)iostrm_testFileWriteReadEndian, "iostrm_testFileWriteReadEndian", "Test writing and reading via the Endian aware functions", TEST_ENABLED + (SDLTest_TestCaseFp)iostrm_testAllocFree, "iostrm_testAllocFree", "Test alloc and free of RW context", TEST_ENABLED }; static const SDLTest_TestCaseReference iostrmTest8 = { + (SDLTest_TestCaseFp)iostrm_testFileWriteReadEndian, "iostrm_testFileWriteReadEndian", "Test writing and reading via the Endian aware functions", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference iostrmTest9 = { (SDLTest_TestCaseFp)iostrm_testCompareRWFromMemWithRWFromFile, "iostrm_testCompareRWFromMemWithRWFromFile", "Compare RWFromMem and RWFromFile IOStream for read and seek", TEST_ENABLED }; /* Sequence of IOStream test cases */ static const SDLTest_TestCaseReference *iostrmTests[] = { &iostrmTest1, &iostrmTest2, &iostrmTest3, &iostrmTest4, &iostrmTest5, &iostrmTest6, - &iostrmTest7, &iostrmTest8, NULL + &iostrmTest7, &iostrmTest8, &iostrmTest9, NULL }; /* IOStream test suite (global) */