Skip to content

Fix heap fragmentation in Snake mode - #800

Draft
JanPetterMG wants to merge 15 commits into
mainfrom
fix/snake-heap-fragmentation
Draft

Fix heap fragmentation in Snake mode#800
JanPetterMG wants to merge 15 commits into
mainfrom
fix/snake-heap-fragmentation

Conversation

@JanPetterMG

Copy link
Copy Markdown
Collaborator

Refactor the Snake mode implementation to address heap fragmentation issues by changing data structures and optimizing memory usage.

Impact

These changes enhance the efficiency of the Snake mode, reducing the likelihood of heap fragmentation and improving overall performance. The refactor maintains compatibility with existing functionality while optimizing memory usage for better stability and performance.

Fixes #793

@JanPetterMG JanPetterMG added bug Something isn't working firmware C++ related labels Sep 1, 2026
@JanPetterMG JanPetterMG added this to the v2.5.2 milestone Sep 1, 2026
@JanPetterMG JanPetterMG moved this from Backlog to In review/test in Frekvens roadmap Sep 1, 2026
@JanPetterMG JanPetterMG changed the title Fix heap fragmentation issues in Snake mode Fix heap fragmentation in Snake mode Sep 1, 2026
@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 57111bc2-dd0a-459d-b49d-8b10d84a1ae6

📥 Commits

Reviewing files that changed from the base of the PR and between 1183486 and 4d38adb.

📒 Files selected for processing (2)
  • firmware/include/modes/SnakeMode.h
  • firmware/src/modes/SnakeMode.cpp

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

📜 Recent review details
🔇 Additional comments (1)
firmware/src/modes/SnakeMode.cpp (1)

105-107: 🩺 Stability & Availability

Measure the modes-task stack high-water mark.

findStepPath() uses about 2.25 kB for its three arrays on the 16×16 grid. ModesService creates the task with an 8 kB stack. The source does not provide the runtime high-water mark. Move the workspace to persistent SnakeMode storage only if the measured margin is insufficient.


Summary by CodeRabbit

  • Bug Fixes
    • Improved Snake mode movement and pathfinding for more reliable gameplay.
    • Snake movement now selects the nearest available step toward its target.
    • Improved target placement by selecting from available grid positions.
    • Added safer handling when no positions remain, including a controlled transition to the death state.
    • Improved Snake mode stage transitions, blinking, cleanup, and timing.

Walkthrough

SnakeMode now stores positions as grid indices, uses enum-based stages, separates pathfinding from fallback movement, and updates lifecycle, rendering, cleanup, and target selection for the new representation.

Changes

SnakeMode index-based state and movement

Layer / File(s) Summary
Index representation and stages
firmware/include/modes/SnakeMode.h, firmware/src/modes/SnakeMode.cpp
SnakeMode replaces Pixel coordinates with size_t indices. It uses fixed-size snake storage, occupancy tracking, and the Stage enum.
Index-based pathfinding
firmware/src/modes/SnakeMode.cpp
Breadth-first pathfinding uses fixed-size arrays and grid indices. Fallback movement selects the unoccupied adjacent index closest to the target.
Lifecycle, movement, and target handling
firmware/src/modes/SnakeMode.cpp
Lifecycle dispatch, movement rendering, blinking, cleanup, target selection, and clock handling use indexed positions. Snake storage helpers centralize insertion, removal, and death transitions.

Merge Risk: ⚪ Minimal · up to 4d38a

Snake mode replaces dynamically growing position storage with fixed-capacity indexed storage to reduce heap fragmentation and reset crashes. No concrete merge-blocking risk is currently identified.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: fixing heap fragmentation in Snake mode.
Description check ✅ Passed The description explains the purpose and impact of the refactor. It omits the required "Key changes" section, but the content is otherwise mostly complete.
Linked Issues check ✅ Passed The changes replace the heap-allocating deque and dynamic path-finding structures with fixed-capacity arrays and occupancy tracking. This directly addresses the StoreProhibited crash in SnakeMode::mov…
Out of Scope Changes check ✅ Passed The changes remain focused on the Snake mode memory and path-finding refactor required to address issue #793. No unrelated code changes are identified.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/snake-heap-fragmentation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@JanPetterMG
JanPetterMG marked this pull request as ready for review September 1, 2026 20:05
Copilot AI lite review requested due to automatic review settings September 1, 2026 20:05
@chatgpt-codex-connector

This comment was marked as off-topic.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

There are confirmed compile/runtime correctness issues (invalid enum class initialization, missing standard includes for used algorithms, and inconsistent DEATH-stage initialization when target placement fails).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This pull request refactors the Snake mode implementation to reduce dynamic allocations (a common source of heap fragmentation on embedded targets) by switching to index-based pixel representation and using fixed-size arrays for pathfinding state.

