Working generic object pooling, pool monitor editor tool and batch component adder editor tool

This commit is contained in:
Michal Pikulski
2025-09-16 15:02:50 +02:00
parent bcc6f05058
commit 75be338065
26 changed files with 1393 additions and 469 deletions

View File

@@ -1,7 +1,10 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using Minigames.DivingForPictures;
using System;
using System.Reflection;
using System.Collections;
using Pooling;
namespace Editor.Utilities
{
@@ -11,8 +14,8 @@ namespace Editor.Utilities
private bool autoRefresh = true;
private float refreshInterval = 1.0f;
private float lastRefreshTime;
private bool showTrenchTiles = true;
private bool showBubbles = true;
private bool showSinglePrefabPools = true;
private bool showMultiPrefabPools = true;
[MenuItem("Tools/Pool Monitor")]
public static void ShowWindow()
@@ -42,8 +45,8 @@ namespace Editor.Utilities
// Display toggles for showing different pool types
EditorGUILayout.BeginHorizontal();
showTrenchTiles = EditorGUILayout.ToggleLeft("Show Trench Tile Pools", showTrenchTiles, GUILayout.Width(200));
showBubbles = EditorGUILayout.ToggleLeft("Show Bubble Pools", showBubbles, GUILayout.Width(200));
showSinglePrefabPools = EditorGUILayout.ToggleLeft("Show Single Prefab Pools", showSinglePrefabPools, GUILayout.Width(200));
showMultiPrefabPools = EditorGUILayout.ToggleLeft("Show Multi-Prefab Pools", showMultiPrefabPools, GUILayout.Width(200));
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
@@ -81,25 +84,31 @@ namespace Editor.Utilities
{
if (!Application.isPlaying) return;
// Call LogPoolStats on all pool instances to update their stats in the console
if (showTrenchTiles)
// Find all pool types and call LogPoolStats
if (showSinglePrefabPools)
{
TrenchTilePool[] tilePools = Object.FindObjectsByType<TrenchTilePool>(FindObjectsSortMode.None);
foreach (var pool in tilePools)
{
pool.LogPoolStats();
}
}
if (showBubbles)
{
BubblePool[] bubblePools = Object.FindObjectsByType<BubblePool>(FindObjectsSortMode.None);
foreach (var pool in bubblePools)
// Find all types that derive from BaseObjectPool<T>
foreach (var pool in FindObjectsOfBaseType(typeof(Component), typeof(BaseObjectPool<>)))
{
if (pool != null && pool.gameObject.activeInHierarchy)
{
// If BubblePool has a LogPoolStats method, call it
var logMethod = typeof(BubblePool).GetMethod("LogPoolStats");
var logMethod = pool.GetType().GetMethod("LogPoolStats");
if (logMethod != null)
{
logMethod.Invoke(pool, null);
}
}
}
}
if (showMultiPrefabPools)
{
// Find all types that derive from MultiPrefabPool<T>
foreach (var pool in FindObjectsOfBaseType(typeof(Component), typeof(MultiPrefabPool<>)))
{
if (pool != null && pool.gameObject.activeInHierarchy)
{
var logMethod = pool.GetType().GetMethod("LogPoolStats");
if (logMethod != null)
{
logMethod.Invoke(pool, null);
@@ -112,133 +121,312 @@ namespace Editor.Utilities
void DisplayPoolInfo()
{
EditorGUILayout.LabelField("Scene Statistics:", EditorStyles.boldLabel);
EditorGUILayout.LabelField($"Total GameObjects: {Object.FindObjectsByType<GameObject>(FindObjectsSortMode.None).Length}");
EditorGUILayout.LabelField($"Total GameObjects: {UnityEngine.Object.FindObjectsByType<GameObject>(FindObjectsSortMode.None).Length}");
EditorGUILayout.Space();
if (showTrenchTiles)
if (showSinglePrefabPools)
{
DisplayTrenchTilePoolInfo();
DisplaySinglePrefabPoolInfo();
}
if (showBubbles)
if (showMultiPrefabPools)
{
DisplayBubblePoolInfo();
DisplayMultiPrefabPoolInfo();
}
}
void DisplayTrenchTilePoolInfo()
void DisplaySinglePrefabPoolInfo()
{
TrenchTilePool[] pools = Object.FindObjectsByType<TrenchTilePool>(FindObjectsSortMode.None);
// Find all types that derive from BaseObjectPool<T>
Component[] pools = FindObjectsOfBaseType(typeof(Component), typeof(BaseObjectPool<>));
if (pools.Length == 0)
{
EditorGUILayout.HelpBox("No trench tile pools found in the scene.", MessageType.Info);
EditorGUILayout.HelpBox("No single prefab pools found in the scene.", MessageType.Info);
return;
}
else
EditorGUILayout.LabelField("Single Prefab Pools", EditorStyles.boldLabel);
foreach (var poolComponent in pools)
{
EditorGUILayout.LabelField("Trench Tile Pools", EditorStyles.boldLabel);
foreach (var pool in pools)
EditorGUILayout.LabelField($"Pool: {poolComponent.name} ({poolComponent.GetType().Name})", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
// Get private field values using reflection
Type poolType = poolComponent.GetType();
FieldInfo pooledObjectsField = poolType.GetField("pooledObjects",
BindingFlags.NonPublic | BindingFlags.Instance);
FieldInfo totalCreatedField = poolType.GetField("totalCreated",
BindingFlags.NonPublic | BindingFlags.Instance);
FieldInfo totalReturnedField = poolType.GetField("totalReturned",
BindingFlags.NonPublic | BindingFlags.Instance);
PropertyInfo maxPoolSizeProp = poolType.GetProperty("maxPoolSize") ??
poolType.GetField("maxPoolSize")?.DeclaringType.GetProperty("maxPoolSize");
PropertyInfo initialPoolSizeProp = poolType.GetProperty("initialPoolSize") ??
poolType.GetField("initialPoolSize")?.DeclaringType.GetProperty("initialPoolSize");
if (pooledObjectsField != null)
{
EditorGUILayout.LabelField($"Pool: {pool.name}", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
object pooledObjects = pooledObjectsField.GetValue(poolComponent);
int count = 0;
// Get private field values using reflection
System.Reflection.FieldInfo totalCountField = typeof(TrenchTilePool).GetField("totalPooledCount",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
System.Reflection.FieldInfo pooledTilesField = typeof(TrenchTilePool).GetField("pooledTiles",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
System.Reflection.FieldInfo prefabUsageField = typeof(TrenchTilePool).GetField("prefabUsageCount",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (totalCountField != null)
// Handle Stack<T>
if (pooledObjects is System.Collections.ICollection collection)
{
int totalCount = (int)totalCountField.GetValue(pool);
EditorGUILayout.LabelField($"Total Pooled Objects: {totalCount}/{pool.totalMaxPoolSize}");
count = collection.Count;
}
if (pooledTilesField != null && prefabUsageField != null)
int maxSize = 0;
if (maxPoolSizeProp != null)
{
var pooledTiles = pooledTilesField.GetValue(pool) as Dictionary<int, Stack<GameObject>>;
var usageCounts = prefabUsageField.GetValue(pool) as Dictionary<int, int>;
if (pooledTiles != null)
maxSize = (int)maxPoolSizeProp.GetValue(poolComponent);
}
else
{
FieldInfo maxPoolSizeField = poolType.GetField("maxPoolSize",
BindingFlags.Public | BindingFlags.Instance);
if (maxPoolSizeField != null)
{
EditorGUILayout.LabelField("Prefab Details:", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
foreach (var entry in pooledTiles)
maxSize = (int)maxPoolSizeField.GetValue(poolComponent);
}
}
EditorGUILayout.LabelField($"Pooled Objects: {count}/{maxSize}");
int initialSize = 0;
if (initialPoolSizeProp != null)
{
initialSize = (int)initialPoolSizeProp.GetValue(poolComponent);
}
else
{
FieldInfo initialPoolSizeField = poolType.GetField("initialPoolSize",
BindingFlags.Public | BindingFlags.Instance);
if (initialPoolSizeField != null)
{
initialSize = (int)initialPoolSizeField.GetValue(poolComponent);
}
}
EditorGUILayout.LabelField($"Initial Pool Size: {initialSize}");
if (totalCreatedField != null && totalReturnedField != null)
{
int created = (int)totalCreatedField.GetValue(poolComponent);
int returned = (int)totalReturnedField.GetValue(poolComponent);
EditorGUILayout.LabelField($"Created: {created}, Returned: {returned}");
}
}
// Try to find active objects of the pool's type
if (poolType.BaseType.IsGenericType)
{
Type elementType = poolType.BaseType.GetGenericArguments()[0];
// More accurately count only active objects in the current scene
int activeCount = 0;
// First, try to get a more accurate count from the current scene
foreach (var obj in UnityEngine.Object.FindObjectsByType(elementType, FindObjectsSortMode.None))
{
var comp = obj as Component;
if (comp != null && comp.gameObject.activeInHierarchy)
{
activeCount++;
}
}
EditorGUILayout.LabelField($"Active Objects (Current Scene): {activeCount}");
// Add a note about pooling status
if (activeCount > 0)
{
int pooledCount = 0;
if (pooledObjectsField != null)
{
object pooledObjects = pooledObjectsField.GetValue(poolComponent);
if (pooledObjects is ICollection collection)
{
int prefabIndex = entry.Key;
Stack<GameObject> stack = entry.Value;
int count = stack != null ? stack.Count : 0;
int usageCount = 0;
if (usageCounts != null && usageCounts.TryGetValue(prefabIndex, out int usage))
{
usageCount = usage;
}
EditorGUILayout.LabelField($"Prefab {prefabIndex}: {count} pooled, {usageCount} usages");
pooledCount = collection.Count;
}
}
EditorGUILayout.LabelField($"Pooling Efficiency: {pooledCount} ready in pool, {activeCount} active");
}
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
}
}
void DisplayMultiPrefabPoolInfo()
{
// Find all types that derive from MultiPrefabPool<T>
Component[] pools = FindObjectsOfBaseType(typeof(Component), typeof(MultiPrefabPool<>));
if (pools.Length == 0)
{
EditorGUILayout.HelpBox("No multi-prefab pools found in the scene.", MessageType.Info);
return;
}
EditorGUILayout.LabelField("Multi-Prefab Pools", EditorStyles.boldLabel);
foreach (var poolComponent in pools)
{
EditorGUILayout.LabelField($"Pool: {poolComponent.name} ({poolComponent.GetType().Name})", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
// Get private field values using reflection
Type poolType = poolComponent.GetType();
FieldInfo totalPooledCountField = poolType.GetField("totalPooledCount",
BindingFlags.NonPublic | BindingFlags.Instance);
FieldInfo pooledObjectsField = poolType.GetField("pooledObjects",
BindingFlags.NonPublic | BindingFlags.Instance);
FieldInfo prefabUsageField = poolType.GetField("prefabUsageCount",
BindingFlags.NonPublic | BindingFlags.Instance);
PropertyInfo totalMaxPoolSizeProp = poolType.GetProperty("totalMaxPoolSize") ??
poolType.GetField("totalMaxPoolSize")?.DeclaringType.GetProperty("totalMaxPoolSize");
if (totalPooledCountField != null && totalMaxPoolSizeProp != null)
{
int totalCount = (int)totalPooledCountField.GetValue(poolComponent);
int maxSize = 0;
if (totalMaxPoolSizeProp != null)
{
maxSize = (int)totalMaxPoolSizeProp.GetValue(poolComponent);
}
else
{
FieldInfo totalMaxPoolSizeField = poolType.GetField("totalMaxPoolSize",
BindingFlags.Public | BindingFlags.Instance);
if (totalMaxPoolSizeField != null)
{
maxSize = (int)totalMaxPoolSizeField.GetValue(poolComponent);
}
}
EditorGUILayout.LabelField($"Total Pooled Objects: {totalCount}/{maxSize}");
}
if (pooledObjectsField != null && prefabUsageField != null)
{
// This is more complex because we don't know the exact generic types
// Just show basic information
object pooledTiles = pooledObjectsField.GetValue(poolComponent);
object usageCounts = prefabUsageField.GetValue(poolComponent);
if (pooledTiles != null && pooledTiles is IDictionary poolDict)
{
EditorGUILayout.LabelField("Prefab Details:", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
foreach (DictionaryEntry entry in poolDict)
{
int prefabIndex = Convert.ToInt32(entry.Key);
object value = entry.Value;
int count = 0;
// Handle Stack<T>
if (value is ICollection collection)
{
count = collection.Count;
}
EditorGUI.indentLevel--;
int usageCount = 0;
if (usageCounts is IDictionary usageDict)
{
if (usageDict.Contains(entry.Key))
{
usageCount = Convert.ToInt32(usageDict[entry.Key]);
}
}
EditorGUILayout.LabelField($"Prefab {prefabIndex}: {count} pooled, {usageCount} usages");
}
EditorGUI.indentLevel--;
}
}
// Try to find active objects of the pool's type
if (poolType.BaseType.IsGenericType)
{
Type elementType = poolType.BaseType.GetGenericArguments()[0];
int activeCount = 0;
// Count active objects of the specific pool's component type
foreach (var obj in UnityEngine.Object.FindObjectsByType(elementType, FindObjectsSortMode.None))
{
var comp = obj as Component;
if (comp != null && comp.gameObject.activeInHierarchy)
{
activeCount++;
}
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
EditorGUILayout.LabelField($"Active Objects (Current Scene): {activeCount}");
// Add a note about pooling status
if (activeCount > 0 && totalPooledCountField != null)
{
int pooledCount = (int)totalPooledCountField.GetValue(poolComponent);
EditorGUILayout.LabelField($"Pooling Efficiency: {pooledCount} ready in pool, {activeCount} active");
}
}
EditorGUI.indentLevel--;
EditorGUILayout.Space();
}
}
void DisplayBubblePoolInfo()
/// <summary>
/// Finds all objects that derive from a generic base type
/// </summary>
private Component[] FindObjectsOfBaseType(Type baseComponentType, Type genericBaseType)
{
BubblePool[] pools = Object.FindObjectsByType<BubblePool>(FindObjectsSortMode.None);
if (pools.Length == 0)
List<Component> results = new List<Component>();
// Find all components in the scene
Component[] allComponents = UnityEngine.Object.FindObjectsByType<Component>(FindObjectsSortMode.None);
foreach (var component in allComponents)
{
EditorGUILayout.HelpBox("No bubble pools found in the scene.", MessageType.Info);
}
else
{
EditorGUILayout.LabelField("Bubble Pools", EditorStyles.boldLabel);
foreach (var pool in pools)
Type componentType = component.GetType();
// Check if this type derives from the generic base type
while (componentType != null && componentType != typeof(object))
{
EditorGUILayout.LabelField($"Pool: {pool.name}", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
// Get private field values using reflection
System.Reflection.FieldInfo pooledBubblesField = typeof(BubblePool).GetField("pooledBubbles",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (pooledBubblesField != null)
if (componentType.IsGenericType &&
componentType.GetGenericTypeDefinition() == genericBaseType)
{
var pooledBubbles = pooledBubblesField.GetValue(pool) as Stack<Bubble>;
int count = pooledBubbles != null ? pooledBubbles.Count : 0;
EditorGUILayout.LabelField($"Pooled Bubbles: {count}/{pool.maxPoolSize}");
EditorGUILayout.LabelField($"Initial Pool Size: {pool.initialPoolSize}");
results.Add(component);
break;
}
// Try to find active bubbles in the scene
Bubble[] activeBubbles = Object.FindObjectsByType<Bubble>(FindObjectsSortMode.None);
int activeBubbleCount = 0;
foreach (var bubble in activeBubbles)
// Also check for non-generic derived types
if (componentType.BaseType != null &&
componentType.BaseType.IsGenericType &&
componentType.BaseType.GetGenericTypeDefinition() == genericBaseType)
{
if (bubble.gameObject.activeInHierarchy)
{
activeBubbleCount++;
}
results.Add(component);
break;
}
EditorGUILayout.LabelField($"Active Bubbles: {activeBubbleCount}");
EditorGUI.indentLevel--;
EditorGUILayout.Space();
componentType = componentType.BaseType;
}
}
return results.ToArray();
}
}
}