com.amanotes.prefabpool 1.0.1 → 1.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 +16 -0
- package/CHANGELOG.md.meta +7 -0
- package/Docs/PrefabPool-API.md +366 -0
- package/Docs/PrefabPool-API.md.meta +7 -0
- package/Docs.meta +8 -0
- package/README.md +80 -69
- package/Runtime/AssemblyInfo.cs +3 -0
- package/Runtime/AssemblyInfo.cs.meta +11 -0
- package/Runtime/PrefabPool.cs +32 -10
- package/Tests/Runtime/PrefabPool.Test.asmdef +11 -0
- package/Tests/Runtime/PrefabPool.Test.asmdef.meta +7 -0
- package/Tests/Runtime/PrefabPoolGroupTests.cs +224 -0
- package/Tests/Runtime/PrefabPoolGroupTests.cs.meta +11 -0
- package/Tests/Runtime/PrefabPoolLifecycleTests.cs +257 -0
- package/Tests/Runtime/PrefabPoolLifecycleTests.cs.meta +11 -0
- package/Tests/Runtime/PrefabPoolPerformanceTests.cs +212 -0
- package/Tests/Runtime/PrefabPoolPerformanceTests.cs.meta +11 -0
- package/Tests/Runtime/PrefabPoolTestsSetup.cs +14 -0
- package/Tests/Runtime/PrefabPoolTestsSetup.cs.meta +11 -0
- package/Tests/Runtime/TestHelpers/MockPoolPrefab.cs +26 -0
- package/Tests/Runtime/TestHelpers/MockPoolPrefab.cs.meta +11 -0
- package/Tests/Runtime/TestHelpers.meta +8 -0
- package/Tests/Runtime.meta +8 -0
- package/Tests.meta +8 -0
- package/package.json +1 -1
package/Runtime/PrefabPool.cs
CHANGED
|
@@ -48,13 +48,12 @@ namespace Amanotes.Core
|
|
|
48
48
|
resultGameObject.hideFlags = HideFlags.DontSave;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
// Move to spawn parent when retrieved
|
|
52
51
|
var t = resultGameObject.transform;
|
|
53
52
|
t.SetParent(spawnParent, false);
|
|
54
53
|
t.localPosition = localPosition;
|
|
55
54
|
t.localRotation = Quaternion.identity;
|
|
55
|
+
resultGameObject.SetActive(true);
|
|
56
56
|
|
|
57
|
-
// Call OnGet on any IPoolPrefab components
|
|
58
57
|
var poolComponents = resultGameObject.GetComponents<IPoolPrefab>();
|
|
59
58
|
foreach (var pp in poolComponents)
|
|
60
59
|
{
|
|
@@ -68,7 +67,6 @@ namespace Amanotes.Core
|
|
|
68
67
|
{
|
|
69
68
|
if (gameObject == null) return;
|
|
70
69
|
|
|
71
|
-
// Call OnReturn on any IPoolPrefab components
|
|
72
70
|
var poolComponents = gameObject.GetComponents<IPoolPrefab>();
|
|
73
71
|
foreach (var pp in poolComponents)
|
|
74
72
|
{
|
|
@@ -76,6 +74,7 @@ namespace Amanotes.Core
|
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
var t = gameObject.transform;
|
|
77
|
+
gameObject.SetActive(false);
|
|
79
78
|
t.SetParent(poolParent, false);
|
|
80
79
|
t.localPosition = Vector3.zero;
|
|
81
80
|
_pool.Add(gameObject);
|
|
@@ -93,12 +92,15 @@ namespace Amanotes.Core
|
|
|
93
92
|
_pool.Clear();
|
|
94
93
|
}
|
|
95
94
|
|
|
95
|
+
public void PurgeDestroyed() => _pool.RemoveAll(obj => obj == null);
|
|
96
|
+
|
|
96
97
|
public IEnumerator PreAllocate(Transform poolParent)
|
|
97
98
|
{
|
|
98
99
|
for (var i = 0; i < prewarmCount; i++)
|
|
99
100
|
{
|
|
100
101
|
var instance = UnityObject.Instantiate(prefab, poolParent.position, Quaternion.identity, poolParent);
|
|
101
102
|
instance.hideFlags = HideFlags.DontSave;
|
|
103
|
+
instance.SetActive(false);
|
|
102
104
|
_pool.Add(instance);
|
|
103
105
|
}
|
|
104
106
|
|
|
@@ -120,16 +122,16 @@ namespace Amanotes.Core
|
|
|
120
122
|
_prefabPool = prefabPool;
|
|
121
123
|
_spawnParent = spawnParent;
|
|
122
124
|
|
|
123
|
-
// Set parent group reference for each entry
|
|
124
125
|
foreach (var entry in entries)
|
|
125
126
|
{
|
|
126
127
|
entry.SetParentGroup(this);
|
|
128
|
+
entry.PurgeDestroyed();
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
// Set parent group reference for fallback entry
|
|
130
131
|
if (fallbackEntry != null)
|
|
131
132
|
{
|
|
132
133
|
fallbackEntry.SetParentGroup(this);
|
|
134
|
+
fallbackEntry.PurgeDestroyed();
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
|
|
@@ -257,6 +259,7 @@ namespace Amanotes.Core
|
|
|
257
259
|
{
|
|
258
260
|
[NonSerialized] private bool _isDestroyed = false;
|
|
259
261
|
[NonSerialized] private bool _isReady = false;
|
|
262
|
+
[NonSerialized] private bool _isInitializing = false;
|
|
260
263
|
[NonSerialized] private readonly Dictionary<string, PoolGroup> _groups = new Dictionary<string, PoolGroup>();
|
|
261
264
|
[NonSerialized] private readonly Dictionary<int, PoolEntry> _instanceTypeMap = new Dictionary<int, PoolEntry>();
|
|
262
265
|
[NonSerialized] private readonly Dictionary<GameObject, PoolEntry> _customPool = new Dictionary<GameObject, PoolEntry>();
|
|
@@ -307,6 +310,7 @@ namespace Amanotes.Core
|
|
|
307
310
|
|
|
308
311
|
_groups[group.id] = group;
|
|
309
312
|
group.Register(this, spawnParent);
|
|
313
|
+
PurgeStaleInstanceMap();
|
|
310
314
|
}
|
|
311
315
|
|
|
312
316
|
public void SetSpawnParent(string groupId, Transform spawnParent)
|
|
@@ -352,10 +356,23 @@ namespace Amanotes.Core
|
|
|
352
356
|
}
|
|
353
357
|
}
|
|
354
358
|
|
|
359
|
+
private void PurgeStaleInstanceMap()
|
|
360
|
+
{
|
|
361
|
+
#if UNITY_EDITOR
|
|
362
|
+
if (_instanceTypeMap.Count == 0) return;
|
|
363
|
+
var stale = new List<int>();
|
|
364
|
+
foreach (var kvp in _instanceTypeMap)
|
|
365
|
+
{
|
|
366
|
+
if (EditorUtility.InstanceIDToObject(kvp.Key) == null) stale.Add(kvp.Key);
|
|
367
|
+
}
|
|
368
|
+
foreach (var id in stale) _instanceTypeMap.Remove(id);
|
|
369
|
+
#endif
|
|
370
|
+
}
|
|
371
|
+
|
|
355
372
|
public IEnumerator Initialize()
|
|
356
373
|
{
|
|
357
|
-
if (_isReady) yield break;
|
|
358
|
-
|
|
374
|
+
if (_isReady || _isInitializing) yield break;
|
|
375
|
+
_isInitializing = true;
|
|
359
376
|
|
|
360
377
|
#if UNITY_EDITOR
|
|
361
378
|
EditorApplication.playModeStateChanged -= HandlePlayModeChanged;
|
|
@@ -377,6 +394,7 @@ namespace Amanotes.Core
|
|
|
377
394
|
}
|
|
378
395
|
}
|
|
379
396
|
|
|
397
|
+
_isInitializing = false;
|
|
380
398
|
_isReady = true;
|
|
381
399
|
}
|
|
382
400
|
|
|
@@ -401,23 +419,27 @@ namespace Amanotes.Core
|
|
|
401
419
|
entry.DestroyPool();
|
|
402
420
|
}
|
|
403
421
|
|
|
404
|
-
// Destroy fallback entry if available
|
|
405
422
|
if (group.fallbackEntry != null)
|
|
406
423
|
{
|
|
407
424
|
group.fallbackEntry.DestroyPool();
|
|
408
425
|
}
|
|
409
426
|
}
|
|
410
|
-
|
|
411
427
|
_groups.Clear();
|
|
412
428
|
|
|
429
|
+
foreach (var entry in _customPool.Values)
|
|
430
|
+
{
|
|
431
|
+
entry.DestroyPool();
|
|
432
|
+
}
|
|
433
|
+
_customPool.Clear();
|
|
434
|
+
|
|
413
435
|
#if UNITY_EDITOR
|
|
414
436
|
foreach (var kvp in _instanceTypeMap)
|
|
415
437
|
{
|
|
416
438
|
var obj = EditorUtility.InstanceIDToObject(kvp.Key);
|
|
417
439
|
if (obj != null) UnityObject.DestroyImmediate(obj);
|
|
418
440
|
}
|
|
419
|
-
_instanceTypeMap.Clear();
|
|
420
441
|
#endif
|
|
442
|
+
_instanceTypeMap.Clear();
|
|
421
443
|
}
|
|
422
444
|
}
|
|
423
445
|
}
|
|
@@ -0,0 +1,224 @@
|
|
|
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
|
+
var instanceIdA = objFromA.GetInstanceID();
|
|
123
|
+
var instanceIdB = objFromB.GetInstanceID();
|
|
124
|
+
|
|
125
|
+
Assert.AreNotEqual(instanceIdA, instanceIdB,
|
|
126
|
+
$"Iteration {i}: Objects from different groups should have different instance IDs");
|
|
127
|
+
|
|
128
|
+
Assert.IsTrue(objFromA.name.Contains("PrefabA"),
|
|
129
|
+
$"Iteration {i}: Object from GroupA should be PrefabA instance");
|
|
130
|
+
Assert.IsTrue(objFromB.name.Contains("PrefabB"),
|
|
131
|
+
$"Iteration {i}: Object from GroupB should be PrefabB instance");
|
|
132
|
+
|
|
133
|
+
_groupA.Return(objFromA);
|
|
134
|
+
_groupB.Return(objFromB);
|
|
135
|
+
|
|
136
|
+
var reusedFromA = _groupA.Get("PrefabA");
|
|
137
|
+
var reusedFromB = _groupB.Get("PrefabB");
|
|
138
|
+
|
|
139
|
+
Assert.AreEqual(instanceIdA, reusedFromA.GetInstanceID(),
|
|
140
|
+
$"Iteration {i}: GroupA should reuse its own objects");
|
|
141
|
+
Assert.AreEqual(instanceIdB, reusedFromB.GetInstanceID(),
|
|
142
|
+
$"Iteration {i}: GroupB should reuse its own objects");
|
|
143
|
+
|
|
144
|
+
Assert.IsTrue(reusedFromA.name.Contains("PrefabA"),
|
|
145
|
+
$"Iteration {i}: GroupA should never return PrefabB instances");
|
|
146
|
+
Assert.IsTrue(reusedFromB.name.Contains("PrefabB"),
|
|
147
|
+
$"Iteration {i}: GroupB should never return PrefabA instances");
|
|
148
|
+
|
|
149
|
+
_groupA.Return(reusedFromA);
|
|
150
|
+
_groupB.Return(reusedFromB);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
[UnityTest]
|
|
155
|
+
public IEnumerator ReturnGroupRouting_ReturnToGroup_SubsequentGetRetrievesSameInstanceFromSameGroup()
|
|
156
|
+
{
|
|
157
|
+
yield return _pool.Initialize();
|
|
158
|
+
|
|
159
|
+
for (int i = 0; i < 100; i++)
|
|
160
|
+
{
|
|
161
|
+
var objA1 = _groupA.Get("PrefabA");
|
|
162
|
+
var objA2 = _groupA.Get("PrefabA");
|
|
163
|
+
var objB1 = _groupB.Get("PrefabB");
|
|
164
|
+
var objB2 = _groupB.Get("PrefabB");
|
|
165
|
+
|
|
166
|
+
Assert.IsNotNull(objA1, $"Iteration {i}: GroupA Get 1 should return valid GameObject");
|
|
167
|
+
Assert.IsNotNull(objA2, $"Iteration {i}: GroupA Get 2 should return valid GameObject");
|
|
168
|
+
Assert.IsNotNull(objB1, $"Iteration {i}: GroupB Get 1 should return valid GameObject");
|
|
169
|
+
Assert.IsNotNull(objB2, $"Iteration {i}: GroupB Get 2 should return valid GameObject");
|
|
170
|
+
|
|
171
|
+
var instanceIdA1 = objA1.GetInstanceID();
|
|
172
|
+
var instanceIdA2 = objA2.GetInstanceID();
|
|
173
|
+
var instanceIdB1 = objB1.GetInstanceID();
|
|
174
|
+
var instanceIdB2 = objB2.GetInstanceID();
|
|
175
|
+
|
|
176
|
+
_groupA.Return(objA1);
|
|
177
|
+
_groupA.Return(objA2);
|
|
178
|
+
_groupB.Return(objB1);
|
|
179
|
+
_groupB.Return(objB2);
|
|
180
|
+
|
|
181
|
+
var reusedA1 = _groupA.Get("PrefabA");
|
|
182
|
+
var reusedA2 = _groupA.Get("PrefabA");
|
|
183
|
+
var reusedB1 = _groupB.Get("PrefabB");
|
|
184
|
+
var reusedB2 = _groupB.Get("PrefabB");
|
|
185
|
+
|
|
186
|
+
var reusedInstanceIdsA = new HashSet<int>
|
|
187
|
+
{
|
|
188
|
+
reusedA1.GetInstanceID(),
|
|
189
|
+
reusedA2.GetInstanceID()
|
|
190
|
+
};
|
|
191
|
+
var reusedInstanceIdsB = new HashSet<int>
|
|
192
|
+
{
|
|
193
|
+
reusedB1.GetInstanceID(),
|
|
194
|
+
reusedB2.GetInstanceID()
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
Assert.IsTrue(reusedInstanceIdsA.Contains(instanceIdA1),
|
|
198
|
+
$"Iteration {i}: Object returned to GroupA should be retrieved from GroupA");
|
|
199
|
+
Assert.IsTrue(reusedInstanceIdsA.Contains(instanceIdA2),
|
|
200
|
+
$"Iteration {i}: Object returned to GroupA should be retrieved from GroupA");
|
|
201
|
+
|
|
202
|
+
Assert.IsTrue(reusedInstanceIdsB.Contains(instanceIdB1),
|
|
203
|
+
$"Iteration {i}: Object returned to GroupB should be retrieved from GroupB");
|
|
204
|
+
Assert.IsTrue(reusedInstanceIdsB.Contains(instanceIdB2),
|
|
205
|
+
$"Iteration {i}: Object returned to GroupB should be retrieved from GroupB");
|
|
206
|
+
|
|
207
|
+
Assert.IsFalse(reusedInstanceIdsA.Contains(instanceIdB1),
|
|
208
|
+
$"Iteration {i}: GroupA should not return objects from GroupB");
|
|
209
|
+
Assert.IsFalse(reusedInstanceIdsA.Contains(instanceIdB2),
|
|
210
|
+
$"Iteration {i}: GroupA should not return objects from GroupB");
|
|
211
|
+
|
|
212
|
+
Assert.IsFalse(reusedInstanceIdsB.Contains(instanceIdA1),
|
|
213
|
+
$"Iteration {i}: GroupB should not return objects from GroupA");
|
|
214
|
+
Assert.IsFalse(reusedInstanceIdsB.Contains(instanceIdA2),
|
|
215
|
+
$"Iteration {i}: GroupB should not return objects from GroupA");
|
|
216
|
+
|
|
217
|
+
_groupA.Return(reusedA1);
|
|
218
|
+
_groupA.Return(reusedA2);
|
|
219
|
+
_groupB.Return(reusedB1);
|
|
220
|
+
_groupB.Return(reusedB2);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
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
|
+
var firstInstanceId = firstGet.GetInstanceID();
|
|
85
|
+
|
|
86
|
+
_testGroup.Return(firstGet);
|
|
87
|
+
|
|
88
|
+
var secondGet = _testGroup.Get("TestPrefab");
|
|
89
|
+
Assert.IsNotNull(secondGet, $"Iteration {i}: Second Get should return valid GameObject");
|
|
90
|
+
|
|
91
|
+
var secondInstanceId = secondGet.GetInstanceID();
|
|
92
|
+
|
|
93
|
+
Assert.AreEqual(firstInstanceId, secondInstanceId,
|
|
94
|
+
$"Iteration {i}: Get-Return-Get should reuse same instance (validates pooling eliminates instantiation)");
|
|
95
|
+
|
|
96
|
+
_testGroup.Return(secondGet);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
[UnityTest]
|
|
101
|
+
public IEnumerator LifecycleCallbackInvocation_GetReturnCycle_InvokesOnGetAndOnReturnExactlyOnce()
|
|
102
|
+
{
|
|
103
|
+
yield return _pool.Initialize();
|
|
104
|
+
|
|
105
|
+
for (int i = 0; i < 100; i++)
|
|
106
|
+
{
|
|
107
|
+
var obj = _testGroup.Get("TestPrefab");
|
|
108
|
+
Assert.IsNotNull(obj, $"Iteration {i}: Get should return valid GameObject");
|
|
109
|
+
|
|
110
|
+
var mock = obj.GetComponent<MockPoolPrefab>();
|
|
111
|
+
Assert.IsNotNull(mock, $"Iteration {i}: GameObject should have MockPoolPrefab component");
|
|
112
|
+
|
|
113
|
+
mock.ResetCounts();
|
|
114
|
+
|
|
115
|
+
var objAgain = _testGroup.Get("TestPrefab");
|
|
116
|
+
var mockAgain = objAgain.GetComponent<MockPoolPrefab>();
|
|
117
|
+
mockAgain.ResetCounts();
|
|
118
|
+
|
|
119
|
+
_testGroup.Return(obj);
|
|
120
|
+
|
|
121
|
+
Assert.AreEqual(1, mock.returnCallCount,
|
|
122
|
+
$"Iteration {i}: OnReturn should be called exactly once during Return");
|
|
123
|
+
Assert.AreEqual(0, mock.getCallCount,
|
|
124
|
+
$"Iteration {i}: OnGet should not be called during Return");
|
|
125
|
+
|
|
126
|
+
var reusedObj = _testGroup.Get("TestPrefab");
|
|
127
|
+
var reusedMock = reusedObj.GetComponent<MockPoolPrefab>();
|
|
128
|
+
|
|
129
|
+
Assert.AreEqual(1, reusedMock.getCallCount,
|
|
130
|
+
$"Iteration {i}: OnGet should be called exactly once during Get");
|
|
131
|
+
|
|
132
|
+
_testGroup.Return(objAgain);
|
|
133
|
+
_testGroup.Return(reusedObj);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
[UnityTest]
|
|
138
|
+
public IEnumerator PreallocationCorrectness_PrewarmCount_CreatesExactNumberOfPreallocatedObjects()
|
|
139
|
+
{
|
|
140
|
+
for (int iteration = 0; iteration < 100; iteration++)
|
|
141
|
+
{
|
|
142
|
+
var prewarmCount = UnityEngine.Random.Range(1, 20);
|
|
143
|
+
|
|
144
|
+
var poolObject = new GameObject($"TestPool_{iteration}");
|
|
145
|
+
var pool = poolObject.AddComponent<PrefabPool>();
|
|
146
|
+
|
|
147
|
+
var spawnParentObject = new GameObject($"SpawnParent_{iteration}");
|
|
148
|
+
var spawnParent = spawnParentObject.transform;
|
|
149
|
+
|
|
150
|
+
var prefab = new GameObject($"Prefab_{iteration}");
|
|
151
|
+
prefab.AddComponent<MockPoolPrefab>();
|
|
152
|
+
|
|
153
|
+
var group = new PoolGroup
|
|
154
|
+
{
|
|
155
|
+
id = $"Group_{iteration}",
|
|
156
|
+
entries = new System.Collections.Generic.List<PoolEntry>
|
|
157
|
+
{
|
|
158
|
+
new PoolEntry
|
|
159
|
+
{
|
|
160
|
+
id = "TestPrefab",
|
|
161
|
+
prefab = prefab,
|
|
162
|
+
prewarmCount = prewarmCount
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
pool.RegisterGroup(group, spawnParent);
|
|
168
|
+
|
|
169
|
+
yield return pool.Initialize();
|
|
170
|
+
|
|
171
|
+
var retrievedObjects = new System.Collections.Generic.List<GameObject>();
|
|
172
|
+
|
|
173
|
+
for (int i = 0; i < prewarmCount; i++)
|
|
174
|
+
{
|
|
175
|
+
var obj = group.Get("TestPrefab");
|
|
176
|
+
Assert.IsNotNull(obj, $"Iteration {iteration}: Should retrieve pre-allocated object {i}");
|
|
177
|
+
retrievedObjects.Add(obj);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
Assert.AreEqual(prewarmCount, retrievedObjects.Count,
|
|
181
|
+
$"Iteration {iteration}: Should retrieve exactly {prewarmCount} pre-allocated objects");
|
|
182
|
+
|
|
183
|
+
foreach (var obj in retrievedObjects)
|
|
184
|
+
{
|
|
185
|
+
group.Return(obj);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
pool.DestroyPool();
|
|
189
|
+
UnityObject.DestroyImmediate(poolObject);
|
|
190
|
+
UnityObject.DestroyImmediate(spawnParentObject);
|
|
191
|
+
UnityObject.DestroyImmediate(prefab);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
[UnityTest]
|
|
196
|
+
public IEnumerator DynamicPoolExpansion_GetBeyondCapacity_SuccessfullyReturnsValidGameObjects()
|
|
197
|
+
{
|
|
198
|
+
for (int iteration = 0; iteration < 100; iteration++)
|
|
199
|
+
{
|
|
200
|
+
var initialCapacity = UnityEngine.Random.Range(2, 10);
|
|
201
|
+
var extraRequests = UnityEngine.Random.Range(5, 15);
|
|
202
|
+
|
|
203
|
+
var poolObject = new GameObject($"TestPool_{iteration}");
|
|
204
|
+
var pool = poolObject.AddComponent<PrefabPool>();
|
|
205
|
+
|
|
206
|
+
var spawnParentObject = new GameObject($"SpawnParent_{iteration}");
|
|
207
|
+
var spawnParent = spawnParentObject.transform;
|
|
208
|
+
|
|
209
|
+
var prefab = new GameObject($"Prefab_{iteration}");
|
|
210
|
+
prefab.AddComponent<MockPoolPrefab>();
|
|
211
|
+
|
|
212
|
+
var group = new PoolGroup
|
|
213
|
+
{
|
|
214
|
+
id = $"Group_{iteration}",
|
|
215
|
+
entries = new System.Collections.Generic.List<PoolEntry>
|
|
216
|
+
{
|
|
217
|
+
new PoolEntry
|
|
218
|
+
{
|
|
219
|
+
id = "TestPrefab",
|
|
220
|
+
prefab = prefab,
|
|
221
|
+
prewarmCount = initialCapacity
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
pool.RegisterGroup(group, spawnParent);
|
|
227
|
+
|
|
228
|
+
yield return pool.Initialize();
|
|
229
|
+
|
|
230
|
+
var retrievedObjects = new System.Collections.Generic.List<GameObject>();
|
|
231
|
+
|
|
232
|
+
var totalRequests = initialCapacity + extraRequests;
|
|
233
|
+
|
|
234
|
+
for (int i = 0; i < totalRequests; i++)
|
|
235
|
+
{
|
|
236
|
+
var obj = group.Get("TestPrefab");
|
|
237
|
+
Assert.IsNotNull(obj,
|
|
238
|
+
$"Iteration {iteration}: Get beyond capacity should succeed (request {i + 1}/{totalRequests})");
|
|
239
|
+
retrievedObjects.Add(obj);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
Assert.AreEqual(totalRequests, retrievedObjects.Count,
|
|
243
|
+
$"Iteration {iteration}: Should retrieve {totalRequests} objects (capacity {initialCapacity} + expansion {extraRequests})");
|
|
244
|
+
|
|
245
|
+
foreach (var obj in retrievedObjects)
|
|
246
|
+
{
|
|
247
|
+
group.Return(obj);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
pool.DestroyPool();
|
|
251
|
+
UnityObject.DestroyImmediate(poolObject);
|
|
252
|
+
UnityObject.DestroyImmediate(spawnParentObject);
|
|
253
|
+
UnityObject.DestroyImmediate(prefab);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|