0.9.2 • Published 2 months ago

@comate/plugin-shared-internals v0.9.2

Weekly downloads
-
License
MIT
Repository
-
Last release
2 months ago

类型结构

本包@comate/plugin-schema提供与插件系统有关的各种共享类型、常量及常用读写能力。

会话通信

由于整体是多进程的体系,不能像普通函数一样进行直接调用,因此制定了一套通信和会话的能力,以满足以下要求:

  1. 能够使用Promise的形态进行一次调用,插件执行完成前,Engine端可以通过简单的await阻塞自身的逻辑。
  2. 在一次调用过程中发生的其它调用,例如日志,都会与这一次调用相关联。

社区中普遍使用的RPC封装方案如async-call-rpc都只能解决第一个问题,因此我们自己实现了一套方案。

名词解释

  • ChannelImplement:指一个原本已经存在的能够进行通信的对象,这个对象必须有一个message事件和一个send方法。从定义可以看出来,一个进程就是典型的ChannelImplement对象,也可以通过WebSocket等方式来实现这个接口。
  • Channel:对ChannelImplement做一次封装,通过对messagesend的处理,能够管理会话。其本质是使用sessionId关联各种message到同一个会话中。
  • Session:代表一次会话,可以由Channel#startSession主动创建,也可以在一条有全新的sessionId的消息到达时被动创建。所有的发送和接收消息都是在Session对象中处理的,即发送消息的方法、接收消息的事件监听,都是通过继承Session类来做的
             ┌───────┐ ┌───────┐
             │Session│ │Session│
             └───────┘ └───────┘
┌────────────────┬─────────┐         ┌─────────┬────────────────┐
│                │         │         │         │                │
│                │         ├────────►│         │                │
│                │         │         │         │                │
│ Engine Process │ Channel │         │ Channel │ Plugin Process │
│                │         │         │         │                │
│                │         │◄────────┤         │                │
│                │         │         │         │                │
└────────────────┴─────────┘         └─────────┴────────────────┘
                                  ┌───────┐ ┌───────┐
                                  │Session│ │Session│
                                  └───────┘ └───────┘

自定义会话

正常的使用方法是写2个类,一个继承Session并定义一系列的事件监听和发送方法,一个继承Channel并重写createSession方法返回自己的Session子类。

对于Session的子类:

  1. 定义一个类型PayloadMap,它的键是你需要监听的action常量,值是对应的payload的类型。
  2. 定义class extends Session<PayloadMap>
  3. 重写initializeListeners方法,先调用super.initializeListeners(),再用setListener方法监听不同的消息action,TypeScript会自动推导出来payload类型。
  4. 如果这个Session类是被其它功能使用的,那么添加一系列方法,每个方法是对send的调用,用来发送指定类型的消息。
  5. 对处于调用链中间的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监听的回调函数)。在处理中可以用sendlog等方法发消息回到调用子(如主进程的Channel)。

所有通过Session对象的logsend发送的消息,都会带上对应的sessionId,以便将所有的信息关联起来。

对于特殊的内置消息类型(当前仅LOG),它们默认会向父会话透传,即子会话的日志最终只在顶层处理,中间层不管理日志。