Refactor nextFrame() and add setFrameDuration()

nextFrame() changes were in colaboration with @MrBlinky

setFrameDuration() can be used as an alternative to setFrameRate()
This commit is contained in:
Scott Allen 2018-02-09 18:18:19 -05:00
parent 4788b1ab01
commit 460e768ea9
3 changed files with 52 additions and 32 deletions

View File

@ -83,6 +83,7 @@ readUnitName KEYWORD2
safeMode KEYWORD2 safeMode KEYWORD2
saveOnOff KEYWORD2 saveOnOff KEYWORD2
setCursor KEYWORD2 setCursor KEYWORD2
setFrameDuration KEYWORD2
setFrameRate KEYWORD2 setFrameRate KEYWORD2
setRGBled KEYWORD2 setRGBled KEYWORD2
setTextBackground KEYWORD2 setTextBackground KEYWORD2

View File

@ -19,13 +19,9 @@ Arduboy2Base::Arduboy2Base()
currentButtonState = 0; currentButtonState = 0;
previousButtonState = 0; previousButtonState = 0;
// frame management // frame management
setFrameRate(60); setFrameDuration(16);
frameCount = -1; frameCount = 0;
nextFrameStart = 0;
justRendered = false; justRendered = false;
// init not necessary, will be reset after first use
// lastFrameStart
// lastFrameDurationMs
} }
// functions called here should be public so users can create their // functions called here should be public so users can create their
@ -185,6 +181,11 @@ void Arduboy2Base::setFrameRate(uint8_t rate)
eachFrameMillis = 1000 / rate; eachFrameMillis = 1000 / rate;
} }
void Arduboy2Base::setFrameDuration(uint8_t duration)
{
eachFrameMillis = duration;
}
bool Arduboy2Base::everyXFrames(uint8_t frames) bool Arduboy2Base::everyXFrames(uint8_t frames)
{ {
return frameCount % frames == 0; return frameCount % frames == 0;
@ -192,38 +193,27 @@ bool Arduboy2Base::everyXFrames(uint8_t frames)
bool Arduboy2Base::nextFrame() bool Arduboy2Base::nextFrame()
{ {
unsigned long now = millis(); uint8_t now = (uint8_t) millis();
bool tooSoonForNextFrame = now < nextFrameStart; uint8_t frameDurationMs = now - thisFrameStart;
if (justRendered) { if (justRendered) {
lastFrameDurationMs = now - lastFrameStart; lastFrameDurationMs = frameDurationMs;
justRendered = false; justRendered = false;
return false; return false;
} }
else if (tooSoonForNextFrame) { else if (frameDurationMs < eachFrameMillis) {
// if we have MORE than 1ms to spare (hence our comparison with 2), // Only idle if at least a full millisecond remains, since idle() may
// lets sleep for power savings. We don't compare against 1 to avoid // sleep the processor until the next millisecond timer interrupt.
// potential rounding errors - say we're actually 0.5 ms away, but a 1 if (++frameDurationMs < eachFrameMillis) {
// is returned if we go to sleep we might sleep a full 1ms and then
// we'd be running the frame slighly late. So the last 1ms we stay
// awake for perfect timing.
// This is likely trading power savings for absolute timing precision
// and the power savings might be the better goal. At 60 FPS trusting
// chance here might actually achieve a "truer" 60 FPS than the 16ms
// frame duration we get due to integer math.
// We should be woken up by timer0 every 1ms, so it's ok to sleep.
if ((uint8_t)(nextFrameStart - now) >= 2)
idle(); idle();
}
return false; return false;
} }
// pre-render // pre-render
justRendered = true; justRendered = true;
lastFrameStart = now; thisFrameStart = now;
nextFrameStart = now + eachFrameMillis;
frameCount++; frameCount++;
return true; return true;

View File

@ -660,17 +660,47 @@ class Arduboy2Base : public Arduboy2Core
* *
* \details * \details
* Set the frame rate, in frames per second, used by `nextFrame()` to update * Set the frame rate, in frames per second, used by `nextFrame()` to update
* frames at a given rate. If this function isn't used, the default rate will * frames at a given rate. If this function or `setFrameDuration()`
* be 60. * isn't used, the default rate will be 60 (actually 62.5, see note below).
* *
* Normally, the frame rate would be set to the desired value once, at the * Normally, the frame rate would be set to the desired value once, at the
* start of the game, but it can be changed at any time to alter the frame * start of the game, but it can be changed at any time to alter the frame
* update rate. * update rate.
* *
* \see nextFrame() * \note
* \parblock
* The given rate is internally converted to a frame duration in milliseconds,
* rounded down to the nearest integer. Therefore, the actual rate will be
* equal to or higher than the rate given.
* For example, 60 FPS would be 16.67ms per frame. This will be rounded down
* to 16ms, giving an actual frame rate of 62.5 FPS.
* \endparblock
*
* \see nextFrame() setFrameDuration()
*/ */
void setFrameRate(uint8_t rate); void setFrameRate(uint8_t rate);
/** \brief
* Set the frame rate, used by the frame control functions, by giving
* the duration of each frame.
*
* \param duration The desired duration of each frame in milliseconds.
*
* \details
* Set the frame rate by specifying the duration of each frame in
* milliseconds. This is used by `nextFrame()` to update frames at a
* given rate. If this function or `setFrameRate()` isn't used,
* the default will be 16ms per frame.
*
* Normally, the frame rate would be set to the desired value once, at the
* start of the game, but it can be changed at any time to alter the frame
* update rate.
*
* \see nextFrame() setFrameRate()
*/
void setFrameDuration(uint8_t duration);
/** \brief /** \brief
* Indicate that it's time to render the next frame. * Indicate that it's time to render the next frame.
* *
@ -694,7 +724,7 @@ class Arduboy2Base : public Arduboy2Core
* } * }
* \endcode * \endcode
* *
* \see setFrameRate() nextFrameDEV() * \see setFrameRate() setFrameDuration() nextFrameDEV()
*/ */
bool nextFrame(); bool nextFrame();
@ -1120,8 +1150,7 @@ class Arduboy2Base : public Arduboy2Core
// For frame funcions // For frame funcions
uint8_t eachFrameMillis; uint8_t eachFrameMillis;
unsigned long lastFrameStart; uint8_t thisFrameStart;
unsigned long nextFrameStart;
bool justRendered; bool justRendered;
uint8_t lastFrameDurationMs; uint8_t lastFrameDurationMs;
}; };