com.amanotes.prefabpool 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.3.0] - 2026-06-12
4
+
5
+ ### Added
6
+ - `MaterialPool` static class — material instance pooling with borrow/return pattern by `borrowId` (`MaterialPool.cs`)
7
+ - `MaterialExtensions.SetAlpha(Material, float)` — convenience method for cloned material alpha modification (`MaterialPool.cs`)
8
+ - `PoolEntryDrawer` — custom `PropertyDrawer` for compact `PoolEntry` inspector display with inline id, prefab, and prewarm count (`PoolEntryDrawer.cs`)
9
+ - `AssemblyInfo.cs` — `InternalsVisibleTo` for test assembly (`AssemblyInfo.cs`)
10
+ - Unit test suite: `PrefabPoolLifecycleTests`, `PrefabPoolGroupTests`, `PrefabPoolPerformanceTests` (`Tests/Runtime/`)
11
+
12
+ ### Changed
13
+ - README.md rewritten with accurate API reference and quick start guide
14
+ - `Docs/PrefabPool-API.md` — complete technical API reference
15
+
16
+ ## [1.2.0] - 2026-05-20
17
+
18
+ ### Added
19
+ - `PoolEntry.EnsureCapacity(int count, Transform poolParent)` — pre-fills pool to target count without coroutine overhead
20
+ - `PoolEntry.PoolCount` property — returns current pooled instance count
21
+ - `PoolGroup.FindEntry(string prefabId)` — O(n) linear search replacing LINQ `Find()` with fallback support
22
+ - `PoolGroup.IsGameplay` field — marks group for runtime instantiation warnings
23
+ - Runtime instantiation warning in Editor/Development builds when `IsGameplay` is true
24
+
25
+ ### Changed
26
+ - `PoolEntry.Get()` / `Return()` uses per-instance `IPoolPrefab[]` cache (`_poolPrefabCache`) — eliminates `GetComponents<IPoolPrefab>()` GC per Get/Return call
27
+ - `PoolEntry.PreAllocate()` yields every 3 instances (`PreAllocBatchSize`) to spread across frames
28
+ - `PrefabPool.Get()` uses `PoolGroup.FindEntry()` instead of LINQ lambda
29
+
30
+ ### Fixed
31
+ - `PoolEntry.ReturnAll()` clears `_poolPrefabCache` to prevent stale references
32
+
3
33
  ## [1.1.0] - 2026-04-21
4
34
 
5
35
  ### Added
@@ -1,6 +1,6 @@
1
1
  # PrefabPool API Reference
2
2
 
3
- **Package:** `com.amanotes.prefabpool` | **Version:** 1.0.1 | **Namespace:** `Amanotes.Core`
3
+ **Package:** `com.amanotes.prefabpool` | **Version:** 1.2.0 | **Namespace:** `Amanotes.Core`
4
4
 
5
5
  Generic object pooling system to eliminate garbage collection spikes from frequent instantiation/destruction.
6
6
 
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # PrefabPool
2
2
 
3
3
  **Package:** `com.amanotes.prefabpool`
4
- **Version:** 1.0.3
4
+ **Version:** 1.3.0
5
5
 
6
6
  Generic object pooling system for Unity with lifecycle callbacks and material pooling support.
7
7
 
