From d9d5b7b5c58a7b044b78ee7993f0a15e5b79f689 Mon Sep 17 00:00:00 2001 From: miepzerino <o.skotnik@gmail.com> Date: Thu, 03 Apr 2025 18:28:15 +0000 Subject: [PATCH] #42 started performance fixes for chunk generation --- Assets/Scripts/Jobs.meta | 8 Assets/Scripts/Jobs/OreGnerationJob.cs | 33 +++ Assets/Scripts/Cache/TilePool.cs.meta | 11 + Assets/Scripts/Cache/TilePool.cs | 79 +++++++ Assets/Scripts/CustomRuleTile.cs | 60 ++++ Assets/Scripts/Jobs/OreGnerationJob.cs.meta | 11 + Assets/Scenes/GameplayScene.unity | 164 +++++--------- Assets/Scripts/Jobs/TerrainGenerationJob.cs | 42 +++ Assets/Scripts/Jobs/TerrainGenerationJob.cs.meta | 11 + Assets/Scripts/GenerateTileMap.cs | 184 +++++++++++++-- 10 files changed, 463 insertions(+), 140 deletions(-) diff --git a/Assets/Scenes/GameplayScene.unity b/Assets/Scenes/GameplayScene.unity index c54abad..e7b2fb7 100644 --- a/Assets/Scenes/GameplayScene.unity +++ b/Assets/Scenes/GameplayScene.unity @@ -4521,11 +4521,6 @@ m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1725312356} m_CullTransparentMesh: 1 ---- !u!1 &1776306376 stripped -GameObject: - m_CorrespondingSourceObject: {fileID: 8598998496262044661, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - m_PrefabInstance: {fileID: 75655679957548990} - m_PrefabAsset: {fileID: 0} --- !u!1 &1794216239 GameObject: m_ObjectHideFlags: 0 @@ -4566,7 +4561,7 @@ m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1794216239} - m_Enabled: 1 + m_Enabled: 0 m_CastShadows: 0 m_ReceiveShadows: 0 m_DynamicOccludee: 1 @@ -5142,6 +5137,7 @@ m_EditorClassIdentifier: forestRuleTile: {fileID: 11400000, guid: 3999614e192b37546a6b710bf5ceb30c, type: 2} borderTile: {fileID: 11400000, guid: dcef846474e534b45ab3b175559c19a2, type: 2} + tilePool: {fileID: 2140657251} --- !u!1 &1955008394 GameObject: m_ObjectHideFlags: 0 @@ -5386,6 +5382,50 @@ hasTopBuddy: 0 hasBottomBuddy: 0 reverseScale: 1 +--- !u!1 &2140657250 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2140657252} + - component: {fileID: 2140657251} + m_Layer: 0 + m_Name: TilePool + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &2140657251 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2140657250} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ca130bf5edb364941807e577a7d1b7d6, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!4 &2140657252 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2140657250} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 84.880615, y: -3.9463425, z: -2.3778288} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &18669457987763773 MonoBehaviour: m_ObjectHideFlags: 0 @@ -5439,6 +5479,18 @@ serializedVersion: 3 m_TransformParent: {fileID: 0} m_Modifications: + - target: {fileID: 851313892602111808, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} + propertyPath: player + value: + objectReference: {fileID: 254538002} + - target: {fileID: 851313892602111808, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} + propertyPath: fogTilemap + value: + objectReference: {fileID: 1794216242} + - target: {fileID: 851313892602111808, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} + propertyPath: mainCamera + value: + objectReference: {fileID: 519420031} - target: {fileID: 5654596948088327753, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} propertyPath: m_LocalPosition.x value: 721.6197 @@ -5484,17 +5536,9 @@ value: objectReference: {fileID: 1409843025} - target: {fileID: 6908574976455911116, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: tileMap - value: - objectReference: {fileID: 1919262559} - - target: {fileID: 6908574976455911116, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} propertyPath: tilemap value: objectReference: {fileID: 1919262559} - - target: {fileID: 6908574976455911116, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: fogOfWar - value: - objectReference: {fileID: 75655679957548994} - target: {fileID: 6908574976455911116, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} propertyPath: playerUI value: @@ -5503,10 +5547,6 @@ propertyPath: fogTilemap value: objectReference: {fileID: 1794216242} - - target: {fileID: 6908574976455911116, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: gameCanvas - value: - objectReference: {fileID: 90387968388784992} - target: {fileID: 6908574976455911116, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} propertyPath: pauseMenuUI value: @@ -5519,30 +5559,6 @@ propertyPath: worldSpaceUI value: objectReference: {fileID: 97771189} - - target: {fileID: 6908574976455911116, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: lootTextPrefab - value: - objectReference: {fileID: 456827189715955869, guid: 9014e3792e948bf4e81fdfdc7f38c5d9, type: 3} - - target: {fileID: 7748736149887392517, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: m_ActionEvents.Array.size - value: 15 - objectReference: {fileID: 0} - - target: {fileID: 7748736149887392517, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: m_ActionEvents.Array.data[14].m_ActionId - value: b425611e-206f-4a50-938e-ad263b5330a1 - objectReference: {fileID: 0} - - target: {fileID: 7748736149887392517, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: m_ActionEvents.Array.data[14].m_ActionName - value: UI/Inventory[/Keyboard/b,/Keyboard/i] - objectReference: {fileID: 0} - - target: {fileID: 7748736149887392517, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: m_ActionEvents.Array.data[14].m_PersistentCalls.m_Calls.Array.size - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 7748736149887392517, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: m_ActionEvents.Array.data[14].m_PersistentCalls.m_Calls.Array.data[0].m_Mode - value: 0 - objectReference: {fileID: 0} - target: {fileID: 7748736149887392517, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} propertyPath: m_ActionEvents.Array.data[13].m_PersistentCalls.m_Calls.Array.data[0].m_Target value: @@ -5551,81 +5567,26 @@ propertyPath: m_ActionEvents.Array.data[14].m_PersistentCalls.m_Calls.Array.data[0].m_Target value: objectReference: {fileID: 7398061984209721034} - - target: {fileID: 7748736149887392517, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: m_ActionEvents.Array.data[14].m_PersistentCalls.m_Calls.Array.data[0].m_CallState - value: 2 - objectReference: {fileID: 0} - - target: {fileID: 7748736149887392517, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: m_ActionEvents.Array.data[13].m_PersistentCalls.m_Calls.Array.data[0].m_MethodName - value: OnEscapedPressed - objectReference: {fileID: 0} - - target: {fileID: 7748736149887392517, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: m_ActionEvents.Array.data[14].m_PersistentCalls.m_Calls.Array.data[0].m_MethodName - value: OnInventoryButtonPressed - objectReference: {fileID: 0} - - target: {fileID: 7748736149887392517, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: m_ActionEvents.Array.data[14].m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName - value: InventoryDisplay, Assembly-CSharp - objectReference: {fileID: 0} - - target: {fileID: 7748736149887392517, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: m_ActionEvents.Array.data[14].m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName - value: UnityEngine.Object, UnityEngine - objectReference: {fileID: 0} - target: {fileID: 8598998496262044661, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} propertyPath: m_Name value: GameManager objectReference: {fileID: 0} - - target: {fileID: 8598998496262044661, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - propertyPath: m_IsActive - value: 1 - objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] - m_AddedComponents: - - targetCorrespondingSourceObject: {fileID: 8598998496262044661, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} - insertIndex: -1 - addedObject: {fileID: 75655679957548994} + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} --- !u!114 &75655679957548991 stripped MonoBehaviour: m_CorrespondingSourceObject: {fileID: 6908574976455911116, guid: 7296d9a2424531f4ba42c0c75e9c48a0, type: 3} m_PrefabInstance: {fileID: 75655679957548990} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1776306376} + m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: ee189283a861cae42b728253b8229316, type: 3} m_Name: m_EditorClassIdentifier: ---- !u!114 &75655679957548994 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1776306376} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: e98672417f6c56e4d8020c2453ced645, type: 3} - m_Name: - m_EditorClassIdentifier: - fogTilemap: {fileID: 1794216242} - fogTile: {fileID: 11400000, guid: 54e9042346ddcc347aa31573844df14b, type: 2} - player: {fileID: 254538002} - viewRadius: 5 - mainCamera: {fileID: 519420031} - fogLevels: - - tile: {fileID: 11400000, guid: ee11684c9f570a741917f425e4ff9bf1, type: 2} - opacity: 20 - - tile: {fileID: 11400000, guid: cfdca8d69259ece46ac1c967fcfcc447, type: 2} - opacity: 40 - - tile: {fileID: 11400000, guid: e70bfd59f79cdce449d7a6a67b4a1722, type: 2} - opacity: 60 - - tile: {fileID: 11400000, guid: 546f1dbae64d6ce49b8474fc131c86b9, type: 2} - opacity: 80 - - tile: {fileID: 11400000, guid: 54e9042346ddcc347aa31573844df14b, type: 2} - opacity: 100 --- !u!223 &90387968388784992 Canvas: m_ObjectHideFlags: 0 @@ -6091,6 +6052,10 @@ objectReference: {fileID: 0} - target: {fileID: 3345854317100013954, guid: c220ec455fce341408d66d880b464cad, type: 3} propertyPath: m_Name + value: Player + objectReference: {fileID: 0} + - target: {fileID: 3345854317100013954, guid: c220ec455fce341408d66d880b464cad, type: 3} + propertyPath: m_TagString value: Player objectReference: {fileID: 0} m_RemovedComponents: [] @@ -14343,3 +14308,4 @@ - {fileID: 318061306} - {fileID: 1190838621} - {fileID: 1162250618} + - {fileID: 2140657252} diff --git a/Assets/Scripts/Cache/TilePool.cs b/Assets/Scripts/Cache/TilePool.cs new file mode 100644 index 0000000..ff0f7c7 --- /dev/null +++ b/Assets/Scripts/Cache/TilePool.cs @@ -0,0 +1,79 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Tilemaps; + +public class TilePool : MonoBehaviour +{ + private Dictionary<Vector3Int, TileData> pooledTileData = new Dictionary<Vector3Int, TileData>(); + private int poolSize = 10000; // Adjust pool size as needed + private Queue<TileData> availableTileData = new Queue<TileData>(); + private HashSet<Vector3Int> activeTilePositions = new HashSet<Vector3Int>(); + + // Cache for rule tile data + private Dictionary<CustomRuleTile, TileBase[]> tileDataCache = new Dictionary<CustomRuleTile, TileBase[]>(); + + [System.Serializable] + public class TileData + { + public Vector3Int position; + public CustomRuleTile tileType; + public bool isActive; + } + + public void InitializePool(CustomRuleTile defaultTile) + { + pooledTileData.Clear(); + availableTileData.Clear(); + activeTilePositions.Clear(); + + // Pre-calculate tile data for the default tile + if (!tileDataCache.ContainsKey(defaultTile)) + { + var tileData = new TileBase[9]; // Cache neighboring tile configurations + tileDataCache[defaultTile] = tileData; + } + + for (int i = 0; i < poolSize; i++) + { + availableTileData.Enqueue(new TileData + { + tileType = defaultTile, + isActive = false + }); + } + } + + public TileData GetTileData(Vector3Int position, CustomRuleTile tileType) + { + if (!activeTilePositions.Contains(position)) + { + if (availableTileData.Count > 0) + { + var tileData = availableTileData.Dequeue(); + tileData.position = position; + tileData.tileType = tileType; + tileData.isActive = true; + pooledTileData[position] = tileData; + activeTilePositions.Add(position); + return tileData; + } + } + return pooledTileData.GetValueOrDefault(position); + } + + public void ReturnTileData(Vector3Int position) + { + if (pooledTileData.TryGetValue(position, out TileData tileData)) + { + tileData.isActive = false; + availableTileData.Enqueue(tileData); + pooledTileData.Remove(position); + } + } + + public bool HasTileAt(Vector3Int position) + { + return pooledTileData.ContainsKey(position) && pooledTileData[position].isActive; + } +} diff --git a/Assets/Scripts/Cache/TilePool.cs.meta b/Assets/Scripts/Cache/TilePool.cs.meta new file mode 100644 index 0000000..eee2bc1 --- /dev/null +++ b/Assets/Scripts/Cache/TilePool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ca130bf5edb364941807e577a7d1b7d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/CustomRuleTile.cs b/Assets/Scripts/CustomRuleTile.cs index e80cac2..1043714 100644 --- a/Assets/Scripts/CustomRuleTile.cs +++ b/Assets/Scripts/CustomRuleTile.cs @@ -10,25 +10,65 @@ { public List<TileBase> siblings = new List<TileBase>(); + private Dictionary<int, bool> ruleMatchCache = new Dictionary<int, bool>(); public class Neighbor : RuleTile.TilingRule.Neighbor { public const int Sibling = 3; } public override bool RuleMatch(int neighbor, TileBase tile) { + // Create a unique key for caching based on neighbor and tile + int cacheKey = neighbor; + if (tile != null) + { + cacheKey = cacheKey * 23 + tile.GetInstanceID(); // Use prime number to reduce collisions + } + + // Check if we have a cached result + if (ruleMatchCache.TryGetValue(cacheKey, out bool cachedResult)) + { + return cachedResult; + } + + // If not cached, compute the result + bool result; + // Handle null tiles if (tile == null) - return neighbor == RuleTile.TilingRule.Neighbor.NotThis; - - // Always allow connections to siblings or self, regardless of surrounding tiles - if (tile == this || siblings.Contains(tile)) - return neighbor == RuleTile.TilingRule.Neighbor.This; - + { + result = neighbor == RuleTile.TilingRule.Neighbor.NotThis; + } + // Always allow connections to siblings or self + else if (tile == this || siblings.Contains(tile)) + { + result = neighbor == RuleTile.TilingRule.Neighbor.This; + } // For Sibling type explicitly - if (neighbor == Neighbor.Sibling) - return siblings.Contains(tile); - + else if (neighbor == Neighbor.Sibling) + { + result = siblings.Contains(tile); + } // For any other case, use base behavior - return base.RuleMatch(neighbor, tile); + else + { + result = base.RuleMatch(neighbor, tile); + } + + // Cache the result + ruleMatchCache[cacheKey] = result; + return result; + } + + // Add method to clear cache when needed (e.g., when rules change or on chunk unload) + public void ClearRuleCache() + { + ruleMatchCache.Clear(); + } + + // Override RefreshTile to clear cache for that specific tile position + public override void RefreshTile(Vector3Int position, ITilemap tilemap) + { + ClearRuleCache(); // Clear cache before refresh to ensure proper updates + base.RefreshTile(position, tilemap); } } \ No newline at end of file diff --git a/Assets/Scripts/GenerateTileMap.cs b/Assets/Scripts/GenerateTileMap.cs index 7bf0667..28d1cc4 100644 --- a/Assets/Scripts/GenerateTileMap.cs +++ b/Assets/Scripts/GenerateTileMap.cs @@ -1,6 +1,9 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; +using Unity.Collections; +using Unity.Jobs; using Unity.VisualScripting; using UnityEngine; using UnityEngine.Tilemaps; @@ -52,6 +55,10 @@ private Transform playerTransform; // Reference to player/camera private Vector2Int lastLoadedChunk; private GameManager gameManager; + private Dictionary<Vector2Int, HashSet<Vector3Int>> activeChunkTiles = new Dictionary<Vector2Int, HashSet<Vector3Int>>(); + public TilePool tilePool; // Reference to the TilePool script + private bool isGenerating = false; + private void Awake() { @@ -70,20 +77,38 @@ // Position adjusted to center horizontally, but align top at y=0 transform.position = new Vector3((maxWidth / 2) * -1, -1, transform.position.z); + tilePool.InitializePool(forestRuleTile); LoadGenerateablesFromResources(); } private void Update() { - if (playerTransform == null) return; - Debug.Log($"Player Position: {playerTransform.position}"); + if (playerTransform == null || isGenerating) return; + Vector2Int currentChunk = GetChunkPosition(playerTransform.position); - Debug.Log($"Current Chunk: {currentChunk}"); if (currentChunk != lastLoadedChunk) { - StartCoroutine(UpdateLoadedChunks(currentChunk)); + isGenerating = true; + StopAllCoroutines(); + + var chunksToUnload = loadedChunks.Keys + .Where(chunk => Mathf.Abs(chunk.x - currentChunk.x) > LOAD_DISTANCE || + Mathf.Abs(chunk.y - currentChunk.y) > LOAD_DISTANCE) + .ToList(); + + foreach (var chunk in chunksToUnload) + { + UnloadChunk(chunk); + } + + StartCoroutine(UpdateLoadedChunksWithCompletion(currentChunk)); lastLoadedChunk = currentChunk; } + } + private IEnumerator UpdateLoadedChunksWithCompletion(Vector2Int currentChunk) + { + yield return UpdateLoadedChunks(currentChunk); + isGenerating = false; } // When a tile is destroyed in a chunk, update GameManager's list private void AddDestroyedTile(Vector3Int tilePos) @@ -92,6 +117,45 @@ { gameManager.destroyedTiles.Add(tilePos); } + } + private void SetTileWithPool(Vector3Int position, CustomRuleTile tileType) + { + var chunk = new Vector2Int( + Mathf.FloorToInt(position.x / (float)CHUNK_SIZE), + Mathf.FloorToInt(position.y / (float)CHUNK_SIZE) + ); + + if (!activeChunkTiles.ContainsKey(chunk)) + { + activeChunkTiles[chunk] = new HashSet<Vector3Int>(); + } + + var tileData = tilePool.GetTileData(position, tileType); + if (tileData != null) + { + tilemap.SetTile(position, tileData.tileType); + activeChunkTiles[chunk].Add(position); + + // Refresh neighboring tiles to maintain proper rule tile connections + for (int nx = -1; nx <= 1; nx++) + { + for (int ny = -1; ny <= 1; ny++) + { + if (nx == 0 && ny == 0) continue; + Vector3Int neighbor = new Vector3Int(position.x + nx, position.y + ny, position.z); + if (tilemap.HasTile(neighbor)) + { + tilemap.RefreshTile(neighbor); + } + } + } + } + } + + private void RemoveTileWithPool(Vector3Int position) + { + tilemap.SetTile(position, null); + tilePool.ReturnTileData(position); } // When checking if a tile is destroyed, use GameManager's list @@ -123,16 +187,25 @@ private void UnloadChunk(Vector2Int chunk) { + if (!loadedChunks.ContainsKey(chunk)) return; + int startX = chunk.x * CHUNK_SIZE; int startY = chunk.y * CHUNK_SIZE; - for (int x = startX; x < startX + CHUNK_SIZE; x++) + // Clear rule cache for this chunk + forestRuleTile.ClearRuleCache(); + + // Remove all tiles in the chunk + if (activeChunkTiles.TryGetValue(chunk, out var tiles)) { - for (int y = startY; y > startY - CHUNK_SIZE; y--) + foreach (var pos in tiles) { - tilemap.SetTile(new Vector3Int(x, y), null); + RemoveTileWithPool(pos); } + activeChunkTiles.Remove(chunk); } + + loadedChunks.Remove(chunk); } private void LoadGenerateablesFromResources() { @@ -232,42 +305,91 @@ private IEnumerator GenerateChunk(Vector2Int chunk, List<Vector3Int> destroyedTiles) { int startX = chunk.x * CHUNK_SIZE; - int startY = (chunk.y * CHUNK_SIZE); + int startY = chunk.y * CHUNK_SIZE; - // Generate ground in chunk - for (int x = startX; x < startX + CHUNK_SIZE; x++) + // Clear rule cache for this chunk's area + forestRuleTile.ClearRuleCache(); + + NativeArray<bool> terrainMap = default; + NativeArray<float> noiseMap = default; + + try { - if (x < 1 || x >= maxWidth) continue; + terrainMap = new NativeArray<bool>(CHUNK_SIZE * CHUNK_SIZE, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); + noiseMap = new NativeArray<float>(CHUNK_SIZE * CHUNK_SIZE, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); - for (int y = startY; y > startY - CHUNK_SIZE; y--) + var job = new TerrainGenerationJob { - if (y < -groundDepth || y >= 0) continue; - Vector3Int tilePos = new Vector3Int(x, y); - if (IsTileDestroyed(tilePos)) - continue; + ChunkStartX = startX, + ChunkStartY = startY, + ChunkSize = CHUNK_SIZE, + Scale = scale, + OffsetX = offsetX, + OffsetY = offsetY, + MaxWidth = maxWidth, + MaxDepth = maxDepth, + TerrainMap = terrainMap, + NoiseMap = noiseMap + }; - float xPerlin = ((float)x / maxWidth) * scale + offsetX; - float yPerlin = ((float)Mathf.Abs(y) / maxDepth) * scale + offsetY; - float perlinNoise = Mathf.PerlinNoise(xPerlin, yPerlin); + // Schedule and complete job immediately to prevent frame lifetime issues + job.Schedule(CHUNK_SIZE * CHUNK_SIZE, 32).Complete(); - if (perlinNoise <= 0.7f) + // Copy data from job before yielding + var tilesToUpdate = new List<(Vector3Int pos, CustomRuleTile tile)>(); + + for (int i = 0; i < terrainMap.Length; i++) + { + if (terrainMap[i]) { - tilemap.SetTile(tilePos, forestRuleTile); + int x = startX + (i % CHUNK_SIZE); + int y = startY - (i / CHUNK_SIZE); + Vector3Int tilePos = new Vector3Int(x, y); + + if (!IsTileDestroyed(tilePos)) + { + tilesToUpdate.Add((tilePos, forestRuleTile)); + } } } - } - // Generate ores in chunk - if (generateables != null) - { - yield return GenerateOresInChunk(chunk, destroyedTiles); + // Dispose native arrays before yielding + terrainMap.Dispose(); + noiseMap.Dispose(); + terrainMap = default; + noiseMap = default; + + // Now we can safely yield and process tiles + const int BATCH_SIZE = 32; + for (int i = 0; i < tilesToUpdate.Count; i += BATCH_SIZE) + { + int batchCount = Math.Min(BATCH_SIZE, tilesToUpdate.Count - i); + for (int j = 0; j < batchCount; j++) + { + var (pos, tile) = tilesToUpdate[i + j]; + SetTileWithPool(pos, tile); + } + yield return null; + } + + // Generate ores + if (generateables != null) + { + yield return GenerateOresInChunk(chunk, destroyedTiles); + } + + // Generate borders if needed + if (startX == 0 || startX + CHUNK_SIZE >= maxWidth || + startY == 0 || startY - CHUNK_SIZE <= -groundDepth) + { + yield return GenerateBorders(); + } } - // Don't generate borders for each chunk - // Only generate borders when at world edges - if (startX == 0 || startX + CHUNK_SIZE >= maxWidth || - startY == 0 || startY - CHUNK_SIZE <= -groundDepth) + finally { - yield return GenerateBorders(); + // Ensure arrays are always disposed + if (terrainMap.IsCreated) terrainMap.Dispose(); + if (noiseMap.IsCreated) noiseMap.Dispose(); } } diff --git a/Assets/Scripts/Jobs.meta b/Assets/Scripts/Jobs.meta new file mode 100644 index 0000000..7bc6c47 --- /dev/null +++ b/Assets/Scripts/Jobs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c530f47158920ad4890aa10e7869b090 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Jobs/OreGnerationJob.cs b/Assets/Scripts/Jobs/OreGnerationJob.cs new file mode 100644 index 0000000..1f717f2 --- /dev/null +++ b/Assets/Scripts/Jobs/OreGnerationJob.cs @@ -0,0 +1,33 @@ +using System.Collections; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +public struct OreGnerationJob : IJobParallelFor +{ + [ReadOnly] public int ChunkStartX; + [ReadOnly] public int ChunkStartY; + [ReadOnly] public int ChunkSize; + [ReadOnly] public int Weight; + [ReadOnly] public int ClusterWeight; + [ReadOnly] public float OffsetX; + [ReadOnly] public float OffsetY; + [ReadOnly] public int MaxWidth; + [ReadOnly] public int MaxDepth; + + public NativeArray<bool> OreMap; + + public void Execute(int index) + { + int x = ChunkStartX + (index % ChunkSize); + int y = ChunkStartY - (index / ChunkSize); + + float xPerlin = ((float)x / MaxWidth) * ClusterWeight + OffsetX; + float yPerlin = ((float)Mathf.Abs(y) / MaxDepth) * ClusterWeight + OffsetY; + float perlinNoise = noise.snoise(new float2(xPerlin, yPerlin)); + + OreMap[index] = perlinNoise <= (1f / (float)Weight); + } +} diff --git a/Assets/Scripts/Jobs/OreGnerationJob.cs.meta b/Assets/Scripts/Jobs/OreGnerationJob.cs.meta new file mode 100644 index 0000000..21d57d2 --- /dev/null +++ b/Assets/Scripts/Jobs/OreGnerationJob.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 25bcaf00876f10b44ade0b57e8159cbe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Jobs/TerrainGenerationJob.cs b/Assets/Scripts/Jobs/TerrainGenerationJob.cs new file mode 100644 index 0000000..afb7db7 --- /dev/null +++ b/Assets/Scripts/Jobs/TerrainGenerationJob.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +public struct TerrainGenerationJob : IJobParallelFor +{ + [ReadOnly] public int ChunkStartX; + [ReadOnly] public int ChunkStartY; + [ReadOnly] public int ChunkSize; + [ReadOnly] public float Scale; + [ReadOnly] public float OffsetX; + [ReadOnly] public float OffsetY; + [ReadOnly] public int MaxWidth; + [ReadOnly] public int MaxDepth; + + [WriteOnly] public NativeArray<bool> TerrainMap; + [WriteOnly] public NativeArray<float> NoiseMap; + + public void Execute(int index) + { + int x = ChunkStartX + (index % ChunkSize); + int y = ChunkStartY - (index / ChunkSize); + + if (x < 1 || x >= MaxWidth || y < -MaxDepth || y >= 0) + { + TerrainMap[index] = false; + NoiseMap[index] = 0f; + return; + } + + float xPerlin = ((float)x / MaxWidth) * Scale + OffsetX; + float yPerlin = ((float)Mathf.Abs(y) / MaxDepth) * Scale + OffsetY; + float perlinNoise = noise.snoise(new float2(xPerlin, yPerlin)); // Using Unity.Mathematics noise + + NoiseMap[index] = perlinNoise; + TerrainMap[index] = perlinNoise <= 0.7f; + } +} diff --git a/Assets/Scripts/Jobs/TerrainGenerationJob.cs.meta b/Assets/Scripts/Jobs/TerrainGenerationJob.cs.meta new file mode 100644 index 0000000..ceeab7a --- /dev/null +++ b/Assets/Scripts/Jobs/TerrainGenerationJob.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 165d406cda8a781498fac74f1348fd81 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: -- Gitblit v1.9.3