diff options
Diffstat (limited to 'opl/opl_timer.c')
-rw-r--r-- | opl/opl_timer.c | 214 |
1 files changed, 214 insertions, 0 deletions
diff --git a/opl/opl_timer.c b/opl/opl_timer.c new file mode 100644 index 00000000..62ffbd37 --- /dev/null +++ b/opl/opl_timer.c @@ -0,0 +1,214 @@ +// Emacs style mode select -*- C++ -*- +//----------------------------------------------------------------------------- +// +// Copyright(C) 2009 Simon Howard +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +// 02111-1307, USA. +// +// DESCRIPTION: +// OPL timer thread. +// Once started using OPL_Timer_StartThread, the thread sleeps, +// waking up to invoke callbacks set using OPL_Timer_SetCallback. +// +//----------------------------------------------------------------------------- + +#include "SDL.h" + +#include "opl_timer.h" +#include "opl_queue.h" + +typedef enum +{ + THREAD_STATE_STOPPED, + THREAD_STATE_RUNNING, + THREAD_STATE_STOPPING, +} thread_state_t; + +static SDL_Thread *timer_thread = NULL; +static thread_state_t timer_thread_state; +static int current_time; + +// Queue of callbacks waiting to be invoked. +// The callback queue mutex is held while the callback queue structure +// or current_time is being accessed. + +static opl_callback_queue_t *callback_queue; +static SDL_mutex *callback_queue_mutex; + +// The timer mutex is held while timer callback functions are being +// invoked, so that the calling code can prevent clashes. + +static SDL_mutex *timer_mutex; + +// Returns true if there is a callback at the head of the queue ready +// to be invoked. Otherwise, next_time is set to the time when the +// timer thread must wake up again to check. + +static int CallbackWaiting(unsigned int *next_time) +{ + // If there are no queued callbacks, sleep for 50ms at a time + // until a callback is added. + + if (OPL_Queue_IsEmpty(callback_queue)) + { + *next_time = current_time + 50; + return 0; + } + + // Read the time of the first callback in the queue. + // If the time for the callback has not yet arrived, + // we must sleep until the callback time. + + *next_time = OPL_Queue_Peek(callback_queue); + + return *next_time <= current_time; +} + +static unsigned int GetNextTime(void) +{ + opl_callback_t callback; + void *callback_data; + unsigned int next_time; + int have_callback; + + // Keep running through callbacks until there are none ready to + // run. When we run out of callbacks, next_time will be set. + + do + { + SDL_LockMutex(callback_queue_mutex); + + // Check if the callback at the head of the list is ready to + // be invoked. If so, pop from the head of the queue. + + have_callback = CallbackWaiting(&next_time); + + if (have_callback) + { + OPL_Queue_Pop(callback_queue, &callback, &callback_data); + } + + SDL_UnlockMutex(callback_queue_mutex); + + // Now invoke the callback, if we have one. + // The timer mutex is held while the callback is invoked. + + if (have_callback) + { + SDL_LockMutex(timer_mutex); + callback(callback_data); + SDL_UnlockMutex(timer_mutex); + } + } while (have_callback); + + return next_time; +} + +static int ThreadFunction(void *unused) +{ + unsigned int next_time; + unsigned int now; + + // Keep running until OPL_Timer_StopThread is called. + + while (timer_thread_state == THREAD_STATE_RUNNING) + { + // Get the next time that we must sleep until, and + // wait until that time. + + next_time = GetNextTime(); + now = SDL_GetTicks(); + + if (next_time > now) + { + SDL_Delay(next_time - now); + } + + // Update the current time. + + SDL_LockMutex(callback_queue_mutex); + current_time = next_time; + SDL_UnlockMutex(callback_queue_mutex); + } + + timer_thread_state = THREAD_STATE_STOPPED; + + return 0; +} + +static void InitResources(void) +{ + callback_queue = OPL_Queue_Create(); + timer_mutex = SDL_CreateMutex(); + callback_queue_mutex = SDL_CreateMutex(); +} + +static void FreeResources(void) +{ + OPL_Queue_Destroy(callback_queue); + SDL_DestroyMutex(callback_queue_mutex); + SDL_DestroyMutex(timer_mutex); +} + +int OPL_Timer_StartThread(void) +{ + InitResources(); + + timer_thread_state = THREAD_STATE_RUNNING; + current_time = SDL_GetTicks(); + + timer_thread = SDL_CreateThread(ThreadFunction, NULL); + + if (timer_thread == NULL) + { + timer_thread_state = THREAD_STATE_STOPPED; + FreeResources(); + + return 0; + } + + return 1; +} + +void OPL_Timer_StopThread(void) +{ + timer_thread_state = THREAD_STATE_STOPPING; + + while (timer_thread_state != THREAD_STATE_STOPPED) + { + SDL_Delay(1); + } + + FreeResources(); +} + +void OPL_Timer_SetCallback(unsigned int ms, opl_callback_t callback, void *data) +{ + SDL_LockMutex(callback_queue_mutex); + OPL_Queue_Push(callback_queue, callback, data, current_time + ms); + SDL_UnlockMutex(callback_queue_mutex); +} + +void OPL_Timer_Lock(void) +{ + SDL_LockMutex(timer_mutex); +} + +void OPL_Timer_Unlock(void) +{ + SDL_UnlockMutex(timer_mutex); +} + |