scribble

Tactics DS

Sobre nosotros Blog de desarrollo GitHub

16 Feb 2015
Implementando temporizadores

Las limitaciones de hardware siempre son un engorro, y más cuando tienes que hacer conversiones entre las unidades con las que piensas y las unidades con las que trabaja el hardware.

Esta ocasión no iba a ser menos y navegando por la documentación de libnds hemos podido comprobar que la consola dispone de 4 relojes en hardware, con un sistema de acceso más bien engorroso.

Aprovechando las posibilidades que ofrece C++ hemos implementado una API para trabajar de forma mucho más cómoda con los relojes:

namespace FMAW {

namespace Timer {

/**
 * Inits time subsystem if required.
 */
void init();

/**
 * Enqueues given function so it will be called
 * @param  callback   Function to be called.
 * @param  delta      Time in ms to wait.
 * @param  repetitive Call it over and over again?
 * @return            An identifier to cancel it later.
 */
int enqueue_function(void (*callback)(int),
                     unsigned int delta,
                     bool repetitive);

/**
 * Dequeues given function so it won't called again.
 * @param  id Function to be dequeued.
 * @return    Whether function was dequeued properly or not.
 */
bool dequeue_function(int id);

/**
 * Runs any function that needed to be called.
 */
void check();

}  // namespace Timer

}  // namespace FMAW

El API cuenta con los métodos enqueue_function y dequeue_function que nos permiten registrar una función para que se ejecute tras una cierta espera, con posibilidad de seguir llamando a la función hasta que decidamos cancelarla, y de solicitar que se deje de llamar a una función previamente registrada.

Los métodos init y check se encargan de iniciar el hardware del reloj y de hacer las comprobaciones pertinentes. La función check puede ser llamada con seguridad en cualquier momento, ya que no ejecuta ninguna función que no tuviese que ser ejecutada, esto permite que se invoque a check tanto desde un reloj implementado en hardware como desde el bucle principal del juego.

Para permitir mayor portabilidad nuestra primera implementación requiere que el programador del juego haga la llamada a check en su bucle principal. Nosotros hacemos la llamada antes de dibujar cada frame.

Internamente el API se sostiene en el reloj de hardware que ha iniciado previamente para saber el tiempo transcurrido y si debe o no debe llamar a cada función.

Así quedaría una función main básica que actualiza los atributos internos del objeto g_bug cada 200 milisegundos:

int main(void) {
    FMAW::Timer::init();  // Init timer API.

    auto func = [](int ID) {
        g_bug.update();  // Assuming it was previously declared and initialized.
    };

    FMAW::Timer::enqueue_function(func, 200, true);

    while (1) {
        // Rendering period:
        // Update game objects.
        update_logic();

        // Wait for the vblank period.
        swiWaitForVBlank();

        FMAW::Timer::check();
    }

    return 0;
}

En las pruebas que hemos realizado no hemos apreciado ninguna sobrecarga. El código queda considerablemente más sencillo que usando los relojes de hardware directamente y nos permite desarrollar una implementación del API independiente para otras plataformas, manteniendo exactamente el mismo código del juego en todas ellas.


Hasta la próxima,
Mark, Víctor y Lluís at 09:40

scribble

Sobre nosotros Blog de desarrollo GitHub