nuclear@1: /************************************************************************************ nuclear@1: nuclear@1: Filename : OVR_OSX_DeviceManager.cpp nuclear@1: Content : OSX specific DeviceManager implementation. nuclear@1: Created : March 14, 2013 nuclear@1: Authors : Lee Cooper nuclear@1: nuclear@1: Copyright : Copyright 2013 Oculus VR, Inc. All Rights reserved. nuclear@1: nuclear@1: Use of this software is subject to the terms of the Oculus license nuclear@1: agreement provided at the time of installation or download, or which nuclear@1: otherwise accompanies this software in either electronic or hard copy form. nuclear@1: nuclear@1: *************************************************************************************/ nuclear@1: nuclear@1: #include "OVR_OSX_DeviceManager.h" nuclear@1: nuclear@1: // Sensor & HMD Factories nuclear@1: #include "OVR_LatencyTestImpl.h" nuclear@1: #include "OVR_SensorImpl.h" nuclear@1: #include "OVR_OSX_HMDDevice.h" nuclear@1: #include "OVR_OSX_HIDDevice.h" nuclear@1: nuclear@1: #include "Kernel/OVR_Timer.h" nuclear@1: #include "Kernel/OVR_Std.h" nuclear@1: #include "Kernel/OVR_Log.h" nuclear@1: nuclear@1: #include nuclear@1: #include nuclear@1: nuclear@1: nuclear@1: namespace OVR { namespace OSX { nuclear@1: nuclear@1: //------------------------------------------------------------------------------------- nuclear@1: // **** OSX::DeviceManager nuclear@1: nuclear@1: DeviceManager::DeviceManager() nuclear@1: { nuclear@1: } nuclear@1: nuclear@1: DeviceManager::~DeviceManager() nuclear@1: { nuclear@1: OVR_DEBUG_LOG(("OSX::DeviceManager::~DeviceManager was called")); nuclear@1: } nuclear@1: nuclear@1: bool DeviceManager::Initialize(DeviceBase*) nuclear@1: { nuclear@1: if (!DeviceManagerImpl::Initialize(0)) nuclear@1: return false; nuclear@1: nuclear@1: // Start the background thread. nuclear@1: pThread = *new DeviceManagerThread(); nuclear@1: if (!pThread || !pThread->Start()) nuclear@1: return false; nuclear@1: nuclear@1: // Wait for the thread to be fully up and running. nuclear@1: pThread->StartupEvent.Wait(); nuclear@1: nuclear@1: // Do this now that we know the thread's run loop. nuclear@1: HidDeviceManager = *HIDDeviceManager::CreateInternal(this); nuclear@1: nuclear@1: CGDisplayRegisterReconfigurationCallback(displayReconfigurationCallBack, this); nuclear@1: nuclear@1: pCreateDesc->pDevice = this; nuclear@1: LogText("OVR::DeviceManager - initialized.\n"); nuclear@1: nuclear@1: return true; nuclear@1: } nuclear@1: nuclear@1: void DeviceManager::Shutdown() nuclear@1: { nuclear@1: LogText("OVR::DeviceManager - shutting down.\n"); nuclear@1: nuclear@1: CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallBack, this); nuclear@1: nuclear@1: // Set Manager shutdown marker variable; this prevents nuclear@1: // any existing DeviceHandle objects from accessing device. nuclear@1: pCreateDesc->pLock->pManager = 0; nuclear@1: nuclear@1: // Push for thread shutdown *WITH NO WAIT*. nuclear@1: // This will have the following effect: nuclear@1: // - Exit command will get enqueued, which will be executed later on the thread itself. nuclear@1: // - Beyond this point, this DeviceManager object may be deleted by our caller. nuclear@1: // - Other commands, such as CreateDevice, may execute before ExitCommand, but they will nuclear@1: // fail gracefully due to pLock->pManager == 0. Future commands can't be enqued nuclear@1: // after pManager is null. nuclear@1: // - Once ExitCommand executes, ThreadCommand::Run loop will exit and release the last nuclear@1: // reference to the thread object. nuclear@1: pThread->Shutdown(); nuclear@1: pThread.Clear(); nuclear@1: nuclear@1: DeviceManagerImpl::Shutdown(); nuclear@1: } nuclear@1: nuclear@1: ThreadCommandQueue* DeviceManager::GetThreadQueue() nuclear@1: { nuclear@1: return pThread; nuclear@1: } nuclear@1: nuclear@1: ThreadId DeviceManager::GetThreadId() const nuclear@1: { nuclear@1: return pThread->GetThreadId(); nuclear@1: } nuclear@1: nuclear@1: bool DeviceManager::GetDeviceInfo(DeviceInfo* info) const nuclear@1: { nuclear@1: if ((info->InfoClassType != Device_Manager) && nuclear@1: (info->InfoClassType != Device_None)) nuclear@1: return false; nuclear@1: nuclear@1: info->Type = Device_Manager; nuclear@1: info->Version = 0; nuclear@1: OVR_strcpy(info->ProductName, DeviceInfo::MaxNameLength, "DeviceManager"); nuclear@1: OVR_strcpy(info->Manufacturer,DeviceInfo::MaxNameLength, "Oculus VR, Inc."); nuclear@1: return true; nuclear@1: } nuclear@1: nuclear@1: DeviceEnumerator<> DeviceManager::EnumerateDevicesEx(const DeviceEnumerationArgs& args) nuclear@1: { nuclear@1: // TBD: Can this be avoided in the future, once proper device notification is in place? nuclear@1: pThread->PushCall((DeviceManagerImpl*)this, nuclear@1: &DeviceManager::EnumerateAllFactoryDevices, true); nuclear@1: nuclear@1: return DeviceManagerImpl::EnumerateDevicesEx(args); nuclear@1: } nuclear@1: nuclear@1: void DeviceManager::displayReconfigurationCallBack (CGDirectDisplayID display, nuclear@1: CGDisplayChangeSummaryFlags flags, nuclear@1: void *userInfo) nuclear@1: { nuclear@1: DeviceManager* manager = reinterpret_cast(userInfo); nuclear@1: OVR_UNUSED(manager); nuclear@1: nuclear@1: if (flags & kCGDisplayAddFlag) nuclear@1: { nuclear@1: LogText("Display Added, id = %d\n", int(display)); nuclear@1: manager->EnumerateDevices(); nuclear@1: } nuclear@1: else if (flags & kCGDisplayRemoveFlag) nuclear@1: { nuclear@1: LogText("Display Removed, id = %d\n", int(display)); nuclear@1: manager->EnumerateDevices(); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: //------------------------------------------------------------------------------------- nuclear@1: // ***** DeviceManager Thread nuclear@1: nuclear@1: DeviceManagerThread::DeviceManagerThread() nuclear@1: : Thread(ThreadStackSize) nuclear@1: { nuclear@1: } nuclear@1: nuclear@1: DeviceManagerThread::~DeviceManagerThread() nuclear@1: { nuclear@1: } nuclear@1: nuclear@1: int DeviceManagerThread::Run() nuclear@1: { nuclear@1: nuclear@1: SetThreadName("OVR::DeviceManagerThread"); nuclear@1: LogText("OVR::DeviceManagerThread - running (ThreadId=0x%p).\n", GetThreadId()); nuclear@1: nuclear@1: // Store out the run loop ref. nuclear@1: RunLoop = CFRunLoopGetCurrent(); nuclear@1: nuclear@1: // Create a 'source' to enable us to signal the run loop to process the command queue. nuclear@1: CFRunLoopSourceContext sourceContext; nuclear@1: memset(&sourceContext, 0, sizeof(sourceContext)); nuclear@1: sourceContext.version = 0; nuclear@1: sourceContext.info = this; nuclear@1: sourceContext.perform = &staticCommandQueueSourceCallback; nuclear@1: nuclear@1: CommandQueueSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0 , &sourceContext); nuclear@1: nuclear@1: CFRunLoopAddSource(RunLoop, CommandQueueSource, kCFRunLoopDefaultMode); nuclear@1: nuclear@1: nuclear@1: // Signal to the parent thread that initialization has finished. nuclear@1: StartupEvent.SetEvent(); nuclear@1: nuclear@1: nuclear@1: ThreadCommand::PopBuffer command; nuclear@1: nuclear@1: while(!IsExiting()) nuclear@1: { nuclear@1: // PopCommand will reset event on empty queue. nuclear@1: if (PopCommand(&command)) nuclear@1: { nuclear@1: command.Execute(); nuclear@1: } nuclear@1: else nuclear@1: { nuclear@1: SInt32 exitReason = 0; nuclear@1: do { nuclear@1: nuclear@1: UInt32 waitMs = INT_MAX; nuclear@1: nuclear@1: // If devices have time-dependent logic registered, get the longest wait nuclear@1: // allowed based on current ticks. nuclear@1: if (!TicksNotifiers.IsEmpty()) nuclear@1: { nuclear@1: UInt64 ticksMks = Timer::GetTicks(); nuclear@1: UInt32 waitAllowed; nuclear@1: nuclear@1: for (UPInt j = 0; j < TicksNotifiers.GetSize(); j++) nuclear@1: { nuclear@1: waitAllowed = (UInt32)(TicksNotifiers[j]->OnTicks(ticksMks) / Timer::MksPerMs); nuclear@1: if (waitAllowed < waitMs) nuclear@1: waitMs = waitAllowed; nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: // Enter blocking run loop. We may continue until we timeout in which nuclear@1: // case it's time to service the ticks. Or if commands arrive in the command nuclear@1: // queue then the source callback will call 'CFRunLoopStop' causing this nuclear@1: // to return. nuclear@1: CFTimeInterval blockInterval = 0.001 * (double) waitMs; nuclear@1: exitReason = CFRunLoopRunInMode(kCFRunLoopDefaultMode, blockInterval, false); nuclear@1: nuclear@1: if (exitReason == kCFRunLoopRunFinished) nuclear@1: { nuclear@1: // Maybe this will occur during shutdown? nuclear@1: break; nuclear@1: } nuclear@1: else if (exitReason == kCFRunLoopRunStopped ) nuclear@1: { nuclear@1: // Commands need processing or we're being shutdown. nuclear@1: break; nuclear@1: } nuclear@1: else if (exitReason == kCFRunLoopRunTimedOut) nuclear@1: { nuclear@1: // Timed out so that we can service our ticks callbacks. nuclear@1: continue; nuclear@1: } nuclear@1: else if (exitReason == kCFRunLoopRunHandledSource) nuclear@1: { nuclear@1: // Should never occur since the last param when we call nuclear@1: // 'CFRunLoopRunInMode' is false. nuclear@1: OVR_ASSERT(false); nuclear@1: break; nuclear@1: } nuclear@1: else nuclear@1: { nuclear@1: OVR_ASSERT_LOG(false, ("CFRunLoopRunInMode returned unexpected code")); nuclear@1: break; nuclear@1: } nuclear@1: } nuclear@1: while(true); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: nuclear@1: CFRunLoopRemoveSource(RunLoop, CommandQueueSource, kCFRunLoopDefaultMode); nuclear@1: CFRelease(CommandQueueSource); nuclear@1: nuclear@1: LogText("OVR::DeviceManagerThread - exiting (ThreadId=0x%p).\n", GetThreadId()); nuclear@1: nuclear@1: return 0; nuclear@1: } nuclear@1: nuclear@1: void DeviceManagerThread::staticCommandQueueSourceCallback(void* pContext) nuclear@1: { nuclear@1: DeviceManagerThread* pThread = (DeviceManagerThread*) pContext; nuclear@1: pThread->commandQueueSourceCallback(); nuclear@1: } nuclear@1: nuclear@1: void DeviceManagerThread::commandQueueSourceCallback() nuclear@1: { nuclear@1: CFRunLoopStop(RunLoop); nuclear@1: } nuclear@1: nuclear@1: bool DeviceManagerThread::AddTicksNotifier(Notifier* notify) nuclear@1: { nuclear@1: TicksNotifiers.PushBack(notify); nuclear@1: return true; nuclear@1: } nuclear@1: nuclear@1: bool DeviceManagerThread::RemoveTicksNotifier(Notifier* notify) nuclear@1: { nuclear@1: for (UPInt i = 0; i < TicksNotifiers.GetSize(); i++) nuclear@1: { nuclear@1: if (TicksNotifiers[i] == notify) nuclear@1: { nuclear@1: TicksNotifiers.RemoveAt(i); nuclear@1: return true; nuclear@1: } nuclear@1: } nuclear@1: return false; nuclear@1: } nuclear@1: nuclear@1: void DeviceManagerThread::Shutdown() nuclear@1: { nuclear@1: // Push for thread shutdown *WITH NO WAIT*. nuclear@1: // This will have the following effect: nuclear@1: // - Exit command will get enqueued, which will be executed later on the thread itself. nuclear@1: // - Beyond this point, this DeviceManager object may be deleted by our caller. nuclear@1: // - Other commands, such as CreateDevice, may execute before ExitCommand, but they will nuclear@1: // fail gracefully due to pLock->pManager == 0. Future commands can't be enqued nuclear@1: // after pManager is null. nuclear@1: // - Once ExitCommand executes, ThreadCommand::Run loop will exit and release the last nuclear@1: // reference to the thread object. nuclear@1: PushExitCommand(false); nuclear@1: nuclear@1: // make sure CFRunLoopRunInMode is woken up nuclear@1: CFRunLoopSourceSignal(CommandQueueSource); nuclear@1: CFRunLoopWakeUp(RunLoop); nuclear@1: } nuclear@1: nuclear@1: } // namespace OSX nuclear@1: nuclear@1: nuclear@1: //------------------------------------------------------------------------------------- nuclear@1: // ***** Creation nuclear@1: nuclear@1: // Creates a new DeviceManager and initializes OVR. nuclear@1: DeviceManager* DeviceManager::Create() nuclear@1: { nuclear@1: nuclear@1: if (!System::IsInitialized()) nuclear@1: { nuclear@1: // Use custom message, since Log is not yet installed. nuclear@1: OVR_DEBUG_STATEMENT(Log::GetDefaultLog()-> nuclear@1: LogMessage(Log_Debug, "DeviceManager::Create failed - OVR::System not initialized"); ); nuclear@1: return 0; nuclear@1: } nuclear@1: nuclear@1: Ptr manager = *new OSX::DeviceManager; nuclear@1: nuclear@1: if (manager) nuclear@1: { nuclear@1: if (manager->Initialize(0)) nuclear@1: { nuclear@1: manager->AddFactory(&LatencyTestDeviceFactory::Instance); nuclear@1: manager->AddFactory(&SensorDeviceFactory::Instance); nuclear@1: manager->AddFactory(&OSX::HMDDeviceFactory::Instance); nuclear@1: nuclear@1: manager->AddRef(); nuclear@1: } nuclear@1: else nuclear@1: { nuclear@1: manager.Clear(); nuclear@1: } nuclear@1: } nuclear@1: nuclear@1: return manager.GetPtr(); nuclear@1: } nuclear@1: nuclear@1: } // namespace OVR