diff --git a/include/SDL3/SDL_properties.h b/include/SDL3/SDL_properties.h index c5d3daca64..d434ba2e39 100644 --- a/include/SDL3/SDL_properties.h +++ b/include/SDL3/SDL_properties.h @@ -81,6 +81,22 @@ extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetGlobalProperties(void); */ extern DECLSPEC SDL_PropertiesID SDLCALL SDL_CreateProperties(void); +/** + * Copy a set of properties + * + * Copy all the properties from one set of properties to another, with the exception of properties requiring cleanup (set using SDL_SetPropertyWithCleanup()), which will not be copied. Any property that already exists on `dst` will be overwritten. + * + * \param src the properties to copy + * \param dst the destination properties + * \returns 0 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 DECLSPEC int SDLCALL SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst); + /** * Lock a set of properties * diff --git a/src/SDL_properties.c b/src/SDL_properties.c index d59cfc632d..50cb2cd682 100644 --- a/src/SDL_properties.c +++ b/src/SDL_properties.c @@ -188,6 +188,78 @@ error: return 0; } +int SDL_CopyProperties(SDL_PropertiesID src, SDL_PropertiesID dst) +{ + SDL_Properties *src_properties = NULL; + SDL_Properties *dst_properties = NULL; + int result = 0; + + if (!src) { + return SDL_InvalidParamError("src"); + } + if (!dst) { + return SDL_InvalidParamError("dst"); + } + + SDL_LockMutex(SDL_properties_lock); + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)src, (const void **)&src_properties); + SDL_FindInHashTable(SDL_properties, (const void *)(uintptr_t)dst, (const void **)&dst_properties); + SDL_UnlockMutex(SDL_properties_lock); + + if (!src_properties) { + return SDL_InvalidParamError("src"); + } + if (!dst_properties) { + return SDL_InvalidParamError("dst"); + } + + SDL_LockMutex(src_properties->lock); + SDL_LockMutex(dst_properties->lock); + { + void *iter; + const void *key, *value; + + iter = NULL; + while (SDL_IterateHashTable(src_properties->props, &key, &value, &iter)) { + const char *src_name = (const char *)key; + const SDL_Property *src_property = (const SDL_Property *)value; + char *dst_name; + SDL_Property *dst_property; + + if (src_property->cleanup) { + /* Can't copy properties with cleanup functions, we don't know how to duplicate the data */ + continue; + } + + SDL_RemoveFromHashTable(dst_properties->props, src_name); + + dst_name = SDL_strdup(src_name); + if (!dst_name) { + result = -1; + continue; + } + dst_property = (SDL_Property *)SDL_malloc(sizeof(*dst_property)); + if (!dst_property) { + SDL_free(dst_name); + result = -1; + continue; + } + SDL_copyp(dst_property, src_property); + if (src_property->type == SDL_PROPERTY_TYPE_STRING) { + dst_property->value.string_value = SDL_strdup(src_property->value.string_value); + } + if (!SDL_InsertIntoHashTable(dst_properties->props, dst_name, dst_property)) { + SDL_FreePropertyWithCleanup(dst_name, dst_property, NULL, SDL_FALSE); + result = -1; + } + } + } + SDL_UnlockMutex(dst_properties->lock); + SDL_UnlockMutex(src_properties->lock); + + return result; +} + int SDL_LockProperties(SDL_PropertiesID props) { SDL_Properties *properties = NULL; diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym index 1dc5a5405e..631fe95d66 100644 --- a/src/dynapi/SDL_dynapi.sym +++ b/src/dynapi/SDL_dynapi.sym @@ -969,6 +969,7 @@ SDL3_0.0.0 { SDL_SetSurfaceColorspace; SDL_GetSurfaceColorspace; SDL_ConvertSurfaceFormatAndColorspace; + SDL_CopyProperties; # 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 42a2e4846f..4f479e8c15 100644 --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -994,3 +994,4 @@ #define SDL_SetSurfaceColorspace SDL_SetSurfaceColorspace_REAL #define SDL_GetSurfaceColorspace SDL_GetSurfaceColorspace_REAL #define SDL_ConvertSurfaceFormatAndColorspace SDL_ConvertSurfaceFormatAndColorspace_REAL +#define SDL_CopyProperties SDL_CopyProperties_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h index b6221bb51d..2fac10f7fa 100644 --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -1019,3 +1019,4 @@ SDL_DYNAPI_PROC(int,SDL_ConvertPixelsAndColorspace,(int a, int b, Uint32 c, SDL_ SDL_DYNAPI_PROC(int,SDL_SetSurfaceColorspace,(SDL_Surface *a, SDL_Colorspace b),(a,b),return) SDL_DYNAPI_PROC(int,SDL_GetSurfaceColorspace,(SDL_Surface *a, SDL_Colorspace *b),(a,b),return) SDL_DYNAPI_PROC(SDL_Surface*,SDL_ConvertSurfaceFormatAndColorspace,(SDL_Surface *a, Uint32 b, SDL_Colorspace c),(a,b,c),return) +SDL_DYNAPI_PROC(int,SDL_CopyProperties,(SDL_PropertiesID a, SDL_PropertiesID b),(a,b),return) diff --git a/test/testautomation_properties.c b/test/testautomation_properties.c index 1fa53d022c..e825c3b455 100644 --- a/test/testautomation_properties.c +++ b/test/testautomation_properties.c @@ -213,6 +213,67 @@ static int properties_testBasic(void *arg) return TEST_COMPLETED; } +/** + * Test copy functionality + */ +static void SDLCALL copy_cleanup(void *userdata, void *value) +{ +} +static int properties_testCopy(void *arg) +{ + SDL_PropertiesID a, b; + int num; + const char *string; + void *data; + int result; + + a = SDL_CreateProperties(); + SDL_SetNumberProperty(a, "num", 1); + SDL_SetStringProperty(a, "string", "foo"); + SDL_SetProperty(a, "data", &a); + SDL_SetPropertyWithCleanup(a, "cleanup", &a, copy_cleanup, &a); + + b = SDL_CreateProperties(); + SDL_SetNumberProperty(b, "num", 2); + + SDLTest_AssertPass("Call to SDL_CopyProperties(a, 0)"); + result = SDL_CopyProperties(a, 0); + SDLTest_AssertCheck(result == -1, + "SDL_CopyProperties() result, got %d, expected -1", result); + + SDLTest_AssertPass("Call to SDL_CopyProperties(0, b)"); + result = SDL_CopyProperties(0, b); + SDLTest_AssertCheck(result == -1, + "SDL_CopyProperties() result, got %d, expected -1", result); + + SDLTest_AssertPass("Call to SDL_CopyProperties(a, b)"); + result = SDL_CopyProperties(a, b); + SDLTest_AssertCheck(result == 0, + "SDL_CopyProperties() result, got %d, expected 0", result); + + SDL_DestroyProperties(a); + + num = SDL_GetNumberProperty(b, "num", 0); + SDLTest_AssertCheck(num == 1, + "Checking number property, got %d, expected 1", num); + + string = SDL_GetStringProperty(b, "string", NULL); + SDLTest_AssertCheck(string && SDL_strcmp(string, "foo") == 0, + "Checking string property, got \"%s\", expected \"foo\"", string); + + data = SDL_GetProperty(b, "data", NULL); + SDLTest_AssertCheck(data == &a, + "Checking data property, got %p, expected %p", data, &a); + + data = SDL_GetProperty(b, "cleanup", NULL); + SDLTest_AssertCheck(data == NULL, + "Checking cleanup property, got %p, expected NULL", data); + + SDL_DestroyProperties(b); + + return TEST_COMPLETED; +} + /** * Test cleanup functionality */ @@ -324,21 +385,29 @@ static int properties_testLocking(void *arg) /* ================= Test References ================== */ /* Properties test cases */ -static const SDLTest_TestCaseReference propertiesTest1 = { +static const SDLTest_TestCaseReference propertiesTestBasic = { (SDLTest_TestCaseFp)properties_testBasic, "properties_testBasic", "Test basic property functionality", TEST_ENABLED }; -static const SDLTest_TestCaseReference propertiesTest2 = { +static const SDLTest_TestCaseReference propertiesTestCopy = { + (SDLTest_TestCaseFp)properties_testCopy, "properties_testCopy", "Test property copy functionality", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference propertiesTestCleanup = { (SDLTest_TestCaseFp)properties_testCleanup, "properties_testCleanup", "Test property cleanup functionality", TEST_ENABLED }; -static const SDLTest_TestCaseReference propertiesTest3 = { +static const SDLTest_TestCaseReference propertiesTestLocking = { (SDLTest_TestCaseFp)properties_testLocking, "properties_testLocking", "Test property locking functionality", TEST_ENABLED }; /* Sequence of Properties test cases */ static const SDLTest_TestCaseReference *propertiesTests[] = { - &propertiesTest1, &propertiesTest2, &propertiesTest3, NULL + &propertiesTestBasic, + &propertiesTestCopy, + &propertiesTestCleanup, + &propertiesTestLocking, + NULL }; /* Properties test suite (global) */