From c157e2074d7d354f71119aa2bf6659566b00aa86 Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Sun, 17 May 2026 20:04:21 +0900 Subject: [PATCH 1/2] [Tizen.NUI.Components] Remove temporary unrealizedItems list in Linear/GridLayouter Inline the unrealize check directly into VisibleItems.RemoveAll predicate in four scroll hot-path / NotifyItemRangeRemoved sites: - LinearLayouter.cs (RequestLayout, NotifyItemRangeRemoved) - GridLayouter.cs (RequestLayout, NotifyItemRangeRemoved) Before: allocated a temporary List per call and used RemoveAll(list.Contains) (O(N*M) linear scan on each predicate call). After: single O(N) RemoveAll pass with zero allocation per call. No public API change. Behavior preserved. Fixes #7608 --- .../RecyclerView/Layouter/GridLayouter.cs | 21 ++++++++---------- .../RecyclerView/Layouter/LinearLayouter.cs | 22 ++++++++----------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs index 0bdd97e9c8b..0424894e597 100755 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs @@ -416,17 +416,16 @@ public override void RequestLayout(float scrollPosition, bool force = false) //Console.WriteLine("[NUI] {0} :visibleArea before [{1},{2}] after [{3},{4}]", scrollPosition, prevFirstVisible, prevLastVisible, FirstVisible, LastVisible); // 2. Unrealize invisible items. - List unrealizedItems = new List(); - foreach (RecyclerViewItem item in VisibleItems) + VisibleItems.RemoveAll(item => { if (item.Index < FirstVisible || item.Index > LastVisible) { //Console.WriteLine("[NUI] Unrealize{0}!", item.Index); - unrealizedItems.Add(item); collectionView.UnrealizeItem(item); + return true; } - } - VisibleItems.RemoveAll(unrealizedItems.Contains); + return false; + }); //Console.WriteLine("Realize Begin [{0} to {1}]", FirstVisible, LastVisible); // 3. Realize and placing visible items. @@ -1029,22 +1028,20 @@ public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, else collectionView.ContentContainer.SizeHeight = ScrollContentSize; // 3. Update Visible Items. - List unrealizedItems = new List(); - foreach (RecyclerViewItem item in VisibleItems) + VisibleItems.RemoveAll(item => { if ((item.Index >= startIndex) && (item.Index < startIndex + count)) { - unrealizedItems.Add(item); collectionView.UnrealizeItem(item); + return true; } - else if (item.Index >= startIndex + count) + if (item.Index >= startIndex + count) { item.Index -= count; } - } - VisibleItems.RemoveAll(unrealizedItems.Contains); - unrealizedItems.Clear(); + return false; + }); // Position Adjust float scrollPosition = PrevScrollPosition; diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs index ff0a145625e..22f7fc29573 100755 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs @@ -419,17 +419,15 @@ public override void RequestLayout(float scrollPosition, bool force = false) LastVisible = end; // 2. Unrealize invisible items. - List unrealizedItems = new List(); - foreach (RecyclerViewItem item in VisibleItems) + VisibleItems.RemoveAll(item => { if (item.Index < FirstVisible || item.Index > LastVisible) { - unrealizedItems.Add(item); collectionView.UnrealizeItem(item); + return true; } - } - VisibleItems.RemoveAll(unrealizedItems.Contains); - unrealizedItems.Clear(); + return false; + }); // 3. Realize and placing visible items. for (int i = FirstVisible; i <= LastVisible; i++) @@ -1134,22 +1132,20 @@ public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, else collectionView.ContentContainer.SizeHeight = ScrollContentSize; // 4. Update Visible Items. - List unrealizedItems = new List(); - foreach (RecyclerViewItem item in VisibleItems) + VisibleItems.RemoveAll(item => { if ((item.Index >= startIndex) && (item.Index < startIndex + count)) { - unrealizedItems.Add(item); collectionView.UnrealizeItem(item); + return true; } - else if (item.Index >= startIndex + count) + if (item.Index >= startIndex + count) { item.Index -= count; } - } - VisibleItems.RemoveAll(unrealizedItems.Contains); - unrealizedItems.Clear(); + return false; + }); if (startIndex <= FirstVisible) { From 03a8955916e0dd6808b21adc0357fc60b6a51aa8 Mon Sep 17 00:00:00 2001 From: Jay Cho Date: Mon, 18 May 2026 21:04:19 +0900 Subject: [PATCH 2/2] Address review feedback Replace RemoveAll(lambda) with reverse for + RemoveAt in four Linear/GridLayouter sites to eliminate the per-call closure allocation noted by reviewers, fully realizing the PR's "zero per-call allocation" goal on the scroll hot path. Applied-Human-Comments: 3254514563,3254514566 Applied-AI-Comments: 3255636218 --- .../RecyclerView/Layouter/GridLayouter.cs | 18 +++++++++--------- .../RecyclerView/Layouter/LinearLayouter.cs | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs index 0424894e597..aa5f994c4ed 100755 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/GridLayouter.cs @@ -416,16 +416,16 @@ public override void RequestLayout(float scrollPosition, bool force = false) //Console.WriteLine("[NUI] {0} :visibleArea before [{1},{2}] after [{3},{4}]", scrollPosition, prevFirstVisible, prevLastVisible, FirstVisible, LastVisible); // 2. Unrealize invisible items. - VisibleItems.RemoveAll(item => + for (int i = VisibleItems.Count - 1; i >= 0; i--) { + RecyclerViewItem item = VisibleItems[i]; if (item.Index < FirstVisible || item.Index > LastVisible) { //Console.WriteLine("[NUI] Unrealize{0}!", item.Index); collectionView.UnrealizeItem(item); - return true; + VisibleItems.RemoveAt(i); } - return false; - }); + } //Console.WriteLine("Realize Begin [{0} to {1}]", FirstVisible, LastVisible); // 3. Realize and placing visible items. @@ -1028,20 +1028,20 @@ public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, else collectionView.ContentContainer.SizeHeight = ScrollContentSize; // 3. Update Visible Items. - VisibleItems.RemoveAll(item => + for (int i = VisibleItems.Count - 1; i >= 0; i--) { + RecyclerViewItem item = VisibleItems[i]; if ((item.Index >= startIndex) && (item.Index < startIndex + count)) { collectionView.UnrealizeItem(item); - return true; + VisibleItems.RemoveAt(i); } - if (item.Index >= startIndex + count) + else if (item.Index >= startIndex + count) { item.Index -= count; } - return false; - }); + } // Position Adjust float scrollPosition = PrevScrollPosition; diff --git a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs index 22f7fc29573..2c623167013 100755 --- a/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs +++ b/src/Tizen.NUI.Components/Controls/RecyclerView/Layouter/LinearLayouter.cs @@ -419,15 +419,15 @@ public override void RequestLayout(float scrollPosition, bool force = false) LastVisible = end; // 2. Unrealize invisible items. - VisibleItems.RemoveAll(item => + for (int i = VisibleItems.Count - 1; i >= 0; i--) { + RecyclerViewItem item = VisibleItems[i]; if (item.Index < FirstVisible || item.Index > LastVisible) { collectionView.UnrealizeItem(item); - return true; + VisibleItems.RemoveAt(i); } - return false; - }); + } // 3. Realize and placing visible items. for (int i = FirstVisible; i <= LastVisible; i++) @@ -1132,20 +1132,20 @@ public override void NotifyItemRangeRemoved(IItemSource source, int startIndex, else collectionView.ContentContainer.SizeHeight = ScrollContentSize; // 4. Update Visible Items. - VisibleItems.RemoveAll(item => + for (int i = VisibleItems.Count - 1; i >= 0; i--) { + RecyclerViewItem item = VisibleItems[i]; if ((item.Index >= startIndex) && (item.Index < startIndex + count)) { collectionView.UnrealizeItem(item); - return true; + VisibleItems.RemoveAt(i); } - if (item.Index >= startIndex + count) + else if (item.Index >= startIndex + count) { item.Index -= count; } - return false; - }); + } if (startIndex <= FirstVisible) {