ET服务器框架学习笔记(五)

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核心内容就是通过异步方式来调用,使用方式非常方便,直接以同步方式书写代码即可,他不会阻塞线程执行。
下一篇讲通信相关内容了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值