xref: /reactos/dll/directx/wine/quartz/systemclock.c (revision c2c66aff)
1 /*
2  * Implementation of IReferenceClock
3  *
4  * Copyright 2004 Raphael Junqueira
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 #include "quartz_private.h"
22 
23 typedef struct SystemClockAdviseEntry SystemClockAdviseEntry;
24 struct SystemClockAdviseEntry {
25   SystemClockAdviseEntry* next;
26   SystemClockAdviseEntry* prev;
27 
28   HANDLE           hEvent;
29   REFERENCE_TIME   rtBaseTime;
30   REFERENCE_TIME   rtIntervalTime;
31 };
32 
33 typedef struct SystemClockImpl {
34   IReferenceClock IReferenceClock_iface;
35   LONG ref;
36 
37   /** IReferenceClock */
38   HANDLE         adviseThread;
39   DWORD          adviseThreadId;
40   BOOL           adviseThreadActive;
41   REFERENCE_TIME lastRefTime;
42   DWORD	         lastTimeTickCount;
43   CRITICAL_SECTION safe;
44 
45   SystemClockAdviseEntry* pSingleShotAdvise;
46   SystemClockAdviseEntry* pPeriodicAdvise;
47 } SystemClockImpl;
48 
49 static inline SystemClockImpl *impl_from_IReferenceClock(IReferenceClock *iface)
50 {
51     return CONTAINING_RECORD(iface, SystemClockImpl, IReferenceClock_iface);
52 }
53 
54 
55 static void QUARTZ_RemoveAviseEntryFromQueue(SystemClockImpl* This, SystemClockAdviseEntry* pEntry) {
56   if (pEntry->prev) pEntry->prev->next = pEntry->next;
57   if (pEntry->next) pEntry->next->prev = pEntry->prev;
58   if (This->pSingleShotAdvise == pEntry) This->pSingleShotAdvise = pEntry->next;
59   if (This->pPeriodicAdvise == pEntry)    This->pPeriodicAdvise = pEntry->next;
60 }
61 
62 static void QUARTZ_InsertAviseEntryFromQueue(SystemClockImpl* This, SystemClockAdviseEntry* pEntry, SystemClockAdviseEntry** pQueue) {
63   SystemClockAdviseEntry* prev_it = NULL;
64   SystemClockAdviseEntry* it = NULL;
65   REFERENCE_TIME bornTime =  pEntry->rtBaseTime + pEntry->rtIntervalTime;
66 
67   for (it = *pQueue; NULL != it && (it->rtBaseTime + it->rtIntervalTime) < bornTime; it = it->next) {
68     prev_it = it;
69   }
70   if (NULL == prev_it) {
71     pEntry->prev = NULL;
72     if (NULL != (*pQueue)) pEntry->next = (*pQueue)->next;
73     /*assert( NULL == pEntry->next->prev );*/
74     if (NULL != pEntry->next) pEntry->next->prev = pEntry;
75     (*pQueue) = pEntry;
76   } else {
77     pEntry->prev = prev_it;
78     pEntry->next = prev_it->next;
79     prev_it->next = pEntry;
80     if (NULL != pEntry->next) pEntry->next->prev = pEntry;
81   }
82 }
83 
84 #define MAX_REFTIME            (REFERENCE_TIME)(0x7FFFFFFFFFFFFFFF)
85 #define ADVISE_EXIT            (WM_APP + 0)
86 #define ADVISE_REMOVE          (WM_APP + 2)
87 #define ADVISE_ADD_SINGLESHOT  (WM_APP + 4)
88 #define ADVISE_ADD_PERIODIC    (WM_APP + 8)
89 
90 static DWORD WINAPI SystemClockAdviseThread(LPVOID lpParam) {
91   SystemClockImpl* This = lpParam;
92   DWORD timeOut = INFINITE;
93   DWORD tmpTimeOut;
94   MSG msg;
95   HRESULT hr;
96   REFERENCE_TIME curTime;
97   SystemClockAdviseEntry* it = NULL;
98 
99   TRACE("(%p): Main Loop\n", This);
100 
101   while (TRUE) {
102     if (timeOut > 0) MsgWaitForMultipleObjects(0, NULL, FALSE, timeOut, QS_POSTMESSAGE|QS_SENDMESSAGE|QS_TIMER);
103 
104     EnterCriticalSection(&This->safe);
105     /*timeOut = IReferenceClock_OnTimerUpdated(This); */
106     hr = IReferenceClock_GetTime(&This->IReferenceClock_iface, &curTime);
107     if (FAILED(hr)) {
108       timeOut = INFINITE;
109       goto outrefresh;
110     }
111 
112     /** First SingleShots Advice: sorted list */
113     it = This->pSingleShotAdvise;
114     while ((NULL != it) && (it->rtBaseTime + it->rtIntervalTime) <= curTime) {
115       SystemClockAdviseEntry* nextit = it->next;
116       /** send event ... */
117       SetEvent(it->hEvent);
118       /** ... and Release it */
119       QUARTZ_RemoveAviseEntryFromQueue(This, it);
120       CoTaskMemFree(it);
121       it = nextit;
122     }
123     if (NULL != it) timeOut = (DWORD) ((it->rtBaseTime + it->rtIntervalTime) - curTime) / (REFERENCE_TIME)10000;
124     else timeOut = INFINITE;
125 
126     /** Now Periodics Advice: semi sorted list (sort cannot be used) */
127     for (it = This->pPeriodicAdvise; NULL != it; it = it->next) {
128       if (it->rtBaseTime <= curTime) {
129 	DWORD nPeriods = (DWORD) ((curTime - it->rtBaseTime) / it->rtIntervalTime);
130 	/** Release the semaphore ... */
131 	ReleaseSemaphore(it->hEvent, nPeriods, NULL);
132 	/** ... and refresh time */
133 	it->rtBaseTime += nPeriods * it->rtIntervalTime;
134 	/*assert( it->rtBaseTime + it->rtIntervalTime < curTime );*/
135       }
136       tmpTimeOut = (DWORD) ((it->rtBaseTime + it->rtIntervalTime) - curTime) / (REFERENCE_TIME)10000;
137       if (timeOut > tmpTimeOut) timeOut = tmpTimeOut;
138     }
139 
140 outrefresh:
141     LeaveCriticalSection(&This->safe);
142 
143     while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
144       /** if hwnd we suppose that is a windows event ... */
145       if  (NULL != msg.hwnd) {
146 	TranslateMessage(&msg);
147 	DispatchMessageW(&msg);
148       } else {
149 	switch (msg.message) {
150 	case WM_QUIT:
151 	case ADVISE_EXIT:
152 	  goto outofthread;
153 	case ADVISE_ADD_SINGLESHOT:
154 	case ADVISE_ADD_PERIODIC:
155 	  /** set timeout to 0 to do a rescan now */
156 	  timeOut = 0;
157 	  break;
158 	case ADVISE_REMOVE:
159 	  /** hmmmm what we can do here ... */
160 	  timeOut = INFINITE;
161 	  break;
162 	default:
163 	  ERR("Unhandled message %u. Critical Path\n", msg.message);
164 	  break;
165 	}
166       }
167     }
168   }
169 
170 outofthread:
171   TRACE("(%p): Exiting\n", This);
172   return 0;
173 }
174 /*static DWORD WINAPI SystemClockAdviseThread(LPVOID lpParam) { */
175 
176 static BOOL SystemClockPostMessageToAdviseThread(SystemClockImpl* This, UINT iMsg) {
177   if (FALSE == This->adviseThreadActive) {
178     BOOL res;
179     This->adviseThread = CreateThread(NULL, 0, SystemClockAdviseThread, This, 0, &This->adviseThreadId);
180     if (NULL == This->adviseThread) return FALSE;
181     SetThreadPriority(This->adviseThread, THREAD_PRIORITY_TIME_CRITICAL);
182     This->adviseThreadActive = TRUE;
183     while(1) {
184       res = PostThreadMessageW(This->adviseThreadId, iMsg, 0, 0);
185       /* Let the thread creates its message queue (with MsgWaitForMultipleObjects call) by yielding and retrying */
186       if (!res && (GetLastError() == ERROR_INVALID_THREAD_ID))
187 	Sleep(0);
188       else
189 	break;
190     }
191     return res;
192   }
193   return PostThreadMessageW(This->adviseThreadId, iMsg, 0, 0);
194 }
195 
196 static ULONG WINAPI SystemClockImpl_AddRef(IReferenceClock* iface) {
197   SystemClockImpl *This = impl_from_IReferenceClock(iface);
198   ULONG ref = InterlockedIncrement(&This->ref);
199 
200   TRACE("(%p): AddRef from %d\n", This, ref - 1);
201 
202   return ref;
203 }
204 
205 static HRESULT WINAPI SystemClockImpl_QueryInterface(IReferenceClock* iface, REFIID riid, void** ppobj) {
206   SystemClockImpl *This = impl_from_IReferenceClock(iface);
207   TRACE("(%p, %s,%p)\n", This, debugstr_guid(riid), ppobj);
208 
209   if (IsEqualIID (riid, &IID_IUnknown) ||
210       IsEqualIID (riid, &IID_IReferenceClock)) {
211     SystemClockImpl_AddRef(iface);
212     *ppobj = &This->IReferenceClock_iface;
213     return S_OK;
214   }
215 
216   *ppobj = NULL;
217   WARN("(%p, %s,%p): not found\n", This, debugstr_guid(riid), ppobj);
218   return E_NOINTERFACE;
219 }
220 
221 static ULONG WINAPI SystemClockImpl_Release(IReferenceClock* iface) {
222   SystemClockImpl *This = impl_from_IReferenceClock(iface);
223   ULONG ref = InterlockedDecrement(&This->ref);
224   TRACE("(%p): ReleaseRef to %d\n", This, ref);
225   if (ref == 0) {
226     if (SystemClockPostMessageToAdviseThread(This, ADVISE_EXIT)) {
227       WaitForSingleObject(This->adviseThread, INFINITE);
228       CloseHandle(This->adviseThread);
229     }
230     This->safe.DebugInfo->Spare[0] = 0;
231     DeleteCriticalSection(&This->safe);
232     CoTaskMemFree(This);
233   }
234   return ref;
235 }
236 
237 static HRESULT WINAPI SystemClockImpl_GetTime(IReferenceClock* iface, REFERENCE_TIME* pTime) {
238   SystemClockImpl *This = impl_from_IReferenceClock(iface);
239   DWORD curTimeTickCount;
240   HRESULT hr = S_OK;
241 
242   TRACE("(%p, %p)\n", This, pTime);
243 
244   if (NULL == pTime) {
245     return E_POINTER;
246   }
247 
248   curTimeTickCount = GetTickCount();
249 
250   EnterCriticalSection(&This->safe);
251   if (This->lastTimeTickCount == curTimeTickCount) hr = S_FALSE;
252   This->lastRefTime += (REFERENCE_TIME) (DWORD) (curTimeTickCount - This->lastTimeTickCount) * (REFERENCE_TIME) 10000;
253   This->lastTimeTickCount = curTimeTickCount;
254   *pTime = This->lastRefTime;
255   LeaveCriticalSection(&This->safe);
256   return hr;
257 }
258 
259 static HRESULT WINAPI SystemClockImpl_AdviseTime(IReferenceClock* iface, REFERENCE_TIME rtBaseTime, REFERENCE_TIME rtStreamTime, HEVENT hEvent, DWORD_PTR* pdwAdviseCookie) {
260   SystemClockImpl *This = impl_from_IReferenceClock(iface);
261   SystemClockAdviseEntry* pEntry = NULL;
262 
263   TRACE("(%p, 0x%s, 0x%s, %ld, %p)\n", This, wine_dbgstr_longlong(rtBaseTime),
264       wine_dbgstr_longlong(rtStreamTime), hEvent, pdwAdviseCookie);
265 
266   if (!hEvent) {
267     return E_INVALIDARG;
268   }
269   if (0 >= rtBaseTime + rtStreamTime) {
270     return E_INVALIDARG;
271   }
272   if (NULL == pdwAdviseCookie) {
273     return E_POINTER;
274   }
275   pEntry = CoTaskMemAlloc(sizeof(SystemClockAdviseEntry));
276   if (NULL == pEntry) {
277     return E_OUTOFMEMORY;
278   }
279   ZeroMemory(pEntry, sizeof(SystemClockAdviseEntry));
280 
281   pEntry->hEvent = (HANDLE) hEvent;
282   pEntry->rtBaseTime = rtBaseTime + rtStreamTime;
283   pEntry->rtIntervalTime = 0;
284 
285   EnterCriticalSection(&This->safe);
286   QUARTZ_InsertAviseEntryFromQueue(This, pEntry, &This->pSingleShotAdvise);
287   LeaveCriticalSection(&This->safe);
288 
289   SystemClockPostMessageToAdviseThread(This, ADVISE_ADD_SINGLESHOT);
290 
291   *pdwAdviseCookie = (DWORD_PTR) (pEntry);
292   return S_OK;
293 }
294 
295 static HRESULT WINAPI SystemClockImpl_AdvisePeriodic(IReferenceClock* iface, REFERENCE_TIME rtStartTime, REFERENCE_TIME rtPeriodTime, HSEMAPHORE hSemaphore, DWORD_PTR* pdwAdviseCookie) {
296   SystemClockImpl *This = impl_from_IReferenceClock(iface);
297   SystemClockAdviseEntry* pEntry = NULL;
298 
299   TRACE("(%p, 0x%s, 0x%s, %ld, %p)\n", This, wine_dbgstr_longlong(rtStartTime),
300       wine_dbgstr_longlong(rtPeriodTime), hSemaphore, pdwAdviseCookie);
301 
302   if (!hSemaphore) {
303     return E_INVALIDARG;
304   }
305   if (0 >= rtStartTime || 0 >= rtPeriodTime) {
306     return E_INVALIDARG;
307   }
308   if (NULL == pdwAdviseCookie) {
309     return E_POINTER;
310   }
311   pEntry = CoTaskMemAlloc(sizeof(SystemClockAdviseEntry));
312   if (NULL == pEntry) {
313     return E_OUTOFMEMORY;
314   }
315   ZeroMemory(pEntry, sizeof(SystemClockAdviseEntry));
316 
317   pEntry->hEvent = (HANDLE) hSemaphore;
318   pEntry->rtBaseTime = rtStartTime;
319   pEntry->rtIntervalTime = rtPeriodTime;
320 
321   EnterCriticalSection(&This->safe);
322   QUARTZ_InsertAviseEntryFromQueue(This, pEntry, &This->pPeriodicAdvise);
323   LeaveCriticalSection(&This->safe);
324 
325   SystemClockPostMessageToAdviseThread(This, ADVISE_ADD_PERIODIC);
326 
327   *pdwAdviseCookie = (DWORD_PTR) (pEntry);
328   return S_OK;
329 }
330 
331 static HRESULT WINAPI SystemClockImpl_Unadvise(IReferenceClock* iface, DWORD_PTR dwAdviseCookie) {
332   SystemClockImpl *This = impl_from_IReferenceClock(iface);
333   SystemClockAdviseEntry* pEntry = NULL;
334   SystemClockAdviseEntry* it = NULL;
335   HRESULT ret = S_OK;
336   TRACE("(%p, %lu)\n", This, dwAdviseCookie);
337 
338   pEntry = (SystemClockAdviseEntry*) dwAdviseCookie;
339 
340   EnterCriticalSection(&This->safe);
341   for (it = This->pPeriodicAdvise; NULL != it && it != pEntry; it = it->next) ;
342   if (it != pEntry) {
343     for (it = This->pSingleShotAdvise; NULL != it && it != pEntry; it = it->next) ;
344     if (it != pEntry) {
345       ret = S_FALSE;
346       goto out;
347     }
348   }
349 
350   QUARTZ_RemoveAviseEntryFromQueue(This, pEntry);
351   CoTaskMemFree(pEntry);
352 
353   SystemClockPostMessageToAdviseThread(This, ADVISE_REMOVE);
354 
355 out:
356   LeaveCriticalSection(&This->safe);
357   return ret;
358 }
359 
360 static const IReferenceClockVtbl SystemClock_Vtbl =
361 {
362     SystemClockImpl_QueryInterface,
363     SystemClockImpl_AddRef,
364     SystemClockImpl_Release,
365     SystemClockImpl_GetTime,
366     SystemClockImpl_AdviseTime,
367     SystemClockImpl_AdvisePeriodic,
368     SystemClockImpl_Unadvise
369 };
370 
371 HRESULT QUARTZ_CreateSystemClock(IUnknown * pUnkOuter, LPVOID * ppv) {
372   SystemClockImpl* obj = NULL;
373 
374   TRACE("(%p,%p)\n", ppv, pUnkOuter);
375 
376   obj = CoTaskMemAlloc(sizeof(SystemClockImpl));
377   if (NULL == obj) 	{
378     *ppv = NULL;
379     return E_OUTOFMEMORY;
380   }
381   ZeroMemory(obj, sizeof(SystemClockImpl));
382 
383   obj->IReferenceClock_iface.lpVtbl = &SystemClock_Vtbl;
384   obj->ref = 0;  /* will be inited by QueryInterface */
385 
386   obj->lastTimeTickCount = GetTickCount();
387   InitializeCriticalSection(&obj->safe);
388   obj->safe.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": SystemClockImpl.safe");
389 
390   return SystemClockImpl_QueryInterface(&obj->IReferenceClock_iface, &IID_IReferenceClock, ppv);
391 }
392