You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
472 lines
10 KiB
472 lines
10 KiB
/*-------------------------------------------------------------------------
|
|
* drawElements Utility Library
|
|
* ----------------------------
|
|
*
|
|
* Copyright 2014 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*//*!
|
|
* \file
|
|
* \brief Periodic timer.
|
|
*//*--------------------------------------------------------------------*/
|
|
|
|
#include "deTimer.h"
|
|
#include "deMemory.h"
|
|
#include "deThread.h"
|
|
|
|
#if (DE_OS == DE_OS_WIN32)
|
|
|
|
#define VC_EXTRALEAN
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
|
|
struct deTimer_s
|
|
{
|
|
deTimerCallback callback;
|
|
void* callbackArg;
|
|
|
|
HANDLE timer;
|
|
};
|
|
|
|
static void CALLBACK timerCallback (PVOID lpParameter, BOOLEAN timerOrWaitFired)
|
|
{
|
|
const deTimer* timer = (const deTimer*)lpParameter;
|
|
DE_UNREF(timerOrWaitFired);
|
|
|
|
timer->callback(timer->callbackArg);
|
|
}
|
|
|
|
deTimer* deTimer_create (deTimerCallback callback, void* arg)
|
|
{
|
|
deTimer* timer = (deTimer*)deCalloc(sizeof(deTimer));
|
|
|
|
if (!timer)
|
|
return DE_NULL;
|
|
|
|
timer->callback = callback;
|
|
timer->callbackArg = arg;
|
|
timer->timer = 0;
|
|
|
|
return timer;
|
|
}
|
|
|
|
void deTimer_destroy (deTimer* timer)
|
|
{
|
|
DE_ASSERT(timer);
|
|
|
|
if (deTimer_isActive(timer))
|
|
deTimer_disable(timer);
|
|
|
|
deFree(timer);
|
|
}
|
|
|
|
deBool deTimer_isActive (const deTimer* timer)
|
|
{
|
|
return timer->timer != 0;
|
|
}
|
|
|
|
deBool deTimer_scheduleSingle (deTimer* timer, int milliseconds)
|
|
{
|
|
BOOL ret;
|
|
|
|
DE_ASSERT(timer && milliseconds > 0);
|
|
|
|
if (deTimer_isActive(timer))
|
|
return DE_FALSE;
|
|
|
|
ret = CreateTimerQueueTimer(&timer->timer, NULL, timerCallback, timer, (DWORD)milliseconds, 0, WT_EXECUTEDEFAULT);
|
|
|
|
if (!ret)
|
|
{
|
|
DE_ASSERT(!timer->timer);
|
|
return DE_FALSE;
|
|
}
|
|
|
|
return DE_TRUE;
|
|
}
|
|
|
|
deBool deTimer_scheduleInterval (deTimer* timer, int milliseconds)
|
|
{
|
|
BOOL ret;
|
|
|
|
DE_ASSERT(timer && milliseconds > 0);
|
|
|
|
if (deTimer_isActive(timer))
|
|
return DE_FALSE;
|
|
|
|
ret = CreateTimerQueueTimer(&timer->timer, NULL, timerCallback, timer, (DWORD)milliseconds, (DWORD)milliseconds, WT_EXECUTEDEFAULT);
|
|
|
|
if (!ret)
|
|
{
|
|
DE_ASSERT(!timer->timer);
|
|
return DE_FALSE;
|
|
}
|
|
|
|
return DE_TRUE;
|
|
}
|
|
|
|
void deTimer_disable (deTimer* timer)
|
|
{
|
|
if (timer->timer)
|
|
{
|
|
const int maxTries = 100;
|
|
HANDLE waitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
int tryNdx = 0;
|
|
DE_ASSERT(waitEvent);
|
|
|
|
for (tryNdx = 0; tryNdx < maxTries; tryNdx++)
|
|
{
|
|
BOOL success = DeleteTimerQueueTimer(NULL, timer->timer, waitEvent);
|
|
if (success)
|
|
{
|
|
/* Wait for all callbacks to complete. */
|
|
DWORD res = WaitForSingleObject(waitEvent, INFINITE);
|
|
DE_ASSERT(res == WAIT_OBJECT_0);
|
|
DE_UNREF(res);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
DWORD err = GetLastError();
|
|
if (err == ERROR_IO_PENDING)
|
|
break; /* \todo [2013-03-21 pyry] Does this mean that callback is still in progress? */
|
|
deYield();
|
|
}
|
|
}
|
|
|
|
DE_ASSERT(tryNdx < maxTries);
|
|
|
|
CloseHandle(waitEvent);
|
|
timer->timer = 0;
|
|
}
|
|
}
|
|
|
|
#elif (DE_OS == DE_OS_UNIX || DE_OS == DE_OS_ANDROID || DE_OS == DE_OS_SYMBIAN || DE_OS == DE_OS_QNX)
|
|
|
|
#include <signal.h>
|
|
#include <time.h>
|
|
|
|
struct deTimer_s
|
|
{
|
|
deTimerCallback callback;
|
|
void* callbackArg;
|
|
|
|
timer_t timer;
|
|
|
|
deBool isActive;
|
|
};
|
|
|
|
static void timerCallback (union sigval val)
|
|
{
|
|
const deTimer* timer = (const deTimer*)val.sival_ptr;
|
|
timer->callback(timer->callbackArg);
|
|
}
|
|
|
|
deTimer* deTimer_create (deTimerCallback callback, void* arg)
|
|
{
|
|
deTimer* timer = (deTimer*)deCalloc(sizeof(deTimer));
|
|
struct sigevent sevp;
|
|
|
|
if (!timer)
|
|
return DE_NULL;
|
|
|
|
deMemset(&sevp, 0, sizeof(sevp));
|
|
sevp.sigev_notify = SIGEV_THREAD;
|
|
sevp.sigev_value.sival_ptr = timer;
|
|
sevp.sigev_notify_function = timerCallback;
|
|
|
|
if (timer_create(CLOCK_REALTIME, &sevp, &timer->timer) != 0)
|
|
{
|
|
deFree(timer);
|
|
return DE_NULL;
|
|
}
|
|
|
|
timer->callback = callback;
|
|
timer->callbackArg = arg;
|
|
timer->isActive = DE_FALSE;
|
|
|
|
return timer;
|
|
}
|
|
|
|
void deTimer_destroy (deTimer* timer)
|
|
{
|
|
DE_ASSERT(timer);
|
|
|
|
timer_delete(timer->timer);
|
|
deFree(timer);
|
|
}
|
|
|
|
deBool deTimer_isActive (const deTimer* timer)
|
|
{
|
|
return timer->isActive;
|
|
}
|
|
|
|
deBool deTimer_scheduleSingle (deTimer* timer, int milliseconds)
|
|
{
|
|
struct itimerspec tspec;
|
|
|
|
DE_ASSERT(timer && milliseconds > 0);
|
|
|
|
if (timer->isActive)
|
|
return DE_FALSE;
|
|
|
|
tspec.it_value.tv_sec = milliseconds / 1000;
|
|
tspec.it_value.tv_nsec = (milliseconds % 1000) * 1000;
|
|
tspec.it_interval.tv_sec = 0;
|
|
tspec.it_interval.tv_nsec = 0;
|
|
|
|
if (timer_settime(timer->timer, 0, &tspec, DE_NULL) != 0)
|
|
return DE_FALSE;
|
|
|
|
timer->isActive = DE_TRUE;
|
|
return DE_TRUE;
|
|
}
|
|
|
|
deBool deTimer_scheduleInterval (deTimer* timer, int milliseconds)
|
|
{
|
|
struct itimerspec tspec;
|
|
|
|
DE_ASSERT(timer && milliseconds > 0);
|
|
|
|
if (timer->isActive)
|
|
return DE_FALSE;
|
|
|
|
tspec.it_value.tv_sec = milliseconds / 1000;
|
|
tspec.it_value.tv_nsec = (milliseconds % 1000) * 1000;
|
|
tspec.it_interval.tv_sec = tspec.it_value.tv_sec;
|
|
tspec.it_interval.tv_nsec = tspec.it_value.tv_nsec;
|
|
|
|
if (timer_settime(timer->timer, 0, &tspec, DE_NULL) != 0)
|
|
return DE_FALSE;
|
|
|
|
timer->isActive = DE_TRUE;
|
|
return DE_TRUE;
|
|
}
|
|
|
|
void deTimer_disable (deTimer* timer)
|
|
{
|
|
struct itimerspec tspec;
|
|
|
|
DE_ASSERT(timer);
|
|
|
|
tspec.it_value.tv_sec = 0;
|
|
tspec.it_value.tv_nsec = 0;
|
|
tspec.it_interval.tv_sec = 0;
|
|
tspec.it_interval.tv_nsec = 0;
|
|
|
|
timer_settime(timer->timer, 0, &tspec, DE_NULL);
|
|
|
|
/* \todo [2012-07-10 pyry] How to wait until all pending callbacks have finished? */
|
|
|
|
timer->isActive = DE_FALSE;
|
|
}
|
|
|
|
#else
|
|
|
|
/* Generic thread-based implementation for OSes that lack proper timers. */
|
|
|
|
#include "deThread.h"
|
|
#include "deMutex.h"
|
|
#include "deClock.h"
|
|
|
|
typedef enum TimerState_e
|
|
{
|
|
TIMERSTATE_INTERVAL = 0, /*!< Active interval timer. */
|
|
TIMERSTATE_SINGLE, /*!< Single callback timer. */
|
|
TIMERSTATE_DISABLED, /*!< Disabled timer. */
|
|
|
|
TIMERSTATE_LAST
|
|
} TimerState;
|
|
|
|
typedef struct deTimerThread_s
|
|
{
|
|
deTimerCallback callback; /*!< Callback function. */
|
|
void* callbackArg; /*!< User pointer. */
|
|
|
|
deThread thread; /*!< Thread. */
|
|
int interval; /*!< Timer interval. */
|
|
|
|
deMutex lock; /*!< State lock. */
|
|
volatile TimerState state; /*!< Timer state. */
|
|
} deTimerThread;
|
|
|
|
struct deTimer_s
|
|
{
|
|
deTimerCallback callback; /*!< Callback function. */
|
|
void* callbackArg; /*!< User pointer. */
|
|
deTimerThread* curThread; /*!< Current timer thread. */
|
|
};
|
|
|
|
static void timerThread (void* arg)
|
|
{
|
|
deTimerThread* thread = (deTimerThread*)arg;
|
|
int numCallbacks = 0;
|
|
deBool destroy = DE_TRUE;
|
|
deInt64 lastCallback = (deInt64)deGetMicroseconds();
|
|
|
|
for (;;)
|
|
{
|
|
int sleepTime = 0;
|
|
|
|
deMutex_lock(thread->lock);
|
|
|
|
if (thread->state == TIMERSTATE_SINGLE && numCallbacks > 0)
|
|
{
|
|
destroy = DE_FALSE; /* Will be destroyed by deTimer_disable(). */
|
|
thread->state = TIMERSTATE_DISABLED;
|
|
break;
|
|
}
|
|
else if (thread->state == TIMERSTATE_DISABLED)
|
|
break;
|
|
|
|
deMutex_unlock(thread->lock);
|
|
|
|
sleepTime = thread->interval - (int)(((deInt64)deGetMicroseconds()-lastCallback)/1000);
|
|
if (sleepTime > 0)
|
|
deSleep(sleepTime);
|
|
|
|
lastCallback = (deInt64)deGetMicroseconds();
|
|
thread->callback(thread->callbackArg);
|
|
numCallbacks += 1;
|
|
}
|
|
|
|
/* State lock is held when loop is exited. */
|
|
deMutex_unlock(thread->lock);
|
|
|
|
if (destroy)
|
|
{
|
|
/* Destroy thread except thread->thread. */
|
|
deMutex_destroy(thread->lock);
|
|
deFree(thread);
|
|
}
|
|
}
|
|
|
|
static deTimerThread* deTimerThread_create (deTimerCallback callback, void* arg, int interval, TimerState state)
|
|
{
|
|
deTimerThread* thread = (deTimerThread*)deCalloc(sizeof(deTimerThread));
|
|
|
|
DE_ASSERT(state == TIMERSTATE_INTERVAL || state == TIMERSTATE_SINGLE);
|
|
|
|
if (!thread)
|
|
return DE_NULL;
|
|
|
|
thread->callback = callback;
|
|
thread->callbackArg = arg;
|
|
thread->interval = interval;
|
|
thread->lock = deMutex_create(DE_NULL);
|
|
thread->state = state;
|
|
|
|
thread->thread = deThread_create(timerThread, thread, DE_NULL);
|
|
if (!thread->thread)
|
|
{
|
|
deMutex_destroy(thread->lock);
|
|
deFree(thread);
|
|
return DE_NULL;
|
|
}
|
|
|
|
return thread;
|
|
}
|
|
|
|
deTimer* deTimer_create (deTimerCallback callback, void* arg)
|
|
{
|
|
deTimer* timer = (deTimer*)deCalloc(sizeof(deTimer));
|
|
|
|
if (!timer)
|
|
return DE_NULL;
|
|
|
|
timer->callback = callback;
|
|
timer->callbackArg = arg;
|
|
|
|
return timer;
|
|
}
|
|
|
|
void deTimer_destroy (deTimer* timer)
|
|
{
|
|
if (timer->curThread)
|
|
deTimer_disable(timer);
|
|
deFree(timer);
|
|
}
|
|
|
|
deBool deTimer_isActive (const deTimer* timer)
|
|
{
|
|
if (timer->curThread)
|
|
{
|
|
deBool isActive = DE_FALSE;
|
|
|
|
deMutex_lock(timer->curThread->lock);
|
|
isActive = timer->curThread->state != TIMERSTATE_LAST;
|
|
deMutex_unlock(timer->curThread->lock);
|
|
|
|
return isActive;
|
|
}
|
|
else
|
|
return DE_FALSE;
|
|
}
|
|
|
|
deBool deTimer_scheduleSingle (deTimer* timer, int milliseconds)
|
|
{
|
|
if (timer->curThread)
|
|
deTimer_disable(timer);
|
|
|
|
DE_ASSERT(!timer->curThread);
|
|
timer->curThread = deTimerThread_create(timer->callback, timer->callbackArg, milliseconds, TIMERSTATE_SINGLE);
|
|
|
|
return timer->curThread != DE_NULL;
|
|
}
|
|
|
|
deBool deTimer_scheduleInterval (deTimer* timer, int milliseconds)
|
|
{
|
|
if (timer->curThread)
|
|
deTimer_disable(timer);
|
|
|
|
DE_ASSERT(!timer->curThread);
|
|
timer->curThread = deTimerThread_create(timer->callback, timer->callbackArg, milliseconds, TIMERSTATE_INTERVAL);
|
|
|
|
return timer->curThread != DE_NULL;
|
|
}
|
|
|
|
void deTimer_disable (deTimer* timer)
|
|
{
|
|
if (!timer->curThread)
|
|
return;
|
|
|
|
deMutex_lock(timer->curThread->lock);
|
|
|
|
if (timer->curThread->state != TIMERSTATE_DISABLED)
|
|
{
|
|
/* Just set state to disabled and destroy thread handle. */
|
|
/* \note Assumes that deThread_destroy() can be called while thread is still running
|
|
* and it will not terminate the thread.
|
|
*/
|
|
timer->curThread->state = TIMERSTATE_DISABLED;
|
|
deThread_destroy(timer->curThread->thread);
|
|
timer->curThread->thread = 0;
|
|
deMutex_unlock(timer->curThread->lock);
|
|
|
|
/* Thread will destroy timer->curThread. */
|
|
}
|
|
else
|
|
{
|
|
/* Single timer has expired - we must destroy whole thread structure. */
|
|
deMutex_unlock(timer->curThread->lock);
|
|
deThread_destroy(timer->curThread->thread);
|
|
deMutex_destroy(timer->curThread->lock);
|
|
deFree(timer->curThread);
|
|
}
|
|
|
|
timer->curThread = DE_NULL;
|
|
}
|
|
|
|
#endif
|