前言

在 Roblox 中探索自主 Agent 里提到:

Roblox 不容易与其他系统进行双向通信,Roblox 社区里没有令我们满意的解决方案。我们最终动手打造了自己的方案。这个话题,会在另一篇文章里单独讨论,我们相信它对 Roblox 社区有独特的价值。

这篇文章将讨论这个话题。

消息通信

构建优秀且可扩展的系统的关键更多的是设计模块之间如何通信,而不仅仅是设计其内部属性和行为。 – Alan Kay

Alan Kay 认为对象之间的通信,是"面向对象"概念的核心构成部分(现代的 OOP 忽视了这点),他的许多观点,一直是我们灵感的来源。

Roblox 的通信机制

Roblox 有多种方式与外部系统通信

HttpService(注意不是 HTTP Server !) 允许 Roblox 脚本发送 HTTP 请求(作为 HTTP client)。

Roblox 是一个"云"优先的软件(不是本地优先)。Roblox 的 Cloud 提供了许多 HTTP API, Messaging Service API 看起来是不错的消息传递接口。 它对外提供了 HTTP 端点(HTTP Endpoint) , 可以接收外部系统发送到 Roblox 游戏中的事件。此时,外部系统充当 HTTP client,而 Roblox 充当 HTTP Server。 需要注意的是,这个机制只支持 Roblox 已经上线的游戏(experience), 不支持在 Roblox Studio 中使用(因为 Roblox Studio 不在云端)。Roblox 的许多设计,都把"云"作为一等公民,这导致了许多功能都与云端强制绑定,引起了Roblox 用户社区的大量不满

利用以上两种机制,似乎可以在 Roblox 中构建与外部系统双向通信的模型:

  • HttpService: 消息流出 Roblox
  • Messaging Service API: 消息流入 Roblox

既然消息可以流入和流入,好像就可以在 Roblox 里构建基于 Actor 计算模型的 Agent。要实现 Actor 只需要实现这样的计算实体:

  • 响应收到的消息
  • 发送消息
  • 创建更多 Actor

以上三点都可以在 Roblox 里做到。

Roblox Studio 与 Roblox experience

“可以在 Roblox 里做到” 是个模糊的话语。

作为玩家,“Roblox” 通常意味着 “Roblox experience”,“experience”(体验) 的意思是已经发布上线, 玩家可以"体验"的游戏,之所以称为"体验",而不叫"游戏",是因为 Roblox 团队认为 Roblox 社区上线的项目有许多类别,不仅限于游戏。

对于创作者,Roblox 更多意味着 “Roblox Studio”,类似于 Unity 那样的游戏开发环境,但强调易用性。 “Roblox experience” 是创作者在 “Roblox Studio” 中创造出来的东西。

前边说通过消息的流入(Messaging Service API)和流出(HttpService), “可以在 Roblox 里” 实现消息的双向通信,是指可以在 Roblox experience 中做到。

Roblox Studio 无法收到 “Messaging Service API” 流入的消息。

解决方案

让任何人在任何地方都能创造任何东西 – Roblox 团队

Roblox 团队致力于将 Roblox Studio 打造为 “世界上最容易使用的 3D 开发工具”。

我们确实如 Roblox 团队期望的,将 Roblox Studio 当作 “3D 开发工具”, 用它进行 3D 世界的实验和探索。Roblox Studio 有着出色的实时编程(Live coding)体验,甚至具有 Smalltalk/Scratch 的 liveness(活性), 这是非常令人惊艳的。

由于我们把它当作开发和实验环境,很自然我们会想要它能够与其他系统互操作,但它的互操作性并不好。这是他们 “云端优先” 的理念造成的。

社区的解决方案

不只是我们,社区里也经常有将 Roblox Studio 连接到外部系统的需求。用户社区提议 Roblox 支持 websocket,Roblox 团队不做回应。人们只好通过一些 hack 手段做到,这些机制大多数建立在官方的HttpService机制上,所以不大有向后兼容的风险。

