Skip to content

Remove FRAMETIME return value from all FX#5314

Open
DedeHai wants to merge 11 commits intowled:mainfrom
DedeHai:removeFRAMETIME
Open

Remove FRAMETIME return value from all FX#5314
DedeHai wants to merge 11 commits intowled:mainfrom
DedeHai:removeFRAMETIME

Conversation

@DedeHai
Copy link
Collaborator

@DedeHai DedeHai commented Jan 24, 2026

since for smooth transitions the FPS was forced to FRAMETIME anyway and only a handful of FX actually used it there is no reason to keep this logic. With persistent segment buffers a frame can also be sent out multiple times, even if the FX does not render it (which it should for smooth transitions but it is not a requirement anymore)

  • Removed return value from all FX
  • added macro FX_FALLBACK_STATIC { mode_static(); return; }
  • Updated user_fx readme removing the reference to return FRAMETIME; and add FX_FALLBACK_STATIC references
  • fixes speedup by modifying FX to always render but not always update
    • updated mode_chase_flash()
    • updated mode_chase_flash_random()
    • updated mode_colortwinkle(), it still runs at fixed frame of 42FPS
    • udpated candle(), also still runs at fixed frame rate
    • had to almsot fully re-write ICU FX to a state-based version. I also added a slight modification: it now blinks while paused and not simply at the start of a pause (@Aircoookie please veto if you do not like it)

Additional changes:

  • bugfix in PS to prevent potential crashes: PS could still run on uninitialized SEGMENT.data in some edge cases like calling a 2D PS preset in a 1D setup.

Summary by CodeRabbit

  • Bug Fixes

    • Improved error reporting and memory cleanup for unsupported particle/2D initializations.
    • Removed inconsistent frame-return pathways to prevent timing misbehavior.
  • Refactor

    • Standardized all effects to a fixed, cadence-driven update model (non-blocking).
    • Consolidated many effect handlers to void-style, side-effect-driven contracts.
    • Simplified particle update flow by removing conditional render-only branches.
  • Documentation

    • Clarified cadence/frame-timing guidance and added fixed-rate examples.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 24, 2026

Walkthrough

Core effect mode functions and related typedefs were converted from returning uint16_t to void, removing per-mode frame-delay returns; the scheduler now uses a fixed FRAMETIME cadence. ParticleSystem2D::updateFire lost its renderonly parameter and always updates; 2D/1D init failures now set error flags and deallocate data. README adds FX_FALLBACK_STATIC.

Changes

Cohort / File(s) Summary
Core FX typedefs & registration
wled00/FX.h
Changed mode_ptr typedef and ModeData constructor to accept functions returning void instead of uint16_t.
Scheduler / service loop
wled00/FX_fcn.cpp
Removed per-mode frameDelay capture/usage; mode handlers are invoked directly and seg.next_time always set to nowUp + FRAMETIME.
FX implementations & usermods
usermods/..., usermods/user_fx/user_fx.cpp, usermods/user_fx/README.md, usermods/Temperature/Temperature.cpp, usermods/TetrisAI_v2/TetrisAI_v2.cpp, usermods/pov_display/pov_display.cpp, usermods/usermod_v2_animartrix/..., usermods/pixels_dice_tray/led_effects.h
Many mode_* functions changed from uint16_tvoid; removed FRAMETIME return values or replaced with plain return; README adds FX_FALLBACK_STATIC and updates sinelon_base/wrappers.
Particle system API & error handling
wled00/FXparticleSystem.cpp, wled00/FXparticleSystem.h
ParticleSystem2D::updateFire() signature simplified (removed renderonly) and always updates; non-matrix init failures now set errorFlag = ERR_NOT_IMPL, call SEGMENT.deallocateData(), and return false.
User-facing docs / examples
usermods/user_fx/README.md
Introduced FX_FALLBACK_STATIC macro; consolidated cadence-only examples; changed sinelon_base to void and updated wrappers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • willmmiles
  • Aircoookie
  • softhack007
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Remove FRAMETIME return value from all FX' directly and clearly summarizes the main objective of this PR, which is to eliminate FRAMETIME return values from all effect functions across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@usermods/user_fx/README.md`:
- Around line 83-85: The tutorial snippets call mode_static() when a non-2D
setup is detected but do not stop execution, allowing the flow to continue into
2D-specific logic; update the conditional that checks strip.isMatrix and
SEGMENT.is2D() so that after calling mode_static() you immediately return (e.g.,
wrap mode_static() in a block and add return;) and apply the same change to the
other occurrence referenced around the second snippet (the one at 130-132) so
both examples exit early after the fallback.

