-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Game of Life Rework #4995
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Game of Life Rework #4995
Conversation
RAM and speed optimizations. Better repeat detection. Mutation toggle. Blur option added.
WalkthroughOverhauls 2D Game of Life in FX.cpp: replaces ColorCount with public Cell bitfield, switches to a contiguous Cell array, removes CRC-based repetition detection, adds per-cell flags (edge/oscillator/spaceship), rewrites setup and evolution logic, and updates the mode’s metadata string. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (1)
wled00/FX.cpp (1)
5289-5303
: Early break both loops when neighbors > 3 (micro-optim).break exits only the inner loop; the outer i-loop continues. Short‑circuit both loops to save work on dense patterns.
- for (int i = 1; i >= -1; i--) for (int j = 1; j >= -1; j--) if (i || j) { + bool done = false; + for (int i = 1; i >= -1 && !done; i--) for (int j = 1; j >= -1; j--) if (i || j) { int nX = x + j, nY = y + i; if (cell.edgeCell) { nX = (nX + cols) % cols; nY = (nY + rows) % rows; } unsigned nIndex = nX + nY * cols; Cell& neighbor = cells[nIndex]; if (neighbor.alive) { - if (++neighbors > 3) break; + if (++neighbors > 3) { done = true; break; } if (!neighbor.toggleStatus) { // Alive and not dying parentIdx[aliveParents++] = nIndex; } } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
wled00/FX.cpp
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
wled00/**/*.cpp
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use 2-space indentation for C++ source files (.cpp)
Files:
wled00/FX.cpp
🧬 Code graph analysis (1)
wled00/FX.cpp (3)
wled00/FX_fcn.cpp (2)
blur
(1071-1097)blur
(1071-1071)wled00/fcn_declare.h (3)
hw_random8
(433-433)hw_random8
(434-434)hw_random8
(435-435)wled00/colors.cpp (1)
color_blend
(11-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (18)
- GitHub Check: wled_build / Build Environments (esp32_eth)
- GitHub Check: wled_build / Build Environments (esp32dev)
- GitHub Check: wled_build / Build Environments (usermods)
- GitHub Check: wled_build / Build Environments (esp32c3dev)
- GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
- GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
- GitHub Check: wled_build / Build Environments (esp32_wrover)
- GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
- GitHub Check: wled_build / Build Environments (lolin_s2_mini)
- GitHub Check: wled_build / Build Environments (nodemcuv2_compat)
- GitHub Check: wled_build / Build Environments (esp8266_2m)
- GitHub Check: wled_build / Build Environments (esp8266_2m_160)
- GitHub Check: wled_build / Build Environments (esp8266_2m_compat)
- GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
- GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
- GitHub Check: wled_build / Build Environments (esp01_1m_full)
- GitHub Check: wled_build / Build Environments (nodemcuv2_160)
- GitHub Check: wled_build / Build Environments (nodemcuv2)
🔇 Additional comments (1)
wled00/FX.cpp (1)
5355-5356
: Metadata: surface fits behavior; confirm slider wiring.Blur is mapped from custom1 and mutation from check3 in code; metadata declares "Blur" and "Mutation" accordingly. Looks consistent. Please confirm mobile UI ties custom1/check3 to these controls.
typedef struct Cell { | ||
uint8_t alive : 1, faded : 1, toggleStatus : 1, edgeCell: 1, oscillatorCheck : 1, spaceshipCheck : 1, unused : 2; | ||
} Cell; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Enforce 1-byte Cell packing at compile time.
Bitfield packing is implementation-defined. Add a static_assert to guarantee 1 byte per cell across toolchains.
typedef struct Cell {
uint8_t alive : 1, faded : 1, toggleStatus : 1, edgeCell: 1, oscillatorCheck : 1, spaceshipCheck : 1, unused : 2;
} Cell;
+static_assert(sizeof(Cell) == 1, "Cell must be 1 byte to meet memory/perf goals");
🤖 Prompt for AI Agents
In wled00/FX.cpp around lines 5199 to 5201, the Cell bitfield layout is
implementation-defined and may not be 1 byte; add a compile-time check
immediately after the typedef: use a static_assert that sizeof(Cell) == 1 with a
clear message (e.g., "Cell must be 1 byte") so builds fail if packing differs
across toolchains; keep the assert close to the struct definition for clarity.
if (!SEGENV.allocateData(SEGMENT.length() * sizeof(Cell))) return mode_static(); // allocation failed | ||
|
||
Cell *cells = reinterpret_cast<Cell*> (SEGENV.data); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix allocation size to match iteration domain (cols*rows).
You iterate maxIndex = cols*rows but allocate SEGMENT.length() cells. In some 2D mappings these can diverge. Allocate exactly maxIndex to avoid OOB writes on atypical mappings.
Apply this diff:
- if (!SEGENV.allocateData(SEGMENT.length() * sizeof(Cell))) return mode_static(); // allocation failed
+ if (!SEGENV.allocateData(maxIndex * sizeof(Cell))) return mode_static(); // allocation failed
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (!SEGENV.allocateData(SEGMENT.length() * sizeof(Cell))) return mode_static(); // allocation failed | |
Cell *cells = reinterpret_cast<Cell*> (SEGENV.data); | |
if (!SEGENV.allocateData(maxIndex * sizeof(Cell))) return mode_static(); // allocation failed | |
Cell *cells = reinterpret_cast<Cell*>(SEGENV.data); |
🤖 Prompt for AI Agents
In wled00/FX.cpp around lines 5209 to 5212, the code allocates SEGMENT.length()
Cell entries but the iteration domain uses maxIndex = cols * rows, which can
differ for 2D mappings and cause out-of-bounds writes; change the allocation to
request maxIndex * sizeof(Cell) (i.e., allocate exactly cols*rows Cells), keep
the existing null-check and fallback to mode_static() on failure, and then cast
SEGENV.data to Cell* as before.
unsigned a = rows, b = cols; | ||
while (b) { unsigned t = b; b = a % b; a = t; } | ||
gliderLength = (cols * rows / a) << 2; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard gliderLength against overflow.
gliderLength = LCM(rows,cols)*4 stored in uint16_t can overflow on larger grids. Use 32-bit during calc and clamp/store to aux1.
- gliderLength = (cols * rows / a) << 2;
+ uint32_t gl = ((uint32_t)cols * (uint32_t)rows / a) << 2;
+ gliderLength = (gl > UINT16_MAX) ? UINT16_MAX : (uint16_t)gl;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
unsigned a = rows, b = cols; | |
while (b) { unsigned t = b; b = a % b; a = t; } | |
gliderLength = (cols * rows / a) << 2; | |
} | |
unsigned a = rows, b = cols; | |
while (b) { unsigned t = b; b = a % b; a = t; } | |
uint32_t gl = ((uint32_t)cols * (uint32_t)rows / a) << 2; | |
gliderLength = (gl > UINT16_MAX) ? UINT16_MAX : (uint16_t)gl; | |
} |
🤖 Prompt for AI Agents
In wled00/FX.cpp around lines 5223 to 5226, the gliderLength calculation
(LCM(rows,cols)*4) can overflow uint16_t; compute using 32-bit temporaries: cast
rows and cols to uint32_t, compute gcd then lcm as (rows / gcd) * cols to avoid
intermediate overflow, multiply the 32-bit lcm by 4, clamp the result to the
maximum uint16_t value (0xFFFF) and then store the clamped value into aux1 (or
the uint16_t gliderLength), ensuring the assignment uses the clamped 16-bit
value.
Yes. 2D is built on top of 1D functions. |
A lightweight version of my MM version to keep code size differences as small as possible (adds ~120 Bytes). Uses 1 Byte per pixel instead of 3. Testing uncapped speeds (esp32dev hub75 build) it ran about 50% faster on 32x32 and 64x32. Improved repeat detection that detects a majority of gliders and oscillators. Not 100% perfect on smaller grids that can produce long repeating patterns due to wrap, I kept mutation as a toggle option for these sizes (8x8 for example). Added blur slider to fade out dying cells.
Previous version had a max update speed of ~10 per second. I increased it to 42 like the old comment suggested.
Videos showing blur:
GoL.64x64.mp4
GoL.Blend.mp4
Patterns that are not detected:
8x8 has oscillators that repeat every 48 and 132 frames. Detection works on patterns <32 for 8x8
24x16 has a spaceship that repeats every 2448 frames. Detection works on patterns <196 for 24x16
Are 2D effects allowed to use 1D version of get/setPixelColor? I didn't include them in this PR but I did test them. They seemed to work fine and reduced code size a bit since in certain places I calculate x and y from an index. For example:
Summary by CodeRabbit