空池触发和借用模式

空池触发,顾名思义就是在对象池空的时候获取对象会直接去生成,首先上一个简单的空池触发和借用模式的对象池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public sealed class GameObjectPool : MonoBehaviour
{
public static GameObjectPool Instance { get; private set; }

private Dictionary<string, List<GameObject>> pool = new Dictionary<string, List<GameObject>>();

private void Awake() {
if (Instance == null) {
Instance = this;
} else {
Destroy(gameObject);
}
}

public GameObject Get(string prefabName, Vector3 position, Quaternion rotation) {
var key = prefabName;
GameObject obj;

// 如果字典里面有这个 key 并且 key 对应的 List 不为空
if (pool.ContainsKey(key) && pool[key].Count > 0) {
var list = pool[key];
obj = list[0];
list.RemoveAt(0);
// initialize
obj.SetActive(true);
obj.transform.position = position;
obj.transform.rotation = rotation;
} else {
// 如果对象池里面没找到该对象
obj = Instantiate(Resources.Load(prefabName), position, rotation, transform) as GameObject;
}

return obj;
}

public void Store(GameObject obj) {
var key = obj.name.Replace("(Clone)", string.Empty);
if (pool.ContainsKey(key)) {
pool[key].Add(obj);
} else {
pool[key] = new List<GameObject>() { obj };
}
obj.SetActive(false);
}
}

此为一个很简单也很经典的对象池实现,很容易我们就可以发现一个问题:

如果我对象获取的频率太高了获取还能连贯吗?

很明显,不能。当某次对象获取请求太快。前面一部分可能是正常获取池中已有物品,但是后面空池的时候,会阻断这次获取,改为先去执行生成。生成本身就是一个耗时的操作,这必然会导致我们的获取不连贯,导致卡顿。

水位线

为了优化空池触发的性能,此处我们引入水位线模式的对象池。和空池类似,只不过水位线加入了一个触发分配的阈值,比如池子里面还剩10个物体的时候就要开始分配了。此时,分配过程get 相互独立

同时我们需要明确一下空池触发的缺点:即 get 的时候现场生成导致 get 时间变成,而空池补充这个操作,我们就得放在非 get 时机,也就是生成和 get 分离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public sealed class GameObjectPool : MonoBehaviour
{
public static GameObjectPool Instance { get; private set; }

private Dictionary<string, List<GameObject>> pool = new Dictionary<string, List<GameObject>>();

public int WaterLevel { get; private set; } = 10;

private bool isNeedGenerate = false;
private string generateName;

private void Awake() {
if (Instance == null) {
Instance = this;
} else {
Destroy(gameObject);
}
}

public GameObject Get(string prefabName, Vector3 position, Quaternion rotation) {
var key = prefabName;

// 如果对象池中没有这个物体,则第一次初始化到水位线
if (!pool.ContainsKey(key)) {
pool[key] = new List<GameObject>();
InitializeToWaterLevel(key);
}

GameObject obj;
// 有物体
if (pool[key].Count > 0) {
var list = pool[key];
obj = list[0];
list.RemoveAt(0);
// initialize
obj.SetActive(true);
obj.transform.position = position;
obj.transform.rotation = rotation;
// 小于水位线
if (pool[key].Count < WaterLevel) {
isNeedGenerate = true;
generateName = prefabName;
}
} else {
Debug.Log("水位线可能较低");
obj = Instantiate(Resources.Load(key), position, rotation, transform) as GameObject;
}

return obj;
}

public void Store(GameObject obj) {
var key = obj.name.Replace("(Clone)", string.Empty);
if (pool.ContainsKey(key)) {
pool[key].Add(obj);
} else {
pool[key] = new List<GameObject>() { obj };
}

obj.SetActive(false);
}

private void InitializeToWaterLevel(string key) {
GenerateToPool(key, WaterLevel);
}

private void GenerateToWaterLevel(string key) {
var currCount = pool[key].Count;
GenerateToPool(key, WaterLevel - currCount);
}

private void GenerateToPool(string key, int num) {
for (int i = 0; i < num; i++) {
var obj = Instantiate(Resources.Load(key), transform) as GameObject;
obj.SetActive(false);
pool[key].Add(obj);
}
}

private void Update() {
if (isNeedGenerate) {
GenerateToWaterLevel(generateName);
isNeedGenerate = false;
}
}
}

此处我实现了一个较为简单的水位线对象池,在 get 时对不足水位线的物体进行标记,然后在 update 里面轮询标记来生成缺少的物体。我没有在 get 里面做一个 Action 之类的东西,因为感觉在返回之前这样还是会导致 get 在生成物体时阻塞掉,以至于 get 速度变慢

TODO:lease/return 对象池 和 借用模式和引用计数模式