added Timer feature
This commit is contained in:
parent
6511994a53
commit
ad1a178320
10
README.md
10
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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue