attempt at auto-increasing pool size, not quite working properly

This commit is contained in:
Sinai 2021-04-21 21:04:26 +10:00
parent b32675e3b1
commit 0a9639f8a9
8 changed files with 157 additions and 101 deletions

View File

@ -86,8 +86,8 @@ namespace UnityExplorer.UI.Panels
var test = new DynamicListTest(scrollPool, this); var test = new DynamicListTest(scrollPool, this);
test.Init(); test.Init();
var prototype = DynamicCell.CreatePrototypeCell(scrollContent); //var prototype = DynamicCell.CreatePrototypeCell(scrollContent);
scrollPool.PrototypeCell = prototype.GetComponent<RectTransform>(); //scrollPool.PrototypeCell = prototype.GetComponent<RectTransform>();
dummyContentHolder = new GameObject("DummyHolder"); dummyContentHolder = new GameObject("DummyHolder");
dummyContentHolder.SetActive(false); dummyContentHolder.SetActive(false);
@ -170,9 +170,10 @@ namespace UnityExplorer.UI.Panels
public void Init() public void Init()
{ {
var prototype = DynamicCell.CreatePrototypeCell(Scroller.UIRoot);
Scroller.DataSource = this; Scroller.DataSource = this;
Scroller.Initialize(this); Scroller.Initialize(this, prototype);
} }
public ICell CreateCell(RectTransform cellTransform) => new DynamicCell(cellTransform.gameObject); public ICell CreateCell(RectTransform cellTransform) => new DynamicCell(cellTransform.gameObject);

View File

@ -223,6 +223,8 @@ namespace UnityExplorer.UI.Panels
refreshRow.SetActive(false); refreshRow.SetActive(false);
// Transform Tree // Transform Tree
//var prototype = TransformCell.CreatePrototypeCell(scrollContent);
var infiniteScroll = UIFactory.CreateScrollPool(content, "TransformTree", out GameObject scrollObj, var infiniteScroll = UIFactory.CreateScrollPool(content, "TransformTree", out GameObject scrollObj,
out GameObject scrollContent, new Color(0.15f, 0.15f, 0.15f)); out GameObject scrollContent, new Color(0.15f, 0.15f, 0.15f));
@ -230,13 +232,10 @@ namespace UnityExplorer.UI.Panels
UIFactory.SetLayoutElement(scrollContent, flexibleHeight: 9999); UIFactory.SetLayoutElement(scrollContent, flexibleHeight: 9999);
// Prototype tree cell // Prototype tree cell
var prototype = TransformCell.CreatePrototypeCell(scrollContent);
infiniteScroll.PrototypeCell = prototype.GetComponent<RectTransform>(); //infiniteScroll.PrototypeCell = prototype.GetComponent<RectTransform>();
Tree = new TransformTree(infiniteScroll) Tree = new TransformTree(infiniteScroll) { GetRootEntriesMethod = GetRootEntries };
{
GetRootEntriesMethod = GetRootEntries
};
Tree.Init(); Tree.Init();
// some references // some references

View File

@ -42,7 +42,7 @@ namespace UnityExplorer.UI.Widgets
uiRoot.SetActive(true); uiRoot.SetActive(true);
} }
public static GameObject CreatePrototypeCell(GameObject parent) public static RectTransform CreatePrototypeCell(GameObject parent)
{ {
var prototype = UIFactory.CreateHorizontalGroup(parent, "PrototypeCell", true, true, true, true, 2, default, var prototype = UIFactory.CreateHorizontalGroup(parent, "PrototypeCell", true, true, true, true, 2, default,
new Color(0.15f, 0.15f, 0.15f), TextAnchor.MiddleCenter); new Color(0.15f, 0.15f, 0.15f), TextAnchor.MiddleCenter);
@ -68,7 +68,7 @@ namespace UnityExplorer.UI.Widgets
prototype.SetActive(false); prototype.SetActive(false);
return prototype; return rect;
} }
} }
} }

View File

@ -49,9 +49,11 @@ namespace UnityExplorer.UI.Widgets
{ {
yield return null; yield return null;
var proto = ButtonCell<T>.CreatePrototypeCell(Scroller.UIRoot);
RefreshData(); RefreshData();
Scroller.DataSource = this; Scroller.DataSource = this;
Scroller.Initialize(this); Scroller.Initialize(this, proto);
} }
public void RefreshData() public void RefreshData()