In `@wled00/FXparticleSystem.h`:
- Line 23: The header currently defines WLED_DEBUG_PS unconditionally which
forces particle-system debug and extra flash into every build; remove the
default `#define` and make WLED_DEBUG_PS opt-in by guarding debug code behind a
build-time macro (e.g., rely on a compile flag such as -DENABLE_WLED_DEBUG_PS or
an existing global debug macro) so that FXparticleSystem.h only enables debug
when that build flag is set; update references to WLED_DEBUG_PS in the file so
they are inside `#ifdef` ENABLE_WLED_DEBUG_PS (or the chosen macro) and document
the new build flag for opt‑in usage.
🧹 Nitpick comments (3)
usermods/user_fx/README.md (1)

253-253: Clarify how to re‑render when skipping state updates.

The new note says to re‑render every frame, but the example still renders only inside the update interval. Consider adding a short hint or snippet showing a “render every call, update conditionally” structure to avoid confusion for readers.

wled00/FXparticleSystem.cpp (2)

1098-1102: Consider nulling PartSys on early exit to avoid dangling pointers.
SEGMENT.deallocateData() invalidates prior PS memory; setting PartSys = nullptr here makes accidental reuse safer.

♻️ Suggested change
   if (!strip.isMatrix) {
     errorFlag = ERR_NOT_IMPL; // TODO: need a better error code if more codes are added
     SEGMENT.deallocateData(); // deallocate any data to make sure data is null (there is no valid PS in data and data can only be checked for null)
+    PartSys = nullptr;
     return false; // only for 2D
   }

1850-1854: Also null PartSys in the 1D early-exit path.
This keeps callers from accidentally using a stale pointer after SEGMENT.deallocateData().

♻️ Suggested change
   if (SEGLEN == 1) {
     errorFlag = ERR_NOT_IMPL; // TODO: need a better error code if more codes are added
     SEGMENT.deallocateData(); // deallocate any data to make sure data is null (there is no valid PS in data and data can only be checked for null)
+    PartSys = nullptr;
     return false; // single pixel not supported
   }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
usermods/user_fx/README.md (1)

302-309: Update Sinelon example to void-return to match FX changes.
The example still declares uint16_t and later shows return FRAMETIME;, but FX_FALLBACK_STATIC expands to return; and the API is now void. Please align the snippet and its narrative to avoid copy-paste compile errors.