诸如 rojo 希望让 Roblox 开发人员能够使用专业级软件工程工具,他们的实现方式是,充分利用了 HttpService。通过维持一个长时间的 HTTP 连接(超时重连),将文件变更推送到 Roblox Studio 里。

rblx-unisocket3 也利用 HttpService 建立了一个双向通信机制,提供的 API 类似于 websocket。

我们的解决方案受到这两个项目的启发。

我们的解决方案

早期的架构图:

早期我们使用了前文提到的 HttpService 和 Messaging Service API 来与 Roblox 环境互操作,这样的坏处是,只能用于 “Roblox experience”, 而无法在 “Roblox Studio” 里使用。Messaging Service API 还有其他的坏处,诸如对消息的速率限制得厉害。

在 Roblox 中探索自主 Agent 提到,我们倾向于对系统持不可知论,并遵循端到端原则,一切都只是流动的异步消息。

关键想法是,网络应该尽可能保持愚蠢,它只是消息的公路,让消息走哪条路,或者让它在路上停上半小时再出发(只影响了抵达时间),都不会造成任何功能上的影响。

这样一来,我们可以轻松在系统里,构建各种消息的处理器: 转发器、观察器、模拟器…

受到 rojorblx-unisocket3 的启发,我们很快构建了一个消息转发器(forwarder), 在消息传递(异步)架构里这样做相当容易.

转发器的工作方式是这样的:

Roblox 中有一个 service 脚本,跑 loop 循环,发送 HTTP 请求连接到转发器上,转发器维持这个 HTTP 连接,一旦消息队列里有其他 Agent 发往 Roblox Agent 的消息,就返回连接的 HTTP 请求。转发器实际上是一个带有消息缓存队列的路由器。

你可能会问,这里允许 Roblox 主动获取"流入"的消息,但如何往外部"流出"消息? 你可能提议,在需要时往 HTTP 请求里携带"流出"的消息即可。这样做是可行的,但由于 DynaTalk 本身可以接收 HTTP 消息(转化到 DynaTalk 内部消息系统),所以我们不需要在转发器里实现。

这个机制同时适用于 Roblox Studio 与 Roblox experience! (用于Roblox experience时,只需要将转发器部署到公网上即可)

这个机制相当通用,任何兼容 DynaTalk 消息协议的系统都可以于Roblox互操作! 几乎可以在任何计算环境里实现,无论是主流编程语言、浏览器环境还是物联网系统。

这个机制相当快速(虽然基于 HTTP):

1 秒钟的时间里投递了 100 条消息!而且是在没有任何优化的情况下。

这个机制非常简单。Roblox 部分的代码一共 50 行不到: 处理了连接超时,连接失败等粗糙边缘。转发器的核心代码才 10 多行。

这个机制非常健壮,转发器与 Roblox Studio 究竟谁先启动,或者任意一方的终止/重启,都不会造成意外。我目前没有遇到过任何边缘问题。

由转发器引发的想法

转发器的架构图(我喜欢用视觉思考),让我想到,既然一切都只是消息,那么转发器是不是真的将消息转发到 Roblox 是不重要的,它完全可以伪装成 Roblox。 而 DynaTalk 消息空间中的其他 Agent 对此一无所知!(前头提到的不可知论发挥作用了)

这导致我们制作了 Roblox 模拟器(核心代码和转发器一样简单),我们只需要让转发器像 Roblox Agent 那样收发消息,其他 Agent 就会以为自己在与 Roblox 交互(不会有任何可分辨的区别),这大大增加了开发效率,开发 DynaBrain 的时候,不再需要每次打开 Roblox Studio 这个大家伙了!

模拟器让我们可以轻松进行调试,可以隔离和模拟系统的任意部分!

转发器和模拟器机制都相当通用,未来可以利用类似的机制,往这个"愚蠢"的消息网络里透明添加各种东西。

这些实际上是消息传递架构的内在力量。

参考