Changes:

  • Replaces the numeric stage state machine with a strongly-typed Stage enum.
  • Refactors snake and target representation from {x,y} pixels to linear frame indices (size_t) and updates rendering calls accordingly.
  • Replaces the prior pathfinding structures (std::map/std::queue) with stack-allocated fixed-size arrays to avoid heap churn.
File summaries
File Description
firmware/src/modes/SnakeMode.cpp Updates Snake mode logic to use index-based pixels, fixed-size BFS pathfinding buffers, and typed stage transitions.
firmware/include/modes/SnakeMode.h Adjusts SnakeMode’s internal state (stage enum, target/snake types) and updates private API accordingly.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread firmware/include/modes/SnakeMode.h Outdated
Comment thread firmware/src/modes/SnakeMode.cpp
Comment thread firmware/src/modes/SnakeMode.cpp Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: d2b1c89a-104e-4d1a-a389-91255cb43840

📥 Commits

Reviewing files that changed from the base of the PR and between 96c1416 and f6e50f9.

📒 Files selected for processing (2)
  • firmware/include/modes/SnakeMode.h
  • firmware/src/modes/SnakeMode.cpp

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: copilot-pull-request-reviewer
🔇 Additional comments (1)
firmware/include/modes/SnakeMode.h (1)

33-33: 🎯 Functional Correctness

No change is required. The pinned pioarduino platform uses gnu++17 by default, so Stage stage{0U} is valid.

Comment thread firmware/src/modes/SnakeMode.cpp Outdated
@github-project-automation github-project-automation Bot moved this from In review/test to In Progress in Frekvens roadmap Sep 1, 2026
@JanPetterMG
JanPetterMG marked this pull request as draft September 1, 2026 20:13
JanPetterMG and others added 7 commits September 1, 2026 22:14
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@JanPetterMG
JanPetterMG marked this pull request as ready for review September 1, 2026 22:15
Copilot AI review requested due to automatic review settings September 1, 2026 22:15
@chatgpt-codex-connector

This comment was marked as off-topic.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

idle() bypasses setTarget() and setTarget() can keep an invalid target when enabling the clock, which can break gameplay correctness.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

