Skip to content
This repository was archived by the owner on Jun 24, 2026. It is now read-only.

Commit 574581a

Browse files
tigCopilot
andcommitted
Improve OCTV streaming performance
- Coalesce Application.Invoke() calls: only one UI notification in-flight at a time, batching all rows added between dispatches - Skip data source copy when no filter is active (OnDataChanged points table directly at master source) - Filter(") returns this instead of copying all rows - Compile regex once per filter operation instead of per-cell - Cache ColumnNames array at construction time Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 5bbb433 commit 574581a

4 files changed

Lines changed: 109 additions & 99 deletions

File tree

src/Microsoft.PowerShell.ConsoleGuiTools/OutConsoleTableView.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ internal sealed class OutConsoleTableView : IDisposable
2626
private List<DataTableColumn>? _columns;
2727
private OutTableViewDataSource? _dataSource;
2828
private int _objectIndex;
29+
private int _pendingNotify; // 0 = no pending UI update, 1 = scheduled
2930
private HashSet<int>? _result;
3031
private Thread? _uiThread;
3132
private OutTableViewWindow? _window;
@@ -66,8 +67,14 @@ public void AddObject(PSObject psObject)
6667
_dataSource!.AddRow(row);
6768
}
6869

69-
// Notify UI thread of new data
70-
_app?.Invoke(() => _window?.OnDataChanged());
70+
// Coalesce UI notifications: only schedule one Invoke at a time.
71+
// When it fires it picks up ALL rows added since the last notification.
72+
if (Interlocked.CompareExchange(ref _pendingNotify, 1, 0) == 0)
73+
_app?.Invoke(() =>
74+
{
75+
Interlocked.Exchange(ref _pendingNotify, 0);
76+
_window?.OnDataChanged();
77+
});
7178
}
7279

7380
/// <summary>

src/Microsoft.PowerShell.ConsoleGuiTools/OutTableViewDataSource.cs

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.Linq;
66
using System.Text.RegularExpressions;
7+
using System.Threading;
78
using Microsoft.PowerShell.OutGridView.Models;
89
using Terminal.Gui.Views;
910

