Mercurial > foo_out_sdl
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 0:e9bb126753e7 | 1:20d02a178406 |
|---|---|
| 1 #include "pfc-lite.h" | |
| 2 | |
| 3 #ifdef _WIN32 | |
| 4 #include "pp-winapi.h" | |
| 5 #endif | |
| 6 | |
| 7 #ifdef __APPLE__ | |
| 8 #include <sys/sysctl.h> | |
| 9 #include <sys/stat.h> | |
| 10 #endif | |
| 11 | |
| 12 #include "threads.h" | |
| 13 #include "debug.h" | |
| 14 #include <memory> | |
| 15 | |
| 16 #include <thread> | |
| 17 | |
| 18 #ifndef _WIN32 | |
| 19 namespace { | |
| 20 class c_pthread_attr { | |
| 21 public: | |
| 22 c_pthread_attr() { | |
| 23 pthread_attr_init(&a); | |
| 24 } | |
| 25 ~c_pthread_attr() { | |
| 26 pthread_attr_destroy(&a); | |
| 27 } | |
| 28 void apply( const pfc::thread::arg_t & arg ) { | |
| 29 #ifdef __APPLE__ | |
| 30 if ( arg.appleThreadQOS != 0 ) { | |
| 31 pthread_attr_set_qos_class_np( &a, (qos_class_t) arg.appleThreadQOS, arg.appleRealtivePriority ); | |
| 32 } | |
| 33 #endif | |
| 34 if ( arg.nixSchedPolicy >= 0 ) { | |
| 35 pthread_attr_setschedpolicy(&a, arg.nixSchedPolicy); | |
| 36 pthread_attr_setschedparam(&a, &arg.nixSchedParam); | |
| 37 } | |
| 38 } | |
| 39 pthread_attr_t a; | |
| 40 | |
| 41 c_pthread_attr( const c_pthread_attr & ) = delete; | |
| 42 void operator=( const c_pthread_attr & ) = delete; | |
| 43 }; | |
| 44 } | |
| 45 #endif | |
| 46 | |
| 47 namespace pfc { | |
| 48 unsigned getOptimalWorkerThreadCount() { | |
| 49 return std::thread::hardware_concurrency(); | |
| 50 } | |
| 51 | |
| 52 unsigned getOptimalWorkerThreadCountEx(size_t taskCountLimit) { | |
| 53 if (taskCountLimit <= 1) return 1; | |
| 54 return (unsigned)pfc::min_t<size_t>(taskCountLimit,getOptimalWorkerThreadCount()); | |
| 55 } | |
| 56 | |
| 57 | |
| 58 void thread::entry() { | |
| 59 try { | |
| 60 threadProc(); | |
| 61 } catch(...) {} | |
| 62 } | |
| 63 | |
| 64 void thread::couldNotCreateThread() { | |
| 65 PFC_ASSERT(!"Could not create thread"); | |
| 66 // Something terminally wrong, better crash leaving a good debug trace | |
| 67 crash(); | |
| 68 } | |
| 69 | |
| 70 #ifdef _WIN32 | |
| 71 thread::thread() : m_thread(INVALID_HANDLE_VALUE) | |
| 72 { | |
| 73 } | |
| 74 | |
| 75 void thread::close() { | |
| 76 if (isActive()) { | |
| 77 | |
| 78 int ctxPriority = GetThreadPriority( GetCurrentThread() ); | |
| 79 if (ctxPriority > GetThreadPriority( m_thread ) ) SetThreadPriority( m_thread, ctxPriority ); | |
| 80 | |
| 81 if (WaitForSingleObject(m_thread,INFINITE) != WAIT_OBJECT_0) couldNotCreateThread(); | |
| 82 CloseHandle(m_thread); m_thread = INVALID_HANDLE_VALUE; | |
| 83 } | |
| 84 } | |
| 85 bool thread::isActive() const { | |
| 86 return m_thread != INVALID_HANDLE_VALUE; | |
| 87 } | |
| 88 | |
| 89 static HANDLE MyBeginThread( unsigned (__stdcall * proc)(void *) , void * arg, DWORD * outThreadID, int priority) { | |
| 90 HANDLE thread; | |
| 91 #ifdef PFC_WINDOWS_DESKTOP_APP | |
| 92 thread = (HANDLE)_beginthreadex(NULL, 0, proc, arg, CREATE_SUSPENDED, (unsigned int*)outThreadID); | |
| 93 #else | |
| 94 thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)proc, arg, CREATE_SUSPENDED, outThreadID); | |
| 95 #endif | |
| 96 if (thread == NULL) thread::couldNotCreateThread(); | |
| 97 SetThreadPriority(thread, priority); | |
| 98 ResumeThread(thread); | |
| 99 return thread; | |
| 100 } | |
| 101 void thread::winStart(int priority, DWORD * outThreadID) { | |
| 102 close(); | |
| 103 m_thread = MyBeginThread(g_entry, reinterpret_cast<void*>(this), outThreadID, priority); | |
| 104 } | |
| 105 void thread::start(arg_t const & arg) { | |
| 106 winStart(arg.winThreadPriority, nullptr); | |
| 107 } | |
| 108 | |
| 109 unsigned CALLBACK thread::g_entry(void* p_instance) { | |
| 110 reinterpret_cast<thread*>(p_instance)->entry(); return 0; | |
| 111 } | |
| 112 | |
| 113 #else | |
| 114 thread::thread() : m_thread(), m_threadValid() { | |
| 115 } | |
| 116 | |
| 117 #ifndef __APPLE__ // Apple specific entrypoint in obj-c.mm | |
| 118 void * thread::g_entry( void * arg ) { | |
| 119 reinterpret_cast<thread*>( arg )->entry(); | |
| 120 return NULL; | |
| 121 } | |
| 122 #endif | |
| 123 | |
| 124 void thread::start(arg_t const & arg) { | |
| 125 close(); | |
| 126 #ifdef __APPLE__ | |
| 127 appleStartThreadPrologue(); | |
| 128 #endif | |
| 129 pthread_t thread; | |
| 130 | |
| 131 c_pthread_attr attr; | |
| 132 | |
| 133 pthread_attr_setdetachstate(&attr.a, PTHREAD_CREATE_JOINABLE); | |
| 134 | |
| 135 attr.apply( arg ); | |
| 136 | |
| 137 if (pthread_create(&thread, &attr.a, g_entry, reinterpret_cast<void*>(this)) != 0) couldNotCreateThread(); | |
| 138 | |
| 139 m_threadValid = true; | |
| 140 m_thread = thread; | |
| 141 } | |
| 142 | |
| 143 void thread::close() { | |
| 144 if (m_threadValid) { | |
| 145 void * rv = NULL; | |
| 146 pthread_join( m_thread, & rv ); m_thread = 0; | |
| 147 m_threadValid = false; | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 bool thread::isActive() const { | |
| 152 return m_threadValid; | |
| 153 } | |
| 154 #endif | |
| 155 #ifdef _WIN32 | |
| 156 unsigned CALLBACK winSplitThreadProc(void* arg) { | |
| 157 auto f = reinterpret_cast<std::function<void() > *>(arg); | |
| 158 (*f)(); | |
| 159 delete f; | |
| 160 return 0; | |
| 161 } | |
| 162 #else | |
| 163 void * nixSplitThreadProc(void * arg) { | |
| 164 auto f = reinterpret_cast<std::function<void() > *>(arg); | |
| 165 #ifdef __APPLE__ | |
| 166 inAutoReleasePool([f] { | |
| 167 (*f)(); | |
| 168 delete f; | |
| 169 }); | |
| 170 #else | |
| 171 (*f)(); | |
| 172 delete f; | |
| 173 #endif | |
| 174 return NULL; | |
| 175 } | |
| 176 #endif | |
| 177 | |
| 178 void splitThread( std::function<void ()> f ) { | |
| 179 splitThread( thread::argCurrentThread(), f ); | |
| 180 } | |
| 181 | |
| 182 void splitThread(thread::arg_t const & arg, std::function<void() > f) { | |
| 183 auto arg2 = std::make_unique<std::function<void()> >(f); | |
| 184 #ifdef _WIN32 | |
| 185 HANDLE h = MyBeginThread(winSplitThreadProc, arg2.get(), NULL, arg.winThreadPriority ); | |
| 186 CloseHandle(h); | |
| 187 #else | |
| 188 #ifdef __APPLE__ | |
| 189 thread::appleStartThreadPrologue(); | |
| 190 #endif | |
| 191 pthread_t thread; | |
| 192 | |
| 193 c_pthread_attr attr; | |
| 194 | |
| 195 attr.apply( arg ); | |
| 196 | |
| 197 if (pthread_create(&thread, &attr.a, nixSplitThreadProc, arg2.get()) != 0) thread::couldNotCreateThread(); | |
| 198 | |
| 199 pthread_detach(thread); | |
| 200 #endif | |
| 201 arg2.release(); | |
| 202 } | |
| 203 #ifndef __APPLE__ | |
| 204 // Stub for non Apple | |
| 205 void inAutoReleasePool(std::function<void()> f) { f(); } | |
| 206 #endif | |
| 207 | |
| 208 | |
| 209 void thread2::startHere(std::function<void()> e) { | |
| 210 setEntry(e); start(); | |
| 211 } | |
| 212 void thread2::startHere(arg_t const& arg, std::function<void()> e) { | |
| 213 setEntry(e); start(arg); | |
| 214 } | |
| 215 | |
| 216 void thread2::setEntry(std::function<void()> e) { | |
| 217 PFC_ASSERT(!isActive()); | |
| 218 m_entryPoint = e; | |
| 219 } | |
| 220 | |
| 221 void thread2::threadProc() { | |
| 222 m_entryPoint(); | |
| 223 } | |
| 224 | |
| 225 thread::handle_t thread::handleToCurrent() { | |
| 226 #ifdef _WIN32 | |
| 227 return GetCurrentThread(); | |
| 228 #else | |
| 229 return pthread_self(); | |
| 230 #endif | |
| 231 } | |
| 232 | |
| 233 thread::arg_t thread::argDefault() { | |
| 234 return arg_t(); | |
| 235 } | |
| 236 | |
| 237 thread::arg_t thread::argCurrentThread() { | |
| 238 arg_t arg; | |
| 239 #ifdef _WIN32 | |
| 240 arg.winThreadPriority = GetThreadPriority( GetCurrentThread() ); | |
| 241 #endif | |
| 242 #ifdef __APPLE__ | |
| 243 qos_class_t qos_class = QOS_CLASS_UNSPECIFIED; | |
| 244 int relative_priority = 0; | |
| 245 if (pthread_get_qos_class_np(pthread_self(), &qos_class, &relative_priority) == 0) { | |
| 246 arg.appleThreadQOS = (int) qos_class; | |
| 247 arg.appleRealtivePriority = relative_priority; | |
| 248 } | |
| 249 #endif | |
| 250 #ifndef _WIN32 | |
| 251 { | |
| 252 int policy; sched_param param; | |
| 253 if (pthread_getschedparam( pthread_self(), &policy, ¶m) == 0) { | |
| 254 arg.nixSchedPolicy = policy; | |
| 255 arg.nixSchedParam = param; | |
| 256 } | |
| 257 } | |
| 258 #endif | |
| 259 return arg; | |
| 260 } | |
| 261 | |
| 262 thread::arg_t thread::argBackground() { | |
| 263 #ifdef _WIN32 | |
| 264 return argWinPriority(THREAD_PRIORITY_LOWEST); | |
| 265 #elif defined(__APPLE__) | |
| 266 return argAppleQOS((int)QOS_CLASS_BACKGROUND); | |
| 267 #else | |
| 268 return argNixPriority(SCHED_OTHER, 0); | |
| 269 #endif | |
| 270 } | |
| 271 | |
| 272 thread::arg_t thread::argHighPriority() { | |
| 273 #ifdef _WIN32 | |
| 274 return argWinPriority(THREAD_PRIORITY_HIGHEST); | |
| 275 #elif defined(__ANDROID__) | |
| 276 // No SCHED_FIFO or SCHED_RR on Android | |
| 277 return argNixPriority(SCHED_OTHER, 100); | |
| 278 #else | |
| 279 return argNixPriority(SCHED_RR, 75); | |
| 280 #endif | |
| 281 } | |
| 282 | |
| 283 thread::arg_t thread::argPlayback() { | |
| 284 // It would be nice to use realtime priority yet lock ourselves to the slow cores, but that just doesn't work | |
| 285 // Using priority overrides the request for the slow cores (QOS_CLASS_BACKGROUND) | |
| 286 // ret.appleThreadQOS = (int) QOS_CLASS_BACKGROUND; | |
| 287 | |
| 288 #ifdef _WIN32 | |
| 289 return argWinPriority(THREAD_PRIORITY_TIME_CRITICAL); | |
| 290 #elif defined(__ANDROID__) | |
| 291 // No SCHED_FIFO or SCHED_RR on Android | |
| 292 return argNixPriority(SCHED_OTHER, 100); | |
| 293 #else | |
| 294 return argNixPriority(SCHED_FIFO, 100); | |
| 295 #endif | |
| 296 | |
| 297 } | |
| 298 | |
| 299 thread::arg_t thread::argUserInitiated() { | |
| 300 #ifdef _WIN32 | |
| 301 return argWinPriority(THREAD_PRIORITY_BELOW_NORMAL); | |
| 302 #elif defined(__APPLE__) | |
| 303 return argAppleQOS((int) QOS_CLASS_USER_INITIATED); | |
| 304 #else | |
| 305 return argNixPriority(SCHED_OTHER, 25); | |
| 306 #endif | |
| 307 } | |
| 308 | |
| 309 #ifdef _WIN32 | |
| 310 thread::arg_t thread::argWinPriority(int priority) { | |
| 311 arg_t arg; | |
| 312 arg.winThreadPriority = priority; | |
| 313 return arg; | |
| 314 } | |
| 315 #endif | |
| 316 #ifdef __APPLE__ | |
| 317 thread::arg_t thread::argAppleQOS(int qos) { | |
| 318 arg_t arg; | |
| 319 arg.appleThreadQOS = qos; | |
| 320 return arg; | |
| 321 } | |
| 322 #endif | |
| 323 #ifndef _WIN32 | |
| 324 thread::arg_t thread::argNixPriority(int policy, int percent) { | |
| 325 int pval; | |
| 326 if ( percent <= 0 ) pval = sched_get_priority_min(policy); | |
| 327 else if ( percent >= 100 ) pval = sched_get_priority_max(policy); | |
| 328 else { | |
| 329 const int pmin = sched_get_priority_min(policy), pmax = sched_get_priority_max(policy); | |
| 330 if (pmax > pmin) { | |
| 331 pval = pmin + ((pmax - pmin) * percent / 100); | |
| 332 } else { | |
| 333 pval = pmin; | |
| 334 } | |
| 335 } | |
| 336 arg_t ret; | |
| 337 ret.nixSchedPolicy = policy; | |
| 338 ret.nixSchedParam = sched_param { pval }; | |
| 339 return ret; | |
| 340 } | |
| 341 #endif | |
| 342 void thread::setCurrentPriority(arg_t const& arg) { | |
| 343 #ifdef _WIN32 | |
| 344 ::SetThreadPriority(GetCurrentThread(), arg.winThreadPriority); | |
| 345 #else | |
| 346 #ifdef __APPLE__ | |
| 347 if (arg.appleThreadQOS != 0) { | |
| 348 pthread_set_qos_class_self_np((qos_class_t)arg.appleThreadQOS, arg.appleRealtivePriority); | |
| 349 } | |
| 350 | |
| 351 #endif | |
| 352 if (arg.nixSchedPolicy >= 0) { | |
| 353 pthread_setschedparam(pthread_self(), arg.nixSchedPolicy, &arg.nixSchedParam); | |
| 354 } | |
| 355 #endif | |
| 356 } | |
| 357 | |
| 358 } |
