Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/reader/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@

#define TARGET_FPS 20

// How long the main loop may block waiting for input when there's nothing
// held/animating, instead of polling at TARGET_FPS. Bounds CPU/battery use
// while idle without meaningfully affecting perceived input latency.
#define IDLE_POLL_TIMEOUT_MS 500

#define IDLE_SAVE_TIME_SEC 60

#define FONT_DIR "resources/fonts"
Expand Down
48 changes: 46 additions & 2 deletions src/reader/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,29 @@ class SystemKeyChordTracker
}
};

// Portable substitute for SDL_WaitEventTimeout, which isn't declared in
// every SDL1.2 build (notably absent from the Miyoo Mini cross-compile
// toolchain's headers). Polls in a short sleep loop instead of a single
// blocking call - still lets the CPU idle between polls, just with
// POLL_STEP_MS granularity instead of an instant wake.
bool poll_event_with_timeout(SDL_Event *event, uint32_t timeout_ms)
{
constexpr uint32_t POLL_STEP_MS = 10;
uint32_t start = SDL_GetTicks();
while (true)
{
if (SDL_PollEvent(event))
{
return true;
}
if (SDL_GetTicks() - start >= timeout_ms)
{
return false;
}
SDL_Delay(POLL_STEP_MS);
}
}

bool quit = false;

void signal_handler(int)
Expand Down Expand Up @@ -283,6 +306,7 @@ int main(int argc, char **argv)

// Timing
Timer idle_timer;
Timer tick_timer;
FPSLimiter limit_fps(TARGET_FPS);
const uint32_t avg_loop_time = 1000 / TARGET_FPS;

Expand All @@ -295,8 +319,25 @@ int main(int argc, char **argv)
{
bool ran_user_code = task_queue.drain();

{
uint32_t elapsed_ms = tick_timer.elapsed_ms();
tick_timer.reset();
view_stack.on_tick(elapsed_ms);
}

// Only stay on a tight poll+sleep cadence while something is held or
// animating; otherwise block until the next real event to let the
// CPU idle between key presses.
bool need_fast_ticks = held_key_tracker.any_held() || (
view_stack.top_view() && view_stack.top_view()->wants_continuous_render()
);

SDL_Event event;
while (SDL_PollEvent(&event))
bool got_event = (!ran_user_code && !need_fast_ticks)
? poll_event_with_timeout(&event, IDLE_POLL_TIMEOUT_MS)
: SDL_PollEvent(&event) != 0;

while (got_event)
{
switch (event.type)
{
Expand Down Expand Up @@ -343,12 +384,15 @@ int main(int argc, char **argv)
default:
break;
}

got_event = SDL_PollEvent(&event) != 0;
}

quit = quit || chord_tracker.exit_requested();

held_key_tracker.accumulate(avg_loop_time); // Pretend perfect loop timing for event firing consistency
ran_user_code = held_key_tracker.for_longest_held(key_held_callback) || ran_user_code;
ran_user_code = ran_user_code || need_fast_ticks;

if (ran_user_code)
{
Expand All @@ -366,7 +410,7 @@ int main(int argc, char **argv)
}
}

if (!quit)
if (!quit && need_fast_ticks)
{
limit_fps();
}
Expand Down
10 changes: 10 additions & 0 deletions src/reader/view.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ class View
// Pass key and held time in ms.
virtual void on_keyheld(SDLKey, uint32_t) {}

// Called once per main loop iteration regardless of input, with elapsed
// time since the last call. Used for animations that aren't driven by a
// held key (e.g. auto-scroll).
virtual void on_tick(uint32_t) {}

// If true, the main loop will keep ticking/rendering at full rate even
// with no input, instead of sleeping between events. Used by views that
// animate on their own (e.g. auto-scroll).
virtual bool wants_continuous_render() const { return false; }

// This view has been popped from the stack (now defunct).
virtual void on_pop() {}

Expand Down
8 changes: 8 additions & 0 deletions src/reader/view_stack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ void ViewStack::on_keyheld(SDLKey key, uint32_t hold_time_ms)
}
}

void ViewStack::on_tick(uint32_t elapsed_ms)
{
if (!views.empty())
{
views.back()->on_tick(elapsed_ms);
}
}

bool ViewStack::pop_completed_views()
{
bool changed_focus = false;
Expand Down
1 change: 1 addition & 0 deletions src/reader/view_stack.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class ViewStack: public View

void on_keypress(SDLKey key) override;
void on_keyheld(SDLKey key, uint32_t hold_time_ms) override;
void on_tick(uint32_t elapsed_ms) override;

// Pop views that report as done. Return true if focus changed.
bool pop_completed_views();
Expand Down
15 changes: 15 additions & 0 deletions src/util/held_key_tracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,18 @@ bool HeldKeyTracker::for_longest_held(const std::function<void(SDLKey, uint32_t)

return false;
}

bool HeldKeyTracker::any_held() const
{
const Uint8 *keystate = SDL_GetKeyState(nullptr);

for (SDLKey key : keycodes)
{
if (keystate[key])
{
return true;
}
}

return false;
}
4 changes: 4 additions & 0 deletions src/util/held_key_tracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ class HeldKeyTracker {

void accumulate(uint32_t ms);
bool for_longest_held(const std::function<void(SDLKey, uint32_t)> &callback);

// Return true if any tracked key is currently held, checked directly
// against live keyboard state (independent of accumulate()'s timing).
bool any_held() const;
};

#endif