251 lines
6.1 KiB
C
251 lines
6.1 KiB
C
//
|
|
// 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);
|
|
}
|
|
|