View File

@ -12,6 +12,7 @@ namespace UnityExplorer.UI.Widgets
public DynamicCell(GameObject uiRoot) public DynamicCell(GameObject uiRoot)
{ {
this.uiRoot = uiRoot; this.uiRoot = uiRoot;
m_enabled = uiRoot.activeSelf;
} }
public bool Enabled => m_enabled; public bool Enabled => m_enabled;
@ -32,7 +33,7 @@ namespace UnityExplorer.UI.Widgets
uiRoot.SetActive(true); uiRoot.SetActive(true);
} }
public static GameObject CreatePrototypeCell(GameObject parent) public static RectTransform CreatePrototypeCell(GameObject parent)
{ {
var prototype = UIFactory.CreateVerticalGroup(parent, "PrototypeCell", true, true, true, true, 0, new Vector4(1, 0, 0, 0), var prototype = UIFactory.CreateVerticalGroup(parent, "PrototypeCell", true, true, true, true, 0, new Vector4(1, 0, 0, 0),
new Color(0.15f, 0.15f, 0.15f), TextAnchor.MiddleCenter); new Color(0.15f, 0.15f, 0.15f), TextAnchor.MiddleCenter);
@ -47,7 +48,7 @@ namespace UnityExplorer.UI.Widgets
prototype.SetActive(false); prototype.SetActive(false);
return prototype; return rect;
} }
} }
} }

View File

