using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
#if ENABLE_BURST
using Unity.Jobs;
using Unity.Collections;
#endif
namespace XericLibrary.Runtime.UIGraph
{
///
/// 基于缓存结构的线段渲染器基类
/// 数据由 NativeArray 管理,支持脏标记范围更新和 Jobs 并行生成
///
[RequireComponent(typeof(CanvasRenderer))]
public abstract class CacheLineRendererBase : MaskableGraphic
{
#region 数据缓存
///
/// 所有线段端点的扁平缓存
///
protected List m_VertexData = new List();
///
/// 线段范围数组
///
protected List m_SegmentRanges = new List();
///
/// 待添加的端点(Begin/End API 模式用)
///
protected List m_PendingVertices = new List();
///
/// 当前 Begin/End 模式的 uvMode
///
protected UVMode m_PendingUVMode = UVMode.ByDistance;
///
/// 是否正在 Begin/End 会话中
///
protected bool m_IsBuilding;
///
/// 模型顶点缓存 (segmentCount × 5)
///
protected List m_MeshVertexCache = new List();
///
/// 三角形索引缓存
///
protected List m_MeshIndexCache = new List();
///
/// 脏标记范围起点(线段范围索引),-1 表示无脏标记
///
protected int m_DirtyStartSegment = -1;
///
/// 脏标记范围终点(线段范围索引)
///
protected int m_DirtyEndSegment = -1;
///
/// 是否使用 Jobs 模式
///
protected static bool UseJobs =>
#if ENABLE_BURST
SystemInfo.graphicsDeviceType != UnityEngine.Rendering.GraphicsDeviceType.Null &&
Application.platform != RuntimePlatform.WebGLPlayer;
#else
false;
#endif
#endregion
#region 线段添加
///
/// 批量添加一根完整线段
///
/// 线段端点列表(至少2个)
/// UV1 均分模式
/// 线段范围索引
public int AddLine(LineVertexData[] vertices, UVMode uvMode = UVMode.ByDistance)
{
if (vertices == null || vertices.Length < 2)
return -1;
int startIndex = m_VertexData.Count;
m_VertexData.AddRange(vertices);
var range = new LineSegmentRange
{
startVertexIndex = startIndex,
vertexCount = vertices.Length,
uvMode = uvMode
};
int rangeIndex = m_SegmentRanges.Count;
m_SegmentRanges.Add(range);
MarkDirty(rangeIndex);
return rangeIndex;
}
///
/// 开始画线(记录模式)
///
public void BeginLine(UVMode uvMode = UVMode.ByDistance)
{
m_IsBuilding = true;
m_PendingUVMode = uvMode;
m_PendingVertices.Clear();
}
///
/// 添加画线节点到当前记录
///
public void AddLineNode(Vector2 position, Color32 color, float thickness, Vector4 userData = default)
{
if (!m_IsBuilding) return;
m_PendingVertices.Add(new LineVertexData
{
position = position,
color = color,
thickness = thickness,
userData = userData
});
}
///
/// 添加画线节点
///
public void AddLineNode(LineVertexData node)
{
if (!m_IsBuilding) return;
m_PendingVertices.Add(node);
}
///
/// 结束画线,将挂起列表写入缓存
///
public void EndLine()
{
if (!m_IsBuilding) return;
m_IsBuilding = false;
if (m_PendingVertices.Count < 2)
{
m_PendingVertices.Clear();
return;
}
int startIndex = m_VertexData.Count;
m_VertexData.AddRange(m_PendingVertices);
var range = new LineSegmentRange
{
startVertexIndex = startIndex,
vertexCount = m_PendingVertices.Count,
uvMode = m_PendingUVMode
};
int rangeIndex = m_SegmentRanges.Count;
m_SegmentRanges.Add(range);
m_PendingVertices.Clear();
MarkDirty(rangeIndex);
}
#endregion
#region 线段管理
///
/// 移除指定线段
///
public void RemoveLine(int segmentRangeIndex)
{
if (segmentRangeIndex < 0 || segmentRangeIndex >= m_SegmentRanges.Count)
return;
m_SegmentRanges.RemoveAt(segmentRangeIndex);
// 移除后后续所有线段索引发生变化,需完全重建
RebuildAll();
}
///
/// 清空所有线段
///
public void ClearAll()
{
m_VertexData.Clear();
m_SegmentRanges.Clear();
m_MeshVertexCache.Clear();
m_MeshIndexCache.Clear();
m_DirtyStartSegment = -1;
m_DirtyEndSegment = -1;
SetVerticesDirty();
}
///
/// 标记指定线段为脏(需要重建)
///
public void SetSegmentDirty(int segmentRangeIndex)
{
MarkDirty(segmentRangeIndex);
}
#endregion
#region 脏标记缓存
///
/// 标记脏范围(合并到现有范围)
///
protected void MarkDirty(int segmentIndex)
{
if (m_DirtyStartSegment < 0 || segmentIndex < m_DirtyStartSegment)
m_DirtyStartSegment = segmentIndex;
if (m_DirtyEndSegment < 0 || segmentIndex > m_DirtyEndSegment)
m_DirtyEndSegment = segmentIndex;
}
///
/// 标记所有线段为脏(下次 LateUpdate 全量重绘)
/// 适用于:更新顶点数据后,不改变元素数量的重绘
///
public void RebuildAll()
{
m_DirtyStartSegment = 0;
m_DirtyEndSegment = m_SegmentRanges.Count - 1;
if (m_DirtyEndSegment < 0)
m_DirtyStartSegment = -1;
}
#endregion
#region 顶点生成
protected virtual void LateUpdate()
{
if (m_DirtyStartSegment < 0)
return;
// 1) 统计脏范围内所有子段数(一个 range 有 vertexCount-1 个子段)
int totalDirtySegments = 0;
for (int ri = m_DirtyStartSegment; ri <= m_DirtyEndSegment; ri++)
totalDirtySegments += m_SegmentRanges[ri].vertexCount - 1;
if (totalDirtySegments == 0)
{
m_DirtyStartSegment = -1;
m_DirtyEndSegment = -1;
return;
}
// 2) 确保 mesh 顶点缓存足够大
int totalSegments = GetTotalSegmentCount();
int neededVertices = totalSegments * LineMeshGenerator.VerticesPerSegment;
if (m_MeshVertexCache.Count < neededVertices)
{
int growBy = neededVertices - m_MeshVertexCache.Count;
for (int i = 0; i < growBy; i++)
m_MeshVertexCache.Add(new UIVertex());
}
m_MeshIndexCache.Clear();
// 3) 按子段构建 SegmentJobInput(同时计算 ByDistance 的 totalDist 缓存)
int globalSegBase = GetGlobalSegmentOffset(m_DirtyStartSegment);
var inputs = new List(totalDirtySegments);
int inSegIdx = 0;
for (int ri = m_DirtyStartSegment; ri <= m_DirtyEndSegment; ri++)
{
var range = m_SegmentRanges[ri];
float cachedTotalDist = (range.uvMode == UVMode.ByDistance)
? CalculateRangeTotalDistance(range)
: -1f;
int subSegCount = range.vertexCount - 1;
for (int si = 0; si < subSegCount; si++)
{
inputs.Add(BuildSegmentInput(range, si,
globalSegBase + inSegIdx, totalSegments, cachedTotalDist));
inSegIdx++;
}
}
// 4) 生成顶点(串行或 Jobs)
if (UseJobs)
{
#if ENABLE_BURST
GenerateMeshJobs(inputs, globalSegBase);
#endif
}
else
{
GenerateMeshSerial(inputs, globalSegBase);
}
// 5) 生成三角形索引
GenerateIndicesForRange(m_DirtyStartSegment, m_DirtyEndSegment,
globalSegBase);
// 清除脏标记
SetVerticesDirty();
m_DirtyStartSegment = -1;
m_DirtyEndSegment = -1;
}
///
/// 串行生成顶点
///
private void GenerateMeshSerial(List inputs, int globalSegBase)
{
var tempVerts = new UIVertex[LineMeshGenerator.VerticesPerSegment];
for (int i = 0; i < inputs.Count; i++)
{
int meshVertStart = (globalSegBase + i) * LineMeshGenerator.VerticesPerSegment;
LineMeshGenerator.GenerateSingleSegment(inputs[i], tempVerts, 0);
for (int v = 0; v < LineMeshGenerator.VerticesPerSegment; v++)
{
int cacheIdx = meshVertStart + v;
if (cacheIdx < m_MeshVertexCache.Count)
m_MeshVertexCache[cacheIdx] = tempVerts[v];
}
}
}
#if ENABLE_BURST
///
/// Jobs 并行生成顶点
///
private void GenerateMeshJobs(List inputs, int globalSegBase)
{
int count = inputs.Count;
int outputSize = count * LineMeshGenerator.VerticesPerSegment;
var nativeInputs = new NativeArray(count, Allocator.TempJob);
var nativeOutputs = new NativeArray(outputSize, Allocator.TempJob);
for (int i = 0; i < count; i++)
nativeInputs[i] = inputs[i];
var handle = LineMeshGenerator.GenerateSegmentsJobs(nativeInputs, nativeOutputs);
handle.Complete();
for (int i = 0; i < count; i++)
{
int srcStart = i * LineMeshGenerator.VerticesPerSegment;
int dstStart = (globalSegBase + i) * LineMeshGenerator.VerticesPerSegment;
for (int v = 0; v < LineMeshGenerator.VerticesPerSegment; v++)
{
if (dstStart + v < m_MeshVertexCache.Count)
m_MeshVertexCache[dstStart + v] = nativeOutputs[srcStart + v];
}
}
nativeInputs.Dispose();
nativeOutputs.Dispose();
}
#endif
///
/// 生成指定 range 范围中所有子段的三角形索引
/// 每条线的首个子段不生成转角连接三角形
///
private void GenerateIndicesForRange(int startRange, int endRange,
int globalSegBase)
{
int segIdx = globalSegBase;
for (int ri = startRange; ri <= endRange; ri++)
{
int subSegCount = m_SegmentRanges[ri].vertexCount - 1;
for (int si = 0; si < subSegCount; si++)
{
// 子段 si == 0 是该条线的首段 → 无前驱同线段 → hasPrevInLine = false
bool hasPrevInLine = (si > 0);
LineMeshGenerator.GenerateSegmentIndices(segIdx, hasPrevInLine, m_MeshIndexCache);
segIdx++;
}
}
}
///
/// 计算某条线段包含的段数(端点数量 - 1)
///
protected static int GetSegmentCountForRange(in LineSegmentRange range) => range.vertexCount - 1;
///
/// 获取总段数(所有线段累加)
///
protected int GetTotalSegmentCount()
{
int count = 0;
for (int i = 0; i < m_SegmentRanges.Count; i++)
count += GetSegmentCountForRange(m_SegmentRanges[i]);
return count;
}
///
/// 获取指定线段范围的全局段偏移
///
protected int GetGlobalSegmentOffset(int rangeIndex)
{
int offset = 0;
for (int i = 0; i < rangeIndex; i++)
offset += GetSegmentCountForRange(m_SegmentRanges[i]);
return offset;
}
///
/// 计算指定 range 的总距离(用于 ByDistance UV 模式)
///
private float CalculateRangeTotalDistance(in LineSegmentRange range)
{
float total = 0f;
int segCount = range.vertexCount - 1;
for (int i = 0; i < segCount; i++)
{
int idx = range.startVertexIndex + i;
total += Vector2.Distance(m_VertexData[idx].position,
m_VertexData[idx + 1].position);
}
return total;
}
///
/// 构建指定范围内第 inSegmentIndex 段的输入参数
///
protected SegmentJobInput BuildSegmentInput(
in LineSegmentRange range, int inSegmentIndex,
int globalSegmentIndex, int totalSegments,
float cachedTotalDist = -1f)
{
int startIdx = range.startVertexIndex + inSegmentIndex;
var start = m_VertexData[startIdx];
var end = m_VertexData[startIdx + 1];
var input = new SegmentJobInput
{
start = start,
end = end,
segmentIndex = globalSegmentIndex,
totalSegmentCount = totalSegments,
uvMode = range.uvMode,
hasPrev = inSegmentIndex > 0 ? (byte)1 : (byte)0,
hasNext = (inSegmentIndex < range.vertexCount - 2) ? (byte)1 : (byte)0
};
if (input.hasPrev == 1)
input.prevEnd = m_VertexData[startIdx - 1];
if (input.hasNext == 1)
input.nextStart = m_VertexData[startIdx + 2];
// 计算 startLineUV / endLineUV
int segCount = GetSegmentCountForRange(range);
if (range.uvMode == UVMode.BySegment)
{
float t0 = (float)inSegmentIndex / segCount;
float t1 = (float)(inSegmentIndex + 1) / segCount;
input.startLineUV = t0;
input.endLineUV = t1;
}
else // ByDistance
{
float totalDist = cachedTotalDist >= 0f
? cachedTotalDist
: CalculateRangeTotalDistance(range);
if (totalDist < float.Epsilon)
{
input.startLineUV = (float)inSegmentIndex / segCount;
input.endLineUV = (float)(inSegmentIndex + 1) / segCount;
}
else
{
float distSoFar = 0f;
for (int i = 0; i < inSegmentIndex; i++)
{
int idx = range.startVertexIndex + i;
distSoFar += Vector2.Distance(m_VertexData[idx].position,
m_VertexData[idx + 1].position);
}
float segDist = Vector2.Distance(start.position, end.position);
input.startLineUV = distSoFar / totalDist;
input.endLineUV = (distSoFar + segDist) / totalDist;
}
}
return input;
}
#endregion
#region OnPopulateMesh
protected override void OnPopulateMesh(VertexHelper vh)
{
vh.Clear();
if (m_MeshVertexCache.Count == 0 || m_MeshIndexCache.Count == 0)
return;
// 将缓存的顶点和索引写入 VertexHelper
vh.AddUIVertexStream(m_MeshVertexCache, m_MeshIndexCache);
}
#endregion
#region 生命周期
protected override void OnEnable()
{
base.OnEnable();
RebuildAll();
}
protected override void OnDisable()
{
base.OnDisable();
}
protected override void OnDestroy()
{
base.OnDestroy();
}
#endregion
}
}