// // Copyright(C) 2005-2014 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. // // 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 uint64_t current_time; // If non-zero, callbacks are currently paused. static int opl_timer_paused; // Offset in microseconds to adjust time due to the fact that playback // was paused. static uint64_t pause_offset = 0; // 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(uint64_t *next_time) { // If paused, just wait in 50ms increments until unpaused. // Update pause_offset so after we unpause, the callback // times will be right. if (opl_timer_paused) { *next_time = current_time + 50 * OPL_MS; pause_offset += 50 * OPL_MS; return 0; } // 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 * OPL_MS; 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) + pause_offset; return *next_time <= current_time; } static uint64_t GetNextTime(void) { opl_callback_t callback; void *callback_data; uint64_t 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) { uint64_t next_time; uint64_t 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() * OPL_MS; if (next_time > now) { SDL_Delay((next_time - now) / OPL_MS); } // 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(); opl_timer_paused = 0; pause_offset = 0; 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(uint64_t us, opl_callback_t callback, void *data) { SDL_LockMutex(callback_queue_mutex); OPL_Queue_Push(callback_queue, callback, data, current_time + us - pause_offset); SDL_UnlockMutex(callback_queue_mutex); } void OPL_Timer_ClearCallbacks(void) { SDL_LockMutex(callback_queue_mutex); OPL_Queue_Clear(callback_queue); SDL_UnlockMutex(callback_queue_mutex); } void OPL_Timer_AdjustCallbacks(float factor) { SDL_LockMutex(callback_queue_mutex); OPL_Queue_AdjustCallbacks(callback_queue, current_time, factor); SDL_UnlockMutex(callback_queue_mutex); } void OPL_Timer_Lock(void) { SDL_LockMutex(timer_mutex); } void OPL_Timer_Unlock(void) { SDL_UnlockMutex(timer_mutex); } void OPL_Timer_SetPaused(int paused) { SDL_LockMutex(callback_queue_mutex); opl_timer_paused = paused; SDL_UnlockMutex(callback_queue_mutex); }