ET服务器框架学习笔记(十五)
文章目录
前言
前面好几篇都是关于通信协议的,本篇将对上篇说的Actor锁来个梳理,避免自己以后懵逼。
一、与Location锁相关组件
通过之前的文章的分析,可以得出与Location锁相关的类如下:
//协程锁组件
Game.Scene.AddComponent<CoroutineLockComponent>();
// location server需要的组件
Game.Scene.AddComponent<LocationComponent>();
// 访问location server的组件
Game.Scene.AddComponent<LocationProxyComponent>();
二、CoroutineLockComponent
1.CoroutineLock
协程锁类,内部有一个Key,对应一个实体ID对应一个CoroutineLock锁,观点在于Dispose时,会调用CoroutineLockComponent.Instance.Notify(this.key);通知CoroutineLockComponent组件这个协程被释放掉是信息。
2.LockQueue
内部包含一个队列,类型为ETTaskCompletionSource,即一个异步tcs存入这个队列中,同时这个tcs关联一个协程CoroutineLock锁。
3.CoroutineLockComponent
包含一个单例,用于其他类直接调用。
- Dictionary<long, LockQueue> lockQueues:一个字典类,一个实体ID,对应一个锁定的队列。
- async ETTask Wait(long key)方法:1.从lockQueues中获取一个锁定队列;2.如果锁定队列不存在,那么说明没人操控这个实体,那么新建一个LockQueue与Key,并将其放入lockQueues中;3.如果存在说明之前已经有人在wait控制这个实体,那么创建一个TCS实例,返回tcs.Task,并将其存入到对应的队列中。
- void Notify(long key):当有方法调用时,1.如果对应的LockQueue没有任何tcs了,直接释放掉lockQueue,2.如果还有tcs,那么从队列中取出一个tcs,然后设置他的SetResult,这样对应异步调用await方法的地方会被唤醒继续执行。
三、LocationComponent
1.LockInfo
锁定信息类,内部包含一个锁定的实体的LockInstanceId,一个CoroutineLock锁。
2.LocationComponent
-
locations:字典类,定位信息,一个实体的ID,对应一个实体的InstanceId
-
lockInfos:字典类,一个实体的ID,对应一个LockInfo信息。
-
async ETTask Add(long key, long instanceId):先异步调用CoroutineLockComponent.Instance.Wait,这样当被其他锁定的时候,会暂停到这里。
-
async ETTask Remove(long key):从locations中移除定位信息。
-
async ETTask Get(long key):从locations中拿到一个实体ID对应的最新的InstanceId。
-
async ETVoid Lock(long key, long instanceId, int time = 0):锁定函数,这个函数很关键。
1.从CoroutineLockComponent.Instance.Wait获取一个CoroutineLock。
2.通过instanceId与coroutineLock,实例化一个LockInfo,然后放入lockInfos中。
3.如果time>0,则增加一个定时器,到时自动解锁。 -
void UnLock(long key, long oldInstanceId, long newInstanceId):解锁,
1.新的InstanceId,将locations中的InstanceId更新为新的newInstanceId。
2.从lockInfos中获取到lockInfo,调用lockInfo的Dispose
3.从而调用CoroutineLock的Dispose,调用CoroutineLockComponent.Instance.Notify
4.从对应的lockQueue中以先进先出的方式,拿到一个tcs,并调用SetResult设置结果。从而使得再最先调用await的异步代码处进行唤醒,继续执行。
四、LocationProxyComponent
这个组件一般是出了Location服务外的内网服务会挂载,内部封装了对定位服务的增加,删除,锁定,解锁等操作。实际就是给Location服务发送协议。
1.Awake:主要任务就是初始化Location服务地址
2.Add:发送一条ObjectAddRequest到Location服务
3.Lock:发送一条ObjectLockRequest到Location服务
4.UnLock:发送一条ObjectUnLockRequest到Location服务
5.Remove:发送一条ObjectRemoveRequest到Location服务
6.Get:发送一条ObjectGetRequest到Location服务。
上面分别对应了下面的实例处理方法:
ObjectAddRequestHandler,
ObjectLockRequestHandler,
ObjectUnLockRequestHandler,
ObjectRemoveRequestHandler,
ObjectGetRequestHandler
对应是调用LocationComponent的各个方法。
注意:
ET在很多地方都调用了类型下面的代码:
using (await CoroutineLockComponent.Instance.Wait(key + (int)AppType.Location)){}
这里的using,有个特别之处,在于当结束{}时,会自动调用()内部类的Dispose方法。因此当没有锁定时,wait函数返回一个 return ComponentFactory.Create<CoroutineLock, long>(key);,一个CoroutineLock实例。然后结束using时会自动调用CoroutineLock的DisPose方法,从而调用到CoroutineLockComponent.Instance.Notify(this.key);从而将锁给移除了。
还需要注意到LocationComponent的Lock方法,并没有使用using而是:CoroutineLock coroutineLock = await CoroutineLockComponent.Instance.Wait(key + (int)AppType.Location);
这样的话,对于多个锁定,都会进入到锁定队列中。而对于其他用法,重复调用时,由于using语法,结束一个,立马就会销毁一个,等于是只会有一个锁定,等待真正的UnLock来触发。
同样的道理适用于在InnerMessageDispatcher时,也会使用await CoroutineLockComponent.Instance.Wait(message.ActorId)
的方式。所以在后续使用中,需要用到Location服务的时候,都应该用这种方式。
大致的原因在ETBOOK中有说,有需要可以去看。
总结
到这里总算应该将所有ET通信的内容,都简单梳理了一遍。对Actor模型,以及分布式框架的理解也多了一分,再次感谢ET作者开源,并且使用相对轻量一些的C#来实现。
希望后面的ET6.0能带来更多惊喜,下一篇开始,对ET中其他的业务模块进行单独分析,一个个来吧。