com.amanotes.prefabpool 2.0.0 → 2.0.2

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.2] - 2026-09-14
4
+
5
+ ### Fixed
6
+ - Pool-group registration during prewarm updates the registration without invalidating the initialization traversal.
7
+
8
+ ### Removed
9
+ - Runtime tests from the distributable UPM package; tests remain in the development project.
10
+
11
+ ## [2.0.1] - 2026-08-15
12
+
13
+ ### Fixed
14
+ - `PrefabPool.DestroyPool` now destroys checked-out instances (not only inactive `_pool` lists). Previously live clones with `HideFlags.DontSave` and a null spawn parent survived scene unload on device because `_instanceTypeMap` was only resolved in the Editor.
15
+ - `PrefabPool.OnDestroy` calls `DestroyPool` so scene teardown always wipes every clone the pool instantiated.
16
+
3
17
  ## [2.0.0] - 2026-07-22
4
18
 
5
19
  ### Added
@@ -1,6 +1,6 @@
1
1
  # PrefabPool API Reference
2
2
 
3
- **Package:** `com.amanotes.prefabpool` | **Version:** 1.2.0 | **Namespace:** `Amanotes.Core`
3
+ **Package:** `com.amanotes.prefabpool` | **Version:** 2.0.2 | **Namespace:** `Amanotes.Core`
4
4
 
5
5
  Generic object pooling system to eliminate garbage collection spikes from frequent instantiation/destruction.
6
6
 
@@ -22,6 +22,8 @@ public interface IPrefabPool
22
22
  GameObject Get(PoolGroup group, string prefabId, Vector3 localPosition = default, Transform customParent = null);
23
23
  void Return(PoolGroup group, GameObject gameObject);
24
24
  IEnumerator Initialize(); // Pre-allocate pooled objects
25
+ void MarkShutdown();
26
+ void DestroyPool(); // Inactive + checked-out clones
25
27
  }
