diff --git a/audit/history.jsonl b/audit/history.jsonl index 953192fb..2911e4bf 100644 --- a/audit/history.jsonl +++ b/audit/history.jsonl @@ -607,3 +607,7 @@ {"date": "2026-09-30", "unit": "EterBase/FileBase.cpp", "action": "all units mapped (FILE* over Win32 HANDLE); stale fstream note corrected", "evidence": "port.utils CRC32/CFileBase/CMappedFile checks"} {"date": "2026-09-30", "unit": "EterBase/MappedFile.cpp", "action": "Map/Unmap ADAPTED (mmap), rest PORTED verbatim; stale ifstream note corrected", "evidence": "port.utils CRC32/CFileBase/CMappedFile checks"} {"date": "2026-09-30", "unit": "EterBase/Debug.cpp", "action": "Log*/Trace*/LogFile*/OpenLogFile PORTED (non-_DEBUG), OpenConsoleWindow N_A", "evidence": "port.debug_log"} +{"date": "2026-09-30", "unit": "EterLib/Mutex.cpp", "action": "all PORTED verbatim over the CRITICAL_SECTION shim", "evidence": "port.file_loader_thread"} +{"date": "2026-09-30", "unit": "EterLib/Thread.cpp", "action": "all PORTED verbatim over new Win32Crt _beginthreadex shim", "evidence": "port.file_loader_thread"} +{"date": "2026-09-30", "unit": "EterLib/FileLoaderThread.cpp", "action": "all PORTED verbatim over new Win32Crt semaphore/thread/WaitForSingleObject/CloseHandle shims", "evidence": "port.file_loader_thread"} +{"date": "2026-09-30", "unit": "EterLib/GrpVertexBufferDynamic.cpp", "action": "all PORTED verbatim (no 40250 instantiation)", "evidence": "build"} diff --git a/audit/port-map/EterLib/FileLoaderThread.cpp.json b/audit/port-map/EterLib/FileLoaderThread.cpp.json new file mode 100644 index 00000000..a93da83a --- /dev/null +++ b/audit/port-map/EterLib/FileLoaderThread.cpp.json @@ -0,0 +1,128 @@ +{ + "reference": "EterLib/FileLoaderThread.cpp", + "reference_sha256": "fe40ffa69088383baec56f2ef3c0b2a70448e018f5661b6aaf6dfb75e4efbf5a", + "priority": "P4", + "contracts": [], + "functions": { + "CFileLoaderThread::CFileLoaderThread": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/FileLoaderThread.cpp:CFileLoaderThread::CFileLoaderThread" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; semaphore/thread/wait calls are the Win32Crt kernel-object shims. 40250 never Creates CResourceManager::ms_loadingThread, so Request logs the ReleaseSemaphore error and Fetch never yields, as tested." + }, + "CFileLoaderThread::~CFileLoaderThread": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/FileLoaderThread.cpp:CFileLoaderThread::~CFileLoaderThread" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; semaphore/thread/wait calls are the Win32Crt kernel-object shims. 40250 never Creates CResourceManager::ms_loadingThread, so Request logs the ReleaseSemaphore error and Fetch never yields, as tested." + }, + "CFileLoaderThread::Create": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/FileLoaderThread.cpp:CFileLoaderThread::Create" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; semaphore/thread/wait calls are the Win32Crt kernel-object shims. 40250 never Creates CResourceManager::ms_loadingThread, so Request logs the ReleaseSemaphore error and Fetch never yields, as tested." + }, + "CFileLoaderThread::Run": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/FileLoaderThread.cpp:CFileLoaderThread::Run" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; semaphore/thread/wait calls are the Win32Crt kernel-object shims. 40250 never Creates CResourceManager::ms_loadingThread, so Request logs the ReleaseSemaphore error and Fetch never yields, as tested." + }, + "CFileLoaderThread::EntryPoint": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/FileLoaderThread.cpp:CFileLoaderThread::EntryPoint" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; semaphore/thread/wait calls are the Win32Crt kernel-object shims. 40250 never Creates CResourceManager::ms_loadingThread, so Request logs the ReleaseSemaphore error and Fetch never yields, as tested." + }, + "CFileLoaderThread::Destroy": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/FileLoaderThread.cpp:CFileLoaderThread::Destroy" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; semaphore/thread/wait calls are the Win32Crt kernel-object shims. 40250 never Creates CResourceManager::ms_loadingThread, so Request logs the ReleaseSemaphore error and Fetch never yields, as tested." + }, + "CFileLoaderThread::Setup": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/FileLoaderThread.cpp:CFileLoaderThread::Setup" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; semaphore/thread/wait calls are the Win32Crt kernel-object shims. 40250 never Creates CResourceManager::ms_loadingThread, so Request logs the ReleaseSemaphore error and Fetch never yields, as tested." + }, + "CFileLoaderThread::Shutdown": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/FileLoaderThread.cpp:CFileLoaderThread::Shutdown" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; semaphore/thread/wait calls are the Win32Crt kernel-object shims. 40250 never Creates CResourceManager::ms_loadingThread, so Request logs the ReleaseSemaphore error and Fetch never yields, as tested." + }, + "CFileLoaderThread::Execute": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/FileLoaderThread.cpp:CFileLoaderThread::Execute" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; semaphore/thread/wait calls are the Win32Crt kernel-object shims. 40250 never Creates CResourceManager::ms_loadingThread, so Request logs the ReleaseSemaphore error and Fetch never yields, as tested." + }, + "CFileLoaderThread::Request": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/FileLoaderThread.cpp:CFileLoaderThread::Request" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; semaphore/thread/wait calls are the Win32Crt kernel-object shims. 40250 never Creates CResourceManager::ms_loadingThread, so Request logs the ReleaseSemaphore error and Fetch never yields, as tested." + }, + "CFileLoaderThread::Fetch": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/FileLoaderThread.cpp:CFileLoaderThread::Fetch" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; semaphore/thread/wait calls are the Win32Crt kernel-object shims. 40250 never Creates CResourceManager::ms_loadingThread, so Request logs the ReleaseSemaphore error and Fetch never yields, as tested." + }, + "CFileLoaderThread::Process": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/FileLoaderThread.cpp:CFileLoaderThread::Process" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; semaphore/thread/wait calls are the Win32Crt kernel-object shims. 40250 never Creates CResourceManager::ms_loadingThread, so Request logs the ReleaseSemaphore error and Fetch never yields, as tested." + } + } +} diff --git a/audit/port-map/EterLib/GrpVertexBufferDynamic.cpp.json b/audit/port-map/EterLib/GrpVertexBufferDynamic.cpp.json new file mode 100644 index 00000000..5170bfe9 --- /dev/null +++ b/audit/port-map/EterLib/GrpVertexBufferDynamic.cpp.json @@ -0,0 +1,29 @@ +{ + "reference": "EterLib/GrpVertexBufferDynamic.cpp", + "reference_sha256": "8bffcb4a24733a2ee4312bf45a690a1f0b46c6290e17867b763f4799055f3d3c", + "priority": "P4", + "contracts": [], + "functions": { + "CDynamicVertexBuffer::Create": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/GrpVertexBufferDynamic.cpp:CDynamicVertexBuffer::Create" + ], + "note": "Verbatim 40250. No 40250 code instantiates CDynamicVertexBuffer." + }, + "CDynamicVertexBuffer::CDynamicVertexBuffer": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/GrpVertexBufferDynamic.cpp:CDynamicVertexBuffer::CDynamicVertexBuffer" + ], + "note": "Verbatim 40250. No 40250 code instantiates CDynamicVertexBuffer." + }, + "CDynamicVertexBuffer::~CDynamicVertexBuffer": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/GrpVertexBufferDynamic.cpp:CDynamicVertexBuffer::~CDynamicVertexBuffer" + ], + "note": "Verbatim 40250. No 40250 code instantiates CDynamicVertexBuffer." + } + } +} diff --git a/audit/port-map/EterLib/Mutex.cpp.json b/audit/port-map/EterLib/Mutex.cpp.json new file mode 100644 index 00000000..c0a617e7 --- /dev/null +++ b/audit/port-map/EterLib/Mutex.cpp.json @@ -0,0 +1,48 @@ +{ + "reference": "EterLib/Mutex.cpp", + "reference_sha256": "b480c0ad1f568caddcb50264b6d704dac9337ee8f7631b0f19002663f36999ff", + "priority": "P4", + "contracts": [], + "functions": { + "Mutex::Mutex": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/Mutex.cpp:Mutex::Mutex" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; CRITICAL_SECTION is the Win32Crt recursive-mutex shim." + }, + "Mutex::~Mutex": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/Mutex.cpp:Mutex::~Mutex" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; CRITICAL_SECTION is the Win32Crt recursive-mutex shim." + }, + "Mutex::Lock": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/Mutex.cpp:Mutex::Lock" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; CRITICAL_SECTION is the Win32Crt recursive-mutex shim." + }, + "Mutex::Unlock": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/Mutex.cpp:Mutex::Unlock" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; CRITICAL_SECTION is the Win32Crt recursive-mutex shim." + } + } +} diff --git a/audit/port-map/EterLib/Thread.cpp.json b/audit/port-map/EterLib/Thread.cpp.json new file mode 100644 index 00000000..fc457a59 --- /dev/null +++ b/audit/port-map/EterLib/Thread.cpp.json @@ -0,0 +1,48 @@ +{ + "reference": "EterLib/Thread.cpp", + "reference_sha256": "b5475b7d5d3ce06b96ab9b534346648896deb6b76f459feabb09c9f4148860b5", + "priority": "P4", + "contracts": [], + "functions": { + "CThread::CThread": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/Thread.cpp:CThread::CThread" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; _beginthreadex/SetThreadPriority are the Win32Crt shims (detached std::thread, waitable handle)." + }, + "CThread::Create": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/Thread.cpp:CThread::Create" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; _beginthreadex/SetThreadPriority are the Win32Crt shims (detached std::thread, waitable handle)." + }, + "CThread::Run": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/Thread.cpp:CThread::Run" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; _beginthreadex/SetThreadPriority are the Win32Crt shims (detached std::thread, waitable handle)." + }, + "CThread::EntryPoint": { + "status": "PORTED", + "impl": [ + "src/platform/EterLib/Thread.cpp:CThread::EntryPoint" + ], + "test": [ + "tests/port/port_file_loader_thread_test.cpp" + ], + "note": "Verbatim 40250; _beginthreadex/SetThreadPriority are the Win32Crt shims (detached std::thread, waitable handle)." + } + } +} diff --git a/src/platform/EterLib/FileLoaderThread.cpp b/src/platform/EterLib/FileLoaderThread.cpp index 4fed4db0..24208fa0 100644 --- a/src/platform/EterLib/FileLoaderThread.cpp +++ b/src/platform/EterLib/FileLoaderThread.cpp @@ -1,72 +1,181 @@ -// Platform skeleton for EterLib/FileLoaderThread.h (40250 EterLib/FileLoaderThread.cpp), generated by platform_stub.py. -// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation. +// 40250 EterLib/FileLoaderThread.cpp, verbatim. The kernel32 calls resolve to the Win32Crt thread/semaphore/critical-section shims. #include "EterLib/StdAfx.h" +#include "EterPack/EterPackManager.h" #include "EterLib/FileLoaderThread.h" +#include "EterLib/ResourceManager.h" -#include "../PlatformStub.h" - -CFileLoaderThread::CFileLoaderThread() +CFileLoaderThread::CFileLoaderThread() : m_bShutdowned(false), m_pArg(NULL), m_hThread(NULL), m_uThreadID(0) { - MT_PLATFORM_STUB(); } CFileLoaderThread::~CFileLoaderThread() { - MT_PLATFORM_STUB(); + Destroy(); } -auto CFileLoaderThread::Create(void *) -> int +int CFileLoaderThread::Create(void * arg) { - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + Arg(arg); + m_hThread = (HANDLE) _beginthreadex(NULL, 0, EntryPoint, this, 0, &m_uThreadID); + + if (!m_hThread) + return false; + + SetThreadPriority(m_hThread, THREAD_PRIORITY_NORMAL); + return true; } -auto CFileLoaderThread::Request(std::string &) -> void +UINT CFileLoaderThread::Run(void * arg) { - MT_PLATFORM_STUB(); + if (!Setup()) + return 0; + + return (Execute(arg)); } -auto CFileLoaderThread::Fetch(TData **) -> bool +/* Static */ +UINT CALLBACK CFileLoaderThread::EntryPoint(void * pThis) { - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + CFileLoaderThread * pThread = (CFileLoaderThread *) pThis; + return pThread->Run(pThread->Arg()); } -auto CFileLoaderThread::Shutdown() -> void +////////////////////////////////////////////////////////////////////////// +void CFileLoaderThread::Destroy() { - MT_PLATFORM_STUB(); + if (m_hSemaphore) + { + CloseHandle(m_hSemaphore); + m_hSemaphore = NULL; + } + + stl_wipe(m_pRequestDeque); + stl_wipe(m_pCompleteDeque); } -auto CFileLoaderThread::EntryPoint(void *) -> UINT +UINT CFileLoaderThread::Setup() { - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + m_hSemaphore = CreateSemaphore(NULL, // no security attributes + 0, // initial count + 65535, // maximum count + NULL); // unnamed semaphore + if (!m_hSemaphore) + return 0; + + return 1; } -auto CFileLoaderThread::Run(void *) -> UINT +void CFileLoaderThread::Shutdown() { - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + if (!m_hSemaphore) + return; + + BOOL bRet; + + m_bShutdowned = true; + + do + { + bRet = ReleaseSemaphore(m_hSemaphore, 1, NULL); + } + while (!bRet); + + WaitForSingleObject(m_hThread, 10000); // 쓰레드가 종료 되기를 10초 기다림 } -auto CFileLoaderThread::Setup() -> UINT +UINT CFileLoaderThread::Execute(void * /*pvArg*/) { - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + while (!m_bShutdowned) + { + DWORD dwWaitResult; + + dwWaitResult = WaitForSingleObject(m_hSemaphore, INFINITE); + + if (m_bShutdowned) + break; + + switch (dwWaitResult) + { + case WAIT_OBJECT_0: + { + Process(); + } + break; + + case WAIT_TIMEOUT: + TraceError("CFileLoaderThread::Execute: Timeout occured while time-out interval is INIFITE"); + break; + } + } + + Destroy(); + return 1; } -auto CFileLoaderThread::Execute(void *) -> UINT +void CFileLoaderThread::Request(std::string & c_rstFileName) // called in main thread { - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + TData * pData = new TData; + + pData->dwSize = 0; + pData->pvBuf = NULL; + pData->stFileName = c_rstFileName; + + m_RequestMutex.Lock(); + m_pRequestDeque.push_back(pData); + m_RequestMutex.Unlock(); + + ++m_iRestSemCount; + + if (!ReleaseSemaphore(m_hSemaphore, m_iRestSemCount, NULL)) + TraceError("CFileLoaderThread::Request: ReleaseSemaphore error"); + + --m_iRestSemCount; } -auto CFileLoaderThread::Destroy() -> void +bool CFileLoaderThread::Fetch(TData ** ppData) // called in main thread { - MT_PLATFORM_STUB(); + m_CompleteMutex.Lock(); + + if (m_pCompleteDeque.empty()) + { + m_CompleteMutex.Unlock(); + return false; + } + + *ppData = m_pCompleteDeque.front(); + m_pCompleteDeque.pop_front(); + + m_CompleteMutex.Unlock(); + return true; } -auto CFileLoaderThread::Process() -> void +void CFileLoaderThread::Process() // called in loader thread { - MT_PLATFORM_STUB(); + m_RequestMutex.Lock(); + + if (m_pRequestDeque.empty()) + { + m_RequestMutex.Unlock(); + return; + } + + TData * pData = m_pRequestDeque.front(); + m_pRequestDeque.pop_front(); + + m_RequestMutex.Unlock(); + + LPCVOID pvBuf; + + if (CEterPackManager::Instance().Get(pData->File, pData->stFileName.c_str(), &pvBuf)) + { + pData->dwSize = pData->File.Size(); + pData->pvBuf = new char [pData->dwSize]; + memcpy(pData->pvBuf, pvBuf, pData->dwSize); + } + + m_CompleteMutex.Lock(); + m_pCompleteDeque.push_back(pData); + m_CompleteMutex.Unlock(); + + Sleep(g_iLoadingDelayTime); } diff --git a/src/platform/EterLib/GrpVertexBufferDynamic.cpp b/src/platform/EterLib/GrpVertexBufferDynamic.cpp index 13c6ef57..f8d0de9e 100644 --- a/src/platform/EterLib/GrpVertexBufferDynamic.cpp +++ b/src/platform/EterLib/GrpVertexBufferDynamic.cpp @@ -1,22 +1,30 @@ -// Platform skeleton for EterLib/GrpVertexBufferDynamic.h (40250 EterLib/GrpVertexBufferDynamic.cpp), generated by platform_stub.py. -// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation. +// 40250 EterLib/GrpVertexBufferDynamic.cpp, verbatim. #include "EterLib/StdAfx.h" #include "EterLib/GrpVertexBufferDynamic.h" -#include "../PlatformStub.h" +bool CDynamicVertexBuffer::Create(int vtxCount, int fvf) +{ + if (m_lpd3dVB) + { + if (m_fvf == fvf) + { + if (m_vtxCount >= vtxCount) + return true; + } + } + + m_vtxCount = vtxCount; + m_fvf = fvf; + + return CGraphicVertexBuffer::Create(m_vtxCount, m_fvf, D3DUSAGE_DYNAMIC, D3DPOOL_SYSTEMMEM); +} CDynamicVertexBuffer::CDynamicVertexBuffer() { - MT_PLATFORM_STUB(); + m_vtxCount = 0; + m_fvf = 0; } CDynamicVertexBuffer::~CDynamicVertexBuffer() { - MT_PLATFORM_STUB(); -} - -auto CDynamicVertexBuffer::Create(int, int) -> bool -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); } diff --git a/src/platform/EterLib/Mutex.cpp b/src/platform/EterLib/Mutex.cpp index d564f815..66400521 100644 --- a/src/platform/EterLib/Mutex.cpp +++ b/src/platform/EterLib/Mutex.cpp @@ -1,32 +1,23 @@ -// Platform skeleton for EterLib/Mutex.h (40250 EterLib/Mutex.cpp), generated by platform_stub.py. -// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation. +// 40250 EterLib/Mutex.cpp, verbatim. The kernel32 calls resolve to the Win32Crt thread/semaphore/critical-section shims. #include "EterLib/StdAfx.h" #include "EterLib/Mutex.h" -#include "../PlatformStub.h" - Mutex::Mutex() { - MT_PLATFORM_STUB(); + InitializeCriticalSection(&lock); } Mutex::~Mutex() { - MT_PLATFORM_STUB(); + DeleteCriticalSection(&lock); } -auto Mutex::Lock() -> void +void Mutex::Lock() { - MT_PLATFORM_STUB(); + EnterCriticalSection(&lock); } -auto Mutex::Unlock() -> void +void Mutex::Unlock() { - MT_PLATFORM_STUB(); -} - -auto Mutex::Trylock() -> bool -{ - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + LeaveCriticalSection(&lock); } diff --git a/src/platform/EterLib/Thread.cpp b/src/platform/EterLib/Thread.cpp index 82787dd5..a128939a 100644 --- a/src/platform/EterLib/Thread.cpp +++ b/src/platform/EterLib/Thread.cpp @@ -1,29 +1,34 @@ -// Platform skeleton for EterLib/Thread.h (40250 EterLib/Thread.cpp), generated by platform_stub.py. -// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation. +// 40250 EterLib/Thread.cpp, verbatim. The kernel32 calls resolve to the Win32Crt thread/semaphore/critical-section shims. #include "EterLib/StdAfx.h" #include "EterLib/Thread.h" -#include "../PlatformStub.h" - -CThread::CThread() +CThread::CThread() : m_pArg(NULL), m_hThread(NULL), m_uThreadID(0) { - MT_PLATFORM_STUB(); } -auto CThread::Create(void *) -> int +int CThread::Create(void * arg) { - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + Arg(arg); + m_hThread = (HANDLE) _beginthreadex(NULL, 0, EntryPoint, this, 0, &m_uThreadID); + + if (!m_hThread) + return false; + + SetThreadPriority(m_hThread, THREAD_PRIORITY_NORMAL); + return true; } -auto CThread::EntryPoint(void *) -> UINT +UINT CThread::Run(void * arg) { - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + if (!Setup()) + return 0; + + return (Execute(arg)); } -auto CThread::Run(void *) -> UINT +/* Static */ +UINT CALLBACK CThread::EntryPoint(void * pThis) { - MT_PLATFORM_STUB(); - return mt_platform_stub_return(); + CThread * pThread = (CThread *) pThis; + return pThread->Run(pThread->Arg()); } diff --git a/src/port/CMakeLists.txt b/src/port/CMakeLists.txt index 076a4799..4337a038 100644 --- a/src/port/CMakeLists.txt +++ b/src/port/CMakeLists.txt @@ -213,6 +213,9 @@ if(BUILD_TESTING AND CMAKE_SYSTEM_NAME STREQUAL CMAKE_HOST_SYSTEM_NAME) add_executable(port_screenshot_test ${PROJECT_SOURCE_DIR}/tests/port/port_screenshot_test.cpp) target_link_libraries(port_screenshot_test PRIVATE port_platform) add_test(NAME port.screenshot COMMAND $) + add_executable(port_file_loader_thread_test ${PROJECT_SOURCE_DIR}/tests/port/port_file_loader_thread_test.cpp) + target_link_libraries(port_file_loader_thread_test PRIVATE port_platform) + add_test(NAME port.file_loader_thread COMMAND $) # EterBase/Debug.cpp: syserr.txt / log.txt (redirects its own stderr). add_executable(port_debug_log_test ${PROJECT_SOURCE_DIR}/tests/port/port_debug_log_test.cpp) diff --git a/src/port/common/Win32Crt.cpp b/src/port/common/Win32Crt.cpp index ba1f2499..aa1d53e7 100644 --- a/src/port/common/Win32Crt.cpp +++ b/src/port/common/Win32Crt.cpp @@ -3,6 +3,10 @@ #if !defined(_WIN32) #include #include +#include +#include +#include +#include #include #include #include @@ -170,6 +174,138 @@ void Sleep(DWORD milliseconds) std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); } +// A HANDLE from CreateSemaphore or _beginthreadex points at one of these. The waitable state is shared +// with a running thread, so CloseHandle on a live thread only drops the handle, as on Win32. +namespace +{ +struct KernelObject +{ + virtual ~KernelObject() = default; + virtual DWORD Wait(DWORD milliseconds) = 0; +}; + +template +DWORD wait_until(std::unique_lock& lock, std::condition_variable& cv, DWORD milliseconds, Ready ready) +{ + if (milliseconds == INFINITE) + cv.wait(lock, ready); + else if (!cv.wait_for(lock, std::chrono::milliseconds(milliseconds), ready)) + return WAIT_TIMEOUT; + return WAIT_OBJECT_0; +} + +struct Semaphore final : KernelObject +{ + std::mutex mutex; + std::condition_variable cv; + LONG count = 0; + LONG maximum = 0; + + DWORD Wait(DWORD milliseconds) override + { + std::unique_lock lock(mutex); + const DWORD result = wait_until(lock, cv, milliseconds, [this] { return count > 0; }); + if (result == WAIT_OBJECT_0) + --count; + return result; + } +}; + +struct ThreadState +{ + std::mutex mutex; + std::condition_variable cv; + bool done = false; +}; + +struct Thread final : KernelObject +{ + std::shared_ptr state = std::make_shared(); + + DWORD Wait(DWORD milliseconds) override + { + std::unique_lock lock(state->mutex); + return wait_until(lock, state->cv, milliseconds, [this] { return state->done; }); + } +}; +} + +HANDLE CreateSemaphore(void*, LONG initial_count, LONG maximum_count, LPCSTR) +{ + if (maximum_count <= 0 || initial_count < 0 || initial_count > maximum_count) + return NULL; + Semaphore* semaphore = new Semaphore; + semaphore->count = initial_count; + semaphore->maximum = maximum_count; + return static_cast(semaphore); +} + +// Fails without changing the count when the release would pass the maximum (ERROR_TOO_MANY_POSTS). +BOOL ReleaseSemaphore(HANDLE handle, LONG release_count, LONG* previous_count) +{ + Semaphore* semaphore = handle ? dynamic_cast(static_cast(handle)) : nullptr; + if (!semaphore || release_count <= 0) + return FALSE; + { + std::lock_guard lock(semaphore->mutex); + if (release_count > semaphore->maximum - semaphore->count) + return FALSE; + if (previous_count) + *previous_count = semaphore->count; + semaphore->count += release_count; + } + semaphore->cv.notify_all(); + return TRUE; +} + +DWORD WaitForSingleObject(HANDLE handle, DWORD milliseconds) +{ + if (!handle || handle == INVALID_HANDLE_VALUE) + return WAIT_FAILED; + return static_cast(handle)->Wait(milliseconds); +} + +BOOL CloseHandle(HANDLE handle) +{ + if (!handle || handle == INVALID_HANDLE_VALUE) + return FALSE; + delete static_cast(handle); + return TRUE; +} + +BOOL SetThreadPriority(HANDLE handle, int) +{ + return handle ? TRUE : FALSE; +} + +uintptr_t _beginthreadex(void*, unsigned, unsigned (*start_address)(void*), void* arglist, unsigned, unsigned* thrdaddr) +{ + Thread* thread = new Thread; + std::shared_ptr state = thread->state; + try + { + std::thread([state, start_address, arglist] { + start_address(arglist); + { + std::lock_guard lock(state->mutex); + state->done = true; + } + state->cv.notify_all(); + }).detach(); + } + catch (const std::system_error&) + { + delete thread; + return 0; + } + if (thrdaddr) + { + static std::atomic next_id{1}; + *thrdaddr = next_id++; + } + return reinterpret_cast(static_cast(thread)); +} + char* _strlwr(char* s) { for (char* p = s; *p; ++p) diff --git a/src/port/common/Win32Crt.h b/src/port/common/Win32Crt.h index de8222e7..d1501aaf 100644 --- a/src/port/common/Win32Crt.h +++ b/src/port/common/Win32Crt.h @@ -73,6 +73,21 @@ BOOL FindNextFile(HANDLE handle, WIN32_FIND_DATA* data); BOOL FindClose(HANDLE handle); void Sleep(DWORD milliseconds); +// kernel32 synchronization objects used by EterLib (FileLoaderThread): a counting semaphore and a +// _beginthreadex thread, both waitable with WaitForSingleObject and freed with CloseHandle. +// Only the unnamed, default-security forms exist; thread priority is accepted and ignored. +#define WAIT_OBJECT_0 0x00000000L +#define WAIT_TIMEOUT 0x00000102L +#define WAIT_FAILED 0xFFFFFFFF +#define THREAD_PRIORITY_NORMAL 0 +HANDLE CreateSemaphore(void* security_attributes, LONG initial_count, LONG maximum_count, LPCSTR name); +BOOL ReleaseSemaphore(HANDLE semaphore, LONG release_count, LONG* previous_count); +DWORD WaitForSingleObject(HANDLE handle, DWORD milliseconds); +BOOL CloseHandle(HANDLE handle); +BOOL SetThreadPriority(HANDLE thread, int priority); +uintptr_t _beginthreadex(void* security, unsigned stack_size, unsigned (*start_address)(void*), void* arglist, + unsigned initflag, unsigned* thrdaddr); + // MSVC __min / __max #ifndef __min #define __min(a, b) (((a) < (b)) ? (a) : (b)) diff --git a/tests/port/port_file_loader_thread_test.cpp b/tests/port/port_file_loader_thread_test.cpp new file mode 100644 index 00000000..e92f71e4 --- /dev/null +++ b/tests/port/port_file_loader_thread_test.cpp @@ -0,0 +1,159 @@ +// EterLib CFileLoaderThread / CThread / Mutex over the Win32Crt kernel-object shims. +// 40250 leaves CResourceManager::ms_loadingThread un-Created (the Create call is commented out), so the +// live path is a static, zero-initialised loader whose Request finds no semaphore and whose Fetch +// never yields; a Created loader reads each request through CEterPackManager on its own thread. +#include "EterLib/StdAfx.h" +#include "EterLib/FileLoaderThread.h" +#include "EterLib/Thread.h" +#include "EterLib/Mutex.h" +#include "EterLib/ResourceManager.h" +#include "EterPack/EterPackManager.h" + +#include +#include +#include +#include +#include +#include + +namespace +{ +int failures = 0; + +void check(bool ok, const char * name) +{ + if (!ok) + { + std::printf("FAIL %s\n", name); + ++failures; + } +} + +CFileLoaderThread s_idleLoader; // like CResourceManager::ms_loadingThread: static storage, never Created +CFileLoaderThread s_loader; + +std::atomic s_counter{0}; + +class CCountThread : public CThread +{ + public: + UINT m_setup = 1; + void * m_arg = NULL; + + protected: + UINT Setup() override { return m_setup; } + UINT Execute(void * arg) override + { + m_arg = arg; + ++s_counter; + return 7; + } +}; +} + +int main() +{ + // Semaphore: counts, the maximum, timeouts. + { + HANDLE sem = CreateSemaphore(NULL, 1, 2, NULL); + check(sem != NULL, "CreateSemaphore"); + check(!CreateSemaphore(NULL, 3, 2, NULL), "CreateSemaphore initial > maximum"); + check(WaitForSingleObject(sem, 0) == WAIT_OBJECT_0, "semaphore initial count"); + check(WaitForSingleObject(sem, 10) == WAIT_TIMEOUT, "semaphore empty times out"); + LONG previous = -1; + check(ReleaseSemaphore(sem, 2, &previous) && previous == 0, "ReleaseSemaphore previous count"); + check(!ReleaseSemaphore(sem, 1, NULL), "ReleaseSemaphore past maximum fails"); + check(WaitForSingleObject(sem, 0) == WAIT_OBJECT_0 && WaitForSingleObject(sem, 0) == WAIT_OBJECT_0 + && WaitForSingleObject(sem, 0) == WAIT_TIMEOUT, "semaphore count kept after failed release"); + check(!ReleaseSemaphore(NULL, 1, NULL), "ReleaseSemaphore NULL handle"); + check(WaitForSingleObject(NULL, 0) == WAIT_FAILED, "WaitForSingleObject NULL handle"); + + std::thread waker([sem] { + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + ReleaseSemaphore(sem, 1, NULL); + }); + check(WaitForSingleObject(sem, INFINITE) == WAIT_OBJECT_0, "semaphore wakes a waiter"); + waker.join(); + check(CloseHandle(sem) && !CloseHandle(NULL), "CloseHandle"); + } + + // Mutex is recursive (CRITICAL_SECTION); CThread runs Setup then Execute on a new thread. + { + Mutex mutex; + mutex.Lock(); + mutex.Lock(); + mutex.Unlock(); + mutex.Unlock(); + + CCountThread thread; + int arg = 0; + check(thread.Create(&arg) != 0, "CThread Create"); + for (int i = 0; i < 200 && s_counter.load() == 0; ++i) + Sleep(5); + check(s_counter.load() == 1 && thread.m_arg == &arg, "CThread Execute with arg"); + + CCountThread noSetup; + noSetup.m_setup = 0; + check(noSetup.Create(NULL) != 0, "CThread Create without Setup"); + Sleep(30); + check(s_counter.load() == 1, "CThread skips Execute when Setup fails"); + } + + char dir[] = "/tmp/mt_fileloader_XXXXXX"; + check(mkdtemp(dir) != NULL, "mkdtemp"); + std::string path = std::string(dir) + "/a.txt"; + FILE * fp = std::fopen(path.c_str(), "wb"); + std::fwrite("hello", 1, 5, fp); + std::fclose(fp); + + CEterPackManager packs; // SEARCH_FILE_FIRST: requests read the file from disk + g_iLoadingDelayTime = 0; + + // 40250 live path: never Created. + { + std::string name = path; + s_idleLoader.Request(name); + CFileLoaderThread::TData * pData = NULL; + check(!s_idleLoader.Fetch(&pData), "un-Created loader never completes a request"); + s_idleLoader.Shutdown(); + } + + // Created: requests complete in order with the file contents; a missing file completes empty. + { + check(s_loader.Create(NULL) != 0, "CFileLoaderThread Create"); + Sleep(20); // Setup (the semaphore) runs on the loader thread + std::string name = path; + std::string missing = std::string(dir) + "/missing.txt"; + s_loader.Request(name); + s_loader.Request(missing); + + CFileLoaderThread::TData * got[2] = {}; + int count = 0; + for (int i = 0; i < 400 && count < 2; ++i) + { + if (!s_loader.Fetch(&got[count])) + Sleep(5); + else + ++count; + } + check(count == 2, "both requests complete"); + if (count == 2) + { + check(got[0]->stFileName == path && got[0]->dwSize == 5 && std::memcmp(got[0]->pvBuf, "hello", 5) == 0, + "request loaded through CEterPackManager"); + check(got[1]->dwSize == 0 && got[1]->pvBuf == NULL, "missing file completes empty"); + for (CFileLoaderThread::TData * pData : got) + { + delete [] ((char *) pData->pvBuf); + delete pData; + } + } + s_loader.Shutdown(); + } + + unlink(path.c_str()); + rmdir(dir); + if (!failures) + std::printf("ok\n"); + return failures ? 1 : 0; +}