rwlock: Added SDL_rwlock API for shared locks.

This commit is contained in:
Ryan C. Gordon 2023-04-24 01:07:59 -04:00
parent 776820526b
commit e474047ff8
31 changed files with 1244 additions and 20 deletions

View File

@ -2008,11 +2008,14 @@ elseif(WINDOWS)
if(SDL_THREADS)
set(SDL_THREAD_GENERIC_COND_SUFFIX 1)
set(SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1)
set(SDL_THREAD_WINDOWS 1)
list(APPEND SOURCE_FILES
${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c
${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c
${SDL3_SOURCE_DIR}/src/thread/windows/SDL_syscond_cv.c
${SDL3_SOURCE_DIR}/src/thread/windows/SDL_sysmutex.c
${SDL3_SOURCE_DIR}/src/thread/windows/SDL_sysrwlock_srw.c
${SDL3_SOURCE_DIR}/src/thread/windows/SDL_syssem.c
${SDL3_SOURCE_DIR}/src/thread/windows/SDL_systhread.c
${SDL3_SOURCE_DIR}/src/thread/windows/SDL_systls.c)
@ -2597,6 +2600,7 @@ elseif(VITA)
${SDL3_SOURCE_DIR}/src/thread/vita/SDL_syssem.c
${SDL3_SOURCE_DIR}/src/thread/vita/SDL_systhread.c
${SDL3_SOURCE_DIR}/src/thread/vita/SDL_syscond.c
${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c
${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c)
set(HAVE_SDL_THREADS TRUE)
endif()
@ -2732,7 +2736,7 @@ elseif(PSP)
endif()
if(SDL_THREADS)
set(SDL_THREAD_PSP 1)
file(GLOB PSP_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/psp/*.c)
file(GLOB PSP_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c ${SDL3_SOURCE_DIR}/src/thread/psp/*.c)
list(APPEND SOURCE_FILES ${PSP_THREAD_SOURCES})
set(HAVE_SDL_THREADS TRUE)
endif()
@ -2791,7 +2795,7 @@ elseif(PS2)
endif()
if(SDL_THREADS)
set(SDL_THREAD_PS2 1)
file(GLOB PS2_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysmutex.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/ps2/*.c)
file(GLOB PS2_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_syscond.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysmutex.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/ps2/*.c)
list(APPEND SOURCE_FILES ${PS2_THREAD_SOURCES})
set(HAVE_SDL_THREADS TRUE)
endif()
@ -2852,7 +2856,7 @@ elseif(N3DS)
if(SDL_THREADS)
set(SDL_THREAD_N3DS 1)
file(GLOB N3DS_THREAD_SOURCES ${SDL3_SOURCE_DIR}/src/thread/n3ds/*.c)
list(APPEND SOURCE_FILES ${N3DS_THREAD_SOURCES} ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c)
list(APPEND SOURCE_FILES ${N3DS_THREAD_SOURCES} ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_systls.c ${SDL3_SOURCE_DIR}/src/thread/generic/SDL_sysrwlock.c)
set(HAVE_SDL_THREADS TRUE)
endif()

View File

@ -1225,6 +1225,9 @@
<ClCompile Include="..\..\src\thread\windows\SDL_sysmutex.c">
<Filter>thread\windows</Filter>
</ClCompile>
<ClCompile Include="..\..\src\thread\windows\SDL_sysrwlock_srw.c">
<Filter>thread\windows</Filter>
</ClCompile>
<ClCompile Include="..\..\src\thread\windows\SDL_syssem.c">
<Filter>thread\windows</Filter>
</ClCompile>
@ -1237,6 +1240,9 @@
<ClCompile Include="..\..\src\thread\generic\SDL_syscond.c">
<Filter>thread\generic</Filter>
</ClCompile>
<ClCompile Include="..\..\src\thread\generic\SDL_sysrwlock.c">
<Filter>thread\generic</Filter>
</ClCompile>
<ClCompile Include="..\..\src\stdlib\SDL_crc16.c">
<Filter>stdlib</Filter>
</ClCompile>

View File

@ -444,6 +444,24 @@
<CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</CompileAsWinRT>
<CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</CompileAsWinRT>
</ClCompile>
<ClCompile Include="..\src\thread\stdcpp\SDL_sysrwlock.cpp">
<PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
<PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
<PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
<PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
<PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
<PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
<PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
<PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
<CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</CompileAsWinRT>
<CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</CompileAsWinRT>
<CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</CompileAsWinRT>
<CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</CompileAsWinRT>
<CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</CompileAsWinRT>
<CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</CompileAsWinRT>
<CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</CompileAsWinRT>
<CompileAsWinRT Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</CompileAsWinRT>
</ClCompile>
<ClCompile Include="..\src\thread\stdcpp\SDL_systhread.cpp">
<PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>
<PrecompiledHeaderOutputFile Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(IntDir)$(TargetName)_cpp.pch</PrecompiledHeaderOutputFile>

View File

@ -678,6 +678,9 @@
<ClCompile Include="..\src\thread\stdcpp\SDL_sysmutex.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\thread\stdcpp\SDL_sysrwlock.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\src\thread\stdcpp\SDL_systhread.cpp">
<Filter>Source Files</Filter>
</ClCompile>

View File

@ -402,6 +402,7 @@
<ClInclude Include="..\..\src\thread\SDL_thread_c.h" />
<ClInclude Include="..\..\src\thread\generic\SDL_syscond_c.h" />
<ClInclude Include="..\..\src\thread\windows\SDL_sysmutex_c.h" />
<ClInclude Include="..\..\src\thread\generic\SDL_sysrwlock_c.h" />
<ClInclude Include="..\..\src\thread\windows\SDL_systhread_c.h" />
<ClInclude Include="..\..\src\timer\SDL_timer_c.h" />
<ClInclude Include="..\..\src\video\dummy\SDL_nullevents_c.h" />
@ -599,9 +600,11 @@
<ClCompile Include="..\..\src\stdlib\SDL_string.c" />
<ClCompile Include="..\..\src\stdlib\SDL_strtokr.c" />
<ClCompile Include="..\..\src\thread\generic\SDL_syscond.c" />
<ClCompile Include="..\..\src\thread\generic\SDL_sysrwlock.c" />
<ClCompile Include="..\..\src\thread\SDL_thread.c" />
<ClCompile Include="..\..\src\thread\windows\SDL_syscond_cv.c" />
<ClCompile Include="..\..\src\thread\windows\SDL_sysmutex.c" />
<ClCompile Include="..\..\src\thread\windows\SDL_sysrwlock_srw.c" />
<ClCompile Include="..\..\src\thread\windows\SDL_syssem.c" />
<ClCompile Include="..\..\src\thread\windows\SDL_systhread.c" />
<ClCompile Include="..\..\src\thread\windows\SDL_systls.c" />

View File

@ -1213,6 +1213,9 @@
<ClCompile Include="..\..\src\thread\windows\SDL_sysmutex.c">
<Filter>thread\windows</Filter>
</ClCompile>
<ClCompile Include="..\..\src\thread\windows\SDL_sysrwlock_srw.c">
<Filter>thread\windows</Filter>
</ClCompile>
<ClCompile Include="..\..\src\thread\windows\SDL_syssem.c">
<Filter>thread\windows</Filter>
</ClCompile>

View File

@ -83,6 +83,15 @@
566E26CF246274CC00718109 /* SDL_syslocale.m in Sources */ = {isa = PBXBuildFile; fileRef = 566E26CC246274CB00718109 /* SDL_syslocale.m */; };
566E26D8246274CC00718109 /* SDL_locale.c in Sources */ = {isa = PBXBuildFile; fileRef = 566E26CD246274CB00718109 /* SDL_locale.c */; };
566E26E1246274CC00718109 /* SDL_syslocale.h in Headers */ = {isa = PBXBuildFile; fileRef = 566E26CE246274CC00718109 /* SDL_syslocale.h */; };
56A2373329F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
56A2373429F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
56A2373529F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
56A2373629F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
56A2373729F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
56A2373829F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
56A2373929F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
56A2373A29F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
56A2373B29F9C113003CCA5F /* SDL_sysrwlock.c in Sources */ = {isa = PBXBuildFile; fileRef = 56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */; };
56C5237F1D8F4985001F2F30 /* CoreAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7381E951D8B69D600B177DD /* CoreAudio.framework */; };
56C523811D8F498C001F2F30 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 00D0D08310675DD9004B05EF /* CoreFoundation.framework */; };
75E0915A241EA924004729E1 /* SDL_virtualjoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = 75E09158241EA924004729E1 /* SDL_virtualjoystick.c */; };
@ -3482,6 +3491,7 @@
566E26CC246274CB00718109 /* SDL_syslocale.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDL_syslocale.m; path = locale/macos/SDL_syslocale.m; sourceTree = "<group>"; };
566E26CD246274CB00718109 /* SDL_locale.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = SDL_locale.c; path = locale/SDL_locale.c; sourceTree = "<group>"; };
566E26CE246274CC00718109 /* SDL_syslocale.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDL_syslocale.h; path = locale/SDL_syslocale.h; sourceTree = "<group>"; };
56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_sysrwlock.c; sourceTree = "<group>"; };
75E09158241EA924004729E1 /* SDL_virtualjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_virtualjoystick.c; sourceTree = "<group>"; };
75E09159241EA924004729E1 /* SDL_virtualjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_virtualjoystick_c.h; sourceTree = "<group>"; };
9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_shield.c; sourceTree = "<group>"; };
@ -4691,6 +4701,7 @@
A7D8A78123E2513E00DCD162 /* pthread */ = {
isa = PBXGroup;
children = (
56A2373229F9C113003CCA5F /* SDL_sysrwlock.c */,
A7D8A78523E2513E00DCD162 /* SDL_syscond.c */,
A7D8A78823E2513E00DCD162 /* SDL_sysmutex_c.h */,
A7D8A78723E2513E00DCD162 /* SDL_sysmutex.c */,
@ -7328,6 +7339,7 @@
A75FCE7023E25AB700529352 /* SDL_hidapi_xboxone.c in Sources */,
A75FCE7123E25AB700529352 /* SDL_blit_auto.c in Sources */,
A75FCE7323E25AB700529352 /* SDL_keyboard.c in Sources */,
56A2373A29F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
A75FCE7523E25AB700529352 /* SDL_rect.c in Sources */,
A75FCE7623E25AB700529352 /* SDL_cocoaopengles.m in Sources */,
A75FCE7723E25AB700529352 /* SDL_qsort.c in Sources */,
@ -7523,6 +7535,7 @@
A75FD02923E25AC700529352 /* SDL_hidapi_xboxone.c in Sources */,
A75FD02A23E25AC700529352 /* SDL_blit_auto.c in Sources */,
A75FD02C23E25AC700529352 /* SDL_keyboard.c in Sources */,
56A2373B29F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
A75FD02E23E25AC700529352 /* SDL_rect.c in Sources */,
A75FD02F23E25AC700529352 /* SDL_cocoaopengles.m in Sources */,
A75FD03023E25AC700529352 /* SDL_qsort.c in Sources */,
@ -7718,6 +7731,7 @@
A769B20423E259AE00872273 /* SDL_hidapi_switch.c in Sources */,
F3984CD525BCC92900374F43 /* SDL_hidapi_stadia.c in Sources */,
A769B20523E259AE00872273 /* SDL_strtokr.c in Sources */,
56A2373829F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
5605720B2473687A00B46B66 /* SDL_syslocale.m in Sources */,
F3820718284F3609004DD584 /* controller_type.c in Sources */,
A769B20623E259AE00872273 /* SDL_clipboardevents.c in Sources */,
@ -7913,6 +7927,7 @@
A7D8BB6A23E2514500DCD162 /* SDL_keyboard.c in Sources */,
A7D8ACE823E2514100DCD162 /* SDL_rect.c in Sources */,
A7D8AE9B23E2514100DCD162 /* SDL_cocoaopengles.m in Sources */,
56A2373429F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
A7D8B96923E2514400DCD162 /* SDL_qsort.c in Sources */,
A7D8B55223E2514300DCD162 /* SDL_hidapi_switch.c in Sources */,
A7D8B96323E2514400DCD162 /* SDL_strtokr.c in Sources */,
@ -8108,6 +8123,7 @@
A7D8BB6B23E2514500DCD162 /* SDL_keyboard.c in Sources */,
A7D8ACE923E2514100DCD162 /* SDL_rect.c in Sources */,
A7D8AE9C23E2514100DCD162 /* SDL_cocoaopengles.m in Sources */,
56A2373529F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
A7D8B96A23E2514400DCD162 /* SDL_qsort.c in Sources */,
A7D8B55323E2514300DCD162 /* SDL_hidapi_switch.c in Sources */,
A7D8B96423E2514400DCD162 /* SDL_strtokr.c in Sources */,
@ -8303,6 +8319,7 @@
A7D8B55523E2514300DCD162 /* SDL_hidapi_switch.c in Sources */,
F3984CD425BCC92900374F43 /* SDL_hidapi_stadia.c in Sources */,
A7D8B96623E2514400DCD162 /* SDL_strtokr.c in Sources */,
56A2373729F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
560572092473687900B46B66 /* SDL_syslocale.m in Sources */,
F3820717284F3609004DD584 /* controller_type.c in Sources */,
A7D8BB7923E2514500DCD162 /* SDL_clipboardevents.c in Sources */,
@ -8469,6 +8486,7 @@
A7D8ADE623E2514100DCD162 /* SDL_blit_0.c in Sources */,
A7D8BB0923E2514500DCD162 /* k_tan.c in Sources */,
A7D8B8A823E2514400DCD162 /* SDL_diskaudio.c in Sources */,
56A2373329F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
566E26CF246274CC00718109 /* SDL_syslocale.m in Sources */,
A7D8AFC023E2514200DCD162 /* SDL_egl.c in Sources */,
A7D8AC3323E2514100DCD162 /* SDL_RLEaccel.c in Sources */,
@ -8663,6 +8681,7 @@
A7D8BBF223E2574800DCD162 /* SDL_uikitevents.m in Sources */,
A7D8BBB923E2560500DCD162 /* SDL_steamcontroller.c in Sources */,
A7D8B8AB23E2514400DCD162 /* SDL_diskaudio.c in Sources */,
56A2373629F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
A7D8AFC323E2514200DCD162 /* SDL_egl.c in Sources */,
A7D8AC3623E2514100DCD162 /* SDL_RLEaccel.c in Sources */,
A7D8BBB423E2514500DCD162 /* SDL_assert.c in Sources */,
@ -8857,6 +8876,7 @@
A7D8ADEB23E2514100DCD162 /* SDL_blit_0.c in Sources */,
A7D8BB0E23E2514500DCD162 /* k_tan.c in Sources */,
A7D8B8AD23E2514400DCD162 /* SDL_diskaudio.c in Sources */,
56A2373929F9C113003CCA5F /* SDL_sysrwlock.c in Sources */,
A7D8AFC523E2514200DCD162 /* SDL_egl.c in Sources */,
A7D8AC3823E2514100DCD162 /* SDL_RLEaccel.c in Sources */,
A7D8BBB623E2514500DCD162 /* SDL_assert.c in Sources */,

View File

@ -860,6 +860,7 @@ macro(CheckPTHREAD)
${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_systhread.c
${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_sysmutex.c # Can be faked, if necessary
${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_syscond.c # Can be faked, if necessary
${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_sysrwlock.c # Can be faked, if necessary
${SDL3_SOURCE_DIR}/src/thread/pthread/SDL_systls.c
)
if(HAVE_PTHREADS_SEM)

View File

@ -203,11 +203,9 @@ extern DECLSPEC int SDLCALL SDL_TryLockMutex(SDL_mutex * mutex) SDL_TRY_ACQUIRE(
* unlock it the same number of times before it is actually made available for
* other threads in the system (this is known as a "recursive mutex").
*
* It is an error to unlock a mutex that has not been locked by the current
* It is illegal to unlock a mutex that has not been locked by the current
* thread, and doing so results in undefined behavior.
*
* It is also an error to unlock a mutex that isn't locked at all.
*
* \param mutex the mutex to unlock.
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
@ -240,6 +238,228 @@ extern DECLSPEC void SDLCALL SDL_DestroyMutex(SDL_mutex * mutex);
/* @} *//* Mutex functions */
/**
* \name Read/write lock functions
*/
/* @{ */
/* The SDL read/write lock structure, defined in SDL_sysrwlock.c */
struct SDL_rwlock;
typedef struct SDL_rwlock SDL_rwlock;
/*
* Synchronization functions which can time out return this value
* if they time out.
*/
#define SDL_RWLOCK_TIMEDOUT SDL_MUTEX_TIMEDOUT
/**
* Create a new read/write lock.
*
* A read/write lock is useful for situations where you have multiple
* threads trying to access a resource that is rarely updated. All threads
* requesting a read-only lock will be allowed to run in parallel; if a
* thread requests a write lock, it will be provided exclusive access.
* This makes it safe for multiple threads to use a resource at the same
* time if they promise not to change it, and when it has to be changed,
* the rwlock will serve as a gateway to make sure those changes can be
* made safely.
*
* In the right situation, a rwlock can be more efficient than a mutex,
* which only lets a single thread proceed at a time, even if it won't be
* modifying the data.
*
* All newly-created read/write locks begin in the _unlocked_ state.
*
* Calls to SDL_LockRWLockForReading() and SDL_LockRWLockForWriting will
* not return while the rwlock is locked _for writing_ by another thread.
* See SDL_TryLockRWLockForReading() and SDL_TryLockRWLockForWriting() to
* attempt to lock without blocking.
*
* SDL read/write locks are only recursive for read-only locks! They
* are not guaranteed to be fair, or provide access in a FIFO manner! They
* are not guaranteed to favor writers. You may not lock a rwlock for
* both read-only and write access at the same time from the same thread
* (so you can't promote your read-only lock to a write lock without
* unlocking first).
*
* \returns the initialized and unlocked read/write lock or NULL on
* failure; call SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_DestroyRWLock
* \sa SDL_LockRWLockForReading
* \sa SDL_TryLockRWLockForReading
* \sa SDL_LockRWLockForWriting
* \sa SDL_TryLockRWLockForWriting
* \sa SDL_UnlockRWLock
*/
extern DECLSPEC SDL_rwlock *SDLCALL SDL_CreateRWLock(void);
/**
* Lock the read/write lock for _read only_ operations.
*
* This will block until the rwlock is available, which is to say it is
* not locked for writing by any other thread. Of all threads waiting to
* lock the rwlock, all may do so at the same time as long as they are
* requesting read-only access; if a thread wants to lock for writing,
* only one may do so at a time, and no other threads, read-only or not,
* may hold the lock at the same time.
*
* It is legal for the owning thread to lock an already-locked rwlock
* for reading. It must unlock it the same number of times before it is
* actually made available for other threads in the system (this is known
* as a "recursive rwlock").
*
* Note that locking for writing is not recursive (this is only available
* to read-only locks).
*
* It is illegal to request a read-only lock from a thread that already
* holds the write lock. Doing so results in undefined behavior. Unlock the
* write lock before requesting a read-only lock. (But, of course, if you
* have the write lock, you don't need further locks to read in any case.)
*
* \param rwlock the read/write lock to lock
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_UnlockRWLock
*/
extern DECLSPEC int SDLCALL SDL_LockRWLockForReading(SDL_rwlock * rwlock) SDL_ACQUIRE_SHARED(rwlock);
/**
* Lock the read/write lock for _write_ operations.
*
* This will block until the rwlock is available, which is to say it is
* not locked for reading or writing by any other thread. Only one thread
* may hold the lock when it requests write access; all other threads,
* whether they also want to write or only want read-only access, must wait
* until the writer thread has released the lock.
*
* It is illegal for the owning thread to lock an already-locked rwlock
* for writing (read-only may be locked recursively, writing can not). Doing
* so results in undefined behavior.
*
* It is illegal to request a write lock from a thread that already holds
* a read-only lock. Doing so results in undefined behavior. Unlock the
* read-only lock before requesting a write lock.
*
* \param rwlock the read/write lock to lock
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_UnlockRWLock
*/
extern DECLSPEC int SDLCALL SDL_LockRWLockForWriting(SDL_rwlock * rwlock) SDL_ACQUIRE(rwlock);
/**
* Try to lock a read/write lock _for reading_ without blocking.
*
* This works just like SDL_LockRWLockForReading(), but if the rwlock is not
* available, then this function returns `SDL_RWLOCK_TIMEDOUT` immediately.
*
* This technique is useful if you need access to a resource but
* don't want to wait for it, and will return to it to try again later.
*
* Trying to lock for read-only access can succeed if other threads are
* holding read-only locks, as this won't prevent access.
*
* \param rwlock the rwlock to try to lock
* \returns 0, `SDL_RWLOCK_TIMEDOUT`, or -1 on error; call SDL_GetError() for
* more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_CreateRWLock
* \sa SDL_DestroyRWLock
* \sa SDL_TryLockRWLockForReading
* \sa SDL_UnlockRWLock
*/
extern DECLSPEC int SDLCALL SDL_TryLockRWLockForReading(SDL_rwlock * rwlock) SDL_TRY_ACQUIRE_SHARED(0, rwlock);
/**
* Try to lock a read/write lock _for writing_ without blocking.
*
* This works just like SDL_LockRWLockForWriting(), but if the rwlock is not available,
* this function returns `SDL_RWLOCK_TIMEDOUT` immediately.
*
* This technique is useful if you need exclusive access to a resource but
* don't want to wait for it, and will return to it to try again later.
*
* It is illegal for the owning thread to lock an already-locked rwlock
* for writing (read-only may be locked recursively, writing can not). Doing
* so results in undefined behavior.
*
* It is illegal to request a write lock from a thread that already holds
* a read-only lock. Doing so results in undefined behavior. Unlock the
* read-only lock before requesting a write lock.
*
* \param rwlock the rwlock to try to lock
* \returns 0, `SDL_RWLOCK_TIMEDOUT`, or -1 on error; call SDL_GetError() for
* more information.
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_CreateRWLock
* \sa SDL_DestroyRWLock
* \sa SDL_TryLockRWLockForWriting
* \sa SDL_UnlockRWLock
*/
extern DECLSPEC int SDLCALL SDL_TryLockRWLockForWriting(SDL_rwlock * rwlock) SDL_TRY_ACQUIRE(0, rwlock);
/**
* Unlock the read/write lock.
*
* Use this function to unlock the rwlock, whether it was locked for read-only
* or write operations.
*
* It is legal for the owning thread to lock an already-locked read-only lock.
* It must unlock it the same number of times before it is actually made
* available for other threads in the system (this is known as a "recursive
* rwlock").
*
* It is illegal to unlock a rwlock that has not been locked by the current
* thread, and doing so results in undefined behavior.
*
* \param rwlock the rwlock to unlock.
* \returns 0 on success or a negative error code on failure; call
* SDL_GetError() for more information.
*
* \since This function is available since SDL 3.0.0.
*/
extern DECLSPEC int SDLCALL SDL_UnlockRWLock(SDL_rwlock * rwlock) SDL_RELEASE_SHARED(rwlock);
/**
* Destroy a read/write lock created with SDL_CreateRWLock().
*
* This function must be called on any read/write lock that is no longer needed.
* Failure to destroy a rwlock will result in a system memory or resource leak. While
* it is safe to destroy a rwlock that is _unlocked_, it is not safe to attempt
* to destroy a locked rwlock, and may result in undefined behavior depending
* on the platform.
*
* \param rwlock the rwlock to destroy
*
* \since This function is available since SDL 3.0.0.
*
* \sa SDL_CreateRWLock
* \sa SDL_LockRWLockForReading
* \sa SDL_LockRWLockForWriting
* \sa SDL_TryLockRWLockForReading
* \sa SDL_TryLockRWLockForWriting
* \sa SDL_UnlockRWLock
*/
extern DECLSPEC void SDLCALL SDL_DestroyRWLock(SDL_rwlock * rwlock);
/* @} *//* Read/write lock functions */
/**
* \name Semaphore functions
*/

View File

@ -348,6 +348,7 @@
/* Enable various threading systems */
#cmakedefine SDL_THREAD_GENERIC_COND_SUFFIX @SDL_THREAD_GENERIC_COND_SUFFIX@
#cmakedefine SDL_THREAD_GENERIC_RWLOCK_SUFFIX @SDL_THREAD_GENERIC_RWLOCK_SUFFIX@
#cmakedefine SDL_THREAD_PTHREAD @SDL_THREAD_PTHREAD@
#cmakedefine SDL_THREAD_PTHREAD_RECURSIVE_MUTEX @SDL_THREAD_PTHREAD_RECURSIVE_MUTEX@
#cmakedefine SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP @SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP@

View File

@ -256,6 +256,7 @@ typedef unsigned int uintptr_t;
/* Enable various threading systems */
#define SDL_THREAD_GENERIC_COND_SUFFIX 1
#define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1
#define SDL_THREAD_WINDOWS 1
/* Enable various timer systems */

View File

@ -196,6 +196,7 @@
/* Enable various threading systems */
#define SDL_THREAD_GENERIC_COND_SUFFIX 1
#define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1
#define SDL_THREAD_WINDOWS 1
/* Enable various timer systems */

View File

@ -184,6 +184,7 @@
/* Enable various threading systems */
#if (NTDDI_VERSION >= NTDDI_WINBLUE)
#define SDL_THREAD_GENERIC_COND_SUFFIX 1
#define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1
#define SDL_THREAD_WINDOWS 1
#else
/* WinRT on Windows 8.0 and Windows Phone 8.0 don't support CreateThread() */

View File

@ -196,6 +196,7 @@
/* Enable various threading systems */
#define SDL_THREAD_GENERIC_COND_SUFFIX 1
#define SDL_THREAD_GENERIC_RWLOCK_SUFFIX 1
#define SDL_THREAD_WINDOWS 1
/* Enable various timer systems */

View File

@ -844,6 +844,13 @@ SDL3_0.0.0 {
SDL_CreateWindowWithPosition;
SDL_GetAudioStreamFormat;
SDL_SetAudioStreamFormat;
SDL_CreateRWLock;
SDL_LockRWLockForReading;
SDL_LockRWLockForWriting;
SDL_TryLockRWLockForReading;
SDL_TryLockRWLockForWriting;
SDL_UnlockRWLock;
SDL_DestroyRWLock;
# extra symbols go here (don't modify this line)
local: *;
};

View File

@ -870,3 +870,10 @@
#define SDL_CreateWindowWithPosition SDL_CreateWindowWithPosition_REAL
#define SDL_GetAudioStreamFormat SDL_GetAudioStreamFormat_REAL
#define SDL_SetAudioStreamFormat SDL_SetAudioStreamFormat_REAL
#define SDL_CreateRWLock SDL_CreateRWLock_REAL
#define SDL_LockRWLockForReading SDL_LockRWLockForReading_REAL
#define SDL_LockRWLockForWriting SDL_LockRWLockForWriting_REAL
#define SDL_TryLockRWLockForReading SDL_TryLockRWLockForReading_REAL
#define SDL_TryLockRWLockForWriting SDL_TryLockRWLockForWriting_REAL
#define SDL_UnlockRWLock SDL_UnlockRWLock_REAL
#define SDL_DestroyRWLock SDL_DestroyRWLock_REAL

View File

@ -915,3 +915,10 @@ SDL_DYNAPI_PROC(SDL_Window*,SDL_GetWindowParent,(SDL_Window *a),(a),return)
SDL_DYNAPI_PROC(SDL_Window*,SDL_CreateWindowWithPosition,(const char *a, int b, int c, int d, int e, Uint32 f),(a,b,c,d,e,f),return)
SDL_DYNAPI_PROC(int,SDL_GetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat *b, int *c, int *d, SDL_AudioFormat *e, int *f, int *g),(a,b,c,d,e,f,g),return)
SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFormat,(SDL_AudioStream *a, SDL_AudioFormat b, int c, int d, SDL_AudioFormat e, int f, int g),(a,b,c,d,e,f,g),return)
SDL_DYNAPI_PROC(SDL_rwlock*,SDL_CreateRWLock,(void),(),return)
SDL_DYNAPI_PROC(int,SDL_LockRWLockForReading,(SDL_rwlock *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_LockRWLockForWriting,(SDL_rwlock *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_TryLockRWLockForReading,(SDL_rwlock *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_TryLockRWLockForWriting,(SDL_rwlock *a),(a),return)
SDL_DYNAPI_PROC(int,SDL_UnlockRWLock,(SDL_rwlock *a),(a),return)
SDL_DYNAPI_PROC(void,SDL_DestroyRWLock,(SDL_rwlock *a),(a),)

View File

@ -159,3 +159,4 @@ int SDL_UnlockMutex(SDL_mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doe
return 0;
#endif /* SDL_THREADS_DISABLED */
}

View File

@ -0,0 +1,185 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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"
/* An implementation of rwlocks using mutexes, condition variables, and atomics. */
#include "SDL_systhread_c.h"
#include "../generic/SDL_sysrwlock_c.h"
/* If two implementations are to be compiled into SDL (the active one
* will be chosen at runtime), the function names need to be
* suffixed
*/
/* !!! FIXME: this is quite a tapdance with macros and the build system, maybe we can simplify how we do this. --ryan. */
#ifndef SDL_THREAD_GENERIC_RWLOCK_SUFFIX
#define SDL_CreateRWLock_generic SDL_CreateRWLock
#define SDL_DestroyRWLock_generic SDL_DestroyRWLock
#define SDL_LockRWLockForReading_generic SDL_LockRWLockForReading
#define SDL_LockRWLockForWriting_generic SDL_LockRWLockForWriting
#define SDL_TryLockRWLockForReading_generic SDL_TryLockRWLockForReading
#define SDL_TryLockRWLockForWriting_generic SDL_TryLockRWLockForWriting
#define SDL_UnlockRWLock_generic SDL_UnlockRWLock
#endif
struct SDL_rwlock
{
SDL_mutex *lock;
SDL_cond *condition;
SDL_threadID writer_thread;
SDL_AtomicInt reader_count;
SDL_AtomicInt writer_count;
};
SDL_rwlock *SDL_CreateRWLock_generic(void)
{
SDL_rwlock *rwlock = (SDL_rwlock *) SDL_malloc(sizeof (*rwlock));
if (!rwlock) {
SDL_OutOfMemory();
return NULL;
}
rwlock->lock = SDL_CreateMutex();
if (!rwlock->lock) {
SDL_free(rwlock);
return NULL;
}
rwlock->condition = SDL_CreateCond();
if (!rwlock->condition) {
SDL_DestroyMutex(rwlock->lock);
SDL_free(rwlock);
return NULL;
}
SDL_AtomicSet(&rwlock->reader_count, 0);
SDL_AtomicSet(&rwlock->writer_count, 0);
return rwlock;
}
void SDL_DestroyRWLock_generic(SDL_rwlock *rwlock)
{
if (rwlock) {
SDL_DestroyMutex(rwlock->lock);
SDL_DestroyCond(rwlock->condition);
SDL_free(rwlock);
}
}
int SDL_LockRWLockForReading_generic(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
if (!rwlock) {
return SDL_InvalidParamError("rwlock");
} else if (SDL_LockMutex(rwlock->lock) == -1) {
return -1;
}
SDL_assert(SDL_AtomicGet(&rwlock->writer_count) == 0); /* shouldn't be able to grab lock if there's a writer! */
SDL_AtomicAdd(&rwlock->reader_count, 1);
SDL_UnlockMutex(rwlock->lock); /* other readers can attempt to share the lock. */
return 0;
}
int SDL_LockRWLockForWriting_generic(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
if (!rwlock) {
return SDL_InvalidParamError("rwlock");
} else if (SDL_LockMutex(rwlock->lock) == -1) {
return -1;
}
while (SDL_AtomicGet(&rwlock->reader_count) > 0) { /* while something is holding the shared lock, keep waiting. */
SDL_CondWait(rwlock->condition, rwlock->lock); /* release the lock and wait for readers holding the shared lock to release it, regrab the lock. */
}
/* we hold the lock! */
SDL_AtomicAdd(&rwlock->writer_count, 1); /* we let these be recursive, but the API doesn't require this. It _does_ trust you unlock correctly! */
return 0;
}
int SDL_TryLockRWLockForReading_generic(SDL_rwlock *rwlock)
{
int rc;
if (!rwlock) {
return SDL_InvalidParamError("rwlock");
}
rc = SDL_TryLockMutex(rwlock->lock);
if (rc != 0) {
/* !!! FIXME: there is a small window where a reader has to lock the mutex, and if we hit that, we will return SDL_RWLOCK_TIMEDOUT even though we could have shared the lock. */
return rc;
}
SDL_assert(SDL_AtomicGet(&rwlock->writer_count) == 0); /* shouldn't be able to grab lock if there's a writer! */
SDL_AtomicAdd(&rwlock->reader_count, 1);
SDL_UnlockMutex(rwlock->lock); /* other readers can attempt to share the lock. */
return 0;
}
int SDL_TryLockRWLockForWriting_generic(SDL_rwlock *rwlock)
{
int rc;
if (!rwlock) {
return SDL_InvalidParamError("rwlock");
} else if ((rc = SDL_TryLockMutex(rwlock->lock)) != 0) {
return rc;
}
if (SDL_AtomicGet(&rwlock->reader_count) > 0) { /* a reader is using the shared lock, treat it as unavailable. */
SDL_UnlockMutex(rwlock->lock);
return SDL_RWLOCK_TIMEDOUT;
}
/* we hold the lock! */
SDL_AtomicAdd(&rwlock->writer_count, 1); /* we let these be recursive, but the API doesn't require this. It _does_ trust you unlock correctly! */
return 0;
}
int SDL_UnlockRWLock_generic(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
if (!rwlock) {
return SDL_InvalidParamError("rwlock");
}
SDL_LockMutex(rwlock->lock); /* recursive lock for writers, readers grab lock to make sure things are sane. */
if (SDL_AtomicGet(&rwlock->reader_count) > 0) { /* we're a reader */
SDL_AtomicAdd(&rwlock->reader_count, -1);
SDL_CondBroadcast(rwlock->condition); /* alert any pending writers to attempt to try to grab the lock again. */
} else if (SDL_AtomicGet(&rwlock->writer_count) > 0) { /* we're a writer */
SDL_AtomicAdd(&rwlock->writer_count, -1);
SDL_UnlockMutex(rwlock->lock); /* recursive unlock. */
}
SDL_UnlockMutex(rwlock->lock);
return 0;
}

View File

@ -0,0 +1,38 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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_sysrwlock_c_h_
#define SDL_sysrwlock_c_h_
#ifdef SDL_THREAD_GENERIC_RWLOCK_SUFFIX
SDL_rwlock *SDL_CreateRWLock_generic(void);
void SDL_DestroyRWLock_generic(SDL_rwlock *rwlock);
int SDL_LockRWLockForReading_generic(SDL_rwlock *rwlock);
int SDL_LockRWLockForWriting_generic(SDL_rwlock *rwlock);
int SDL_TryLockRWLockForReading_generic(SDL_rwlock *rwlock);
int SDL_TryLockRWLockForWriting_generic(SDL_rwlock *rwlock);
int SDL_UnlockRWLock_generic(SDL_rwlock *rwlock);
#endif /* SDL_THREAD_GENERIC_RWLOCK_SUFFIX */
#endif /* SDL_sysrwlock_c_h_ */

View File

@ -109,3 +109,4 @@ int SDL_UnlockMutex(SDL_mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doe
return 0;
}

View File

@ -24,6 +24,7 @@
#include <e32std.h>
/* !!! FIXME: Should this be SDL_MUTEX_TIMEDOUT? */
#define SDL_MUTEX_TIMEOUT -2
struct SDL_semaphore

View File

@ -23,20 +23,7 @@
#include <errno.h>
#include <pthread.h>
#if !(defined(SDL_THREAD_PTHREAD_RECURSIVE_MUTEX) || \
defined(SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP))
#define FAKE_RECURSIVE_MUTEX
#endif
struct SDL_mutex
{
pthread_mutex_t id;
#ifdef FAKE_RECURSIVE_MUTEX
int recursive;
pthread_t owner;
#endif
};
#include "SDL_sysmutex_c.h"
SDL_mutex *
SDL_CreateMutex(void)
@ -186,3 +173,4 @@ int SDL_UnlockMutex(SDL_mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doe
return 0;
}

View File

@ -23,9 +23,18 @@
#ifndef SDL_mutex_c_h_
#define SDL_mutex_c_h_
#if !(defined(SDL_THREAD_PTHREAD_RECURSIVE_MUTEX) || \
defined(SDL_THREAD_PTHREAD_RECURSIVE_MUTEX_NP))
#define FAKE_RECURSIVE_MUTEX
#endif
struct SDL_mutex
{
pthread_mutex_t id;
#ifdef FAKE_RECURSIVE_MUTEX
int recursive;
pthread_t owner;
#endif
};
#endif /* SDL_mutex_c_h_ */

View File

@ -0,0 +1,127 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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 <errno.h>
#include <pthread.h>
struct SDL_rwlock
{
pthread_rwlock_t id;
};
SDL_rwlock *
SDL_CreateRWLock(void)
{
SDL_rwlock *rwlock;
/* Allocate the structure */
rwlock = (SDL_rwlock *)SDL_calloc(1, sizeof(*rwlock));
if (rwlock) {
if (pthread_rwlock_init(&rwlock->id, NULL) != 0) {
SDL_SetError("pthread_rwlock_init() failed");
SDL_free(rwlock);
rwlock = NULL;
}
} else {
SDL_OutOfMemory();
}
return rwlock;
}
void SDL_DestroyRWLock(SDL_rwlock *rwlock)
{
if (rwlock) {
pthread_rwlock_destroy(&rwlock->id);
SDL_free(rwlock);
}
}
int SDL_LockRWLockForReading(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
if (rwlock == NULL) {
return SDL_InvalidParamError("rwlock");
} else if (pthread_rwlock_rdlock(&rwlock->id) != 0) {
return SDL_SetError("pthread_rwlock_rdlock() failed");
}
return 0;
}
int SDL_LockRWLockForWriting(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
if (rwlock == NULL) {
return SDL_InvalidParamError("rwlock");
} else if (pthread_rwlock_wrlock(&rwlock->id) != 0) {
return SDL_SetError("pthread_rwlock_wrlock() failed");
}
return 0;
}
int SDL_TryLockRWLockForReading(SDL_rwlock *rwlock)
{
int retval = 0;
if (rwlock == NULL) {
retval = SDL_InvalidParamError("rwlock");
} else {
const int result = pthread_rwlock_tryrdlock(&rwlock->id);
if (result != 0) {
if (result == EBUSY) {
retval = SDL_RWLOCK_TIMEDOUT;
} else {
retval = SDL_SetError("pthread_rwlock_tryrdlock() failed");
}
}
}
return retval;
}
int SDL_TryLockRWLockForWriting(SDL_rwlock *rwlock)
{
int retval = 0;
if (rwlock == NULL) {
retval = SDL_InvalidParamError("rwlock");
} else {
const int result = pthread_rwlock_trywrlock(&rwlock->id);
if (result != 0) {
if (result == EBUSY) {
retval = SDL_RWLOCK_TIMEDOUT;
} else {
retval = SDL_SetError("pthread_rwlock_tryrdlock() failed");
}
}
}
return retval;
}
int SDL_UnlockRWLock(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
if (rwlock == NULL) {
return SDL_InvalidParamError("rwlock");
} else if (pthread_rwlock_unlock(&rwlock->id) != 0) {
return SDL_SetError("pthread_rwlock_unlock() failed");
}
return 0;
}

View File

@ -97,3 +97,4 @@ SDL_UnlockMutex(SDL_mutex *mutex) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't
mutex->cpp_mutex.unlock();
return 0;
}

View File

@ -26,3 +26,4 @@ struct SDL_mutex
{
std::recursive_mutex cpp_mutex;
};

View File

@ -0,0 +1,130 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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 <shared_mutex>
#include <system_error>
#include <Windows.h>
struct SDL_rwlock
{
std::shared_mutex cpp_mutex;
SDL_threadID write_owner;
};
/* Create a rwlock */
extern "C" SDL_rwlock *SDL_CreateRWLock(void)
{
/* Allocate and initialize the rwlock */
try {
SDL_rwlock *rwlock = new SDL_rwlock;
return rwlock;
} catch (std::system_error &ex) {
SDL_SetError("unable to create a C++ rwlock: code=%d; %s", ex.code(), ex.what());
return NULL;
} catch (std::bad_alloc &) {
SDL_OutOfMemory();
return NULL;
}
}
/* Free the rwlock */
extern "C" void SDL_DestroyRWLock(SDL_rwlock *rwlock)
{
if (rwlock != NULL) {
delete rwlock;
}
}
/* Lock the rwlock */
extern "C" int SDL_LockRWLockForReading(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
if (!rwlock) {
return SDL_InvalidParamError("rwlock");
}
try {
rwlock->cpp_mutex.lock_shared();
return 0;
} catch (std::system_error &ex) {
return SDL_SetError("unable to lock a C++ rwlock: code=%d; %s", ex.code(), ex.what());
}
}
/* Lock the rwlock for writing */
extern "C" int SDL_LockRWLockForWriting(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
if (!rwlock) {
return SDL_InvalidParamError("rwlock");
}
try {
rwlock->cpp_mutex.lock();
rwlock->write_owner = SDL_ThreadID();
return 0;
} catch (std::system_error &ex) {
return SDL_SetError("unable to lock a C++ rwlock: code=%d; %s", ex.code(), ex.what());
}
}
/* TryLock the rwlock for reading */
int SDL_TryLockRWLockForReading(SDL_rwlock *rwlock)
{
int retval = 0;
if (!rwlock) {
retval = SDL_InvalidParamError("rwlock");
} else if (rwlock->cpp_mutex.try_lock_shared() == false) {
retval = SDL_RWLOCK_TIMEDOUT;
}
return retval;
}
/* TryLock the rwlock for writing */
int SDL_TryLockRWLockForWriting(SDL_rwlock *rwlock)
{
int retval = 0;
if (!rwlock) {
retval = SDL_InvalidParamError("rwlock");
} else if (rwlock->cpp_mutex.try_lock() == false) {
retval = SDL_RWLOCK_TIMEDOUT;
} else {
rwlock->write_owner = SDL_ThreadID();
}
return retval;
}
/* Unlock the rwlock */
extern "C" int
SDL_UnlockRWLock(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
if (!rwlock) {
return SDL_InvalidParamError("rwlock");
} else if (rwlock->write_owner == SDL_ThreadID()) {
rwlock->write_owner = 0;
rwlock->cpp_mutex.unlock();
} else {
rwlock->cpp_mutex.unlock_shared();
}
return 0;
}

View File

@ -0,0 +1,258 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2023 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"
/**
* Implementation based on Slim Reader/Writer (SRW) Locks for Win 7 and newer.
*/
/* This header makes sure SRWLOCK is actually declared, even on ancient WinSDKs. */
#include "SDL_sysmutex_c.h"
typedef VOID(WINAPI *pfnInitializeSRWLock)(PSRWLOCK);
typedef VOID(WINAPI *pfnReleaseSRWLockShared)(PSRWLOCK);
typedef VOID(WINAPI *pfnAcquireSRWLockShared)(PSRWLOCK);
typedef BOOLEAN(WINAPI *pfnTryAcquireSRWLockShared)(PSRWLOCK);
typedef VOID(WINAPI *pfnReleaseSRWLockExclusive)(PSRWLOCK);
typedef VOID(WINAPI *pfnAcquireSRWLockExclusive)(PSRWLOCK);
typedef BOOLEAN(WINAPI *pfnTryAcquireSRWLockExclusive)(PSRWLOCK);
#ifdef __WINRT__
/* Functions are guaranteed to be available */
#define pTryAcquireSRWLockExclusive TryAcquireSRWLockExclusive
#define pInitializeSRWLock InitializeSRWLock
#define pReleaseSRWLockShared ReleaseSRWLockShared
#define pAcquireSRWLockShared AcquireSRWLockShared
#define pTryAcquireSRWLockShared TryAcquireSRWLockShared
#define pReleaseSRWLockExclusive ReleaseSRWLockExclusive
#define pAcquireSRWLockExclusive AcquireSRWLockExclusive
#define pTryAcquireSRWLockExclusive TryAcquireSRWLockExclusive
#else
static pfnInitializeSRWLock pInitializeSRWLock = NULL;
static pfnReleaseSRWLockShared pReleaseSRWLockShared = NULL;
static pfnAcquireSRWLockShared pAcquireSRWLockShared = NULL;
static pfnTryAcquireSRWLockShared pTryAcquireSRWLockShared = NULL;
static pfnReleaseSRWLockExclusive pReleaseSRWLockExclusive = NULL;
static pfnAcquireSRWLockExclusive pAcquireSRWLockExclusive = NULL;
static pfnTryAcquireSRWLockExclusive pTryAcquireSRWLockExclusive = NULL;
#endif
typedef SDL_rwlock *(*pfnSDL_CreateRWLock)(void);
typedef void (*pfnSDL_DestroyRWLock)(SDL_rwlock *);
typedef int (*pfnSDL_LockRWLockForReading)(SDL_rwlock *);
typedef int (*pfnSDL_LockRWLockForWriting)(SDL_rwlock *);
typedef int (*pfnSDL_TryLockRWLockForReading)(SDL_rwlock *);
typedef int (*pfnSDL_TryLockRWLockForWriting)(SDL_rwlock *);
typedef int (*pfnSDL_UnlockRWLock)(SDL_rwlock *);
typedef struct SDL_rwlock_impl_t
{
pfnSDL_CreateRWLock Create;
pfnSDL_DestroyRWLock Destroy;
pfnSDL_LockRWLockForReading LockForReading;
pfnSDL_LockRWLockForWriting LockForWriting;
pfnSDL_TryLockRWLockForReading TryLockForReading;
pfnSDL_TryLockRWLockForWriting TryLockForWriting;
pfnSDL_UnlockRWLock Unlock;
} SDL_rwlock_impl_t;
/* Implementation will be chosen at runtime based on available Kernel features */
static SDL_rwlock_impl_t SDL_rwlock_impl_active = { 0 };
/* rwlock implementation using Win7+ slim read/write locks (SRWLOCK) */
typedef struct SDL_rwlock_srw
{
SRWLOCK srw;
SDL_threadID write_owner;
} SDL_rwlock_srw;
static SDL_rwlock *SDL_CreateRWLock_srw(void)
{
SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *)SDL_calloc(1, sizeof(*rwlock));
if (rwlock == NULL) {
SDL_OutOfMemory();
}
pInitializeSRWLock(&rwlock->srw);
return (SDL_rwlock *)rwlock;
}
static void SDL_DestroyRWLock_srw(SDL_rwlock *_rwlock)
{
SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock;
if (rwlock) {
/* There are no kernel allocated resources */
SDL_free(rwlock);
}
}
static int SDL_LockRWLockForReading_srw(SDL_rwlock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock;
if (rwlock == NULL) {
return SDL_InvalidParamError("rwlock");
}
pAcquireSRWLockShared(&rwlock->srw);
return 0;
}
static int SDL_LockRWLockForWriting_srw(SDL_rwlock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock;
if (rwlock == NULL) {
return SDL_InvalidParamError("rwlock");
}
pAcquireSRWLockExclusive(&rwlock->srw);
rwlock->write_owner = SDL_ThreadID();
return 0;
}
static int SDL_TryLockRWLockForReading_srw(SDL_rwlock *_rwlock)
{
SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock;
int retval = 0;
if (rwlock == NULL) {
retval = SDL_InvalidParamError("rwlock");
} else {
retval = pTryAcquireSRWLockShared(&rwlock->srw) ? 0 : SDL_RWLOCK_TIMEDOUT;
}
return retval;
}
static int SDL_TryLockRWLockForWriting_srw(SDL_rwlock *_rwlock)
{
SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock;
int retval = 0;
if (rwlock == NULL) {
retval = SDL_InvalidParamError("rwlock");
} else {
retval = pTryAcquireSRWLockExclusive(&rwlock->srw) ? 0 : SDL_RWLOCK_TIMEDOUT;
}
return retval;
}
static int SDL_UnlockRWLock_srw(SDL_rwlock *_rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
SDL_rwlock_srw *rwlock = (SDL_rwlock_srw *) _rwlock;
if (rwlock == NULL) {
return SDL_InvalidParamError("rwlock");
} else if (rwlock->write_owner == SDL_ThreadID()) {
rwlock->write_owner = 0;
pReleaseSRWLockExclusive(&rwlock->srw);
} else {
pReleaseSRWLockShared(&rwlock->srw);
}
return 0;
}
static const SDL_rwlock_impl_t SDL_rwlock_impl_srw = {
&SDL_CreateRWLock_srw,
&SDL_DestroyRWLock_srw,
&SDL_LockRWLockForReading_srw,
&SDL_LockRWLockForWriting_srw,
&SDL_TryLockRWLockForReading_srw,
&SDL_TryLockRWLockForWriting_srw,
&SDL_UnlockRWLock_srw
};
#ifndef __WINRT__
#include "../generic/SDL_sysrwlock_c.h"
/* Generic rwlock implementation using SDL_mutex, SDL_cond, and SDL_AtomicInt */
static const SDL_rwlock_impl_t SDL_rwlock_impl_generic = {
&SDL_CreateRWLock_generic,
&SDL_DestroyRWLock_generic,
&SDL_LockRWLockForReading_generic,
&SDL_LockRWLockForWriting_generic,
&SDL_TryLockRWLockForReading_generic,
&SDL_TryLockRWLockForWriting_generic,
&SDL_UnlockRWLock_generic
};
#endif
SDL_rwlock *SDL_CreateRWLock(void)
{
if (SDL_rwlock_impl_active.Create == NULL) {
const SDL_rwlock_impl_t *impl;
#ifdef __WINRT__
/* Link statically on this platform */
impl = &SDL_rwlock_impl_srw;
#else
/* Default to generic implementation, works with all mutex implementations */
impl = &SDL_rwlock_impl_generic;
{
HMODULE kernel32 = GetModuleHandle(TEXT("kernel32.dll"));
if (kernel32) {
SDL_bool okay = SDL_TRUE;
#define LOOKUP_SRW_SYM(sym) if (okay) { if ((p##sym = (pfn##sym)GetProcAddress(kernel32, #sym)) == NULL) { okay = SDL_FALSE; } }
LOOKUP_SRW_SYM(InitializeSRWLock);
LOOKUP_SRW_SYM(ReleaseSRWLockShared);
LOOKUP_SRW_SYM(AcquireSRWLockShared);
LOOKUP_SRW_SYM(TryAcquireSRWLockShared);
LOOKUP_SRW_SYM(ReleaseSRWLockExclusive);
LOOKUP_SRW_SYM(AcquireSRWLockExclusive);
LOOKUP_SRW_SYM(TryAcquireSRWLockExclusive);
#undef LOOKUP_SRW_SYM
if (okay) {
impl = &SDL_rwlock_impl_srw; /* Use the Windows provided API instead of generic fallback */
}
}
}
#endif
SDL_copyp(&SDL_rwlock_impl_active, impl);
}
return SDL_rwlock_impl_active.Create();
}
void SDL_DestroyRWLock(SDL_rwlock *rwlock)
{
SDL_rwlock_impl_active.Destroy(rwlock);
}
int SDL_LockRWLockForReading(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
return SDL_rwlock_impl_active.LockForReading(rwlock);
}
int SDL_LockRWLockForWriting(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
return SDL_rwlock_impl_active.LockForWriting(rwlock);
}
int SDL_TryLockRWLockForReading(SDL_rwlock *rwlock)
{
return SDL_rwlock_impl_active.TryLockForReading(rwlock);
}
int SDL_TryLockRWLockForWriting(SDL_rwlock *rwlock)
{
return SDL_rwlock_impl_active.TryLockForWriting(rwlock);
}
int SDL_UnlockRWLock(SDL_rwlock *rwlock) SDL_NO_THREAD_SAFETY_ANALYSIS /* clang doesn't know about NULL mutexes */
{
return SDL_rwlock_impl_active.Unlock(rwlock);
}

View File

@ -225,6 +225,7 @@ add_sdl_test_executable(testkeys SOURCES testkeys.c)
add_sdl_test_executable(testloadso SOURCES testloadso.c)
add_sdl_test_executable(testlocale NONINTERACTIVE SOURCES testlocale.c)
add_sdl_test_executable(testlock SOURCES testlock.c)
add_sdl_test_executable(testrwlock SOURCES testrwlock.c)
add_sdl_test_executable(testmouse SOURCES testmouse.c)
add_sdl_test_executable(testoverlay NEEDS_RESOURCES TESTUTILS SOURCES testoverlay.c)

179
test/testrwlock.c Normal file
View File

@ -0,0 +1,179 @@
/*
Copyright (C) 1997-2023 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.
*/
/* Test the thread and rwlock locking functions
Also exercises the system's signal/thread interaction
*/
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
#include <SDL3/SDL_test.h>
static SDL_rwlock *rwlock = NULL;
static SDL_threadID mainthread;
static SDL_AtomicInt doterminate;
static int nb_threads = 6;
static SDL_Thread **threads;
static int worktime = 1000;
static int writerworktime = 100;
static int timeout = 10000;
static SDLTest_CommonState *state;
static void DoWork(const int workticks) /* "Work" */
{
const SDL_threadID tid = SDL_ThreadID();
const SDL_bool is_reader = tid != mainthread;
const char *typestr = is_reader ? "Reader" : "Writer";
int rc;
SDL_Log("%s Thread %lu: ready to work\n", typestr, (unsigned long) tid);
rc = is_reader ? SDL_LockRWLockForReading(rwlock) : SDL_LockRWLockForWriting(rwlock);
if (rc < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s Thread %lu: Couldn't lock rwlock: %s", typestr, (unsigned long) tid, SDL_GetError());
} else {
SDL_Log("%s Thread %lu: start work!\n", typestr, (unsigned long) tid);
SDL_Delay(workticks);
SDL_Log("%s Thread %lu: work done!\n", typestr, (unsigned long) tid);
if (SDL_UnlockRWLock(rwlock) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s Thread %lu: Couldn't unlock rwlock: %s", typestr, (unsigned long) tid, SDL_GetError());
}
/* If this sleep isn't done, then threads may starve */
SDL_Delay(10);
}
}
static int SDLCALL
ReaderRun(void *data)
{
SDL_Log("Reader Thread %lu: starting up", SDL_ThreadID());
while (!SDL_AtomicGet(&doterminate)) {
DoWork(worktime);
}
SDL_Log("Reader Thread %lu: exiting!\n", SDL_ThreadID());
return 0;
}
int main(int argc, char *argv[])
{
int i;
/* Initialize test framework */
state = SDLTest_CommonCreateState(argv, 0);
if (state == NULL) {
return 1;
}
SDL_AtomicSet(&doterminate, 0);
/* Enable standard application logging */
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
/* Parse commandline */
for (i = 1; i < argc;) {
int consumed;
consumed = SDLTest_CommonArg(state, i);
if (!consumed) {
if (SDL_strcmp(argv[i], "--nbthreads") == 0) {
if (argv[i + 1]) {
char *endptr;
nb_threads = SDL_strtol(argv[i + 1], &endptr, 0);
if (endptr != argv[i + 1] && *endptr == '\0' && nb_threads > 0) {
consumed = 2;
}
}
} else if (SDL_strcmp(argv[i], "--worktime") == 0) {
if (argv[i + 1]) {
char *endptr;
worktime = SDL_strtol(argv[i + 1], &endptr, 0);
if (endptr != argv[i + 1] && *endptr == '\0' && worktime > 0) {
consumed = 2;
}
}
} else if (SDL_strcmp(argv[i], "--writerworktime") == 0) {
if (argv[i + 1]) {
char *endptr;
writerworktime = SDL_strtol(argv[i + 1], &endptr, 0);
if (endptr != argv[i + 1] && *endptr == '\0' && writerworktime > 0) {
consumed = 2;
}
}
} else if (SDL_strcmp(argv[i], "--timeout") == 0) {
if (argv[i + 1]) {
char *endptr;
timeout = (Uint64) SDL_strtol(argv[i + 1], &endptr, 0);
if (endptr != argv[i + 1] && *endptr == '\0' && timeout > 0) {
consumed = 2;
}
}
}
}
if (consumed <= 0) {
static const char *options[] = {
"[--nbthreads NB]",
"[--worktime ms]",
"[--writerworktime ms]",
"[--timeout ms]",
NULL,
};
SDLTest_CommonLogUsage(state, argv[0], options);
return 1;
}
i += consumed;
}
threads = SDL_malloc(nb_threads * sizeof(SDL_Thread*));
/* Load the SDL library */
if (SDL_Init(0) < 0) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s\n", SDL_GetError());
return 1;
}
SDL_AtomicSet(&doterminate, 0);
rwlock = SDL_CreateRWLock();
if (rwlock == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create rwlock: %s\n", SDL_GetError());
SDL_Quit();
SDLTest_CommonDestroyState(state);
return 1;
}
mainthread = SDL_ThreadID();
SDL_Log("Writer thread: %lu\n", mainthread);
for (i = 0; i < nb_threads; ++i) {
char name[64];
(void)SDL_snprintf(name, sizeof(name), "Reader%d", i);
threads[i] = SDL_CreateThread(ReaderRun, name, NULL);
if (threads[i] == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create reader thread! %s\n", SDL_GetError());
}
}
while (!SDL_AtomicGet(&doterminate) && (SDL_GetTicks() < ((Uint64) timeout))) {
DoWork(writerworktime);
}
SDL_AtomicSet(&doterminate, 1);
SDL_Log("Waiting on reader threads to terminate...");
for (i = 0; i < nb_threads; ++i) {
SDL_WaitThread(threads[i], NULL);
}
SDL_Log("Reader threads have terminated, quitting!");
SDL_DestroyRWLock(rwlock);
SDLTest_CommonDestroyState(state);
SDL_Quit();
return 0;
}