Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

maa-cli 前后端分离 #322

Open
wangl-cc opened this issue Sep 9, 2024 · 10 comments · May be fixed by #375 or #350
Open

maa-cli 前后端分离 #322

wangl-cc opened this issue Sep 9, 2024 · 10 comments · May be fixed by #375 or #350
Labels
enhancement New feature or request
Milestone

Comments

@wangl-cc
Copy link
Member

wangl-cc commented Sep 9, 2024

现状

当前,maa-cli 执行任务主要包括以下步骤:

  1. 解析 MaaCore 相关配置 (profile/*.toml);
  2. 解析任务:对于自定义任务读取任务定义文件,对于预定义任务处理命令行参数;
  3. 根据任务修改部分配置;
  4. 加载 MaaCore 并根据配置进行初始化;
  5. 向 MaaCore 添加解析任务,并启动;
  6. 等待任务完成并推出程序。

问题

上述实现比较简单直接,但是存在以下几个问题:

  • 每次执行任务都需要加载并配置 MaaCore,这需要耗费一定时间(约几秒)。这在执行比较长的任务可以忽略,但是对于执行简单的任务,尤其是通过命令行连续执行多个任务时,这个开销是不能忽略。
  • 目前 maa-cli 启动后没有任何外部干预途径。MaaCore 部分选项允许运行时更改,而 maa-cli 当前实现方式无法做到。
  • 未来 maa-cli 可以作为一个后端,给供其他前端调用。这样其他前端就可以避免相对复杂的 MaaCore FFI,同时也可以基于此开发 WebUI。

解决办法

为 maa-cli 引入一个 Server 模式,通过 maa serve 启动。启动后 maa-cli 将作为一个 RPC Server, 并监听某一个 Unix 或者 TCP Socket。maa runmaa startup 等命令将解析任务后向服务器发送请求。maa-cli 在运行任务时如果 server 未启动,将自行启动一个 server,并将其作为守护程序在后台活跃一段时间,然后通过相应的

具体实施

  • RPC 框架:JSON-RPC (crate jsonrpsee)。简单,易于调试,浏览器原生支持,易于和已有代码集成。RPC 协议相关的结构体,通过一个单独的 Crate 实现。
  • 传输:WebSocket or WS+TLS。WS 相比 HTTP 更高效,全双工,同时和 HTTP 一样受到浏览器原生支持。

RPC API 列表

添加任务

方法名称: AppendTask

请求:

任务类型以及序列化为 JSON 的字符串。

{
  "taskType": "StartUp",
  "taskParams": \"{}\",
}

响应:

{
  "taskId": "1"
}

其中 0 表示添加失败。

修改任务参数

方法名称: SetParams

请求:

{
  "taskId": "1",
  "newParams": {
	"client_type": "Txwy"
  }
}

响应:

{
  "success": true
}

启动任务

方法名称: Start

请求:

{}

响应:

{
  "success": true
}

停止任务

方法名称: Stop

请求:

{}

响应:

{
  "success": true
}

检查任务是否正在进行

方法名称: Running

请求:

{}

响应:

{
  "running": false
}

关闭 Server

方法名称: Terminate

请求:

{}

响应:

{
  "success": true
}
@wangl-cc wangl-cc changed the title maa-cli server 实现细节 maa-cli server 实现 Sep 9, 2024
@wangl-cc wangl-cc changed the title maa-cli server 实现 maa-cli server, client 分离实现 Sep 9, 2024
@wangl-cc wangl-cc changed the title maa-cli server, client 分离实现 maa-cli server, client 分离实现文档 Sep 9, 2024
@wangl-cc
Copy link
Member Author

wangl-cc commented Sep 9, 2024

@BoredTape 你可以在这个 issue 写一下你的想法,我其实还没有完全确定要怎么做,我也不是专业的开发,所以欢迎提出任何意见。

@BoredTape
Copy link

BoredTape commented Sep 10, 2024

@BoredTape 你可以在这个 issue 写一下你的想法,我其实还没有完全确定要怎么做,我也不是专业的开发,所以欢迎提出任何意见。

首先,我这边的想法不是普通用户的想法,可以不用太在意,对于普通用户来说,我的想法是比较离谱的。

1.分开client和server的话,我比较关心server端,server如果可以做成一个crate的话,我这边比较方便嵌入到maabo(实话实说,我觉得我这想法是很离谱的)。
2.server端希望可以提供一个优雅的退出方式。(目前的Terminate应该足够了)
3.希望client和server之间的通信方式是流的,server端在执行一个任务时,会不断输出信息,如:目前的战斗,干员识别等。这个时候希望作为client端可以持续接收到server端的输出信息。grpc的流模式或者是websocket是可以做到的。
4.如果3可以实现,那么持续收到的server输出信息中,可以明确一个任务状态,如:DONEERRORRUNNINGPENDING

目前想到的就这些

@wangl-cc
Copy link
Member Author

wangl-cc commented Sep 10, 2024

我觉得的话,client 不一定要负责启动客户端。走 RPC 的话,我觉得如果有可能,能让 MaaBo 支持非自己启动的 server 吗?这样就可以复用 cli 和 core,以及理论上可能的连接远程 server。

@BoredTape
Copy link

我觉得的话,client 不一定要负责启动客户端。走 RPC 的话,我觉得如果有可能,能让 MaaBo 支持非自己启动的 server 吗?这样就可以复用 cli 和 core,以及理论上可能的连接远程 server。

理论上是可以连接远程server的,不过task信息就要做相关的接口获取,现在MaaBo为了跟官方的体验一直,是写死了一份task配置包含:startup、flight等等的任务,这个看你server怎么定,我再适配。至于MaaBo启动server这个,是因为我日后想适配安卓端

@wangl-cc wangl-cc added the enhancement New feature or request label Oct 16, 2024
@wangl-cc wangl-cc changed the title maa-cli server, client 分离实现文档 maa-cli 前后端分离 Oct 16, 2024
@wangl-cc wangl-cc added this to the v0.6 milestone Oct 22, 2024
@wangl-cc wangl-cc linked a pull request Dec 23, 2024 that will close this issue
@Jackhr-arch
Copy link

Hi,我用tonic实现了一部分RPC,在我的分支

目前订阅callback的部分还在写,但client.rs里的都可以实机使用,(而且确实不太依赖cli本体),事实上我找资料的时候还发现官方的一个示例(我觉得天都要塌了,我写了几天啊)

不知道你对目前的api看法如何,希望能得到你的建议

还有,真的能单独设置日志目录吗,我在binding里找不到啊

@wangl-cc
Copy link
Member Author

wangl-cc commented Mar 4, 2025

感谢你的工作。

目前订阅callback的部分还在写,但client.rs里的都可以实机使用,(而且确实不太依赖cli本体),事实上我找资料的时候还发现官方的一个示例(我觉得天都要塌了,我写了几天啊)

主仓库确实有一个 REST API,那个只是一个 raw binding,我希望 maa-cli 提供的 RPC 能提供更多的功能,比如处理日志,更新 Core,下载作业,这样可以降低客户端的代码量,同时在 web 客户端这种受限的环境下提供和本机客户端接近的功能。另一方面,REST API 适合在 Web 端,但在本机走 HTTP 开销比较大,而且客户端也需要更多的依赖,而 RPC 可以在本机下使用开销更低的通信方式,比如直接使用 stdio,不过 Rust 目前好像没有很好的库来实现走多种协议的 RPC。 Tonic 应该是 gRPC,但是 gRPC 好像对 web 的支持有点麻烦,这也是我考虑使用更简单的 JSON RPC 的原因。

不知道你对目前的api看法如何,希望能得到你的建议

我其实一直也没有确定除了 Raw binding 之外 API 具体要包含哪些,所以我一直拖着没有实现,这些都可以讨论。而且我其实也没有相关经验,所以其实可能不一定比你更懂。

真的能单独设置日志目录吗,我在binding里找不到啊

/// Set the user directory of the assistant.
///
/// The user directory is used to store the log file and some cache files.
///
/// Must by called before `set_static_option` and `load_resource`.
/// If user directory is not set, the first load resource directory will be used.
///
/// # Errors
///
/// This function will raise an error if the path is not a valid UTF-8 string,
/// or raise an error if set the user directory failed.
pub fn set_user_dir(path: impl ToCString) -> Result<()> {
unsafe { binding::AsstSetUserDir(path.to_cstring()?.as_ptr()) }.to_result()
}

日志是放在 user dir 下的,所以 maa-cli 目前是通过设置日志来控制日志的位置的。目前 maa-cli 其实不适合多开,因为 user dir 直接是 data dir 会全都输出在同一个文件里。

@Jackhr-arch
Copy link

tonic使用protobuf来传输数据,理论上只要支持protobuf就可以直接对接,我觉得兼容性应当不是问题

这个设置日志目录似乎是对一个maa core生效的?而且看起来核心加载好之后就不能改变,我打算把它合并到load_core里

cli支持的功能我也有打算引入,不过目前的代码很混乱,估计要个几天

而且显然,rpc下旧有的summary更新方式没法用,这还需要改一改。。。

@wangl-cc
Copy link
Member Author

wangl-cc commented Mar 4, 2025

tonic使用protobuf来传输数据,理论上只要支持protobuf就可以直接对接,我觉得兼容性应当不是问题

protobuf 是用来序列化的,不是兼容性障碍。主要是 没办法在浏览器中实现 HTTP/2 gRPC 规范,需要走一层 gRPC-web 的代理。

这个设置日志目录似乎是对一个maa core生效的?而且看起来核心加载好之后就不能改变,我打算把它合并到load_core里

对,这个是静态的对所有实例生效,合并到 load_core 挺好的。此外 core 的位置,资源的位置以及日志文件夹的位置,我更倾向于通过 server 启动时参数实现,而不是使用 maa_dir 的默认位置。这样可以避免运行时指定库的安全问题,同时也可以允许如果客户端自己启动服务器时可以更好的控制位置,比如 MaaBo 就是用的他自己的文件夹。

cli支持的功能我也有打算引入,不过目前的代码很混乱,估计要个几天。

cli 的功能可能需要挺多修改的才能引入的,如果你有兴趣,可以提个 Draft PR,这样有什么问题,我可以给你提供一些帮助。

而且显然,rpc下旧有的summary更新方式没法用,这还需要改一改。。。

日志和 Summary 在 RPC 下比较麻烦。我觉得可以在 server 端维护一个日志池和状态池,可以根据 task id 来读取历史日志和状态,客户端也可以 subscribe 然后获得实时的日志

@Jackhr-arch
Copy link

Jackhr-arch commented Mar 4, 2025

代理可以在程序一并实现,tonic走本地socket,另外起一个axum服务http并提供json接口,然后把请求通过socket转发到tonic

不过tower支持中间件,也许可以写一个中间件判断请求的类型然后直接json转protobuf,就不需要axum了

似乎tonic本身就有json codec的实现,也许还能再简单点

日志池是个不错的考虑,目前我使用无界通道缓存消息,客户端拿走了就不存在复件了(是的,实时订阅消息已经实现了(虽然是把callback里的string原封不动的发出来

track目前在用adb device的uuid,正在考虑替换成随机数据生成器。

状态机还没开始,这部分和summary相关,我得想想怎么写

cli的核心就是预定义的配置文件了吧,那些profile和tasks,和资源更新之类,目前我没什么头绪,先放一放吧,等server差不多完善了再整合到cli里作子指令,或者把这些从cli里分离出来给server作依赖

@wangl-cc
Copy link
Member Author

wangl-cc commented Mar 4, 2025

cli 的核心就是预定义的配置文件了吧,那些profile和tasks,和资源更新之类,目前我没什么头绪,先放一放吧,等server差不多完善了再整合到cli里作子指令,或者把这些从cli里分离出来给server作依赖。

cli 的功能多大程度上移到 server 里面我还没有确定。我目前觉得比较需要的:Core 以及 server 自身的更新和重载,对部分任务参数的处理,比如对 maa://12345 这种作业 URL 的处理。但是我不确定需要把 CLI 的配置文件放在服务端,因为客户端可能使用自己的方式来储存和管理配置,所以我倾向于把这些保留作为 cli 自身的功能。

@Jackhr-arch Jackhr-arch linked a pull request Mar 5, 2025 that will close this issue
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
3 participants