✏️ Proposed doc fix
-static uint16_t sinelon_base(bool dual, bool rainbow=false) {
+static void sinelon_base(bool dual, bool rainbow=false) {
   if (SEGLEN <= 1) FX_FALLBACK_STATIC;
   ...
-  return FRAMETIME;
 }
-uint16_t mode_sinelon(void) {
-  return sinelon_base(false);
-}
+void mode_sinelon(void) {
+  sinelon_base(false);
+}
 
-uint16_t mode_sinelon_dual(void) {
-  return sinelon_base(true);
-}
+void mode_sinelon_dual(void) {
+  sinelon_base(true);
+}
 
-uint16_t mode_sinelon_rainbow(void) {
-  return sinelon_base(false, true);
-}
+void mode_sinelon_rainbow(void) {
+  sinelon_base(false, true);
+}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@wled00/FXparticleSystem.h`:
- Around line 145-147: The comment for updateFire still mentions the removed
renderonly parameter and should be updated to reflect the new always-update
behavior; modify the comment on the updateFire(const uint8_t intensity)
declaration in FXparticleSystem.h to remove the "if renderonly is set, particles
are not updated" clause and instead state that updateFire always updates
particles (and is used to keep fire behavior consistent across frame skips/
transitions), or simply remove the outdated phrase so the comment matches
current behavior.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
usermods/Temperature/Temperature.cpp (1)

3-3: Fix forward declaration return type (compile error).

The forward declaration still returns uint16_t while the definition is void, which is a conflicting type error.

🛠️ Proposed fix
-static uint16_t mode_temperature();
+static void mode_temperature();
usermods/TetrisAI_v2/TetrisAI_v2.cpp (1)

101-108: Remove return value from void function.

mode_2DTetrisAI() is void, but still returns 350, which is invalid.

🛠️ Proposed fix
   if (!strip.isMatrix || !SEGENV.allocateData(sizeof(tetrisai_data)))
   {
     // not a 2D set-up
     SEGMENT.fill(SEGCOLOR(0));
-    return 350;
+    return;
   }
usermods/pixels_dice_tray/led_effects.h (2)

44-66: Fix return-type mismatch after switching running_copy to void (compile break).

running_copy() is now void, but check_roll() still does return running_copy(...) (Line 112) and the mode wrappers (pulse_roll, check_roll, plus the extern mode_* declarations) still use uint16_t returns. With the global FX return removal, this will fail to compile. Update the usermod effects and externs to void and stop returning their results.

Example shape (outside this hunk, for illustration):

extern void mode_breath();
extern void mode_blends();
extern void mode_glitter();
extern void mode_gravcenter();

static void pulse_roll() {
  auto roll = GetLastRollForSegment();
  if (roll.state != pixels::RollState::ON_FACE) { mode_breath(); return; }
  mode_blends();
  ...
}

static void check_roll() {
  auto roll = GetLastRollForSegment();
  if (roll.state != pixels::RollState::ON_FACE) { running_copy(SEGCOLOR(0), SEGCOLOR(2)); return; }
  if (roll.current_face + 1 >= SEGMENT.custom2) { mode_glitter(); return; }
  mode_gravcenter();
}

68-81: Clamp num_segments and avoid <= to prevent out-of-range index.

When roll.current_face is max, num_segments can equal SEGLEN, and i <= num_segments calls setPixelColor(SEGLEN, ...). Safer to clamp and use <.

✅ Suggested fix
-    uint16_t num_segments = float(roll.current_face + 1) / 20.0 * SEGLEN;
-    for (int i = 0; i <= num_segments; i++) {
+    uint16_t num_segments = (uint32_t)(roll.current_face + 1) * SEGLEN / 20;
+    if (num_segments > SEGLEN) num_segments = SEGLEN;
+    for (int i = 0; i < num_segments; i++) {
       SEGMENT.setPixelColor(i, SEGCOLOR(0));
     }
🤖 Fix all issues with AI agents
In `@usermods/user_fx/README.md`:
- Around line 255-257: Typo: change the misspelled word "fuction" to "function"
in the README paragraph that mentions SEGMENT.call and FRAMETIME; update the
sentence "Also `SEGMENT.call` is still incremented on each fuction call." to
"Also `SEGMENT.call` is still incremented on each function call." to correct the
spelling while keeping the wording and references to FRAMETIME, refresh_ms and
SEGMENT.call intact.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
usermods/pixels_dice_tray/led_effects.h (1)

73-79: Prevent out-of-bounds write when the roll fills the segment.
If num_segments == SEGLEN, i <= num_segments writes index SEGLEN (invalid).

🐛 Proposed fix
-    uint16_t num_segments = float(roll.current_face + 1) / 20.0 * SEGLEN;
-    for (int i = 0; i <= num_segments; i++) {
+    uint16_t num_segments = float(roll.current_face + 1) / 20.0 * SEGLEN;
+    if (num_segments > SEGLEN) num_segments = SEGLEN;
+    for (int i = 0; i < num_segments; i++) {
       SEGMENT.setPixelColor(i, SEGCOLOR(0));
     }
🤖 Fix all issues with AI agents
In `@usermods/pixels_dice_tray/led_effects.h`:
- Around line 109-114: In function check_roll() fix the compile error by adding
the missing semicolon after the bare return inside the if branch; locate
check_roll and the offending return within the block that calls
running_copy(SEGCOLOR(0), SEGCOLOR(2)) and terminate that statement with a
semicolon so the function compiles correctly.

In `@usermods/user_fx/README.md`:
- Line 309: The function signature for sinelon_base is missing the explicit
return type; update the declaration "static sinelon_base(bool dual, bool
rainbow=false)" to include the void return type (i.e., change it to "static void
sinelon_base(...)") so it matches the PR's move from uint16_t to void and the
rest of the API; modify the declaration and any matching forward declarations or
definitions of sinelon_base to use the void return type.
- Around line 398-410: The three wrapper functions mode_sinelon,
mode_sinelon_dual, and mode_sinelon_rainbow are missing return type specifiers;
update their declarations to include the proper WLED FX convention (e.g., static
void) before each function name so they match sinelon_base's usage and signature
expectations; ensure you change mode_sinelon(void), mode_sinelon_dual(void), and
mode_sinelon_rainbow(void) to static void mode_sinelon(void), static void
mode_sinelon_dual(void), and static void mode_sinelon_rainbow(void)
respectively.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@usermods/user_fx/README.md`:
- Around line 255-262: The snippet currently uses an early return which prevents
rendering and breaks smooth transitions; instead modify the logic around
SEGENV.step/strip.now so you only skip updating effect state when (strip.now -
SEGENV.step) < FRAMETIME_FIXED but do not return — allow the rest of the
function to run to perform rendering and let SEGMENT.call increment. Concretely,
change the early-return pattern to a conditional branch that bypasses
state-mutating code (state update block) when the interval hasn't elapsed, but
still executes rendering/gradient/brightness/segment output code; keep updating
SEGENV.step only when you actually perform the state update so timing remains
correct.

@netmindz
Copy link
Member

I've not tested all the effects, but approve in principle the change

@DedeHai
Copy link
Collaborator Author

DedeHai commented Jan 31, 2026

thanks, its almost exclusively "house-keeping". only the "ICU" FX has fudamental changes.

While checking feasiblity of #5135 a few hours ago I actually found that seg.next_time is no longer needed, it could be made static or since it is used only in strip class, moved there (which I think should be the solution). Or is there a good reason to keep next_time per segment or even in the segment class?

the segmen's internal "next time" could be replaced with "time_offset" then all that is missing is instead of strip.now() use strip.now()+time_offset and #5135 has a base to work with.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 5, 2026

@coderabbitai please explain the use of "direct" in
WS2812FX::setBrightness(uint8_t b, bool direct)
and if it is still useful with the changes from this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the setBrightness function definition
rg -n -A 20 "void.*setBrightness\s*\(\s*uint8_t.*bool\s+direct" --type=cpp

Length of output: 1283


🏁 Script executed:

#!/bin/bash
# Also search for where setBrightness is called with the direct parameter
rg -n -C 3 "setBrightness\s*\([^)]*,\s*(true|false|direct)" --type=cpp | head -100

Length of output: 3220


🏁 Script executed:

#!/bin/bash
# Find the declaration in header files
rg -n "setBrightness.*direct" --type=h

Length of output: 96


@DedeHai The direct parameter in setBrightness(uint8_t b, bool direct) controls whether the function should automatically trigger an immediate LED update when changing brightness.

How it works

Looking at the implementation in wled00/FX_fcn.cpp:1715-1727:

void WS2812FX::setBrightness(uint8_t b, bool direct) {
  // ... brightness update logic ...
  BusManager::setBrightness(scaledBri(b));
  if (!direct) {
    unsigned long t = millis();
    if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger();
  }
}

When direct = false (default):

  • Checks if the next scheduled frame update is more than 22ms away
  • If so, calls trigger() to immediately apply the brightness change
  • Prevents visible delay in brightness response when effects are running slowly

When direct = true:

  • Skips the automatic trigger check
  • Caller takes responsibility for updating the LEDs
  • Used when the caller will handle the update themselves or wants precise timing control

Usage patterns in the codebase

  • Realtime modes (UDP, DMX): Use direct=true to manage their own frame timing
  • E131: Uses direct=true for direct pixel control, direct=false for brightness-only changes to trigger state updates
  • JSON API: Uses direct=true when stopping transitions to avoid race conditions

Still useful after this PR?

Yes, the direct parameter remains useful after your changes:

  1. Your PR changes how next_time is calculated (fixed FRAMETIME instead of dynamic), but doesn't remove the next_time mechanism itself
  2. The check _segments[0].next_time > t + 22 still makes sense - it detects when the next frame is far away
  3. Realtime modes still need to bypass the automatic trigger to maintain their own timing control
  4. The parameter provides important control over when LED updates happen, which is independent of how effect frame timing works

The fixed frame timing from your PR actually makes the behavior more predictable, but the need to optionally trigger immediate updates when changing brightness remains valid.


🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-12-28T14:06:48.772Z
Learning: In WLED post-commit ee9ac947, the rendering pipeline uses per-segment buffers and per-pixel bus updates. Unmapped (0xFFFF) mapping entries are now skipped in WS2812FX::show() (no “clear to black”), which can leave physical gap LEDs with stale/random colors unless they are explicitly cleared. This is a behavior change from pre-0.16 where a full physical buffer was effectively refreshed each frame.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5293
File: usermods/user_fx/user_fx.cpp:167-171
Timestamp: 2026-01-13T03:07:20.560Z
Learning: In WLED effects (usermods/user_fx/user_fx.cpp and similar), when storing future timestamps, assignments like `stored_time = now + delay` are correct and handle uint32_t wraparound properly. Only comparison statements need to use subtraction-based elapsed-time checks (e.g., `(now - stored_time) >= delay`). Do not flag assignment statements as needing fixes for wraparound handling.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5109
File: wled00/FX.cpp:3174-3343
Timestamp: 2025-11-27T06:33:11.436Z
Learning: WLED Ants effect (wled00/FX.cpp): The author prefers the current velocity initialization using hw_random16(1000, 5000)/5000.0f, resulting in an effective range of ~3.6–10.0 (with VELOCITY_MIN=2.0, VELOCITY_MAX=10.0), and wants the code kept as-is with comments updated to document this behavior. Avoid suggesting changes to span the full 2.0–10.0 range in future reviews.

Learnt from: DedeHai
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-01-13T21:23:35.514Z
Learning: In WLED, the global `paletteBlend` variable (wled.h:603) and the `WS2812FX::paletteBlend` member (FX.h:940) are duplicates without synchronization code. The global is loaded/saved in cfg.cpp and set via UI in set.cpp, but never copied to the strip member. This is the only such case in the codebase; other settings are either strip-only members (autoSegments, correctWB, cctFromRgb, isMatrix) or global-only (gammaCorrectCol/Bri/Val, blendingStyle).

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Learnt from: freakintoddles2
Repo: wled/WLED PR: 4904
File: wled00/FX.cpp:3915-3930
Timestamp: 2025-09-02T19:42:57.475Z
Learning: WLED wled00/FX.cpp::mode_percent — For time-based options (% of Minute/Hour/Day), behavior is clock-like: at rollover (elapsed==0) intentionally show SEGLEN lit for one tick to represent completion, then move to 1 LED at the next second. This is by design (not a progress bar) and should not be “fixed”.

Learnt from: DedeHai
Repo: wled/WLED PR: 4926
File: wled00/FX.cpp:4727-4730
Timestamp: 2025-11-16T19:40:46.260Z
Learning: WLED AuroraWave (wled00/FX.cpp): wave_start and wave_end intentionally use int16_t; segments longer than 32k LEDs are not supported (bounded by MAX_LEDS), so widening to 32-bit is unnecessary.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5293
File: usermods/user_fx/user_fx.cpp:149-151
Timestamp: 2026-01-24T18:51:09.740Z
Learning: In WLED's Spinning Wheel effect (usermods/user_fx/user_fx.cpp), the color scale calculation (SEGENV.aux1) must match the position modulo range. When position uses `pos = (pos_fixed >> 16) % SEGLEN`, the color scale must be `aux1 = (255 << 8) / SEGLEN` to distribute the palette evenly across the full position range. Using vstripLen for color scaling while position uses SEGLEN causes palette colors to cycle too quickly and appear incomplete.

Learnt from: softhack007
Repo: wled/WLED PR: 5251
File: usermods/user_fx/user_fx.cpp:225-227
Timestamp: 2026-01-01T13:18:14.738Z
Learning: In WLED effects, when computing time differences between unsigned long timestamps (e.g., `strip.now`, `lastBumpUpdate`), always cast the subtraction result to `int` before converting to `float` to ensure correct behavior on ESP32-C3 (RISC-V). Use pattern: `float(int(timestamp1 - timestamp2))` instead of `float(timestamp1 - timestamp2)`.

Learnt from: KrX3D
Repo: wled/WLED PR: 4585
File: usermods/seven_segment_display_reloaded_v2/seven_segment_display_reloaded_v2.cpp:430-435
Timestamp: 2025-04-28T20:51:29.773Z
Learning: In WLED, `bri` is a global variable used for brightness control.

Learnt from: blazoncek
Repo: wled/WLED PR: 5140
File: wled00/data/settings_time.htm:66-76
Timestamp: 2025-12-01T07:01:16.949Z
Learning: In WLED PR `#5134`, the fix for macros being initialized with the enable bit set only handles new configurations, not existing ones. If there is a bug in timer/macro handling code (e.g., in settings_time.htm), it must be fixed to work correctly for existing configurations as well.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 4891
File: wled00/FX.cpp:3333-3349
Timestamp: 2025-08-31T03:38:14.114Z
Learning: WLED PacMan effect (wled00/FX.cpp): Keep pacmancharacters_t position fields as signed int (not int16_t). Maintainer preference (blazoncek) prioritizes avoiding potential overhead/regressions over minor RAM savings. Avoid type shrinking here unless memory pressure is demonstrated.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 5, 2026

@coderabbitai regarding the above comment about seg.next_time: since all segments now update at the same pace, should this be made static or is it better to move it into the strip class?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the declaration of next_time in the segment structure
rg -n "next_time" --type=h -C 3

# Find all usages of next_time in the codebase
echo "=== All next_time usages ==="
rg -n "next_time" --type=cpp -C 2

# Check if there are any segment-specific timing patterns
echo "=== Checking for per-segment timing logic ==="
rg -n "seg\.next_time|SEGMENT\.next_time|segments\[.*\]\.next_time" --type=cpp -C 3

Length of output: 3304


🏁 Script executed:

#!/bin/bash
# Check if segments can be started/stopped independently or have different states
echo "=== Checking segment activation/lifecycle ==="
rg -n "seg\.isActive|SEGMENT\.isActive" --type=cpp -C 2 | head -50

# Check the service loop more carefully for timing logic
echo "=== Service loop context ==="
rg -n "void.*service\(" wled00/FX_fcn.cpp -A 50

# Look for any segment-specific speed or delay modifications
echo "=== Checking for segment speed modifications ==="
rg -n "seg\.speed|SEGMENT\.speed" --type=cpp -C 2 | head -30

Length of output: 8711


@DedeHai, excellent question! After analyzing the codebase, I recommend keeping next_time per-segment rather than making it static or moving it to the strip class. Here's why:

Current usage patterns

From the code analysis:

  • Line FX_fcn.cpp:1285: Each segment is checked individually: if (nowUp > seg.next_time || _triggered...)
  • Line FX_fcn.cpp:1312: All segments set seg.next_time = nowUp + FRAMETIME (same rate)
  • Line FX_fcn.cpp:220: When a segment resets, next_time = 0 (immediate update)
  • Line FX_fcn.cpp:1725: setBrightness checks _segments[0].next_time for trigger decision

Why per-segment timing is still valuable

Even though all segments now update at the same rate (FRAMETIME), they don't necessarily update at the same time:

  1. Independent lifecycle: Segments can be activated/deactivated at any point. When a segment becomes active, its next_time = 0 allows it to start rendering immediately in the next service() call, rather than waiting for a global timer tick.

  2. Staggered starts: If you activate multiple segments at different times, they'll naturally stagger their updates slightly, which can help distribute processing load.

  3. Reset behavior: When a segment is reset (mode change, effect restart), it can update immediately without forcing all other segments to update.

  4. Future flexibility: Your time_offset idea for issue #5135 would work naturally with per-segment next_time. You could implement per-segment timing offsets by adjusting when each segment's next_time is set, allowing synchronized or phased animations across segments.

If you moved to static/strip-level

A single global timer would mean:

  • Newly activated segments must wait for the next global tick (up to FRAMETIME delay)
  • All segments would update in perfect lockstep (might cause processing spikes)
  • Less flexibility for future per-segment timing features
  • The setBrightness check would be simpler (no _segments[0] indexing needed)

Recommendation

Keep it per-segment. The memory cost is minimal (4 bytes × num_segments), and it preserves the ability for segments to have independent lifecycles while still rendering at a uniform rate. This design also leaves the door open for your time_offset enhancement without requiring another refactor.


🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-12-28T14:06:48.772Z
Learning: In WLED post-commit ee9ac947, the rendering pipeline uses per-segment buffers and per-pixel bus updates. Unmapped (0xFFFF) mapping entries are now skipped in WS2812FX::show() (no “clear to black”), which can leave physical gap LEDs with stale/random colors unless they are explicitly cleared. This is a behavior change from pre-0.16 where a full physical buffer was effectively refreshed each frame.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5293
File: usermods/user_fx/user_fx.cpp:167-171
Timestamp: 2026-01-13T03:07:20.560Z
Learning: In WLED effects (usermods/user_fx/user_fx.cpp and similar), when storing future timestamps, assignments like `stored_time = now + delay` are correct and handle uint32_t wraparound properly. Only comparison statements need to use subtraction-based elapsed-time checks (e.g., `(now - stored_time) >= delay`). Do not flag assignment statements as needing fixes for wraparound handling.

Learnt from: DedeHai
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-01-13T21:23:35.514Z
Learning: In WLED, the global `paletteBlend` variable (wled.h:603) and the `WS2812FX::paletteBlend` member (FX.h:940) are duplicates without synchronization code. The global is loaded/saved in cfg.cpp and set via UI in set.cpp, but never copied to the strip member. This is the only such case in the codebase; other settings are either strip-only members (autoSegments, correctWB, cctFromRgb, isMatrix) or global-only (gammaCorrectCol/Bri/Val, blendingStyle).

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Learnt from: freakintoddles2
Repo: wled/WLED PR: 4904
File: wled00/FX.cpp:3915-3930
Timestamp: 2025-09-02T19:42:57.475Z
Learning: WLED wled00/FX.cpp::mode_percent — For time-based options (% of Minute/Hour/Day), behavior is clock-like: at rollover (elapsed==0) intentionally show SEGLEN lit for one tick to represent completion, then move to 1 LED at the next second. This is by design (not a progress bar) and should not be “fixed”.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5293
File: usermods/user_fx/user_fx.cpp:149-151
Timestamp: 2026-01-24T18:51:09.740Z
Learning: In WLED's Spinning Wheel effect (usermods/user_fx/user_fx.cpp), the color scale calculation (SEGENV.aux1) must match the position modulo range. When position uses `pos = (pos_fixed >> 16) % SEGLEN`, the color scale must be `aux1 = (255 << 8) / SEGLEN` to distribute the palette evenly across the full position range. Using vstripLen for color scaling while position uses SEGLEN causes palette colors to cycle too quickly and appear incomplete.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5109
File: wled00/FX.cpp:3174-3343
Timestamp: 2025-11-27T06:33:11.436Z
Learning: WLED Ants effect (wled00/FX.cpp): The author prefers the current velocity initialization using hw_random16(1000, 5000)/5000.0f, resulting in an effective range of ~3.6–10.0 (with VELOCITY_MIN=2.0, VELOCITY_MAX=10.0), and wants the code kept as-is with comments updated to document this behavior. Avoid suggesting changes to span the full 2.0–10.0 range in future reviews.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 4891
File: wled00/FX.cpp:3333-3349
Timestamp: 2025-08-31T03:38:14.114Z
Learning: WLED PacMan effect (wled00/FX.cpp): Keep pacmancharacters_t position fields as signed int (not int16_t). Maintainer preference (blazoncek) prioritizes avoiding potential overhead/regressions over minor RAM savings. Avoid type shrinking here unless memory pressure is demonstrated.

Learnt from: DedeHai
Repo: wled/WLED PR: 4926
File: wled00/FX.cpp:4727-4730
Timestamp: 2025-11-16T19:40:46.260Z
Learning: WLED AuroraWave (wled00/FX.cpp): wave_start and wave_end intentionally use int16_t; segments longer than 32k LEDs are not supported (bounded by MAX_LEDS), so widening to 32-bit is unnecessary.

Learnt from: blazoncek
Repo: wled/WLED PR: 5140
File: wled00/data/settings_time.htm:66-76
Timestamp: 2025-12-01T07:01:16.949Z
Learning: In WLED PR `#5134`, the fix for macros being initialized with the enable bit set only handles new configurations, not existing ones. If there is a bug in timer/macro handling code (e.g., in settings_time.htm), it must be fixed to work correctly for existing configurations as well.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Feb 5, 2026

@softhack007 would there be any downside to moving the seg.next_time into the strip class and have all segments & strips always update in sync? I can not think of a reason not to do that.

@softhack007
Copy link
Member

@DedeHai good question.
As much as I remember, "next_time" was for segments have individual "frame rates" based on FRAMETIME returned by each effect. If all effects now progress at the same rate, next_time could be removed. At least that's what i recall, @blazoncek might know more.

  • Line FX_fcn.cpp:1312: All segments set seg.next_time = nowUp + FRAMETIME (same rate)

nowUp (and SEGMENT.now) is already controlled by the same (global) clock, so these might not be needed or could be moved to strip level.

  • Line FX_fcn.cpp:220: When a segment resets, next_time = 0 (immediate update)

This might be a tricky one. Not sure if next_time = 0 causes special behaviour in some functions. A segment reset should also lead to segment::call == 0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants