using System.Collections.Generic; using UnityEngine; namespace Minigames.DivingForPictures { /// /// Manages a pool of trench tile objects to reduce garbage collection overhead. /// Optimized for handling a large number of different prefab types. /// public class TrenchTilePool : MonoBehaviour { [Tooltip("Whether to pre-instantiate tiles during initialization or create them on demand")] public bool preInstantiateTiles = false; [Tooltip("Initial number of tiles to pre-instantiate per prefab (if preInstantiateTiles is true)")] public int initialTilesPerPrefab = 2; [Tooltip("Maximum number of tiles to keep in the pool across all prefab types")] public int totalMaxPoolSize = 50; [Tooltip("Maximum number of inactive instances to keep per prefab type")] public int maxPerPrefabPoolSize = 5; [Tooltip("Maximum number of tiles to keep in the pool (legacy, use maxPerPrefabPoolSize instead)")] public int maxPoolSize = 5; private Dictionary> pooledTiles = new Dictionary>(); private Dictionary prefabUsageCount = new Dictionary(); private List tilePrefabs; private int totalPooledCount = 0; /// /// Initialize the pool with the tile prefabs /// /// List of tile prefabs to use public void Initialize(List prefabs) { tilePrefabs = prefabs; // Initialize usage tracking for (int i = 0; i < prefabs.Count; i++) { prefabUsageCount[i] = 0; pooledTiles[i] = new Stack(); } // Pre-instantiate tiles only if enabled if (preInstantiateTiles) { // Calculate how many to pre-instantiate based on available pool size int totalToCreate = Mathf.Min(totalMaxPoolSize, prefabs.Count * initialTilesPerPrefab); int perPrefab = Mathf.Max(1, totalToCreate / prefabs.Count); for (int i = 0; i < prefabs.Count; i++) { for (int j = 0; j < perPrefab; j++) { if (totalPooledCount >= totalMaxPoolSize) break; CreateNewTile(i); } } } } /// /// Creates a new tile instance and adds it to the pool /// private GameObject CreateNewTile(int prefabIndex) { if (tilePrefabs == null || prefabIndex >= tilePrefabs.Count) { Debug.LogError("TrenchTilePool: Invalid prefab index or tilePrefabs is null!"); return null; } GameObject prefab = tilePrefabs[prefabIndex]; GameObject tile = Instantiate(prefab, transform); tile.SetActive(false); if (!pooledTiles.ContainsKey(prefabIndex)) { pooledTiles[prefabIndex] = new Stack(); } pooledTiles[prefabIndex].Push(tile); totalPooledCount++; return tile; } /// /// Gets a tile from the pool, or creates a new one if the pool is empty /// /// A tile instance ready to use public GameObject GetTile(int prefabIndex) { GameObject tile; // Track usage frequency if (prefabUsageCount.ContainsKey(prefabIndex)) { prefabUsageCount[prefabIndex]++; } else { prefabUsageCount[prefabIndex] = 1; } if (pooledTiles.ContainsKey(prefabIndex) && pooledTiles[prefabIndex].Count > 0) { tile = pooledTiles[prefabIndex].Pop(); totalPooledCount--; } else { // Create new tile without adding to pool GameObject prefab = tilePrefabs[prefabIndex]; tile = Instantiate(prefab, transform); } tile.SetActive(true); return tile; } /// /// Returns a tile to the pool /// /// The tile to return to the pool /// The index of the prefab this tile was created from public void ReturnTile(GameObject tile, int prefabIndex) { if (tile == null) return; // Check if we're under the maximum pool size for this prefab type bool keepTile = totalPooledCount < totalMaxPoolSize; // Additional constraint: don't keep too many of any single prefab type if (pooledTiles.ContainsKey(prefabIndex) && pooledTiles[prefabIndex].Count >= maxPerPrefabPoolSize) { keepTile = false; } if (keepTile) { tile.SetActive(false); tile.transform.SetParent(transform); if (!pooledTiles.ContainsKey(prefabIndex)) { pooledTiles[prefabIndex] = new Stack(); } pooledTiles[prefabIndex].Push(tile); totalPooledCount++; } else { Destroy(tile); } } /// /// Trims the pool to remove excess objects /// Can be called periodically or when memory pressure is high /// public void TrimExcess() { // If we're under the limit, no need to trim if (totalPooledCount <= totalMaxPoolSize) return; // Calculate how many to remove int excessCount = totalPooledCount - totalMaxPoolSize; // Get prefab indices sorted by usage (least used first) List> sortedUsage = new List>(prefabUsageCount); sortedUsage.Sort((a, b) => a.Value.CompareTo(b.Value)); // Remove tiles from least used prefabs first foreach (var usage in sortedUsage) { int prefabIndex = usage.Key; if (!pooledTiles.ContainsKey(prefabIndex) || pooledTiles[prefabIndex].Count == 0) continue; // How many to remove from this prefab type int toRemove = Mathf.Min(pooledTiles[prefabIndex].Count, excessCount); for (int i = 0; i < toRemove; i++) { if (pooledTiles[prefabIndex].Count == 0) break; GameObject tile = pooledTiles[prefabIndex].Pop(); Destroy(tile); totalPooledCount--; excessCount--; if (excessCount <= 0) return; } } } /// /// Logs pool statistics to the console /// public void LogPoolStats() { Debug.Log($"[TrenchTilePool] Total pooled objects: {totalPooledCount}/{totalMaxPoolSize}"); string prefabDetails = ""; int index = 0; foreach (var entry in pooledTiles) { int prefabIndex = entry.Key; int count = entry.Value.Count; int usageCount = prefabUsageCount.ContainsKey(prefabIndex) ? prefabUsageCount[prefabIndex] : 0; string prefabName = prefabIndex < tilePrefabs.Count ? tilePrefabs[prefabIndex].name : "Unknown"; prefabDetails += $"\n - {prefabName}: {count} pooled, {usageCount} usages"; // Limit the output to avoid too much text if (++index >= 10 && pooledTiles.Count > 10) { prefabDetails += $"\n - ...and {pooledTiles.Count - 10} more prefab types"; break; } } Debug.Log($"[TrenchTilePool] Pool details:{prefabDetails}"); } #if UNITY_EDITOR private float _lastLogTime = 0f; void Update() { // Log pool stats every 5 seconds if in the editor if (Time.time - _lastLogTime > 5f) { LogPoolStats(); _lastLogTime = Time.time; } } #endif } }