rev |
line source |
nuclear@1
|
1 /************************************************************************************
|
nuclear@1
|
2
|
nuclear@1
|
3 Filename : OVR_Win32_DeviceManager.cpp
|
nuclear@1
|
4 Content : Win32 implementation of DeviceManager.
|
nuclear@1
|
5 Created : September 21, 2012
|
nuclear@1
|
6 Authors : Michael Antonov
|
nuclear@1
|
7
|
nuclear@1
|
8 Copyright : Copyright 2012 Oculus VR, Inc. All Rights reserved.
|
nuclear@1
|
9
|
nuclear@1
|
10 Use of this software is subject to the terms of the Oculus license
|
nuclear@1
|
11 agreement provided at the time of installation or download, or which
|
nuclear@1
|
12 otherwise accompanies this software in either electronic or hard copy form.
|
nuclear@1
|
13
|
nuclear@1
|
14 *************************************************************************************/
|
nuclear@1
|
15
|
nuclear@1
|
16 #include "OVR_Win32_DeviceManager.h"
|
nuclear@1
|
17
|
nuclear@1
|
18 // Sensor & HMD Factories
|
nuclear@1
|
19 #include "OVR_SensorImpl.h"
|
nuclear@1
|
20 #include "OVR_LatencyTestImpl.h"
|
nuclear@1
|
21 #include "OVR_Win32_HMDDevice.h"
|
nuclear@1
|
22 #include "OVR_Win32_DeviceStatus.h"
|
nuclear@1
|
23 #include "OVR_Win32_HIDDevice.h"
|
nuclear@1
|
24
|
nuclear@1
|
25 #include "Kernel/OVR_Timer.h"
|
nuclear@1
|
26 #include "Kernel/OVR_Std.h"
|
nuclear@1
|
27 #include "Kernel/OVR_Log.h"
|
nuclear@1
|
28
|
nuclear@1
|
29 DWORD Debug_WaitedObjectCount = 0;
|
nuclear@1
|
30
|
nuclear@1
|
31 namespace OVR { namespace Win32 {
|
nuclear@1
|
32
|
nuclear@1
|
33
|
nuclear@1
|
34 //-------------------------------------------------------------------------------------
|
nuclear@1
|
35 // **** Win32::DeviceManager
|
nuclear@1
|
36
|
nuclear@1
|
37 DeviceManager::DeviceManager()
|
nuclear@1
|
38 {
|
nuclear@1
|
39 HidDeviceManager = *HIDDeviceManager::CreateInternal(this);
|
nuclear@1
|
40 }
|
nuclear@1
|
41
|
nuclear@1
|
42 DeviceManager::~DeviceManager()
|
nuclear@1
|
43 {
|
nuclear@1
|
44 // make sure Shutdown was called.
|
nuclear@1
|
45 OVR_ASSERT(!pThread);
|
nuclear@1
|
46 }
|
nuclear@1
|
47
|
nuclear@1
|
48 bool DeviceManager::Initialize(DeviceBase*)
|
nuclear@1
|
49 {
|
nuclear@1
|
50 if (!DeviceManagerImpl::Initialize(0))
|
nuclear@1
|
51 return false;
|
nuclear@1
|
52
|
nuclear@1
|
53 pThread = *new DeviceManagerThread(this);
|
nuclear@1
|
54 if (!pThread || !pThread->Start())
|
nuclear@1
|
55 return false;
|
nuclear@1
|
56
|
nuclear@1
|
57 pCreateDesc->pDevice = this;
|
nuclear@1
|
58 LogText("OVR::DeviceManager - initialized.\n");
|
nuclear@1
|
59 return true;
|
nuclear@1
|
60 }
|
nuclear@1
|
61
|
nuclear@1
|
62 void DeviceManager::Shutdown()
|
nuclear@1
|
63 {
|
nuclear@1
|
64 LogText("OVR::DeviceManager - shutting down.\n");
|
nuclear@1
|
65
|
nuclear@1
|
66 // Set Manager shutdown marker variable; this prevents
|
nuclear@1
|
67 // any existing DeviceHandle objects from accessing device.
|
nuclear@1
|
68 pCreateDesc->pLock->pManager = 0;
|
nuclear@1
|
69
|
nuclear@1
|
70 // Push for thread shutdown *WITH NO WAIT*.
|
nuclear@1
|
71 // This will have the following effect:
|
nuclear@1
|
72 // - Exit command will get enqueued, which will be executed later on the thread itself.
|
nuclear@1
|
73 // - Beyond this point, this DeviceManager object may be deleted by our caller.
|
nuclear@1
|
74 // - Other commands, such as CreateDevice, may execute before ExitCommand, but they will
|
nuclear@1
|
75 // fail gracefully due to pLock->pManager == 0. Future commands can't be enqued
|
nuclear@1
|
76 // after pManager is null.
|
nuclear@1
|
77 // - Once ExitCommand executes, ThreadCommand::Run loop will exit and release the last
|
nuclear@1
|
78 // reference to the thread object.
|
nuclear@1
|
79 pThread->PushExitCommand(false);
|
nuclear@1
|
80 pThread->DetachDeviceManager();
|
nuclear@1
|
81 pThread.Clear();
|
nuclear@1
|
82
|
nuclear@1
|
83 DeviceManagerImpl::Shutdown();
|
nuclear@1
|
84 }
|
nuclear@1
|
85
|
nuclear@1
|
86 ThreadCommandQueue* DeviceManager::GetThreadQueue()
|
nuclear@1
|
87 {
|
nuclear@1
|
88 return pThread;
|
nuclear@1
|
89 }
|
nuclear@1
|
90
|
nuclear@1
|
91 bool DeviceManager::GetDeviceInfo(DeviceInfo* info) const
|
nuclear@1
|
92 {
|
nuclear@1
|
93 if ((info->InfoClassType != Device_Manager) &&
|
nuclear@1
|
94 (info->InfoClassType != Device_None))
|
nuclear@1
|
95 return false;
|
nuclear@1
|
96
|
nuclear@1
|
97 info->Type = Device_Manager;
|
nuclear@1
|
98 info->Version = 0;
|
nuclear@1
|
99 OVR_strcpy(info->ProductName, DeviceInfo::MaxNameLength, "DeviceManager");
|
nuclear@1
|
100 OVR_strcpy(info->Manufacturer,DeviceInfo::MaxNameLength, "Oculus VR, Inc.");
|
nuclear@1
|
101 return true;
|
nuclear@1
|
102 }
|
nuclear@1
|
103
|
nuclear@1
|
104 DeviceEnumerator<> DeviceManager::EnumerateDevicesEx(const DeviceEnumerationArgs& args)
|
nuclear@1
|
105 {
|
nuclear@1
|
106 // TBD: Can this be avoided in the future, once proper device notification is in place?
|
nuclear@1
|
107 if (GetThreadId() != OVR::GetCurrentThreadId())
|
nuclear@1
|
108 {
|
nuclear@1
|
109 pThread->PushCall((DeviceManagerImpl*)this,
|
nuclear@1
|
110 &DeviceManager::EnumerateAllFactoryDevices, true);
|
nuclear@1
|
111 }
|
nuclear@1
|
112 else
|
nuclear@1
|
113 DeviceManager::EnumerateAllFactoryDevices();
|
nuclear@1
|
114
|
nuclear@1
|
115 return DeviceManagerImpl::EnumerateDevicesEx(args);
|
nuclear@1
|
116 }
|
nuclear@1
|
117
|
nuclear@1
|
118 ThreadId DeviceManager::GetThreadId() const
|
nuclear@1
|
119 {
|
nuclear@1
|
120 return pThread->GetThreadId();
|
nuclear@1
|
121 }
|
nuclear@1
|
122
|
nuclear@1
|
123 bool DeviceManager::GetHIDDeviceDesc(const String& path, HIDDeviceDesc* pdevDesc) const
|
nuclear@1
|
124 {
|
nuclear@1
|
125 if (GetHIDDeviceManager())
|
nuclear@1
|
126 return static_cast<HIDDeviceManager*>(GetHIDDeviceManager())->GetHIDDeviceDesc(path, pdevDesc);
|
nuclear@1
|
127 return false;
|
nuclear@1
|
128 }
|
nuclear@1
|
129
|
nuclear@1
|
130
|
nuclear@1
|
131 //-------------------------------------------------------------------------------------
|
nuclear@1
|
132 // ***** DeviceManager Thread
|
nuclear@1
|
133
|
nuclear@1
|
134 DeviceManagerThread::DeviceManagerThread(DeviceManager* pdevMgr)
|
nuclear@1
|
135 : Thread(ThreadStackSize), hCommandEvent(0), pDeviceMgr(pdevMgr)
|
nuclear@1
|
136 {
|
nuclear@1
|
137 // Create a non-signaled manual-reset event.
|
nuclear@1
|
138 hCommandEvent = ::CreateEvent(0, TRUE, FALSE, 0);
|
nuclear@1
|
139 if (!hCommandEvent)
|
nuclear@1
|
140 return;
|
nuclear@1
|
141
|
nuclear@1
|
142 // Must add event before starting.
|
nuclear@1
|
143 AddOverlappedEvent(0, hCommandEvent);
|
nuclear@1
|
144
|
nuclear@1
|
145 // Create device messages object.
|
nuclear@1
|
146 pStatusObject = *new DeviceStatus(this);
|
nuclear@1
|
147 }
|
nuclear@1
|
148
|
nuclear@1
|
149 DeviceManagerThread::~DeviceManagerThread()
|
nuclear@1
|
150 {
|
nuclear@1
|
151 // Remove overlapped event [0], after thread service exit.
|
nuclear@1
|
152 if (hCommandEvent)
|
nuclear@1
|
153 {
|
nuclear@1
|
154 RemoveOverlappedEvent(0, hCommandEvent);
|
nuclear@1
|
155 ::CloseHandle(hCommandEvent);
|
nuclear@1
|
156 hCommandEvent = 0;
|
nuclear@1
|
157 }
|
nuclear@1
|
158 }
|
nuclear@1
|
159
|
nuclear@1
|
160 int DeviceManagerThread::Run()
|
nuclear@1
|
161 {
|
nuclear@1
|
162 ThreadCommand::PopBuffer command;
|
nuclear@1
|
163
|
nuclear@1
|
164 SetThreadName("OVR::DeviceManagerThread");
|
nuclear@1
|
165 LogText("OVR::DeviceManagerThread - running (ThreadId=0x%X).\n", GetThreadId());
|
nuclear@1
|
166
|
nuclear@1
|
167 if (!pStatusObject->Initialize())
|
nuclear@1
|
168 {
|
nuclear@1
|
169 LogText("OVR::DeviceManagerThread - failed to initialize MessageObject.\n");
|
nuclear@1
|
170 }
|
nuclear@1
|
171
|
nuclear@1
|
172 while(!IsExiting())
|
nuclear@1
|
173 {
|
nuclear@1
|
174 // PopCommand will reset event on empty queue.
|
nuclear@1
|
175 if (PopCommand(&command))
|
nuclear@1
|
176 {
|
nuclear@1
|
177 command.Execute();
|
nuclear@1
|
178 }
|
nuclear@1
|
179 else
|
nuclear@1
|
180 {
|
nuclear@1
|
181 DWORD eventIndex = 0;
|
nuclear@1
|
182 do {
|
nuclear@1
|
183 UPInt numberOfWaitHandles = WaitHandles.GetSize();
|
nuclear@1
|
184 Debug_WaitedObjectCount = (DWORD)numberOfWaitHandles;
|
nuclear@1
|
185
|
nuclear@1
|
186 DWORD waitMs = INFINITE;
|
nuclear@1
|
187
|
nuclear@1
|
188 // If devices have time-dependent logic registered, get the longest wait
|
nuclear@1
|
189 // allowed based on current ticks.
|
nuclear@1
|
190 if (!TicksNotifiers.IsEmpty())
|
nuclear@1
|
191 {
|
nuclear@1
|
192 UInt64 ticksMks = Timer::GetTicks();
|
nuclear@1
|
193 DWORD waitAllowed;
|
nuclear@1
|
194
|
nuclear@1
|
195 for (UPInt j = 0; j < TicksNotifiers.GetSize(); j++)
|
nuclear@1
|
196 {
|
nuclear@1
|
197 waitAllowed = (DWORD)(TicksNotifiers[j]->OnTicks(ticksMks) / Timer::MksPerMs);
|
nuclear@1
|
198 if (waitAllowed < waitMs)
|
nuclear@1
|
199 waitMs = waitAllowed;
|
nuclear@1
|
200 }
|
nuclear@1
|
201 }
|
nuclear@1
|
202
|
nuclear@1
|
203 // Wait for event signals or window messages.
|
nuclear@1
|
204 eventIndex = MsgWaitForMultipleObjects((DWORD)numberOfWaitHandles, &WaitHandles[0], FALSE, waitMs, QS_ALLINPUT);
|
nuclear@1
|
205
|
nuclear@1
|
206 if (eventIndex != WAIT_FAILED)
|
nuclear@1
|
207 {
|
nuclear@1
|
208 if (eventIndex == WAIT_TIMEOUT)
|
nuclear@1
|
209 continue;
|
nuclear@1
|
210
|
nuclear@1
|
211 // TBD: Does this ever apply?
|
nuclear@1
|
212 OVR_ASSERT(eventIndex < WAIT_ABANDONED_0);
|
nuclear@1
|
213
|
nuclear@1
|
214 if (eventIndex == WAIT_OBJECT_0)
|
nuclear@1
|
215 {
|
nuclear@1
|
216 // Handle [0] services commands.
|
nuclear@1
|
217 break;
|
nuclear@1
|
218 }
|
nuclear@1
|
219 else if (eventIndex == WAIT_OBJECT_0 + numberOfWaitHandles)
|
nuclear@1
|
220 {
|
nuclear@1
|
221 // Handle Windows messages.
|
nuclear@1
|
222 pStatusObject->ProcessMessages();
|
nuclear@1
|
223 }
|
nuclear@1
|
224 else
|
nuclear@1
|
225 {
|
nuclear@1
|
226 // Notify waiting device that its event is signaled.
|
nuclear@1
|
227 unsigned i = eventIndex - WAIT_OBJECT_0;
|
nuclear@1
|
228 OVR_ASSERT(i < numberOfWaitHandles);
|
nuclear@1
|
229 if (WaitNotifiers[i])
|
nuclear@1
|
230 WaitNotifiers[i]->OnOverlappedEvent(WaitHandles[i]);
|
nuclear@1
|
231 }
|
nuclear@1
|
232 }
|
nuclear@1
|
233
|
nuclear@1
|
234 } while(eventIndex != WAIT_FAILED);
|
nuclear@1
|
235
|
nuclear@1
|
236 }
|
nuclear@1
|
237 }
|
nuclear@1
|
238
|
nuclear@1
|
239 pStatusObject->ShutDown();
|
nuclear@1
|
240
|
nuclear@1
|
241 LogText("OVR::DeviceManagerThread - exiting (ThreadId=0x%X).\n", GetThreadId());
|
nuclear@1
|
242 return 0;
|
nuclear@1
|
243 }
|
nuclear@1
|
244
|
nuclear@1
|
245 bool DeviceManagerThread::AddOverlappedEvent(Notifier* notify, HANDLE hevent)
|
nuclear@1
|
246 {
|
nuclear@1
|
247 WaitNotifiers.PushBack(notify);
|
nuclear@1
|
248 WaitHandles.PushBack(hevent);
|
nuclear@1
|
249
|
nuclear@1
|
250 OVR_ASSERT(WaitNotifiers.GetSize() <= MAXIMUM_WAIT_OBJECTS);
|
nuclear@1
|
251 return true;
|
nuclear@1
|
252 }
|
nuclear@1
|
253
|
nuclear@1
|
254 bool DeviceManagerThread::RemoveOverlappedEvent(Notifier* notify, HANDLE hevent)
|
nuclear@1
|
255 {
|
nuclear@1
|
256 // [0] is reserved for thread commands with notify of null, but we still
|
nuclear@1
|
257 // can use this function to remove it.
|
nuclear@1
|
258 for (UPInt i = 0; i < WaitNotifiers.GetSize(); i++)
|
nuclear@1
|
259 {
|
nuclear@1
|
260 if ((WaitNotifiers[i] == notify) && (WaitHandles[i] == hevent))
|
nuclear@1
|
261 {
|
nuclear@1
|
262 WaitNotifiers.RemoveAt(i);
|
nuclear@1
|
263 WaitHandles.RemoveAt(i);
|
nuclear@1
|
264 return true;
|
nuclear@1
|
265 }
|
nuclear@1
|
266 }
|
nuclear@1
|
267 return false;
|
nuclear@1
|
268 }
|
nuclear@1
|
269
|
nuclear@1
|
270 bool DeviceManagerThread::AddTicksNotifier(Notifier* notify)
|
nuclear@1
|
271 {
|
nuclear@1
|
272 TicksNotifiers.PushBack(notify);
|
nuclear@1
|
273 return true;
|
nuclear@1
|
274 }
|
nuclear@1
|
275
|
nuclear@1
|
276 bool DeviceManagerThread::RemoveTicksNotifier(Notifier* notify)
|
nuclear@1
|
277 {
|
nuclear@1
|
278 for (UPInt i = 0; i < TicksNotifiers.GetSize(); i++)
|
nuclear@1
|
279 {
|
nuclear@1
|
280 if (TicksNotifiers[i] == notify)
|
nuclear@1
|
281 {
|
nuclear@1
|
282 TicksNotifiers.RemoveAt(i);
|
nuclear@1
|
283 return true;
|
nuclear@1
|
284 }
|
nuclear@1
|
285 }
|
nuclear@1
|
286 return false;
|
nuclear@1
|
287 }
|
nuclear@1
|
288
|
nuclear@1
|
289 bool DeviceManagerThread::AddMessageNotifier(Notifier* notify)
|
nuclear@1
|
290 {
|
nuclear@1
|
291 MessageNotifiers.PushBack(notify);
|
nuclear@1
|
292 return true;
|
nuclear@1
|
293 }
|
nuclear@1
|
294
|
nuclear@1
|
295 bool DeviceManagerThread::RemoveMessageNotifier(Notifier* notify)
|
nuclear@1
|
296 {
|
nuclear@1
|
297 for (UPInt i = 0; i < MessageNotifiers.GetSize(); i++)
|
nuclear@1
|
298 {
|
nuclear@1
|
299 if (MessageNotifiers[i] == notify)
|
nuclear@1
|
300 {
|
nuclear@1
|
301 MessageNotifiers.RemoveAt(i);
|
nuclear@1
|
302 return true;
|
nuclear@1
|
303 }
|
nuclear@1
|
304 }
|
nuclear@1
|
305 return false;
|
nuclear@1
|
306 }
|
nuclear@1
|
307
|
nuclear@1
|
308 bool DeviceManagerThread::OnMessage(MessageType type, const String& devicePath)
|
nuclear@1
|
309 {
|
nuclear@1
|
310 Notifier::DeviceMessageType notifierMessageType = Notifier::DeviceMessage_DeviceAdded;
|
nuclear@1
|
311 if (type == DeviceAdded)
|
nuclear@1
|
312 {
|
nuclear@1
|
313 }
|
nuclear@1
|
314 else if (type == DeviceRemoved)
|
nuclear@1
|
315 {
|
nuclear@1
|
316 notifierMessageType = Notifier::DeviceMessage_DeviceRemoved;
|
nuclear@1
|
317 }
|
nuclear@1
|
318 else
|
nuclear@1
|
319 {
|
nuclear@1
|
320 OVR_ASSERT(false);
|
nuclear@1
|
321 }
|
nuclear@1
|
322
|
nuclear@1
|
323 bool error = false;
|
nuclear@1
|
324 bool deviceFound = false;
|
nuclear@1
|
325 for (UPInt i = 0; i < MessageNotifiers.GetSize(); i++)
|
nuclear@1
|
326 {
|
nuclear@1
|
327 if (MessageNotifiers[i] &&
|
nuclear@1
|
328 MessageNotifiers[i]->OnDeviceMessage(notifierMessageType, devicePath, &error))
|
nuclear@1
|
329 {
|
nuclear@1
|
330 // The notifier belonged to a device with the specified device name so we're done.
|
nuclear@1
|
331 deviceFound = true;
|
nuclear@1
|
332 break;
|
nuclear@1
|
333 }
|
nuclear@1
|
334 }
|
nuclear@1
|
335 if (type == DeviceAdded && !deviceFound)
|
nuclear@1
|
336 {
|
nuclear@1
|
337 Lock::Locker devMgrLock(&DevMgrLock);
|
nuclear@1
|
338 // a new device was connected. Go through all device factories and
|
nuclear@1
|
339 // try to detect the device using HIDDeviceDesc.
|
nuclear@1
|
340 HIDDeviceDesc devDesc;
|
nuclear@1
|
341 if (pDeviceMgr->GetHIDDeviceDesc(devicePath, &devDesc))
|
nuclear@1
|
342 {
|
nuclear@1
|
343 Lock::Locker deviceLock(pDeviceMgr->GetLock());
|
nuclear@1
|
344 DeviceFactory* factory = pDeviceMgr->Factories.GetFirst();
|
nuclear@1
|
345 while(!pDeviceMgr->Factories.IsNull(factory))
|
nuclear@1
|
346 {
|
nuclear@1
|
347 if (factory->DetectHIDDevice(pDeviceMgr, devDesc))
|
nuclear@1
|
348 {
|
nuclear@1
|
349 deviceFound = true;
|
nuclear@1
|
350 break;
|
nuclear@1
|
351 }
|
nuclear@1
|
352 factory = factory->pNext;
|
nuclear@1
|
353 }
|
nuclear@1
|
354 }
|
nuclear@1
|
355 }
|
nuclear@1
|
356
|
nuclear@1
|
357 if (!deviceFound && strstr(devicePath.ToCStr(), "#OVR00"))
|
nuclear@1
|
358 {
|
nuclear@1
|
359 Ptr<DeviceManager> pmgr;
|
nuclear@1
|
360 {
|
nuclear@1
|
361 Lock::Locker devMgrLock(&DevMgrLock);
|
nuclear@1
|
362 pmgr = pDeviceMgr;
|
nuclear@1
|
363 }
|
nuclear@1
|
364 // HMD plugged/unplugged
|
nuclear@1
|
365 // This is not a final solution to enumerate HMD devices and get
|
nuclear@1
|
366 // a first available handle. This won't work with multiple rifts.
|
nuclear@1
|
367 // @TODO (!AB)
|
nuclear@1
|
368 pmgr->EnumerateDevices<HMDDevice>();
|
nuclear@1
|
369 }
|
nuclear@1
|
370
|
nuclear@1
|
371 return !error;
|
nuclear@1
|
372 }
|
nuclear@1
|
373
|
nuclear@1
|
374 void DeviceManagerThread::DetachDeviceManager()
|
nuclear@1
|
375 {
|
nuclear@1
|
376 Lock::Locker devMgrLock(&DevMgrLock);
|
nuclear@1
|
377 pDeviceMgr = NULL;
|
nuclear@1
|
378 }
|
nuclear@1
|
379
|
nuclear@1
|
380 } // namespace Win32
|
nuclear@1
|
381
|
nuclear@1
|
382
|
nuclear@1
|
383 //-------------------------------------------------------------------------------------
|
nuclear@1
|
384 // ***** Creation
|
nuclear@1
|
385
|
nuclear@1
|
386
|
nuclear@1
|
387 // Creates a new DeviceManager and initializes OVR.
|
nuclear@1
|
388 DeviceManager* DeviceManager::Create()
|
nuclear@1
|
389 {
|
nuclear@1
|
390
|
nuclear@1
|
391 if (!System::IsInitialized())
|
nuclear@1
|
392 {
|
nuclear@1
|
393 // Use custom message, since Log is not yet installed.
|
nuclear@1
|
394 OVR_DEBUG_STATEMENT(Log::GetDefaultLog()->
|
nuclear@1
|
395 LogMessage(Log_Debug, "DeviceManager::Create failed - OVR::System not initialized"); );
|
nuclear@1
|
396 return 0;
|
nuclear@1
|
397 }
|
nuclear@1
|
398
|
nuclear@1
|
399 Ptr<Win32::DeviceManager> manager = *new Win32::DeviceManager;
|
nuclear@1
|
400
|
nuclear@1
|
401 if (manager)
|
nuclear@1
|
402 {
|
nuclear@1
|
403 if (manager->Initialize(0))
|
nuclear@1
|
404 {
|
nuclear@1
|
405 manager->AddFactory(&SensorDeviceFactory::Instance);
|
nuclear@1
|
406 manager->AddFactory(&LatencyTestDeviceFactory::Instance);
|
nuclear@1
|
407 manager->AddFactory(&Win32::HMDDeviceFactory::Instance);
|
nuclear@1
|
408
|
nuclear@1
|
409 manager->AddRef();
|
nuclear@1
|
410 }
|
nuclear@1
|
411 else
|
nuclear@1
|
412 {
|
nuclear@1
|
413 manager.Clear();
|
nuclear@1
|
414 }
|
nuclear@1
|
415
|
nuclear@1
|
416 }
|
nuclear@1
|
417
|
nuclear@1
|
418 return manager.GetPtr();
|
nuclear@1
|
419 }
|
nuclear@1
|
420
|
nuclear@1
|
421
|
nuclear@1
|
422 } // namespace OVR
|
nuclear@1
|
423
|