ObjectEvents
请大家关注一下我的微博 @NormanLin_BadPixel坏像素
前面几段是单例的写法,不讲。
public Assembly HotfixAssembly;
这里不懂Assembly的可以先去了解一下C#的反射技术。这里简要介绍一下
通俗的来讲,就是反射是通过一个对象A去了解另一个对象B的内部结构和信息,即使在你不知道那个对象B存在的情况下。
Assembly:表示一个程序集,它是一个可重用、无版本冲突并且可自我描述的公共语言运行时应用程序构建基块。
这是有关热更新的程序集,我们现在不学热更新,先放着。但是对Assembly得了解。
private readonly Dictionary
IObjectEvent
public interface IObjectEvent
{
Type Type();
void Set(object value);
}
哎呦这接口啥用呦,一个返回Type的方法,一个参数是object的Set方法。看起来像是有点特殊的Get/Set方法。留着之后再研究吧。回到ObjectEvents
ObjectEvents
private EQueue<Disposer> updates = new EQueue<Disposer>();
Equeue
进去看了下,发现跟普通的Queue没什么区别,不知道为什么用这个,知道的大佬可以告知一声。
Disposer
前面的几个都是特性的定义,都是关于Bson序列化的。
1. 如果值等于默认值则忽略
2. 默认值是 1L
3. 是一个Bson元素
4. 是索引
Disposer的意思是处理者。应该是处理各种事件的抽象类。
在构造方法里把自己加入ObjectEvents里面。我们看一下ObjectEvents里面的 Add(Disposer) 方法。
ObjectEvents
public void Add(Disposer disposer)
{
IObjectEvent objectEvent;
if (!this.disposerEvents.TryGetValue(disposer.GetType(), out objectEvent))
{
return;
}
if (objectEvent is ILoad)
{
this.loaders.Enqueue(disposer);
}
if (objectEvent is IUpdate)
{
this.updates.Enqueue(disposer);
}
if (objectEvent is IStart)
{
this.starts.Enqueue(disposer);
}
}
看到这,大家思考一下大概就能猜到了,这里只是自己管理了一套Unity里面的Start/Update还有一个Load。当实例化一个Disposer的时候,首先会判断一下是否有已经注册的该事件类型,如果有,则根据事件类型把自己压入对应的队列中。这里需要注意的是,ILoad/IUpdate/IStart都是接口,这里objectEvent is ILoad 是判断是否实现了ILoad接口。如何注册事件类型,这我们在之后会说到的。我们现在再继续来看Disposer。
Disposer
这里实现了一个接口 IDisposable
我去看了一下IDisposavle,发现有一个特性[ComVisible(true)],顺便百度了一下,原来:
COM = Component Object Model,微软的上一代编程模型
[ComVisible(true/false)]控制程序集中个别托管类型、成员或所有类型对 COM 的可访问性。
但是,它默认就是true。。
好了,看Disposer里面怎么实现这个接口的吧。默认是把Id设为0,然后调用ObjectPool里面的Recycle方法,把自己传进去。我们来看一下ObjectPool。
ObjectPool
很好猜到这是一个对象池单例,Recycle应该是资源回收,也就是把这个处理器对象回收。
进去看一下代码,哦吼还真是。如果对对象池这个概念不理解的,这里不想说,百度一下吧,很简单好好理解的。
好,知道Disposer是什么了,我们回到ObjectEvents
ObjectEvents
我们看到,每个update/lateUpdate/loader都有两个队列。等等,两个干嘛?而且名字直接就是updates2。。。不是很优雅啊。我去看了一下ET源码,发现根本就没有ObjectEvents这个类。。。原来这是LandlordsCore的作者自己加进去的。可能是为了做统一管理游戏流程所以加了这个吧,很不错,我们也学习一下。
Close 是关闭的方法,略过。
public void Add(string name, Assembly assembly)
OK,看到了之前Init里面调用的方法了。我们一句一句来看它干了什么。
this.assemblies[name] = assembly;
foreach (Assembly ass in this.assemblies.Values)
{
foreach (Type type in ass.GetTypes())
{
object[] attrs = type.GetCustomAttributes(typeof(ObjectEventAttribute), false);
if (attrs.Length == 0)
{
continue;
}
object obj = Activator.CreateInstance(type);
IObjectEvent objectEvent = obj as IObjectEvent;
if (objectEvent == null)
{
Log.Error($"组件事件没有继承IObjectEvent: {type.Name}");
continue;
}
this.disposerEvents[objectEvent.Type()] = objectEvent;
}
}
在储存程序集的字典里放入这个name的程序集,我们可以理解为不同模块的程序集。
然后,遍历字典里面存放的所有程序集。
这里我有疑惑,我们每次Add的是一个模块的程序集,其他模块的程序集没有变,为什么要对其他模块的程序集再做处理?我之后会试试不遍历所有模块会不会有影响。这里先按照作者的思路走吧。
首先遍历所有模块的程序集,再遍历单个模块程序集里的所有类。再获取这个类的所有指定自定义特性ObjectEventAttribute。这里用到了大量C#反射的知识,和我一样对反射了解不是很深的同学可以跟我一起百度一下了。浅析C#中的Attribute
ObjectEventAttribute只是一个标识ObjectEvent的特性。
attrs这个变量只是用来判断这个类是否有ObjectEvent特性,无则跳过,有则继续。
然后通过 Activator.CreateInstance(type) 来实例化一个Type类型的实例,再尝试从这个实例获取IObjectEvent接口,如果有,则注册事件。
继续往下读,Get和GetAll就不说了。Add之前讲过了。
public void Awake(Disposer disposer)
激活传入的处理器。首先获取处理器的类型来确定事件的类型,再从已经注册的事件列表里找是否已经注册该类型的事件,如果有,则设置这个事件的处理器,并且执行Awake方法。后面的几个Awake只是多了几个参数的激活方法。
private void Start()
跟Awake很相似,是执行starts队列里的事件处理器。
public void Load()
public void Update()
public void LateUpdate()
这三个很相似,分别是调用Load/Update/LateUpdate队列里面的事件处理器。需要主意的是,这些事件都有两个队列,在一次执行的时候会把队列1里面符合条件的处理器存入队列2。当全部执行完的时候,队列1是空的,这个时候会把队列1跟队列2互换。不是很清楚这样写是为了什么。可能是用来多次执行的,比如Update,如果在执行方法里面没有把Id设为0,那它就会出现在下一次执行的队列1里面继续执行。(我猜的)
至此,ObjectEvents讲完了。大家应该还有很多不理解的地方,比如我在什么时候注册了什么事件,又在哪个地方调用了这些方法。不急,我们先大概了解了这个ObjectEvents是干什么的,后面遇到它的应用理解起来就快了。这些问题后面肯定是会解决的。