diff --git a/src/reader/config.h b/src/reader/config.h index fc92f19..f9e969d 100644 --- a/src/reader/config.h +++ b/src/reader/config.h @@ -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" diff --git a/src/reader/main.cpp b/src/reader/main.cpp index fe7ff34..6b2db79 100644 --- a/src/reader/main.cpp +++ b/src/reader/main.cpp @@ -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) @@ -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; @@ -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) { @@ -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) { @@ -366,7 +410,7 @@ int main(int argc, char **argv) } } - if (!quit) + if (!quit && need_fast_ticks) { limit_fps(); } diff --git a/src/reader/view.h b/src/reader/view.h index e116a18..2496d74 100644 --- a/src/reader/view.h +++ b/src/reader/view.h @@ -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() {} diff --git a/src/reader/view_stack.cpp b/src/reader/view_stack.cpp index 4f2990a..8617eca 100644 --- a/src/reader/view_stack.cpp +++ b/src/reader/view_stack.cpp @@ -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; diff --git a/src/reader/view_stack.h b/src/reader/view_stack.h index 0d17ffb..fb83df6 100644 --- a/src/reader/view_stack.h +++ b/src/reader/view_stack.h @@ -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(); diff --git a/src/util/held_key_tracker.cpp b/src/util/held_key_tracker.cpp index f0d4295..6e7bae9 100644 --- a/src/util/held_key_tracker.cpp +++ b/src/util/held_key_tracker.cpp @@ -63,3 +63,18 @@ bool HeldKeyTracker::for_longest_held(const std::function &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