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

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


前言

之前一直忘了说,我用的ET框架基于5.0的,虽然6.0更牛逼,更强大,但是基于猫大的说法,6.0大概还得个一年才会有个完全版本出来,所以到时候出了6.0再去学习一遍。


接着上篇文章来写,这篇主要写点关于时间管理器的

一、TimerComponent是什么?

TimerComponent,主要用于做一些定时器任务,比如每一秒执行一次,或者延时多长时间执行一次的。
TimerComponent是比较常见的一种组件功能,一般情况下就是将自己的方法传入到TimerComponent组件中作为回调方法来使用,接下来看看ET里面的TimerComponent的实现。

二、TimerComponent相关System

跳转到相关TimerComponent文件,可以看到与TimerComponent关联的两个System,一个是TimerComponentAwakeSystem:

public class TimerComponentAwakeSystem : AwakeSystem<TimerComponent>
	{
		public override void Awake(TimerComponent self)
		{
			TimerComponent.Instance = self;
		}
	}

结合上篇文章,往entity(这里是Game.Scene)里面AddComponent组件时会实例化一个TimerComponent,然后这里的TimerComponentAwakeSystem 就是实现了一个单例赋值。
另一个System是:TimerComponentUpdateSystem

public class TimerComponentUpdateSystem : UpdateSystem<TimerComponent>
	{
		public override void Update(TimerComponent self)
		{
			self.Update();
		}
	}

很明显这个就是在EventSystem里面,update循环调用的,用于驱动TimerComponent。

1.Timer

:下面认识下,TimerComponent驱动的单元,即

public struct Timer
	{
		public long Id { get; set; }
		public long Time { get; set; }
		public ETTaskCompletionSource tcs;
	}

这里主要包含3个部分,Id:一个id生产器,生成的唯一Id,一个延时时间Time,一个ETTaskCompletionSource 。
需要重点记录下ETTaskCompletionSource

2.ETTASK的牛逼之处

说到ETTaskCompletionSource 就不得不说到ETTASK等相关内容:

在这里插入图片描述
如上图,如果把上面这些类全部搞明白,那么ETTASK等相关内容应该就很清晰了。

1.ETTASK干什么的

一开始看到这个,我也是很懵逼的状态,TASK大家都知道是一个C#优化Thread的产物,即线程池的概念。避免大家滥用线程,导致各种问题,线程池很好的帮助我们管理线程问题。那么ETTASK又是什么呢,个人理解是用来实现异步函数所用的类。

举例:假如现在我需要延时60秒后,让变量A=100,那么一般的计时器就是传入一个回调函数,传入60秒,回调里面将A=100。然后计时器等到60秒后,执行回调函数,这样A就被赋值了。
而ETTASK可以使用如下方式:
在一个方法里面:

await timerComponent.WaitAsync(60, cancellationToken);
a = 100

这样就像是以同步方式写代码一样,不需要回调。有人可能和我以前一样觉得,回调方式也挺好用的。那么如果连续回调呢,就是延时50秒后执行A= 100,在这个基础上再延时100秒 A= 50,在这个基础上再延时50秒 A= 100…这样的话代码可能嵌套N个回调,进入了回调地狱了。异步函数很好的解决了这个问题

1.为啥需要ETTASK

之前我刚开始的时候,也会发现为啥要用ETTASK,直接用TASK不就有C#支持的异步方式了吗。
大家可以看看ET Book里面的内容
ETBOOK
在这里插入图片描述
这里的内容比较老了,但是基础思想还在。
而猫大在ET5.0中,使用的扩展异步函数,以及类TASK方式,使得可以用await方式实现单线程异步,避免回调地狱,以同步方式写异步代码,简单清晰。

2.ETTASK的原理

先看懂这几篇译文:
https://www.cnblogs.com/raytheweak/tag/C%23/
在这里插入图片描述
这里翻译了msdn上的关于异步方法的解释。而猫大的ETTASK几乎包含了上面所说的所有。扩展了异步方法,同时也使用了类TASK。
在这里插入图片描述
图中红框部分,都是猫大有自己定义的部分,对应于ET项目中的
在这里插入图片描述
猫大这里扩展了两个类TASK,一个是有返回结果的,一个是无返回结果的。

