对应版本:
2020-9-14 a9b44b68ecc3c325be81f71217712e07266df58b commit
EventSystem
private readonly Dictionary<long, Entity> allComponents;
private readonly Dictionary<string, Assembly> assemblies;
private readonly UnOrderMultiMapSet<Type, Type> types;
private readonly UnOrderMultiMap<Type, IAwakeSystem> awakeSystems;
...
UnOrderMultiMapSet<Type, Type>
内部实现是 Dictionary<Type, HashSet<Type>>
UnOrderMultiMap<Type, Type>
内部实现是 Dictionary<Type, List<Type>>
关于Add(Assembly assembly)方法
Game.EventSystem.Add(typeof (Game).Assembly)流程
从Add方法开始一步一步看完事件系统
// Game所在的程序集为Model.dll也就是Model层(只有数据没有逻辑)
Game.EventSystem.Add(typeof (Game).Assembly);// 这句代码多余了?
// 获取到热更层程序集Hotfix.dll (只有逻辑没有数据)
// 先注释, 之后再看
// Game.EventSystem.Add(DllHelper.GetHotfixAssembly());
最早调用Add方法是在EventSystem的构造函数中,
public static EventSystem Instance
{
get
{
return instance ?? (instance = new EventSystem());
}
}
private EventSystem()
{
this.Add(typeof(EventSystem).Assembly);
}
Add(typeof(EventSystem).Assembly);和 Game.EventSystem.Add(typeof (Game).Assembly); 重复添加了, 感觉是多余了..
运行后可以看到assemblies
字典中键值对如下图:

进入Add(typeof (Game).Assembly)
方法,
第一部分types
public void Add(Assembly assembly)
{
// ManifestModule 获取包含当前程序集清单的模块。
// ScopeName 获取表示模块名称的字符串。e.g Model.dll Hotfix.dll
// ① 映射字符串和程序集 (热重载时覆盖已有的Hotfix.dll)
this.assemblies[assembly.ManifestModule.ScopeName] = assembly;
// 清空types字典, 防止Reload重复添加
this.types.Clear();
foreach (Assembly value in this.assemblies.Values)
{
foreach (Type type in value.GetTypes())
{
if (type.IsAbstract)
continue;
// ② 从所有类型中找出应用了BaseAttribute特性的类型
// true: 包括继承BaseAttribute的子特性
object[] objects = type.GetCustomAttributes(typeof(BaseAttribute), true);
if (objects.Length == 0)
continue;
// ③ 应用此子特性的类加入到集合中
foreach (BaseAttribute baseAttribute in objects)
this.types.Add(baseAttribute.AttributeType, type);
}
}
...

AttributeTargets.Class
: 该特性应用于类
AllowMultiple
: 该值指示是否可以为一个程序元素指定多个实例所指示的特性。在单个字段上使用单一属性的次数。在缺省情况下,所有属性都是单用途的。
例如:
[AttributeUsage(AttributeTargets.Class, AllowMultiple=true)]
public class SomethingAttribute : Attribute
{
public SomethingAttribute(String str){}
}
[Something("aaa")]
[Something("bbb")]
class MyClass {}
第二部分各种Systems
// 清空以下字典, 防止Reload重复添加
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里获取应用[ObjectSystem]特性类的无序集合HashSet
foreach (Type type in this.GetTypes(typeof(ObjectSystemAttribute)))
{
// ④实例化对应的类, 加入到各个系统中
object obj = Activator.CreateInstance(type);
// ⑤switch中的语法是模式匹配, 看类型实现的是什么接口,匹配对应的case加入字典中
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;
}
}

关于this.loadSystems.Add(loadSystem.Type(), loadSystem);
这句是怎么获取到OpcodeTypeComponentLoadSystem
对象所对应的实体呢? 如下:
public class OpcodeTypeComponentLoadSystem : LoadSystem<OpcodeTypeComponent>
{
....
}
OpcodeTypeComponentLoadSystem
类继承自LoadSystem<T>
T是OpcodeTypeComponent
, 抽象基类的LoadSystem<T>
代码如下所示:
public interface ILoadSystem
{
Type Type();
void Run(object o);
}
[ObjectSystem]
public abstract class LoadSystem<T> : ILoadSystem
{
public void Run(object o)
this.Load((T)o);
// Type方法返回的是T的类型
public Type Type()
return typeof(T);
public abstract void Load(T self);
}
所以this.loadSystems.Add(loadSystem.Type(), loadSystem);
加入到字典中的键值对是{ typeof(OpcodeTypeComponent) , List<ILoadSystem> }
第三部分Event
// 清空以下字典, 防止Reload重复添加
this.allEvents.Clear();
// ⑥从types里获取应用[Event]特性类的无序集合HashSet
foreach (Type type in types[typeof(EventAttribute)])
{
// ⑥实例化,转型为对应的接口对象
IEvent obj = Activator.CreateInstance(type) as IEvent;
if (obj == null)
{
throw new Exception($"type not is AEvent: {obj.GetType().Name}");
}
Type eventType = obj.GetEventType();
if (!this.allEvents.ContainsKey(eventType))
{
this.allEvents.Add(eventType, new List<object>());
}
this.allEvents[eventType].Add(obj);
}

