OVR_ThreadsPthread.cpp
Go to the documentation of this file.
00001 
00002 #include "OVR_Threads.h"
00003 #include "OVR_Hash.h"
00004 
00005 #ifdef OVR_ENABLE_THREADS
00006 
00007 #include "OVR_Timer.h"
00008 #include "OVR_Log.h"
00009 
00010 #include <pthread.h>
00011 #include <time.h>
00012 
00013 #ifdef OVR_OS_PS3
00014 #include <sys/sys_time.h>
00015 #include <sys/timer.h>
00016 #include <sys/synchronization.h>
00017 #define sleep(x) sys_timer_sleep(x)
00018 #define usleep(x) sys_timer_usleep(x)
00019 using std::timespec;
00020 #else
00021 #include <unistd.h>
00022 #include <sys/time.h>
00023 #include <errno.h>
00024 #endif
00025 
00026 namespace OVR {
00027 
00028 // ***** Mutex implementation
00029 
00030 
00031 // *** Internal Mutex implementation structure
00032 
00033 class MutexImpl : public NewOverrideBase
00034 {
00035     // System mutex or semaphore
00036     pthread_mutex_t   SMutex;
00037     bool          Recursive;
00038     unsigned      LockCount;
00039     pthread_t     LockedBy;
00040 
00041     friend class WaitConditionImpl;
00042 
00043 public:
00044     // Constructor/destructor
00045     MutexImpl(Mutex* pmutex, bool recursive = 1);
00046     ~MutexImpl();
00047 
00048     // Locking functions
00049     void                DoLock();
00050     bool                TryLock();
00051     void                Unlock(Mutex* pmutex);
00052     // Returns 1 if the mutes is currently locked
00053     bool                IsLockedByAnotherThread(Mutex* pmutex);        
00054     bool                IsSignaled() const;
00055 };
00056 
00057 pthread_mutexattr_t Lock::RecursiveAttr;
00058 bool Lock::RecursiveAttrInit = 0;
00059 
00060 // *** Constructor/destructor
00061 MutexImpl::MutexImpl(Mutex* pmutex, bool recursive)
00062 {   
00063     Recursive           = recursive;
00064     LockCount           = 0;
00065 
00066     if (Recursive)
00067     {
00068         if (!Lock::RecursiveAttrInit)
00069         {
00070             pthread_mutexattr_init(&Lock::RecursiveAttr);
00071             pthread_mutexattr_settype(&Lock::RecursiveAttr, PTHREAD_MUTEX_RECURSIVE);
00072             Lock::RecursiveAttrInit = 1;
00073         }
00074 
00075         pthread_mutex_init(&SMutex, &Lock::RecursiveAttr);
00076     }
00077     else
00078         pthread_mutex_init(&SMutex, 0);
00079 }
00080 
00081 MutexImpl::~MutexImpl()
00082 {
00083     pthread_mutex_destroy(&SMutex);
00084 }
00085 
00086 
00087 // Lock and try lock
00088 void MutexImpl::DoLock()
00089 {
00090     while (pthread_mutex_lock(&SMutex));
00091     LockCount++;
00092     LockedBy = pthread_self();
00093 }
00094 
00095 bool MutexImpl::TryLock()
00096 {
00097     if (!pthread_mutex_trylock(&SMutex))
00098     {
00099         LockCount++;
00100         LockedBy = pthread_self();
00101         return 1;
00102     }
00103     
00104     return 0;
00105 }
00106 
00107 void MutexImpl::Unlock(Mutex* pmutex)
00108 {
00109     OVR_ASSERT(pthread_self() == LockedBy && LockCount > 0);
00110 
00111     unsigned lockCount;
00112     LockCount--;
00113     lockCount = LockCount;
00114 
00115     pthread_mutex_unlock(&SMutex);
00116 }
00117 
00118 bool    MutexImpl::IsLockedByAnotherThread(Mutex* pmutex)
00119 {
00120     // There could be multiple interpretations of IsLocked with respect to current thread
00121     if (LockCount == 0)
00122         return 0;
00123     if (pthread_self() != LockedBy)
00124         return 1;
00125     return 0;
00126 }
00127 
00128 bool    MutexImpl::IsSignaled() const
00129 {
00130     // An mutex is signaled if it is not locked ANYWHERE
00131     // Note that this is different from IsLockedByAnotherThread function,
00132     // that takes current thread into account
00133     return LockCount == 0;
00134 }
00135 
00136 
00137 // *** Actual Mutex class implementation
00138 
00139 Mutex::Mutex(bool recursive)
00140 {
00141     // NOTE: RefCount mode already thread-safe for all waitables.
00142     pImpl = new MutexImpl(this, recursive);
00143 }
00144 
00145 Mutex::~Mutex()
00146 {
00147     delete pImpl;
00148 }
00149 
00150 // Lock and try lock
00151 void Mutex::DoLock()
00152 {
00153     pImpl->DoLock();
00154 }
00155 bool Mutex::TryLock()
00156 {
00157     return pImpl->TryLock();
00158 }
00159 void Mutex::Unlock()
00160 {
00161     pImpl->Unlock(this);
00162 }
00163 bool    Mutex::IsLockedByAnotherThread()
00164 {
00165     return pImpl->IsLockedByAnotherThread(this);
00166 }
00167 
00168 
00169 
00170 //-----------------------------------------------------------------------------------
00171 // ***** Event
00172 
00173 bool Event::Wait(unsigned delay)
00174 {
00175     Mutex::Locker lock(&StateMutex);
00176 
00177     // Do the correct amount of waiting
00178     if (delay == OVR_WAIT_INFINITE)
00179     {
00180         while(!State)
00181             StateWaitCondition.Wait(&StateMutex);
00182     }
00183     else if (delay)
00184     {
00185         if (!State)
00186             StateWaitCondition.Wait(&StateMutex, delay);
00187     }
00188 
00189     bool state = State;
00190     // Take care of temporary 'pulsing' of a state
00191     if (Temporary)
00192     {
00193         Temporary   = false;
00194         State       = false;
00195     }
00196     return state;
00197 }
00198 
00199 void Event::updateState(bool newState, bool newTemp, bool mustNotify)
00200 {
00201     Mutex::Locker lock(&StateMutex);
00202     State       = newState;
00203     Temporary   = newTemp;
00204     if (mustNotify)
00205         StateWaitCondition.NotifyAll();    
00206 }
00207 
00208 
00209 
00210 // ***** Wait Condition Implementation
00211 
00212 // Internal implementation class
00213 class WaitConditionImpl : public NewOverrideBase
00214 {
00215     pthread_mutex_t     SMutex;
00216     pthread_cond_t      Condv;
00217 
00218 public:
00219 
00220     // Constructor/destructor
00221     WaitConditionImpl();
00222     ~WaitConditionImpl();
00223 
00224     // Release mutex and wait for condition. The mutex is re-aqured after the wait.
00225     bool    Wait(Mutex *pmutex, unsigned delay = OVR_WAIT_INFINITE);
00226 
00227     // Notify a condition, releasing at one object waiting
00228     void    Notify();
00229     // Notify a condition, releasing all objects waiting
00230     void    NotifyAll();
00231 };
00232 
00233 
00234 WaitConditionImpl::WaitConditionImpl()
00235 {
00236     pthread_mutex_init(&SMutex, 0);
00237     pthread_cond_init(&Condv, 0);
00238 }
00239 
00240 WaitConditionImpl::~WaitConditionImpl()
00241 {
00242     pthread_mutex_destroy(&SMutex);
00243     pthread_cond_destroy(&Condv);
00244 }    
00245 
00246 bool    WaitConditionImpl::Wait(Mutex *pmutex, unsigned delay)
00247 {
00248     bool            result = 1;
00249     unsigned            lockCount = pmutex->pImpl->LockCount;
00250 
00251     // Mutex must have been locked
00252     if (lockCount == 0)
00253         return 0;
00254 
00255     pthread_mutex_lock(&SMutex);
00256 
00257     // Finally, release a mutex or semaphore
00258     if (pmutex->pImpl->Recursive)
00259     {
00260         // Release the recursive mutex N times
00261         pmutex->pImpl->LockCount = 0;
00262         for(unsigned i=0; i<lockCount; i++)
00263             pthread_mutex_unlock(&pmutex->pImpl->SMutex);
00264     }
00265     else
00266     {
00267         pmutex->pImpl->LockCount = 0;
00268         pthread_mutex_unlock(&pmutex->pImpl->SMutex);
00269     }
00270 
00271     // Note that there is a gap here between mutex.Unlock() and Wait().
00272     // The other mutex protects this gap.
00273 
00274     if (delay == OVR_WAIT_INFINITE)
00275         pthread_cond_wait(&Condv,&SMutex);
00276     else
00277     {
00278         timespec ts;
00279 #ifdef OVR_OS_PS3
00280         sys_time_sec_t s;
00281         sys_time_nsec_t ns;
00282         sys_time_get_current_time(&s, &ns);
00283 
00284         ts.tv_sec = s + (delay / 1000);
00285         ts.tv_nsec = ns + (delay % 1000) * 1000000;
00286 
00287 #else
00288         struct timeval tv;
00289         gettimeofday(&tv, 0);
00290 
00291         ts.tv_sec = tv.tv_sec + (delay / 1000);
00292         ts.tv_nsec = (tv.tv_usec + (delay % 1000) * 1000) * 1000;
00293 #endif
00294         if (ts.tv_nsec > 999999999)
00295         {
00296             ts.tv_sec++;
00297             ts.tv_nsec -= 1000000000;
00298         }
00299         int r = pthread_cond_timedwait(&Condv,&SMutex, &ts);
00300         OVR_ASSERT(r == 0 || r == ETIMEDOUT);
00301         if (r)
00302             result = 0;
00303     }
00304 
00305     pthread_mutex_unlock(&SMutex);
00306 
00307     // Re-aquire the mutex
00308     for(unsigned i=0; i<lockCount; i++)
00309         pmutex->DoLock(); 
00310 
00311     // Return the result
00312     return result;
00313 }
00314 
00315 // Notify a condition, releasing the least object in a queue
00316 void    WaitConditionImpl::Notify()
00317 {
00318     pthread_mutex_lock(&SMutex);
00319     pthread_cond_signal(&Condv);
00320     pthread_mutex_unlock(&SMutex);
00321 }
00322 
00323 // Notify a condition, releasing all objects waiting
00324 void    WaitConditionImpl::NotifyAll()
00325 {
00326     pthread_mutex_lock(&SMutex);
00327     pthread_cond_broadcast(&Condv);
00328     pthread_mutex_unlock(&SMutex);
00329 }
00330 
00331 
00332 
00333 // *** Actual implementation of WaitCondition
00334 
00335 WaitCondition::WaitCondition()
00336 {
00337     pImpl = new WaitConditionImpl;
00338 }
00339 WaitCondition::~WaitCondition()
00340 {
00341     delete pImpl;
00342 }
00343     
00344 bool    WaitCondition::Wait(Mutex *pmutex, unsigned delay)
00345 {
00346     return pImpl->Wait(pmutex, delay);
00347 }
00348 // Notification
00349 void    WaitCondition::Notify()
00350 {
00351     pImpl->Notify();
00352 }
00353 void    WaitCondition::NotifyAll()
00354 {
00355     pImpl->NotifyAll();
00356 }
00357 
00358 
00359 // ***** Current thread
00360 
00361 // Per-thread variable
00362 /*
00363 static __thread Thread* pCurrentThread = 0;
00364 
00365 // Static function to return a pointer to the current thread
00366 void    Thread::InitCurrentThread(Thread *pthread)
00367 {
00368     pCurrentThread = pthread;
00369 }
00370 
00371 // Static function to return a pointer to the current thread
00372 Thread*    Thread::GetThread()
00373 {
00374     return pCurrentThread;
00375 }
00376 */
00377 
00378 
00379 // *** Thread constructors.
00380 
00381 Thread::Thread(UPInt stackSize, int processor)
00382 {
00383     // NOTE: RefCount mode already thread-safe for all Waitable objects.
00384     CreateParams params;
00385     params.stackSize = stackSize;
00386     params.processor = processor;
00387     Init(params);
00388 }
00389 
00390 Thread::Thread(Thread::ThreadFn threadFunction, void*  userHandle, UPInt stackSize,
00391                  int processor, Thread::ThreadState initialState)
00392 {
00393     CreateParams params(threadFunction, userHandle, stackSize, processor, initialState);
00394     Init(params);
00395 }
00396 
00397 Thread::Thread(const CreateParams& params)
00398 {
00399     Init(params);
00400 }
00401 
00402 void Thread::Init(const CreateParams& params)
00403 {
00404     // Clear the variables    
00405     ThreadFlags     = 0;
00406     ThreadHandle    = 0;
00407     ExitCode        = 0;
00408     SuspendCount    = 0;
00409     StackSize       = params.stackSize;
00410     Processor       = params.processor;
00411     Priority        = params.priority;
00412 
00413     // Clear Function pointers
00414     ThreadFunction  = params.threadFunction;
00415     UserHandle      = params.userHandle;
00416     if (params.initialState != NotRunning)
00417         Start(params.initialState);
00418 }
00419 
00420 Thread::~Thread()
00421 {
00422     // Thread should not running while object is being destroyed,
00423     // this would indicate ref-counting issue.
00424     //OVR_ASSERT(IsRunning() == 0);
00425 
00426     // Clean up thread.    
00427     ThreadHandle = 0;
00428 }
00429 
00430 
00431 
00432 // *** Overridable User functions.
00433 
00434 // Default Run implementation
00435 int    Thread::Run()
00436 {
00437     // Call pointer to function, if available.    
00438     return (ThreadFunction) ? ThreadFunction(this, UserHandle) : 0;
00439 }
00440 void    Thread::OnExit()
00441 {   
00442 }
00443 
00444 
00445 // Finishes the thread and releases internal reference to it.
00446 void    Thread::FinishAndRelease()
00447 {
00448     // Note: thread must be US.
00449     ThreadFlags &= (UInt32)~(OVR_THREAD_STARTED);
00450     ThreadFlags |= OVR_THREAD_FINISHED;
00451 
00452     // Release our reference; this is equivalent to 'delete this'
00453     // from the point of view of our thread.
00454     Release();
00455 }
00456 
00457 
00458 
00459 // *** ThreadList - used to track all created threads
00460 
00461 class ThreadList : public NewOverrideBase
00462 {
00463     //------------------------------------------------------------------------
00464     struct ThreadHashOp
00465     {
00466         size_t operator()(const Thread* ptr)
00467         {
00468             return (((size_t)ptr) >> 6) ^ (size_t)ptr;
00469         }
00470     };
00471 
00472     HashSet<Thread*, ThreadHashOp>        ThreadSet;
00473     Mutex                                 ThreadMutex;
00474     WaitCondition                         ThreadsEmpty;
00475     // Track the root thread that created us.
00476     pthread_t                             RootThreadId;
00477 
00478     static ThreadList* volatile pRunningThreads;
00479 
00480     void addThread(Thread *pthread)
00481     {
00482         Mutex::Locker lock(&ThreadMutex);
00483         ThreadSet.Add(pthread);
00484     }
00485 
00486     void removeThread(Thread *pthread)
00487     {
00488         Mutex::Locker lock(&ThreadMutex);
00489         ThreadSet.Remove(pthread);
00490         if (ThreadSet.GetSize() == 0)
00491             ThreadsEmpty.Notify();
00492     }
00493 
00494     void finishAllThreads()
00495     {
00496         // Only original root thread can call this.
00497         OVR_ASSERT(pthread_self() == RootThreadId);
00498 
00499         Mutex::Locker lock(&ThreadMutex);
00500         while (ThreadSet.GetSize() != 0)
00501             ThreadsEmpty.Wait(&ThreadMutex);
00502     }
00503 
00504 public:
00505 
00506     ThreadList()
00507     {
00508         RootThreadId = pthread_self();
00509     }
00510     ~ThreadList() { }
00511 
00512 
00513     static void AddRunningThread(Thread *pthread)
00514     {
00515         // Non-atomic creation ok since only the root thread
00516         if (!pRunningThreads)
00517         {
00518             pRunningThreads = new ThreadList;
00519             OVR_ASSERT(pRunningThreads);
00520         }
00521         pRunningThreads->addThread(pthread);
00522     }
00523 
00524     // NOTE: 'pthread' might be a dead pointer when this is
00525     // called so it should not be accessed; it is only used
00526     // for removal.
00527     static void RemoveRunningThread(Thread *pthread)
00528     {
00529         OVR_ASSERT(pRunningThreads);        
00530         pRunningThreads->removeThread(pthread);
00531     }
00532 
00533     static void FinishAllThreads()
00534     {
00535         // This is ok because only root thread can wait for other thread finish.
00536         if (pRunningThreads)
00537         {           
00538             pRunningThreads->finishAllThreads();
00539             delete pRunningThreads;
00540             pRunningThreads = 0;
00541         }        
00542     }
00543 };
00544 
00545 // By default, we have no thread list.
00546 ThreadList* volatile ThreadList::pRunningThreads = 0;
00547 
00548 
00549 // FinishAllThreads - exposed publicly in Thread.
00550 void Thread::FinishAllThreads()
00551 {
00552     ThreadList::FinishAllThreads();
00553 }
00554 
00555 // *** Run override
00556 
00557 int    Thread::PRun()
00558 {
00559     // Suspend us on start, if requested
00560     if (ThreadFlags & OVR_THREAD_START_SUSPENDED)
00561     {
00562         Suspend();
00563         ThreadFlags &= (UInt32)~OVR_THREAD_START_SUSPENDED;
00564     }
00565 
00566     // Call the virtual run function
00567     ExitCode = Run();    
00568     return ExitCode;
00569 }
00570 
00571 
00572 
00573 
00574 // *** User overridables
00575 
00576 bool    Thread::GetExitFlag() const
00577 {
00578     return (ThreadFlags & OVR_THREAD_EXIT) != 0;
00579 }       
00580 
00581 void    Thread::SetExitFlag(bool exitFlag)
00582 {
00583     // The below is atomic since ThreadFlags is AtomicInt.
00584     if (exitFlag)
00585         ThreadFlags |= OVR_THREAD_EXIT;
00586     else
00587         ThreadFlags &= (UInt32) ~OVR_THREAD_EXIT;
00588 }
00589 
00590 
00591 // Determines whether the thread was running and is now finished
00592 bool    Thread::IsFinished() const
00593 {
00594     return (ThreadFlags & OVR_THREAD_FINISHED) != 0;
00595 }
00596 // Determines whether the thread is suspended
00597 bool    Thread::IsSuspended() const
00598 {   
00599     return SuspendCount > 0;
00600 }
00601 // Returns current thread state
00602 Thread::ThreadState Thread::GetThreadState() const
00603 {
00604     if (IsSuspended())
00605         return Suspended;    
00606     if (ThreadFlags & OVR_THREAD_STARTED)
00607         return Running;    
00608     return NotRunning;
00609 }
00610 /*
00611 static const char* mapsched_policy(int policy)
00612 {
00613     switch(policy)
00614     {
00615     case SCHED_OTHER:
00616         return "SCHED_OTHER";
00617     case SCHED_RR:
00618         return "SCHED_RR";
00619     case SCHED_FIFO:
00620         return "SCHED_FIFO";
00621 
00622     }
00623     return "UNKNOWN";
00624 }
00625     int policy;
00626     sched_param sparam;
00627     pthread_getschedparam(pthread_self(), &policy, &sparam);
00628     int max_prior = sched_get_priority_max(policy);
00629     int min_prior = sched_get_priority_min(policy);
00630     printf(" !!!! policy: %s, priority: %d, max priority: %d, min priority: %d\n", mapsched_policy(policy), sparam.sched_priority, max_prior, min_prior);
00631 #include <stdio.h>
00632 */
00633 // ***** Thread management
00634 
00635 // The actual first function called on thread start
00636 void* Thread_PthreadStartFn(void* phandle)
00637 {
00638     Thread* pthread = (Thread*)phandle;
00639     int     result = pthread->PRun();
00640     // Signal the thread as done and release it atomically.
00641     pthread->FinishAndRelease();
00642     // At this point Thread object might be dead; however we can still pass
00643     // it to RemoveRunningThread since it is only used as a key there.   
00644     ThreadList::RemoveRunningThread(pthread);
00645     return (void*) result;
00646 }
00647 
00648 int Thread::InitAttr = 0;
00649 pthread_attr_t Thread::Attr; 
00650 
00651 /* static */
00652 int Thread::GetOSPriority(ThreadPriority p)
00653 //static inline int MapToSystemPrority(Thread::ThreadPriority p)
00654 {
00655 #ifdef OVR_OS_PS3 
00656     switch(p)
00657     {
00658     case Thread::CriticalPriority:     return 0;
00659     case Thread::HighestPriority:      return 300;
00660     case Thread::AboveNormalPriority:  return 600;
00661     case Thread::NormalPriority:       return 1000;
00662     case Thread::BelowNormalPriority:  return 1500;
00663     case Thread::LowestPriority:       return 2500;
00664     case Thread::IdlePriority:         return 3071;
00665     }                                  return 1000;
00666 #else
00667     OVR_UNUSED(p);
00668     return -1;
00669 #endif
00670 }
00671 
00672 bool    Thread::Start(ThreadState initialState)
00673 {
00674     if (initialState == NotRunning)
00675         return 0;
00676     if (GetThreadState() != NotRunning)
00677     {
00678         OVR_DEBUG_LOG(("Thread::Start failed - thread %p already running", this));
00679         return 0;
00680     }
00681 
00682     if (!InitAttr)
00683     {
00684         pthread_attr_init(&Attr);
00685         pthread_attr_setdetachstate(&Attr, PTHREAD_CREATE_DETACHED);
00686         pthread_attr_setstacksize(&Attr, 128 * 1024);
00687         sched_param sparam;
00688         sparam.sched_priority = Thread::GetOSPriority(NormalPriority);
00689         pthread_attr_setschedparam(&Attr, &sparam);
00690         InitAttr = 1;
00691     }
00692 
00693     ExitCode        = 0;
00694     SuspendCount    = 0;
00695     ThreadFlags     = (initialState == Running) ? 0 : OVR_THREAD_START_SUSPENDED;
00696 
00697     // AddRef to us until the thread is finished
00698     AddRef();
00699     ThreadList::AddRunningThread(this);
00700 
00701     int result;
00702     if (StackSize != 128 * 1024 || Priority != NormalPriority)
00703     {
00704         pthread_attr_t attr;
00705 
00706         pthread_attr_init(&attr);
00707         pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
00708         pthread_attr_setstacksize(&attr, StackSize);
00709         sched_param sparam;
00710         sparam.sched_priority = Thread::GetOSPriority(Priority);
00711         pthread_attr_setschedparam(&attr, &sparam);
00712         result = pthread_create(&ThreadHandle, &attr, Thread_PthreadStartFn, this);
00713         pthread_attr_destroy(&attr);
00714     }
00715     else
00716         result = pthread_create(&ThreadHandle, &Attr, Thread_PthreadStartFn, this);
00717 
00718     if (result)
00719     {
00720         ThreadFlags = 0;
00721         Release();
00722         ThreadList::RemoveRunningThread(this);
00723         return 0;
00724     }
00725     return 1;
00726 }
00727 
00728 
00729 // Suspend the thread until resumed
00730 bool    Thread::Suspend()
00731 {
00732     OVR_DEBUG_LOG(("Thread::Suspend - cannot suspend threads on this system"));
00733     return 0;
00734 }
00735 
00736 // Resumes currently suspended thread
00737 bool    Thread::Resume()
00738 {
00739     return 0;
00740 }
00741 
00742 
00743 // Quits with an exit code  
00744 void    Thread::Exit(int exitCode)
00745 {
00746     // Can only exist the current thread
00747    // if (GetThread() != this)
00748    //     return;
00749 
00750     // Call the virtual OnExit function
00751     OnExit();   
00752 
00753     // Signal this thread object as done and release it's references.
00754     FinishAndRelease();
00755     ThreadList::RemoveRunningThread(this);
00756 
00757     pthread_exit((void *) exitCode);
00758 }
00759 
00760 ThreadId GetCurrentThreadId()
00761 {
00762     return (void*)pthread_self();
00763 }
00764 
00765 // *** Sleep functions
00766 
00767 /* static */
00768 bool    Thread::Sleep(unsigned secs)
00769 {
00770     sleep(secs);
00771     return 1;
00772 }
00773 /* static */
00774 bool    Thread::MSleep(unsigned msecs)
00775 {
00776     usleep(msecs*1000);
00777     return 1;
00778 }
00779 
00780 /* static */
00781 int     Thread::GetCPUCount()
00782 {
00783     return 1;
00784 }
00785 
00786 
00787 #ifdef OVR_OS_PS3
00788 
00789 sys_lwmutex_attribute_t Lock::LockAttr = { SYS_SYNC_PRIORITY, SYS_SYNC_RECURSIVE };
00790 
00791 #endif
00792 
00793 }
00794 
00795 #endif  // OVR_ENABLE_THREADS


oculus_sdk
Author(s):
autogenerated on Fri Aug 28 2015 11:53:11