port(EterLib): Mutex/Thread/FileLoaderThread/GrpVertexBufferDynamic verbatim over Win32Crt kernel-object shims

Adds CreateSemaphore/ReleaseSemaphore/WaitForSingleObject/CloseHandle/
SetThreadPriority/_beginthreadex to Win32Crt; port.file_loader_thread covers
the shims, CThread and both the un-Created (40250 live) and Created loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
shenlei
2026-09-30 12:56:24 +09:00
co-authored by Claude Opus 5.5
parent e0cdcee2e6
commit 042bddf195
13 changed files with 758 additions and 75 deletions
+159
View File
@@ -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 <atomic>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <thread>
#include <unistd.h>
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<int> 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;
}