public interface IEvent
{
Type GetEventType();
}
[Event]
public abstract class AEvent<A>: IEvent where A: struct
{
public Type GetEventType()
return typeof (A);
public abstract ETTask Run(A a);
}
[Event]
特性应用于抽象基类, 因此只要继承AEvent就能加入到事件系统的allEvents
字典中
public class AppStart_Init: AEvent<EventType.AppStart>
{
public override async ETTask Run(EventType.AppStart args)
{
..
}
}
注意EventType是命名空间, AppStart是一个结构体可用于传参
namespace EventType
{
public struct AppStart{}
}
// 这里需要注意, 键是传的泛型类型,值数组是object
Type eventType = obj.GetEventType(); // typeof (EventType.AppStart);
if (!this.allEvents.ContainsKey(eventType))
{
this.allEvents.Add(eventType, new List<object>());
}
所以最后allEvents
字典中的键值对是{ typeof(EventType.AppStart) , List<object>{ AppStart_Init } }
第四部分Load()
此部分用于热重载. 需要熟悉其他方法, 后续再说.
Add第二个程序集流程
为什么服务器可以热更逻辑? 看完你就知道啦
// 服务器热重载命令
case "reload":
try
{
Game.EventSystem.Add(DllHelper.GetHotfixAssembly());
}
...
break;
// 服务器热更消息处理类:
[MessageHandler]
public class M2A_ReloadHandler : AMRpcHandler<M2A_Reload, A2M_Reload>
{
protected override async ETTask Run(Session session, M2A_Reload request, A2M_Reload response, Action reply)
{
Game.EventSystem.Add(DllHelper.GetHotfixAssembly());
reply();
await ETTask.CompletedTask;
}
}
都用到了Game.EventSystem.Add
, 再来看看Add的流程
先从服务器初始化运行第二次Game.EventSystem.Add
开始
public void Add(Assembly assembly)
{
// ManifestModule 获取包含当前程序集清单的模块。
// ScopeName 获取表示模块名称的字符串。e.g Model.dll Hotfix.dll
// ① 添加Hotfix.dll字符串对应的程序集
this.assemblies[assembly.ManifestModule.ScopeName] = assembly;
// ② 重点:
// 清空types字典, 防止Reload重复添加
this.types.Clear();
...
// 清空以下字典, 防止Reload重复添加
this.awakeSystems.Clear();
this.lateUpdateSystems.Clear();
this.updateSystems.Clear();
this.startSystems.Clear();
this.loadSystems.Clear();
this.changeSystems.Clear();
this.destroySystems.Clear();
this.deserializeSystems.Clear();
...
// 清空以下字典, 防止Reload重复添加
this.allEvents.Clear();

这里清空了types
, 但是assemblies
里保存了2个程序集, 重新添加
// 遍历assemblies里所有的程序集, 将这些程序集里的类型重新添加到types字典中
foreach (Assembly value in this.assemblies.Values)
{
foreach (Type type in value.GetTypes())
{
...
}
}
为什么要types
要Clear?
因为程序集可以添加很多个很多次. 不清除之前会出现冲突和遗留项.
假设添加了Hotfix.dll, 之后修改了热更层代码删除了一些例如handler的一些逻辑, 使用Reload
调用Game.EventSystem.Add
重新添加Hotfix.dll覆盖掉原先assemblies["Hotfix.dll"]
的值. 为了正确热更, 必须清空types
字典.
xxxSystem,allEvents清空也是一样的道理.
关于服务器热更还要注意:
- 服务端对于正在update的模块..热更会导致什么结果?
- 热更后走的逻辑是新的还是旧的..?
- 包括在start()里while+wait固定时长的逻辑..应该是什么结果..?
【群主】熊猫(80081771)
update会走新的逻辑。协程没有结束会走旧的,结束重新开协程会走新的
所以while await这种是不能热更的,要在load中取消这个协程然后重新创建新的协程才能更新逻辑
相关讨论贴