refactor: move render() to Activity super class, use freeRTOS notification#774
Conversation
|
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); | |||
There was a problem hiding this comment.
Note: these locks will be converted to RAII style in a follow-up PR
| { | ||
| RenderLock lock(*this); | ||
| WIFI_STORE.loadFromFile(); | ||
| } |
There was a problem hiding this comment.
Example of a RAII lock, same idea as std::lock_guard
| xSemaphoreGive(renderingMutex); | ||
| updateRequired = true; | ||
| requestUpdate(); | ||
| vTaskDelay(10 / portTICK_PERIOD_MS); |
There was a problem hiding this comment.
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
src/activities/Activity.cpp
Outdated
|
|
||
| void Activity::renderTaskTrampoline(void* param) { | ||
| auto* self = static_cast<Activity*>(param); | ||
| return self->renderTaskLoop(); |
There was a problem hiding this comment.
Warning here: renderTaskTrampoline is marked [[noreturn]] yet has a return statement.
There was a problem hiding this comment.
Technically ok I guess as self->renderTaskLoop() is also [[noreturn]] but a small smell.
There was a problem hiding this comment.
Agreed, technically doesn't hurt anything, but does cause my compiler to emit a warning which I'd prefer to eliminate before merging. 🙂
There was a problem hiding this comment.
The return is removed in my last commit, the warning should now disappear
|
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. |
There was a problem hiding this comment.
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 toActivity, guarded by a sharedRenderLock. - Update activities to expose
render()and callrequestUpdate()instead of managing their own render loops/tasks. - Update
ActivityWithSubactivityto 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); |
There was a problem hiding this comment.
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.
| vTaskDelay(10 / portTICK_PERIOD_MS); |
There was a problem hiding this comment.
No idea why this vTaskDelay was there in the first place, I decide to leave it here and improve things in a follow-up
|
Copilot caught everything I was experiencing in testing (good bot) |
|
@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) |
|
nice, looks like it's free for open source too, will give it a look |
|
Will also daily drive this PR to test if there are side effects. |
|
Would you consider a proof-of-lock pattern, where |
| Activity& activity; | ||
|
|
||
| public: | ||
| explicit RenderLock(Activity& activity); |
There was a problem hiding this comment.
Consider adding some guardrails to prevent misuse of RenderLock:
| explicit RenderLock(Activity& activity); | |
| explicit RenderLock(Activity& activity); | |
| RenderLock(const RenderLock&) = delete; | |
| RenderLock& operator=(const RenderLock&) = delete; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
}Co-authored-by: znelson <[email protected]>
|
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! |
## 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 -->
…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]>
## 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 -->
…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]>
## 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 -->
…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]>
## 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 -->
Summary
Currently, each activity has to manage their own
displayTaskLoopwhich adds redundant boilerplate code. The loop is a wait loop which is also not the best practice, as theupdateRequestedboolean is not protected by a mutex.In this PR:
displayTaskLoopto the superActivityclassupdateRequestedwith freeRTOS's direct to task notificationActivityWithSubactivity, whenever a sub-activity is present, the parent'srender()automatically goes inactiveWith this change, activities now only need to expose
render()function, and anywhere in the code base can callrequestUpdate()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.
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
Bug Fixes