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写的比较多,不是很习惯这种用法,不过用了几次后,发现越来越喜欢这种同步方式写异步代码了。希望自己以后也能写出这么牛逼的异步扩展来,当然现在还是老老实实搬砖猫大的东西来用。