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.
#### 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
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();
TextStream::instance().setText("PRESS START", 3, 8);
engine->getTimer()->start();
engine->enqueueMusic(zelda_music_16K_mono, zelda_music_16K_mono_bytes);
}
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(!engine->isTransitioning()) {
engine->enqueueSound(zelda_secret_16K_mono, zelda_secret_16K_mono_bytes);
@ -63,5 +72,7 @@ void SampleStartScene::tick(u16 keys) {
animation->flipVertically(true);
} else if(keys & KEY_DOWN) {
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> finalFantasyGuy;
std::unique_ptr<Sprite> smiley;
bool pressingAorB = false;
public:
std::vector<Sprite *> sprites() override;

View File

@ -18,7 +18,7 @@ add_library(${PROJECT_NAME}
src/background/text_stream.cpp
src/background/background.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
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>

View File

@ -12,6 +12,7 @@
#include <libgba-sprite-engine/effects/scene_effect.h>
#include "scene.h"
#include "sound_control.h"
#include "timer.h"
#define GBA_SCREEN_WIDTH 240
#define GBA_SCREEN_HEIGHT 160
@ -26,6 +27,7 @@ private:
bool disableTextBg;
SpriteManager spriteManager;
static std::unique_ptr<Timer> timer;
static std::unique_ptr<SoundControl> activeChannelA;
static std::unique_ptr<SoundControl> activeChannelB;
@ -40,6 +42,7 @@ private:
public:
GBAEngine();
Timer* getTimer();
void setScene(Scene* scene);
void dynamicallyAddSprite(Sprite* s) { spriteManager.add(s); }
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::activeChannelB;
std::unique_ptr<Timer> GBAEngine::timer;
void GBAEngine::vsync() {
while (REG_VCOUNT >= 160);
while (REG_VCOUNT < 160);
}
Timer* GBAEngine::getTimer() {
return GBAEngine::timer.get();
}
void GBAEngine::onVBlank() {
// 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!
@ -23,6 +28,8 @@ void GBAEngine::onVBlank() {
unsigned short tempInterruptState = REG_IF;
if((REG_IF & INTERRUPT_VBLANK) == INTERRUPT_VBLANK) {
GBAEngine::timer->onvblank();
if(GBAEngine::activeChannelA) {
if(GBAEngine::activeChannelA->done()) {
GBAEngine::activeChannelA->reset();
@ -91,6 +98,7 @@ void GBAEngine::enqueueSound(const s8 *data, int totalSamples, int sampleRate, S
}
GBAEngine::GBAEngine() {
GBAEngine::timer = std::unique_ptr<Timer>(new Timer());
// setup screen control flags
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/allocator.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)

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);
}