Compare commits
1
Commits
thr_dev/dev2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b132e8e167 |
@@ -47,6 +47,14 @@ namespace XericUI.XTable.Core
|
||||
[Tooltip("预制体池允许创建的实例总上限")]
|
||||
public int PrefabPoolMaxCapacity = 256;
|
||||
|
||||
[Min(1)]
|
||||
[Tooltip("文本池 cellId 路由桶数量")]
|
||||
public int TextPoolRouteBucketCount = 256;
|
||||
|
||||
[Min(1)]
|
||||
[Tooltip("图片池 cellId 路由桶数量")]
|
||||
public int ImagePoolRouteBucketCount = 256;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 样式回退值
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
private readonly Transform m_Parent;
|
||||
private readonly Dictionary<(int, int), Tracked> m_Tracked = new();
|
||||
private readonly HashSet<(int, int)> m_Rendered = new();
|
||||
private readonly HashSet<(int, int)> m_Target = new();
|
||||
private readonly Dictionary<Texture2D, Sprite> m_SpriteCache = new();
|
||||
private GameObject m_BackgroundGo;
|
||||
|
||||
@@ -90,27 +91,51 @@ namespace XericUI.XTable.Rendering.Component
|
||||
sc = Mathf.Max(sc, StartCol);
|
||||
er = Mathf.Min(er, EndRow > 0 ? EndRow : rows.Length);
|
||||
ec = Mathf.Min(ec, EndCol > 0 ? EndCol : cols.Length);
|
||||
if (sr >= er || sc >= ec) return;
|
||||
|
||||
var target = new HashSet<(int, int)>();
|
||||
m_Target.Clear();
|
||||
if (sr >= er || sc >= ec)
|
||||
{
|
||||
ReleaseAll();
|
||||
return;
|
||||
}
|
||||
for (int r = sr; r < er; r++)
|
||||
for (int c = sc; c < ec; c++)
|
||||
target.Add((r, c));
|
||||
m_Target.Add((r, c));
|
||||
|
||||
foreach (var cell in target)
|
||||
foreach (var cell in m_Rendered)
|
||||
if (!m_Target.Contains(cell))
|
||||
ReleaseCell(cell.Item1, cell.Item2);
|
||||
|
||||
foreach (var cell in m_Target)
|
||||
if (fullRefresh || !m_Rendered.Contains(cell))
|
||||
RenderCell(cell.Item1, cell.Item2, rows, cols, editorMode);
|
||||
|
||||
m_Rendered.Clear();
|
||||
foreach (var cell in target) m_Rendered.Add(cell);
|
||||
foreach (var cell in m_Target) m_Rendered.Add(cell);
|
||||
}
|
||||
|
||||
public void ReleaseAll()
|
||||
{
|
||||
foreach (var pair in m_Tracked)
|
||||
ReleaseTracked(pair.Value);
|
||||
foreach (var cell in m_Rendered)
|
||||
ReleaseCell(cell.Item1, cell.Item2);
|
||||
m_Tracked.Clear();
|
||||
m_Rendered.Clear();
|
||||
m_Target.Clear();
|
||||
}
|
||||
|
||||
private void ReleaseCell(int row, int col)
|
||||
{
|
||||
var key = (row, col);
|
||||
if (m_Tracked.TryGetValue(key, out var tracked))
|
||||
{
|
||||
ReleaseTracked(tracked);
|
||||
m_Tracked.Remove(key);
|
||||
}
|
||||
if (ActivePrefabMap != null && ActivePrefabMap.TryGetValue(key, out var instance))
|
||||
{
|
||||
PrefabPool?.Release(instance, GetTableData?.Invoke()?.GetCell(row, col)?.OnPrefabRelease);
|
||||
ActivePrefabMap.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseTracked(Tracked tracked)
|
||||
@@ -136,6 +161,8 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
|
||||
int cellId = row * cols.Length + col;
|
||||
var key = (row, col);
|
||||
m_Tracked.TryGetValue(key, out var previousTracked);
|
||||
var tracked = new Tracked();
|
||||
var cell = data?.GetCell(row, col);
|
||||
if (cell != null)
|
||||
@@ -170,10 +197,16 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
else if (cell.ContentType == CellContentType.Prefab && cell.Prefab != null && PrefabPool != null)
|
||||
{
|
||||
var key = (row, col);
|
||||
key = (row, col);
|
||||
GameObject activePrefab = null;
|
||||
bool wasActive = ActivePrefabMap != null
|
||||
&& ActivePrefabMap.TryGetValue(key, out activePrefab);
|
||||
if (wasActive && !PrefabPool.IsInstanceOfPrefab(activePrefab, cell.Prefab))
|
||||
{
|
||||
PrefabPool.Release(activePrefab, cell.OnPrefabRelease);
|
||||
ActivePrefabMap.Remove(key);
|
||||
wasActive = false;
|
||||
}
|
||||
var prefab = wasActive ? activePrefab : PrefabPool.Acquire(cell.Prefab, cellId, m_Parent);
|
||||
if (prefab != null)
|
||||
{
|
||||
@@ -187,7 +220,15 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
}
|
||||
}
|
||||
m_Tracked[(row, col)] = tracked;
|
||||
if (previousTracked.Text != null && previousTracked.Text != tracked.Text)
|
||||
Assembler.ReleaseText(previousTracked.Text);
|
||||
if (previousTracked.Image != null && previousTracked.Image != tracked.Image)
|
||||
Assembler.ReleaseImage(previousTracked.Image);
|
||||
|
||||
if (tracked.Text != null || tracked.Image != null)
|
||||
m_Tracked[key] = tracked;
|
||||
else
|
||||
m_Tracked.Remove(key);
|
||||
}
|
||||
|
||||
private Sprite GetSprite(Texture2D texture)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
using XericUI.XTable.Rendering.Elements;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
|
||||
namespace XericUI.XTable.Rendering.Component
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
#region 增量渲染追踪
|
||||
|
||||
[NonSerialized] private HashSet<(int, int)> m_LastRenderedCells = new();
|
||||
[NonSerialized] private HashSet<(int, int)> m_VisibleCellsScratch = new();
|
||||
[NonSerialized] private Dictionary<(int, int), CellTrackedObjs> m_CellTracked = new();
|
||||
|
||||
/// <summary>可复用列表,避免每帧分配 GC</summary>
|
||||
@@ -40,10 +41,21 @@ namespace XericUI.XTable.Rendering.Component
|
||||
private void ReleaseTrackedCell(int row, int col)
|
||||
{
|
||||
var key = (row, col);
|
||||
if (!m_CellTracked.TryGetValue(key, out var objs)) return;
|
||||
m_Assembler?.ReleaseText(objs.CellText);
|
||||
m_Assembler?.ReleaseImage(objs.CellImage);
|
||||
m_CellTracked.Remove(key);
|
||||
if (m_CellTracked.TryGetValue(key, out var objs))
|
||||
{
|
||||
m_Assembler?.ReleaseText(objs.CellText);
|
||||
m_Assembler?.ReleaseImage(objs.CellImage);
|
||||
m_CellTracked.Remove(key);
|
||||
}
|
||||
ReleasePrefabCell(row, col);
|
||||
}
|
||||
|
||||
private void ReleasePrefabCell(int row, int col)
|
||||
{
|
||||
var key = (row, col);
|
||||
if (m_ActivePrefabMap == null || !m_ActivePrefabMap.TryGetValue(key, out var instance)) return;
|
||||
m_PrefabPool?.Release(instance, TableData.GetCell(row, col)?.OnPrefabRelease);
|
||||
m_ActivePrefabMap.Remove(key);
|
||||
}
|
||||
|
||||
private void ReleaseAllTrackedCells()
|
||||
@@ -148,6 +160,8 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
|
||||
int cellId = row * m_ColWidths.Length + col;
|
||||
var key = (row, col);
|
||||
m_CellTracked.TryGetValue(key, out var previousObjs);
|
||||
var objs = new CellTrackedObjs();
|
||||
|
||||
// ── 单元格内容 ──
|
||||
@@ -167,8 +181,16 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
}
|
||||
|
||||
m_CellTracked[(row, col)] = objs;
|
||||
m_LastRenderedCells.Add((row, col));
|
||||
if (previousObjs.CellText != null && previousObjs.CellText != objs.CellText)
|
||||
m_Assembler.ReleaseText(previousObjs.CellText);
|
||||
if (previousObjs.CellImage != null && previousObjs.CellImage != objs.CellImage)
|
||||
m_Assembler.ReleaseImage(previousObjs.CellImage);
|
||||
|
||||
if (objs.CellText != null || objs.CellImage != null)
|
||||
m_CellTracked[key] = objs;
|
||||
else
|
||||
m_CellTracked.Remove(key);
|
||||
m_LastRenderedCells.Add(key);
|
||||
}
|
||||
|
||||
private void RenderCellText(int cellId, XTableCellData data,
|
||||
@@ -219,6 +241,12 @@ namespace XericUI.XTable.Rendering.Component
|
||||
GameObject instance = null;
|
||||
bool wasActive = m_ActivePrefabMap != null
|
||||
&& m_ActivePrefabMap.TryGetValue(key, out instance);
|
||||
if (wasActive && !m_PrefabPool.IsInstanceOfPrefab(instance, data.Prefab))
|
||||
{
|
||||
m_PrefabPool.Release(instance, TableData.GetCell(row, col)?.OnPrefabRelease);
|
||||
m_ActivePrefabMap.Remove(key);
|
||||
wasActive = false;
|
||||
}
|
||||
if (!wasActive)
|
||||
instance = m_PrefabPool.Acquire(data.Prefab, cellId);
|
||||
if (instance == null) return;
|
||||
@@ -262,7 +290,8 @@ namespace XericUI.XTable.Rendering.Component
|
||||
{
|
||||
if (rectTransform != null)
|
||||
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace,
|
||||
Config.TextPoolMaxCapacity, Config.ImagePoolMaxCapacity);
|
||||
Config.TextPoolMaxCapacity, Config.ImagePoolMaxCapacity,
|
||||
Config.TextPoolRouteBucketCount, Config.ImagePoolRouteBucketCount);
|
||||
else return;
|
||||
}
|
||||
|
||||
@@ -293,16 +322,20 @@ namespace XericUI.XTable.Rendering.Component
|
||||
// 清理孤立子对象
|
||||
CleanOrphanChildren();
|
||||
|
||||
// 渲染所有可见单元格
|
||||
// 同步主区可见集合:只释放不再可见的绑定,保留仍可见单元格的路由归属。
|
||||
#if UNITY_EDITOR
|
||||
bool editorMode = !Application.isPlaying;
|
||||
#else
|
||||
bool editorMode = false;
|
||||
#endif
|
||||
string ns = m_DefaultStyleNamespace;
|
||||
ComputeVisibleCellsInto(m_LastRenderedCells);
|
||||
foreach (var (r, c) in m_LastRenderedCells)
|
||||
RenderCellContent(r, c, ns, editorMode);
|
||||
ComputeVisibleCellsInto(m_VisibleCellsScratch);
|
||||
foreach (var cell in m_LastRenderedCells)
|
||||
if (!m_VisibleCellsScratch.Contains(cell))
|
||||
ReleaseTrackedCell(cell.Item1, cell.Item2);
|
||||
foreach (var cell in m_VisibleCellsScratch)
|
||||
RenderCellContent(cell.Item1, cell.Item2, ns, editorMode);
|
||||
(m_LastRenderedCells, m_VisibleCellsScratch) = (m_VisibleCellsScratch, m_LastRenderedCells);
|
||||
|
||||
// 主 Content 已先渲染,冻结区随后复用其释放的对象;边框按区域最后提交。
|
||||
RenderFrozenCells(true);
|
||||
@@ -324,7 +357,8 @@ namespace XericUI.XTable.Rendering.Component
|
||||
{
|
||||
if (rectTransform != null)
|
||||
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace,
|
||||
Config.TextPoolMaxCapacity, Config.ImagePoolMaxCapacity);
|
||||
Config.TextPoolMaxCapacity, Config.ImagePoolMaxCapacity,
|
||||
Config.TextPoolRouteBucketCount, Config.ImagePoolRouteBucketCount);
|
||||
else return;
|
||||
}
|
||||
|
||||
@@ -347,24 +381,24 @@ namespace XericUI.XTable.Rendering.Component
|
||||
|
||||
// 选区由固定 Overlay 绘制,选择变化无需回收或重绘单元格内容。
|
||||
|
||||
// 1. 计算新一帧的可见单元格(滚动路径用简单范围遍历,避免四叉树/分配开销)
|
||||
var newVisible = new HashSet<(int, int)>();
|
||||
ComputeVisibleCellsScrollInto(newVisible);
|
||||
// 1. 计算新一帧的可见单元格(滚动路径用简单范围遍历)。
|
||||
ComputeVisibleCellsScrollInto(m_VisibleCellsScratch);
|
||||
|
||||
// 2. 离屏单元格保持其绑定,供后续重绘直接复用。
|
||||
// 2. 先释放离屏单元格的 tracked 槽位与预制体实例;Counter 仅在全部槽位释放后整体归还。
|
||||
foreach (var cell in m_LastRenderedCells)
|
||||
if (!m_VisibleCellsScratch.Contains(cell))
|
||||
ReleaseTrackedCell(cell.Item1, cell.Item2);
|
||||
|
||||
// 3. 渲染单元格:选区变更时全量重渲所有可见单元格,否则仅新进入的
|
||||
foreach (var cell in newVisible)
|
||||
{
|
||||
// 3. 仅渲染新进入的单元格。
|
||||
foreach (var cell in m_VisibleCellsScratch)
|
||||
if (!m_LastRenderedCells.Contains(cell))
|
||||
RenderCellContent(cell.Item1, cell.Item2, ns, editorMode);
|
||||
}
|
||||
|
||||
// 冻结列、冻结行与左上角在 Content 后更新,并复用同一全局对象池。
|
||||
RenderFrozenCells(false);
|
||||
|
||||
// 5. 交换:将 newVisible 作为下一帧的比较基准
|
||||
(m_LastRenderedCells, newVisible) = (newVisible, m_LastRenderedCells);
|
||||
// 5. 交换:scratch 不作为 Compute 输出前保留旧集合。
|
||||
(m_LastRenderedCells, m_VisibleCellsScratch) = (m_VisibleCellsScratch, m_LastRenderedCells);
|
||||
|
||||
m_DirtyFlag.ClearFlags(refreshedFlags & (TableDirtyType.ScrollChanged | TableDirtyType.SelectionChanged));
|
||||
OnTableRefreshed?.Invoke();
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using XericUI.XTable.Core;
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
|
||||
namespace XericUI.XTable.Rendering.Component
|
||||
{
|
||||
|
||||
@@ -221,7 +221,8 @@ namespace XericUI.XTable.Rendering.Component
|
||||
private void CreatePools()
|
||||
{
|
||||
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace,
|
||||
Config.TextPoolMaxCapacity, Config.ImagePoolMaxCapacity);
|
||||
Config.TextPoolMaxCapacity, Config.ImagePoolMaxCapacity,
|
||||
Config.TextPoolRouteBucketCount, Config.ImagePoolRouteBucketCount);
|
||||
m_PrefabPool = new PrefabPrototypePool(ContentTransform, Config.PrefabPoolMaxCapacity);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,14 +50,14 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
#region 构造函数与析构
|
||||
|
||||
public CellAssembler(Transform parent, string defaultStyleNamespace = null, int textPoolMaxCapacity = 256,
|
||||
int imagePoolMaxCapacity = 256)
|
||||
int imagePoolMaxCapacity = 256, int textPoolRouteBucketCount = 256, int imagePoolRouteBucketCount = 256)
|
||||
{
|
||||
m_Parent = parent;
|
||||
m_DefaultStyleNamespace = defaultStyleNamespace;
|
||||
m_ImagePool = new SlotCounterPool<PooledImage>(() => CreateImage("cell_img"), imagePoolMaxCapacity,
|
||||
SetImageCounterActive, SetImageCounterInactive);
|
||||
SetImageCounterActive, SetImageCounterInactive, imagePoolRouteBucketCount);
|
||||
m_TextPool = new SlotCounterPool<PooledText>(() => CreateText("cell_text"), textPoolMaxCapacity,
|
||||
SetTextCounterActive, SetTextCounterInactive);
|
||||
SetTextCounterActive, SetTextCounterInactive, textPoolRouteBucketCount);
|
||||
}
|
||||
|
||||
/// <summary>彻底销毁所有池对象。</summary>
|
||||
@@ -175,13 +175,13 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
/// <summary>解除指定 Image 的槽位绑定;显隐仅在所属 Counter 完整归还时变更。</summary>
|
||||
public void ReleaseImage(PooledImage item)
|
||||
{
|
||||
if (item != null) m_ImagePool.ReleaseCell(item.CellId);
|
||||
if (item != null) m_ImagePool.ReleaseByObject(item);
|
||||
}
|
||||
|
||||
/// <summary>解除指定 Text 的槽位绑定;显隐仅在所属 Counter 完整归还时变更。</summary>
|
||||
public void ReleaseText(PooledText item)
|
||||
{
|
||||
if (item != null) m_TextPool.ReleaseCell(item.CellId);
|
||||
if (item != null) m_TextPool.ReleaseByObject(item);
|
||||
}
|
||||
|
||||
private void SetImageCounterActive(IReadOnlyList<PooledImage> items)
|
||||
|
||||
@@ -59,6 +59,13 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsInstanceOfPrefab(GameObject instance, GameObject prefab)
|
||||
{
|
||||
if (instance == null || prefab == null) return false;
|
||||
return m_Pools.TryGetValue(prefab, out PrefabPool pool)
|
||||
&& pool.ActiveSlots.ContainsValue(instance);
|
||||
}
|
||||
|
||||
public bool Release(GameObject instance, Action<GameObject> beforeRelease = null)
|
||||
{
|
||||
if (instance == null) return false;
|
||||
|
||||
@@ -30,10 +30,9 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
|
||||
#region 字段
|
||||
|
||||
/// <summary>cellId 路由桶数量。</summary>
|
||||
private const int ROUTE_BUCKET_COUNT = 256;
|
||||
private const int DEFAULT_ROUTE_BUCKET_COUNT = 256;
|
||||
|
||||
private readonly SlotCounter[] m_Counters = new SlotCounter[ROUTE_BUCKET_COUNT];
|
||||
private readonly SlotCounter[] m_Counters;
|
||||
private readonly int m_MaxCreatedCount;
|
||||
private readonly Stack<SlotCounter> m_StandbyCounters = new Stack<SlotCounter>();
|
||||
private readonly Func<T> m_Factory;
|
||||
@@ -43,16 +42,31 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
|
||||
/// <summary>已创建对象总数(只读)</summary>
|
||||
public int CreatedCount => m_CreatedCount;
|
||||
public int MaxCreatedCount => m_MaxCreatedCount;
|
||||
public int RouteBucketCount => m_Counters.Length;
|
||||
public int ActiveCounterCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
for (int i = 0; i < m_Counters.Length; i++)
|
||||
if (m_Counters[i] != null) count++;
|
||||
return count;
|
||||
}
|
||||
}
|
||||
public bool IsPoolFull => m_CreatedCount >= m_MaxCreatedCount;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造
|
||||
|
||||
public SlotCounterPool(Func<T> factory, int maxCreatedCount = 256,
|
||||
Action<IReadOnlyList<T>> onCounterBorrowed = null, Action<IReadOnlyList<T>> onCounterReturned = null)
|
||||
Action<IReadOnlyList<T>> onCounterBorrowed = null, Action<IReadOnlyList<T>> onCounterReturned = null,
|
||||
int routeBucketCount = DEFAULT_ROUTE_BUCKET_COUNT)
|
||||
{
|
||||
m_Factory = factory ?? throw new ArgumentNullException(nameof(factory));
|
||||
m_MaxCreatedCount = Mathf.Max(1, maxCreatedCount);
|
||||
m_Counters = new SlotCounter[Mathf.Max(1, routeBucketCount)];
|
||||
m_OnCounterBorrowed = onCounterBorrowed;
|
||||
m_OnCounterReturned = onCounterReturned;
|
||||
}
|
||||
@@ -159,7 +173,7 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
{
|
||||
if (pooledObject == null) return false;
|
||||
|
||||
for (int ci = 0; ci < ROUTE_BUCKET_COUNT; ci++)
|
||||
for (int ci = 0; ci < m_Counters.Length; ci++)
|
||||
{
|
||||
var counter = m_Counters[ci];
|
||||
if (counter == null) continue;
|
||||
@@ -176,7 +190,7 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
/// <summary>解除全部槽位绑定,并将所有计数器整体归还待命栈。</summary>
|
||||
public void ReleaseAllBindings()
|
||||
{
|
||||
for (int ci = 0; ci < ROUTE_BUCKET_COUNT; ci++)
|
||||
for (int ci = 0; ci < m_Counters.Length; ci++)
|
||||
{
|
||||
var counter = m_Counters[ci];
|
||||
if (counter == null) continue;
|
||||
@@ -192,7 +206,7 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
{
|
||||
if (beforeRelease != null)
|
||||
{
|
||||
for (int ci = 0; ci < ROUTE_BUCKET_COUNT; ci++)
|
||||
for (int ci = 0; ci < m_Counters.Length; ci++)
|
||||
{
|
||||
var counter = m_Counters[ci];
|
||||
if (counter == null) continue;
|
||||
@@ -208,7 +222,7 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
{
|
||||
if (destroyFunc == null) return;
|
||||
var destroyedCounters = new HashSet<SlotCounter>();
|
||||
for (int ci = 0; ci < ROUTE_BUCKET_COUNT; ci++)
|
||||
for (int ci = 0; ci < m_Counters.Length; ci++)
|
||||
{
|
||||
var counter = m_Counters[ci];
|
||||
if (counter != null) destroyedCounters.Add(counter);
|
||||
@@ -221,10 +235,10 @@ namespace XericUI.XTable.Rendering.Elements
|
||||
m_CreatedCount = 0;
|
||||
}
|
||||
|
||||
private static int GetRouteIndex(int cellId)
|
||||
private int GetRouteIndex(int cellId)
|
||||
{
|
||||
int index = cellId % ROUTE_BUCKET_COUNT;
|
||||
return index < 0 ? index + ROUTE_BUCKET_COUNT : index;
|
||||
int index = cellId % m_Counters.Length;
|
||||
return index < 0 ? index + m_Counters.Length : index;
|
||||
}
|
||||
|
||||
private void ReturnCounter(int index, SlotCounter counter)
|
||||
|
||||
Reference in New Issue
Block a user