added Timer feature

This commit is contained in:
wgroeneveld 2018-12-06 21:53:53 +01:00
parent 6511994a53
commit ad1a178320
10 changed files with 180 additions and 2 deletions

View File

@ -191,6 +191,16 @@ Useful text manipulation:
Changing the font style assumes a tile width of 32 and the same symbol indexes! It also resets the font color and map so call this before doing anything else. Changing the font style assumes a tile width of 32 and the same symbol indexes! It also resets the font color and map so call this before doing anything else.
#### Utilities
There's a game `Timer` class available using `engine->getTimer()`. It counts miliseconds, seconds, minutes and hours.
Do not forget this is an estimate as modulo and divide operations are expensive.
Each VBlank occurs every 280806 cycles (`CYCLES_PER_BLANK`), each cycle is 59.59ns
So, each VBlank occurs every 16.73322954 miliseconds or 16733.22954 microseconds.
The microseconds after the comma are rounded so irregularities are bound to occur after hours of timing..
#### Error logging #### Error logging
The text stream is also used if something goes wrong, there's a macro `failure_gba(WHOOPS)` that prints file, line, method and "exception" message to the text stream background. The text stream is also used if something goes wrong, there's a macro `failure_gba(WHOOPS)` that prints file, line, method and "exception" message to the text stream background.

View File

