fix: prevent TUI StackOverflowException from excessive log controls

The TUI log view used an ItemsControl with 5000 max entries and no UI
virtualization. Avalonia's composition renderer traverses the entire
visual tree on each frame -- with thousands of TextBlock controls, the
recursive Render/RenderCore calls exceed the thread stack size on
constrained devices (especially ARM where each stack frame is larger
due to ABI differences), causing a StackOverflowException in
ServerCompositionContainerVisual.Render.

Changes:
- Enable VirtualizingStackPanel on the log ItemsControl so Avalonia
  only creates visuals for the rows currently in the viewport.
- Add [Console.General] TUI_Log_Scrollback config option so users can
  control max log lines in TUI mode. Default is 0 (automatic: 3000 on
  x86/x64, 500 on ARM/ARM64).

Made-with: Cursor
This commit is contained in:
BruceChen 2026-04-03 03:01:15 +08:00
parent 2003786608
commit 512445cb1d
4 changed files with 41 additions and 1 deletions

View file

@ -1429,6 +1429,24 @@ namespace MinecraftClient {
}
}
/// <summary>
/// Looks up a localized string similar to Maximum number of input history records to keep..
/// </summary>
internal static string Console_General_History_Input_Records {
get {
return ResourceManager.GetString("Console.General.History_Input_Records", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Maximum log lines kept in TUI mode scrollback. Set to 0 for automatic (3000 on x86/x64, 500 on ARM)..
/// </summary>
internal static string Console_General_TUI_Log_Scrollback {
get {
return ResourceManager.GetString("Console.General.TUI_Log_Scrollback", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Startup Config File
///Please do not record extraneous data in this file as it will be overwritten by MCC.

View file

@ -587,6 +587,12 @@ Custom colors are only available when using "vt100_24bit" color mode.</value>
<data name="Console.General.Display_Input" xml:space="preserve">
<value>You can use "Ctrl+P" to print out the current input and cursor position.</value>
</data>
<data name="Console.General.History_Input_Records" xml:space="preserve">
<value>Maximum number of input history records to keep.</value>
</data>
<data name="Console.General.TUI_Log_Scrollback" xml:space="preserve">
<value>Maximum log lines kept in TUI mode scrollback. Set to 0 for automatic.</value>
</data>
<data name="Head" xml:space="preserve">
<value>Startup Config File
Please do not record extraneous data in this file as it will be overwritten by MCC.

View file

@ -1218,6 +1218,9 @@ namespace MinecraftClient
[TomlInlineComment("$Console.General.History_Input_Records$")]
public int History_Input_Records = 32;
[TomlInlineComment("$Console.General.TUI_Log_Scrollback$")]
public int TUI_Log_Scrollback = 0;
}
[TomlDoNotInlineObject]

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
@ -16,9 +17,20 @@ namespace MinecraftClient.Tui
{
public class MainTuiView : UserControl
{
private const int MaxLogLines = 5000;
private static readonly int MaxLogLines = ResolveMaxLogLines();
private const int CtrlCDoublePressMsec = 1500;
private static int ResolveMaxLogLines()
{
int configured = Settings.Config.Console.General.TUI_Log_Scrollback;
if (configured > 0)
return configured;
bool isArm = RuntimeInformation.ProcessArchitecture
is Architecture.Arm or Architecture.Arm64;
return isArm ? 500 : 3000;
}
private readonly ObservableCollection<string> _logLines = new();
private readonly ObservableCollection<Control> _logControls = new();
private readonly ItemsControl _logItemsControl;
@ -78,6 +90,7 @@ namespace MinecraftClient.Tui
{
ItemsSource = _logControls,
Focusable = false,
ItemsPanel = new FuncTemplate<Panel?>(() => new VirtualizingStackPanel()),
};
_logScrollViewer = new ScrollViewer