| /*------------------------------------------------------------------------- |
| * 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 |