Mercurial > foo_out_sdl
diff foosdk/sdk/pfc/threads.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/foosdk/sdk/pfc/threads.cpp Mon Jan 05 02:15:46 2026 -0500 @@ -0,0 +1,358 @@ +#include "pfc-lite.h" + +#ifdef _WIN32 +#include "pp-winapi.h" +#endif + +#ifdef __APPLE__ +#include <sys/sysctl.h> +#include <sys/stat.h> +#endif + +#include "threads.h" +#include "debug.h" +#include <memory> + +#include <thread> + +#ifndef _WIN32 +namespace { + class c_pthread_attr { + public: + c_pthread_attr() { + pthread_attr_init(&a); + } + ~c_pthread_attr() { + pthread_attr_destroy(&a); + } + void apply( const pfc::thread::arg_t & arg ) { +#ifdef __APPLE__ + if ( arg.appleThreadQOS != 0 ) { + pthread_attr_set_qos_class_np( &a, (qos_class_t) arg.appleThreadQOS, arg.appleRealtivePriority ); + } +#endif + if ( arg.nixSchedPolicy >= 0 ) { + pthread_attr_setschedpolicy(&a, arg.nixSchedPolicy); + pthread_attr_setschedparam(&a, &arg.nixSchedParam); + } + } + pthread_attr_t a; + + c_pthread_attr( const c_pthread_attr & ) = delete; + void operator=( const c_pthread_attr & ) = delete; + }; +} +#endif + +namespace pfc { + unsigned getOptimalWorkerThreadCount() { + return std::thread::hardware_concurrency(); + } + + unsigned getOptimalWorkerThreadCountEx(size_t taskCountLimit) { + if (taskCountLimit <= 1) return 1; + return (unsigned)pfc::min_t<size_t>(taskCountLimit,getOptimalWorkerThreadCount()); + } + + + void thread::entry() { + try { + threadProc(); + } catch(...) {} + } + + void thread::couldNotCreateThread() { + PFC_ASSERT(!"Could not create thread"); + // Something terminally wrong, better crash leaving a good debug trace + crash(); + } + +#ifdef _WIN32 + thread::thread() : m_thread(INVALID_HANDLE_VALUE) + { + } + + void thread::close() { + if (isActive()) { + + int ctxPriority = GetThreadPriority( GetCurrentThread() ); + if (ctxPriority > GetThreadPriority( m_thread ) ) SetThreadPriority( m_thread, ctxPriority ); + + if (WaitForSingleObject(m_thread,INFINITE) != WAIT_OBJECT_0) couldNotCreateThread(); + CloseHandle(m_thread); m_thread = INVALID_HANDLE_VALUE; + } + } + bool thread::isActive() const { + return m_thread != INVALID_HANDLE_VALUE; + } + + static HANDLE MyBeginThread( unsigned (__stdcall * proc)(void *) , void * arg, DWORD * outThreadID, int priority) { + HANDLE thread; +#ifdef PFC_WINDOWS_DESKTOP_APP + thread = (HANDLE)_beginthreadex(NULL, 0, proc, arg, CREATE_SUSPENDED, (unsigned int*)outThreadID); +#else + thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)proc, arg, CREATE_SUSPENDED, outThreadID); +#endif + if (thread == NULL) thread::couldNotCreateThread(); + SetThreadPriority(thread, priority); + ResumeThread(thread); + return thread; + } + void thread::winStart(int priority, DWORD * outThreadID) { + close(); + m_thread = MyBeginThread(g_entry, reinterpret_cast<void*>(this), outThreadID, priority); + } + void thread::start(arg_t const & arg) { + winStart(arg.winThreadPriority, nullptr); + } + + unsigned CALLBACK thread::g_entry(void* p_instance) { + reinterpret_cast<thread*>(p_instance)->entry(); return 0; + } + +#else + thread::thread() : m_thread(), m_threadValid() { + } + +#ifndef __APPLE__ // Apple specific entrypoint in obj-c.mm + void * thread::g_entry( void * arg ) { + reinterpret_cast<thread*>( arg )->entry(); + return NULL; + } +#endif + + void thread::start(arg_t const & arg) { + close(); +#ifdef __APPLE__ + appleStartThreadPrologue(); +#endif + pthread_t thread; + + c_pthread_attr attr; + + pthread_attr_setdetachstate(&attr.a, PTHREAD_CREATE_JOINABLE); + + attr.apply( arg ); + + if (pthread_create(&thread, &attr.a, g_entry, reinterpret_cast<void*>(this)) != 0) couldNotCreateThread(); + + m_threadValid = true; + m_thread = thread; + } + + void thread::close() { + if (m_threadValid) { + void * rv = NULL; + pthread_join( m_thread, & rv ); m_thread = 0; + m_threadValid = false; + } + } + + bool thread::isActive() const { + return m_threadValid; + } +#endif +#ifdef _WIN32 + unsigned CALLBACK winSplitThreadProc(void* arg) { + auto f = reinterpret_cast<std::function<void() > *>(arg); + (*f)(); + delete f; + return 0; + } +#else + void * nixSplitThreadProc(void * arg) { + auto f = reinterpret_cast<std::function<void() > *>(arg); +#ifdef __APPLE__ + inAutoReleasePool([f] { + (*f)(); + delete f; + }); +#else + (*f)(); + delete f; +#endif + return NULL; + } +#endif + + void splitThread( std::function<void ()> f ) { + splitThread( thread::argCurrentThread(), f ); + } + + void splitThread(thread::arg_t const & arg, std::function<void() > f) { + auto arg2 = std::make_unique<std::function<void()> >(f); +#ifdef _WIN32 + HANDLE h = MyBeginThread(winSplitThreadProc, arg2.get(), NULL, arg.winThreadPriority ); + CloseHandle(h); +#else +#ifdef __APPLE__ + thread::appleStartThreadPrologue(); +#endif + pthread_t thread; + + c_pthread_attr attr; + + attr.apply( arg ); + + if (pthread_create(&thread, &attr.a, nixSplitThreadProc, arg2.get()) != 0) thread::couldNotCreateThread(); + + pthread_detach(thread); +#endif + arg2.release(); + } +#ifndef __APPLE__ + // Stub for non Apple + void inAutoReleasePool(std::function<void()> f) { f(); } +#endif + + + void thread2::startHere(std::function<void()> e) { + setEntry(e); start(); + } + void thread2::startHere(arg_t const& arg, std::function<void()> e) { + setEntry(e); start(arg); + } + + void thread2::setEntry(std::function<void()> e) { + PFC_ASSERT(!isActive()); + m_entryPoint = e; + } + + void thread2::threadProc() { + m_entryPoint(); + } + + thread::handle_t thread::handleToCurrent() { +#ifdef _WIN32 + return GetCurrentThread(); +#else + return pthread_self(); +#endif + } + + thread::arg_t thread::argDefault() { + return arg_t(); + } + + thread::arg_t thread::argCurrentThread() { + arg_t arg; +#ifdef _WIN32 + arg.winThreadPriority = GetThreadPriority( GetCurrentThread() ); +#endif +#ifdef __APPLE__ + qos_class_t qos_class = QOS_CLASS_UNSPECIFIED; + int relative_priority = 0; + if (pthread_get_qos_class_np(pthread_self(), &qos_class, &relative_priority) == 0) { + arg.appleThreadQOS = (int) qos_class; + arg.appleRealtivePriority = relative_priority; + } +#endif +#ifndef _WIN32 + { + int policy; sched_param param; + if (pthread_getschedparam( pthread_self(), &policy, ¶m) == 0) { + arg.nixSchedPolicy = policy; + arg.nixSchedParam = param; + } + } +#endif + return arg; + } + + thread::arg_t thread::argBackground() { +#ifdef _WIN32 + return argWinPriority(THREAD_PRIORITY_LOWEST); +#elif defined(__APPLE__) + return argAppleQOS((int)QOS_CLASS_BACKGROUND); +#else + return argNixPriority(SCHED_OTHER, 0); +#endif + } + + thread::arg_t thread::argHighPriority() { +#ifdef _WIN32 + return argWinPriority(THREAD_PRIORITY_HIGHEST); +#elif defined(__ANDROID__) + // No SCHED_FIFO or SCHED_RR on Android + return argNixPriority(SCHED_OTHER, 100); +#else + return argNixPriority(SCHED_RR, 75); +#endif + } + + thread::arg_t thread::argPlayback() { + // It would be nice to use realtime priority yet lock ourselves to the slow cores, but that just doesn't work + // Using priority overrides the request for the slow cores (QOS_CLASS_BACKGROUND) + // ret.appleThreadQOS = (int) QOS_CLASS_BACKGROUND; + +#ifdef _WIN32 + return argWinPriority(THREAD_PRIORITY_TIME_CRITICAL); +#elif defined(__ANDROID__) + // No SCHED_FIFO or SCHED_RR on Android + return argNixPriority(SCHED_OTHER, 100); +#else + return argNixPriority(SCHED_FIFO, 100); +#endif + + } + + thread::arg_t thread::argUserInitiated() { +#ifdef _WIN32 + return argWinPriority(THREAD_PRIORITY_BELOW_NORMAL); +#elif defined(__APPLE__) + return argAppleQOS((int) QOS_CLASS_USER_INITIATED); +#else + return argNixPriority(SCHED_OTHER, 25); +#endif + } + +#ifdef _WIN32 + thread::arg_t thread::argWinPriority(int priority) { + arg_t arg; + arg.winThreadPriority = priority; + return arg; + } +#endif +#ifdef __APPLE__ + thread::arg_t thread::argAppleQOS(int qos) { + arg_t arg; + arg.appleThreadQOS = qos; + return arg; + } +#endif +#ifndef _WIN32 + thread::arg_t thread::argNixPriority(int policy, int percent) { + int pval; + if ( percent <= 0 ) pval = sched_get_priority_min(policy); + else if ( percent >= 100 ) pval = sched_get_priority_max(policy); + else { + const int pmin = sched_get_priority_min(policy), pmax = sched_get_priority_max(policy); + if (pmax > pmin) { + pval = pmin + ((pmax - pmin) * percent / 100); + } else { + pval = pmin; + } + } + arg_t ret; + ret.nixSchedPolicy = policy; + ret.nixSchedParam = sched_param { pval }; + return ret; + } +#endif + void thread::setCurrentPriority(arg_t const& arg) { +#ifdef _WIN32 + ::SetThreadPriority(GetCurrentThread(), arg.winThreadPriority); +#else +#ifdef __APPLE__ + if (arg.appleThreadQOS != 0) { + pthread_set_qos_class_self_np((qos_class_t)arg.appleThreadQOS, arg.appleRealtivePriority); + } + +#endif + if (arg.nixSchedPolicy >= 0) { + pthread_setschedparam(pthread_self(), arg.nixSchedPolicy, &arg.nixSchedParam); + } +#endif + } + +}
