/* ______ ___ ___ * /\ _ \ /\_ \ /\_ \ * \ \ \L\ \\//\ \ \//\ \ __ __ _ __ ___ * \ \ __ \ \ \ \ \ \ \ /'__`\ /'_ `\/\`'__\/ __`\ * \ \ \/\ \ \_\ \_ \_\ \_/\ __//\ \L\ \ \ \//\ \L\ \ * \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/ * \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/ * /\____/ * \_/__/ * * New timer API for Unixen. * * By Peter Wang. * * See readme.txt for copyright information. */ #include #include #include "allegro.h" #include "allegro/internal/aintern.h" #include "allegro/internal/aintern_dtor.h" #include "allegro/internal/aintern_events.h" /* readability typedefs */ typedef long msecs_t; typedef long usecs_t; /* forward declarations */ static usecs_t timer_thread_handle_tick(usecs_t interval); static void timer_handle_tick(AL_TIMER *this); struct AL_TIMER { AL_EVENT_SOURCE es; bool started; usecs_t speed_usecs; long count; long counter; /* counts down to zero=blastoff */ }; /* * The timer thread that runs in the background to drive the timers. */ static _AL_THREAD timer_thread; static _AL_MUTEX timer_thread_mutex = _AL_MUTEX_UNINITED; static _AL_VECTOR active_timers = _AL_VECTOR_INITIALIZER(AL_TIMER *); /* timer_thread_proc: [timer thread] * The timer thread procedure itself. */ static void timer_thread_proc(_AL_THREAD *self, void *unused) { #if 0 /* Block all signals. */ /* This was needed for some reason in v4, but I can't remember why, * and it might not be relevant now. It has a tendency to create * zombie processes if the program is aborted abnormally, so I'm * taking it out for now. */ { sigset_t mask; sigfillset(&mask); pthread_sigmask(SIG_BLOCK, &mask, NULL); } #endif #ifdef ALLEGRO_QNX /* thread priority adjustment for QNX: * The timer thread is set to the highest relative priority. * (see the comment in src/qnx/qsystem.c about the scheduling policy) */ { struct sched_param sparam; int spolicy; if (pthread_getschedparam(pthread_self(), &spolicy, &sparam) == EOK) { sparam.sched_priority += 4; pthread_setschedparam(pthread_self(), spolicy, &sparam); } } #endif struct timeval old_time; struct timeval new_time; struct timeval delay; usecs_t interval = 0x8000; gettimeofday(&old_time, NULL); while (!_al_thread_should_stop(self)) { /* Go to sleep for a short time. `select' is more accurate than * `usleep' (or even `nanosleep') on my Linux system. */ delay.tv_sec = interval / 1000000L; delay.tv_usec = interval % 1000000L; select(0, NULL, NULL, NULL, &delay); _al_mutex_lock(&timer_thread_mutex); { /* Calculate actual time elapsed. */ gettimeofday(&new_time, NULL); interval = ((new_time.tv_sec - old_time.tv_sec) * 1000000L + (new_time.tv_usec - old_time.tv_usec)); old_time = new_time; /* Handle a tick. */ interval = timer_thread_handle_tick(interval); } _al_mutex_unlock(&timer_thread_mutex); } } /* timer_thread_handle_tick: [timer thread] * Call handle_tick() method of every timer in active_timers, and * returns the duration that the timer thread should try to sleep * next time. */ static usecs_t timer_thread_handle_tick(usecs_t interval) { usecs_t new_delay = 0x8000; unsigned int i; for (i = 0; i < _al_vector_size(&active_timers); i++) { AL_TIMER **slot = _al_vector_ref(&active_timers, i); AL_TIMER *timer = *slot; timer->counter -= interval; while (timer->counter <= 0) { timer->counter += timer->speed_usecs; timer_handle_tick(timer); } if ((timer->counter > 0) && (timer->counter < new_delay)) new_delay = timer->counter; } return new_delay; } /* * Timer objects */ /* al_install_timer: [primary thread] * Create a new timer object. */ AL_TIMER* al_install_timer(msecs_t speed_msecs) { ASSERT(speed_msecs > 0); { AL_TIMER *timer = _AL_MALLOC(sizeof *timer); ASSERT(timer); if (timer) { _al_event_source_init(&timer->es, AL_EVENT_TIMER); timer->started = false; timer->count = 0; timer->speed_usecs = speed_msecs * 1000; timer->counter = 0; _al_register_destructor(timer, (void (*)(void *)) al_uninstall_timer); } return timer; } } /* al_uninstall_timer: [primary thread] * Destroy this timer object. */ void al_uninstall_timer(AL_TIMER *this) { ASSERT(this); al_stop_timer(this); _al_unregister_destructor(this); _al_event_source_free(&this->es); _AL_FREE(this); } /* al_start_timer: [primary thread] * Start this timer. If it is the first started timer, the * background timer thread is subsequently started. */ void al_start_timer(AL_TIMER *this) { ASSERT(this); { size_t new_size; if (this->started) return; _al_mutex_lock(&timer_thread_mutex); { AL_TIMER **slot; this->started = true; this->counter = this->speed_usecs; slot = _al_vector_alloc_back(&active_timers); *slot = this; new_size = _al_vector_size(&active_timers); } _al_mutex_unlock(&timer_thread_mutex); if (new_size == 1) { _al_mutex_init(&timer_thread_mutex); _al_thread_create(&timer_thread, timer_thread_proc, NULL); } } } /* al_stop_timer: [primary thread] * Stop this timer. If it is the last started timer, the background * timer thread is subsequently stopped. */ void al_stop_timer(AL_TIMER *this) { ASSERT(this); { size_t new_size; if (!this->started) return; _al_mutex_lock(&timer_thread_mutex); { _al_vector_find_and_delete(&active_timers, &this); this->started = false; new_size = _al_vector_size(&active_timers); } _al_mutex_unlock(&timer_thread_mutex); if (new_size == 0) { _al_thread_join(&timer_thread); _al_mutex_destroy(&timer_thread_mutex); _al_vector_free(&active_timers); } } } /* al_timer_is_started: [primary thread] * Return if this timer is started. */ bool al_timer_is_started(AL_TIMER *this) { ASSERT(this); return this->started; } /* al_timer_get_speed: [primary thread] * Return this timer's speed. */ msecs_t al_timer_get_speed(AL_TIMER *this) { ASSERT(this); return this->speed_usecs / 1000; } /* al_timer_set_speed: [primary thread] * Change this timer's speed. */ void al_timer_set_speed(AL_TIMER *this, msecs_t new_speed_msecs) { ASSERT(this); ASSERT(new_speed_msecs > 0); _al_mutex_lock(&timer_thread_mutex); { if (this->started) { this->counter -= this->speed_usecs; this->counter += new_speed_msecs * 1000; } this->speed_usecs = new_speed_msecs * 1000; } _al_mutex_unlock(&timer_thread_mutex); } /* al_timer_get_count: [primary thread] * Return this timer's count. */ long al_timer_get_count(AL_TIMER *this) { ASSERT(this); return this->count; } /* al_timer_set_count: [primary thread] * Change this timer's count. */ void al_timer_set_count(AL_TIMER *this, long new_count) { ASSERT(this); _al_mutex_lock(&timer_thread_mutex); { this->count = new_count; } _al_mutex_unlock(&timer_thread_mutex); } /* timer_handle_tick: [timer thread] * Handle a single tick. */ static void timer_handle_tick(AL_TIMER *this) { /* Lock out event source helper functions (e.g. the release hook * could be invoked simultaneously with this function). */ _al_event_source_lock(&this->es); { /* Update the count. */ this->count++; /* Generate an event, maybe. */ if (_al_event_source_needs_to_generate_event(&this->es, AL_EVENT_TIMER)) { AL_EVENT *event = _al_event_source_get_unused_event(&this->es); if (event) { event->timer.type = AL_EVENT_TIMER; event->timer.timestamp = al_current_time(); event->timer.count = this->count; _al_event_source_emit_event(&this->es, event); } } } _al_event_source_unlock(&this->es); } /* * Local Variables: * c-basic-offset: 3 * indent-tabs-mode: nil * End: */