com.amanotes.prefabpool 1.0.3 → 1.2.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,21 +1,45 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.0] - 2026-05-20
4
+
5
+ ### Added
6
+ - `PoolEntry.EnsureCapacity(int count, Transform poolParent)` — pre-fills pool to target count without coroutine overhead
7
+ - `PoolEntry.PoolCount` property — returns current pooled instance count
8
+ - `PoolGroup.FindEntry(string prefabId)` — O(n) linear search replacing LINQ `Find()` with fallback support
9
+ - `PoolGroup.IsGameplay` field — marks group for runtime instantiation warnings
10
+ - Runtime instantiation warning in Editor/Development builds when `IsGameplay` is true
11
+
12
+ ### Changed
13
+ - `PoolEntry.Get()` / `Return()` uses per-instance `IPoolPrefab[]` cache (`_poolPrefabCache`) — eliminates `GetComponents<IPoolPrefab>()` GC per Get/Return call
14
+ - `PoolEntry.PreAllocate()` yields every 3 instances (`PreAllocBatchSize`) to spread across frames
15
+ - `PrefabPool.Get()` uses `PoolGroup.FindEntry()` instead of LINQ lambda
16
+
17
+ ### Fixed
18
+ - `PoolEntry.ReturnAll()` clears `_poolPrefabCache` to prevent stale references
19
+
20
+ ## [1.1.0] - 2026-04-21
21
+
22
+ ### Added
23
+ - `IPrefabPool.MarkShutdown()` — marks pool as shut down so Get/Return calls no-op safely
24
+ - `PrefabPool.MarkShutdown()` implementation — sets `_isDestroyed = true`
25
+
3
26
  ## [1.0.3] - 2026-04-07
4
27
 
5
28
  ### Fixed
6
- - `PoolEntry.Return`: renamed pooled GameObjects back to their entry `id` in Editor for easier hierarchy debugging (`PrefabPool.cs`)
29
+ - `PoolEntry.Return` renames pooled GameObjects back to entry `id` in Editor
7
30
 
8
31
  ## [1.0.2] - 2026-04-06
9
32
 
10
33
  ### Fixed
11
- - `PoolEntry.Return`: deactivate GameObject before `SetParent` to prevent `Cannot set the parent of the GameObject while activating or deactivating the parent GameObject` error during scene shutdown
12
- - `PoolEntry.Get`: explicit `SetActive(true)` to match — objects are now consistently inactive in pool, active when in use
13
- - `PoolEntry.PreAllocate`: prewarm objects are now spawned inactive, consistent with the return state
14
- - `PrefabPool.DestroyPool`: `_customPool` (Resize<T>) entries are now properly destroyed on shutdown
15
- - `PrefabPool.Initialize`: added `_isInitializing` guard to prevent double-prewarm if called concurrently
16
- - `PoolEntry.PurgeDestroyed`: new method, called on `Register` to remove stale destroyed-object refs when a PoolGroup ScriptableObject is reused across scene loads
17
- - `PrefabPool.PurgeStaleInstanceMap`: cleans up `_instanceTypeMap` entries for externally-destroyed objects (Editor only)
34
+ - `PoolEntry.Return` deactivates before `SetParent` to prevent parent activation error
35
+ - `PoolEntry.Get` explicit `SetActive(true)` for consistent pool state
36
+ - `PoolEntry.PreAllocate` prewarm objects spawned inactive
37
+ - `PrefabPool.DestroyPool` properly destroys `_customPool` entries
38
+ - `PrefabPool.Initialize` added `_isInitializing` guard against double-prewarm
39
+ - `PoolEntry.PurgeDestroyed` removes stale refs on `Register`
40
+ - `PrefabPool.PurgeStaleInstanceMap` cleans up externally-destroyed objects (Editor only)
18
41
 
19
42
  ## [1.0.1]
20
43
 
44
+ ### Added
21
45
  - Initial release
@@ -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.2.0
5
5
 
6
6
  Generic object pooling system for Unity with lifecycle callbacks and material pooling support.
7
7
 
@@ -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);
@@ -168,6 +208,12 @@ namespace Amanotes.Core
168
208
  GameObject Get(PoolGroup group, string prefabId, Vector3 localPosition = default, Transform customParent = null);
169
209
  void Return(PoolGroup group, GameObject gameObject);
170
210
  IEnumerator Initialize();
211
+ /// <summary>
212
+ /// Marks the pool as shut down so all subsequent Get/Return calls no-op safely.
213
+ /// Call this before detaching tiles during game shutdown to prevent SetParent errors
214
+ /// when the pool's hierarchy is being deactivated.
215
+ /// </summary>
216
+ void MarkShutdown();
171
217
  }
172
218
 
173
219
  public static class PrefabPoolExtensions
@@ -308,6 +354,8 @@ namespace Amanotes.Core
308
354
 
309
355
  public bool IsReady => _isReady;
310
356
 
357
+ public void MarkShutdown() => _isDestroyed = true;
358
+
311
359
  public void RegisterGroup(PoolGroup group, Transform spawnParent)
312
360
  {
313
361
  if (group == null) return;
@@ -330,13 +378,8 @@ namespace Amanotes.Core
330
378
  if (_isDestroyed || group == null) return null;
331
379
  if (string.IsNullOrEmpty(prefabId)) return null;
332
380
 
333
- var entry = group.entries.Find(e => e.id == prefabId);
334
- if (entry == null)
335
- {
336
- // Try fallback entry if available
337
- entry = group.fallbackEntry;
338
- if (entry == null) return null;
339
- }
381
+ var entry = group.FindEntry(prefabId);
382
+ if (entry == null) return null;
340
383
 
341
384
  var p = customParent == null ? group.SpawnParent : customParent;
342
385
  var result = entry.Get(transform, p, localPosition);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "com.amanotes.prefabpool",
3
- "version": "1.0.3",
4
- "displayName": "Amanotes Prefab Pool",
3
+ "version": "1.2.0",
4
+ "displayName": "[Ama] Prefab Pool",
5
5
  "description": "Generic object pooling system for Unity",
6
6
  "unity": "2022.3",
7
7
  "keywords": [