ET-EventSystem
上篇记录了对基础类的理解,这片回到program.cs文件
文章目录
前言
program主要用于初始化game.scene里面的内容,首先最重要的就是事件系统。
一、EventSystem?
EventSystem用于事件通知,事件回调,走的消息监听,消息派发,消息回调的方式,是一种有效解耦各种业务逻辑代码的编程方式。
二、EventSystem详解
1.UnOrderMultiMap
要想完全理解EventSystem,先要弄清楚ET里面的UnOrderMultiMap,这是个数据结构辅助类。专门用于管理某个类型对应的List。当然这个类,还带有重用功能,用于提升性能。
- Dictionary<T, List> dictionary,用于存放一个类型,对应另外一个类型的List字典
- Add(T t, K k):往T这个类型对应的实例t,所代表的List中增加一个实例k。
- First():返回字典里面第一个KeyValuePair类型的实例
- FetchList():queue里面放着回收的list,这个方法可以获得一个List,可能是回收的,或者新实例化的。
- RecycleList(List list):把一个list回收到quene中,当然这里做了优化处理,当超过100条当时候,就不回收了,以免白白浪费内存
- Remove(T t, K k):与ADD对应,从t实例对应的List中移除某个项,这里有判定如果对应的List空了,就进行回收,并从字典里面移除
- Remove(T t):将整个t对应的list给回收掉
- K[] GetAll(T t):获取t对应的整个List,需要注意这里使用List.ToArray()来返回一个新的List
- List this[T t]:通过这个属性可以获得内部真实的一个List
- K GetOne(T t):获取t对应的List里面第一个元素
- Contains(T t, K k):对应List是否包含某个实例
- ContainsKey(T t):查询字典里面是不是包含有这个t作为key
- Clear():清理所有字典里面的List,同时将List回收
2.interface IEvent
事件相关接口,用于定义事件处理,一个事件监听一个IEvent,AEvent,class AEvent。具体需要具体实现
public interface IEvent
{
void Handle();
void Handle(object a);
void Handle(object a, object b);
void Handle(object a, object b, object c);
}
3.回到EventSystem
了解类上面的基础数据结构之后回到EventSystem.
定义几个DLLType枚举类型。
- assemblies:字典类,定义上面的枚举对应的真实的程序集信息
- Dictionary<long, Component> allComponents:以组件的id为key,存放所有事件系统的组件
- UnOrderMultiMap<Type, Type> types::类型列表,存放各种类型对应的类型列表
- Dictionary<string, List> allEvents:所有事件以及事件对应的IEVENT列表。即每个事件可以有N个监听,对应N个处理。
- UnOrderMultiMap<Type, IAwakeSystem> awakeSystems:用于将所有带有awakeSystem类的awakeSystem放进这里进行统一管理,每个类型对应一个List<**System>
- startSystems,destroySystems,loadSystems,updateSystems,lateUpdateSystems,changeSystems,deserializeSystems如上一样,只是触发的事件时机不同
4.EventSystem重要方法
- Add(DLLType dllType, Assembly assembly)
首先添加枚举以及对应的程序集到assemblies中。
- 遍历assemblies中所有程序集,再从每个程序集中遍历每个类型。
- 获取每个类型的BaseAttribute特性,包括由他派生出来的特性。这样筛选出对应类型信息出来
- 由于一个类可能标记类几个类型信息,然后ET使用的都是标记在类名前的第一个类型信息,当做筛选条件
- 获取到第一个特性后,根据第一个特性的特性类,将类型信息存放到types里面
- 由于types是上面说过的UnOrderMultiMap<Type, Type>类型, 这样每个特性类型,都可以通过内置的各种方法找到对应的该特性类型信息的,所有类型List集合。、
- 举例:类型A,B,C都标记了一个awakeBaseAttribute,那么ABC三个类都类型信息就都存放在 types[awakeBaseAttribute]对应的一个List里面了
- 清空各个事件系统对应的map,方便后面根据types重新反射获取
this.awakeSystems.Clear();
this.lateUpdateSystems.Clear();
this.updateSystems.Clear();
this.startSystems.Clear();
this.loadSystems.Clear();
this.changeSystems.Clear();
this.destroySystems.Clear();
this.deserializeSystems.Clear();
- 从types里面特性,从中取出ObjectSystemAttribute特性对应的类的List信息(这里用到了上面的this属性方法),然后遍历获取类型。
- 针对每个类型,获取特性,通过反射实例化,然后再根据实例的具体类型,将他们加到不同的eventSystem中。
- 举例:类A标记了一个ObjectSystem,然后类A是继承于AwakeSystem,那么类A被实例化出来,然后被放到awakeSystems[component的type信息]对应的List中
这里重中之重要理解的是,所有system类已经实例化完毕,并且以他关联的组件类型为key,放入到一个列表中,然后在增加组件时,会根据组件的类型,找到对应的各个system来调用相对应的方法。
foreach (Type type in types[typeof(ObjectSystemAttribute)])
{
object[] attrs = type.GetCustomAttributes(typeof(ObjectSystemAttribute), false);
if (attrs.Length == 0)
{
continue;
}
object obj = Activator.CreateInstance(type);
switch (obj)
{
case IAwakeSystem objectSystem:
this.awakeSystems.Add(objectSystem.Type(), objectSystem);
break;
case IUpdateSystem updateSystem:
this.updateSystems.Add(updateSystem.Type(), updateSystem);
break;
case ILateUpdateSystem lateUpdateSystem:
this.lateUpdateSystems.Add(lateUpdateSystem.Type(), lateUpdateSystem);
break;
case IStartSystem startSystem:
this.startSystems.Add(startSystem.Type(), startSystem);
break;
case IDestroySystem destroySystem:
this.destroySystems.Add(destroySystem.Type(), destroySystem);
break;
case ILoadSystem loadSystem:
this.loadSystems.Add(loadSystem.Type(), loadSystem);
break;
case IChangeSystem changeSystem:
this.changeSystems.Add(changeSystem.Type(), changeSystem);
break;
case IDeserializeSystem deserializeSystem:
this.deserializeSystems.Add(deserializeSystem.Type(), deserializeSystem);
break;
}
}
11.然后同样的,筛选出所有EventAttribute特性标记的类,实例化出来,这些类都继承或者实现类IEvent等,然后将特性打进去的事件字符串当做key,添加到对应到事件列表中
// 分发数值监听
[Event(EventIdType.NumbericChange)]
public class NumericChangeEvent_NotifyWatcher: AEvent<long, NumericType, int>
{
public override void Run(long id, NumericType numericType, int value)
{
Game.Scene.GetComponent<NumericWatcherComponent>().Run(numericType, id, value);
}
}
上面的事件实例添加到了allEvents[EventIdType.NumbericChange]对应的List中,这样一旦派发EventIdType.NumbericChange事件,就会调用对应类型实例的Run方法里面去
5.EventSystem的Add
Game.EventSystem.Add(DLLType.Model, typeof(Game).Assembly);
通过将当前Game所在的程序集(ETModel),添加到事件系统中
Game.EventSystem.Add(DLLType.Hotfix, DllHelper.GetHotfixAssembly());
GetHotfixAssembly()方法内部通过Assembly.Load(dllBytes, pdbBytes);方式加载热更程序集,获取到程序集,并添加到事件系统中
通过上面两句调用,将下面两个程序集内的所有相关事件system进行实例化,并放入到各个事件UnOrderMultiMap中
监听当component通过public void Add(Component component)方法加入到事件系统中时,添加到各个队列中,然后在合适时机(比如update,在每个update中调用),调用与componnet对应的system中的对应方法
public void Add(Component component)
{
this.allComponents.Add(component.InstanceId, component);
Type type = component.GetType();
if (this.loadSystems.ContainsKey(type))
{
this.loaders.Enqueue(component.InstanceId);
}
if (this.updateSystems.ContainsKey(type))
{
this.updates.Enqueue(component.InstanceId);
}
if (this.startSystems.ContainsKey(type))
{
this.starts.Enqueue(component.InstanceId);
}
if (this.lateUpdateSystems.ContainsKey(type))
{
this.lateUpdates.Enqueue(component.InstanceId);
}
}
总结
这里先记录到这,下篇继续。回顾一下,通过遍历程序集类,获取特性标记与类型的类型信息,存到types里面,然后再遍历这个typs通过特性,以及类型信息,反射实例化,放到不同的map或者字典里面处理。
总体梳理一下现在碰到的几个特性对应的意义:
- ObjectSystemAttribute:组件的System,比如Update,Load,Awake等,如下:
[ObjectSystem]
public class ActorMessageDispatcherComponentStartSystem: AwakeSystem<ActorMessageDispatcherComponent>
{
public override void Awake(ActorMessageDispatcherComponent self)
{
self.Awake();
}
}
这个system就是继承AwakeSystem,并且关联ActorMessageDispatcherComponent组件,现在注册好这个system后,当ActorMessageDispatcherComponent添加到EventSystem后,会调用到public override void Awake(ActorMessageDispatcherComponent self)这个函数,进一步调用到ActorMessageDispatcherComponent本身的Awake函数,其他system类似如此。
- EventAttribute:事件处理监听,当有对应事件派发时,会走到对应这个类的处理上
// 分发数值监听
[Event(EventIdType.NumbericChange)]
public class NumericChangeEvent_NotifyWatcher: AEvent<long, NumericType, int>
{
public override void Run(long id, NumericType numericType, int value)
{
Game.Scene.GetComponent<NumericWatcherComponent>().Run(numericType, id, value);
}
}
}
当有调用时,会调用到这个类的处理上
Game.EventSystem.Run(EventIdType.NumbericChange, this.Entity.Id, (NumericType) final, result);
public void Run<A, B, C>(string type, A a, B b, C c)
{
List<IEvent> iEvents;
if (!this.allEvents.TryGetValue(type, out iEvents))
{
return;
}
foreach (IEvent iEvent in iEvents)
{
try
{
iEvent?.Handle(a, b, c);
}
catch (Exception e)
{
Log.Error(e);
}
}
}