26
28
  ```
27
29
 
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # PrefabPool
2
2
 
3
3
  **Package:** `com.amanotes.prefabpool`
4
- **Version:** 2.0.0
4
+ **Version:** 2.0.2
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": "2.0.0"
33
+ "com.amanotes.prefabpool": "2.0.2"
34
34
  },
35
35
  "scopedRegistries": [
36
36
  {
@@ -214,6 +214,10 @@ namespace Amanotes.Core
214
214
  /// when the pool's hierarchy is being deactivated.
215
215
  /// </summary>
216
216
  void MarkShutdown();
217
+ /// <summary>
218
+ /// Destroys every clone this pool instantiated (inactive lists and checked-out live instances).
219
+ /// </summary>
220
+ void DestroyPool();
217
221
  }
218
222
 
219
223
  public static class PrefabPoolExtensions
@@ -311,7 +315,10 @@ namespace Amanotes.Core
311
315
  [NonSerialized] private bool _isReady = false;
312
316
  [NonSerialized] private bool _isInitializing = false;
313
317
  [NonSerialized] private readonly Dictionary<string, PoolGroup> _groups = new Dictionary<string, PoolGroup>();
318
+ // Registration order for Initialize — only appended on first RegisterGroup per id.
319
+ [NonSerialized] private readonly List<PoolGroup> _groupOrder = new List<PoolGroup>();
314
320
  [NonSerialized] private readonly Dictionary<UnityObjectId, PoolEntry> _instanceTypeMap = new Dictionary<UnityObjectId, PoolEntry>();
321
+ [NonSerialized] private readonly Dictionary<UnityObjectId, GameObject> _liveInstances = new Dictionary<UnityObjectId, GameObject>();
315
322
  [NonSerialized] private readonly Dictionary<GameObject, PoolEntry> _customPool = new Dictionary<GameObject, PoolEntry>();
316
323
 
317
324
  public void Resize<T>(GameObject prefab, List<T> list, int count, Transform parent) where T: Component
@@ -359,8 +366,24 @@ namespace Amanotes.Core
359
366
  public void RegisterGroup(PoolGroup group, Transform spawnParent)
360
367
  {
361
368
  if (group == null) return;
362
-
369
+
370
+ if (_groups.TryGetValue(group.id, out var existing))
371
+ {
372
+ // AttachHost re-registers tile/vfx groups from Start while Initialize is yielding.
373
+ // Updating spawn parent must not touch _groups — that invalidates Dictionary enumeration.
374
+ group.Register(this, spawnParent);
375
+ if (!ReferenceEquals(existing, group))
376
+ {
377
+ _groups[group.id] = group;
378
+ var index = _groupOrder.IndexOf(existing);
379
+ if (index >= 0) _groupOrder[index] = group;
380
+ }
381
+ PurgeStaleInstanceMap();
382
+ return;
383
+ }
384
+
363
385
  _groups[group.id] = group;
386
+ _groupOrder.Add(group);
364
387
  group.Register(this, spawnParent);
365
388
  PurgeStaleInstanceMap();
366
389
  }
@@ -385,7 +408,9 @@ namespace Amanotes.Core
385
408
  var result = entry.Get(transform, p, localPosition);
386
409
  if (result != null)
387
410
  {
388
- _instanceTypeMap[UnityObjectIdentity.GetId(result)] = entry;
411
+ var id = UnityObjectIdentity.GetId(result);
412
+ _instanceTypeMap[id] = entry;
413
+ _liveInstances[id] = result;
389
414
  }
390
415
 
391
416
  return result;
@@ -400,6 +425,7 @@ namespace Amanotes.Core
400
425
  {
401
426
  entry.Return(go, transform);
402
427
  _instanceTypeMap.Remove(instanceId);
428
+ _liveInstances.Remove(instanceId);
403
429
  }
404
430
  }
405
431
 
@@ -412,7 +438,11 @@ namespace Amanotes.Core
412
438
  {
413
439
  if (UnityObjectIdentity.IdToObject(kvp.Key) == null) stale.Add(kvp.Key);
414
440
  }
415
- foreach (var id in stale) _instanceTypeMap.Remove(id);
441
+ foreach (var id in stale)
442
+ {
443
+ _instanceTypeMap.Remove(id);
444
+ _liveInstances.Remove(id);
445
+ }
416
446
  #endif
417
447
  }
418
448
 
@@ -425,26 +455,29 @@ namespace Amanotes.Core
425
455
  EditorApplication.playModeStateChanged -= HandlePlayModeChanged;
426
456
  EditorApplication.playModeStateChanged += HandlePlayModeChanged;
427
457
  #endif
428
-
429
- foreach (var group in _groups.Values)
430
- {
431
- foreach (var entry in group.entries)
432
- {
433
- if (entry.prefab == null) continue;
434
- yield return entry.PreAllocate(transform);
435
- }
436
-
437
- // Pre-allocate fallback entry if available
438
- if (group.fallbackEntry != null && group.fallbackEntry.prefab != null)
439
- {
440
- yield return group.fallbackEntry.PreAllocate(transform);
441
- }
442
- }
458
+
459
+ // Drain _groupOrder by index so groups appended during PreAllocate yields still prewarm.
460
+ for (var i = 0; i < _groupOrder.Count; i++)
461
+ yield return PrewarmGroup(_groupOrder[i]);
443
462
 
444
463
  _isInitializing = false;
445
464
  _isReady = true;
446
465
  }
447
466
 
467
+ IEnumerator PrewarmGroup(PoolGroup group)
468
+ {
469
+ if (group == null) yield break;
470
+
471
+ foreach (var entry in group.entries)
472
+ {
473
+ if (entry.prefab == null) continue;
474
+ yield return entry.PreAllocate(transform);
475
+ }
476
+
477
+ if (group.fallbackEntry != null && group.fallbackEntry.prefab != null)
478
+ yield return group.fallbackEntry.PreAllocate(transform);
479
+ }
480
+
448
481
  #if UNITY_EDITOR
449
482
  protected void HandlePlayModeChanged(PlayModeStateChange changed)
450
483
  {
@@ -454,39 +487,54 @@ namespace Amanotes.Core
454
487
  }
455
488
  }
456
489
  #endif
490
+
491
+ void OnDestroy()
492
+ {
493
+ #if UNITY_EDITOR
494
+ EditorApplication.playModeStateChanged -= HandlePlayModeChanged;
495
+ #endif
496
+ DestroyPool();
497
+ }
457
498
 
458
499
  public void DestroyPool()
459
500
  {
460
501
  _isDestroyed = true;
502
+
503
+ foreach (var go in _liveInstances.Values)
504
+ DestroyClone(go);
505
+ _liveInstances.Clear();
506
+ _instanceTypeMap.Clear();
461
507
 
462
- foreach (var group in _groups.Values)
508
+ foreach (var group in _groupOrder)
463
509
  {
464
510
  foreach (var entry in group.entries)
465
- {
466
511
  entry.DestroyPool();
467
- }
468
-
512
+
469
513
  if (group.fallbackEntry != null)
470
- {
471
514
  group.fallbackEntry.DestroyPool();
472
- }
473
515
  }
474
516
  _groups.Clear();
517
+ _groupOrder.Clear();
475
518
 
476
519
  foreach (var entry in _customPool.Values)
477
520
  {
478
521
  entry.DestroyPool();
479
522
  }
480
523
  _customPool.Clear();
481
-
524
+ }
525
+
526
+ static void DestroyClone(GameObject go)
527
+ {
528
+ if (go == null) return;
482
529
  #if UNITY_EDITOR
483
- foreach (var kvp in _instanceTypeMap)
530
+ if (!Application.isPlaying)
484
531
  {
485
- var obj = UnityObjectIdentity.IdToObject(kvp.Key);
486
- if (obj != null) UnityObject.DestroyImmediate(obj);
532
+ UnityObject.DestroyImmediate(go);
533
+ return;
487
534
  }
488
535
  #endif
489
- _instanceTypeMap.Clear();
536
+ UnityObject.Destroy(go);
490
537
  }
491
538
  }
492
- }
539
+ }
540
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.amanotes.prefabpool",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "displayName": "[Ama] Prefab Pool",
5
5
  "description": "Generic object pooling system for Unity",
6
6
  "unity": "2022.3",
@@ -1,11 +0,0 @@
1
- {
2
- "name": "PrefabPool.Tests",
3
- "rootNamespace": "Amanotes.Core",
4
- "references": [
5
- "PrefabPool",
6
- "Unity.PerformanceTesting"
7
- ],
8
- "optionalUnityReferences": [
9
- "TestAssemblies"
10
- ]
11
- }
@@ -1,7 +0,0 @@
1
- fileFormatVersion: 2
2
- guid: f4f4c6bd22a404f98bd5cc47335df5d8
3
- AssemblyDefinitionImporter:
4
- externalObjects: {}
5
- userData:
6
- assetBundleName:
7
- assetBundleVariant:
@@ -1,208 +0,0 @@
1
- using System.Collections;
2
- using System.Collections.Generic;
3
- using NUnit.Framework;
4
- using UnityEngine;
5
- using UnityEngine.TestTools;
6
- using UnityObject = UnityEngine.Object;
7
-
8
- namespace Amanotes.Core
9
- {
10
- public class PrefabPoolGroupTests
11
- {
12
- private PrefabPool _pool;
13
- private PoolGroup _groupA;
14
- private PoolGroup _groupB;
15
- private GameObject _prefabA;
16
- private GameObject _prefabB;
17
- private Transform _spawnParentA;
18
- private Transform _spawnParentB;
19
-
20
- [SetUp]
21
- public void SetUp()
22
- {
23
- var poolObject = new GameObject("TestPool");
24
- _pool = poolObject.AddComponent<PrefabPool>();
25
-
26
- var spawnParentObjectA = new GameObject("SpawnParentA");
27
- _spawnParentA = spawnParentObjectA.transform;
28
-
29
- var spawnParentObjectB = new GameObject("SpawnParentB");
30
- _spawnParentB = spawnParentObjectB.transform;
31
-
32
- _prefabA = new GameObject("PrefabA");
33
- _prefabA.AddComponent<MockPoolPrefab>();
34
-
35
- _prefabB = new GameObject("PrefabB");
36
- _prefabB.AddComponent<MockPoolPrefab>();
37
-
38
- _groupA = new PoolGroup
39
- {
40
- id = "GroupA",
41
- entries = new List<PoolEntry>
42
- {
43
- new PoolEntry
44
- {
45
- id = "PrefabA",
46
- prefab = _prefabA,
47
- prewarmCount = 5
48
- }
49
- }
50
- };
51
-
52
- _groupB = new PoolGroup
53
- {
54
- id = "GroupB",
55
- entries = new List<PoolEntry>
56
- {
57
- new PoolEntry
58
- {
59
- id = "PrefabB",
60
- prefab = _prefabB,
61
- prewarmCount = 5
62
- }
63
- }
64
- };
65
-
66
- _pool.RegisterGroup(_groupA, _spawnParentA);
67
- _pool.RegisterGroup(_groupB, _spawnParentB);
68
- }
69
-
70
- [TearDown]
71
- public void TearDown()
72
- {
73
- if (_pool != null)
74
- {
75
- _pool.DestroyPool();
76
- UnityObject.DestroyImmediate(_pool.gameObject);
77
- }
78
-
79
- if (_spawnParentA != null)
80
- {
81
- UnityObject.DestroyImmediate(_spawnParentA.gameObject);
82
- }
83
-
84
- if (_spawnParentB != null)
85
- {
86
- UnityObject.DestroyImmediate(_spawnParentB.gameObject);
87
- }
88
-
89
- if (_prefabA != null)
90
- {
91
- UnityObject.DestroyImmediate(_prefabA);
92
- }
93
-
94
- if (_prefabB != null)
95
- {
96
- UnityObject.DestroyImmediate(_prefabB);
97
- }
98
-
99
- var allObjects = UnityObject.FindObjectsOfType<GameObject>();
100
- foreach (var obj in allObjects)
101
- {
102
- if (obj.hideFlags == HideFlags.DontSave)
103
- {
104
- UnityObject.DestroyImmediate(obj);
105
- }
106
- }
107
- }
108
-
109
- [UnityTest]
110
- public IEnumerator PoolGroupIsolation_MultipleGroups_ObjectsNeverCrossBetweenGroups()
111
- {
112
- yield return _pool.Initialize();
113
-
114
- for (int i = 0; i < 100; i++)
115
- {
116
- var objFromA = _groupA.Get("PrefabA");
117
- Assert.IsNotNull(objFromA, $"Iteration {i}: GroupA should return valid GameObject");
118
-
119
- var objFromB = _groupB.Get("PrefabB");
120
- Assert.IsNotNull(objFromB, $"Iteration {i}: GroupB should return valid GameObject");
121
-
122
- Assert.AreNotSame(objFromA, objFromB,
123
- $"Iteration {i}: Objects from different groups should be different instances");
124
-
125
- Assert.IsTrue(objFromA.name.Contains("PrefabA"),
126
- $"Iteration {i}: Object from GroupA should be PrefabA instance");
127
- Assert.IsTrue(objFromB.name.Contains("PrefabB"),
128
- $"Iteration {i}: Object from GroupB should be PrefabB instance");
129
-
130
- _groupA.Return(objFromA);
131
- _groupB.Return(objFromB);
132
-
133
- var reusedFromA = _groupA.Get("PrefabA");
134
- var reusedFromB = _groupB.Get("PrefabB");
135
-
136
- Assert.AreSame(objFromA, reusedFromA,
137
- $"Iteration {i}: GroupA should reuse its own objects");
138
- Assert.AreSame(objFromB, reusedFromB,
139
- $"Iteration {i}: GroupB should reuse its own objects");
140
-
141
- Assert.IsTrue(reusedFromA.name.Contains("PrefabA"),
142
- $"Iteration {i}: GroupA should never return PrefabB instances");
143
- Assert.IsTrue(reusedFromB.name.Contains("PrefabB"),
144
- $"Iteration {i}: GroupB should never return PrefabA instances");
145
-
146
- _groupA.Return(reusedFromA);
147
- _groupB.Return(reusedFromB);
148
- }
149
- }
150
-
151
- [UnityTest]
152
- public IEnumerator ReturnGroupRouting_ReturnToGroup_SubsequentGetRetrievesSameInstanceFromSameGroup()
153
- {
154
- yield return _pool.Initialize();
155
-
156
- for (int i = 0; i < 100; i++)
157
- {
158
- var objA1 = _groupA.Get("PrefabA");
159
- var objA2 = _groupA.Get("PrefabA");
160
- var objB1 = _groupB.Get("PrefabB");
161
- var objB2 = _groupB.Get("PrefabB");
162
-
163
- Assert.IsNotNull(objA1, $"Iteration {i}: GroupA Get 1 should return valid GameObject");
164
- Assert.IsNotNull(objA2, $"Iteration {i}: GroupA Get 2 should return valid GameObject");
165
- Assert.IsNotNull(objB1, $"Iteration {i}: GroupB Get 1 should return valid GameObject");
166
- Assert.IsNotNull(objB2, $"Iteration {i}: GroupB Get 2 should return valid GameObject");
167
-
168
- _groupA.Return(objA1);
169
- _groupA.Return(objA2);
170
- _groupB.Return(objB1);
171
- _groupB.Return(objB2);
172
-
173
- var reusedA1 = _groupA.Get("PrefabA");
174
- var reusedA2 = _groupA.Get("PrefabA");
175
- var reusedB1 = _groupB.Get("PrefabB");
176
- var reusedB2 = _groupB.Get("PrefabB");
177
-
178
- var reusedObjectsA = new HashSet<GameObject> { reusedA1, reusedA2 };
179
- var reusedObjectsB = new HashSet<GameObject> { reusedB1, reusedB2 };
180
-
181
- Assert.IsTrue(reusedObjectsA.Contains(objA1),
182
- $"Iteration {i}: Object returned to GroupA should be retrieved from GroupA");
183
- Assert.IsTrue(reusedObjectsA.Contains(objA2),
184
- $"Iteration {i}: Object returned to GroupA should be retrieved from GroupA");
185
-
186
- Assert.IsTrue(reusedObjectsB.Contains(objB1),
187
- $"Iteration {i}: Object returned to GroupB should be retrieved from GroupB");
188
- Assert.IsTrue(reusedObjectsB.Contains(objB2),
189
- $"Iteration {i}: Object returned to GroupB should be retrieved from GroupB");
190
-
191
- Assert.IsFalse(reusedObjectsA.Contains(objB1),
192
- $"Iteration {i}: GroupA should not return objects from GroupB");
193
- Assert.IsFalse(reusedObjectsA.Contains(objB2),
194
- $"Iteration {i}: GroupA should not return objects from GroupB");
195
-
196
- Assert.IsFalse(reusedObjectsB.Contains(objA1),
197
- $"Iteration {i}: GroupB should not return objects from GroupA");
198
- Assert.IsFalse(reusedObjectsB.Contains(objA2),
199
- $"Iteration {i}: GroupB should not return objects from GroupA");
200
-
201
- _groupA.Return(reusedA1);
202
- _groupA.Return(reusedA2);
203
- _groupB.Return(reusedB1);
204
- _groupB.Return(reusedB2);
205
- }
206
- }
207
- }
208
- }
@@ -1,11 +0,0 @@
1
- fileFormatVersion: 2
2
- guid: 52ad4e4f94f0b41ff9df7f00bcbdd701
3
- MonoImporter:
4
- externalObjects: {}
5
- serializedVersion: 2
6
- defaultReferences: []
7
- executionOrder: 0
8
- icon: {instanceID: 0}
9
- userData:
10
- assetBundleName:
11
- assetBundleVariant:
@@ -1,253 +0,0 @@
1
- using System.Collections;
2
- using NUnit.Framework;
3
- using UnityEngine;
4
- using UnityEngine.TestTools;
5
- using UnityObject = UnityEngine.Object;
6
-
7
- namespace Amanotes.Core
8
- {
9
- public class PrefabPoolLifecycleTests
10
- {
11
- private PrefabPool _pool;
12
- private GameObject _testPrefab;
13
- private PoolGroup _testGroup;
14
- private Transform _spawnParent;
15
-
16
- [SetUp]
17
- public void SetUp()
18
- {
19
- var poolObject = new GameObject("TestPool");
20
- _pool = poolObject.AddComponent<PrefabPool>();
21
-
22
- var spawnParentObject = new GameObject("SpawnParent");
23
- _spawnParent = spawnParentObject.transform;
24
-
25
- _testPrefab = new GameObject("TestPrefab");
26
- _testPrefab.AddComponent<MockPoolPrefab>();
27
-
28
- _testGroup = new PoolGroup
29
- {
30
- id = "TestGroup",
31
- entries = new System.Collections.Generic.List<PoolEntry>
32
- {
33
- new PoolEntry
34
- {
35
- id = "TestPrefab",
36
- prefab = _testPrefab,
37
- prewarmCount = 5
38
- }
39
- }
40
- };
41
-
42
- _pool.RegisterGroup(_testGroup, _spawnParent);
43
- }
44
-
45
- [TearDown]
46
- public void TearDown()
47
- {
48
- if (_pool != null)
49
- {
50
- _pool.DestroyPool();
51
- UnityObject.DestroyImmediate(_pool.gameObject);
52
- }
53
-
54
- if (_spawnParent != null)
55
- {
56
- UnityObject.DestroyImmediate(_spawnParent.gameObject);
57
- }
58
-
59
- if (_testPrefab != null)
60
- {
61
- UnityObject.DestroyImmediate(_testPrefab);
62
- }
63
-
64
- var allObjects = UnityObject.FindObjectsOfType<GameObject>();
65
- foreach (var obj in allObjects)
66
- {
67
- if (obj.hideFlags == HideFlags.DontSave)
68
- {
69
- UnityObject.DestroyImmediate(obj);
70
- }
71
- }
72
- }
73
-
74
- [UnityTest]
75
- public IEnumerator PoolObjectRoundTrip_GetReturnGet_ReuseSameInstance()
76
- {
77
- yield return _pool.Initialize();
78
-
79
- for (int i = 0; i < 100; i++)
80
- {
81
- var firstGet = _testGroup.Get("TestPrefab");
82
- Assert.IsNotNull(firstGet, $"Iteration {i}: First Get should return valid GameObject");
83
-
84
- _testGroup.Return(firstGet);
85
-
86
- var secondGet = _testGroup.Get("TestPrefab");
87
- Assert.IsNotNull(secondGet, $"Iteration {i}: Second Get should return valid GameObject");
88
-
89
- Assert.AreSame(firstGet, secondGet,
90
- $"Iteration {i}: Get-Return-Get should reuse same instance (validates pooling eliminates instantiation)");
91
-
92
- _testGroup.Return(secondGet);
93
- }
94
- }
95
-
96
- [UnityTest]
97
- public IEnumerator LifecycleCallbackInvocation_GetReturnCycle_InvokesOnGetAndOnReturnExactlyOnce()
98
- {
99
- yield return _pool.Initialize();
100
-
101
- for (int i = 0; i < 100; i++)
102
- {
103
- var obj = _testGroup.Get("TestPrefab");
104
- Assert.IsNotNull(obj, $"Iteration {i}: Get should return valid GameObject");
105
-
106
- var mock = obj.GetComponent<MockPoolPrefab>();
107
- Assert.IsNotNull(mock, $"Iteration {i}: GameObject should have MockPoolPrefab component");
108
-
109
- mock.ResetCounts();
110
-
111
- var objAgain = _testGroup.Get("TestPrefab");
112
- var mockAgain = objAgain.GetComponent<MockPoolPrefab>();
113
- mockAgain.ResetCounts();
114
-
115
- _testGroup.Return(obj);
116
-
117
- Assert.AreEqual(1, mock.returnCallCount,
118
- $"Iteration {i}: OnReturn should be called exactly once during Return");
119
- Assert.AreEqual(0, mock.getCallCount,
120
- $"Iteration {i}: OnGet should not be called during Return");
121
-
122
- var reusedObj = _testGroup.Get("TestPrefab");
123
- var reusedMock = reusedObj.GetComponent<MockPoolPrefab>();
124
-
125
- Assert.AreEqual(1, reusedMock.getCallCount,
126
- $"Iteration {i}: OnGet should be called exactly once during Get");
127
-
128
- _testGroup.Return(objAgain);
129
- _testGroup.Return(reusedObj);
130
- }
131
- }
132
-
133
- [UnityTest]
134
- public IEnumerator PreallocationCorrectness_PrewarmCount_CreatesExactNumberOfPreallocatedObjects()
135
- {
136
- for (int iteration = 0; iteration < 100; iteration++)
137
- {
138
- var prewarmCount = UnityEngine.Random.Range(1, 20);
139
-
140
- var poolObject = new GameObject($"TestPool_{iteration}");
141
- var pool = poolObject.AddComponent<PrefabPool>();
142
-
143
- var spawnParentObject = new GameObject($"SpawnParent_{iteration}");
144
- var spawnParent = spawnParentObject.transform;
145
-
146
- var prefab = new GameObject($"Prefab_{iteration}");
147
- prefab.AddComponent<MockPoolPrefab>();
148
-
149
- var group = new PoolGroup
150
- {
151
- id = $"Group_{iteration}",
152
- entries = new System.Collections.Generic.List<PoolEntry>
153
- {
154
- new PoolEntry
155
- {
156
- id = "TestPrefab",
157
- prefab = prefab,
158
- prewarmCount = prewarmCount
159
- }
160
- }
161
- };
162
-
163
- pool.RegisterGroup(group, spawnParent);
164
-
165
- yield return pool.Initialize();
166
-
167
- var retrievedObjects = new System.Collections.Generic.List<GameObject>();
168
-
169
- for (int i = 0; i < prewarmCount; i++)
170
- {
171
- var obj = group.Get("TestPrefab");
172
- Assert.IsNotNull(obj, $"Iteration {iteration}: Should retrieve pre-allocated object {i}");
173
- retrievedObjects.Add(obj);
174
- }
175
-
176
- Assert.AreEqual(prewarmCount, retrievedObjects.Count,
177
- $"Iteration {iteration}: Should retrieve exactly {prewarmCount} pre-allocated objects");
178
-
179
- foreach (var obj in retrievedObjects)
180
- {
181
- group.Return(obj);
182
- }
183
-
184
- pool.DestroyPool();
185
- UnityObject.DestroyImmediate(poolObject);
186
- UnityObject.DestroyImmediate(spawnParentObject);
187
- UnityObject.DestroyImmediate(prefab);
188
- }
189
- }
190
-
191
- [UnityTest]
192
- public IEnumerator DynamicPoolExpansion_GetBeyondCapacity_SuccessfullyReturnsValidGameObjects()
193
- {
194
- for (int iteration = 0; iteration < 100; iteration++)
195
- {
196
- var initialCapacity = UnityEngine.Random.Range(2, 10);
197
- var extraRequests = UnityEngine.Random.Range(5, 15);
198
-
199
- var poolObject = new GameObject($"TestPool_{iteration}");
200
- var pool = poolObject.AddComponent<PrefabPool>();
201
-
202
- var spawnParentObject = new GameObject($"SpawnParent_{iteration}");
203
- var spawnParent = spawnParentObject.transform;
204
-
205
- var prefab = new GameObject($"Prefab_{iteration}");
206
- prefab.AddComponent<MockPoolPrefab>();
207
-
208
- var group = new PoolGroup
209
- {
210
- id = $"Group_{iteration}",
211
- entries = new System.Collections.Generic.List<PoolEntry>
212
- {
213
- new PoolEntry
214
- {
215
- id = "TestPrefab",
216
- prefab = prefab,
217
- prewarmCount = initialCapacity
218
- }
219
- }
220
- };
221
-
222
- pool.RegisterGroup(group, spawnParent);
223
-
224
- yield return pool.Initialize();
225
-
226
- var retrievedObjects = new System.Collections.Generic.List<GameObject>();
227
-
228
- var totalRequests = initialCapacity + extraRequests;
229
-
230
- for (int i = 0; i < totalRequests; i++)
231
- {
232
- var obj = group.Get("TestPrefab");
233
- Assert.IsNotNull(obj,
234
- $"Iteration {iteration}: Get beyond capacity should succeed (request {i + 1}/{totalRequests})");
235
- retrievedObjects.Add(obj);
236
- }
237
-
238
- Assert.AreEqual(totalRequests, retrievedObjects.Count,
239
- $"Iteration {iteration}: Should retrieve {totalRequests} objects (capacity {initialCapacity} + expansion {extraRequests})");
240
-
241
- foreach (var obj in retrievedObjects)
242
- {
243
- group.Return(obj);
244
- }
245
-
246
- pool.DestroyPool();
247
- UnityObject.DestroyImmediate(poolObject);
248
- UnityObject.DestroyImmediate(spawnParentObject);
249
- UnityObject.DestroyImmediate(prefab);
250
- }
251
- }
252
- }
253
- }
@@ -1,11 +0,0 @@
1
- fileFormatVersion: 2
2
- guid: 9c6969a47577843fb977ab5e2c7c1ad2
3
- MonoImporter:
4
- externalObjects: {}
5
- serializedVersion: 2
6
- defaultReferences: []
7
- executionOrder: 0
8
- icon: {instanceID: 0}
9
- userData:
10
- assetBundleName:
11
- assetBundleVariant:
@@ -1,212 +0,0 @@
1
- using System.Collections;
2
- using NUnit.Framework;
3
- using Unity.PerformanceTesting;
4
- using UnityEngine;
5
- using UnityEngine.TestTools;
6
- using UnityObject = UnityEngine.Object;
7
-
8
- namespace Amanotes.Core
9
- {
10
- public class PrefabPoolPerformanceTests
11
- {
12
- private PrefabPool _pool;
13
- private GameObject _testPrefab;
14
- private PoolGroup _testGroup;
15
- private Transform _spawnParent;
16
-
17
- [SetUp]
18
- public void SetUp()
19
- {
20
- var poolObject = new GameObject("TestPool");
21
- _pool = poolObject.AddComponent<PrefabPool>();
22
-
23
- var spawnParentObject = new GameObject("SpawnParent");
24
- _spawnParent = spawnParentObject.transform;
25
-
26
- _testPrefab = new GameObject("TestPrefab");
27
- _testPrefab.AddComponent<MockPoolPrefab>();
28
-
29
- _testGroup = new PoolGroup
30
- {
31
- id = "TestGroup",
32
- entries = new System.Collections.Generic.List<PoolEntry>
33
- {
34
- new PoolEntry
35
- {
36
- id = "TestPrefab",
37
- prefab = _testPrefab,
38
- prewarmCount = 10
39
- }
40
- }
41
- };
42
-
43
- _pool.RegisterGroup(_testGroup, _spawnParent);
44
- }
45
-
46
- [TearDown]
47
- public void TearDown()
48
- {
49
- if (_pool != null)
50
- {
51
- _pool.DestroyPool();
52
- UnityObject.DestroyImmediate(_pool.gameObject);
53
- }
54
-
55
- if (_spawnParent != null)
56
- {
57
- UnityObject.DestroyImmediate(_spawnParent.gameObject);
58
- }
59
-
60
- if (_testPrefab != null)
61
- {
62
- UnityObject.DestroyImmediate(_testPrefab);
63
- }
64
-
65
- var allObjects = UnityObject.FindObjectsOfType<GameObject>();
66
- foreach (var obj in allObjects)
67
- {
68
- if (obj.hideFlags == HideFlags.DontSave)
69
- {
70
- UnityObject.DestroyImmediate(obj);
71
- }
72
- }
73
- }
74
-
75
- [UnityTest, Performance]
76
- public IEnumerator Performance_GetReturnCycles()
77
- {
78
- yield return _pool.Initialize();
79
-
80
- Measure.Method(() =>
81
- {
82
- for (int i = 0; i < 1000; i++)
83
- {
84
- var obj = _testGroup.Get("TestPrefab");
85
- _testGroup.Return(obj);
86
- }
87
- })
88
- .WarmupCount(5)
89
- .MeasurementCount(10)
90
- .IterationsPerMeasurement(1)
91
- .GC()
92
- .Run();
93
- }
94
-
95
- [UnityTest, Performance]
96
- public IEnumerator Performance_GCAllocation()
97
- {
98
- yield return _pool.Initialize();
99
-
100
- Measure.Method(() =>
101
- {
102
- for (int i = 0; i < 100; i++)
103
- {
104
- var obj = _testGroup.Get("TestPrefab");
105
- _testGroup.Return(obj);
106
- }
107
- })
108
- .WarmupCount(5)
109
- .MeasurementCount(10)
110
- .IterationsPerMeasurement(1)
111
- .GC()
112
- .Run();
113
- }
114
-
115
- [UnityTest, Performance]
116
- public IEnumerator Performance_PoolExpansion()
117
- {
118
- yield return _pool.Initialize();
119
-
120
- var retrievedObjects = new System.Collections.Generic.List<GameObject>();
121
-
122
- for (int i = 0; i < 10; i++)
123
- {
124
- var obj = _testGroup.Get("TestPrefab");
125
- retrievedObjects.Add(obj);
126
- }
127
-
128
- Measure.Method(() =>
129
- {
130
- for (int i = 0; i < 100; i++)
131
- {
132
- var obj = _testGroup.Get("TestPrefab");
133
- retrievedObjects.Add(obj);
134
- }
135
- })
136
- .WarmupCount(2)
137
- .MeasurementCount(10)
138
- .IterationsPerMeasurement(1)
139
- .GC()
140
- .Run();
141
-
142
- foreach (var obj in retrievedObjects)
143
- {
144
- _testGroup.Return(obj);
145
- }
146
- }
147
-
148
- [UnityTest, Performance]
149
- public IEnumerator Performance_ParentTransformOverhead()
150
- {
151
- yield return _pool.Initialize();
152
-
153
- var withParentTime = 0.0;
154
- var withoutParentTime = 0.0;
155
-
156
- Measure.Method(() =>
157
- {
158
- for (int i = 0; i < 100; i++)
159
- {
160
- var obj = _testGroup.Get("TestPrefab");
161
- _testGroup.Return(obj);
162
- }
163
- })
164
- .WarmupCount(5)
165
- .MeasurementCount(10)
166
- .IterationsPerMeasurement(1)
167
- .GC()
168
- .Run();
169
-
170
- var poolObjectNoParent = new GameObject("TestPoolNoParent");
171
- var poolNoParent = poolObjectNoParent.AddComponent<PrefabPool>();
172
-
173
- var prefabNoParent = new GameObject("TestPrefabNoParent");
174
- prefabNoParent.AddComponent<MockPoolPrefab>();
175
-
176
- var groupNoParent = new PoolGroup
177
- {
178
- id = "TestGroupNoParent",
179
- entries = new System.Collections.Generic.List<PoolEntry>
180
- {
181
- new PoolEntry
182
- {
183
- id = "TestPrefab",
184
- prefab = prefabNoParent,
185
- prewarmCount = 10
186
- }
187
- }
188
- };
189
-
190
- poolNoParent.RegisterGroup(groupNoParent, null);
191
- yield return poolNoParent.Initialize();
192
-
193
- Measure.Method(() =>
194
- {
195
- for (int i = 0; i < 100; i++)
196
- {
197
- var obj = groupNoParent.Get("TestPrefab");
198
- groupNoParent.Return(obj);
199
- }
200
- })
201
- .WarmupCount(5)
202
- .MeasurementCount(10)
203
- .IterationsPerMeasurement(1)
204
- .GC()
205
- .Run();
206
-
207
- poolNoParent.DestroyPool();
208
- UnityObject.DestroyImmediate(poolObjectNoParent);
209
- UnityObject.DestroyImmediate(prefabNoParent);
210
- }
211
- }
212
- }
@@ -1,11 +0,0 @@
1
- fileFormatVersion: 2
2
- guid: 16aa39925a01643c3894a56145c56a21
3
- MonoImporter:
4
- externalObjects: {}
5
- serializedVersion: 2
6
- defaultReferences: []
7
- executionOrder: 0
8
- icon: {instanceID: 0}
9
- userData:
10
- assetBundleName:
11
- assetBundleVariant:
@@ -1,14 +0,0 @@
1
- using NUnit.Framework;
2
- using Amanotes.Core;
3
-
4
- namespace Amanotes.Core
5
- {
6
- public class PrefabPoolTestsSetup
7
- {
8
- [Test]
9
- public void TestInfrastructureVerification()
10
- {
11
- Assert.IsTrue(true, "PrefabPool test infrastructure is working");
12
- }
13
- }
14
- }
@@ -1,11 +0,0 @@
1
- fileFormatVersion: 2
2
- guid: e61f89af128b84b429ac96d86bd0554f
3
- MonoImporter:
4
- externalObjects: {}
5
- serializedVersion: 2
6
- defaultReferences: []
7
- executionOrder: 0
8
- icon: {instanceID: 0}
9
- userData:
10
- assetBundleName:
11
- assetBundleVariant:
@@ -1,26 +0,0 @@
1
- using UnityEngine;
2
-
3
- namespace Amanotes.Core
4
- {
5
- public class MockPoolPrefab : MonoBehaviour, IPoolPrefab
6
- {
7
- public int getCallCount;
8
- public int returnCallCount;
9
-
10
- public void OnGet()
11
- {
12
- getCallCount++;
13
- }
14
-
15
- public void OnReturn()
16
- {
17
- returnCallCount++;
18
- }
19
-
20
- public void ResetCounts()
21
- {
22
- getCallCount = 0;
23
- returnCallCount = 0;
24
- }
25
- }
26
- }
@@ -1,11 +0,0 @@
1
- fileFormatVersion: 2
2
- guid: 8e7402f0e0f2440338429ebbb480ffc3
3
- MonoImporter:
4
- externalObjects: {}
5
- serializedVersion: 2
6
- defaultReferences: []
7
- executionOrder: 0
8
- icon: {instanceID: 0}
9
- userData:
10
- assetBundleName:
11
- assetBundleVariant:
@@ -1,8 +0,0 @@
1
- fileFormatVersion: 2
2
- guid: 2ce9db2584cc4444f930d1ab257afcaf
3
- folderAsset: yes
4
- DefaultImporter:
5
- externalObjects: {}
6
- userData:
7
- assetBundleName:
8
- assetBundleVariant:
@@ -1,8 +0,0 @@
1
- fileFormatVersion: 2
2
- guid: f4d670095ee40448b93f6f5dbc2c07e6
3
- folderAsset: yes
4
- DefaultImporter:
5
- externalObjects: {}
6
- userData:
7
- assetBundleName:
8
- assetBundleVariant:
package/Tests.meta DELETED
@@ -1,8 +0,0 @@
1
- fileFormatVersion: 2
2
- guid: 518067fd8d1ac4415b0468af746cce4a
3
- folderAsset: yes
4
- DefaultImporter:
5
- externalObjects: {}
6
- userData:
7
- assetBundleName:
8
- assetBundleVariant: