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