/*-------------------------------------------------------------------------
* 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. */
DE_VERIFY(WaitForSingleObject(waitEvent, INFINITE) == WAIT_OBJECT_0);
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)
#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