GF_Entity
概要
Entity管理核心为EntityManager,Show和Hide接口会被lua层所调用。
其中Show的实现类似UI模块,首先检查对象池中是否有可用GameObject资源(EntityInstance),如果有,则使用之,并显示,否则先LoadAsset,加载完毕后注册于对象池,而后显示。
对象池在此处封装为EntityGroup,内部包含一个维护EntitiyInstance的对象池,指代GameObject对象资源,以及一个Entity链表,指代当前正在使用的GameObject。
此处对象池和其他模块的对象池类似,可以配置自动释放时间,大小等参数。
最终生成 的GameObject上会挂载一个EntityLogic + Entity
关于Preload的坑:
如果只是Show/Hide来实现Preload,Preload后的Entity可能会被自动释放掉。所以实际项目中禁用了自动释放相关的代码,完全手动管理。
实现上的代码细节
ShowEntity
直接进入EntityManager:
1 | public void ShowEntity(int entityId, string entityAssetName, string entityGroupName, int priority, object userData) |
这里的处理和UIManager十分相似:
对象池(EntityGroup)尝试获取EntityInstance
- 如果成功,直接进入InternalShowEntity,显示之
- 如果失败,就走LoadAsset逻辑,尝试加载对应资源
加载成功会构造对应的EntityInstance,注册于对象池,然后再进入InternalShowEntity。
Anyway,这里暂时吧资源加载放一边,进入InternalShowEntity:
1 | private void InternalShowEntity(int entityId, string entityAssetName, EntityGroup entityGroup, object entityInstance, bool isNewInstance, float duration, object userData) |
在entity.OnInit中:
1 | m_EntityLogic = gameObject.AddComponent(entityLogicType) as EntityLogic; |
这里会根据之前传入的Logic type来构造Component,这样就将对应的logic lua脚本挂到了Entity上。
另外,这里entity实际上是个Component脚本,并非GameObject,所以真正生成的gameObject上挂了两个脚本:
- Entity
- Logic
所以这里可能有点迷惑的地方就是这个Entity并非直接指代Unity中的GameObject,本体是GameObject上挂的一个c#脚本,其内部引用了EntityLogic,来执行真正的游戏业务逻辑。
之所以这么设计是为了方便对象池管理,后文提到。
对象管理
这里Entity精髓在于它利用了底层的通用对象池来管理Entity对象。
回到EntityManager,这里的EnttiyGroup可以看做一个对象池,不同的Group分别管理:
1 | public void ShowEntity(int entityId, string entityAssetName, string entityGroupName, int priority, object userData) |
实际上对于EntityGroup,内部维护了两个东西,
- m_InstancePool :对象池,这个运行时的类型是ObjectPoolManager.ObjectPool
,EntityInstanceObject内部有个Target指向真正的GameObject - m_Entities : 当前使用的Entity链表
1 | private sealed class EntityGroup : IEntityGroup |
可以这么理解:
- EntityInstance指代了真正的GameObject,可以看做真正的资源,这个资源被对象池自动管理。
- Entity则可以看做当前正在使用的GameObject。
举个栗子:
如果hide了,Entity是会被从这个链表中移除的,如果又show了一个,可能依然会用对象池中已经有的EntityInstance来造一个Entity。
而正是因为Entity会被经常移入移除,所以用链表维护。
此外,如果lua设置hide某个Entity了,由于对象池内部机制,时间超过了AutoReleaseInterval之后,EntityInstance是会被自动释放的。
一些问题的进一步讨论
1.同一帧show/hide会有什么问题
TL;DR
Entity模块已经考虑到了这种情况,对于正在加载的Entity会打个特殊标记,加载完毕如果发现已经失效,就正常释放。
此外,hide需要用CClientEntityMgr:HideEntityWithId(entityId)接口,CClientEntityMgr:HideEntity(entityLogic)的问题后文讨论
代码细节
这里分两种情况:
show的时候对象池中没有资源,需要新加载
对象池中已经preload了资源,可以直接show
1.show的时候对象池中没有资源,需要新加载
依然是正常进入ShowEntity,发现没有资源,然后异步加载:
1 | public void ShowEntity(int entityId, string entityAssetName, string entityGroupName, int priority, object userData) |
这里会对正在异步加载的Entity放到一个叫m_EntitieseBeingLoadedDict中,而判断某个Entity是否在异步加载就是通过这个来判断IsLoadingEntity。
不过接着立即调用了hide,此时如果发现这个Entity只是一个正在被加载的Entity,就将其从Dict中移除,然后加入m_EntitiesToReleaseOnLoad这么个HashSet中,表示加载完成就Release之,不会走真正的Hide逻辑:
1 | public void HideEntity(int entityId, object userData) |
然后异步加载完成了,如果发现这个Entity实际上并不需要,就直接干掉,不会走InternalShow逻辑:
1 | private void LoadAssetSuccessCallback(string entityAssetName, object entityAsset, float duration, object userData) |
2.对象池中已经preload了资源,可以直接show
这种就比较简单了,因为不走异步加载资源逻辑,正常show/hide即可
2. 如何Preload
TODO