panda3d/panda/src/pipeline/threadWin32Impl.cxx

284 lines
7.5 KiB
C++

/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file threadWin32Impl.cxx
* @author drose
* @date 2006-02-07
*/
#include "threadWin32Impl.h"
#include "selectThreadImpl.h"
#ifdef THREAD_WIN32_IMPL
#include "thread.h"
#include "pointerTo.h"
#include "config_pipeline.h"
#include <windows.h>
static thread_local Thread *_current_thread = nullptr;
static patomic_flag _main_thread_known = ATOMIC_FLAG_INIT;
#if _WIN32_WINNT < 0x0601
// Requires Windows 7.
static DWORD (__stdcall *EnableThreadProfiling)(HANDLE, DWORD, DWORD64, HANDLE *) = nullptr;
static DWORD (__stdcall *DisableThreadProfiling)(HANDLE) = nullptr;
static DWORD (__stdcall *ReadThreadProfilingData)(HANDLE, DWORD, PPERFORMANCE_DATA data) = nullptr;
static bool init_thread_profiling() {
static bool inited = false;
if (!inited) {
HMODULE kernel32 = GetModuleHandleA("kernel32.dll");
EnableThreadProfiling = (decltype(EnableThreadProfiling))GetProcAddress(kernel32, "EnableThreadProfiling");
DisableThreadProfiling = (decltype(DisableThreadProfiling))GetProcAddress(kernel32, "DisableThreadProfiling");
ReadThreadProfilingData = (decltype(ReadThreadProfilingData))GetProcAddress(kernel32, "ReadThreadProfilingData");
inited = true;
}
return (EnableThreadProfiling && DisableThreadProfiling && ReadThreadProfilingData);
}
#else
static bool init_thread_profiling() {
return true;
}
#endif
/**
* Called by get_current_thread() if the current thread pointer is null; checks
* whether it might be the main thread.
* Note that adding noinline speeds up this call *significantly*, don't remove!
*/
static __declspec(noinline) Thread *
init_current_thread() {
Thread *thread = _current_thread;
if (!_main_thread_known.test_and_set(std::memory_order_relaxed)) {
// Assume that we must be in the main thread, since this method must be
// called before the first thread is spawned.
thread = Thread::get_main_thread();
_current_thread = thread;
}
// If this assertion triggers, you are making Panda calls from a thread
// that has not first been registered using Thread::bind_thread().
nassertr(thread != nullptr, nullptr);
return thread;
}
/**
*
*/
ThreadWin32Impl::
~ThreadWin32Impl() {
if (thread_cat->is_debug()) {
thread_cat.debug() << "Deleting thread " << _parent_obj->get_name() << "\n";
}
CloseHandle(_thread);
}
/**
* Called for the main thread only, which has been already started, to fill in
* the values appropriate to that thread.
*/
void ThreadWin32Impl::
setup_main_thread() {
_status = S_running;
}
/**
*
*/
bool ThreadWin32Impl::
start(ThreadPriority priority, bool joinable) {
_mutex.lock();
if (thread_cat->is_debug()) {
thread_cat.debug() << "Starting " << *_parent_obj << "\n";
}
nassertd(_status == S_new && _thread == 0) {
_mutex.unlock();
return false;
}
_joinable = joinable;
_status = S_start_called;
// Increment the parent object's reference count first. The thread will
// eventually decrement it when it terminates.
_parent_obj->ref();
_thread =
CreateThread(nullptr, 0, &root_func, (void *)this, 0, &_thread_id);
if (_thread_id == 0) {
// Oops, we couldn't start the thread. Be sure to decrement the reference
// count we incremented above, and return false to indicate failure.
unref_delete(_parent_obj);
_mutex.unlock();
return false;
}
// Thread was successfully started. Set the priority as specified.
switch (priority) {
case TP_low:
SetThreadPriority(_thread, THREAD_PRIORITY_BELOW_NORMAL);
break;
case TP_high:
SetThreadPriority(_thread, THREAD_PRIORITY_ABOVE_NORMAL);
break;
case TP_urgent:
SetThreadPriority(_thread, THREAD_PRIORITY_HIGHEST);
break;
case TP_normal:
default:
SetThreadPriority(_thread, THREAD_PRIORITY_NORMAL);
break;
}
_mutex.unlock();
return true;
}
/**
* Blocks the calling process until the thread terminates. If the thread has
* already terminated, this returns immediately.
*/
void ThreadWin32Impl::
join() {
_mutex.lock();
nassertd(_joinable && _status != S_new) {
_mutex.unlock();
return;
}
while (_status != S_finished) {
_cv.wait();
}
_mutex.unlock();
}
/**
*
*/
std::string ThreadWin32Impl::
get_unique_id() const {
std::ostringstream strm;
strm << GetCurrentProcessId() << "." << _thread_id;
return strm.str();
}
/**
*
*/
Thread *ThreadWin32Impl::
get_current_thread() {
Thread *thread = _current_thread;
return (thread != nullptr) ? thread : init_current_thread();
}
/**
* Associates the indicated Thread object with the currently-executing thread.
* You should not call this directly; use Thread::bind_thread() instead.
*/
void ThreadWin32Impl::
bind_thread(Thread *thread) {
if (_current_thread == nullptr && thread == Thread::get_main_thread()) {
_main_thread_known.test_and_set(std::memory_order_relaxed);
}
_current_thread = thread;
}
/**
* Returns the number of context switches that occurred on the current thread.
* The first number is the total number of context switches reported by the OS,
* and the second number is the number of involuntary context switches (ie. the
* thread was scheduled out by the OS), if known, otherwise zero.
* Returns true if context switch information was available, false otherwise.
*/
bool ThreadWin32Impl::
get_context_switches(size_t &total, size_t &involuntary) {
Thread *thread = get_current_thread();
ThreadWin32Impl *self = &thread->_impl;
if (!self->_profiling && init_thread_profiling()) {
DWORD result = EnableThreadProfiling(GetCurrentThread(), THREAD_PROFILING_FLAG_DISPATCH, 0, &self->_profiling);
if (result != ERROR_SUCCESS) {
self->_profiling = 0;
return false;
}
}
PERFORMANCE_DATA data = {sizeof(PERFORMANCE_DATA), PERFORMANCE_DATA_VERSION};
if (ReadThreadProfilingData(self->_profiling, READ_THREAD_PROFILING_FLAG_DISPATCHING, &data) == ERROR_SUCCESS) {
total = data.ContextSwitchCount;
involuntary = 0;
return true;
}
return false;
}
/**
* The entry point of each thread.
*/
DWORD ThreadWin32Impl::
root_func(LPVOID data) {
TAU_REGISTER_THREAD();
{
// TAU_PROFILE("void ThreadWin32Impl::root_func()", " ", TAU_USER);
ThreadWin32Impl *self = (ThreadWin32Impl *)data;
_current_thread = self->_parent_obj;
{
self->_mutex.lock();
nassertd(self->_status == S_start_called) {
self->_mutex.unlock();
return 1;
}
self->_status = S_running;
self->_cv.notify();
self->_mutex.unlock();
}
self->_parent_obj->thread_main();
if (thread_cat->is_debug()) {
thread_cat.debug()
<< "Terminating thread " << self->_parent_obj->get_name()
<< ", count = " << self->_parent_obj->get_ref_count() << "\n";
}
{
self->_mutex.lock();
nassertd(self->_status == S_running) {
self->_mutex.unlock();
return 1;
}
self->_status = S_finished;
self->_cv.notify();
self->_mutex.unlock();
}
if (self->_profiling != 0) {
DisableThreadProfiling(self->_profiling);
self->_profiling = 0;
}
// Now drop the parent object reference that we grabbed in start(). This
// might delete the parent object, and in turn, delete the ThreadWin32Impl
// object.
unref_delete(self->_parent_obj);
}
return 0;
}
#endif // THREAD_WIN32_IMPL