[escher] Add RunLoop and Timer

Change-Id: Icb5b2e82cc9fe999eb4b1c7e9dff75ca92dcca43
This commit is contained in:
Romain Goyet
2017-02-20 17:44:13 +01:00
parent 7b575a204b
commit ebb633ff23
11 changed files with 195 additions and 41 deletions

View File

@@ -57,15 +57,16 @@ VariableBoxController * AppsContainer::variableBoxController() {
return &m_variableBoxController;
}
bool AppsContainer::handleEvent(Ion::Events::Event event) {
void AppsContainer::dispatchEvent(Ion::Events::Event event) {
if (event == Ion::Events::Home) {
switchTo(appAtIndex(0));
return true;
return;
}
if (event == Ion::Events::OnOff) {
Ion::Power::suspend();
return;
}
return false;
Container::dispatchEvent(event);
}
void AppsContainer::switchTo(App * app) {

View File

@@ -27,7 +27,7 @@ public:
Poincare::Context * globalContext();
MathToolbox * mathToolbox();
VariableBoxController * variableBoxController();
bool handleEvent(Ion::Events::Event event) override;
void dispatchEvent(Ion::Events::Event event) override;
void switchTo(App * app) override;
void refreshPreferences();
private:

View File

@@ -36,6 +36,7 @@ objs += $(addprefix escher/src/,\
pointer_table_cell_with_switch.o\
pointer_text_view.o\
responder.o\
run_loop.o\
scroll_view.o\
scroll_view_indicator.o\
selectable_table_view.o\
@@ -55,6 +56,7 @@ objs += $(addprefix escher/src/,\
text_view.o\
tiled_view.o\
toolbox.o\
timer.o\
view.o\
view_controller.o\
warning_controller.o\

View File

@@ -10,16 +10,17 @@
* When writing an Escher program, you typically subclass Container, and your
* subclass owns one or more App. You then call "run()" on your container. */
#include <escher/run_loop.h>
#include <escher/app.h>
#include <escher/window.h>
#include <ion/events.h>
class Container {
class Container : private RunLoop {
public:
Container();
void run();
App * activeApp();
virtual bool handleEvent(Ion::Events::Event event);
virtual void dispatchEvent(Ion::Events::Event event) override;
virtual void switchTo(App * app);
protected:
virtual Window * window() = 0;

View File

@@ -0,0 +1,20 @@
#ifndef ESCHER_RUN_LOOP_H
#define ESCHER_RUN_LOOP_H
#include <ion.h>
#include <escher/timer.h>
class RunLoop {
public:
RunLoop();
void run();
protected:
virtual void dispatchEvent(Ion::Events::Event e) = 0;
virtual int numberOfTimers();
virtual Timer * timerAtIndex(int i);
private:
void step();
int m_time;
};
#endif

View File

@@ -0,0 +1,29 @@
#ifndef ESCHER_TIMER_H
#define ESCHER_TIMER_H
#include <escher/invocation.h>
#include <stdint.h>
/* Timers we'll need
* - Blink cursor timer
* - Dim Screen timer
* - Power down timer
* - Watchdog timer ?
* - Battery level timer
* - LED blink timer
*/
class Timer {
public:
static constexpr int TickDuration = 100; // In Miliseconds
Timer(Invocation invocation, uint32_t period); // Period is in ticks
void tick();
void reset();
private:
void fire();
Invocation m_invocation;
uint32_t m_period;
uint32_t m_numberOfTicksBeforeFire;
};
#endif

View File

@@ -1,10 +1,8 @@
#include <escher/container.h>
#include <ion.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
Container::Container() :
RunLoop(),
m_activeApp(nullptr)
{
}
@@ -26,36 +24,26 @@ App * Container::activeApp() {
return m_activeApp;
}
bool Container::handleEvent(Ion::Events::Event event) {
return false;
void Container::dispatchEvent(Ion::Events::Event event) {
#if ESCHER_LOG_EVENTS_BINARY
char message[2] = { (char)event.id(), 0};
ion_log_string(message);
#endif
#if ESCHER_LOG_EVENTS_NAME
const char * name = event.name();
if (name == nullptr) {
name = "UNDEFINED";
}
ion_log_string("Ion::Events::");
ion_log_string(name);
ion_log_string("\n");
#endif
m_activeApp->processEvent(event);
window()->redraw();
}
void Container::run() {
window()->setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height));
window()->redraw();
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop_arg([](void * ctx){ ((Container *)ctx)->step(); }, this, 0, 1);
#else
while (true) {
step();
}
#endif
}
void Container::step() {
Ion::Events::Event event = Ion::Events::getEvent(); // This is a blocking call
#if ESCHER_LOG_EVENTS
char message[2] = { (char)event.id(), 0};
Ion::Log::print(message);
#endif
if (event == Ion::Events::None) {
return;
}
if (handleEvent(event)) {
return;
}
m_activeApp->processEvent(event);
window()->redraw();
RunLoop::run();
}

72
escher/src/run_loop.cpp Normal file
View File

@@ -0,0 +1,72 @@
#include <escher/run_loop.h>
#include <assert.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
RunLoop::RunLoop() :
m_time(0) {
}
int RunLoop::numberOfTimers() {
return 0;
}
Timer * RunLoop::timerAtIndex(int i) {
assert(false);
return nullptr;
}
void RunLoop::run() {
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop_arg([](void * ctx){ ((Container *)ctx)->step(); }, this, 0, 1);
#else
while (true) {
step();
}
#endif
}
void RunLoop::step() {
// Fetch the event, if any
int eventDuration = Timer::TickDuration;
int timeout = eventDuration;
Ion::Events::Event event = Ion::Events::getEvent(&timeout);
eventDuration -= timeout;
assert(eventDuration >= 0);
assert(eventDuration <= Timer::TickDuration);
/* At this point, eventDuration contains the time it took to retrieve the
* event. It can be zero, and is at most equal to the timeout value, Timer::
* TickDuration. The event returned can be None if nothing worth taking care
* of happened. In other words, getEvent is a blocking call with a timeout. */
m_time += eventDuration;
if (m_time >= Timer::TickDuration) {
m_time -= Timer::TickDuration;
for (int i=0; i<numberOfTimers(); i++) {
Timer * timer = timerAtIndex(i);
timer->tick();
}
}
#if ESCHER_LOG_EVENTS_BINARY
char message[2] = { (char)event.id(), 0};
ion_log_string(message);
#endif
#if ESCHER_LOG_EVENTS_NAME
const char * name = event.name();
if (name == nullptr) {
name = "UNDEFINED";
}
ion_log_string("Ion::Events::");
ion_log_string(name);
ion_log_string("\n");
#endif
if (event != Ion::Events::None) {
dispatchEvent(event);
}
}

24
escher/src/timer.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include <escher/timer.h>
Timer::Timer(Invocation invocation, uint32_t period) :
m_invocation(invocation),
m_period(period),
m_numberOfTicksBeforeFire(period)
{
}
void Timer::tick() {
m_numberOfTicksBeforeFire--;
if (m_numberOfTicksBeforeFire == 0) {
fire();
reset();
}
}
void Timer::reset() {
m_numberOfTicksBeforeFire = m_period;
}
void Timer::fire() {
m_invocation.perform(this);
}

View File

@@ -34,7 +34,8 @@ private:
uint8_t m_id;
};
Event getEvent();
// Timeout is decremented
Event getEvent(int * timeout);
bool isShiftActive();
bool isAlphaActive();

View File

@@ -38,8 +38,20 @@ void updateModifiersFromEvent(Event e) {
}
}
static bool sleepWithTimeout(int duration, int * timeout) {
if (*timeout >= duration) {
msleep(duration);
*timeout -= duration;
return false;
} else {
msleep(*timeout);
*timeout = 0;
return true;
}
}
// Debouncing, and change to get_key event.
Event getEvent() {
Event getEvent(int * timeout) {
// Let's start by saving which keys we've seen up
bool keySeenUp[Keyboard::NumberOfKeys];
for (int k=0; k<Keyboard::NumberOfKeys; k++) {
@@ -47,7 +59,9 @@ Event getEvent() {
}
// Wait a little to debounce the button.
msleep(10);
if (sleepWithTimeout(10, timeout)) {
return Ion::Events::None;
}
/* Let's discard the keys we previously saw up but which aren't anymore: those
* were probably bouncing! */
@@ -55,7 +69,7 @@ Event getEvent() {
keySeenUp[k] &= !Keyboard::keyDown((Ion::Keyboard::Key)k);
}
while (1) {
while (true) {
for (int k=0; k<Keyboard::NumberOfKeys; k++) {
if (Keyboard::keyDown((Ion::Keyboard::Key)k)) {
if (keySeenUp[k]) {
@@ -67,7 +81,9 @@ Event getEvent() {
keySeenUp[k] = true;
}
}
msleep(10);
if (sleepWithTimeout(10, timeout)) {
return Ion::Events::None;
}
}
}