From ad1a178320909c31fd9132d9a326273f81d4fd53 Mon Sep 17 00:00:00 2001 From: wgroeneveld Date: Thu, 6 Dec 2018 21:53:53 +0100 Subject: [PATCH] added Timer feature --- README.md | 10 ++++ .../src/sample_start_scene.cpp | 11 ++++ .../src/sample_start_scene.h | 1 + engine/CMakeLists.txt | 2 +- .../include/libgba-sprite-engine/gba_engine.h | 3 + engine/include/libgba-sprite-engine/timer.h | 41 +++++++++++++ engine/src/gba_engine.cpp | 8 +++ engine/src/timer.cpp | 57 +++++++++++++++++++ test/CMakeLists.txt | 3 +- test/timertest.cpp | 46 +++++++++++++++ 10 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 engine/include/libgba-sprite-engine/timer.h create mode 100644 engine/src/timer.cpp create mode 100644 test/timertest.cpp diff --git a/README.md b/README.md index 29599a6..d3da546 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/demos/demo1-basicfeatures/src/sample_start_scene.cpp b/demos/demo1-basicfeatures/src/sample_start_scene.cpp index 0637c56..58172ce 100644 --- a/demos/demo1-basicfeatures/src/sample_start_scene.cpp +++ b/demos/demo1-basicfeatures/src/sample_start_scene.cpp @@ -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; } } diff --git a/demos/demo1-basicfeatures/src/sample_start_scene.h b/demos/demo1-basicfeatures/src/sample_start_scene.h index 9e68c60..43c934d 100644 --- a/demos/demo1-basicfeatures/src/sample_start_scene.h +++ b/demos/demo1-basicfeatures/src/sample_start_scene.h @@ -12,6 +12,7 @@ private: std::unique_ptr animation; std::unique_ptr finalFantasyGuy; std::unique_ptr smiley; + bool pressingAorB = false; public: std::vector sprites() override; diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 11e36a3..b1b1061 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -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 $ diff --git a/engine/include/libgba-sprite-engine/gba_engine.h b/engine/include/libgba-sprite-engine/gba_engine.h index 9e86f9e..d2b357e 100644 --- a/engine/include/libgba-sprite-engine/gba_engine.h +++ b/engine/include/libgba-sprite-engine/gba_engine.h @@ -12,6 +12,7 @@ #include #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; static std::unique_ptr activeChannelA; static std::unique_ptr 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); diff --git a/engine/include/libgba-sprite-engine/timer.h b/engine/include/libgba-sprite-engine/timer.h new file mode 100644 index 0000000..0cd32eb --- /dev/null +++ b/engine/include/libgba-sprite-engine/timer.h @@ -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 + +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 diff --git a/engine/src/gba_engine.cpp b/engine/src/gba_engine.cpp index 0dadb70..4136265 100644 --- a/engine/src/gba_engine.cpp +++ b/engine/src/gba_engine.cpp @@ -9,12 +9,17 @@ std::unique_ptr GBAEngine::activeChannelA; std::unique_ptr GBAEngine::activeChannelB; +std::unique_ptr 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(new Timer()); // setup screen control flags REG_DISPCNT = DCNT_MODE0 | DCNT_OBJ | DCNT_OBJ_1D | DCNT_BG0 | DCNT_BG1 | DCNT_BG2 | DCNT_BG3; diff --git a/engine/src/timer.cpp b/engine/src/timer.cpp new file mode 100644 index 0000000..c4b2607 --- /dev/null +++ b/engine/src/timer.cpp @@ -0,0 +1,57 @@ +// +// Created by Wouter Groeneveld on 06/12/18. +// + +#include +#include + +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; +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 61f2e2c..3251a35 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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) diff --git a/test/timertest.cpp b/test/timertest.cpp new file mode 100644 index 0000000..12dee6b --- /dev/null +++ b/test/timertest.cpp @@ -0,0 +1,46 @@ +// +// Created by Wouter Groeneveld on 06/12/18. +// + +#include +#include + +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); +} \ No newline at end of file