@ -43,10 +43,19 @@ void SampleStartScene::load() {
.buildPtr(); .buildPtr();
TextStream::instance().setText("PRESS START", 3, 8); TextStream::instance().setText("PRESS START", 3, 8);
engine->getTimer()->start();
engine->enqueueMusic(zelda_music_16K_mono, zelda_music_16K_mono_bytes); engine->enqueueMusic(zelda_music_16K_mono, zelda_music_16K_mono_bytes);
} }
void SampleStartScene::tick(u16 keys) { void SampleStartScene::tick(u16 keys) {
TextStream::instance().setText(engine->getTimer()->to_string(), 18, 1);
if(pressingAorB && !((keys & KEY_A) || (keys & KEY_B))) {
engine->getTimer()->toggle();
pressingAorB = false;
}
if(keys & KEY_START) { if(keys & KEY_START) {
if(!engine->isTransitioning()) { if(!engine->isTransitioning()) {
engine->enqueueSound(zelda_secret_16K_mono, zelda_secret_16K_mono_bytes); engine->enqueueSound(zelda_secret_16K_mono, zelda_secret_16K_mono_bytes);
@ -63,5 +72,7 @@ void SampleStartScene::tick(u16 keys) {
animation->flipVertically(true); animation->flipVertically(true);
} else if(keys & KEY_DOWN) { } else if(keys & KEY_DOWN) {
animation->flipVertically(false); animation->flipVertically(false);
} else if((keys & KEY_A) || (keys & KEY_B)) {
pressingAorB = true;
} }
} }

View File

@ -12,6 +12,7 @@ private:
std::unique_ptr<Sprite> animation; std::unique_ptr<Sprite> animation;
std::unique_ptr<Sprite> finalFantasyGuy; std::unique_ptr<Sprite> finalFantasyGuy;
std::unique_ptr<Sprite> smiley; std::unique_ptr<Sprite> smiley;
bool pressingAorB = false;
public: public:
std::vector<Sprite *> sprites() override; std::vector<Sprite *> sprites() override;

View File

@ -18,7 +18,7 @@ add_library(${PROJECT_NAME}
src/background/text_stream.cpp src/background/text_stream.cpp
src/background/background.cpp src/background/background.cpp
src/effects/fade_out_scene.cpp src/effects/fade_out_scene.cpp
src/sound_control.cpp src/scene.cpp) src/sound_control.cpp src/scene.cpp src/timer.cpp)
target_include_directories(${PROJECT_NAME} PUBLIC target_include_directories(${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>

View File

@ -12,6 +12,7 @@
#include <libgba-sprite-engine/effects/scene_effect.h> #include <libgba-sprite-engine/effects/scene_effect.h>
#include "scene.h" #include "scene.h"
#include "sound_control.h" #include "sound_control.h"
#include "timer.h"
#define GBA_SCREEN_WIDTH 240 #define GBA_SCREEN_WIDTH 240
#define GBA_SCREEN_HEIGHT 160 #define GBA_SCREEN_HEIGHT 160
@ -26,6 +27,7 @@ private:
bool disableTextBg; bool disableTextBg;
SpriteManager spriteManager; SpriteManager spriteManager;
static std::unique_ptr<Timer> timer;
static std::unique_ptr<SoundControl> activeChannelA; static std::unique_ptr<SoundControl> activeChannelA;
static std::unique_ptr<SoundControl> activeChannelB; static std::unique_ptr<SoundControl> activeChannelB;
@ -40,6 +42,7 @@ private:
public: public:
GBAEngine(); GBAEngine();
Timer* getTimer();
void setScene(Scene* scene); void setScene(Scene* scene);
void dynamicallyAddSprite(Sprite* s) { spriteManager.add(s); } void dynamicallyAddSprite(Sprite* s) { spriteManager.add(s); }
void transitionIntoScene(Scene* scene, SceneEffect* effect); void transitionIntoScene(Scene* scene, SceneEffect* effect);

View File

@ -0,0 +1,41 @@
//
// Created by Wouter Groeneveld on 06/12/18.
//
#ifndef GBA_SPRITE_ENGINE_PROJECT_TIMER_H
#define GBA_SPRITE_ENGINE_PROJECT_TIMER_H
#include <ostream>
class Timer {
private:
int microsecs, msecs, secs, minutes, hours;
bool active;
public:
Timer() : active(false) {
reset();
}
void reset();
void start();
void toggle() {
if(isActive()) stop();
else start();
}
bool isActive() { return active; }
void stop();
void onvblank();
std::string to_string();
int getMsecs() { return msecs; }
int getSecs() { return secs; }
int getMinutes() { return minutes; }
int getHours() { return hours; }
friend std::ostream& operator<<(std::ostream& os, Timer& timer);
};
#endif //GBA_SPRITE_ENGINE_PROJECT_TIMER_H

View File

@ -9,12 +9,17 @@
std::unique_ptr<SoundControl> GBAEngine::activeChannelA; std::unique_ptr<SoundControl> GBAEngine::activeChannelA;
std::unique_ptr<SoundControl> GBAEngine::activeChannelB; std::unique_ptr<SoundControl> GBAEngine::activeChannelB;
std::unique_ptr<Timer> GBAEngine::timer;
void GBAEngine::vsync() { void GBAEngine::vsync() {
while (REG_VCOUNT >= 160); while (REG_VCOUNT >= 160);
while (REG_VCOUNT < 160); while (REG_VCOUNT < 160);
} }
Timer* GBAEngine::getTimer() {
return GBAEngine::timer.get();
}
void GBAEngine::onVBlank() { void GBAEngine::onVBlank() {
// WARNING this is a very dangerous piece of code. // WARNING this is a very dangerous piece of code.
// GBA IRQs seem eager to crash or eat up CPU. Get in, disable stuff, work, enable, get out! // GBA IRQs seem eager to crash or eat up CPU. Get in, disable stuff, work, enable, get out!
@ -23,6 +28,8 @@ void GBAEngine::onVBlank() {
unsigned short tempInterruptState = REG_IF; unsigned short tempInterruptState = REG_IF;
if((REG_IF & INTERRUPT_VBLANK) == INTERRUPT_VBLANK) { if((REG_IF & INTERRUPT_VBLANK) == INTERRUPT_VBLANK) {
GBAEngine::timer->onvblank();
if(GBAEngine::activeChannelA) { if(GBAEngine::activeChannelA) {
if(GBAEngine::activeChannelA->done()) { if(GBAEngine::activeChannelA->done()) {
GBAEngine::activeChannelA->reset(); GBAEngine::activeChannelA->reset();
@ -91,6 +98,7 @@ void GBAEngine::enqueueSound(const s8 *data, int totalSamples, int sampleRate, S
} }
GBAEngine::GBAEngine() { GBAEngine::GBAEngine() {
GBAEngine::timer = std::unique_ptr<Timer>(new Timer());
// setup screen control flags // setup screen control flags
REG_DISPCNT = DCNT_MODE0 | DCNT_OBJ | DCNT_OBJ_1D | DCNT_BG0 | DCNT_BG1 | DCNT_BG2 | DCNT_BG3; REG_DISPCNT = DCNT_MODE0 | DCNT_OBJ | DCNT_OBJ_1D | DCNT_BG0 | DCNT_BG1 | DCNT_BG2 | DCNT_BG3;

57
engine/src/timer.cpp Normal file
View File

@ -0,0 +1,57 @@
//
// Created by Wouter Groeneveld on 06/12/18.
//
#include <libgba-sprite-engine/timer.h>
#include <sstream>
void Timer::onvblank() {
if(!active) return;
// each VBlank occurs every 280806 cycles (CYCLES_PER_BLANK), each cycle is 59.59ns
// So, each VBlank occurs every 16.73322954 miliseconds or 16733.22954 microseconds
// So, each second is 59,76132686219041 vblanks.
// to avoid any big rounding errors, divisions and modulos, estimate!
msecs += 16;
microsecs += 733;
if(microsecs >= 1000) {
msecs++;
microsecs -= 1000;
}
if(msecs >= 1000) {
secs++;
msecs -= 1000;
}
if(secs >= 60) {
minutes++;
secs -= 60;
}
if(minutes >= 60) {
hours++;
minutes -= 60;
}
}
std::string Timer::to_string() {
std::ostringstream stringStream;
stringStream << *this;
return stringStream.str();
}
std::ostream& operator<<(std::ostream &os, Timer &timer) {
os << timer.hours << "h:" << timer.minutes << "m:" << timer.secs << "s:" << timer.msecs;
return os;
}
void Timer::reset() {
microsecs = msecs = secs = minutes = hours = 0;
}
void Timer::start() {
active = true;
}
void Timer::stop() {
active = false;
}

View File

@ -34,6 +34,7 @@ add_executable(unittest
../engine/src/sprites/sprite.cpp ../engine/src/sprites/sprite.cpp
../engine/src/allocator.cpp ../engine/src/allocator.cpp
../engine/src/palette/combined_palette.cpp ../engine/src/palette/combined_palette.cpp
) ../engine/src/timer.cpp
timertest.cpp)
target_link_libraries(unittest ${GTEST_LIBRARY}/build/libgtest.a ${GTEST_LIBRARY}/build/libgtest_main.a) target_link_libraries(unittest ${GTEST_LIBRARY}/build/libgtest.a ${GTEST_LIBRARY}/build/libgtest_main.a)

46
test/timertest.cpp Normal file
View File

@ -0,0 +1,46 @@
//
// Created by Wouter Groeneveld on 06/12/18.
//
#include <gtest/gtest.h>
#include <libgba-sprite-engine/timer.h>
class TimerSuite : public ::testing::Test {
protected:
Timer *t;
virtual void TearDown() {
delete t;
}
virtual void SetUp() {
t = new Timer();
}
};
TEST_F(TimerSuite, OnVBlankIncreasesMsecsAmountOfTimeIfStarted) {
t->start();
t->onvblank();
t->stop();
t->onvblank();
ASSERT_EQ(t->getMsecs(), 16);
}
TEST_F(TimerSuite, OnVBlankAfterEnoughTimesToGetToSec) {
t->start();
for(int i = 0; i < 100; i++) {
t->onvblank();
}
ASSERT_EQ(t->getSecs(), 1);
ASSERT_EQ(t->getMsecs(), 673);
}
TEST_F(TimerSuite, OnVBlankDoesNothingIfNotEnabled) {
t->onvblank();
ASSERT_EQ(t->getMsecs(), 0);
}