Wiki
Tutoriales Programacion Timer
Timer de alta resolución en Windows (Q/A)
En muchas ocasiones, al hacer efectos, los programadores principiantes suelen cometer el error de no hacer los movimientos independientes de la velocidad del procesador.
Pongamos el caso, un punto empieza a moverse en la posición horizontal 100, y debe alcanzar la posición horizontal 200.
La idea general para cualquier programador recién llegado sería:
void comienza()
{
x = 100;
}
void redibuja()
{
if (x < 200)
x++;
dibuja_punto(x, 0);
}
Esto conlleva un problema. Funcionará a una velocidad constante que depende de lo rápido que se llame a la función redibuja(). Pongamos por caso que la función redibuja se llama 60 veces por segundo, X tardaría en llegar a 200, 1.6 segundos.
Ahora si esta misma aplicación la llevamos a otro ordenador, en el cual (por la velocidad de refresco, por la diferencia de velocidad del procesador, o por lo que sea), esa función se llama 80 veces por segundo, X tardaría en llegar a 200, 1.25 segundos.
Esto es aceptable para una aplicación de gestión, o incluso para una demo que no requiera sincronización con una música (la experiencia dirá que no es aceptable para una demo incluso si no requiere sincronización, pero eso es otra historia).
En el caso de una demo, generalmente no sólo queremos que X pase de 100 a 200, sino que además lo queremos hacer en un tiempo determinado.
Aquí es donde entra en juego un temporizador (o timer).
La idea general, usando un timer, sería, poniendo el caso de que queremos que x pase de 100 a 200 en 5 segundos:
void comienza()
{
comienzo = 100;
final = 200;
tiempo_animacion = 5000; // milisegundos
}
void redibuja()
{
x = comienzo + diferencia_de_tiempo() * (final-comienzo) / tiempo_animacion;
dibuja_punto(x, 0);
}
La stdlib de C, al igual que prácticamente cualquier librería estándar de lenguajes de programación proveen de timers, pero normalmente la resolución de estos es más bien baja. La función clock() de C nos devolverá el numero de ticks de procesador (lo cual podríamos usar para devolver el número de segundos, usando la macro CLOCKS_PER_SEC). En realidad, dependerá de la librería, esta macro junto con esta función tendrá diferente resolución, lo cual no es aceptable (por ejemplo, en DJGPP, CLOCKS_PER_SEC es 91, mientras que en Borland y en Dev-C++, CLOCKS_PER_SEC es 1000).
La solución pasa (en Windows) por usar uno de los llamados "Timers Multimedia", que la API provee.
Por comodidad, lo más sencillo es hacer un objeto que contenga todo lo que se necesita para mover cualquier tipo de objeto (mover, rotar, o lo que sea).
Aquí os dejo la clase que yo estuve usando todo el tiempo que estuve haciendo demos (bajo Windows), sin ningún problema:
timer.h
#ifndef TIMER_H
#define TIMER_H
#include <windows.h>
#include "../include/types.h"
#define TIMER_PRECISION_S 1
#define TIMER_PRECISION_TENTH_S 10
#define TIMER_PRECISION_HUNDREDTH_S 100
#define TIMER_PRECISION_MILLISECOND 1000
#define TIMER_PRECISION_TENTH_MS 10000
#define TIMER_PRECISION_HUNDREDTH_MS 100000
#define TIMER_PRECISION_THOUSANDTH_MS 1000000
class TTimer {
private:
int64 startValue;
int64 actualValue;
int64 counterFrequency;
int32 msOffset;
public:
TTimer();
void start(void);
void setOffset(int32 offset);
int32 MulDiv64(int64 nNumber, int64 nNumerator, int64 nDenominator);
int32 getTicks(void);
int32 getMs(void);
int32 getTicksPerSecond(void);
int32 getPrecisionTimeCount(int32 precision);
int32 getCurrentFrame(int32 totalframes, int32 totalms);
};
#endif
timer.cpp
#include "timer.h"
#include <time.h>
int32 TTimer::MulDiv64(int64 nNumber, int64 nNumerator, int64 nDenominator) {
return (int32) ((nNumber * nNumerator) / nDenominator);
}
TTimer::TTimer() {
msOffset = 0;
QueryPerformanceFrequency((LARGE_INTEGER*)&counterFrequency);
}
void TTimer::start(void) {
QueryPerformanceCounter((LARGE_INTEGER*)&startValue);
}
int32 TTimer::getMs(void) {
QueryPerformanceCounter((LARGE_INTEGER*)&actualValue);
actualValue -= startValue;
return MulDiv64(actualValue, 1000, counterFrequency) + msOffset;
}
void TTimer::setOffset(int32 offset) {
msOffset = offset;
}
int32 TTimer::getPrecisionTimeCount(int32 precision) {
QueryPerformanceCounter((LARGE_INTEGER*)&actualValue);
actualValue -= startValue;
return MulDiv64(actualValue, precision, counterFrequency);
}
int32 TTimer::getCurrentFrame(int32 totalframes, int32 totalms) {
QueryPerformanceCounter((LARGE_INTEGER*)&actualValue);
actualValue -= startValue;
int32 t=MulDiv64(actualValue, 1000, counterFrequency)+msOffset;
return MulDiv64(totalframes, t, totalms);
}
int32 TTimer::getTicks(void) {
QueryPerformanceCounter((LARGE_INTEGER*)&actualValue);
actualValue -= startValue;
return (int32) actualValue;
}
int32 TTimer::getTicksPerSecond(void) {
return (int32) counterFrequency/1000;
}
El modo de empleo, en el ejemplo que pusimos antes, sería de lo más sencillo:
#include "timer.h"
TTimer *mi_timer;
void comienza()
{
mi_timer = new TTimer();
mi_timer->start();
comienzo = 100;
final = 200;
tiempo_animacion = 5000; // milisegundos
}
void redibuja()
{
x = comienzo + TTimer->getCurrentFrame(final-comienzo, tiempo_animacion);
dibuja_punto(x, 0);
}
Espero que os sirva, cualquier duda, en mi e-mail: jcl (arroba) javiercampos (punto) info, o a la página de Q/A.
Por Jcl / Unknown Productions
Un añadido, el código del timer para c#, con una función GetSlidePosition aún más completa, para usar dicha función en el ejemplo antes puesto:
void mi_clase::redibuja()
{
x = mi_timer->GetSlidePosition(comienzo, fin, 0, tiempo_animacion);
dibuja_punto(x, 0);
}
Aqui el codigo en c#:
/*****************************************************************
* CodeName: SceneFactory.Net
* Assembly: Core
* Type: Source Code (Class Library)
* Version: 0.1
* Description: Hi-Res Timer Library (Win32 Only)
*
* Revisions
* ------------------------------------------------
* [F] 20/10/2003, Jcl - Shaping the thing up
* - Implemented Standard Timer using Kernel32 Performance Counters (Win32 Only)
* [F] 7/11/2003, Jcl
* - Implemented SlideCounts, renamed CClass to .NET default
* - Fixed a bug with timing offset (presumably)
*****************************************************************/
using System;
using System.Runtime.InteropServices;
namespace SceneFactory.Core.Utils
{
public class Timer
{
[DllImport("kernel32", EntryPoint="QueryPerformanceCounter")]
protected static extern uint QueryPerformanceCounter(ref long t);
[DllImport("kernel32", EntryPoint="QueryPerformanceFrequency")]
protected static extern uint QueryPerformanceFrequency(ref long t);
public const long TimerPrecision_Seconds = 1;
public const long TimerPrecision_TenthSeconds = 10;
public const long TimerPrecision_HundredthSeconds = 100;
public const long TimerPrecision_Millisecond = 1000;
public const long TimerPrecision_TenthMillisecond = 10000;
public const long TimerPrecision_HundredthMillisecond = 100000;
public const long TimerPrecision_MicroSecond = 1000000;
public const long TimerPrecision_NanoSecond = 1000000000;
protected long m_StartValue;
protected long m_ActualValue;
protected long m_CounterFrequency;
protected long m_Offset;
protected long m_OffsetPrecision;
protected bool m_Initialized;
public long Milliseconds { get { return GetMs(); } }
public Timer()
{
Initialize(0);
}
public Timer(long Offset)
{
Initialize(Offset);
}
protected void Initialize(long Offset)
{
m_Initialized = false;
m_StartValue = 0;
m_ActualValue = 0;
m_Offset = Offset;
m_OffsetPrecision = TimerPrecision_Millisecond;
try
{
QueryPerformanceFrequency(ref m_CounterFrequency);
m_Initialized = true;
}
catch(System.EntryPointNotFoundException e)
{
SFLog.ExceptionLog(Lang.GetString("CantLoadTimerRoutine"),e);
}
}
public void Start()
{
if(!m_Initialized)
return;
QueryPerformanceCounter(ref m_StartValue);
}
public void ResetOffset()
{
m_Offset = 0;
m_OffsetPrecision = TimerPrecision_Millisecond;
}
public void TimerOffset(long Offset)
{
if(!m_Initialized)
return;
TimerOffset(Offset, m_OffsetPrecision);
}
public void TimerOffset(long Offset, long Precision)
{
if(!m_Initialized)
return;
if(Precision != m_OffsetPrecision)
{
// Convert Offset to requested precision before adding
m_Offset = m_Offset * Precision / m_OffsetPrecision;
}
m_OffsetPrecision = Precision;
m_Offset += Offset;
}
public long GetPrecisionTimeCount(long Precision)
{
if(!m_Initialized)
return 0;
QueryPerformanceCounter(ref m_ActualValue);
m_ActualValue -= m_StartValue;
return (m_ActualValue * Precision / m_CounterFrequency)
+ ((m_Offset * m_OffsetPrecision)/ Precision);
}
public long GetSlidePosition(long StartValue, long EndValue,
long StartMilliseconds, long EndMilliseconds)
{
long Time = GetMs();
if(EndMilliseconds-StartMilliseconds == 0)
return StartValue;
if(StartMilliseconds>EndMilliseconds)
{
long vSwap = EndMilliseconds;
EndMilliseconds = StartMilliseconds;
StartMilliseconds = vSwap;
}
if(Time<=StartMilliseconds)
return StartValue;
if(Time>=EndMilliseconds)
return EndValue;
return StartValue + ((Time-StartMilliseconds)*(EndValue-StartValue)
/ (EndMilliseconds-StartMilliseconds));
}
public long GetCurrentFrame(long TotalFrames, long TotalMilliseconds)
{
if(!m_Initialized)
return 0;
QueryPerformanceCounter(ref m_ActualValue);
m_ActualValue -= m_StartValue;
long t = (m_ActualValue * TimerPrecision_Millisecond / m_CounterFrequency)
+ ((m_Offset * m_OffsetPrecision)/ TimerPrecision_Millisecond);
return (TotalFrames * t)/TotalMilliseconds;
}
public long GetMs()
{
if(!m_Initialized)
return 0;
return GetPrecisionTimeCount(TimerPrecision_Millisecond);
}
public long GetTimerTicks()
{
if(!m_Initialized)
return 0;
QueryPerformanceCounter(ref m_ActualValue);
m_ActualValue -= m_StartValue;
return m_ActualValue;
}
public long GetTicksPerSecond()
{
if(!m_Initialized)
return 0;
return m_CounterFrequency/1000;
}
}
}