@@ -16,15 +17,16 @@ namespace Microsoft.PowerShell.ConsoleGuiTools;
1617
internal sealed class OutTableViewDataSource : ITableSource
1718
{
1819
private readonly List<DataTableColumn> _columns;
20+
private readonly Lock _lock = new();
1921
private readonly List<DataTableRow> _rows;
20-
private readonly object _lock = new();
2122

2223
/// <summary>
2324
/// Creates an empty data source with the specified columns.
2425
/// </summary>
2526
public OutTableViewDataSource(List<DataTableColumn> columns)
2627
{
2728
_columns = columns;
29+
ColumnNames = columns.Select(c => c.Label).ToArray();
2830
_rows = new List<DataTableRow>();
2931
}
3032

@@ -34,19 +36,26 @@ public OutTableViewDataSource(List<DataTableColumn> columns)
3436
public OutTableViewDataSource(List<DataTableColumn> columns, List<DataTableRow> rows)
3537
{
3638
_columns = columns;
39+
ColumnNames = columns.Select(c => c.Label).ToArray();
3740
_rows = new List<DataTableRow>(rows);
3841
}
3942

4043
/// <inheritdoc />
41-
public string[] ColumnNames => _columns.Select(c => c.Label).ToArray();
44+
public string[] ColumnNames { get; }
4245

4346
/// <inheritdoc />
4447
public int Columns => _columns.Count;
4548

4649
/// <inheritdoc />
4750
public int Rows
4851
{
49-
get { lock (_lock) return _rows.Count; }
52+
get
53+
{
54+
lock (_lock)
55+
{
56+
return _rows.Count;
57+
}
58+
}
5059
}
5160

5261
/// <inheritdoc />
@@ -73,64 +82,63 @@ public int Rows
7382
/// </summary>
7483
public void AddRow(DataTableRow row)
7584
{
76-
lock (_lock) _rows.Add(row);
85+
lock (_lock)
86+
{
87+
_rows.Add(row);
88+
}
7789
}
7890

7991
/// <summary>
8092
/// Gets the original object index for the specified row.
8193
/// </summary>
8294
public int GetOriginalObjectIndex(int row)
8395
{
84-
lock (_lock) return row >= 0 && row < _rows.Count ? _rows[row].OriginalObjectIndex : -1;
96+
lock (_lock)
97+
{
98+
return row >= 0 && row < _rows.Count ? _rows[row].OriginalObjectIndex : -1;
99+
}
85100
}
86101

87102
/// <summary>
88-
/// Gets a snapshot of all rows.
103+
/// Gets the column definitions.
89104
/// </summary>
90-
public List<DataTableRow> GetAllRows()
105+
public List<DataTableColumn> GetColumns()
91106
{
92-
lock (_lock) return new List<DataTableRow>(_rows);
107+
return _columns;
93108
}
94109

95-
/// <summary>
96-
/// Gets the column definitions.
97-
/// </summary>
98-
public List<DataTableColumn> GetColumns() => _columns;
99-
100110
/// <summary>
101111
/// Creates a new filtered data source containing only rows matching the regex pattern.
112+
/// Returns itself if the pattern is empty (no copy needed).
102113
/// </summary>
103114
public OutTableViewDataSource Filter(string pattern)
104115
{
105-
List<DataTableRow> allRows;
106-
lock (_lock) allRows = new List<DataTableRow>(_rows);
107-
108116
if (string.IsNullOrEmpty(pattern))
109-
return new OutTableViewDataSource(_columns, allRows);
117+
return this;
110118

111-
var result = allRows.Where(r => RowMatchesFilter(r, pattern)).ToList();
119+
var regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
112120

113-
return new OutTableViewDataSource(_columns, result);
114-
}
121+
List<DataTableRow> allRows;
122+
lock (_lock)
123+
{
124+
allRows = new List<DataTableRow>(_rows);
125+
}
115126

116-
/// <summary>
117-
/// Creates a data source from an existing <see cref="DataTable" />.
118-
/// </summary>
119-
public static OutTableViewDataSource FromDataTable(DataTable dataTable)
120-
{
121-
return new OutTableViewDataSource(dataTable.DataColumns, dataTable.Data);
127+
var result = allRows.Where(r => RowMatchesFilter(r, regex, _columns)).ToList();
128+
129+
return new OutTableViewDataSource(_columns, result);
122130
}
123131

124-
private bool RowMatchesFilter(DataTableRow row, string pattern)
132+
private static bool RowMatchesFilter(DataTableRow row, Regex regex, List<DataTableColumn> columns)
125133
{
126-
foreach (var column in _columns)
134+
foreach (var column in columns)
127135
{
128136
var columnKey = column.ToString();
129137
if (row.Values.TryGetValue(columnKey, out var value) &&
130-
Regex.IsMatch(value.DisplayValue, pattern, RegexOptions.IgnoreCase))
138+
regex.IsMatch(value.DisplayValue))
131139
return true;
132140
}
133141

134142
return false;
135143
}
136-
}
144+
}

src/Microsoft.PowerShell.ConsoleGuiTools/OutTableViewWindow.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,21 @@ protected override void OnIsRunningChanged(bool newIsRunning)
117117
/// </summary>
118118
public void OnDataChanged()
119119
{
120-
ApplyFilter();
120+
if (string.IsNullOrEmpty(_applicationData.Filter))
121+
{
122+
// No filter active — just point the table at the master source directly (no copy)
123+
if (_filteredDataSource != _masterDataSource)
124+
{
125+
_filteredDataSource = _masterDataSource;
126+
_tableView!.Table = _filteredDataSource;
127+
}
128+
_tableView?.Update();
129+
}
130+
else
131+
{
132+
ApplyFilter();
133+
}
134+
121135
if (_isLoading)
122136
_rowsShortcut.Text = $"{_masterDataSource.Rows} rows";
123137
}
@@ -214,7 +228,7 @@ private void ApplySearch()
214228
{
215229
for (var col = 0; col < _filteredDataSource.Columns; col++)
216230
{
217-
var cellValue = _filteredDataSource[row, col]?.ToString() ?? string.Empty;
231+
var cellValue = _filteredDataSource[row, col].ToString() ?? string.Empty;
218232
if (regex.IsMatch(cellValue))
219233
{
220234
_tableView.SetSelection(0, row, false);

0 commit comments

Comments
 (0)