Skip to content

Conversation

@InsanityGod
Copy link
Contributor

@InsanityGod InsanityGod commented Oct 10, 2025

throttle text update, this was the simplest way to do it but might want to look into options that allow for larger throttles.

Summary by CodeRabbit

  • Bug Fixes

    • Eliminates flicker and incorrect auto-height jumps during rapid typing by batching text-change updates and ensuring main-thread layout updates.
    • Adds error handling and reliable reset of update state to prevent intermittent failures during text edits.
  • Performance

    • Reduces redundant layout recalculations while editing, yielding smoother typing and improved responsiveness of auto-height text areas.

…nt to look into options that allow for larger throttles)
@coderabbitai
Copy link

coderabbitai bot commented Oct 10, 2025

Walkthrough

Adds a debounced two-step text-change flow to GuiElementTextArea: TextChanged now enqueues a main-thread task TextChangedBatched and sets a private debounce flag; TextChangedBatched recalculates auto-height/world bounds, calls base.TextChanged, and clears the flag.

Changes

Cohort / File(s) Summary
Debounced TextChanged handling
VTMLEditor/GuiElements/Vanilla/GuiElementTextArea.cs
Adds a private boolean textChangedBatchedQueued flag; changes TextChanged to enqueue a main-thread task TextChangedBatched instead of processing inline; implements TextChangedBatched to recalc auto-height bounds (if enabled), recalc world bounds, call base.TextChanged(), and reset the debounce flag inside a finally block with try/catch error logging.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant TA as GuiElementTextArea
    participant UI as MainThread
    participant Base as BaseGuiElement

    User->>TA: Text input
    TA->>TA: TextChanged()\n(check textChangedBatchedQueued)
    alt not queued
        TA->>TA: set textChangedBatchedQueued = true
        TA->>UI: enqueue TextChangedBatched()
    else queued
        TA-->>User: return (debounced)
    end

    UI->>TA: TextChangedBatched()
    rect rgba(230,245,255,0.5)
    note over TA: If Autoheight enabled\nupdate auto-height bounds
    TA->>TA: Recalculate world bounds
    TA->>Base: base.TextChanged()
    end
    TA->>TA: textChangedBatchedQueued = false (finally)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I twitch my whiskers at queued bytes,
A buffered hop through quiet nights.
Batched and tidy, bounds align,
No frantic loops—just orderly time.
Rabbit nods: the editor's fine. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 clearly summarizes the addition of throttling for the TextChanged event to reduce delays when typing quickly, which matches the core change in the PR. It's concise, specific, and free of unnecessary details, giving a clear indication of the primary functionality introduced.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95217d7 and 57dc3b2.

📒 Files selected for processing (1)
  • VTMLEditor/GuiElements/Vanilla/GuiElementTextArea.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • VTMLEditor/GuiElements/Vanilla/GuiElementTextArea.cs

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

@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

🧹 Nitpick comments (1)
VTMLEditor/GuiElements/Vanilla/GuiElementTextArea.cs (1)

38-38: Consider adding a comment to explain the debounce mechanism.

The field name textChangeDelayed doesn't fully convey the coalescing behavior where rapid text changes are batched together. A brief comment would help future maintainers understand the intent.

Example:

+    // Prevents redundant text processing by batching rapid updates
     private bool textChangeDelayed;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a726e9 and 5be29d0.

📒 Files selected for processing (1)
  • VTMLEditor/GuiElements/Vanilla/GuiElementTextArea.cs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
VTMLEditor/GuiElements/Vanilla/GuiElementTextArea.cs (2)
VTMLEditor/GuiElements/Vanilla/GuiElementEditableTextBase.cs (1)
  • TextChanged (552-557)
VTMLEditor/GuiElements/Vanilla/GuiElementTextBase.cs (1)
  • GetMultilineTextHeight (49-52)

Comment on lines 38 to 45
internal override void TextChanged()
{
if(textChangeDelayed) return;
textChangeDelayed = true;
api.Event.EnqueueMainThreadTask(TextChangedDelayed, "TextChangedDelayed");
}
Copy link

@coderabbitai coderabbitai bot Oct 10, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Critical: Missing exception handling could permanently disable text change processing.

If TextChangedDelayed() throws an exception before line 55 executes, the textChangeDelayed flag will remain true indefinitely, causing all subsequent text changes to be silently ignored. This would completely break the text area functionality.

Additionally, verify that TextChanged() is only called from the main thread. If multiple threads can invoke this method concurrently, the check-and-set pattern at lines 42-43 creates a race condition.

Apply this diff to add exception safety:

 internal override void TextChanged()
 {
     if(textChangeDelayed) return;
     textChangeDelayed = true;
-    api.Event.EnqueueMainThreadTask(TextChangedDelayed, "TextChangedDelayed");
+    try
+    {
+        api.Event.EnqueueMainThreadTask(TextChangedDelayed, "TextChangedDelayed");
+    }
+    catch
+    {
+        textChangeDelayed = false;
+        throw;
+    }
 }

And update TextChangedDelayed() with a try-finally block (see next comment).


🏁 Script executed:

#!/bin/bash
set -eux
# Find TextChangedDelayed implementation
rg -n "TextChangedDelayed" -C3 --type cs
# Find EnqueueMainThreadTask definition
rg -n "EnqueueMainThreadTask" -C3 --type cs

Length of output: 1488


🏁 Script executed:

#!/bin/bash
set -eux
sed -n '40,120p' VTMLEditor/GuiElements/Vanilla/GuiElementTextArea.cs

Length of output: 2082


Guard against exceptions in TextChanged workflows

  • Wrap api.Event.EnqueueMainThreadTask(TextChangedDelayed, "TextChangedDelayed") in a try/catch that resets textChangeDelayed on scheduling failure.
  • Encapsulate the entire body of TextChangedDelayed() in a try/finally to always clear textChangeDelayed, even if an exception occurs.
  • Confirm TextChanged() is only ever invoked on the main/UI thread to prevent races on textChangeDelayed.
🤖 Prompt for AI Agents
In VTMLEditor/GuiElements/Vanilla/GuiElementTextArea.cs around lines 40-45,
TextChanged currently sets textChangeDelayed and calls
api.Event.EnqueueMainThreadTask without handling failures and TextChangedDelayed
doesn't guarantee clearing the flag; wrap the EnqueueMainThreadTask call in a
try/catch that on any exception resets textChangeDelayed and rethrows or logs
the error, and modify TextChangedDelayed so its entire body is enclosed in
try/finally that sets textChangeDelayed = false in the finally block;
additionally add a short runtime assertion or check at the start of TextChanged
(e.g. verify current thread is the main/UI thread or throw/log) to ensure
TextChanged is only invoked on the UI thread to avoid races on
textChangeDelayed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I changed the order of operations (swap of boolean set and enqueue) but really if the enqueue fails the game would have long crashed (pretty vital/core method for VS)

Copy link

Choose a reason for hiding this comment

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

@InsanityGod, thank you for the clarification! I can see you've addressed the exception handling concern by:

  1. Swapping the order - Setting textChangedBatchedQueued after the enqueue call means that if EnqueueMainThreadTask throws, the flag won't be stuck in a true state
  2. Adding try-finally - The TextChangedBatched() method now ensures the flag is always reset in the finally block, even if exceptions occur during processing

This is a solid solution, and your point about EnqueueMainThreadTask being a core VS method makes sense - if it's failing, there are bigger problems to worry about. The current implementation looks good! 👍

…nThreadTask`, not that it really matters as you'd have far bigger issues if that fails)
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