@ -9,20 +9,19 @@ using UnityExplorer.UI.Models;
namespace UnityExplorer.UI.Widgets namespace UnityExplorer.UI.Widgets
{ {
// TODO: there is possibly still a bug causing the content to jump around, sometimes observed when
// the pooled content height is extremely large (compared to viewport). maybe it depends on the
// top/bottom cells or something.
/// <summary> /// <summary>
/// An object-pooled ScrollRect, attempts to support content of any size and provide a scrollbar for it.<br/> /// An object-pooled ScrollRect, attempts to support content of any size and provide a scrollbar for it.</summary>
/// <br/>
/// IMPORTANT CAVEATS:<br/>
/// - A cell cannot be smaller than the Prototype cell's default height<br/>
/// - (maybe?) A cell must start at the default height and only increase after being displayed for the first time<br/>
/// </summary>
public class ScrollPool : UIBehaviourModel public class ScrollPool : UIBehaviourModel
{ {
// used to track and manage cell views // used to track and manage cell views
public class CachedCell public class CachedCell
{ {
public ScrollPool Pool { get; } public ScrollPool Pool { get; }
public RectTransform Rect { get; } public RectTransform Rect { get; internal set; }
public ICell Cell { get; } public ICell Cell { get; }
public CachedCell(ScrollPool pool, RectTransform rect, ICell cell) public CachedCell(ScrollPool pool, RectTransform rect, ICell cell)
@ -38,13 +37,15 @@ namespace UnityExplorer.UI.Widgets
this.scrollRect = scrollRect; this.scrollRect = scrollRect;
} }
public int ExtraPoolCells => 10;
public float ExtraPoolThreshold => PrototypeCell.rect.height * ExtraPoolCells;
public float HalfPoolThreshold => ExtraPoolThreshold * 0.5f;
public IPoolDataSource DataSource; public IPoolDataSource DataSource;
public RectTransform PrototypeCell; public RectTransform PrototypeCell;
private float PrototypeHeight => PrototypeCell.rect.height;
public int ExtraPoolCells => 10;
public float ExtraPoolThreshold => PrototypeHeight * ExtraPoolCells;
public float HalfPoolThreshold => ExtraPoolThreshold * 0.5f;
// UI // UI
public override GameObject UIRoot => scrollRect.gameObject; public override GameObject UIRoot => scrollRect.gameObject;
@ -81,7 +82,7 @@ namespace UnityExplorer.UI.Widgets
private int CurrentDataCount => bottomDataIndex + 1; private int CurrentDataCount => bottomDataIndex + 1;
private Vector2 _prevAnchoredPos; private Vector2 _prevAnchoredPos;
private Vector2 _prevViewportSize; // TODO track viewport height and add if height increased private float _prevViewportHeight; // TODO track viewport height and add if height increased
#region Internal set tracking and update #region Internal set tracking and update
@ -111,11 +112,17 @@ namespace UnityExplorer.UI.Widgets
public void Rebuild() public void Rebuild()
{ {
Initialize(DataSource); Initialize(DataSource, PrototypeCell);
} }
public void Initialize(IPoolDataSource dataSource) public void Initialize(IPoolDataSource dataSource, RectTransform prototypeCell)
{ {
if (!prototypeCell)
throw new Exception("No prototype cell set, cannot initialize");
this.PrototypeCell = prototypeCell;
PrototypeCell.transform.SetParent(Viewport, false);
HeightCache = new DataHeightManager(this); HeightCache = new DataHeightManager(this);
DataSource = dataSource; DataSource = dataSource;
@ -136,10 +143,9 @@ namespace UnityExplorer.UI.Widgets
yield return null; yield return null;
_prevAnchoredPos = scrollRect.content.anchoredPosition; _prevAnchoredPos = Content.anchoredPosition;
_prevViewportSize = new Vector2(scrollRect.viewport.rect.width, scrollRect.viewport.rect.height);
SetRecycleViewBounds(); SetRecycleViewBounds(false);
float start = Time.realtimeSinceStartup; float start = Time.realtimeSinceStartup;
CreateCellPool(); CreateCellPool();
@ -151,11 +157,114 @@ namespace UnityExplorer.UI.Widgets
scrollRect.onValueChanged.AddListener(OnValueChangedListener); scrollRect.onValueChanged.AddListener(OnValueChangedListener);
} }
private void SetRecycleViewBounds() // Cell pool
private void CreateCellPool()
{
if (CellPool.Any())
{
foreach (var cell in CellPool)
GameObject.Destroy(cell.Rect.gameObject);
CellPool.Clear();
}
float currentPoolCoverage = 0f;
float requiredCoverage = scrollRect.viewport.rect.height + ExtraPoolThreshold;// * ExtraPoolCoverageRatio;
topPoolCellIndex = 0;
bottomPoolIndex = -1;
// create cells until the Pool area is covered.
// use minimum default height so that maximum pool count is reached.
while (currentPoolCoverage <= requiredCoverage)
{
bottomPoolIndex++;
//Instantiate and add to Pool
RectTransform rect = GameObject.Instantiate(PrototypeCell.gameObject).GetComponent<RectTransform>();
rect.gameObject.SetActive(true);
rect.name = $"Cell_{CellPool.Count + 1}";
var cell = DataSource.CreateCell(rect);
CellPool.Add(new CachedCell(this, rect, cell));
rect.SetParent(scrollRect.content, false);
currentPoolCoverage += rect.rect.height;
}
bottomDataIndex = CellPool.Count - 1;
// after creating pool, set displayed cells.
for (int i = 0; i < CellPool.Count; i++)
{
var cell = CellPool[i];
SetCell(cell, i);
}
LayoutRebuilder.ForceRebuildLayoutImmediate(Content);
}
private void SetRecycleViewBounds(bool checkHeightGrow)
{ {
var extra = ExtraPoolThreshold; var extra = ExtraPoolThreshold;
extra *= 0.5f; extra *= 0.5f;
RecycleViewBounds = new Vector2(Viewport.MinY() + extra, Viewport.MaxY() - extra); RecycleViewBounds = new Vector2(Viewport.MinY() + extra, Viewport.MaxY() - extra);
if (checkHeightGrow && _prevViewportHeight < Viewport.rect.height && _prevViewportHeight != 0.0f)
RefillCellPool();
_prevViewportHeight = Viewport.rect.height;
}
private void RefillCellPool()
{
// TODO buggy for some reason, not quite right.
var requiredCoverage = Math.Abs(RecycleViewBounds.y - RecycleViewBounds.x);
var currentCoverage = CellPool.Count * PrototypeHeight;
if (currentCoverage < requiredCoverage)
{
//int cellsRequired = (int)Math.Ceiling((decimal)(requiredCoverage - currentCoverage) / (decimal)PrototypeHeight);
while (currentCoverage <= requiredCoverage)
{
//bottomPoolIndex++;
ExplorerCore.Log("Adding to end of pool");
//Instantiate and add to Pool
RectTransform rect = GameObject.Instantiate(PrototypeCell.gameObject).GetComponent<RectTransform>();
rect.gameObject.SetActive(true);
rect.name = $"Cell_{CellPool.Count + 1}";
rect.SetParent(scrollRect.content, false);
currentCoverage += rect.rect.height;
bottomDataIndex++;
}
CellPool.Clear();
int childCount = Content.childCount;
for (int i = 0; i < childCount; i++)
{
var rect = Content.GetChild(i).GetComponent<RectTransform>();
var cell = DataSource.CreateCell(rect);
CellPool.Add(new CachedCell(this, rect, cell));
ExplorerCore.Log("Assigned cell rect " + i);
}
// reassign cell references
topPoolCellIndex = 0;
bottomPoolIndex = CellPool.Count - 1;
// after creating pool, set displayed cells.
for (int i = 0; i < CellPool.Count; i++)
{
var cell = CellPool[i];
SetCell(cell, i);
}
}
} }
// Refresh methods // Refresh methods
@ -190,7 +299,7 @@ namespace UnityExplorer.UI.Widgets
// ExplorerCore.Log("RefreshCells | " + Time.time); // ExplorerCore.Log("RefreshCells | " + Time.time);
SetRecycleViewBounds(); SetRecycleViewBounds(true);
// jump to bottom if the data count went below our bottom data index // jump to bottom if the data count went below our bottom data index
bool jumpToBottom = false; bool jumpToBottom = false;
@ -205,7 +314,7 @@ namespace UnityExplorer.UI.Widgets
} }
if (HeightCache.Count < count) if (HeightCache.Count < count)
HeightCache.SetIndex(count - 1, PrototypeCell.rect.height); HeightCache.SetIndex(count - 1, PrototypeHeight);
else if (HeightCache.Count > count) else if (HeightCache.Count > count)
{ {
while (HeightCache.Count > count) while (HeightCache.Count > count)
@ -243,7 +352,7 @@ namespace UnityExplorer.UI.Widgets
} }
LayoutRebuilder.ForceRebuildLayoutImmediate(Content); LayoutRebuilder.ForceRebuildLayoutImmediate(Content);
SetRecycleViewBounds(); //SetRecycleViewBounds(false);
NormalizedScrollBounds = new Vector2(Viewport.rect.height * 0.5f, TotalDataHeight - (Viewport.rect.height * 0.5f)); NormalizedScrollBounds = new Vector2(Viewport.rect.height * 0.5f, TotalDataHeight - (Viewport.rect.height * 0.5f));
scrollRect.UpdatePrevData(); scrollRect.UpdatePrevData();
} }
@ -259,64 +368,6 @@ namespace UnityExplorer.UI.Widgets
HeightCache.SetIndex(dataIndex, cachedCell.Cell.Enabled ? cachedCell.Rect.rect.height : 0f); HeightCache.SetIndex(dataIndex, cachedCell.Cell.Enabled ? cachedCell.Rect.rect.height : 0f);
} }
// Cell pool
private void CreateCellPool()
{
if (CellPool.Any())
{
foreach (var cell in CellPool)
GameObject.Destroy(cell.Rect.gameObject);
CellPool.Clear();
}
if (!PrototypeCell)
throw new Exception("No prototype cell set, cannot initialize");
//Set the prototype cell active and set cell anchor as top
//PrototypeCell.gameObject.SetActive(true);
float currentPoolCoverage = 0f;
float requiredCoverage = scrollRect.viewport.rect.height + ExtraPoolThreshold;// * ExtraPoolCoverageRatio;
topPoolCellIndex = 0;
bottomPoolIndex = -1;
// create cells until the Pool area is covered.
// use minimum default height so that maximum pool count is reached.
while (currentPoolCoverage <= requiredCoverage)
{
bottomPoolIndex++;
//Instantiate and add to Pool
RectTransform rect = GameObject.Instantiate(PrototypeCell.gameObject).GetComponent<RectTransform>();
rect.gameObject.SetActive(true);
rect.name = $"Cell_{CellPool.Count + 1}";
var cell = DataSource.CreateCell(rect);
CellPool.Add(new CachedCell(this, rect, cell));
rect.SetParent(scrollRect.content, false);
//cell.Disable();
currentPoolCoverage += rect.rect.height;
}
bottomDataIndex = CellPool.Count - 1;
// after creating pool, set displayed cells.
for (int i = 0; i < CellPool.Count; i++)
{
var cell = CellPool[i];
SetCell(cell, i);
}
LayoutRebuilder.ForceRebuildLayoutImmediate(Content);
//Deactivate prototype cell if it is not a prefab(i.e it's present in scene)
if (PrototypeCell.gameObject.scene.IsValid())
PrototypeCell.gameObject.SetActive(false);
}
// Value change processor // Value change processor
private void OnValueChangedListener(Vector2 val) private void OnValueChangedListener(Vector2 val)
@ -326,7 +377,7 @@ namespace UnityExplorer.UI.Widgets
//ExplorerCore.Log("ScrollRect.OnValueChanged | " + Time.time + ", val: " + val.y.ToString("F5")); //ExplorerCore.Log("ScrollRect.OnValueChanged | " + Time.time + ", val: " + val.y.ToString("F5"));
SetRecycleViewBounds(); SetRecycleViewBounds(true);
RefreshCells(); RefreshCells();
float yChange = (scrollRect.content.anchoredPosition - _prevAnchoredPos).y; float yChange = (scrollRect.content.anchoredPosition - _prevAnchoredPos).y;
@ -356,11 +407,11 @@ namespace UnityExplorer.UI.Widgets
UpdateSliderHandle(); UpdateSliderHandle();
} }
private bool ShouldRecycleTop => GetCellExtent(CellPool[topPoolCellIndex]) >= RecycleViewBounds.x private bool ShouldRecycleTop => GetCellExtent(CellPool[topPoolCellIndex]) >= RecycleViewBounds.x;
&& CellPool[bottomPoolIndex].Rect.position.y < Viewport.MaxY(); //&& CellPool[bottomPoolIndex].Rect.position.y < Viewport.MaxY();
private bool ShouldRecycleBottom => CellPool[bottomPoolIndex].Rect.position.y < RecycleViewBounds.y private bool ShouldRecycleBottom => CellPool[bottomPoolIndex].Rect.position.y < RecycleViewBounds.y;
&& GetCellExtent(CellPool[topPoolCellIndex]) < Viewport.MinY(); //&& GetCellExtent(CellPool[topPoolCellIndex]) < Viewport.MinY();
private float GetCellExtent(CachedCell cell) => cell.Rect.MaxY() - contentLayout.spacing; private float GetCellExtent(CachedCell cell) => cell.Rect.MaxY() - contentLayout.spacing;