3.ETTASK的个人理解

看了上面的实现后,内心再一次向猫大表示佩服。这里简单说下我个人对ETTASK的理解。
当我们需要使用异步方式执行某个函数时,可以用await来实现对这个函数的等待,不需要将等待完毕后,要继续处理的逻辑封装成一个回调,可以直接接着await后面写。
但是这个函数需要返回一个ETTASK,或者直接用ETTaskCompletionSource或者ETCancellationTokenSource包含的TASK当作返回值即可。上面两种包含了异步方法的各种实现,还包含了取消异步,异常异步等处理,非常方便。
使用的话,多敲几遍代码,多调试几遍,应该能够很快上手使用。
猫大的例子里面也有很多使用方式,使用的时候抄一抄,挪一挪应该就没问题了。下面摘抄一个例子出来:

while (true)
            {
                await timerComponent.WaitAsync(50, cancellationToken);
                
                long timeNow = TimeHelper.Now();
                
                if (timeNow - this.StartTime >= this.needTime)
                {
                    unit.Position = this.Target;
                    break;
                }

                float amount = (timeNow - this.StartTime) * 1f / this.needTime;
                unit.Position = Vector3.Lerp(this.StartPos, this.Target, amount);
            }

上面是一个移动组件的内容,由于移动常态存在,除非移动组件移除,所以用while来循环处理,内部包含一个异步函数,就是调用时间组件延时50毫秒,这样后面的位移逻辑,相当于是50毫秒触发一次。同时传入了一个取消异步的回调进来。

代码往上查找调用,在UnitPathComponentHelper可以看到如下代码:

self.CancellationTokenSource?.Cancel();
            self.CancellationTokenSource = new CancellationTokenSource();
            await self.MoveAsync(self.ABPath.Result);
            self.CancellationTokenSource.Dispose();
            self.CancellationTokenSource = null;

即当玩家每次调用移动,那么首先取消之前的移动,在取消任务中注册的回调都会执行到:

// 协程如果取消,将算出玩家的真实位置,赋值给玩家
            cancellationToken.Register(() =>
            {
                long timeNow = TimeHelper.Now();
                if (timeNow - this.StartTime >= this.needTime)
                {
                    unit.Position = this.Target;
                }
                else
                {
                    float amount = (timeNow - this.StartTime) * 1f / this.needTime;
                    unit.Position = Vector3.Lerp(this.StartPos, this.Target, amount);
                }
            });

还包括计时器内的也会执行到。这样通过一个取消任务调用一个取消,可以相应很多取消操作,而不用一个个去回调各种取消逻辑。

cancellationToken.Register(() => { this.Remove(timer.Id); });

ETTASK的由于没有开新线程,也没有使用线程池Task,所以肯定是在主线程运行的,那么游戏开始的SynchronizationContext.SetSynchronizationContext(OneThreadSynchronizationContext.Instance);这句代码有啥用呢?
个人理解为,在ET中虽然主逻辑是单线程的,但是与IO设备,比如从socket读取数据,或者从TCP,KCP获取网络数据得时候,是多线程的获取数据的,所以当数据到达时,为了保证是单线程,所以在获取数据的地方,以回调得方式,将回调方法扔到OneThreadSynchronizationContext中执行,毕竟program文件最下面,是有如下代码:

while (true)
				{
					try
					{
						Thread.Sleep(1);
						OneThreadSynchronizationContext.Instance.Update();
						Game.EventSystem.Update();
					}
					catch (Exception e)
					{
						Log.Error(e);
					}
				}

总结

本来还想一下子就介绍完timer组件,发现ET内部的ETTASK太过强大,因此多写一点。
备注:个人开始也各种不习惯,还是写LUA写的比较多,不是很习惯这种用法,不过用了几次后,发现越来越喜欢这种同步方式写异步代码了。希望自己以后也能写出这么牛逼的异步扩展来,当然现在还是老老实实搬砖猫大的东西来用。

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值