com.amanotes.prefabpool 1.0.1 → 1.0.3
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 +21 -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 +36 -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
|
@@ -41,6 +41,7 @@ namespace Amanotes.Core
|
|
|
41
41
|
_pool.RemoveAt(lastIndex);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
bool fromPool = resultGameObject != null;
|
|
44
45
|
if (resultGameObject == null)
|
|
45
46
|
{
|
|
46
47
|
// Create prefab inside the pool's transform
|
|
@@ -48,13 +49,12 @@ namespace Amanotes.Core
|
|
|
48
49
|
resultGameObject.hideFlags = HideFlags.DontSave;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
// Move to spawn parent when retrieved
|
|
52
52
|
var t = resultGameObject.transform;
|
|
53
53
|
t.SetParent(spawnParent, false);
|
|
54
54
|
t.localPosition = localPosition;
|
|
55
55
|
t.localRotation = Quaternion.identity;
|
|
56
|
+
resultGameObject.SetActive(true);
|
|
56
57
|
|
|
57
|
-
// Call OnGet on any IPoolPrefab components
|
|
58
58
|
var poolComponents = resultGameObject.GetComponents<IPoolPrefab>();
|
|
59
59
|
foreach (var pp in poolComponents)
|
|
60
60
|
{
|
|
@@ -68,7 +68,6 @@ namespace Amanotes.Core
|
|
|
68
68
|
{
|
|
69
69
|
if (gameObject == null) return;
|
|
70
70
|
|
|
71
|
-
// Call OnReturn on any IPoolPrefab components
|
|
72
71
|
var poolComponents = gameObject.GetComponents<IPoolPrefab>();
|
|
73
72
|
foreach (var pp in poolComponents)
|
|
74
73
|
{
|
|
@@ -76,8 +75,12 @@ namespace Amanotes.Core
|
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
var t = gameObject.transform;
|
|
78
|
+
gameObject.SetActive(false);
|
|
79
79
|
t.SetParent(poolParent, false);
|
|
80
80
|
t.localPosition = Vector3.zero;
|
|
81
|
+
#if UNITY_EDITOR
|
|
82
|
+
gameObject.name = id;
|
|
83
|
+
#endif
|
|
81
84
|
_pool.Add(gameObject);
|
|
82
85
|
}
|
|
83
86
|
|
|
@@ -93,12 +96,15 @@ namespace Amanotes.Core
|
|
|
93
96
|
_pool.Clear();
|
|
94
97
|
}
|
|
95
98
|
|
|
99
|
+
public void PurgeDestroyed() => _pool.RemoveAll(obj => obj == null);
|
|
100
|
+
|
|
96
101
|
public IEnumerator PreAllocate(Transform poolParent)
|
|
97
102
|
{
|
|
98
103
|
for (var i = 0; i < prewarmCount; i++)
|
|
99
104
|
{
|
|
100
105
|
var instance = UnityObject.Instantiate(prefab, poolParent.position, Quaternion.identity, poolParent);
|
|
101
106
|
instance.hideFlags = HideFlags.DontSave;
|
|
107
|
+
instance.SetActive(false);
|
|
102
108
|
_pool.Add(instance);
|
|
103
109
|
}
|
|
104
110
|
|
|
@@ -120,16 +126,16 @@ namespace Amanotes.Core
|
|
|
120
126
|
_prefabPool = prefabPool;
|
|
121
127
|
_spawnParent = spawnParent;
|
|
122
128
|
|
|
123
|
-
// Set parent group reference for each entry
|
|
124
129
|
foreach (var entry in entries)
|
|
125
130
|
{
|
|
126
131
|
entry.SetParentGroup(this);
|
|
132
|
+
entry.PurgeDestroyed();
|
|
127
133
|
}
|
|
128
134
|
|
|
129
|
-
// Set parent group reference for fallback entry
|
|
130
135
|
if (fallbackEntry != null)
|
|
131
136
|
{
|
|
132
137
|
fallbackEntry.SetParentGroup(this);
|
|
138
|
+
fallbackEntry.PurgeDestroyed();
|
|
133
139
|
}
|
|
134
140
|
}
|
|
135
141
|
|
|
@@ -257,6 +263,7 @@ namespace Amanotes.Core
|
|
|
257
263
|
{
|
|
258
264
|
[NonSerialized] private bool _isDestroyed = false;
|
|
259
265
|
[NonSerialized] private bool _isReady = false;
|
|
266
|
+
[NonSerialized] private bool _isInitializing = false;
|
|
260
267
|
[NonSerialized] private readonly Dictionary<string, PoolGroup> _groups = new Dictionary<string, PoolGroup>();
|
|
261
268
|
[NonSerialized] private readonly Dictionary<int, PoolEntry> _instanceTypeMap = new Dictionary<int, PoolEntry>();
|
|
262
269
|
[NonSerialized] private readonly Dictionary<GameObject, PoolEntry> _customPool = new Dictionary<GameObject, PoolEntry>();
|
|
@@ -307,6 +314,7 @@ namespace Amanotes.Core
|
|
|
307
314
|
|
|
308
315
|
_groups[group.id] = group;
|
|
309
316
|
group.Register(this, spawnParent);
|
|
317
|
+
PurgeStaleInstanceMap();
|
|
310
318
|
}
|
|
311
319
|
|
|
312
320
|
public void SetSpawnParent(string groupId, Transform spawnParent)
|
|
@@ -352,10 +360,23 @@ namespace Amanotes.Core
|
|
|
352
360
|
}
|
|
353
361
|
}
|
|
354
362
|
|
|
363
|
+
private void PurgeStaleInstanceMap()
|
|
364
|
+
{
|
|
365
|
+
#if UNITY_EDITOR
|
|
366
|
+
if (_instanceTypeMap.Count == 0) return;
|
|
367
|
+
var stale = new List<int>();
|
|
368
|
+
foreach (var kvp in _instanceTypeMap)
|
|
369
|
+
{
|
|
370
|
+
if (EditorUtility.InstanceIDToObject(kvp.Key) == null) stale.Add(kvp.Key);
|
|
371
|
+
}
|
|
372
|
+
foreach (var id in stale) _instanceTypeMap.Remove(id);
|
|
373
|
+
#endif
|
|
374
|
+
}
|
|
375
|
+
|
|
355
376
|
public IEnumerator Initialize()
|
|
356
377
|
{
|
|
357
|
-
if (_isReady) yield break;
|
|
358
|
-
|
|
378
|
+
if (_isReady || _isInitializing) yield break;
|
|
379
|
+
_isInitializing = true;
|
|
359
380
|
|
|
360
381
|
#if UNITY_EDITOR
|
|
361
382
|
EditorApplication.playModeStateChanged -= HandlePlayModeChanged;
|
|
@@ -377,6 +398,7 @@ namespace Amanotes.Core
|
|
|
377
398
|
}
|
|
378
399
|
}
|
|
379
400
|
|
|
401
|
+
_isInitializing = false;
|
|
380
402
|
_isReady = true;
|
|
381
403
|
}
|
|
382
404
|
|
|
@@ -401,23 +423,27 @@ namespace Amanotes.Core
|
|
|
401
423
|
entry.DestroyPool();
|
|
402
424
|
}
|
|
403
425
|
|
|
404
|
-
// Destroy fallback entry if available
|
|
405
426
|
if (group.fallbackEntry != null)
|
|
406
427
|
{
|
|
407
428
|
group.fallbackEntry.DestroyPool();
|
|
408
429
|
}
|
|
409
430
|
}
|
|
410
|
-
|
|
411
431
|
_groups.Clear();
|
|
412
432
|
|
|
433
|
+
foreach (var entry in _customPool.Values)
|
|
434
|
+
{
|
|
435
|
+
entry.DestroyPool();
|
|
436
|
+
}
|
|
437
|
+
_customPool.Clear();
|
|
438
|
+
|
|
413
439
|
#if UNITY_EDITOR
|
|
414
440
|
foreach (var kvp in _instanceTypeMap)
|
|
415
441
|
{
|
|
416
442
|
var obj = EditorUtility.InstanceIDToObject(kvp.Key);
|
|
417
443
|
if (obj != null) UnityObject.DestroyImmediate(obj);
|
|
418
444
|
}
|
|
419
|
-
_instanceTypeMap.Clear();
|
|
420
445
|
#endif
|
|
446
|
+
_instanceTypeMap.Clear();
|
|
421
447
|
}
|
|
422
448
|
}
|
|
423
449
|
}
|
|
@@ -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
|
+
}
|