Why would rendering need a fixed timestep?
Anyways, here is the code I use for timestepping:
rms_timer.hUsed to try and generically define a timer setup. The original idea was meant to allow this code to be interchangable with higher performance timers, or ported more easily to non windows OSes. This is the interface, with one global interface defined for app-wide use.
It's missing a [decent] 'pause' feature.
Otherwise, the class takes a parameter to define how many milliseconds is a 'tick' of the time step. By modifying this parameter, the entire app can be tuned to be faster or slower.
#ifndef RMS_TIMER#define RMS_TIMER#include <limits>struct rmstimer{protected: long ticks; long remainder; long mspertick; void addms(long ms); void countbyms(long ms); void countbyticks(long tks);public: long count(){return(ticks);} virtual void update()=0; rmstimer(long mpt=33):mspertick(mpt),ticks(0),remainder(0){}};void rmstimer::addms(long ms){//// Add ms milliseconds to the counter.//ticks=ms/mspertick;remainder=remainder+ms%mspertick;if(remainder > mspertick){ ++ticks; remainder=remainder-mspertick;}}void rmstimer::countbyms(long ms){//// Add ms milliseconds to the timer.//addms(ms);}void rmstimer::countbyticks(long tks){//// Add tks ticks to the timer.//addms(tks * mspertick);}rmstimer *timer=0;#endif
rms_win32_timer.hThe concrete implimentation for win32.#ifndef RMS_WIN32_TIMER#define RMS_WIN32_TIMER// include kernel32.dll#include <windows.h>#include "rms_timer.h"#include <limits>/*struct hp_win32_timer: protected rmstimer{private: DWORD hpc_per_ms; void sethpc();protected: DWORD last_count;public: hp_win32_timer(long mpt):rmstimer(mpt){sethpc();} void update(); long */struct win32_tickcount_timer: public rmstimer{private:protected: DWORD last_count;public: win32_tickcount_timer(long mpt):rmstimer(mpt),last_count(GetTickCount()){} void update(); void reset();};void win32_tickcount_timer::update(){//// Update ticks from GetTickCount()//DWORD new_count=GetTickCount();// handle rollover isolated case.if (last_count > new_count){ addms((numeric_limits<DWORD>::max)() - last_count + new_count);}else{ addms(new_count - last_count);}last_count=new_count;}void win32_tickcount_timer::reset(){//// Reset the last_count due to the app losing focus.//DWORD new_count=GetTickCount();last_count=new_count;ticks=0;remainder=0;}#endif
Now, in actual use, functions are generally defined to process one increment of time. The increment might be one or more ticks. If more than one increment passes, the function will be called multiple times to accumulate the effect. This allows the function to be ignorant of how much time a tick actually is, and focus on the task at hand.
Here's some simple renderers that use the timer. I render as fast as possible. By placing things in the rendering path, they're called each game loop.
rms_timer_renderers.hIncluded is the base class, timed_trigger. A timed_trigger stores a functor, and executed that functor a specified number of times, every specified number of 'ticks'. After executing the last time, it auto-matically removes itself from the rendering tree.
Also included is a renderable to cause other renderables to blink. Every so often [determined by the timed_trigger] it toggles a visibility flag for all its children.
fps_counter is a simple fps renderer, which is explicitly set to 1 second checks. It'd be easily fixable to do wider checks by modifying the parameter to timed_trigger to be non-constant.
alpha_fade is a simple renderer to fade in/out its children, at a rate specified.
By having the classes inherit from the base timed_trigger, all of the common functionality of triggering is handled. The only thing the classes need to worry about is implimenting what happens every timestep [or X timesteps].
#ifndef RMS_TIMER_RENDERERS#define RMS_TIMER_RENDERERS#include "rms_basic_gui.h"#include "rms_timer.h"#include "rmsrect.h"#include "rmsd3dfont.h"#include "voidvoid_guiaction.h"#include "vine.h"template <typename F>struct timed_trigger: virtual public basero{private:protected: F f; long repeat; long trigger; long ticks; bool onlyf;public: virtual void render(){ ticks=ticks + timer->count(); while (ticks>=trigger){ if (repeat>0){ repeat--; } ticks=ticks-trigger; if (onlyf){ // to allow f() to delete 'this' f(); // // RMS: jul-24-05: // // commenting return, as the current incarnation of rendertree [for_each] // does not gracefully continue in the tree should the current iterator's // object be yanked out from underneigth it. // // oops... Changed to use an out-of-band sort of callback mechanism. // //return; }else{ f(); } if (repeat==0){ //out_of_order_executables.push_back(storega(voidvoidfunctor(this, &basero::no_tree_delete))); no_tree_close(); //repeat--; //return; rendertree(); return; } } rendertree(); } void manual_trigger(){ ticks=trigger; } timed_trigger(F inf=F(), long trig=30, long rep=1, bool oof=0):basero(new standard_rect(rect())), f(inf), trigger(trig), repeat(rep), ticks(0), onlyf(oof){} virtual ~timed_trigger(){}};struct blinky_funct{protected: basero *target;public: void operator()(){ br_child_iterator it; for (it=target->children.begin(); it != target->children.end(); ++it){ (*it)->toggle_visible(); } } blinky_funct(basero *t):target(t){}};struct blinky: virtual public basero, virtual public timed_trigger<blinky_funct>{private:protected: bool enabled;public: virtual void enable(){enabled=1;} virtual void disable(){enabled=0;} virtual void render(){ if (enabled){ timed_trigger::render(); }else{ basero::render(); } } blinky(long rate=33, bool e=1): basero(new standard_rect(rect())), timed_trigger<blinky_funct>( blinky_funct(this), rate, -1, 0), enabled(e){}};struct fps_counter: virtual public basero, virtual public timed_trigger<storega>{private:protected: long frames; int fps;public: // TODO: rerect. void tick(){ fps=frames; frames=0; } virtual void render(){ ++frames; timed_trigger::render(); } const int *fpsref(){return(&fps);} fps_counter(depended_rect_generator *rg, d3dfont *f=fonts.fetch(), DWORD c=0xff000000): basero(rg), timed_trigger<storega>(storega(voidvoidfunctor(this,&fps_counter::tick)),30,-1,0), frames(0), fps(0){ ro_text *rot; ref_int_renderer *rint; calculated_font_rect_generator<standard_rect> *cfrg=new calculated_font_rect_generator<standard_rect>(0,0,new standard_rect(rg->fetch())); rot=new ro_text(cfrg,"FPS: ", f, c); cfrg->set(&rot->font,rot->txtref()); rot->parent=this; children.push_back(rot); rint=new ref_int_renderer(new relative_rect(cfrg->dependable(),new rect_area(cfrg->dependable()),4,0,0,1),fpsref(),f,c); rint->parent=this; children.push_back(rint); } virtual ~fps_counter(){}};struct alpha_fade: virtual public basero, virtual public timed_trigger<storega>{private:protected: bool direction; unsigned char limit; unsigned char rate;public: void fade(){ br_child_iterator it; DWORD c; unsigned char a; for(it=children.begin();it!=children.end();++it){ c=(*it)->color(); a=(c & 0xff000000) / 0x01000000; if (direction){ if (a + rate >= limit){ a=limit; }else{ a=a+rate; } }else{ if (a - rate <= limit){ a=limit; }else{ a=a-rate; } } c=c % 0x01000000; c=c + (a * 0x01000000); (*it)->color(c); } } virtual void render(){ timed_trigger::render(); } void short_circut(){ br_child_iterator it; DWORD c; unsigned char a; for(it=children.begin();it!=children.end();++it){ c=(*it)->color(); a=limit; c=c % 0x01000000; c=c + (a * 0x01000000); (*it)->color(c); } } alpha_fade(unsigned char r=4, bool d=1, unsigned char l=255):direction(d), limit(l), rate(r), basero(new standard_rect(rect())), timed_trigger<storega>(storega(voidvoidfunctor(this,&alpha_fade::fade)),1,255,0){} virtual ~alpha_fade(){}};#endif