From 5f35ed8a6ef173a6bfc9c08bef16c49b8d3ffe08 Mon Sep 17 00:00:00 2001 From: Konstantin Dyachenko Date: Tue, 31 Mar 2026 12:33:11 +0700 Subject: [PATCH] [Add] Optimization --- .../VoxelWorld/Runtime/VoxelWorldGenerator.cs | 538 +++++++++++++++--- 1 file changed, 465 insertions(+), 73 deletions(-) diff --git a/Assets/Scripts/VoxelWorld/Runtime/VoxelWorldGenerator.cs b/Assets/Scripts/VoxelWorld/Runtime/VoxelWorldGenerator.cs index 52ceaa25..2a770e19 100644 --- a/Assets/Scripts/VoxelWorld/Runtime/VoxelWorldGenerator.cs +++ b/Assets/Scripts/VoxelWorld/Runtime/VoxelWorldGenerator.cs @@ -42,21 +42,34 @@ namespace InfiniteWorld.VoxelWorld [Header("Runtime")] [SerializeField, Min(1)] private int maxAsyncChunkJobs = 2; [SerializeField, Min(1)] private int maxChunkBuildsPerFrame = 1; + [SerializeField, Min(1)] private int maxChunkMeshBuildsPerFrame = 1; + [SerializeField, Min(1)] private int maxColliderAppliesPerFrame = 1; + [SerializeField, Min(0)] private int maxNeighborRefreshesPerFrame = 2; [SerializeField, Min(1)] private int renderRegionSizeInChunks = 4; [SerializeField, Min(1)] private int maxRegionBuildsPerFrame = 1; private readonly Dictionary chunks = new Dictionary(); private readonly Dictionary regions = new Dictionary(); + private readonly Dictionary regionVersions = new Dictionary(); private readonly Queue completedBuilds = new Queue(); + private readonly Queue completedRegionBuilds = new Queue(); + private readonly Queue pendingColliderApplies = new Queue(); + private readonly Queue dirtyChunkMeshes = new Queue(); + private readonly HashSet queuedChunkMeshes = new HashSet(); private readonly Queue dirtyRegions = new Queue(); private readonly HashSet queuedRegions = new HashSet(); + private readonly Queue pendingNeighborRefreshes = new Queue(); + private readonly HashSet queuedNeighborRefreshes = new HashSet(); private readonly object generationLock = new object(); + private readonly object regionBuildLock = new object(); private Transform chunkRoot; private Transform regionRoot; private Vector2Int lastGeneratedCenter = new Vector2Int(int.MinValue, int.MinValue); private int activeGenerationJobs; + private int nextChunkRuntimeId; private int generationSession; + private int regionRebuildSession; private VoxelWorldAtlas atlas; private int atlasBiomeCount; private bool regionRebuildLoopRunning; @@ -64,6 +77,7 @@ namespace InfiniteWorld.VoxelWorld private void Awake() { generationSession++; + regionRebuildSession++; EnsureRuntimeData(); EnsureChunkRoot(); EnsureRegionRoot(); @@ -88,6 +102,10 @@ namespace InfiniteWorld.VoxelWorld } DrainCompletedBuilds(maxChunkBuildsPerFrame); + DrainDirtyChunkMeshes(maxChunkMeshBuildsPerFrame); + DrainPendingColliderApplies(maxColliderAppliesPerFrame); + DrainNeighborRefreshes(maxNeighborRefreshesPerFrame); + DrainCompletedRegionBuilds(maxRegionBuildsPerFrame); ScheduleChunkGeneration(centerChunk); EnsureBlockingChunksLoaded(centerChunk); } @@ -95,9 +113,15 @@ namespace InfiniteWorld.VoxelWorld private void OnDisable() { generationSession++; + regionRebuildSession++; lock (generationLock) { completedBuilds.Clear(); + activeGenerationJobs = 0; + } + lock (regionBuildLock) + { + completedRegionBuilds.Clear(); } CleanupChunks(); @@ -110,6 +134,9 @@ namespace InfiniteWorld.VoxelWorld { maxMountainHeight = Mathf.Max(1, maxMountainHeight); chunkSize = Mathf.Max(8, chunkSize); + maxChunkMeshBuildsPerFrame = Mathf.Max(1, maxChunkMeshBuildsPerFrame); + maxColliderAppliesPerFrame = Mathf.Max(1, maxColliderAppliesPerFrame); + maxNeighborRefreshesPerFrame = Mathf.Max(0, maxNeighborRefreshesPerFrame); renderRegionSizeInChunks = Mathf.Max(1, renderRegionSizeInChunks); maxRegionBuildsPerFrame = Mathf.Max(1, maxRegionBuildsPerFrame); int configuredBiomeCount = CountConfiguredBiomes(); @@ -220,7 +247,7 @@ namespace InfiniteWorld.VoxelWorld runtime.State = ChunkState.Generating; runtime.Version++; - GenerateChunkDataAsync(coord, runtime.Version, generationSession).Forget(); + GenerateChunkDataAsync(coord, runtime.Version, generationSession, runtime.RuntimeId).Forget(); } } @@ -231,21 +258,18 @@ namespace InfiniteWorld.VoxelWorld { Vector2Int coord = requiredCoords[i]; ChunkRuntime runtime = GetOrCreateChunkRuntime(coord); - if (runtime.IsRendered) - { - continue; - } - - if (runtime.State == ChunkState.Generating) + if (runtime.IsRendered || runtime.HasData || runtime.State == ChunkState.Generating || runtime.State == ChunkState.SyncBuilding) { continue; } runtime.Version++; runtime.State = ChunkState.SyncBuilding; - ApplyBuildResult(GenerateChunkData(coord, runtime.Version, generationSession)); - RenderChunk(coord); - RefreshNeighborBorders(coord); + if (ApplyBuildResult(GenerateChunkData(coord, runtime.Version, generationSession, runtime.RuntimeId))) + { + EnqueueChunkMeshBuild(coord); + QueueNeighborRefresh(coord); + } } } @@ -273,11 +297,11 @@ namespace InfiniteWorld.VoxelWorld MarkRegionDirty(coord); chunks.Remove(coord); runtime.Dispose(); - RefreshNeighborBorders(coord); + QueueNeighborRefresh(coord); } } - private ChunkBuildResult GenerateChunkData(Vector2Int coord, int version, int session) + private ChunkBuildResult GenerateChunkData(Vector2Int coord, int version, int session, int runtimeId) { int margin = Mathf.Max(2, smoothingPasses + 1); int sampleSize = chunkSize + margin * 2; @@ -317,7 +341,7 @@ namespace InfiniteWorld.VoxelWorld } } - return new ChunkBuildResult(coord, heights, biomeIndices, version, session); + return new ChunkBuildResult(coord, heights, biomeIndices, version, session, runtimeId); } private bool[,] SmoothSampledMask(bool[,] source) @@ -439,7 +463,8 @@ namespace InfiniteWorld.VoxelWorld runtime.EnsureCreated(coord, chunkRoot, chunkSize); ChunkMeshBuild meshBuild = BuildChunkMesh(coord, runtime.Heights, runtime.BiomeIndices); - runtime.ApplyMeshes(meshBuild, chunkSize); + runtime.ApplyRenderData(meshBuild.RenderSnapshot); + EnqueueColliderApply(coord, runtime.Version, runtime.RuntimeId, meshBuild.ColliderMesh); runtime.State = ChunkState.Rendered; MarkRegionDirty(coord); } @@ -453,7 +478,8 @@ namespace InfiniteWorld.VoxelWorld BuildMountainTops(coord, heights, biomeIndices, renderBuffers, colliderBuffers); BuildMountainSides(coord, heights, biomeIndices, renderBuffers, colliderBuffers); - return new ChunkMeshBuild(renderBuffers.ToMesh($"Render_{coord.x}_{coord.y}"), colliderBuffers.ToMesh($"Collider_{coord.x}_{coord.y}")); + ChunkRenderSnapshot renderSnapshot = renderBuffers.ToSnapshot(); + return new ChunkMeshBuild(colliderBuffers.ToMesh($"Collider_{coord.x}_{coord.y}"), renderSnapshot); } private void BuildGroundSurface(int[] heights, byte[] biomeIndices, MeshBuffers renderBuffers) @@ -884,22 +910,33 @@ namespace InfiniteWorld.VoxelWorld return new Vector2Int(coord.x * chunkSize + localX, coord.y * chunkSize + localZ); } - private async UniTaskVoid GenerateChunkDataAsync(Vector2Int coord, int version, int session) + private async UniTaskVoid GenerateChunkDataAsync(Vector2Int coord, int version, int session, int runtimeId) { try { - ChunkBuildResult result = await UniTask.RunOnThreadPool(() => GenerateChunkData(coord, version, session)); + ChunkBuildResult result = await UniTask.RunOnThreadPool(() => GenerateChunkData(coord, version, session, runtimeId)); lock (generationLock) { - completedBuilds.Enqueue(result); - activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1); + if (session == generationSession) + { + completedBuilds.Enqueue(result); + activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1); + } } } catch (Exception exception) { lock (generationLock) { - activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1); + if (session == generationSession) + { + activeGenerationJobs = Mathf.Max(0, activeGenerationJobs - 1); + } + } + + if (session == generationSession && chunks.TryGetValue(coord, out ChunkRuntime runtime) && runtime.Version == version && runtime.RuntimeId == runtimeId) + { + runtime.State = ChunkState.None; } Debug.LogException(exception, this); @@ -927,12 +964,100 @@ namespace InfiniteWorld.VoxelWorld continue; } - RenderChunk(result.Coord); - RefreshNeighborBorders(result.Coord); + EnqueueChunkMeshBuild(result.Coord); + QueueNeighborRefresh(result.Coord); builds++; } } + private void DrainDirtyChunkMeshes(int maxBuilds) + { + int builds = 0; + while (builds < maxBuilds) + { + if (dirtyChunkMeshes.Count == 0) + { + break; + } + + Vector2Int coord = dirtyChunkMeshes.Dequeue(); + queuedChunkMeshes.Remove(coord); + + if (!chunks.TryGetValue(coord, out ChunkRuntime runtime) || !runtime.HasData) + { + continue; + } + + RenderChunk(coord); + builds++; + } + } + + private void EnqueueChunkMeshBuild(Vector2Int coord) + { + if (!queuedChunkMeshes.Add(coord)) + { + return; + } + + dirtyChunkMeshes.Enqueue(coord); + } + + private void EnqueueColliderApply(Vector2Int coord, int version, int runtimeId, Mesh colliderMesh) + { + pendingColliderApplies.Enqueue(new PendingColliderMeshApply(coord, version, runtimeId, colliderMesh)); + } + + private void DrainPendingColliderApplies(int maxApplies) + { + int applies = 0; + while (applies < maxApplies) + { + if (pendingColliderApplies.Count == 0) + { + break; + } + + PendingColliderMeshApply pending = pendingColliderApplies.Dequeue(); + if (!chunks.TryGetValue(pending.Coord, out ChunkRuntime runtime) || runtime.Version != pending.Version || runtime.RuntimeId != pending.RuntimeId) + { + DestroyMeshAsset(pending.ColliderMesh); + applies++; + continue; + } + + runtime.ApplyColliderMesh(pending.ColliderMesh); + applies++; + } + } + + private void QueueNeighborRefresh(Vector2Int coord) + { + if (!queuedNeighborRefreshes.Add(coord)) + { + return; + } + + pendingNeighborRefreshes.Enqueue(coord); + } + + private void DrainNeighborRefreshes(int maxRefreshes) + { + int refreshes = 0; + while (refreshes < maxRefreshes) + { + if (pendingNeighborRefreshes.Count == 0) + { + break; + } + + Vector2Int coord = pendingNeighborRefreshes.Dequeue(); + queuedNeighborRefreshes.Remove(coord); + RefreshNeighborBorders(coord); + refreshes++; + } + } + private void RefreshNeighborBorders(Vector2Int coord) { TryRenderNeighbor(coord + Vector2Int.up); @@ -948,12 +1073,15 @@ namespace InfiniteWorld.VoxelWorld return; } - RenderChunk(coord); + EnqueueChunkMeshBuild(coord); } private void MarkRegionDirty(Vector2Int chunkCoord) { Vector2Int regionCoord = ChunkToRegion(chunkCoord); + regionVersions.TryGetValue(regionCoord, out int version); + regionVersions[regionCoord] = version + 1; + if (!queuedRegions.Add(regionCoord)) { return; @@ -971,16 +1099,16 @@ namespace InfiniteWorld.VoxelWorld } regionRebuildLoopRunning = true; - ProcessDirtyRegionsAsync().Forget(); + ProcessDirtyRegionsAsync(regionRebuildSession).Forget(); } - private async UniTaskVoid ProcessDirtyRegionsAsync() + private async UniTaskVoid ProcessDirtyRegionsAsync(int session) { try { while (dirtyRegions.Count > 0) { - if (!this || !isActiveAndEnabled) + if (!this || !isActiveAndEnabled || session != regionRebuildSession) { break; } @@ -990,7 +1118,19 @@ namespace InfiniteWorld.VoxelWorld { Vector2Int regionCoord = dirtyRegions.Dequeue(); queuedRegions.Remove(regionCoord); - RebuildRegion(regionCoord); + + if (!TryCaptureRegionBuildRequest(regionCoord, out RegionBuildRequest request)) + { + buildsThisFrame++; + continue; + } + + RegionBuildResult result = await UniTask.RunOnThreadPool(() => BuildRegionBuffers(request)); + lock (regionBuildLock) + { + completedRegionBuilds.Enqueue(result); + } + buildsThisFrame++; } @@ -1002,61 +1142,45 @@ namespace InfiniteWorld.VoxelWorld } finally { - regionRebuildLoopRunning = false; - if (dirtyRegions.Count > 0) + if (session == regionRebuildSession) { - EnsureRegionRebuildLoop(); + regionRebuildLoopRunning = false; + if (dirtyRegions.Count > 0) + { + EnsureRegionRebuildLoop(); + } } } } - private void RebuildRegion(Vector2Int regionCoord) + private bool TryCaptureRegionBuildRequest(Vector2Int regionCoord, out RegionBuildRequest request) { if (atlas == null || regionRoot == null) { - return; + request = new RegionBuildRequest(); + return false; } Vector2Int baseChunk = new Vector2Int(regionCoord.x * renderRegionSizeInChunks, regionCoord.y * renderRegionSizeInChunks); - List combineInstances = new List(renderRegionSizeInChunks * renderRegionSizeInChunks); + List chunkSnapshots = new List(renderRegionSizeInChunks * renderRegionSizeInChunks); for (int z = 0; z < renderRegionSizeInChunks; z++) { for (int x = 0; x < renderRegionSizeInChunks; x++) { Vector2Int chunkCoord = new Vector2Int(baseChunk.x + x, baseChunk.y + z); - if (!chunks.TryGetValue(chunkCoord, out ChunkRuntime runtime) || runtime.RenderMesh == null || runtime.RenderMesh.vertexCount == 0) + if (!chunks.TryGetValue(chunkCoord, out ChunkRuntime runtime) || runtime.RenderSnapshot == null || runtime.RenderSnapshot.IsEmpty) { continue; } - combineInstances.Add(new CombineInstance - { - mesh = runtime.RenderMesh, - transform = Matrix4x4.Translate(new Vector3(x * chunkSize, 0f, z * chunkSize)) - }); + chunkSnapshots.Add(new RegionChunkSnapshot(runtime.RenderSnapshot, new Vector3(x * chunkSize, 0f, z * chunkSize))); } } - if (combineInstances.Count == 0) - { - if (regions.TryGetValue(regionCoord, out RegionRuntime regionToRemove)) - { - regionToRemove.Dispose(); - regions.Remove(regionCoord); - } - - return; - } - - RegionRuntime region = GetOrCreateRegionRuntime(regionCoord); - region.EnsureCreated(regionCoord, regionRoot, chunkSize, renderRegionSizeInChunks, atlas.Material); - - Mesh combinedMesh = new Mesh { name = $"Region_{regionCoord.x}_{regionCoord.y}" }; - combinedMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; - combinedMesh.CombineMeshes(combineInstances.ToArray(), true, true, false); - combinedMesh.RecalculateBounds(); - region.ApplyMesh(combinedMesh); + regionVersions.TryGetValue(regionCoord, out int version); + request = new RegionBuildRequest(regionCoord, version, generationSession, chunkSnapshots.ToArray()); + return true; } private Vector2Int ChunkToRegion(Vector2Int chunkCoord) @@ -1077,6 +1201,131 @@ namespace InfiniteWorld.VoxelWorld return region; } + private static RegionBuildResult BuildRegionBuffers(RegionBuildRequest request) + { + if (request.Chunks == null || request.Chunks.Length == 0) + { + return RegionBuildResult.CreateEmpty(request.RegionCoord, request.Version, request.Session); + } + + int totalVertexCount = 0; + int totalTriangleCount = 0; + for (int i = 0; i < request.Chunks.Length; i++) + { + ChunkRenderSnapshot snapshot = request.Chunks[i].Snapshot; + totalVertexCount += snapshot.Vertices.Length; + totalTriangleCount += snapshot.Triangles.Length; + } + + if (totalVertexCount == 0 || totalTriangleCount == 0) + { + return RegionBuildResult.CreateEmpty(request.RegionCoord, request.Version, request.Session); + } + + Vector3[] vertices = new Vector3[totalVertexCount]; + Vector3[] normals = new Vector3[totalVertexCount]; + Vector2[] uv0 = new Vector2[totalVertexCount]; + Vector2[] uv1 = new Vector2[totalVertexCount]; + int[] triangles = new int[totalTriangleCount]; + + int vertexOffset = 0; + int triangleOffset = 0; + Vector3 min = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); + Vector3 max = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity); + + for (int chunkIndex = 0; chunkIndex < request.Chunks.Length; chunkIndex++) + { + RegionChunkSnapshot chunk = request.Chunks[chunkIndex]; + ChunkRenderSnapshot snapshot = chunk.Snapshot; + Vector3 offset = chunk.LocalOffset; + + for (int vertexIndex = 0; vertexIndex < snapshot.Vertices.Length; vertexIndex++) + { + Vector3 vertex = snapshot.Vertices[vertexIndex] + offset; + int writeIndex = vertexOffset + vertexIndex; + vertices[writeIndex] = vertex; + normals[writeIndex] = snapshot.Normals[vertexIndex]; + uv0[writeIndex] = snapshot.Uv0[vertexIndex]; + uv1[writeIndex] = snapshot.Uv1[vertexIndex]; + + min = Vector3.Min(min, vertex); + max = Vector3.Max(max, vertex); + } + + for (int triangleIndex = 0; triangleIndex < snapshot.Triangles.Length; triangleIndex++) + { + triangles[triangleOffset + triangleIndex] = snapshot.Triangles[triangleIndex] + vertexOffset; + } + + vertexOffset += snapshot.Vertices.Length; + triangleOffset += snapshot.Triangles.Length; + } + + Bounds bounds = new Bounds((min + max) * 0.5f, max - min); + return new RegionBuildResult(request.RegionCoord, request.Version, request.Session, vertices, normals, uv0, uv1, triangles, bounds); + } + + private void DrainCompletedRegionBuilds(int maxBuilds) + { + int builds = 0; + while (builds < maxBuilds) + { + RegionBuildResult result; + lock (regionBuildLock) + { + if (completedRegionBuilds.Count == 0) + { + break; + } + + result = completedRegionBuilds.Dequeue(); + } + + ApplyRegionBuildResult(result); + builds++; + } + } + + private void ApplyRegionBuildResult(RegionBuildResult result) + { + if (result.Session != generationSession || atlas == null || regionRoot == null) + { + return; + } + + regionVersions.TryGetValue(result.RegionCoord, out int currentVersion); + if (result.Version != currentVersion) + { + return; + } + + if (result.IsEmpty) + { + if (regions.TryGetValue(result.RegionCoord, out RegionRuntime regionToRemove)) + { + regionToRemove.Dispose(); + regions.Remove(result.RegionCoord); + } + + regionVersions.Remove(result.RegionCoord); + + return; + } + + RegionRuntime region = GetOrCreateRegionRuntime(result.RegionCoord); + region.EnsureCreated(result.RegionCoord, regionRoot, chunkSize, renderRegionSizeInChunks, atlas.Material); + + Mesh mesh = new Mesh { name = $"Region_{result.RegionCoord.x}_{result.RegionCoord.y}" }; + mesh.indexFormat = result.Vertices.Length > 65535 ? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16; + mesh.vertices = result.Vertices; + mesh.normals = result.Normals; + mesh.uv = result.Uv0; + mesh.uv2 = result.Uv1; + mesh.triangles = result.Triangles; + mesh.bounds = result.Bounds; + region.ApplyMesh(mesh); + } + private bool ApplyBuildResult(ChunkBuildResult result) { if (result.Session != generationSession) @@ -1094,6 +1343,11 @@ namespace InfiniteWorld.VoxelWorld return false; } + if (runtime.RuntimeId != result.RuntimeId) + { + return false; + } + runtime.Heights = result.Heights; runtime.BiomeIndices = result.BiomeIndices; if (!runtime.IsRendered) @@ -1165,6 +1419,7 @@ namespace InfiniteWorld.VoxelWorld if (!chunks.TryGetValue(coord, out ChunkRuntime runtime)) { runtime = new ChunkRuntime(); + runtime.RuntimeId = ++nextChunkRuntimeId; chunks.Add(coord, runtime); } @@ -1186,6 +1441,31 @@ namespace InfiniteWorld.VoxelWorld } chunks.Clear(); + dirtyChunkMeshes.Clear(); + queuedChunkMeshes.Clear(); + pendingNeighborRefreshes.Clear(); + queuedNeighborRefreshes.Clear(); + while (pendingColliderApplies.Count > 0) + { + DestroyMeshAsset(pendingColliderApplies.Dequeue().ColliderMesh); + } + } + + private static void DestroyMeshAsset(Mesh mesh) + { + if (mesh == null) + { + return; + } + + if (Application.isPlaying) + { + UnityEngine.Object.Destroy(mesh); + } + else + { + UnityEngine.Object.DestroyImmediate(mesh); + } } private void CleanupRegions() @@ -1196,8 +1476,13 @@ namespace InfiniteWorld.VoxelWorld } regions.Clear(); + regionVersions.Clear(); dirtyRegions.Clear(); queuedRegions.Clear(); + lock (regionBuildLock) + { + completedRegionBuilds.Clear(); + } regionRebuildLoopRunning = false; } @@ -1215,15 +1500,16 @@ namespace InfiniteWorld.VoxelWorld return Mathf.Max(1, count); } - private readonly struct ChunkBuildResult + private struct ChunkBuildResult { - public ChunkBuildResult(Vector2Int coord, int[] heights, byte[] biomeIndices, int version, int session) + public ChunkBuildResult(Vector2Int coord, int[] heights, byte[] biomeIndices, int version, int session, int runtimeId) { Coord = coord; Heights = heights; BiomeIndices = biomeIndices; Version = version; Session = session; + RuntimeId = runtimeId; } public Vector2Int Coord { get; } @@ -1231,20 +1517,116 @@ namespace InfiniteWorld.VoxelWorld public byte[] BiomeIndices { get; } public int Version { get; } public int Session { get; } + public int RuntimeId { get; } } - private readonly struct ChunkMeshBuild + private struct ChunkMeshBuild { - public ChunkMeshBuild(Mesh renderMesh, Mesh colliderMesh) + public ChunkMeshBuild(Mesh colliderMesh, ChunkRenderSnapshot renderSnapshot) { - RenderMesh = renderMesh; + ColliderMesh = colliderMesh; + RenderSnapshot = renderSnapshot; + } + + public Mesh ColliderMesh { get; } + public ChunkRenderSnapshot RenderSnapshot { get; } + } + + private struct PendingColliderMeshApply + { + public PendingColliderMeshApply(Vector2Int coord, int version, int runtimeId, Mesh colliderMesh) + { + Coord = coord; + Version = version; + RuntimeId = runtimeId; ColliderMesh = colliderMesh; } - public Mesh RenderMesh { get; } + public Vector2Int Coord { get; } + public int Version { get; } + public int RuntimeId { get; } public Mesh ColliderMesh { get; } } + private sealed class ChunkRenderSnapshot + { + public ChunkRenderSnapshot(Vector3[] vertices, Vector3[] normals, Vector2[] uv0, Vector2[] uv1, int[] triangles) + { + Vertices = vertices; + Normals = normals; + Uv0 = uv0; + Uv1 = uv1; + Triangles = triangles; + } + + public Vector3[] Vertices { get; } + public Vector3[] Normals { get; } + public Vector2[] Uv0 { get; } + public Vector2[] Uv1 { get; } + public int[] Triangles { get; } + public bool IsEmpty => Vertices == null || Vertices.Length == 0 || Triangles == null || Triangles.Length == 0; + } + + private struct RegionChunkSnapshot + { + public RegionChunkSnapshot(ChunkRenderSnapshot snapshot, Vector3 localOffset) + { + Snapshot = snapshot; + LocalOffset = localOffset; + } + + public ChunkRenderSnapshot Snapshot { get; } + public Vector3 LocalOffset { get; } + } + + private struct RegionBuildRequest + { + public RegionBuildRequest(Vector2Int regionCoord, int version, int session, RegionChunkSnapshot[] chunks) + { + RegionCoord = regionCoord; + Version = version; + Session = session; + Chunks = chunks; + } + + public Vector2Int RegionCoord { get; } + public int Version { get; } + public int Session { get; } + public RegionChunkSnapshot[] Chunks { get; } + } + + private struct RegionBuildResult + { + public RegionBuildResult(Vector2Int regionCoord, int version, int session, Vector3[] vertices, Vector3[] normals, Vector2[] uv0, Vector2[] uv1, int[] triangles, Bounds bounds) + { + RegionCoord = regionCoord; + Version = version; + Session = session; + Vertices = vertices; + Normals = normals; + Uv0 = uv0; + Uv1 = uv1; + Triangles = triangles; + Bounds = bounds; + } + + public Vector2Int RegionCoord { get; } + public int Version { get; } + public int Session { get; } + public Vector3[] Vertices { get; } + public Vector3[] Normals { get; } + public Vector2[] Uv0 { get; } + public Vector2[] Uv1 { get; } + public int[] Triangles { get; } + public Bounds Bounds { get; } + public bool IsEmpty => Vertices == null || Vertices.Length == 0 || Triangles == null || Triangles.Length == 0; + + public static RegionBuildResult CreateEmpty(Vector2Int regionCoord, int version, int session) + { + return new RegionBuildResult(regionCoord, version, session, null, null, null, null, null, new Bounds()); + } + } + private sealed class MeshBuffers { public readonly List Vertices = new List(512); @@ -1279,6 +1661,16 @@ namespace InfiniteWorld.VoxelWorld mesh.RecalculateBounds(); return mesh; } + + public ChunkRenderSnapshot ToSnapshot() + { + return new ChunkRenderSnapshot( + Vertices.ToArray(), + Normals.ToArray(), + Uvs.ToArray(), + TextureData.ToArray(), + Triangles.ToArray()); + } } private sealed class ChunkRuntime @@ -1286,12 +1678,13 @@ namespace InfiniteWorld.VoxelWorld public Transform Root; public MeshCollider MountainCollider; public BoxCollider GroundCollider; - public Mesh RenderMesh; public Mesh ColliderMesh; + public ChunkRenderSnapshot RenderSnapshot; public int[] Heights; public byte[] BiomeIndices; public ChunkState State; public int Version; + public int RuntimeId; public bool HasData => Heights != null && BiomeIndices != null; public bool IsRendered => State == ChunkState.Rendered && Root != null; @@ -1312,20 +1705,19 @@ namespace InfiniteWorld.VoxelWorld GroundCollider.center = new Vector3(chunkSize * 0.5f, -0.1f, chunkSize * 0.5f); } - public void ApplyMeshes(ChunkMeshBuild build, int chunkSize) + public void ApplyRenderData(ChunkRenderSnapshot renderSnapshot) { - if (RenderMesh != null) - { - DestroyMesh(RenderMesh); - } + RenderSnapshot = renderSnapshot; + } + public void ApplyColliderMesh(Mesh colliderMesh) + { if (ColliderMesh != null) { DestroyMesh(ColliderMesh); } - RenderMesh = build.RenderMesh; - ColliderMesh = build.ColliderMesh; + ColliderMesh = colliderMesh; MountainCollider.sharedMesh = null; MountainCollider.sharedMesh = ColliderMesh != null && ColliderMesh.vertexCount > 0 ? ColliderMesh : null; } @@ -1344,8 +1736,8 @@ namespace InfiniteWorld.VoxelWorld } } - DestroyMesh(RenderMesh); DestroyMesh(ColliderMesh); + RenderSnapshot = null; } private static void DestroyMesh(Mesh mesh)