ET服务器框架学习笔记(五)
前言
上篇文章简单的理解了下ETTASK,当然还有ETVOID相关的东西,两个差不多,多用即可,接下来继续看TimerComponent。
一、MultiMap
查看源码又发现一个数据结构,用于记录管理SortedDictionary<T, List>这种类型的数据,其中可学习的地方在于,有个回收与重复使用的List池子,这种以内存换性能的方式,在ET里面比较常见。
// 重用list
private readonly Queue<List<K>> queue = new Queue<List<K>>();
private readonly Dictionary<long, Timer> timers = new Dictionary<long, Timer>();
/// <summary>
/// key: time, value: timer id
/// </summary>
private readonly MultiMap<long, long> timeId = new MultiMap<long, long>();
private readonly Queue<long> timeOutTime = new Queue<long>();
private readonly Queue<long> timeOutTimerIds = new Queue<long>();
1.数据结构
上面是TimerComponent主要使用的数据结构。
- timeId:存储一个时间Time,对应一个List的结构,这样当时间超过这个Time的时候,可以取出所有相关ID,然后经过几轮取出与放入的操作,最后调用到相关timer的tcs.SetResult,这样异步那边的延时得以继续往下运行。
foreach (KeyValuePair<long, List<long>> kv in this.timeId.GetDictionary())
{
long k = kv.Key;
if (k > timeNow)
{
minTime = k;
break;
}
this.timeOutTime.Enqueue(k);
}
while(this.timeOutTime.Count > 0)
{
long time = this.timeOutTime.Dequeue();
foreach(long timerId in this.timeId[time])
{
this.timeOutTimerIds.Enqueue(timerId);
}
this.timeId.Remove(time);
}
while(this.timeOutTimerIds.Count > 0)
{
long timerId = this.timeOutTimerIds.Dequeue();
Timer timer;
if (!this.timers.TryGetValue(timerId, out timer))
{
continue;
}
this.timers.Remove(timerId);
timer.tcs.SetResult();
}
2.核心逻辑
主要使用的时间戳方式,来判定是否要调用,timerComponent提供了几种调用方式:
public ETTask WaitTillAsync(long tillTime, CancellationToken cancellationToken)
{
ETTaskCompletionSource tcs = new ETTaskCompletionSource();
Timer timer = new Timer { Id = IdGenerater.GenerateId(), Time = tillTime, tcs = tcs };
this.timers[timer.Id] = timer;
this.timeId.Add(timer.Time, timer.Id);
if (timer.Time < this.minTime)
{
this.minTime = timer.Time;
}
cancellationToken.Register(() => { this.Remove(timer.Id); });
return tcs.Task;
}
public ETTask WaitTillAsync(long tillTime)
{
ETTaskCompletionSource tcs = new ETTaskCompletionSource();
Timer timer = new Timer { Id = IdGenerater.GenerateId(), Time = tillTime, tcs = tcs };
this.timers[timer.Id] = timer;
this.timeId.Add(timer.Time, timer.Id);
if (timer.Time < this.minTime)
{
this.minTime = timer.Time;
}
return tcs.Task;
}
public ETTask WaitAsync(long time, CancellationToken cancellationToken)
{
ETTaskCompletionSource tcs = new ETTaskCompletionSource();
Timer timer = new Timer { Id = IdGenerater.GenerateId(), Time = TimeHelper.Now() + time, tcs = tcs };
this.timers[timer.Id] = timer;
this.timeId.Add(timer.Time, timer.Id);
if (timer.Time < this.minTime)
{
this.minTime = timer.Time;
}
cancellationToken.Register(() => { this.Remove(timer.Id); });
return tcs.Task;
}
public ETTask WaitAsync(long time)
{
ETTaskCompletionSource tcs = new ETTaskCompletionSource();
Timer timer = new Timer { Id = IdGenerater.GenerateId(), Time = TimeHelper.Now() + time, tcs = tcs };
this.timers[timer.Id] = timer;
this.timeId.Add(timer.Time, timer.Id);
if (timer.Time < this.minTime)
{
this.minTime = timer.Time;
}
return tcs.Task;
}
}
依次为:
-WaitTillAsync(long tillTime, CancellationToken cancellationToken):带取消异步的,传入时间戳的定时功能,异步取消时,会自动调用remove清理掉timer
cancellationToken.Register(() => { this.Remove(timer.Id); });
- WaitTillAsync(long tillTime):传入时间戳的定时功能,不带自动取消
- WaitAsync(long time, CancellationToken cancellationToken),传入延时时间,带取消的定时器,原理是就是在上面的基础上,将当前时间+延时时间,变为时间戳。
- WaitAsync(long time):传入延时时间,不带自动取消功能,其他同上。
3.优化点
this.minTime,每次增加一个时间戳定时器,比较这个值,获取到距离当前时间最近的一次时间戳。这样在update的时候,没到最近的时间戳,则直接return,提升性能。
如果到了这个时间戳,则取出对应的time结构,调用里面的tcs.setResult,中间通过几次queue的操作,来提升性能,而不是直接在List或者字典中操作,这样也能提升性能。queue由于底层的数据结构,所以很适合做这种取出,压入的操作。
foreach (KeyValuePair<long, List<long>> kv in this.timeId.GetDictionary())
{
long k = kv.Key;
if (k > timeNow)
{
minTime = k;
break;
}
this.timeOutTime.Enqueue(k);
}
while(this.timeOutTime.Count > 0)
{
long time = this.timeOutTime.Dequeue();
foreach(long timerId in this.timeId[time])
{
this.timeOutTimerIds.Enqueue(timerId);
}
this.timeId.Remove(time);
}
while(this.timeOutTimerIds.Count > 0)
{
long timerId = this.timeOutTimerIds.Dequeue();
Timer timer;
if (!this.timers.TryGetValue(timerId, out timer))
{
continue;
}
this.timers.Remove(timerId);
timer.tcs.SetResult();
}
二、使用方式
1.直接拿ET内的方式
代码如下(示例):
// 等待0.5s再发送
long instanceId = self.InstanceId;
await TimerComponent.Instance.WaitAsync(500);
if (self.InstanceId != instanceId)
{
throw new RpcException(ErrorCode.ERR_ActorRemove, $"{MongoHelper.ToJson(iActorRequest)}");
}
self.ActorId = await Game.Scene.GetComponent<LocationProxyComponent>().Get(self.Id);
return await self.RunInner(iActorRequest);
逻辑:
if (self.InstanceId != instanceId)
{
throw new RpcException(ErrorCode.ERR_ActorRemove, $"{MongoHelper.ToJson(iActorRequest)}");
}
self.ActorId = await Game.Scene.GetComponent<LocationProxyComponent>().Get(self.Id);
return await self.RunInner(iActorRequest);
会在调用这个函数后的500毫秒后才会完成,由于是异步方式,所以不会阻塞调用者的线程,让他去做其他事情。
总结
timerComponent核心内容就是通过异步方式来调用,使用方式非常方便,直接以同步方式书写代码即可,他不会阻塞线程执行。
下一篇讲通信相关内容了。