firmware/src/modes/SnakeMode.cpp:292

  • setTarget() can leave target unchanged if it currently points at an empty pixel, which means when the clock is enabled it may keep a target in the reserved top rows (< offset). This happens because the random selection only runs inside while (Display.getPixel(target) != 0U). Force at least one random draw from the valid range so target always ends up >= offset when the clock is enabled.
    const size_t offset{static_cast<size_t>(clock == nullptr ? 0U : 5U * GRID_COLUMNS)};
    for (size_t idx{offset}; idx < GRID_COLUMNS * GRID_ROWS; ++idx)
    {
        if (Display.getPixel(idx) == 0U)
        {
            while (Display.getPixel(target) != 0U)
            {
                target =
                    static_cast<size_t>(random(static_cast<long>(offset), static_cast<long>(GRID_COLUMNS * GRID_ROWS)));
            }
            Display.setPixel(target, static_cast<uint8_t>(random(1L, static_cast<long>(0b1U << 8U))));
            return;
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread firmware/src/modes/SnakeMode.cpp Outdated
Comment on lines +87 to +91
snake = {static_cast<size_t>((random(clock == nullptr ? 0 : 5, GRID_ROWS) * GRID_COLUMNS) + random(GRID_COLUMNS))};
Display.setPixel(snake.front(), static_cast<uint8_t>(random(1, 0b1U << 8U)));
target = static_cast<size_t>(random(static_cast<long>(clock == nullptr ? 0U : 5U * GRID_COLUMNS),
static_cast<long>(GRID_COLUMNS * GRID_ROWS)));
stage = Stage::MOVE;

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 816ab063-76ef-43df-a558-d738f78a6782

📥 Commits

Reviewing files that changed from the base of the PR and between f6e50f9 and 1183486.

📒 Files selected for processing (2)
  • firmware/include/modes/SnakeMode.h
  • firmware/src/modes/SnakeMode.cpp

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

📜 Review details
⏰ Context from checks skipped due to timeout. (64)
  • GitHub Check: Minimal (esp32-c3-devkitm-1)
  • GitHub Check: Minimal (lolin_d32)
  • GitHub Check: Minimal (lolin_s3_mini)
  • GitHub Check: Minimal (lolin_d32_pro)
  • GitHub Check: Minimal (seeed_xiao_esp32c6)
  • GitHub Check: Minimal (esp32-c5-devkitc-1)
  • GitHub Check: Minimal (seeed_xiao_esp32s3)
  • GitHub Check: Minimal (wemos_d1_mini32)
  • GitHub Check: Minimal (esp32-s3-devkitc-1)
  • GitHub Check: Minimal (seeed_xiao_esp32c3)
  • GitHub Check: Default (seeed_xiao_esp32c6)
  • GitHub Check: Minimal (esp32dev)
  • GitHub Check: Minimal (esp32-c6-devkitm-1)
  • GitHub Check: Default (esp32-c6-devkitm-1)
  • GitHub Check: Default (lolin_d32)
  • GitHub Check: Default (seeed_xiao_esp32s3)
  • GitHub Check: Minimal (adafruit_qtpy_esp32s2)
  • GitHub Check: Default (esp32-c5-devkitc-1)
  • GitHub Check: Default (lolin_s3_mini)
  • GitHub Check: Default (adafruit_qtpy_esp32s2)
  • GitHub Check: Default (esp32dev)
  • GitHub Check: Default (wemos_d1_mini32)
  • GitHub Check: Minimal (adafruit_qtpy_esp32s3_n4r2)
  • GitHub Check: Default (adafruit_qtpy_esp32s3_n4r2)
  • GitHub Check: Minimal (adafruit_qtpy_esp32s3_nopsram)
  • GitHub Check: Default (esp32-c3-devkitm-1)
  • GitHub Check: Default (esp32-s3-devkitc-1)
  • GitHub Check: Default (seeed_xiao_esp32c3)
  • GitHub Check: Default (adafruit_qtpy_esp32s3_nopsram)
  • GitHub Check: Extensive (adafruit_qtpy_esp32s3_nopsram)
  • GitHub Check: Typical (adafruit_qtpy_esp32s3_nopsram)
  • GitHub Check: Extensive (seeed_xiao_esp32s3)
  • GitHub Check: Minimal (adafruit_qtpy_esp32s2)
  • GitHub Check: Default (wemos_d1_mini32)
  • GitHub Check: Minimal (lolin_d32)
  • GitHub Check: Minimal (esp32-c6-devkitm-1)
  • GitHub Check: Minimal (lolin_d32_pro)
  • GitHub Check: Minimal (esp32-c3-devkitm-1)
  • GitHub Check: Default (seeed_xiao_esp32s3)
  • GitHub Check: Default (adafruit_qtpy_esp32s3_nopsram)
  • GitHub Check: Minimal (adafruit_qtpy_esp32s3_n4r2)
  • GitHub Check: Minimal (lolin_s3_mini)
  • GitHub Check: Default (lolin_d32)
  • GitHub Check: Default (adafruit_qtpy_esp32s2)
  • GitHub Check: Minimal (seeed_xiao_esp32s3)
  • GitHub Check: Default (esp32-s3-devkitc-1)
  • GitHub Check: Minimal (wemos_d1_mini32)
  • GitHub Check: Default (esp32dev)
  • GitHub Check: Minimal (seeed_xiao_esp32c3)
  • GitHub Check: Default (seeed_xiao_esp32c6)
  • GitHub Check: Minimal (esp32-c5-devkitc-1)
  • GitHub Check: Default (lolin_s3_mini)
  • GitHub Check: Default (seeed_xiao_esp32c3)
  • GitHub Check: Minimal (seeed_xiao_esp32c6)
  • GitHub Check: Minimal (esp32dev)
  • GitHub Check: Minimal (adafruit_qtpy_esp32s3_nopsram)
  • GitHub Check: Extensive (seeed_xiao_esp32s3)
  • GitHub Check: Default (esp32-c3-devkitm-1)
  • GitHub Check: Default (esp32-c5-devkitc-1)
  • GitHub Check: Extensive (esp32-s3-devkitc-1)
  • GitHub Check: Typical (adafruit_qtpy_esp32s3_nopsram)
  • GitHub Check: Tidy (IKEA_OBEGRANSAD, esp32-s3-devkitc-1)
  • GitHub Check: Tidy (IKEA_OBEGRANSAD, esp32-c6-devkitm-1)
  • GitHub Check: Tidy (IKEA_FREKVENS, esp32-c5-devkitc-1)
🔇 Additional comments (4)
firmware/include/modes/SnakeMode.h (1)

33-46: LGTM!

firmware/src/modes/SnakeMode.cpp (3)

12-13: LGTM!


266-271: LGTM!

Also applies to: 281-294


103-105: 🩺 Stability & Availability

The configured 16×16 grid requires 2,304 bytes for these arrays with 4-byte size_t, while the Mode task stack is 8 kB. The inspected source does not establish that the remaining stack is insufficient for the function frame and existing task usage.

Comment thread firmware/src/modes/SnakeMode.cpp Outdated
Copilot AI review requested due to automatic review settings September 1, 2026 22:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The PR’s “Fixes #793” linkage appears incorrect and the crash context indicates heap-allocation during snake growth still exists due to continued std::deque usage.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

firmware/src/modes/SnakeMode.cpp:55

  • The linked issue reference Fixes #793 in the PR description appears unrelated (Issue #793 is about playlist auto stop), so it’s unclear whether this change set actually addresses that issue. Please confirm the correct issue/ID and update the reference (or PR description) accordingly so release notes and automation aren’t misleading.
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread firmware/include/modes/SnakeMode.h
Co-authored-by: JanPetterMG <11933090+JanPetterMG@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working firmware C++ related

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

Playlist Auto Stop

3 participants