This repository has been archived on 2023-07-11. You can view files and clone it, but cannot push or open issues or pull requests.
chocolate-doom-wii/opl/opl_timer.c
2016-10-30 18:40:00 -04:00

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);
}