ET伺服器架構學習筆記(五)
文章目錄
- ET伺服器架構學習筆記(五)
- 前言
- 一、MultiMap
-
- 1.資料結構
- 2.核心邏輯
- 3.優化點
- 二、使用方式
-
- 1.直接拿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
- 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核心内容就是通過異步方式來調用,使用方式非常友善,直接以同步方式書寫代碼即可,他不會阻塞線程執行。
下一篇講通信相關内容了。