天天看點

ET伺服器架構學習筆記(五)ET伺服器架構學習筆記(五)前言一、MultiMap二、使用方式總結

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核心内容就是通過異步方式來調用,使用方式非常友善,直接以同步方式書寫代碼即可,他不會阻塞線程執行。

下一篇講通信相關内容了。

ET