@comate/plugin-shared-internals
v0.9.2
Published
本包`@comate/plugin-schema`提供与插件系统有关的各种共享类型、常量及常用读写能力。
Readme
类型结构
本包@comate/plugin-schema提供与插件系统有关的各种共享类型、常量及常用读写能力。
会话通信
由于整体是多进程的体系,不能像普通函数一样进行直接调用,因此制定了一套通信和会话的能力,以满足以下要求:
- 能够使用
Promise的形态进行一次调用,插件执行完成前,Engine端可以通过简单的await阻塞自身的逻辑。 - 在一次调用过程中发生的其它调用,例如日志,都会与这一次调用相关联。
社区中普遍使用的RPC封装方案如async-call-rpc都只能解决第一个问题,因此我们自己实现了一套方案。
名词解释
ChannelImplement:指一个原本已经存在的能够进行通信的对象,这个对象必须有一个message事件和一个send方法。从定义可以看出来,一个进程就是典型的ChannelImplement对象,也可以通过WebSocket等方式来实现这个接口。Channel:对ChannelImplement做一次封装,通过对message和send的处理,能够管理会话。其本质是使用sessionId关联各种message到同一个会话中。Session:代表一次会话,可以由Channel#startSession主动创建,也可以在一条有全新的sessionId的消息到达时被动创建。所有的发送和接收消息都是在Session对象中处理的,即发送消息的方法、接收消息的事件监听,都是通过继承Session类来做的。
┌───────┐ ┌───────┐
│Session│ │Session│
└───────┘ └───────┘
┌────────────────┬─────────┐ ┌─────────┬────────────────┐
│ │ │ │ │ │
│ │ ├────────►│ │ │
│ │ │ │ │ │
│ Engine Process │ Channel │ │ Channel │ Plugin Process │
│ │ │ │ │ │
│ │ │◄────────┤ │ │
│ │ │ │ │ │
└────────────────┴─────────┘ └─────────┴────────────────┘
┌───────┐ ┌───────┐
│Session│ │Session│
└───────┘ └───────┘自定义会话
正常的使用方法是写2个类,一个继承Session并定义一系列的事件监听和发送方法,一个继承Channel并重写createSession方法返回自己的Session子类。
对于Session的子类:
- 定义一个类型
PayloadMap,它的键是你需要监听的action常量,值是对应的payload的类型。 - 定义
class extends Session<PayloadMap>。 - 重写
initializeListeners方法,先调用super.initializeListeners(),再用setListener方法监听不同的消息action,TypeScript会自动推导出来payload类型。 - 如果这个
Session类是被其它功能使用的,那么添加一系列方法,每个方法是对send的调用,用来发送指定类型的消息。 - 对处于调用链中间的
Session实现,你可以使用forwardMessageToParent方法透传消息到父会话中。
interface GreetingPayload {
name: string;
text: string;
}
interface GoodbyePayload {
name: string;
}
const ACTION_GREETING = 'GREETING';
const ACTION_GOODBYE = 'GOODBYE';
interface PayloadMap {
[ACTION_GREETING]: GreetingPayload;
[ACTION_GOODBYE]: GoodbyePayload;
}
class MySession extends Session<PayloadMap> {
sendGift(price: number) {
this.send({action: 'SEND_GIFT', payload: {price}});
}
protected initializeListeners() {
super.initializeListeners();
this.setListener(
ACTION_GREETING,
payload => console.log(`Greeting from ${payload.name}: ${payload.text}`)
);
this.setListener(
ACTION_GOODBYE,
payload => console.log(`${payload.name} leaves`)
);
}
}随后,实现自己的Channel类型,继承时通过泛型指定自己的Session子类,只需要createSession方法即可:
class MyChannel extends Channel<MySession> {
protected createSession(init: SessionInit) {
return new MySession(init, this.implement);
}
}由于Session只能由Channel创建,因此如果你的Session实现需要很多其它的依赖,就要先在Channel构造函数中获取,再通过createSession传给Session子类。
调用会话
作为主动调用方,使用Channel#startSession可以启动一个会话并发送一个消息过去,这个方法接收一个sessionId字符串(使用UUID即可)或者一个父的Session对象,会返回Promise直到会话结束(收到SESSION_FINISH消息)。
如果调用startSession时传的是一个父Session对象,那么2个会话就会建立父子关系,部分特殊的消息会由子向父的透传。
在调用startSession后,指定的消息被发送到接收端(例如子进程的Channel),在收到第一条有全新的sessionId时,Channel会创建一个Session对象并处理这条消息(对应setListener监听的回调函数)。在处理中可以用send、log等方法发消息回到调用子(如主进程的Channel)。
所有通过Session对象的log和send发送的消息,都会带上对应的sessionId,以便将所有的信息关联起来。
对于特殊的内置消息类型(当前仅LOG),它们默认会向父会话透传,即子会话的日志最终只在顶层处理,中间层不管理日志。
