Skip to content

refactor: move render() to Activity super class, use freeRTOS notification#774

Merged
daveallie merged 21 commits intocrosspoint-reader:masterfrom
ngxson:xsn/activity_render_refactor
Feb 16, 2026
Merged

refactor: move render() to Activity super class, use freeRTOS notification#774
daveallie merged 21 commits intocrosspoint-reader:masterfrom
ngxson:xsn/activity_render_refactor

Conversation

@ngxson
Copy link
Contributor

@ngxson ngxson commented Feb 8, 2026

Summary

Currently, each activity has to manage their own displayTaskLoop which adds redundant boilerplate code. The loop is a wait loop which is also not the best practice, as the updateRequested boolean is not protected by a mutex.

In this PR:

  • Move displayTaskLoop to the super Activity class
  • Replace updateRequested with freeRTOS's direct to task notification
  • For ActivityWithSubactivity, whenever a sub-activity is present, the parent's render() automatically goes inactive

With this change, activities now only need to expose render() function, and anywhere in the code base can call requestUpdate() to request a new rendering pass.

Additional Context

In theory, this change may also make the battery life a bit better, since one wait loop is removed. Although the equipment in my home lab wasn't been able to verify it (the electric current is too noisy and small). Would appreciate if anyone has any insights on this subject.

Update: I managed to hack a small piece of code that allow tracking CPU idle time.

The CPU load does decrease a bit (1.47% down to 1.39%), which make sense, because the display task is now sleeping most of the time unless notified. This should translate to a slightly increase in battery life in the long run.