@@ -30,7 +30,7 @@ Add the package to your Unity project via `Packages/manifest.json`:
30
30
  ```json
31
31
  {
32
32
  "dependencies": {
33
- "com.amanotes.prefabpool": "1.0.3"
33
+ "com.amanotes.prefabpool": "1.3.0"
34
34
  },
35
35
  "scopedRegistries": [
36
36
  {
@@ -16,19 +16,24 @@ namespace Amanotes.Core
16
16
  var tilePrefabProp = property.FindPropertyRelative(nameof(PoolEntry.prefab));
17
17
  var prewarmProp = property.FindPropertyRelative(nameof(PoolEntry.prewarmCount));
18
18
 
19
- var thirdWidth = position.width / 3f - 2f;
20
- var nameRect = new Rect(position.x, position.y, thirdWidth, EditorGUIUtility.singleLineHeight);
21
- var prefabRect = new Rect(position.x + thirdWidth + 3f, position.y, thirdWidth, EditorGUIUtility.singleLineHeight);
22
- var sliderRect = new Rect(position.x + (thirdWidth + 3f) * 2, position.y, thirdWidth, EditorGUIUtility.singleLineHeight);
19
+ const float gap = 2f;
20
+ const float prewarmWidth = 80f;
21
+ var remaining = position.width - prewarmWidth - gap * 2;
22
+ var nameWidth = remaining * 0.35f;
23
+ var prefabWidth = remaining * 0.65f;
24
+
25
+ var nameRect = new Rect(position.x, position.y, nameWidth, EditorGUIUtility.singleLineHeight);
26
+ var prefabRect = new Rect(nameRect.xMax + gap, position.y, prefabWidth, EditorGUIUtility.singleLineHeight);
27
+ var sliderRect = new Rect(prefabRect.xMax + gap, position.y, prewarmWidth, EditorGUIUtility.singleLineHeight);
23
28
 
24
29
  EditorGUI.PropertyField(nameRect, tileNameProp, GUIContent.none);
25
30
  EditorGUI.PropertyField(prefabRect, tilePrefabProp, GUIContent.none);
26
31
 
27
32
  EditorGUI.BeginChangeCheck();
28
- var sliderValue = EditorGUI.IntSlider(sliderRect, prewarmProp.intValue, 0, 10);
33
+ var newValue = EditorGUI.IntField(sliderRect, prewarmProp.intValue);
29
34
  if (EditorGUI.EndChangeCheck())
30
35
  {
31
- prewarmProp.intValue = sliderValue;
36
+ prewarmProp.intValue = Mathf.Max(0, newValue);
32
37
  }
33
38
 
34
39
  EditorGUI.EndProperty();
@@ -22,8 +22,24 @@ namespace Amanotes.Core
22
22
  [NonSerialized] private readonly List<GameObject> _pool = new List<GameObject>();
23
23
  [NonSerialized] private PoolGroup _parentGroup;
24
24
 
25
+ // Per-instance IPoolPrefab cache — components don't change on pooled prefabs
26
+ [NonSerialized] private readonly Dictionary<int, IPoolPrefab[]> _poolPrefabCache = new Dictionary<int, IPoolPrefab[]>(32);
27
+ [NonSerialized] private static readonly List<IPoolPrefab> _tempPoolPrefabs = new List<IPoolPrefab>(8);
28
+
25
29
  public void SetParentGroup(PoolGroup parentGroup) => _parentGroup = parentGroup;
26
30
 
31
+ private IPoolPrefab[] GetCachedPoolPrefabs(GameObject go)
32
+ {
33
+ var instanceId = go.GetInstanceID();
34
+ if (_poolPrefabCache.TryGetValue(instanceId, out var cached)) return cached;
35
+
36
+ go.GetComponents(_tempPoolPrefabs);
37
+ cached = _tempPoolPrefabs.Count > 0 ? _tempPoolPrefabs.ToArray() : System.Array.Empty<IPoolPrefab>();
38
+ _tempPoolPrefabs.Clear();
39
+ _poolPrefabCache[instanceId] = cached;
40
+ return cached;
41
+ }
42
+
27
43
  public GameObject Get(Transform poolParent, Transform spawnParent, Vector3 localPosition)
28
44
  {
29
45
  GameObject resultGameObject = null;
@@ -44,9 +60,12 @@ namespace Amanotes.Core
44
60
  bool fromPool = resultGameObject != null;
45
61
  if (resultGameObject == null)
46
62
  {
47
- // Create prefab inside the pool's transform
48
63
  resultGameObject = UnityObject.Instantiate(prefab, poolParent.position, Quaternion.identity, poolParent);
49
64
  resultGameObject.hideFlags = HideFlags.DontSave;
65
+ #if UNITY_EDITOR || DEVELOPMENT_BUILD
66
+ if (_parentGroup != null && _parentGroup.IsGameplay)
67
+ Debug.LogWarning($"[Pool] Runtime instantiate '{id}' — prewarm undercount");
68
+ #endif
50
69
  }
51
70
 
52
71
  var t = resultGameObject.transform;
@@ -55,11 +74,8 @@ namespace Amanotes.Core
55
74
  t.localRotation = Quaternion.identity;
56
75
  resultGameObject.SetActive(true);
57
76
 
58
- var poolComponents = resultGameObject.GetComponents<IPoolPrefab>();
59
- foreach (var pp in poolComponents)
60
- {
61
- pp.OnGet();
62
- }
77
+ var prefabs = GetCachedPoolPrefabs(resultGameObject);
78
+ for (var i = 0; i < prefabs.Length; i++) prefabs[i].OnGet();
63
79
 
64
80
  return resultGameObject;
65
81
  }
@@ -68,11 +84,8 @@ namespace Amanotes.Core
68
84
  {
69
85
  if (gameObject == null) return;
70
86
 
71
- var poolComponents = gameObject.GetComponents<IPoolPrefab>();
72
- foreach (var pp in poolComponents)
73
- {
74
- pp.OnReturn();
75
- }
87
+ var prefabs = GetCachedPoolPrefabs(gameObject);
88
+ for (var i = 0; i < prefabs.Length; i++) prefabs[i].OnReturn();
76
89
 
77
90
  var t = gameObject.transform;
78
91
  gameObject.SetActive(false);
@@ -94,10 +107,26 @@ namespace Amanotes.Core
94
107
  }
95
108
  }
96
109
  _pool.Clear();
110
+ _poolPrefabCache.Clear();
97
111
  }
98
112
 
99
113
  public void PurgeDestroyed() => _pool.RemoveAll(obj => obj == null);
100
114
 
115
+ public int PoolCount => _pool.Count;
116
+
117
+ public void EnsureCapacity(int count, Transform poolParent)
118
+ {
119
+ while (_pool.Count < count)
120
+ {
121
+ var instance = UnityObject.Instantiate(prefab, poolParent.position, Quaternion.identity, poolParent);
122
+ instance.hideFlags = HideFlags.DontSave;
123
+ instance.SetActive(false);
124
+ _pool.Add(instance);
125
+ }
126
+ }
127
+
128
+ private const int PreAllocBatchSize = 3;
129
+
101
130
  public IEnumerator PreAllocate(Transform poolParent)
102
131
  {
103
132
  for (var i = 0; i < prewarmCount; i++)
@@ -106,6 +135,9 @@ namespace Amanotes.Core
106
135
  instance.hideFlags = HideFlags.DontSave;
107
136
  instance.SetActive(false);
108
137
  _pool.Add(instance);
138
+
139
+ if ((i + 1) % PreAllocBatchSize == 0)
140
+ yield return null;
109
141
  }
110
142
 
111
143
  yield return null;
@@ -120,6 +152,7 @@ namespace Amanotes.Core
120
152
 
121
153
  [NonSerialized] private IPrefabPool _prefabPool;
122
154
  [NonSerialized] private Transform _spawnParent;
155
+ [NonSerialized] public bool IsGameplay;
123
156
 
124
157
  public void Register(IPrefabPool prefabPool, Transform spawnParent)
125
158
  {
@@ -144,6 +177,13 @@ namespace Amanotes.Core
144
177
  return _prefabPool?.Get(this, prefabId, localPosition, customParent);
145
178
  }
146
179
 
180
+ public PoolEntry FindEntry(string prefabId)
181
+ {
182
+ for (var i = 0; i < entries.Count; i++)
183
+ if (entries[i].id == prefabId) return entries[i];
184
+ return fallbackEntry;
185
+ }
186
+
147
187
  public T Get<T>(string prefabId, Vector3 localPosition = default) where T : class
148
188
  {
149
189
  var go = Get(prefabId, localPosition);
@@ -338,13 +378,8 @@ namespace Amanotes.Core
338
378
  if (_isDestroyed || group == null) return null;
339
379
  if (string.IsNullOrEmpty(prefabId)) return null;
340
380
 
341
- var entry = group.entries.Find(e => e.id == prefabId);
342
- if (entry == null)
343
- {
344
- // Try fallback entry if available
345
- entry = group.fallbackEntry;
346
- if (entry == null) return null;
347
- }
381
+ var entry = group.FindEntry(prefabId);
382
+ if (entry == null) return null;
348
383
 
349
384
  var p = customParent == null ? group.SpawnParent : customParent;
350
385
  var result = entry.Get(transform, p, localPosition);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.amanotes.prefabpool",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "displayName": "[Ama] Prefab Pool",
5
5
  "description": "Generic object pooling system for Unity",
6
6
  "unity": "2022.3",