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
}
}