View File

@ -103,7 +103,7 @@ namespace UnityExplorer.UI.Widgets
ExplorerCore.LogWarning("The object was destroyed!"); ExplorerCore.LogWarning("The object was destroyed!");
} }
public static GameObject CreatePrototypeCell(GameObject parent) public static RectTransform CreatePrototypeCell(GameObject parent)
{ {
var prototype = UIFactory.CreateHorizontalGroup(parent, "PrototypeCell", true, true, true, true, 2, default, var prototype = UIFactory.CreateHorizontalGroup(parent, "PrototypeCell", true, true, true, true, 2, default,
new Color(0.15f, 0.15f, 0.15f), TextAnchor.MiddleCenter); new Color(0.15f, 0.15f, 0.15f), TextAnchor.MiddleCenter);
@ -136,7 +136,7 @@ namespace UnityExplorer.UI.Widgets
prototype.SetActive(false); prototype.SetActive(false);
return prototype; return rect;
} }
} }
} }

View File

@ -70,8 +70,10 @@ namespace UnityExplorer.UI.Widgets
{ {
yield return null; yield return null;
var prototype = TransformCell.CreatePrototypeCell(Scroller.UIRoot);
RefreshData(); RefreshData();
Scroller.Initialize(this); Scroller.Initialize(this, prototype);
} }
public ICell CreateCell(RectTransform cellTransform) public ICell CreateCell(RectTransform cellTransform)