Skip to content

fix(text): mark WorldPosition dirty after slot reallocation in _updateLocalData#2981

Open
cptbtptpbcptdtptp wants to merge 1 commit intogalacean:dev/2.0from
cptbtptpbcptdtptp:fix/text-bounds-dirty-flag
Open

fix(text): mark WorldPosition dirty after slot reallocation in _updateLocalData#2981
cptbtptpbcptdtptp wants to merge 1 commit intogalacean:dev/2.0from
cptbtptpbcptdtptp:fix/text-bounds-dirty-flag

Conversation

@cptbtptpbcptdtptp
Copy link
Copy Markdown
Collaborator

@cptbtptpbcptdtptp cptbtptpbcptdtptp commented Apr 30, 2026

Summary

  • _updateLocalData (both TextRenderer and UI Text) calls _freeTextChunks + _buildChunk → allocateSubChunk, which under PrimitiveChunk's first-fit + free-list-merge allocator can hand back a slot previously owned by another renderer. _buildChunk writes UV/color but not pos, so the new slot retains the previous owner's pos floats as residue.
  • The bounds getter path runs _updateLocalData then checks WorldPosition. When only LocalPositionBounds is dirty (e.g. UI Text's _onRootCanvasModify(ReferenceResolutionPerUnit)), _updatePosition is skipped and _setDirtyFlagFalse(Font) clears all dirty bits at once. The next _render then uploads the residue pos to GPU — text glyphs jump to the wrong spot or appear missing after UI tab switches that free + reallocate chunk slots in the same frame.
  • Fix: force WorldPosition dirty at the end of _updateLocalData so the contract "after this call, pos must be rewritten" is unconditionally honored regardless of caller.

Test plan

  • _updateLocalData must leave WorldPosition dirty on exit (dirty-flag invariant)
  • bounds getter with only LocalPositionBounds dirty rewrites pos even when slot memory is poisoned (corrupted-slot)
  • Destroying a sibling renderer occupying a lower offset, then triggering bounds getter on the survivor, keeps the survivor's pos correct after the slot moves (full slot-reuse repro)
  • All three regression tests fail without this fix and pass with it

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Fixed text rendering issue where stale position data could occasionally appear in text elements, particularly when chunks are reused across updates.
  • Tests

    • Added regression tests for text rendering vertex buffer management to prevent future regressions.

…eLocalData

Both Text (UI) and TextRenderer share a `bounds` getter that runs
`_updateLocalData` then checks `WorldPosition` dirty. `_updateLocalData`
internally `_freeTextChunks` + `_buildChunk → allocateSubChunk`, which
under PrimitiveChunk's first-fit + free-list-merge allocator can return
a slot previously owned by another renderer. `_buildChunk` writes UV
and color but never pos (pos is `_updatePosition`'s job), so the new
slot retains the previous owner's pos floats as residue.

Before this fix, when a path sets only `LocalPositionBounds` dirty
(e.g. `Text._onRootCanvasModify(ReferenceResolutionPerUnit)` in UI
Text), the bounds getter would:
  1. see LocalPositionBounds → run _updateLocalData (slot may swap)
  2. see WorldPosition not dirty → skip _updatePosition
  3. _setDirtyFlagFalse(Font) clear all dirty bits at once
The next _render also sees clean dirty bits and uploads the residue
pos to GPU — the renderer ends up rendering at someone else's old
world position. In practice this manifested as text glyphs jumping
to the wrong spot or appearing missing after UI tab switches that
free + reallocate chunk slots in the same frame.

Fix: force WorldPosition dirty at the end of _updateLocalData so the
contract "after this call, pos must be rewritten" is unconditionally
honored regardless of which caller invoked it.

Tests cover three layers:
  - dirty-flag invariant: _updateLocalData must leave WorldPosition
    dirty on exit
  - corrupted-slot: bounds getter with only LocalPositionBounds dirty
    rewrites pos even when the slot memory is poisoned
  - full slot-reuse repro: destroy a sibling renderer occupying a
    lower offset, then trigger bounds getter on the survivor — its
    pos must remain correct after the slot moves

Without the fix, all three regression tests fail with the survivor
rendering at the destroyed sibling's old position.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Walkthrough

This PR adds explicit DirtyFlag.WorldPosition marking after chunk rebuilding in text renderers across core and UI packages. This ensures vertex position data is refreshed downstream, preventing stale position values from remaining in reused chunk memory after _buildChunk updates only UV/color fields.

Changes

Cohort / File(s) Summary
Text Renderer Logic
packages/core/src/2d/text/TextRenderer.ts, packages/ui/src/component/advanced/Text.ts
Added explicit DirtyFlag.WorldPosition marking after _buildChunk execution in _updateLocalData, ensuring subsequent _updatePosition() calls overwrite stale vertex position data.
Regression Tests
tests/src/core/2d/text/TextRenderer.test.ts, tests/src/ui/Text.test.ts
Added comprehensive test suites validating dirty-flag contracts: verifying WorldPosition remains dirty post-rebuild, confirming vertex positions are rewritten when bounds is accessed, and testing slot-reuse scenarios with corrupted vertex data.

Sequence Diagram

sequenceDiagram
    participant Caller as External Caller
    participant UpdateLocal as _updateLocalData()
    participant BuildChunk as _buildChunk()
    participant DirtyFlag as Dirty Flag State
    participant Bounds as bounds Getter
    participant UpdatePos as _updatePosition()

    Caller->>UpdateLocal: Call with cleared _dirtyFlag
    UpdateLocal->>BuildChunk: Execute (updates UV/Color only)
    BuildChunk-->>UpdateLocal: Complete
    Note over UpdateLocal,DirtyFlag: FIX: Mark WorldPosition as dirty
    UpdateLocal->>DirtyFlag: Set WorldPosition = dirty
    DirtyFlag-->>UpdateLocal: Confirmed
    UpdateLocal-->>Caller: Return
    
    Caller->>Bounds: Access bounds property
    Bounds->>DirtyFlag: Check WorldPosition dirty flag
    alt WorldPosition is dirty
        Bounds->>UpdatePos: Execute to rewrite vertex pos
        UpdatePos->>UpdatePos: Overwrite stale pos data
        UpdatePos-->>Bounds: Complete
    end
    Bounds-->>Caller: Return bounds value
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Positions were whispered, then lost to the void,
But now flags dance bright—no more to avoid!
Fresh vertices bloom where stale ones once lay,
Chunks reborn and gleaming throughout the display! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely identifies the primary fix: marking WorldPosition dirty after slot reallocation in _updateLocalData to prevent rendering stale position data.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.14%. Comparing base (a6f0504) to head (7e0a873).

Additional details and impacted files
@@             Coverage Diff             @@
##           dev/2.0    #2981      +/-   ##
===========================================
+ Coverage    78.04%   78.14%   +0.09%     
===========================================
  Files          906      906              
  Lines        99892    99902      +10     
  Branches     10190    10173      -17     
===========================================
+ Hits         77960    78067     +107     
+ Misses       21763    21665      -98     
- Partials       169      170       +1     
Flag Coverage Δ
unittests 78.14% <100.00%> (+0.09%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented Apr 30, 2026

🤖 Augment PR Summary

Summary: Fixes a text rendering edge case where vertex-position data can be left as “residue” after text chunk slot reallocation, causing glyphs to jump/misrender.

Changes:

  • In both core TextRenderer and UI Text, force DirtyFlag.WorldPosition after _updateLocalData rebuilds chunks so _updatePosition always rewrites vertex pos.
  • Documents the allocator/slot-reuse scenario in code comments to clarify why UV/color-only writes are insufficient.
  • Adds regression tests for TextRenderer and UI Text to enforce the dirty-flag invariant and validate the bounds-getter path rewrites positions even with “poisoned” slot memory.
  • Includes an end-to-end repro test where a sibling renderer is destroyed and the survivor reuses the freed slot without position corruption.

Technical Notes: The fix ensures callers that only mark LocalPositionBounds (e.g., root canvas resolution changes) still trigger a position rewrite before dirty flags are cleared in the bounds path.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

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

Review completed. 1 suggestion posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread tests/src/ui/Text.test.ts
*/
describe("Text - bounds-getter slot residue regression", async () => {
const canvas = document.createElement("canvas");
const engine = await WebGLEngine.create({ canvas });
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 30, 2026

Choose a reason for hiding this comment

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

tests/src/ui/Text.test.ts:170: This new regression suite creates a WebGLEngine but never calls engine.destroy(), and this file now creates two engines total. That can leak WebGL contexts across tests and make CI runs flaky due to context/resource exhaustion.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.14%. Comparing base (a6f0504) to head (7e0a873).

Additional details and impacted files
@@             Coverage Diff             @@
##           dev/2.0    #2981      +/-   ##
===========================================
+ Coverage    78.04%   78.14%   +0.09%     
===========================================
  Files          906      906              
  Lines        99892    99902      +10     
  Branches     10190    10173      -17     
===========================================
+ Hits         77960    78067     +107     
+ Misses       21763    21665      -98     
- Partials       169      170       +1     
Flag Coverage Δ
unittests 78.14% <100.00%> (+0.09%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
tests/src/core/2d/text/TextRenderer.test.ts (1)

401-403: 💤 Low value

Consider adding a note about maintaining sync with source enum.

These constants duplicate internal DirtyFlag enum values from TextRenderer.ts. If those values change, tests may silently pass/fail incorrectly.

Consider adding a comment noting this dependency, or alternatively importing/exporting the enum (if feasible for the project's architecture).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/src/core/2d/text/TextRenderer.test.ts` around lines 401 - 403, Tests
define TR_DIRTY_LOCAL_POSITION_BOUNDS and TR_DIRTY_WORLD_POSITION which
duplicate the internal DirtyFlag enum from TextRenderer; update the test to
either import/export the DirtyFlag enum from the TextRenderer module (preferred)
or add a clear comment above TR_DIRTY_LOCAL_POSITION_BOUNDS and
TR_DIRTY_WORLD_POSITION stating they must remain in sync with DirtyFlag in
TextRenderer and reference the enum names, so future changes to DirtyFlag will
be noticed and the test values updated accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/src/core/2d/text/TextRenderer.test.ts`:
- Around line 401-403: Tests define TR_DIRTY_LOCAL_POSITION_BOUNDS and
TR_DIRTY_WORLD_POSITION which duplicate the internal DirtyFlag enum from
TextRenderer; update the test to either import/export the DirtyFlag enum from
the TextRenderer module (preferred) or add a clear comment above
TR_DIRTY_LOCAL_POSITION_BOUNDS and TR_DIRTY_WORLD_POSITION stating they must
remain in sync with DirtyFlag in TextRenderer and reference the enum names, so
future changes to DirtyFlag will be noticed and the test values updated
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c1b44e90-72b6-4c6d-934c-7e0f6fd29443

📥 Commits

Reviewing files that changed from the base of the PR and between a6f0504 and 7e0a873.

📒 Files selected for processing (4)
  • packages/core/src/2d/text/TextRenderer.ts
  • packages/ui/src/component/advanced/Text.ts
  • tests/src/core/2d/text/TextRenderer.test.ts
  • tests/src/ui/Text.test.ts

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.

1 participant