Greatly improve performance when many hidden panels are on-screen

This commit is contained in:
Dean Herbert
2017-12-17 05:53:13 +09:00
parent 5d7413f19c
commit 54cc6fadf9
3 changed files with 44 additions and 39 deletions

View File

@ -44,7 +44,7 @@ namespace osu.Game.Screens.Select
/// </summary> /// </summary>
public BeatmapSetInfo SelectedBeatmapSet => selectedBeatmapSet?.BeatmapSet; public BeatmapSetInfo SelectedBeatmapSet => selectedBeatmapSet?.BeatmapSet;
private CarouselBeatmapSet selectedBeatmapSet => beatmapSets.FirstOrDefault(s => s.State == CarouselItemState.Selected); private CarouselBeatmapSet selectedBeatmapSet;
/// <summary> /// <summary>
/// Raised when the <see cref="SelectedBeatmap"/> is changed. /// Raised when the <see cref="SelectedBeatmap"/> is changed.
@ -73,26 +73,14 @@ namespace osu.Game.Screens.Select
Schedule(() => Schedule(() =>
{ {
root = newRoot; root = newRoot;
updateItems(); scrollableContent.Clear(false);
yPositionsCache.Invalidate();
BeatmapSetsChanged?.Invoke(); BeatmapSetsChanged?.Invoke();
}); });
}); });
} }
} }
/// <summary>
/// Call after altering <see cref="BeatmapSets"/> in any way.
/// </summary>
private void updateItems()
{
scrollableContent.Clear(false);
Items = root.Drawables.ToList();
yPositionsCache.Invalidate();
if (root.Children == null || root.Children.All(c => c.Filtered))
SelectionChanged?.Invoke(null);
}
private readonly List<float> yPositions = new List<float>(); private readonly List<float> yPositions = new List<float>();
private Cached yPositionsCache = new Cached(); private Cached yPositionsCache = new Cached();
@ -126,7 +114,7 @@ namespace osu.Game.Screens.Select
return; return;
root.RemoveChild(existingSet); root.RemoveChild(existingSet);
updateItems(); yPositionsCache.Invalidate();
} }
public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet)
@ -142,7 +130,7 @@ namespace osu.Game.Screens.Select
if (newSet == null) if (newSet == null)
{ {
updateItems(); yPositionsCache.Invalidate();
SelectNext(); SelectNext();
return; return;
} }
@ -155,7 +143,7 @@ namespace osu.Game.Screens.Select
if (hadSelection) if (hadSelection)
select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID) ?? newSet); select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID) ?? newSet);
updateItems(); yPositionsCache.Invalidate();
} }
public void SelectBeatmap(BeatmapInfo beatmap) public void SelectBeatmap(BeatmapInfo beatmap)
@ -200,8 +188,10 @@ namespace osu.Game.Screens.Select
select(beatmap); select(beatmap);
return; return;
case CarouselBeatmapSet set: case CarouselBeatmapSet set:
if (!skipDifficulties) continue; if (skipDifficulties)
select(set); select(set);
else
select(direction > 0 ? set.Beatmaps.First() : set.Beatmaps.Last());
return; return;
} }
} }
@ -209,10 +199,7 @@ namespace osu.Game.Screens.Select
public void SelectNextRandom() public void SelectNextRandom()
{ {
if (!beatmapSets.Any()) var visible = beatmapSets.Where(s => !s.Filtered).ToList();
return;
var visible = beatmapSets.Where(select => !select.Filtered).ToList();
if (!visible.Any()) if (!visible.Any())
return; return;
@ -291,7 +278,7 @@ namespace osu.Game.Screens.Select
FilterTask = null; FilterTask = null;
root.Filter(activeCriteria); root.Filter(activeCriteria);
updateItems(); yPositionsCache.Invalidate();
if (scroll) ScrollToSelected(false); if (scroll) ScrollToSelected(false);
} }
@ -329,6 +316,7 @@ namespace osu.Game.Screens.Select
{ {
if (v == CarouselItemState.Selected) if (v == CarouselItemState.Selected)
{ {
selectedBeatmapSet = set;
SelectionChanged?.Invoke(c.Beatmap); SelectionChanged?.Invoke(c.Beatmap);
yPositionsCache.Invalidate(); yPositionsCache.Invalidate();
Schedule(() => ScrollToSelected()); Schedule(() => ScrollToSelected());
@ -410,6 +398,12 @@ namespace osu.Game.Screens.Select
yPositionsCache.Validate(); yPositionsCache.Validate();
} }
private void noSelection()
{
if (root.Children == null || root.Children.All(c => c.Filtered))
SelectionChanged?.Invoke(null);
}
private void select(CarouselItem item) private void select(CarouselItem item)
{ {
if (item == null) return; if (item == null) return;
@ -450,11 +444,18 @@ namespace osu.Game.Screens.Select
{ {
base.Update(); base.Update();
float drawHeight = DrawHeight; // todo: scheduled scrolls...
if (!yPositionsCache.IsValid) if (!yPositionsCache.IsValid)
{
Items = root.Drawables.ToList();
computeYPositions(); computeYPositions();
if (selectedBeatmapSet != null && beatmapSets.All(s => s.Filtered))
SelectionChanged?.Invoke(null);
}
float drawHeight = DrawHeight;
// Remove all items that should no longer be on-screen // Remove all items that should no longer be on-screen
scrollableContent.RemoveAll(delegate (DrawableCarouselItem p) scrollableContent.RemoveAll(delegate (DrawableCarouselItem p)
{ {
@ -469,21 +470,21 @@ namespace osu.Game.Screens.Select
int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT); int firstIndex = yPositions.BinarySearch(Current - DrawableCarouselItem.MAX_HEIGHT);
if (firstIndex < 0) firstIndex = ~firstIndex; if (firstIndex < 0) firstIndex = ~firstIndex;
int lastIndex = yPositions.BinarySearch(Current + drawHeight); int lastIndex = yPositions.BinarySearch(Current + drawHeight);
if (lastIndex < 0) if (lastIndex < 0) lastIndex = ~lastIndex;
{
lastIndex = ~lastIndex;
// Add the first item of the last visible beatmap group to preload its data. int notVisibleCount = 0;
if (lastIndex != 0 && Items[lastIndex - 1] is DrawableCarouselBeatmapSet)
lastIndex++;
}
// Add those items within the previously found index range that should be displayed. // Add those items within the previously found index range that should be displayed.
for (int i = firstIndex; i < lastIndex; ++i) for (int i = firstIndex; i < lastIndex; ++i)
{ {
DrawableCarouselItem item = Items[i]; DrawableCarouselItem item = Items[i];
if (!item.Item.Visible) continue; if (!item.Item.Visible)
{
if (!item.IsPresent)
notVisibleCount++;
continue;
}
// Only add if we're not already part of the content. // Only add if we're not already part of the content.
if (!scrollableContent.Contains(item)) if (!scrollableContent.Contains(item))
@ -506,6 +507,10 @@ namespace osu.Game.Screens.Select
} }
} }
// this is not actually useful right now, but once we have groups may well be.
if (notVisibleCount > 50)
yPositionsCache.Invalidate();
// Update externally controlled state of currently visible items // Update externally controlled state of currently visible items
// (e.g. x-offset and opacity). // (e.g. x-offset and opacity).
float halfHeight = drawHeight / 2; float halfHeight = drawHeight / 2;

View File

@ -67,6 +67,7 @@ namespace osu.Game.Screens.Select.Carousel
updateSelected(item); updateSelected(item);
break; break;
case CarouselItemState.NotSelected: case CarouselItemState.NotSelected:
case CarouselItemState.Collapsed:
attemptSelection(); attemptSelection();
break; break;
} }

View File

@ -29,12 +29,11 @@ namespace osu.Game.Screens.Select.Carousel
List<DrawableCarouselItem> items = new List<DrawableCarouselItem>(); List<DrawableCarouselItem> items = new List<DrawableCarouselItem>();
var self = drawableRepresentation.Value; var self = drawableRepresentation.Value;
if (self != null) items.Add(self); if (self?.IsPresent == true) items.Add(self);
if (InternalChildren != null) if (InternalChildren != null)
foreach (var c in InternalChildren) foreach (var c in InternalChildren)
// if (!c.Filtered) <- potential optimisation at the cost of no fade out animations. items.AddRange(c.Drawables);
items.AddRange(c.Drawables);
return items; return items;
} }
@ -48,7 +47,7 @@ namespace osu.Game.Screens.Select.Carousel
// it's important we do the deselection after removing, so any further actions based on // it's important we do the deselection after removing, so any further actions based on
// State.ValueChanged make decisions post-removal. // State.ValueChanged make decisions post-removal.
if (i.State.Value == CarouselItemState.Selected) i.State.Value = CarouselItemState.NotSelected; i.State.Value = CarouselItemState.Collapsed;
} }
protected CarouselItem() protected CarouselItem()