ET|08 虚拟进程/纤程
这篇文章开始,学习的是ET 8.0的内容,注意,我这里学习的是Alex大大拆分过后的前后端分离版本的ET
Git仓库在这里:ALEXTANGXIAO/GameNetty: GameNetty (github.com)
与原版ET有一定差距,但是不大,主要是前后端拆开了,ET可以作为独立的一部分加入你的工程
框架启动流程
对于客户端而言,启动部分在Init.cs中,这一部分是不可更新的,可以说是工程启动入口
大部分内容都不重要,主要是这一块程序集的获取,作用分为两部分,第一个获取的程序集runtime是会了反射调用启动入口
后面创建一堆程序集,并放入CodeTypes里,是为了做程序集分析,因为ET的一些写法很依赖[Attribute],这里是提前分析好所有类的标签(比如ET的Event就不需要注册,而是以来标签进行监听)
private async ETTask StartAsync()
{
...
// GameNetty.Runtime (Assembly)
Assembly runtime = typeof(Entry).Assembly;
World.Instance.AddSingleton<CodeTypes, Assembly[]>(new[]
{
// GameNetty.Runtime (Assembly)
typeof(Entry).Assembly,
// Assembly-CSharp (Assembly)
typeof(Init).Assembly,
// GameProto (Assembly)
typeof(C2G_Ping).Assembly,
// GameLogic (Assembly)
typeof(PingComponent).Assembly
});
IStaticMethod start = new StaticMethod(runtime, "ET.Entry", "Start");
start.Run();
}
然后进入Entry,这里头才是真的游戏启动入口
private static async ETTask StartAsync()
{
Log.Debug("Entry Start");
// 注册Entity序列化器
EntitySerializeRegister.Init();
World.Instance.AddSingleton<IdGenerater>();
...
// 创建需要reload的code singleton
CodeTypes.Instance.CreateCode();
// await World.Instance.AddSingleton<ConfigLoader>().LoadAsync();
await FiberManager.Instance.Create(SchedulerType.Main, ConstFiberId.Main, 0, SceneType.Main, "");
}
这里同样通过World创建了一些单例类,比较特殊的是这个CreateCode()方法,我们进去看一下
public void CreateCode()
{
var hashSet = this.GetTypes(typeof (CodeAttribute));
foreach (Type type in hashSet)
{
object obj = Activator.CreateInstance(type);
((ISingletonAwake)obj).Awake();
World.Instance.AddSingleton((ASingleton)obj);
}
}
可以看到,它收集了所有带有[CodeAttribute]标签的类,然后同样使用Wrold创建单例,有挺多类添加了这个标签
比如:
[Code]
public class EventSystem: Singleton<EventSystem>, ISingletonAwake { ... }
为啥要多此一举?
因为通过标签自动添加的单例类,可以进行逻辑热重载,这部分先不谈,反正是有好处的
最后,就是核心入口了,我们调用纤程管理器的创建方法
虚拟进程/纤程
先放一张ET 8.0的整体架构图
在ET 8.0之前,ET是单线程,多进程的模式,也就说为了性能,你得区分很多个服务器进程,不同进程之前的交流通过服务器内部协议进行
而ET 8.0直接改成多线程多进程了,也就每一个纤程你都可以当做一个进程
在传统多线程架构中,经常会产生多线程资源竞争问题,所以必须要加线程锁,或者各种原子操作,但这些都会浪费性能,而纤程可以避免多线程之间的资源竞争
每一个纤程下的所有Entity,在创建时,都会被注册进这个纤程的EntitySystem
public class Fiber
{
...
public EntitySystem EntitySystem { get; }
}
Entity的所有生命周期,都是由EntitySystem驱动的,本质还是由Fiber在驱动,那么谁会去驱动Fiber呢?
以Update为例,其实有3个地方在调用
这和Fiberr的驱动模式有关系
public enum SchedulerType
{
Main, //主线程
Thread, //固定线程
ThreadPool, //线程池
}
主线程调用
最朴实无华的驱动模式,所有Fiber排成队列,一个一个进行Update
固定线程
线程和Fiberr一一对应,一个线程只会创建一个Fiber,并且也只会驱动这个Fiber
线程池
在运行开始就会创建很多线程,而Fiber则会被随机的线程进行驱动,但是ET能保证同一时间,一个Fiber只会隶属于某一个线程,避免多个线程同时驱动一个Fiber