Fix scroller draining off-screen on low frame rates

The scroller recycled at most one line per frame while movement was
continuous (velocity * dt), so whenever a frame's displacement exceeded
one line height the text block drifted toward the boundary and the
screen drained with nothing replacing it. This surfaced on slower
hardware / 60Hz panels (|move| > lineHeight) but not on fast machines.

Recycle in a loop until every line that scrolled fully off-screen has a
replacement, for both scroll directions. addLineAt() now returns whether
a line was produced so the loop stops cleanly instead of losing lines.
This commit is contained in:
NeilClack
2026-08-13 16:57:39 -05:00
parent 1c14c0401e
commit 515056a92c
2 changed files with 22 additions and 15 deletions
+21 -14
View File
@@ -140,26 +140,32 @@ void ScrollerPage::update(float dt)
if (velocity_ > 0.0f) if (velocity_ > 0.0f)
{ {
// Lines move downward; recycle the bottom line off-screen and add a new // Lines move downward. Recycle every line that scrolled fully off the
// line just above the top. // bottom this frame (movement is continuous, so one frame can cover
ScrollerLine &back = lines_.back(); // several line heights) and add a replacement just above the top.
if (back.words[0].y > screenH_) while (!lines_.empty() && lines_.back().words[0].y > screenH_)
{ {
float baseY = lines_.front().words[0].y - lineHeight_; float baseY = lines_.front().words[0].y - lineHeight_;
lines_.pop_back(); lines_.pop_back();
addLineAt(baseY); if (!addLineAt(baseY))
{
break;
}
} }
} }
else if (velocity_ < 0.0f) else if (velocity_ < 0.0f)
{ {
// Lines move upward; recycle the top line off-screen and add a new line // Lines move upward. Recycle every line that scrolled fully off the top
// just below the bottom. // this frame and add a replacement just below the bottom.
ScrollerLine &front = lines_.front(); while (!lines_.empty() &&
if (front.words[0].y + front.words[0].height < 0.0f) lines_.front().words[0].y + lines_.front().words[0].height < 0.0f)
{ {
float baseY = lines_.back().words[0].y + lineHeight_; float baseY = lines_.back().words[0].y + lineHeight_;
lines_.pop_front(); lines_.pop_front();
addLineAt(baseY); if (!addLineAt(baseY))
{
break;
}
} }
} }
} }
@@ -380,18 +386,18 @@ void ScrollerPage::prefillLines()
} }
} }
void ScrollerPage::addLineAt(float baseY) bool ScrollerPage::addLineAt(float baseY)
{ {
if (streams_.empty() || !font_) if (streams_.empty() || !font_)
{ {
return; return false;
} }
ProclamationStream &stream = streams_[streamIndex_]; ProclamationStream &stream = streams_[streamIndex_];
if (stream.words.empty()) if (stream.words.empty())
{ {
advanceStream(); advanceStream();
return; return false;
} }
FibonacciLinePattern &pattern = patterns_[streamIndex_]; FibonacciLinePattern &pattern = patterns_[streamIndex_];
@@ -451,7 +457,7 @@ void ScrollerPage::addLineAt(float baseY)
if (line.words.empty()) if (line.words.empty())
{ {
return; return false;
} }
line.width = width; line.width = width;
@@ -471,6 +477,7 @@ void ScrollerPage::addLineAt(float baseY)
{ {
lines_.push_back(std::move(line)); lines_.push_back(std::move(line));
} }
return true;
} }
void ScrollerPage::advanceStream() void ScrollerPage::advanceStream()
+1 -1
View File
@@ -79,7 +79,7 @@ private:
void destroyResources(); void destroyResources();
void rebuildWordTextures(); void rebuildWordTextures();
void prefillLines(); void prefillLines();
void addLineAt(float baseY); bool addLineAt(float baseY);
void advanceStream(); void advanceStream();
void switchAudioForCurrentStream(); void switchAudioForCurrentStream();
void updateAutoVelocity(float dt); void updateAutoVelocity(float dt);