PR:
[40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes
[40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%)
[50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes
[50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%)
[60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes
[60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%)

master:
[20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes
[20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%)
[30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes
[30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%)
[40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes
[40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%)

AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? NO

Summary by CodeRabbit

  • Refactor

    • Streamlined rendering architecture by consolidating update mechanisms across all activities, improving efficiency and consistency.
    • Modernized synchronization patterns for display updates to ensure reliable, conflict-free rendering.
  • Bug Fixes

    • Enhanced rendering stability through improved locking mechanisms and explicit update requests.

@ngxson ngxson marked this pull request as draft February 8, 2026 22:32
@ngxson ngxson marked this pull request as ready for review February 8, 2026 23:59
@ngxson
Copy link
Contributor Author

ngxson commented Feb 9, 2026

Alright, it turns out to be quite a lot of changes in the end, but the complexity of each activity is now reduced a bit.

Since this PR touches large portion of the code, it's possible that I can make mistake somewhere. I appreciate if maintainers can help testing as many activities as possible @crosspoint-reader/firmware-maintainers

Thank you!

@@ -28,15 +23,15 @@ void OtaUpdateActivity::onWifiSelectionComplete(const bool success) {
xSemaphoreTake(renderingMutex, portMAX_DELAY);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: these locks will be converted to RAII style in a follow-up PR

Comment on lines +18 to +21
{
RenderLock lock(*this);
WIFI_STORE.loadFromFile();
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example of a RAII lock, same idea as std::lock_guard

xSemaphoreGive(renderingMutex);
updateRequired = true;
requestUpdate();
vTaskDelay(10 / portTICK_PERIOD_MS);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that it seems like some activities need a mechanism to "wait" for the screen to render, then continue executing. Now that we are using freeRTOS direct notification, it will be much cleaner to handle such case (this feature was made for this exact purpose)

I will abstract it to a requestUpdateAndWait() in a follow-up PR

@ngxson ngxson requested a review from daveallie February 9, 2026 12:05

void Activity::renderTaskTrampoline(void* param) {
auto* self = static_cast<Activity*>(param);
return self->renderTaskLoop();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning here: renderTaskTrampoline is marked [[noreturn]] yet has a return statement.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically ok I guess as self->renderTaskLoop() is also [[noreturn]] but a small smell.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, technically doesn't hurt anything, but does cause my compiler to emit a warning which I'd prefer to eliminate before merging. 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return is removed in my last commit, the warning should now disappear

@daveallie
Copy link
Member

daveallie commented Feb 10, 2026

Thanks for this @ngxson, been meaning to move over to RTOS notifications since I first read about them the other week, but never got to it, I'll give this an on-device test and peek through the code. Requested AI review as the change count is high.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR centralizes UI rendering in the Activity base class to remove per-activity display task boilerplate and replaces ad-hoc updateRequired flags with FreeRTOS direct-to-task notifications (requestUpdate()), including routing updates to active subactivities.

Changes:

  • Add a dedicated render task + requestUpdate() mechanism to Activity, guarded by a shared RenderLock.
  • Update activities to expose render() and call requestUpdate() instead of managing their own render loops/tasks.
  • Update ActivityWithSubactivity to forward update requests to the active subactivity and serialize activity transitions.

Reviewed changes

Copilot reviewed 52 out of 52 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/activities/Activity.h Adds render task fields, render()/requestUpdate(), and RenderLock API.
src/activities/Activity.cpp Implements render task loop + task notification based update mechanism.
src/activities/ActivityWithSubactivity.h Declares subactivity-aware requestUpdate() behavior.
src/activities/ActivityWithSubactivity.cpp Forwards updates to subactivity and locks during transitions.
src/activities/util/KeyboardEntryActivity.h Removes per-activity render task/mutex; switches to render() override.
src/activities/util/KeyboardEntryActivity.cpp Uses requestUpdate() instead of updateRequired task loop.
src/activities/settings/SettingsActivity.h Removes per-activity render task/mutex; switches to render() override.
src/activities/settings/SettingsActivity.cpp Uses requestUpdate() and removes custom display task loop.
src/activities/settings/OtaUpdateActivity.h Removes per-activity render task fields; adds render() override.
src/activities/settings/OtaUpdateActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/settings/KOReaderSettingsActivity.h Removes per-activity render task fields; adds render() override.
src/activities/settings/KOReaderSettingsActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/settings/KOReaderAuthActivity.h Removes per-activity render task fields; adds render() override.
src/activities/settings/KOReaderAuthActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/settings/ClearCacheActivity.h Removes per-activity render task fields; adds render() override.
src/activities/settings/ClearCacheActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/settings/CalibreSettingsActivity.h Removes per-activity render task fields; adds render() override.
src/activities/settings/CalibreSettingsActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/settings/ButtonRemapActivity.h Removes per-activity render task fields; adds render() override.
src/activities/settings/ButtonRemapActivity.cpp Adapts remap flow to requestUpdate() and RenderLock.
src/activities/reader/XtcReaderChapterSelectionActivity.h Removes per-activity render task fields; adds render() override.
src/activities/reader/XtcReaderChapterSelectionActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/reader/XtcReaderActivity.h Removes per-activity render task fields; adds render() override.
src/activities/reader/XtcReaderActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/reader/TxtReaderActivity.h Removes per-activity render task fields; adds render() override.
src/activities/reader/TxtReaderActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/reader/KOReaderSyncActivity.h Removes per-activity render task fields; adds render() override.
src/activities/reader/KOReaderSyncActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/reader/EpubReaderPercentSelectionActivity.h Removes per-activity render task fields; adds render() override.
src/activities/reader/EpubReaderPercentSelectionActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/reader/EpubReaderMenuActivity.h Removes per-activity render task fields; adds render() override.
src/activities/reader/EpubReaderMenuActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/reader/EpubReaderChapterSelectionActivity.h Removes per-activity render task fields; adds render() override.
src/activities/reader/EpubReaderChapterSelectionActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/reader/EpubReaderActivity.h Removes per-activity render task fields; adds render() override.
src/activities/reader/EpubReaderActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/network/WifiSelectionActivity.h Removes per-activity render task fields; adds render() override.
src/activities/network/WifiSelectionActivity.cpp Converts update signaling to requestUpdate(); uses RenderLock for credential store ops.
src/activities/network/NetworkModeSelectionActivity.h Removes per-activity render task fields; adds render() override.
src/activities/network/NetworkModeSelectionActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/network/CrossPointWebServerActivity.h Removes per-activity render task fields; adds render() override.
src/activities/network/CrossPointWebServerActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/network/CalibreConnectActivity.h Removes per-activity render task fields; adds render() override.
src/activities/network/CalibreConnectActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/home/RecentBooksActivity.h Removes per-activity render task fields; adds render() override.
src/activities/home/RecentBooksActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/home/MyLibraryActivity.h Removes per-activity render task fields; adds render() override.
src/activities/home/MyLibraryActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/home/HomeActivity.h Removes per-activity render task fields; adds render() override.
src/activities/home/HomeActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.
src/activities/browser/OpdsBookBrowserActivity.h Removes per-activity render task fields; adds render() override.
src/activities/browser/OpdsBookBrowserActivity.cpp Converts update signaling to requestUpdate() and removes display task loop.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// Don't render if we're in PASSWORD_ENTRY state - we're just transitioning
// from the keyboard subactivity back to the main activity
if (state == WifiSelectionState::PASSWORD_ENTRY) {
vTaskDelay(10 / portTICK_PERIOD_MS);
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

render() should not call vTaskDelay() (and especially not while the activity render mutex is held by RenderLock in the render task). If the goal is to skip drawing during PASSWORD_ENTRY, just return without delaying; the render task already blocks on notifications when idle.

Suggested change
vTaskDelay(10 / portTICK_PERIOD_MS);

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea why this vTaskDelay was there in the first place, I decide to leave it here and improve things in a follow-up

@daveallie
Copy link
Member

Copilot caught everything I was experiencing in testing (good bot)

@ngxson
Copy link
Contributor Author

ngxson commented Feb 10, 2026

@daveallie I have Code Rabbit bot on my fork too, see ngxson#1 . It's a bit better than copilot because it has a memory that can be preserved from one PR to another. Probably should give it a try in the future.

(I'll look at your comments a bit later today)

@daveallie
Copy link
Member

nice, looks like it's free for open source too, will give it a look

@jonasdiemer
Copy link
Contributor

Will also daily drive this PR to test if there are side effects.

@znelson
Copy link
Contributor

znelson commented Feb 10, 2026

Would you consider a proof-of-lock pattern, where render takes the RenderLock as a param?

virtual void render(const Activity::RenderLock&) {}

proof-of-lock.patch

Activity& activity;

public:
explicit RenderLock(Activity& activity);
Copy link
Contributor

@znelson znelson Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding some guardrails to prevent misuse of RenderLock:

Suggested change
explicit RenderLock(Activity& activity);
explicit RenderLock(Activity& activity);
RenderLock(const RenderLock&) = delete;
RenderLock& operator=(const RenderLock&) = delete;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I applied this + your proof of lock patch in f45e740

Note that I'm now using rvalue render(RenderLock&& lock). Not quite sure if it has any impacts in terms of code clarity though, but I hope that's a bit more foolproof.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I'll leave it to you and other reviewers to consider, but I think actually const-ref is preferable over rvalue-ref for this.

Consider what the rvalue-ref with move means for the nested render call case in EpubReaderActivity::render: after the lock has been moved in to the nested render at EpubReaderActivity.cpp‎:602, and that nested call completes, wouldn't the remainder of EpubReaderActivity::render no longer hold a lock?

The const-ref version better indicates "someone outside this scope is holding the lock", and that property is transitive through nested calls.

Copy link
Contributor Author

@ngxson ngxson Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My reason for using rvalue is that I initially wanted the render() function to create and manage its own lock, something like:

void SomeActivity::render() {
  RenderLock lock(*this);
  // ... then start doing the rendering
}

But that will requires copy-paste the lock all over the code base. So rvalue seems to be a better option as it has mostly the same semantic as my version above.

A simple ref is still ok. While const-ref make things semantically more clear, functionally the const prevent the render function from actually "manage" the lock, for example, if we have an explicit lock.unlock() in the future, then the const prevent render() from calling it.

The nested render() case is not clean IMO. My intent is that the render() must strictly hold the lock and never transfer it to somewhere else.

The code should be refactored into something like:

auto p = section->loadPageFromSectionFile();
if (!p) {
  Serial.printf("[%lu] [ERS] Failed to load page from SD - clearing section cache\n", millis());
  section->clearCache();
  section.reset();
  errorCode = LOAD_PAGE_FAILED;
  requestUpdate(); // next update, the screen will render the LOAD_PAGE_FAILED error message
}

@ngxson
Copy link
Contributor Author

ngxson commented Feb 12, 2026

This PR should be pretty much ready now. I've done some more testing to confirm that these is no unexpected behaviors.

Would appreciate if someone can do a quick test. Thanks!

Copy link
Member

@daveallie daveallie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

@daveallie daveallie merged commit a616f42 into crosspoint-reader:master Feb 16, 2026
6 checks passed
daveallie pushed a commit that referenced this pull request Feb 16, 2026
## Summary

Follow-up to
#774

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? **NO**


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **Refactor**
* Modernized internal synchronization mechanisms across multiple
components to improve code reliability and maintainability. All
functionality remains unchanged.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Unintendedsideeffects pushed a commit to Unintendedsideeffects/crosspoint-reader that referenced this pull request Feb 17, 2026
…ation (crosspoint-reader#774)

## Summary

Currently, each activity has to manage their own `displayTaskLoop` which
adds redundant boilerplate code. The loop is a wait loop which is also
not the best practice, as the `updateRequested` boolean is not protected
by a mutex.

In this PR:
- Move `displayTaskLoop` to the super `Activity` class
- Replace `updateRequested` with freeRTOS's [direct to task
notification](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-Task-notifications)
- For `ActivityWithSubactivity`, whenever a sub-activity is present, the
parent's `render()` automatically goes inactive

With this change, activities now only need to expose `render()`
function, and anywhere in the code base can call `requestUpdate()` to
request a new rendering pass.

## Additional Context

In theory, this change may also make the battery life a bit better,
since one wait loop is removed. Although the equipment in my home lab
wasn't been able to verify it (the electric current is too noisy and
small). Would appreciate if anyone has any insights on this subject.

Update: I managed to hack [a small piece of
code](https://github.com/ngxson/crosspoint-reader/tree/xsn/measure_cpu_usage)
that allow tracking CPU idle time.

The CPU load does decrease a bit (1.47% down to 1.39%), which make
sense, because the display task is now sleeping most of the time unless
notified. This should translate to a slightly increase in battery life
in the long run.

```
PR:
[40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes
[40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%)
[50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes
[50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%)
[60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes
[60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%)

master:
[20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes
[20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%)
[30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes
[30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%)
[40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes
[40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%)
```

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? **NO**


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Streamlined rendering architecture by consolidating update mechanisms
across all activities, improving efficiency and consistency.
* Modernized synchronization patterns for display updates to ensure
reliable, conflict-free rendering.

* **Bug Fixes**
* Enhanced rendering stability through improved locking mechanisms and
explicit update requests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: znelson <[email protected]>
Unintendedsideeffects pushed a commit to Unintendedsideeffects/crosspoint-reader that referenced this pull request Feb 17, 2026
## Summary

Follow-up to
crosspoint-reader#774

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? **NO**


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **Refactor**
* Modernized internal synchronization mechanisms across multiple
components to improve code reliability and maintainability. All
functionality remains unchanged.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@coderabbitai coderabbitai bot mentioned this pull request Feb 18, 2026
Closed
saslv pushed a commit to saslv/crosspoint-reader that referenced this pull request Feb 19, 2026
…ation (crosspoint-reader#774)

## Summary

Currently, each activity has to manage their own `displayTaskLoop` which
adds redundant boilerplate code. The loop is a wait loop which is also
not the best practice, as the `updateRequested` boolean is not protected
by a mutex.

In this PR:
- Move `displayTaskLoop` to the super `Activity` class
- Replace `updateRequested` with freeRTOS's [direct to task
notification](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-Task-notifications)
- For `ActivityWithSubactivity`, whenever a sub-activity is present, the
parent's `render()` automatically goes inactive

With this change, activities now only need to expose `render()`
function, and anywhere in the code base can call `requestUpdate()` to
request a new rendering pass.

## Additional Context

In theory, this change may also make the battery life a bit better,
since one wait loop is removed. Although the equipment in my home lab
wasn't been able to verify it (the electric current is too noisy and
small). Would appreciate if anyone has any insights on this subject.

Update: I managed to hack [a small piece of
code](https://github.com/ngxson/crosspoint-reader/tree/xsn/measure_cpu_usage)
that allow tracking CPU idle time.

The CPU load does decrease a bit (1.47% down to 1.39%), which make
sense, because the display task is now sleeping most of the time unless
notified. This should translate to a slightly increase in battery life
in the long run.

```
PR:
[40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes
[40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%)
[50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes
[50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%)
[60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes
[60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%)

master:
[20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes
[20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%)
[30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes
[30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%)
[40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes
[40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%)
```

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? **NO**


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Streamlined rendering architecture by consolidating update mechanisms
across all activities, improving efficiency and consistency.
* Modernized synchronization patterns for display updates to ensure
reliable, conflict-free rendering.

* **Bug Fixes**
* Enhanced rendering stability through improved locking mechanisms and
explicit update requests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: znelson <[email protected]>
saslv pushed a commit to saslv/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

Follow-up to
crosspoint-reader#774

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? **NO**


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **Refactor**
* Modernized internal synchronization mechanisms across multiple
components to improve code reliability and maintainability. All
functionality remains unchanged.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
el pushed a commit to el/crosspoint-reader that referenced this pull request Feb 19, 2026
…ation (crosspoint-reader#774)

## Summary

Currently, each activity has to manage their own `displayTaskLoop` which
adds redundant boilerplate code. The loop is a wait loop which is also
not the best practice, as the `updateRequested` boolean is not protected
by a mutex.

In this PR:
- Move `displayTaskLoop` to the super `Activity` class
- Replace `updateRequested` with freeRTOS's [direct to task
notification](https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/03-Direct-to-task-notifications/01-Task-notifications)
- For `ActivityWithSubactivity`, whenever a sub-activity is present, the
parent's `render()` automatically goes inactive

With this change, activities now only need to expose `render()`
function, and anywhere in the code base can call `requestUpdate()` to
request a new rendering pass.

## Additional Context

In theory, this change may also make the battery life a bit better,
since one wait loop is removed. Although the equipment in my home lab
wasn't been able to verify it (the electric current is too noisy and
small). Would appreciate if anyone has any insights on this subject.

Update: I managed to hack [a small piece of
code](https://github.com/ngxson/crosspoint-reader/tree/xsn/measure_cpu_usage)
that allow tracking CPU idle time.

The CPU load does decrease a bit (1.47% down to 1.39%), which make
sense, because the display task is now sleeping most of the time unless
notified. This should translate to a slightly increase in battery life
in the long run.

```
PR:
[40012] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes
[40012] [IDLE] Idle time: 98.61% (CPU load: 1.39%)
[50017] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes
[50017] [IDLE] Idle time: 98.61% (CPU load: 1.39%)
[60022] [MEM] Free: 185856 bytes, Total: 231004 bytes, Min Free: 123316 bytes
[60022] [IDLE] Idle time: 98.61% (CPU load: 1.39%)

master:
[20012] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes
[20012] [IDLE] Idle time: 98.53% (CPU load: 1.47%)
[30017] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes
[30017] [IDLE] Idle time: 98.53% (CPU load: 1.47%)
[40022] [MEM] Free: 195016 bytes, Total: 231532 bytes, Min Free: 132460 bytes
[40022] [IDLE] Idle time: 98.53% (CPU load: 1.47%)
```

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? **NO**


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Streamlined rendering architecture by consolidating update mechanisms
across all activities, improving efficiency and consistency.
* Modernized synchronization patterns for display updates to ensure
reliable, conflict-free rendering.

* **Bug Fixes**
* Enhanced rendering stability through improved locking mechanisms and
explicit update requests.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: znelson <[email protected]>
el pushed a commit to el/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

Follow-up to
crosspoint-reader#774

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? **NO**


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

## Release Notes

* **Refactor**
* Modernized internal synchronization mechanisms across multiple
components to improve code reliability and maintainability. All
functionality remains unchanged.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants