# EventReactor **Repository Path**: acoloco/event-reactor ## Basic Information - **Project Name**: EventReactor - **Description**: 一种依靠优先级责任链和回调实现的事件中心,使用接口对权限进行隔离。 - **Primary Language**: C# - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-11 - **Last Updated**: 2025-09-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: Unity, Csharp ## README ### EventReactor #### 介绍 一种可以用于Unity的事件中心,欢迎参考和交流。 **细节请参照[WIKI](https://gitee.com/acoloco/event-reactor/wikis/%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B)** #### 软件架构 文件: - Interfaces 定义各种接口 - EventBus 带有优先级机制的事件和事件总线容器 - Module 包含模块中心、模块组、模块和交互器 - Request 请求和结果等事件基类 交互层级: - ModuleHub 模块中心,负责管理各个模块的初始化和销毁 - Module 模块,负责监听事件并提供绝大部分的功能实现 - Interactor 交互器,负责发送事件,是各种流程的入口 - Event 事件,负责传递数据,本身不包含任何行为逻辑,做什么事情完全取决于监听者 #### 环境要求 可能要求C#8.0及以上。 使用到了以下命名空间: - using System; - using System.Collections; - using System.Collections.Generic; - using System.Threading; 虽然使用到Threading,但该事件中心并不关心多线程。 #### 最初的设计思路 以下是最初版本的设计思路,和现在的版本有一些出入。此部分内容仅用于记录各个部分的设计初衷,以及提供一种设计思路。 1. 优先级机制。 每个事件的监听者在注册监听时需要传递优先级(一个int值),优先级越高的监听越早被处理。 2. 依靠回调实现的责任链。 一般情况下,各个监听会被放到一个循环中依次执行。对于比较耗时的操作(例如加载资源和场景),也可以在操作完成后才把事件传递给下一个监听者。依此保证各个监听始终在上一个监听处理完成之后才开始执行。 相对的,也提供另一种执行方式,可以不在意各个监听的顺序,只需要确保所有监听都执行完成即可。 3. 主要定义了三种类型的事件:请求、结果和查询。 请求事件表示请求进行某种可能具有副作用的行为。无论是否被处理,每个请求总是得到一个结果。 结果事件表示某个请求的结果,一般会在请求事件发送结束后被一键发送。 查询事件表示进行某种无副作用的查询。例如说,即使查询的对象不存在,也不会新建或从哪里加载一个。 4. 利用接口实现权限隔离。 即使是同一个对象,通过转化为不同的互不继承的接口,也可以表现出不同的功能视图。 例如模块中心,本身是个众多功能的集合体,对不同的对象展现不同的接口,就可以实现不同交互层级的权限隔离。 当然,这种隔离是软隔离,仍然可以通过类型转换突破限制。一切限制都是在限制程序员(包括未来的自己),是限制还是便利只在一念之间。 5. 模块组&分阶段初始化。 有些模块们可能需要拥有相同的生命周期,此时建议放入模块组中。模块组中的模块们会一起初始化,一起销毁。 另一种情景是模块间在初始化时可能在顺序上互相依赖,无法独立完成。因此依赖模块们的初始化分为三个方法,每个初始化方法给予模块不同权限。 - 同步初始化方法中可以注册监听和在监听环境下激活交互器,因而还可以注册依赖于其他模块实现的监听。不允许有直接的异步操作。在同组的所有依赖模组执行完第一个方法后,自动调用第二个方法。 - 异步初始化方法中可以直接激活交互器,因而可以发送依赖于其他模块实现的请求。允许通过回调进行异步操作。在同组的所有依赖模组执行完第二个方法后,自动调用第三个方法。 - 解决依赖后的初始化方法中,所有依赖模块都已经初步初始化完成,可以直接查询所依赖的数据了。 #### 总结 **事件传递的本质是“回调传递”,即把后续处理的方法暂存到委托中,交给监听者决定** 可以简单理解为,第一个监听者得到了一个委托,操作(无论是同步还是异步)完成后通过回调调用这个委托。 委托内容是把新的委托交给第二个监听者,然后执行第二个监听者的监听内容,如此循环。 具体实现中使用“标记”优化了这一过程,如果监听者返回同步标记,那么就无需通过回调传递,而是直接循环到下一个;如果返回异步标记(本质是回调标记),就中断循环,通过回调发起一个新的循环。 如果是不要求顺序的执行方式,其实就是在委托中闭包了一个计数器,直接循环执行完所有监听。如果是同步标记,就直接计数加一,异步标记则在回调中增加计数。每次增加计数时都判断,当总数和监听数一致时才算完成。 #### 使用说明 请参照[WIKI文档](https://gitee.com/acoloco/event-reactor/wikis/%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B)。 #### 示例 **由于版本更新,以下的部分使用方式可能已经过时。请参照[WIKI](https://gitee.com/acoloco/event-reactor/wikis/%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B)** 以下用一个简单的例子说明模块的初始化、监听和发送。 设定这样一个场景:有红色按钮和蓝色按钮。你可以把它们想象成是两个实际开发中的不同模块。 需要在按下红色按钮后,按下蓝色按钮,触发一个输出。 为了方便展示,这里假设这两个模块的初始化都依赖于另一个模块,并且红色按钮从请求中获取,蓝色按钮从查询中获取。 ``` //创建一个模块中心继承自默认实现。一般来说一个项目只需要有一个模块中心 public class GameEventHub : ModuleHub { //模块中心初始化 public override void OnInit(IModuleHubInitView hub,Action onEnd) { //把模块添加到组中,对添加顺序没有要求 var package = new ModulePackage("Base"); package.Add(); package.Add(); package.Add(); //把模块组添加到模块中心里 hub.Add(package); hub.Start(onEnd); } } //一个初始化时被依赖的模块 public class GetButtonModule : Module { Button _blue; //同步初始化,此阶段可以注册监听,或着做一些简单的数据初始化 protected override void OnListen(IModuleHubModuleListenView hub) { //这里的_rmvs是一个收集工具,用于在模块销毁时自动注销所有监听 //使用hub.Listen监听红色按钮请求事件。请求事件中允许进行异步操作,所以监听请求事件必须要返回一个标记 _rmvs += hub.Listen(100, (sender, request) => { //如果是只读,说明被其他模块处理过并且不允许修改了,则直接返回结束标记 if (request.IsReadOnly) return request.End; if(request.loadAsync) { Resources.LoadAsync("xxx/xxx").completed += r => { var load = (r as ResourceRequest).asset as GameObject; //设置请求的结果 request.Result.red = GameObject.Instantiate(load).GetComponent