diff --git a/panda/src/display/graphicsEngine.cxx b/panda/src/display/graphicsEngine.cxx index 89999fd2ba..efc7e0dd5c 100644 --- a/panda/src/display/graphicsEngine.cxx +++ b/panda/src/display/graphicsEngine.cxx @@ -43,6 +43,7 @@ #include #endif +PStatCollector GraphicsEngine::_wait_pcollector("Wait"); PStatCollector GraphicsEngine::_app_pcollector("App"); PStatCollector GraphicsEngine::_yield_pcollector("App:Yield"); PStatCollector GraphicsEngine::_cull_pcollector("Cull"); @@ -490,12 +491,24 @@ render_frame() { } } new_windows.swap(_windows); + + // Now it's time to do any drawing from the main frame--after all of + // the App code has executed, but before we begin the next frame. + _app.do_frame(this); - // Grab each thread's mutex again after all windows have flipped. + // Grab each thread's mutex again after all windows have flipped, + // and wait for the thread to finish. Threads::const_iterator ti; for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->_cv_mutex.lock(); + + if (thread->_thread_state != TS_wait) { + PStatTimer timer(_wait_pcollector); + while (thread->_thread_state != TS_wait) { + thread->_cv_done.wait(); + } + } } // Now cycle the pipeline and officially begin the next frame. @@ -524,12 +537,11 @@ render_frame() { } // Now signal all of our threads to begin their next frame. - _app.do_frame(this); for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; if (thread->_thread_state == TS_wait) { thread->_thread_state = TS_do_frame; - thread->_cv.signal(); + thread->_cv_start.signal(); } thread->_cv_mutex.release(); } @@ -579,10 +591,16 @@ open_windows() { for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->_cv_mutex.lock(); - if (thread->_thread_state == TS_wait) { - thread->_thread_state = TS_do_windows; - thread->_cv.signal(); + + if (thread->_thread_state != TS_wait) { + PStatTimer timer(_wait_pcollector); + while (thread->_thread_state != TS_wait) { + thread->_cv_done.wait(); + } } + + thread->_thread_state = TS_do_windows; + thread->_cv_start.signal(); thread->_cv_mutex.release(); } @@ -591,10 +609,16 @@ open_windows() { for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->_cv_mutex.lock(); - if (thread->_thread_state == TS_wait) { - thread->_thread_state = TS_do_windows; - thread->_cv.signal(); + + if (thread->_thread_state != TS_wait) { + PStatTimer timer(_wait_pcollector); + while (thread->_thread_state != TS_wait) { + thread->_cv_done.wait(); + } } + + thread->_thread_state = TS_do_windows; + thread->_cv_start.signal(); thread->_cv_mutex.release(); } } @@ -980,21 +1004,28 @@ do_flip_frame() { nassertv(_flip_state == FS_draw || _flip_state == FS_sync); // First, wait for all the threads to finish their current frame, if - // necessary. Grabbing the mutex should achieve that. + // necessary. Grabbing the mutex (and waiting for TS_wait) should + // achieve that. Threads::const_iterator ti; for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->_cv_mutex.lock(); + + if (thread->_thread_state != TS_wait) { + PStatTimer timer(_wait_pcollector); + while (thread->_thread_state != TS_wait) { + thread->_cv_done.wait(); + } + } } // Now signal all of our threads to flip the windows. _app.do_flip(this); for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; - if (thread->_thread_state == TS_wait) { - thread->_thread_state = TS_do_flip; - thread->_cv.signal(); - } + nassertv(thread->_thread_state == TS_wait); + thread->_thread_state = TS_do_flip; + thread->_cv_start.signal(); thread->_cv_mutex.release(); } @@ -1305,10 +1336,15 @@ terminate_threads() { // Now tell them to release their windows' graphics contexts. for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; - if (thread->_thread_state == TS_wait) { - thread->_thread_state = TS_do_release; - thread->_cv.signal(); + + if (thread->_thread_state != TS_wait) { + PStatTimer timer(_wait_pcollector); + while (thread->_thread_state != TS_wait) { + thread->_cv_done.wait(); + } } + thread->_thread_state = TS_do_release; + thread->_cv_start.signal(); thread->_cv_mutex.release(); } @@ -1322,7 +1358,7 @@ terminate_threads() { for (ti = _threads.begin(); ti != _threads.end(); ++ti) { RenderThread *thread = (*ti).second; thread->_thread_state = TS_terminate; - thread->_cv.signal(); + thread->_cv_start.signal(); thread->_cv_mutex.release(); } @@ -1738,9 +1774,10 @@ do_callbacks(GraphicsEngine::CallbackTime callback_time) { //////////////////////////////////////////////////////////////////// GraphicsEngine::RenderThread:: RenderThread(const string &name, GraphicsEngine *engine) : - Thread(name), + Thread(name, "Main"), _engine(engine), - _cv(_cv_mutex) + _cv_start(_cv_mutex), + _cv_done(_cv_mutex) { _thread_state = TS_wait; } @@ -1756,6 +1793,8 @@ void GraphicsEngine::RenderThread:: thread_main() { MutexHolder holder(_cv_mutex); while (true) { + nassertv(_cv_mutex.debug_is_locked()); + switch (_thread_state) { case TS_wait: break; @@ -1763,31 +1802,41 @@ thread_main() { case TS_do_frame: do_pending(_engine); do_frame(_engine); - _thread_state = TS_wait; break; case TS_do_flip: do_flip(_engine); - _thread_state = TS_wait; break; case TS_do_release: do_pending(_engine); do_release(_engine); - _thread_state = TS_wait; break; case TS_do_windows: do_windows(_engine); do_pending(_engine); - _thread_state = TS_wait; break; case TS_terminate: do_pending(_engine); do_close(_engine); + _thread_state = TS_done; + _cv_done.signal(); + return; + + case TS_done: + // Shouldn't be possible to get here. + nassertv(false); return; } - _cv.wait(); + + _thread_state = TS_wait; + _cv_done.signal(); + + { + PStatTimer timer(_wait_pcollector); + _cv_start.wait(); + } } } diff --git a/panda/src/display/graphicsEngine.h b/panda/src/display/graphicsEngine.h index b2e9696650..b06f32e1c7 100644 --- a/panda/src/display/graphicsEngine.h +++ b/panda/src/display/graphicsEngine.h @@ -107,7 +107,8 @@ public: TS_do_flip, TS_do_release, TS_do_windows, - TS_terminate + TS_terminate, + TS_done }; enum CallbackTime { @@ -216,7 +217,8 @@ private: GraphicsEngine *_engine; Mutex _cv_mutex; - ConditionVar _cv; + ConditionVar _cv_start; + ConditionVar _cv_done; ThreadState _thread_state; }; @@ -243,6 +245,7 @@ private: FlipState _flip_state; Mutex _lock; + static PStatCollector _wait_pcollector; static PStatCollector _app_pcollector; static PStatCollector _yield_pcollector; static PStatCollector _cull_pcollector; diff --git a/panda/src/express/Sources.pp b/panda/src/express/Sources.pp index 557a0b84ed..6754639c9a 100644 --- a/panda/src/express/Sources.pp +++ b/panda/src/express/Sources.pp @@ -25,6 +25,7 @@ dcast.T dcast.h \ encryptStreamBuf.h encryptStreamBuf.I encryptStream.h encryptStream.I \ error_utils.h \ + externalThread.h \ hashGeneratorBase.I hashGeneratorBase.h \ hashVal.I hashVal.h \ indirectLess.I indirectLess.h \ @@ -88,6 +89,7 @@ datagramSink.cxx dcast.cxx \ encryptStreamBuf.cxx encryptStream.cxx \ error_utils.cxx \ + externalThread.cxx \ hashGeneratorBase.cxx hashVal.cxx \ mainThread.cxx \ memoryInfo.cxx memoryUsage.cxx memoryUsagePointerCounts.cxx \ @@ -145,6 +147,7 @@ datagramSink.I datagramSink.h dcast.T dcast.h \ encryptStreamBuf.h encryptStreamBuf.I encryptStream.h encryptStream.I \ error_utils.h \ + externalThread.h \ hashGeneratorBase.I \ hashGeneratorBase.h hashVal.I hashVal.h \ indirectLess.I indirectLess.h \ diff --git a/panda/src/express/express_composite1.cxx b/panda/src/express/express_composite1.cxx index 13b8b5ddd9..d1f9583ce7 100644 --- a/panda/src/express/express_composite1.cxx +++ b/panda/src/express/express_composite1.cxx @@ -16,6 +16,7 @@ #include "error_utils.cxx" #include "encryptStreamBuf.cxx" #include "encryptStream.cxx" +#include "externalThread.cxx" #include "hashGeneratorBase.cxx" #include "hashVal.cxx" #include "mainThread.cxx" diff --git a/panda/src/express/externalThread.cxx b/panda/src/express/externalThread.cxx new file mode 100644 index 0000000000..a8a2cf89c1 --- /dev/null +++ b/panda/src/express/externalThread.cxx @@ -0,0 +1,40 @@ +// Filename: externalThread.cxx +// Created by: drose (30Jan06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "externalThread.h" + +TypeHandle ExternalThread::_type_handle; + +//////////////////////////////////////////////////////////////////// +// Function: ExternalThread::Constructor +// Access: Private +// Description: +//////////////////////////////////////////////////////////////////// +ExternalThread:: +ExternalThread() : Thread("External", "External") { + _started = true; +} + +//////////////////////////////////////////////////////////////////// +// Function: ExternalThread::thread_main +// Access: Private, Virtual +// Description: +//////////////////////////////////////////////////////////////////// +void ExternalThread:: +thread_main() { +} diff --git a/panda/src/express/externalThread.h b/panda/src/express/externalThread.h new file mode 100644 index 0000000000..67811ac143 --- /dev/null +++ b/panda/src/express/externalThread.h @@ -0,0 +1,56 @@ +// Filename: externalThread.h +// Created by: drose (30Jan06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#ifndef EXTERNALTHREAD_H +#define EXTERNALTHREAD_H + +#include "pandabase.h" +#include "thread.h" + +//////////////////////////////////////////////////////////////////// +// Class : ExternalThread +// Description : The special "external thread" class. There is one +// instance of these in the world, and it is returned by +// Thread::get_external_thread(). +//////////////////////////////////////////////////////////////////// +class EXPCL_PANDAEXPRESS ExternalThread : public Thread { +private: + ExternalThread(); + virtual void thread_main(); + +public: + static TypeHandle get_class_type() { + return _type_handle; + } + static void init_type() { + Thread::init_type(); + register_type(_type_handle, "ExternalThread", + Thread::get_class_type()); + } + virtual TypeHandle get_type() const { + return get_class_type(); + } + virtual TypeHandle force_init_type() {init_type(); return get_class_type();} + +private: + static TypeHandle _type_handle; + + friend class Thread; +}; + +#endif diff --git a/panda/src/express/mainThread.cxx b/panda/src/express/mainThread.cxx index 096595ab3a..125442da75 100644 --- a/panda/src/express/mainThread.cxx +++ b/panda/src/express/mainThread.cxx @@ -26,7 +26,8 @@ TypeHandle MainThread::_type_handle; // Description: //////////////////////////////////////////////////////////////////// MainThread:: -MainThread() : Thread("main") { +MainThread() : Thread("Main", "Main") { + _started = true; } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/express/thread.I b/panda/src/express/thread.I index aebf4026af..f4370efc4c 100644 --- a/panda/src/express/thread.I +++ b/panda/src/express/thread.I @@ -27,10 +27,22 @@ // terminate and destruct itself. // // Call start() to begin thread execution. +// +// The name should be unique for each thread (though +// this is not enforced, and not strictly required). +// The sync_name can be shared between multiple +// different threads; threads that run synchronously +// with each other should be given the same sync_name, +// for the benefit of PStats. //////////////////////////////////////////////////////////////////// INLINE Thread:: -Thread(const string &name) : _name(name), _impl(this) { +Thread(const string &name, const string &sync_name) : + _name(name), + _sync_name(sync_name), + _impl(this) +{ _started = false; + _pstats_index = -1; } //////////////////////////////////////////////////////////////////// @@ -56,13 +68,55 @@ operator = (const Thread ©) { //////////////////////////////////////////////////////////////////// // Function: Thread::get_name // Access: Public -// Description: Returns the name of the thread. +// Description: Returns the name of the thread. Each thread should +// probably have a unique name, but this is not enforced +// (and not strictly required). //////////////////////////////////////////////////////////////////// INLINE const string &Thread:: get_name() const { return _name; } +//////////////////////////////////////////////////////////////////// +// Function: Thread::get_sync_name +// Access: Public +// Description: Returns the sync name of the thread. This name +// collects threads into "sync groups", which are +// expected to run synchronously. This is mainly used +// for the benefit of PStats; threads with the same sync +// name can be ticked all at once via the thread_tick() +// call. +//////////////////////////////////////////////////////////////////// +INLINE const string &Thread:: +get_sync_name() const { + return _sync_name; +} + +//////////////////////////////////////////////////////////////////// +// Function: Thread::get_pstats_index +// Access: Public +// Description: Returns the PStats index associated with this thread, +// or -1 if no index has yet been associated with this +// thread. This is used internally by the PStatClient; +// you should not need to call this directly. +//////////////////////////////////////////////////////////////////// +INLINE int Thread:: +get_pstats_index() const { + return _pstats_index; +} + +//////////////////////////////////////////////////////////////////// +// Function: Thread::set_pstats_index +// Access: Public +// Description: Stores a PStats index to be associated with this +// thread. This is used internally by the PStatClient; +// you should not need to call this directly. +//////////////////////////////////////////////////////////////////// +INLINE void Thread:: +set_pstats_index(int pstats_index) { + _pstats_index = pstats_index; +} + //////////////////////////////////////////////////////////////////// // Function: Thread::start // Access: Public @@ -147,18 +201,47 @@ get_main_thread() { return _main_thread; } +//////////////////////////////////////////////////////////////////// +// Function: Thread::get_external_thread +// Access: Public, Static +// Description: Returns a pointer to the "external" Thread +// object--this is a special Thread object that +// corresponds to any thread spawned outside of Panda's +// threading interface. Note that multiple different +// threads may share this same pointer. +//////////////////////////////////////////////////////////////////// +INLINE Thread *Thread:: +get_external_thread() { + if (_external_thread == (Thread *)NULL) { + init_external_thread(); + } + return _external_thread; +} + //////////////////////////////////////////////////////////////////// // Function: Thread::get_current_thread // Access: Public, Static // Description: Returns a pointer to the currently-executing Thread // object. If this is called from the main thread, this -// will return the same value as get_main_thread(). If -// the current thread was spawned outside of Panda's -// threading system, this might return NULL. +// will return the same value as get_main_thread(). +// +// This will always return some valid Thread pointer. +// It will never return NULL, even if the current thread +// was spawned outside of Panda's threading system, +// although all non-Panda threads will return the exact +// same Thread pointer. //////////////////////////////////////////////////////////////////// INLINE Thread *Thread:: get_current_thread() { - return ThreadImpl::get_current_thread(); +#ifndef HAVE_THREADS + return get_main_thread(); +#else // HAVE_THREADS + Thread *thread = ThreadImpl::get_current_thread(); + if (thread == (Thread *)NULL) { + return Thread::get_external_thread(); + } + return thread; +#endif // HAVE_THREADS } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/express/thread.cxx b/panda/src/express/thread.cxx index 88815277a8..da134d7e94 100644 --- a/panda/src/express/thread.cxx +++ b/panda/src/express/thread.cxx @@ -18,8 +18,10 @@ #include "thread.h" #include "mainThread.h" +#include "externalThread.h" Thread *Thread::_main_thread; +Thread *Thread::_external_thread; TypeHandle Thread::_type_handle; //////////////////////////////////////////////////////////////////// @@ -51,6 +53,18 @@ void Thread:: init_main_thread() { if (_main_thread == (Thread *)NULL) { _main_thread = new MainThread; - ((Thread *)_main_thread)->_started = true; + } +} + +//////////////////////////////////////////////////////////////////// +// Function: Thread::init_external_thread +// Access: Private, Static +// Description: Creates the Thread object that represents all of the +// external threads. +//////////////////////////////////////////////////////////////////// +void Thread:: +init_external_thread() { + if (_external_thread == (Thread *)NULL) { + _external_thread = new ExternalThread; } } diff --git a/panda/src/express/thread.h b/panda/src/express/thread.h index 7a328f6565..98ed3d84d7 100644 --- a/panda/src/express/thread.h +++ b/panda/src/express/thread.h @@ -39,7 +39,7 @@ //////////////////////////////////////////////////////////////////// class EXPCL_PANDAEXPRESS Thread : public TypedReferenceCount { public: - INLINE Thread(const string &name); + INLINE Thread(const string &name, const string &sync_name); virtual ~Thread(); private: @@ -51,6 +51,10 @@ protected: public: INLINE const string &get_name() const; + INLINE const string &get_sync_name() const; + + INLINE int get_pstats_index() const; + INLINE void set_pstats_index(int pstats_index); INLINE bool start(ThreadPriority priority, bool global, bool joinable); INLINE void interrupt(); @@ -59,6 +63,7 @@ public: INLINE static void prepare_for_exit(); INLINE static Thread *get_main_thread(); + INLINE static Thread *get_external_thread(); INLINE static Thread *get_current_thread(); INLINE static bool is_threading_supported(); INLINE static void sleep(double seconds); @@ -67,12 +72,18 @@ public: private: static void init_main_thread(); + static void init_external_thread(); +protected: bool _started; string _name; + string _sync_name; ThreadImpl _impl; + int _pstats_index; +private: static Thread *_main_thread; + static Thread *_external_thread; public: static TypeHandle get_class_type() { diff --git a/panda/src/express/weakPointerTo.I b/panda/src/express/weakPointerTo.I index 8c2d0d435a..03d968b350 100644 --- a/panda/src/express/weakPointerTo.I +++ b/panda/src/express/weakPointerTo.I @@ -106,6 +106,18 @@ p() const { return (To *)WeakPointerToBase::_void_ptr; } +//////////////////////////////////////////////////////////////////// +// Function: WeakPointerTo::get_orig +// Access: Public +// Description: Returns the original pointer value, even if the +// object has since been deleted. +//////////////////////////////////////////////////////////////////// +template +INLINE TYPENAME WeakPointerTo::To *WeakPointerTo:: +get_orig() const { + return (To *)WeakPointerToBase::_void_ptr; +} + //////////////////////////////////////////////////////////////////// // Function: WeakPointerTo::Assignment operator // Access: Public @@ -258,6 +270,18 @@ p() const { return (To *)WeakPointerToBase::_void_ptr; } +//////////////////////////////////////////////////////////////////// +// Function: WeakConstPointerTo::get_orig +// Access: Public +// Description: Returns the original pointer value, even if the +// object has since been deleted. +//////////////////////////////////////////////////////////////////// +template +INLINE const TYPENAME WeakConstPointerTo::To *WeakConstPointerTo:: +get_orig() const { + return (To *)WeakPointerToBase::_void_ptr; +} + //////////////////////////////////////////////////////////////////// // Function: WeakConstPointerTo::Assignment operator // Access: Public diff --git a/panda/src/express/weakPointerTo.h b/panda/src/express/weakPointerTo.h index 351c79afd8..7e47bb022c 100644 --- a/panda/src/express/weakPointerTo.h +++ b/panda/src/express/weakPointerTo.h @@ -47,6 +47,7 @@ public: PUBLISHED: INLINE To *p() const; + INLINE To *get_orig() const; INLINE WeakPointerTo &operator = (To *ptr); INLINE WeakPointerTo &operator = (const PointerTo ©); @@ -84,6 +85,7 @@ public: PUBLISHED: INLINE const To *p() const; + INLINE const To *get_orig() const; INLINE WeakConstPointerTo &operator = (const To *ptr); INLINE WeakConstPointerTo &operator = (const PointerTo ©); diff --git a/panda/src/pstatclient/Sources.pp b/panda/src/pstatclient/Sources.pp index 040e9185d3..0620dd6eaf 100644 --- a/panda/src/pstatclient/Sources.pp +++ b/panda/src/pstatclient/Sources.pp @@ -24,7 +24,8 @@ pStatClientVersion.cxx \ pStatClientControlMessage.cxx pStatCollectorDef.cxx \ pStatFrameData.cxx pStatProperties.cxx \ - pStatServerControlMessage.cxx + pStatServerControlMessage.cxx \ + pStatThread.cxx #define INSTALL_HEADERS \ config_pstats.h pStatClient.I pStatClient.h \ diff --git a/panda/src/pstatclient/pStatClient.I b/panda/src/pstatclient/pStatClient.I index 3c34cc9b08..8e94408f84 100644 --- a/panda/src/pstatclient/pStatClient.I +++ b/panda/src/pstatclient/pStatClient.I @@ -120,6 +120,34 @@ get_thread_name(int index) const { return _threads[index]._name; } +//////////////////////////////////////////////////////////////////// +// Function: PStatClient::get_thread_sync_name +// Access: Published +// Description: Returns the sync_name of the indicated thread. +//////////////////////////////////////////////////////////////////// +INLINE string PStatClient:: +get_thread_sync_name(int index) const { + ReMutexHolder holder(_lock); + nassertr(index >= 0 && index < (int)_threads.size(), string()); + return _threads[index]._sync_name; +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClient::get_thread_object +// Access: Published +// Description: Returns the Panda Thread object associated with the +// indicated PStatThread. +//////////////////////////////////////////////////////////////////// +INLINE Thread *PStatClient:: +get_thread_object(int index) const { + ReMutexHolder holder(_lock); + nassertr(index >= 0 && index < (int)_threads.size(), NULL); + if (_threads[index]._thread.was_deleted()) { + return NULL; + } + return _threads[index]._thread; +} + //////////////////////////////////////////////////////////////////// // Function: PStatClient::get_clock // Access: Published diff --git a/panda/src/pstatclient/pStatClient.cxx b/panda/src/pstatclient/pStatClient.cxx index 5c5478f49f..b80ad757ae 100644 --- a/panda/src/pstatclient/pStatClient.cxx +++ b/panda/src/pstatclient/pStatClient.cxx @@ -29,6 +29,7 @@ #include "pStatThread.h" #include "config_pstats.h" #include "pStatProperties.h" +#include "thread.h" PStatCollector PStatClient::_total_size_pcollector("Memory usage"); PStatCollector PStatClient::_cpp_size_pcollector("Memory usage:C++"); @@ -68,8 +69,8 @@ PStatClient() : //collector._def->_suggested_color.set(0.5, 0.5, 0.5); _collectors.push_back(collector); - // We also always have a thread at index 0 named "Main". - make_thread("Main"); + // The main thread is always at index 0. + make_thread(Thread::get_main_thread()); } //////////////////////////////////////////////////////////////////// @@ -148,20 +149,40 @@ get_thread(int index) const { //////////////////////////////////////////////////////////////////// // Function: PStatClient::get_main_thread // Access: Published -// Description: Returns a handle to the client's "Main", or default, -// thread. This is where collectors will be started and -// stopped if they don't specify otherwise. +// Description: Returns a handle to the client's Main thread. This +// is the thread that started the application. //////////////////////////////////////////////////////////////////// PStatThread PStatClient:: get_main_thread() const { return PStatThread((PStatClient *)this, 0); } +//////////////////////////////////////////////////////////////////// +// Function: PStatClient::get_current_thread +// Access: Published +// Description: Returns a handle to the currently-executing thread. +// This is the thread that PStatCollectors will be +// counted in if they do not specify otherwise. +//////////////////////////////////////////////////////////////////// +PStatThread PStatClient:: +get_current_thread() const { + Thread *thread = Thread::get_current_thread(); + int thread_index = thread->get_pstats_index(); + if (thread_index != -1) { + return PStatThread((PStatClient *)this, thread_index); + } + + // This is the first time we have encountered this current Thread. + // Make a new PStatThread object for it. + return ((PStatClient *)this)->make_thread(thread); +} + //////////////////////////////////////////////////////////////////// // Function: PStatClient::main_tick // Access: Published, Static // Description: A convenience function to call new_frame() on the -// global PStatClient's main thread. +// global PStatClient's main thread, and any other +// threads with a sync_name of "Main". //////////////////////////////////////////////////////////////////// void PStatClient:: main_tick() { @@ -185,19 +206,65 @@ main_tick() { get_global_pstats()->client_main_tick(); } +//////////////////////////////////////////////////////////////////// +// Function: PStatClient::thread_tick +// Access: Published, Static +// Description: A convenience function to call new_frame() on any +// threads with the indicated sync_name +//////////////////////////////////////////////////////////////////// +void PStatClient:: +thread_tick(const string &sync_name) { + get_global_pstats()->client_thread_tick(sync_name); +} + //////////////////////////////////////////////////////////////////// // Function: PStatClient::client_main_tick // Access: Published // Description: A convenience function to call new_frame() on the -// the given client's main thread. +// given PStatClient's main thread, and any other +// threads with a sync_name of "Main". //////////////////////////////////////////////////////////////////// void PStatClient:: client_main_tick() { ReMutexHolder holder(_lock); if (has_impl()) { _impl->client_main_tick(); + + MultiThingsByName::const_iterator ni = + _threads_by_sync_name.find("Main"); + if (ni != _threads_by_sync_name.end()) { + const vector_int &indices = (*ni).second; + for (vector_int::const_iterator vi = indices.begin(); + vi != indices.end(); + ++vi) { + _impl->new_frame(*vi); + } + } + } +} + +//////////////////////////////////////////////////////////////////// +// Function: PStatClient::client_thread_tick +// Access: Published +// Description: A convenience function to call new_frame() on all of +// the threads with the indicated sync name. +//////////////////////////////////////////////////////////////////// +void PStatClient:: +client_thread_tick(const string &sync_name) { + ReMutexHolder holder(_lock); + + if (has_impl()) { + MultiThingsByName::const_iterator ni = + _threads_by_sync_name.find(sync_name); + if (ni != _threads_by_sync_name.end()) { + const vector_int &indices = (*ni).second; + for (vector_int::const_iterator vi = indices.begin(); + vi != indices.end(); + ++vi) { + _impl->new_frame(*vi); + } + } } - get_main_thread().new_frame(); } //////////////////////////////////////////////////////////////////// @@ -347,35 +414,56 @@ make_collector_with_name(int parent_index, const string &name) { //////////////////////////////////////////////////////////////////// // Function: PStatClient::make_thread // Access: Private -// Description: Returns a PStatThread with the indicated name -// suitable for grouping collectors. This is normally -// called by a PStatThread constructor. +// Description: Returns a PStatThread for the indicated Panda Thread +// object. This is normally called by a PStatThread +// constructor. //////////////////////////////////////////////////////////////////// PStatThread PStatClient:: -make_thread(const string &name) { +make_thread(Thread *thread) { ReMutexHolder holder(_lock); - ThingsByName::const_iterator ni = - _threads_by_name.find(name); - - if (ni != _threads_by_name.end()) { - // We already had a thread by this name; return it. - int index = (*ni).second; - nassertr(index >= 0 && index < (int)_threads.size(), PStatThread()); - return PStatThread(this, (*ni).second); + int thread_index = thread->get_pstats_index(); + if (thread_index != -1) { + return PStatThread((PStatClient *)this, thread_index); } - // Create a new thread for this name. + MultiThingsByName::const_iterator ni = + _threads_by_name.find(thread->get_name()); + + if (ni != _threads_by_name.end()) { + // We have seen a thread with this name before. Can we re-use any + // of them? + const vector_int &indices = (*ni).second; + for (vector_int::const_iterator vi = indices.begin(); + vi != indices.end(); + ++vi) { + int index = (*vi); + nassertr(index >= 0 && index < (int)_threads.size(), PStatThread()); + if (_threads[index]._thread.was_deleted() && + _threads[index]._sync_name == thread->get_sync_name()) { + // Yes, re-use this one. + _threads[index]._thread = thread; + thread->set_pstats_index(index); + return PStatThread(this, index); + } + } + } + + // Create a new PStatsThread for this thread pointer. int new_index = _threads.size(); - _threads_by_name.insert(ThingsByName::value_type(name, new_index)); + thread->set_pstats_index(new_index); + _threads_by_name[thread->get_name()].push_back(new_index); + _threads_by_sync_name[thread->get_sync_name()].push_back(new_index); + + InternalThread pthread; + pthread._thread = thread; + pthread._name = thread->get_name(); + pthread._sync_name = thread->get_sync_name(); + pthread._is_active = false; + pthread._next_packet = 0.0; + pthread._frame_number = 0; - Thread thread; - thread._name = name; - thread._is_active = false; - thread._next_packet = 0.0; - thread._frame_number = 0; - - _threads.push_back(thread); + _threads.push_back(pthread); // We need an additional PerThreadData for this thread in all of the // collectors. @@ -508,10 +596,12 @@ stop(int collector_index, int thread_index) { _collectors[collector_index].is_active() && _threads[thread_index]._is_active) { if (_collectors[collector_index]._per_thread[thread_index]._nested_count == 0) { - pstats_cat.warning() - << "Collector " << get_collector_fullname(collector_index) - << " was already stopped in thread " << get_thread_name(thread_index) - << "!\n"; + if (pstats_cat.is_debug()) { + pstats_cat.debug() + << "Collector " << get_collector_fullname(collector_index) + << " was already stopped in thread " << get_thread_name(thread_index) + << "!\n"; + } return; } @@ -546,10 +636,12 @@ stop(int collector_index, int thread_index, float as_of) { _collectors[collector_index].is_active() && _threads[thread_index]._is_active) { if (_collectors[collector_index]._per_thread[thread_index]._nested_count == 0) { - pstats_cat.warning() - << "Collector " << get_collector_fullname(collector_index) - << " was already stopped in thread " << get_thread_name(thread_index) - << "!\n"; + if (pstats_cat.is_debug()) { + pstats_cat.debug() + << "Collector " << get_collector_fullname(collector_index) + << " was already stopped in thread " << get_thread_name(thread_index) + << "!\n"; + } return; } diff --git a/panda/src/pstatclient/pStatClient.h b/panda/src/pstatclient/pStatClient.h index ee3436ee4e..3c2037b583 100644 --- a/panda/src/pstatclient/pStatClient.h +++ b/panda/src/pstatclient/pStatClient.h @@ -28,6 +28,9 @@ #include "reMutexHolder.h" #include "luse.h" #include "pmap.h" +#include "thread.h" +#include "weakPointerTo.h" +#include "vector_int.h" class PStatCollector; class PStatCollectorDef; @@ -71,8 +74,11 @@ PUBLISHED: INLINE int get_num_threads() const; PStatThread get_thread(int index) const; INLINE string get_thread_name(int index) const; + INLINE string get_thread_sync_name(int index) const; + INLINE Thread *get_thread_object(int index) const; PStatThread get_main_thread() const; + PStatThread get_current_thread() const; INLINE const ClockObject &get_clock() const; @@ -83,8 +89,10 @@ PUBLISHED: INLINE static void resume_after_pause(); static void main_tick(); + static void thread_tick(const string &sync_name); void client_main_tick(); + void client_thread_tick(const string &sync_name); INLINE bool client_connect(string hostname, int port); void client_disconnect(); INLINE bool client_is_connected() const; @@ -100,7 +108,7 @@ private: PStatCollector make_collector_with_relname(int parent_index, string relname); PStatCollector make_collector_with_name(int parent_index, const string &name); - PStatThread make_thread(const string &name); + PStatThread make_thread(Thread *thread); bool is_active(int collector_index, int thread_index) const; bool is_started(int collector_index, int thread_index) const; @@ -119,9 +127,9 @@ private: // This mutex protects everything in this class. ReMutex _lock; - // Not a phash_map, so the threads remain sorted by name. typedef pmap ThingsByName; - ThingsByName _threads_by_name; + typedef pmap MultiThingsByName; + MultiThingsByName _threads_by_name, _threads_by_sync_name; // This is for the data that is per-collector, per-thread. A vector // of these is stored in each Collector object, below, indexed by @@ -169,15 +177,17 @@ private: // This defines a single thread, i.e. a separate chain of execution, // independent of all other threads. Timing and level data are // maintained separately for each thread. - class Thread { + class InternalThread { public: + WPT(Thread) _thread; string _name; + string _sync_name; PStatFrameData _frame_data; bool _is_active; int _frame_number; float _next_packet; }; - typedef pvector Threads; + typedef pvector Threads; Threads _threads; PStatClientImpl *_impl; diff --git a/panda/src/pstatclient/pStatClientImpl.cxx b/panda/src/pstatclient/pStatClientImpl.cxx index f72aa44c6a..6ab4d668ae 100644 --- a/panda/src/pstatclient/pStatClientImpl.cxx +++ b/panda/src/pstatclient/pStatClientImpl.cxx @@ -168,7 +168,7 @@ void PStatClientImpl:: new_frame(int thread_index) { nassertv(thread_index >= 0 && thread_index < (int)_client->_threads.size()); - PStatClient::Thread &thread = _client->_threads[thread_index]; + PStatClient::InternalThread &pthread = _client->_threads[thread_index]; // If we're the main thread, we should exchange control packets with // the server. @@ -179,34 +179,34 @@ new_frame(int thread_index) { // If we've got the UDP port by the time the frame starts, it's // time to become active and start actually tracking data. if (_got_udp_port) { - thread._is_active = true; + pthread._is_active = true; } - if (!thread._is_active) { + if (!pthread._is_active) { return; } float frame_start = _clock.get_real_time(); - if (!thread._frame_data.is_empty()) { + if (!pthread._frame_data.is_empty()) { // Collector 0 is the whole frame. _client->stop(0, thread_index, frame_start); // Fill up the level data for all the collectors who have level - // data for this thread. + // data for this pthread. int num_collectors = _client->_collectors.size(); for (int i = 0; i < num_collectors; i++) { const PStatClient::PerThreadData &ptd = _client->_collectors[i]._per_thread[thread_index]; if (ptd._has_level) { - thread._frame_data.add_level(i, ptd._level); + pthread._frame_data.add_level(i, ptd._level); } } transmit_frame_data(thread_index); } - thread._frame_data.clear(); - thread._frame_number++; + pthread._frame_data.clear(); + pthread._frame_number++; _client->start(0, thread_index, frame_start); // Also record the time for the PStats operation itself. diff --git a/panda/src/pstatclient/pStatCollector.I b/panda/src/pstatclient/pStatCollector.I index 4e3079ff66..2fe54f6a3a 100644 --- a/panda/src/pstatclient/pStatCollector.I +++ b/panda/src/pstatclient/pStatCollector.I @@ -134,7 +134,11 @@ operator = (const PStatCollector ©) { //////////////////////////////////////////////////////////////////// INLINE bool PStatCollector:: is_active() { +#ifndef HAVE_THREADS return _client->is_active(_index, 0); +#else // HAVE_THREADS + return is_active(_client->get_current_thread()); +#endif // HAVE_THREADS } //////////////////////////////////////////////////////////////////// @@ -145,7 +149,11 @@ is_active() { //////////////////////////////////////////////////////////////////// INLINE bool PStatCollector:: is_started() { +#ifndef HAVE_THREADS return _client->is_started(_index, 0); +#else // HAVE_THREADS + return is_started(_client->get_current_thread()); +#endif // HAVE_THREADS } //////////////////////////////////////////////////////////////////// @@ -156,7 +164,11 @@ is_started() { //////////////////////////////////////////////////////////////////// INLINE void PStatCollector:: start() { +#ifndef HAVE_THREADS _client->start(_index, 0); +#else // HAVE_THREADS + start(_client->get_current_thread()); +#endif // HAVE_THREADS } //////////////////////////////////////////////////////////////////// @@ -167,7 +179,11 @@ start() { //////////////////////////////////////////////////////////////////// INLINE void PStatCollector:: stop() { +#ifndef HAVE_THREADS _client->stop(_index, 0); +#else // HAVE_THREADS + stop(_client->get_current_thread()); +#endif // HAVE_THREADS } //////////////////////////////////////////////////////////////////// @@ -180,7 +196,11 @@ stop() { //////////////////////////////////////////////////////////////////// INLINE void PStatCollector:: clear_level() { +#ifndef HAVE_THREADS _client->clear_level(_index, 0); +#else // HAVE_THREADS + clear_level(_client->get_current_thread()); +#endif // HAVE_THREADS } //////////////////////////////////////////////////////////////////// @@ -192,7 +212,11 @@ clear_level() { //////////////////////////////////////////////////////////////////// INLINE void PStatCollector:: set_level(float level) { +#ifndef HAVE_THREADS _client->set_level(_index, 0, level); +#else // HAVE_THREADS + set_level(_client->get_current_thread(), level); +#endif // HAVE_THREADS } //////////////////////////////////////////////////////////////////// @@ -206,7 +230,11 @@ set_level(float level) { //////////////////////////////////////////////////////////////////// INLINE void PStatCollector:: add_level(float increment) { +#ifndef HAVE_THREADS _client->add_level(_index, 0, increment); +#else // HAVE_THREADS + add_level(_client->get_current_thread(), increment); +#endif // HAVE_THREADS } //////////////////////////////////////////////////////////////////// @@ -220,7 +248,11 @@ add_level(float increment) { //////////////////////////////////////////////////////////////////// INLINE void PStatCollector:: sub_level(float decrement) { - _client->add_level(_index, 0, -decrement); +#ifndef HAVE_THREADS + _client->sub_level(_index, 0, decrement); +#else // HAVE_THREADS + sub_level(_client->get_current_thread(), decrement); +#endif // HAVE_THREADS } //////////////////////////////////////////////////////////////////// @@ -230,12 +262,16 @@ sub_level(float decrement) { //////////////////////////////////////////////////////////////////// INLINE float PStatCollector:: get_level() { +#ifndef HAVE_THREADS return _client->get_level(_index, 0); +#else // HAVE_THREADS + return get_level(_client->get_current_thread()); +#endif // HAVE_THREADS } //////////////////////////////////////////////////////////////////// // Function: PStatCollector::is_active -// Access: Public +// Access: Published // Description: Returns true if this particular collector is active // on the indicated thread, and we are currently // transmitting PStats data. @@ -247,7 +283,7 @@ is_active(const PStatThread &thread) { //////////////////////////////////////////////////////////////////// // Function: PStatCollector::is_started -// Access: Public +// Access: Published // Description: Returns true if this particular collector has been // started on the indicated thread, or false otherwise. //////////////////////////////////////////////////////////////////// @@ -258,7 +294,7 @@ is_started(const PStatThread &thread) { //////////////////////////////////////////////////////////////////// // Function: PStatCollector::start -// Access: Public +// Access: Published // Description: Starts this timer ticking within a particular thread. //////////////////////////////////////////////////////////////////// INLINE void PStatCollector:: @@ -268,7 +304,7 @@ start(const PStatThread &thread) { //////////////////////////////////////////////////////////////////// // Function: PStatCollector::start -// Access: Public +// Access: Published // Description: Marks that the timer should have been started as of // the indicated time. This must be a time based on the // PStatClient's clock (see PStatClient::get_clock()), @@ -282,7 +318,7 @@ start(const PStatThread &thread, float as_of) { //////////////////////////////////////////////////////////////////// // Function: PStatCollector::stop -// Access: Public +// Access: Published // Description: Stops this timer within a particular thread. //////////////////////////////////////////////////////////////////// INLINE void PStatCollector:: @@ -292,7 +328,7 @@ stop(const PStatThread &thread) { //////////////////////////////////////////////////////////////////// // Function: PStatCollector::stop -// Access: Public +// Access: Published // Description: Marks that the timer should have been stopped as of // the indicated time. This must be a time based on the // PStatClient's clock (see PStatClient::get_clock()), @@ -306,7 +342,7 @@ stop(const PStatThread &thread, float as_of) { //////////////////////////////////////////////////////////////////// // Function: PStatCollector::clear_level -// Access: Public +// Access: Published // Description: Removes the level setting associated with this // collector for the indicated thread. The collector // will no longer show up on any level graphs in this @@ -319,7 +355,7 @@ clear_level(const PStatThread &thread) { //////////////////////////////////////////////////////////////////// // Function: PStatCollector::set_level -// Access: Public +// Access: Published // Description: Sets the level setting associated with this // collector for the indicated thread to the indicated // value. @@ -331,7 +367,7 @@ set_level(const PStatThread &thread, float level) { //////////////////////////////////////////////////////////////////// // Function: PStatCollector::add_level -// Access: Public +// Access: Published // Description: Adds the indicated increment (which may be negative) // to the level setting associated with this collector // for the indicated thread. If the collector did not @@ -345,7 +381,7 @@ add_level(const PStatThread &thread, float increment) { //////////////////////////////////////////////////////////////////// // Function: PStatCollector::sub_level -// Access: Public +// Access: Published // Description: Subtracts the indicated decrement (which may be // negative) to the level setting associated with this // collector for the indicated thread. If the collector @@ -359,7 +395,7 @@ sub_level(const PStatThread &thread, float decrement) { //////////////////////////////////////////////////////////////////// // Function: PStatCollector::get_level -// Access: Public +// Access: Published // Description: Returns the current level value of the given collector. //////////////////////////////////////////////////////////////////// INLINE float PStatCollector:: @@ -369,7 +405,7 @@ get_level(const PStatThread &thread) { //////////////////////////////////////////////////////////////////// // Function: PStatCollector::get_index -// Access: Public +// Access: Published // Description: Returns the index number of this particular collector // within the PStatClient. //////////////////////////////////////////////////////////////////// diff --git a/panda/src/pstatclient/pStatCollector.h b/panda/src/pstatclient/pStatCollector.h index 1fdc035823..f446ba3801 100644 --- a/panda/src/pstatclient/pStatCollector.h +++ b/panda/src/pstatclient/pStatCollector.h @@ -77,9 +77,6 @@ PUBLISHED: INLINE void sub_level(float decrement); INLINE float get_level(); - INLINE int get_index() const; - -public: INLINE bool is_active(const PStatThread &thread); INLINE bool is_started(const PStatThread &thread); INLINE void start(const PStatThread &thread); @@ -93,6 +90,8 @@ public: INLINE void sub_level(const PStatThread &thread, float decrement); INLINE float get_level(const PStatThread &thread); + INLINE int get_index() const; + private: PStatClient *_client; int _index; @@ -117,7 +116,6 @@ PUBLISHED: INLINE void sub_level(float) { } INLINE float get_level() { return 0.0; } -public: INLINE bool is_active(const PStatThread &) { return false; } INLINE void start(const PStatThread &) { } INLINE void start(const PStatThread &, float) { } @@ -130,6 +128,8 @@ public: INLINE void sub_level(const PStatThread &, float) { } INLINE float get_level(const PStatThread &) { return 0.0; } + INLINE int get_index() const { return 0; } + #endif // DO_PSTATS }; diff --git a/panda/src/pstatclient/pStatProperties.cxx b/panda/src/pstatclient/pStatProperties.cxx index 00efb54501..873b7a472d 100644 --- a/panda/src/pstatclient/pStatProperties.cxx +++ b/panda/src/pstatclient/pStatProperties.cxx @@ -109,6 +109,7 @@ struct LevelCollectorProperties { }; static TimeCollectorProperties time_properties[] = { + { 1, "Wait", { 0.6, 0.6, 0.6 } }, { 1, "App", { 0.0, 0.8, 0.4 }, 1.0 / 30.0 }, { 1, "App:Collisions", { 1.0, 0.5, 0.0 } }, { 1, "App:Collisions:Reset", { 0.0, 0.0, 0.5 } }, diff --git a/panda/src/pstatclient/pStatThread.I b/panda/src/pstatclient/pStatThread.I index c5f02ece98..b89ed2907e 100644 --- a/panda/src/pstatclient/pStatThread.I +++ b/panda/src/pstatclient/pStatThread.I @@ -48,17 +48,15 @@ PStatThread(PStatClient *client, int index) : // Description: Creates a new named thread. This will be used to // unify tasks that share a common thread, and // differentiate tasks that occur in different threads. -// If any two different PStatThread objects share the -// same name, then they are really the same thread. //////////////////////////////////////////////////////////////////// INLINE PStatThread:: -PStatThread(const string &name, PStatClient *client) { +PStatThread(Thread *thread, PStatClient *client) { #ifdef DO_PSTATS if (client == (PStatClient *)NULL) { client = PStatClient::get_global_pstats(); } - PStatThread thread(client->make_thread(name)); - (*this) = thread; + PStatThread pthread(client->make_thread(thread)); + (*this) = pthread; #else _client = (PStatClient *)NULL; _index = 0; @@ -95,6 +93,10 @@ operator = (const PStatThread ©) { // whatever a frame may be deemed to be, to accumulate // all the stats that have collected so far for the // thread and ship them off to the server. +// +// Calling PStatClient::thread_tick() will automatically +// call this for any threads with the indicated sync +// name. //////////////////////////////////////////////////////////////////// INLINE void PStatThread:: new_frame() { @@ -102,3 +104,14 @@ new_frame() { _client->get_impl()->new_frame(_index); #endif } + +//////////////////////////////////////////////////////////////////// +// Function: PStatThread::get_index +// Access: Published +// Description: Returns the index number of this particular thread +// within the PStatClient. +//////////////////////////////////////////////////////////////////// +INLINE int PStatThread:: +get_index() const { + return _index; +} diff --git a/panda/src/pstatclient/pStatThread.cxx b/panda/src/pstatclient/pStatThread.cxx new file mode 100644 index 0000000000..eef0b8de60 --- /dev/null +++ b/panda/src/pstatclient/pStatThread.cxx @@ -0,0 +1,31 @@ +// Filename: pStatThread.cxx +// Created by: drose (30Jan06) +// +//////////////////////////////////////////////////////////////////// +// +// PANDA 3D SOFTWARE +// Copyright (c) 2001 - 2004, Disney Enterprises, Inc. All rights reserved +// +// All use of this software is subject to the terms of the Panda 3d +// Software license. You should have received a copy of this license +// along with this source code; you will also find a current copy of +// the license at http://etc.cmu.edu/panda3d/docs/license/ . +// +// To contact the maintainers of this program write to +// panda3d-general@lists.sourceforge.net . +// +//////////////////////////////////////////////////////////////////// + +#include "pStatThread.h" +#include "pStatClient.h" + +//////////////////////////////////////////////////////////////////// +// Function: PStatThread::get_thread +// Access: Published +// Description: Returns the Panda Thread object associated with this +// particular PStatThread. +//////////////////////////////////////////////////////////////////// +INLINE Thread *PStatThread:: +get_thread() const { + return _client->get_thread_object(_index); +} diff --git a/panda/src/pstatclient/pStatThread.h b/panda/src/pstatclient/pStatThread.h index 997f9d2327..80c9426afa 100644 --- a/panda/src/pstatclient/pStatThread.h +++ b/panda/src/pstatclient/pStatThread.h @@ -23,29 +23,30 @@ #include "pStatClient.h" +class Thread; + //////////////////////////////////////////////////////////////////// // Class : PStatThread // Description : A lightweight class that represents a single thread -// of execution to PStats. It doesn't have any real -// connection to any actual threads, but it's used to -// differentiate tasks which run in different threads, -// so we can measure them properly. It is presently the -// user's responsibility to correctly differentiate the -// various threads. +// of execution to PStats. It corresponds one-to-one +// with Panda's Thread instance. //////////////////////////////////////////////////////////////////// class EXPCL_PANDA PStatThread { private: INLINE PStatThread(); INLINE PStatThread(PStatClient *client, int index); -public: - INLINE PStatThread(const string &name, PStatClient *client = NULL); +PUBLISHED: + INLINE PStatThread(Thread *thread, PStatClient *client = NULL); INLINE PStatThread(const PStatThread ©); INLINE void operator = (const PStatThread ©); INLINE void new_frame(); + Thread *get_thread() const; + INLINE int get_index() const; + private: PStatClient *_client; int _index; diff --git a/panda/src/pstatclient/pstatclient_composite2.cxx b/panda/src/pstatclient/pstatclient_composite2.cxx index 4aecd39f9e..db8ae82c96 100644 --- a/panda/src/pstatclient/pstatclient_composite2.cxx +++ b/panda/src/pstatclient/pstatclient_composite2.cxx @@ -3,3 +3,4 @@ #include "pStatFrameData.cxx" #include "pStatProperties.cxx" #include "pStatServerControlMessage.cxx" +#include "pStatThread.cxx"