com.amanotes.prefabpool 1.0.0 → 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/README.md.meta +7 -0
- package/Runtime/AssemblyInfo.cs +3 -0
- package/Runtime/AssemblyInfo.cs.meta +11 -0
- package/Runtime/MaterialPool.cs.meta +11 -0
- package/Runtime/PoolEntryDrawer.cs.meta +11 -0
- package/Runtime/PrefabPool.asmdef +3 -0
- package/Runtime/PrefabPool.asmdef.meta +7 -0
- package/Runtime/PrefabPool.cs +42 -10
- package/Runtime/PrefabPool.cs.meta +11 -0
- package/Runtime.meta +8 -0
- 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/package.json.meta +7 -0
- package/Runtime/Amanotes.PrefabPool.asmdef +0 -14
- package/Runtime/MeshRendererExtensions.cs +0 -80
- /package/{Editor → Runtime}/PoolEntryDrawer.cs +0 -0
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
|
|
|
@@ -166,6 +168,16 @@ namespace Amanotes.Core
|
|
|
166
168
|
|
|
167
169
|
public static class PrefabPoolExtensions
|
|
168
170
|
{
|
|
171
|
+
internal static T GetComponentWithInterface<T>(this GameObject go)
|
|
172
|
+
{
|
|
173
|
+
var components = go.GetComponents<MonoBehaviour>();
|
|
174
|
+
for (var i = 0; i < components.Length; i++)
|
|
175
|
+
{
|
|
176
|
+
if (components[i] is T t) return t;
|
|
177
|
+
}
|
|
178
|
+
return default(T);
|
|
179
|
+
}
|
|
180
|
+
|
|
169
181
|
public static void DestroyLast<T>(this List<T> list) where T : Component
|
|
170
182
|
{
|
|
171
183
|
if (list.Count == 0) return;
|
|
@@ -247,6 +259,7 @@ namespace Amanotes.Core
|
|
|
247
259
|
{
|
|
248
260
|
[NonSerialized] private bool _isDestroyed = false;
|
|
249
261
|
[NonSerialized] private bool _isReady = false;
|
|
262
|
+
[NonSerialized] private bool _isInitializing = false;
|
|
250
263
|
[NonSerialized] private readonly Dictionary<string, PoolGroup> _groups = new Dictionary<string, PoolGroup>();
|
|
251
264
|
[NonSerialized] private readonly Dictionary<int, PoolEntry> _instanceTypeMap = new Dictionary<int, PoolEntry>();
|
|
252
265
|
[NonSerialized] private readonly Dictionary<GameObject, PoolEntry> _customPool = new Dictionary<GameObject, PoolEntry>();
|
|
@@ -297,6 +310,7 @@ namespace Amanotes.Core
|
|
|
297
310
|
|
|
298
311
|
_groups[group.id] = group;
|
|
299
312
|
group.Register(this, spawnParent);
|
|
313
|
+
PurgeStaleInstanceMap();
|
|
300
314
|
}
|
|
301
315
|
|
|
302
316
|
public void SetSpawnParent(string groupId, Transform spawnParent)
|
|
@@ -342,10 +356,23 @@ namespace Amanotes.Core
|
|
|
342
356
|
}
|
|
343
357
|
}
|
|
344
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
|
+
|
|
345
372
|
public IEnumerator Initialize()
|
|
346
373
|
{
|
|
347
|
-
if (_isReady) yield break;
|
|
348
|
-
|
|
374
|
+
if (_isReady || _isInitializing) yield break;
|
|
375
|
+
_isInitializing = true;
|
|
349
376
|
|
|
350
377
|
#if UNITY_EDITOR
|
|
351
378
|
EditorApplication.playModeStateChanged -= HandlePlayModeChanged;
|
|
@@ -367,6 +394,7 @@ namespace Amanotes.Core
|
|
|
367
394
|
}
|
|
368
395
|
}
|
|
369
396
|
|
|
397
|
+
_isInitializing = false;
|
|
370
398
|
_isReady = true;
|
|
371
399
|
}
|
|
372
400
|
|
|
@@ -391,23 +419,27 @@ namespace Amanotes.Core
|
|
|
391
419
|
entry.DestroyPool();
|
|
392
420
|
}
|
|
393
421
|
|
|
394
|
-
// Destroy fallback entry if available
|
|
395
422
|
if (group.fallbackEntry != null)
|
|
396
423
|
{
|
|
397
424
|
group.fallbackEntry.DestroyPool();
|
|
398
425
|
}
|
|
399
426
|
}
|
|
400
|
-
|
|
401
427
|
_groups.Clear();
|
|
402
428
|
|
|
429
|
+
foreach (var entry in _customPool.Values)
|
|
430
|
+
{
|
|
431
|
+
entry.DestroyPool();
|
|
432
|
+
}
|
|
433
|
+
_customPool.Clear();
|
|
434
|
+
|
|
403
435
|
#if UNITY_EDITOR
|
|
404
436
|
foreach (var kvp in _instanceTypeMap)
|
|
405
437
|
{
|
|
406
438
|
var obj = EditorUtility.InstanceIDToObject(kvp.Key);
|
|
407
439
|
if (obj != null) UnityObject.DestroyImmediate(obj);
|
|
408
440
|
}
|
|
409
|
-
_instanceTypeMap.Clear();
|
|
410
441
|
#endif
|
|
442
|
+
_instanceTypeMap.Clear();
|
|
411
443
|
}
|
|
412
444
|
}
|
|
413
445
|
}
|
package/Runtime.meta
ADDED
|
@@ -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
|
+
}
|