Files
EVEngine/docs/usr/modules/rx.md
T

133 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 响应式编程模块
**脚本入口:** `eve.Rx()`
参考 [UniRx](https://github.com/neuecc/UniRx) 提供基于推送的响应式流:`Subject` 家族、
LINQ 风格操作符、`ReactiveProperty`,以及把现有事件队列桥接为 Observable 流的能力。
## 基本用法
```squirrel
local rx = eve.Rx();
local clicks = rx.newSubject();
// 订阅(返回 Subscription,可取消)
local sub = clicks.subscribe(function(v) {
print("click at " + v);
});
// 推送
clicks.onNext("(100, 200)");
clicks.onNext("(300, 50)");
// 不再需要时取消订阅
sub.dispose();
```
## 对象关系与调用时机
`Rx` 是工厂模块:`newSubject()` / `newBehaviorSubject(v)` / `newReplaySubject(n)` /
`newProperty(v)` 创建流对象。流对象本身不绑定到帧循环——游戏代码负责在合适的时机
调用 `onNext` / `setValue`,订阅回调在调用线程同步执行。`fromEvent` + `pump` 提供与
`event` 模块的桥接:`pump(ev)` 排空事件队列并把匹配名称的消息推给对应流。
## 目标导向指南
### 解耦生产者与消费者
生产者持有 `Subject` 并调用 `onNext`;消费者持有同一个对象并 `subscribe`。双方无需
互相引用,取消订阅即解除关联。适合按键、点击、资源加载完成等事件广播。
### 用操作符链做数据变换
```squirrel
local numbers = rx.newSubject();
numbers
.filter(function(v) { return v % 2 == 0; })
.map(function(v) { return v * 10; })
.subscribe(function(v) { print("result " + v); });
numbers.onNext(1); // 不输出
numbers.onNext(2); // result 20
```
### 观察可变状态
`ReactiveProperty` 既是读取入口也是可订阅流:状态变化自动通知订阅者,适合 UI 绑定、
模型字段监听等场景。
```squirrel
local hp = rx.newProperty(100);
hp.subscribe(function(v) { print("hp -> " + v); });
hp.set(80); // 打印 hp -> 80
print(hp.get()); // 80
```
### 桥接事件队列
```squirrel
local ev = eve.PlatformEvent();
local rx = eve.Rx();
local quest = rx.fromEvent("quest-complete");
quest.subscribe(function(data) { print("quest done: " + data); });
// 帧循环或任何时机排空事件队列,匹配的消息进入对应流
rx.pump(ev);
```
## 常见问题
- 忘记 `dispose()`:订阅者会一直留在流里接收通知。持订阅对象并在不再需要时 dispose。
- 在 worker 线程推送:回调在 `onNext` 的调用线程同步执行;脚本闭包只能在主线程被调用,
worker 请通过 `thread.postMain` 回主线程后再推送。
- 把 `ReactiveProperty` 当普通变量:写 `set()` 会触发通知,若只是临时状态请用局部变量。
- 混淆 `subscribe` 与 `subscribe3`:前者只有 onNext 回调;后者是
`subscribe3(onNext, onError, onCompleted)`,全部必填(不需要就传 `null`)。
## API 快查
下列方法名来自当前 Squirrel 绑定;同一模块创建的辅助对象的方法也列在这里。
**模块 `Rx`:**
- `newSubject()` → `Subject`
- `newBehaviorSubject(v)` → `BehaviorSubject`
- `newReplaySubject(capacity)` → `ReplaySubject`
- `newProperty(v)` → `ReactiveProperty`
- `fromEvent(name)` → `Observable`
- `pump(event)`:排空事件队列,把匹配 `fromEvent` 的消息推给对应流
**`Observable` / `Subject` / `BehaviorSubject` / `ReplaySubject`:**
- `subscribe(onNext)` → `Subscription`
- `subscribe3(onNext, onError, onCompleted)` → `Subscription`
- `map(fn)` → `Observable`
- `filter(pred)` → `Observable`
- `take(n)` → `Observable`
- `skip(n)` → `Observable`
- `first()` → `Observable`
- `distinctUntilChanged()` → `Observable`
- `Subject` 额外:`onNext(v)` / `onError(msg)` / `onCompleted()` / `hasObservers()`
- `BehaviorSubject` 额外:`getValue()` / `setValue(v)`
- `ReactiveProperty`:`get()` / `set(v)` / `subscribe` / `subscribe3`
**`Subscription`:**
- `dispose()` / `isDisposed()`
## 使用要点
- 流对象和订阅对象应保存在全局或实体状态中,不要在每帧重复创建。
- 默认回调在当前线程同步执行;跨线程需求先回到主线程再推送。
- 参数约束、默认值和返回类型以对应模块头文件及 `addFunc` 绑定为准;本文 API 快查与当前源码同步生成。
## 引擎内的 Editor 观察会话
Editor automation 直接复用本模块的 C++ `Subject` 和 `distinctUntilChanged` 管线,实现 MCP
`eve_editor_observe_start/poll/close`。Editor 持有会话、流和订阅,是生命周期唯一所有者;MCP 的
poll 只在主线程采样 live runtime 并推送事件,不复制场景或材质权威状态。连续相同 JSON 事件不会
重复交给 Agent,provider 丢失或 stale identity 则作为可观察的状态变化返回。使用方必须显式 close;
target 或引擎对象仍按原所有权销毁,关闭观察会话不会销毁被观察对象。
**源码:** [`src/modules/rx/`](../../../src/modules/rx/)
**相关测试:** [`test/rx.cpp`](../../../test/rx.cpp)