blob: a3e150476838106ca3afec6506d675a71feca77c [file] [log] [blame]
/*
* Implementation of IReferenceClock
*
* Copyright 2004 Raphael Junqueira
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "quartz_private.h"
#include "wine/debug.h"
#include "wine/unicode.h"
#include <assert.h>
WINE_DEFAULT_DEBUG_CHANNEL(quartz);
typedef struct SystemClockAdviseEntry SystemClockAdviseEntry;
struct SystemClockAdviseEntry {
SystemClockAdviseEntry* next;
SystemClockAdviseEntry* prev;
HANDLE hEvent;
REFERENCE_TIME rtBaseTime;
REFERENCE_TIME rtIntervalTime;
};
typedef struct SystemClockImpl {
IReferenceClock IReferenceClock_iface;
LONG ref;
/** IReferenceClock */
HANDLE adviseThread;
DWORD adviseThreadId;
BOOL adviseThreadActive;
REFERENCE_TIME lastRefTime;
DWORD lastTimeTickCount;
CRITICAL_SECTION safe;
SystemClockAdviseEntry* pSingleShotAdvise;
SystemClockAdviseEntry* pPeriodicAdvise;
} SystemClockImpl;
static inline SystemClockImpl *impl_from_IReferenceClock(IReferenceClock *iface)
{
return CONTAINING_RECORD(iface, SystemClockImpl, IReferenceClock_iface);
}
static void QUARTZ_RemoveAviseEntryFromQueue(SystemClockImpl* This, SystemClockAdviseEntry* pEntry) {
if (pEntry->prev) pEntry->prev->next = pEntry->next;
if (pEntry->next) pEntry->next->prev = pEntry->prev;
if (This->pSingleShotAdvise == pEntry) This->pSingleShotAdvise = pEntry->next;
if (This->pPeriodicAdvise == pEntry) This->pPeriodicAdvise = pEntry->next;
}
static void QUARTZ_InsertAviseEntryFromQueue(SystemClockImpl* This, SystemClockAdviseEntry* pEntry, SystemClockAdviseEntry** pQueue) {
SystemClockAdviseEntry* prev_it = NULL;
SystemClockAdviseEntry* it = NULL;
REFERENCE_TIME bornTime = pEntry->rtBaseTime + pEntry->rtIntervalTime;
for (it = *pQueue; NULL != it && (it->rtBaseTime + it->rtIntervalTime) < bornTime; it = it->next) {
prev_it = it;
}
if (NULL == prev_it) {
pEntry->prev = NULL;
if (NULL != (*pQueue)) pEntry->next = (*pQueue)->next;
/*assert( NULL == pEntry->next->prev );*/
if (NULL != pEntry->next) pEntry->next->prev = pEntry;
(*pQueue) = pEntry;
} else {
pEntry->prev = prev_it;
pEntry->next = prev_it->next;
prev_it->next = pEntry;
if (NULL != pEntry->next) pEntry->next->prev = pEntry;
}
}
#define MAX_REFTIME (REFERENCE_TIME)(0x7FFFFFFFFFFFFFFF)
#define ADVISE_EXIT (WM_APP + 0)
#define ADVISE_REMOVE (WM_APP + 2)
#define ADVISE_ADD_SINGLESHOT (WM_APP + 4)
#define ADVISE_ADD_PERIODIC (WM_APP + 8)
static DWORD WINAPI SystemClockAdviseThread(LPVOID lpParam) {
SystemClockImpl* This = lpParam;
DWORD timeOut = INFINITE;
DWORD tmpTimeOut;
MSG msg;
HRESULT hr;
REFERENCE_TIME curTime;
SystemClockAdviseEntry* it = NULL;
TRACE("(%p): Main Loop\n", This);
while (TRUE) {
if (timeOut > 0) MsgWaitForMultipleObjects(0, NULL, FALSE, timeOut, QS_POSTMESSAGE|QS_SENDMESSAGE|QS_TIMER);
EnterCriticalSection(&This->safe);
/*timeOut = IReferenceClock_OnTimerUpdated(This); */
hr = IReferenceClock_GetTime(&This->IReferenceClock_iface, &curTime);
if (FAILED(hr)) {
timeOut = INFINITE;
goto outrefresh;
}
/** First SingleShots Advice: sorted list */
it = This->pSingleShotAdvise;
while ((NULL != it) && (it->rtBaseTime + it->rtIntervalTime) <= curTime) {
SystemClockAdviseEntry* nextit = it->next;
/** send event ... */
SetEvent(it->hEvent);
/** ... and Release it */
QUARTZ_RemoveAviseEntryFromQueue(This, it);
CoTaskMemFree(it);
it = nextit;
}
if (NULL != it) timeOut = (DWORD) ((it->rtBaseTime + it->rtIntervalTime) - curTime) / (REFERENCE_TIME)10000;
else timeOut = INFINITE;
/** Now Periodics Advice: semi sorted list (sort cannot be used) */
for (it = This->pPeriodicAdvise; NULL != it; it = it->next) {
if (it->rtBaseTime <= curTime) {
DWORD nPeriods = (DWORD) ((curTime - it->rtBaseTime) / it->rtIntervalTime);
/** Release the semaphore ... */
ReleaseSemaphore(it->hEvent, nPeriods, NULL);
/** ... and refresh time */
it->rtBaseTime += nPeriods * it->rtIntervalTime;
/*assert( it->rtBaseTime + it->rtIntervalTime < curTime );*/
}
tmpTimeOut = (DWORD) ((it->rtBaseTime + it->rtIntervalTime) - curTime) / (REFERENCE_TIME)10000;
if (timeOut > tmpTimeOut) timeOut = tmpTimeOut;
}
outrefresh:
LeaveCriticalSection(&This->safe);
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) {
/** if hwnd we suppose that is a windows event ... */
if (NULL != msg.hwnd) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
} else {
switch (msg.message) {
case WM_QUIT:
case ADVISE_EXIT:
goto outofthread;
case ADVISE_ADD_SINGLESHOT:
case ADVISE_ADD_PERIODIC:
/** set timeout to 0 to do a rescan now */
timeOut = 0;
break;
case ADVISE_REMOVE:
/** hmmmm what we can do here ... */
timeOut = INFINITE;
break;
default:
ERR("Unhandled message %u. Critical Path\n", msg.message);
break;
}
}
}
}
outofthread:
TRACE("(%p): Exiting\n", This);
return 0;
}
/*static DWORD WINAPI SystemClockAdviseThread(LPVOID lpParam) { */
static BOOL SystemClockPostMessageToAdviseThread(SystemClockImpl* This, UINT iMsg) {
if (FALSE == This->adviseThreadActive) {
BOOL res;
This->adviseThread = CreateThread(NULL, 0, SystemClockAdviseThread, This, 0, &This->adviseThreadId);
if (NULL == This->adviseThread) return FALSE;
SetThreadPriority(This->adviseThread, THREAD_PRIORITY_TIME_CRITICAL);
This->adviseThreadActive = TRUE;
while(1) {
res = PostThreadMessageW(This->adviseThreadId, iMsg, 0, 0);
/* Let the thread creates its message queue (with MsgWaitForMultipleObjects call) by yielding and retrying */
if (!res && (GetLastError() == ERROR_INVALID_THREAD_ID))
Sleep(0);
else
break;
}
return res;
}
return PostThreadMessageW(This->adviseThreadId, iMsg, 0, 0);
}
static ULONG WINAPI SystemClockImpl_AddRef(IReferenceClock* iface) {
SystemClockImpl *This = impl_from_IReferenceClock(iface);
ULONG ref = InterlockedIncrement(&This->ref);
TRACE("(%p): AddRef from %d\n", This, ref - 1);
return ref;
}
static HRESULT WINAPI SystemClockImpl_QueryInterface(IReferenceClock* iface, REFIID riid, void** ppobj) {
SystemClockImpl *This = impl_from_IReferenceClock(iface);
TRACE("(%p, %s,%p)\n", This, debugstr_guid(riid), ppobj);
if (IsEqualIID (riid, &IID_IUnknown) ||
IsEqualIID (riid, &IID_IReferenceClock)) {
SystemClockImpl_AddRef(iface);
*ppobj = &This->IReferenceClock_iface;
return S_OK;
}
*ppobj = NULL;
WARN("(%p, %s,%p): not found\n", This, debugstr_guid(riid), ppobj);
return E_NOINTERFACE;
}
static ULONG WINAPI SystemClockImpl_Release(IReferenceClock* iface) {
SystemClockImpl *This = impl_from_IReferenceClock(iface);
ULONG ref = InterlockedDecrement(&This->ref);
TRACE("(%p): ReleaseRef to %d\n", This, ref);
if (ref == 0) {
if (SystemClockPostMessageToAdviseThread(This, ADVISE_EXIT)) {
WaitForSingleObject(This->adviseThread, INFINITE);
CloseHandle(This->adviseThread);
}
This->safe.DebugInfo->Spare[0] = 0;
DeleteCriticalSection(&This->safe);
CoTaskMemFree(This);
}
return ref;
}
static HRESULT WINAPI SystemClockImpl_GetTime(IReferenceClock* iface, REFERENCE_TIME* pTime) {
SystemClockImpl *This = impl_from_IReferenceClock(iface);
DWORD curTimeTickCount;
HRESULT hr = S_OK;
TRACE("(%p, %p)\n", This, pTime);
if (NULL == pTime) {
return E_POINTER;
}
curTimeTickCount = GetTickCount();
EnterCriticalSection(&This->safe);
if (This->lastTimeTickCount == curTimeTickCount) hr = S_FALSE;
This->lastRefTime += (REFERENCE_TIME) (DWORD) (curTimeTickCount - This->lastTimeTickCount) * (REFERENCE_TIME) 10000;
This->lastTimeTickCount = curTimeTickCount;
*pTime = This->lastRefTime;
LeaveCriticalSection(&This->safe);
return hr;
}
static HRESULT WINAPI SystemClockImpl_AdviseTime(IReferenceClock* iface, REFERENCE_TIME rtBaseTime, REFERENCE_TIME rtStreamTime, HEVENT hEvent, DWORD_PTR* pdwAdviseCookie) {
SystemClockImpl *This = impl_from_IReferenceClock(iface);
SystemClockAdviseEntry* pEntry = NULL;
TRACE("(%p, 0x%s, 0x%s, %ld, %p)\n", This, wine_dbgstr_longlong(rtBaseTime),
wine_dbgstr_longlong(rtStreamTime), hEvent, pdwAdviseCookie);
if (!hEvent) {
return E_INVALIDARG;
}
if (0 >= rtBaseTime + rtStreamTime) {
return E_INVALIDARG;
}
if (NULL == pdwAdviseCookie) {
return E_POINTER;
}
pEntry = CoTaskMemAlloc(sizeof(SystemClockAdviseEntry));
if (NULL == pEntry) {
return E_OUTOFMEMORY;
}
ZeroMemory(pEntry, sizeof(SystemClockAdviseEntry));
pEntry->hEvent = (HANDLE) hEvent;
pEntry->rtBaseTime = rtBaseTime + rtStreamTime;
pEntry->rtIntervalTime = 0;
EnterCriticalSection(&This->safe);
QUARTZ_InsertAviseEntryFromQueue(This, pEntry, &This->pSingleShotAdvise);
LeaveCriticalSection(&This->safe);
SystemClockPostMessageToAdviseThread(This, ADVISE_ADD_SINGLESHOT);
*pdwAdviseCookie = (DWORD_PTR) (pEntry);
return S_OK;
}
static HRESULT WINAPI SystemClockImpl_AdvisePeriodic(IReferenceClock* iface, REFERENCE_TIME rtStartTime, REFERENCE_TIME rtPeriodTime, HSEMAPHORE hSemaphore, DWORD_PTR* pdwAdviseCookie) {
SystemClockImpl *This = impl_from_IReferenceClock(iface);
SystemClockAdviseEntry* pEntry = NULL;
TRACE("(%p, 0x%s, 0x%s, %ld, %p)\n", This, wine_dbgstr_longlong(rtStartTime),
wine_dbgstr_longlong(rtPeriodTime), hSemaphore, pdwAdviseCookie);
if (!hSemaphore) {
return E_INVALIDARG;
}
if (0 >= rtStartTime || 0 >= rtPeriodTime) {
return E_INVALIDARG;
}
if (NULL == pdwAdviseCookie) {
return E_POINTER;
}
pEntry = CoTaskMemAlloc(sizeof(SystemClockAdviseEntry));
if (NULL == pEntry) {
return E_OUTOFMEMORY;
}
ZeroMemory(pEntry, sizeof(SystemClockAdviseEntry));
pEntry->hEvent = (HANDLE) hSemaphore;
pEntry->rtBaseTime = rtStartTime;
pEntry->rtIntervalTime = rtPeriodTime;
EnterCriticalSection(&This->safe);
QUARTZ_InsertAviseEntryFromQueue(This, pEntry, &This->pPeriodicAdvise);
LeaveCriticalSection(&This->safe);
SystemClockPostMessageToAdviseThread(This, ADVISE_ADD_PERIODIC);
*pdwAdviseCookie = (DWORD_PTR) (pEntry);
return S_OK;
}
static HRESULT WINAPI SystemClockImpl_Unadvise(IReferenceClock* iface, DWORD_PTR dwAdviseCookie) {
SystemClockImpl *This = impl_from_IReferenceClock(iface);
SystemClockAdviseEntry* pEntry = NULL;
SystemClockAdviseEntry* it = NULL;
HRESULT ret = S_OK;
TRACE("(%p, %lu)\n", This, dwAdviseCookie);
pEntry = (SystemClockAdviseEntry*) dwAdviseCookie;
EnterCriticalSection(&This->safe);
for (it = This->pPeriodicAdvise; NULL != it && it != pEntry; it = it->next) ;
if (it != pEntry) {
for (it = This->pSingleShotAdvise; NULL != it && it != pEntry; it = it->next) ;
if (it != pEntry) {
ret = S_FALSE;
goto out;
}
}
QUARTZ_RemoveAviseEntryFromQueue(This, pEntry);
CoTaskMemFree(pEntry);
SystemClockPostMessageToAdviseThread(This, ADVISE_REMOVE);
out:
LeaveCriticalSection(&This->safe);
return ret;
}
static const IReferenceClockVtbl SystemClock_Vtbl =
{
SystemClockImpl_QueryInterface,
SystemClockImpl_AddRef,
SystemClockImpl_Release,
SystemClockImpl_GetTime,
SystemClockImpl_AdviseTime,
SystemClockImpl_AdvisePeriodic,
SystemClockImpl_Unadvise
};
HRESULT QUARTZ_CreateSystemClock(IUnknown * pUnkOuter, LPVOID * ppv) {
SystemClockImpl* obj = NULL;
TRACE("(%p,%p)\n", ppv, pUnkOuter);
obj = CoTaskMemAlloc(sizeof(SystemClockImpl));
if (NULL == obj) {
*ppv = NULL;
return E_OUTOFMEMORY;
}
ZeroMemory(obj, sizeof(SystemClockImpl));
obj->IReferenceClock_iface.lpVtbl = &SystemClock_Vtbl;
obj->ref = 0; /* will be inited by QueryInterface */
obj->lastTimeTickCount = GetTickCount();
InitializeCriticalSection(&obj->safe);
obj->safe.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": SystemClockImpl.safe");
return SystemClockImpl_QueryInterface(&obj->IReferenceClock_iface, &IID_IReferenceClock, ppv);
}