> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/core-concepts/runtime-architecture.md).

# 运行时架构

了解分层的 Convai Unity SDK 运行时——每一层负责什么、单个角色的具身层，以及运行时生命周期状态如何管理。

Convai Unity SDK 采用分层架构。每一层都有明确的职责，并且只向内通信——外层依赖内层，绝不相反。理解这种结构可以让你知道 SDK 的哪些部分面向开发者、哪些可以替换，以及哪些是你无需触碰的内部实现细节。

***

### 系统层

下图展示了主要层级及其相互关系。 `ConvaiRuntime` 在第二层持有四个直接子系统—— `IRoomRuntime`, `IEventHub`, `IAgentRegistry`，以及模块列表。角色和玩家位于 `IRoomRuntime` 和 `IAgentRegistry`之下。运行时级别的功能模块共享一个公共模块上下文；携带具身模块的角色也会获得自己独立的按角色组成根。

```mermaid
graph TD
    A[ConvaiRuntime<br/>IConvaiRuntime] --> B[IRoomRuntime<br/>连接 · 音频 · 所有权 · 诊断]
    A --> C[IEventHub<br/>解耦的发布/订阅]
    A --> D[IAgentRegistry<br/>角色 · 玩家]
    A --> E[IReadOnlyList&lt;IConvaiModule&gt;<br/>功能模块]
    B --> F[ConvaiCharacter<br/>按角色会话 + 状态]
    B --> G[ConvaiPlayer<br/>本地参与者身份]
    F --> H[Module Context<br/>LipSync · Vision · Narrative]
    G --> H
    F --> I[EmbodimentContext<br/>按角色组成根]
    I --> J[Embodiment modules<br/>目光 · 身体动画 · 身体语言 · 对话流程 · 情绪]
```

**`ConvaiRuntime` （顶层）** ——拥有所有子系统并协调完整生命周期。通过 `ConvaiRuntimeBuilder`在应用程序生命周期内仅创建一次。提供 start、pause、resume 和 stop 操作，这些操作会传播到所有已注册模块。它持有四个直接子系统：

* **`IRoomRuntime`** ——管理实时会话：连接、断开、音频路由、角色所有权和诊断。角色和本地玩家位于这一层之下。
* **`IEventHub`** ——整个 SDK 中用于跨系统通信的解耦发布/订阅总线。
* **`IAgentRegistry`** ——所有活动 `ConvaiCharacter` 和 `ConvaiPlayer` 实例的注册表。
* **`IReadOnlyList<IConvaiModule>`** ——已注册功能模块的集合。

**角色 / 玩家层** — `ConvaiCharacter` 和 `ConvaiPlayer` 注册到 `IAgentRegistry` ，并通过 `IRoomRuntime`接收各自的会话上下文。每个角色都维护自己的会话状态。携带具身模块——目光、身体动画、身体语言、对话流程或情绪——的角色也会获得自己的 `EmbodimentContext`；见下方的具身层。

**模块上下文层** ——运行时级功能模块（`IConvaiModule`，例如 LipSync、Vision 和 Narrative）共享一个 `IModuleContext` ，它提供对运行时服务的访问。模块可以声明对彼此模块 ID 和服务的依赖，但它们通过 `IEventHub` 或注册在 `IModuleContext` 上的服务进行交互，而不是持有彼此具体类型的直接引用。

***

### 运行时接口清单

`IConvaiRuntime` 将以下子系统作为属性暴露。每个属性都是特定功能域的入口点。

| 属性                   | 类型                              | 其拥有内容         |
| -------------------- | ------------------------------- | ------------- |
| `State`              | `RuntimeState`                  | 运行时当前生命周期状态   |
| `Room`               | `IRoomRuntime`                  | 连接、音频、所有权和诊断  |
| `事件`                 | `IEventHub`                     | 解耦的发布/订阅通信    |
| `Agents`             | `IAgentRegistry`                | 所有活动角色和玩家的注册表 |
| `Modules`            | `IReadOnlyList<IConvaiModule>`  | 所有已注册的功能模块    |
| `Transport`          | `ITransportProvider`            | 平台特定的实时传输     |
| `Conversation`       | `IConversationProvider`         | 与 Convai 的通信  |
| `Config`             | `ConvaiBootstrapConfigSnapshot` | 不可变的启动配置      |
| `RuntimePreferences` | `IRuntimePreferences`           | 可变的运行时偏好设置    |
| `FeatureVariants`    | `IFeatureVariantProvider`       | 功能变体 / A-B 选择 |
| `Persistence`        | `IPersistenceProvider`          | 运行时拥有的数据存储    |
| `Telemetry`          | `ITelemetryProvider`            | 可观测性和分析       |

开发者最常与 `Room`, `Agents`，以及 `事件` 交互。 `Transport`, `Conversation`, `Persistence`, `Telemetry`，以及 `FeatureVariants` 可通过 `ConvaiRuntimeBuilder`.

***

### 你可以替换的内容

`ConvaiRuntimeBuilder` 是用于在运行时启动前组合运行时的流畅 API。每个方法都返回 `this`，因此调用可以链式连接。

```csharp
var runtime = new ConvaiRuntimeBuilder()
    .UsePersistence(myPersistenceProvider)
    .UseTelemetry(myTelemetryProvider)
    .WithEndUserIdentityProvider(myIdentityProvider)
    .AddModule<MyCustomModule>()
    .Build();
```

下表列出了哪些是可替换的，哪些仅限内部使用。

| 组件       | 可通过 Builder 替换                   | 默认                        |
| -------- | -------------------------------- | ------------------------- |
| 传输提供程序   | `UseTransport()`                 | 平台默认（WebSocket / LiveKit） |
| 会话提供程序   | `UseConversation()`              | Convai RTVI 会话提供程序        |
| 持久化提供程序  | `UsePersistence()`               | `PlayerPrefs`基于 - 的键值存储   |
| 遥测提供程序   | `UseTelemetry()`                 | 空操作遥测                     |
| 功能变体提供程序 | `WithFeatureVariants()`          | 静态功能开关                    |
| 运行时偏好设置  | `WithRuntimePreferences()`       | 默认值来自 `ConvaiSettings`    |
| 事件中心     | `UseEventHub()`                  | 默认的内存内事件中心                |
| 代理注册表    | `UseAgentRegistry()`             | 默认注册表                     |
| 终端用户身份   | `WithEndUserIdentityProvider()`  | 设备 ID 提供程序                |
| 终端用户元数据  | `WithEndUserMetadataProvider()`  | 无                         |
| Modules  | `AddModule()` / `AddModule<T>()` | 仅限 SDK 功能模块               |
| 房间运行时    | `UseRoomRuntime()`               | 内部的、由 LiveKit 支持的房间       |

{% hint style="info" %}
`ConvaiRuntime` 由 `ConvaiManager` MonoBehaviour 自动创建。大多数项目从不直接调用 `ConvaiRuntimeBuilder` 。只有在你需要替换默认提供程序或添加自定义模块时才使用它。
{% endhint %}

***

### `IRoomRuntime` 子结构

房间层本身由四个协调器组成，全部都可通过 `IConvaiRuntime.Room`.

| 属性    | 类型                           | 职责            |
| ----- | ---------------------------- | ------------- |
| `连接`  | `IRoomConnectionCoordinator` | 连接、断开、会话状态    |
| `音频`  | `IRoomAudioCoordinator`      | 麦克风采集、远程音频播放  |
| `所有权` | `IRoomOwnershipCoordinator`  | 此客户端拥有并聚焦哪些角色 |
| `诊断`  | `IRoomDiagnostics`           | 会话指标、健康监控     |

连接和音频是你最可能从脚本中调用的协调器。当场景中有多个角色时，所有权会自动管理。诊断用于性能监控和调试。

***

### 模块层

模块是在运行时生命周期内运行的功能扩展。它们接收一个共享的 `IModuleContext` ，并且可以注册其他模块或表现层会消费的服务。

在 `Build()` 被调用之前，通过 builder 添加模块：

```csharp
new ConvaiRuntimeBuilder()
    .AddModule<LipSyncModule>()
    .AddModule(new MyCustomModule(someConfig))
    .Build();
```

模块会随着运行时一起启动、暂停、恢复和停止。 `IConvaiModule` 接口定义了这些生命周期钩子。使用 `RequiredModules`声明依赖，运行时会按依赖顺序启动模块，并按相反顺序停止它们——但模块仍然通过 `IEventHub` 或 `IModuleContext.TryGetModuleService`访问另一个模块的数据，绝不会直接引用另一个模块的具体类型。参见 [扩展 SDK](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/advanced-topics/extending-the-sdk.md) 以获取完整的模块编写参考。

***

### 具身层

一个 `ConvaiCharacter` 携带具身模块——目光、身体动画、身体语言、对话流程或情绪——的角色会获得自己的 `EmbodimentContext`。Convai 会在该角色上的某个具身模块首次解析到它时自动添加此组件；你永远不会从 Add Component 菜单中手动添加它，而且它本身也没有菜单项。

`EmbodimentContext` 是按角色的组成根，而不是注册到 `ConvaiRuntimeBuilder`的功能模块。它暴露了每个具身模块接入的共享基础设施：

| 属性              | 类型                    | 其拥有内容                     |
| --------------- | --------------------- | ------------------------- |
| `CharacterRoot` | `Transform`           | 角色的根变换                    |
| `EventHub`      | `IEventHub`           | 与 `IConvaiRuntime.Events` |
| `Logger`        | `ILogger`             | 面向具身模块的结构化诊断              |
| `RigBinding`    | `IStandardRigBinding` | 已检测骨架的语义骨骼和 BlendShape 查找 |
| `Character`     | `ConvaiCharacter`     | 所属角色                      |

具身模块—— `ConvaiGazeController`, `ConvaiBodyAnimationController`, `ConvaiBodyLanguageController`, `ConvaiConversationFlowController`，以及 `ConvaiEmotionController` ——在 `OnEnable` 上解析上下文，并向其发布自己的契约，而不是互相持有直接引用。 `EmbodimentContext` 触发 `RigBindingChanged`, `EmbodimentConfigurationChanged`，以及 `DependenciesPopulated` ，这样模块就可以在骨架重建、预设切换或运行时依赖首次可用时做出反应。项目自身的组件可以通过 `RegisterTickable`/`UnregisterTickable`.

#### 确定性 Tick

具身模块不会按照 Unity 每个组件的 `Update()` 顺序运行。每个模块都会向该上下文的 Tick 调度器注册，调度器会在每一帧中按声明的 `认知 → 表达 → 完成` 顺序精确地对每个已注册模块 Tick 一次——同一阶段内的先后由注册顺序决定，而不是由层级位置或哪个模块先启用决定。调度器本身从 `Update`开始 Tick；动画驱动器和接收其输出的面部合成器随后从 `LateUpdate`开始运行，一旦 Animator 为该帧摆好了骨架姿势。

#### 单写入 Animator 和面部 BlendShape

两个基础设施组件强制执行单写入访问，因此两个具身模块绝不可能争夺同一输出：

* 该 **animator conductor** 是唯一会调用 `Animator.SetFloat` 以及相关方法来处理具身驱动参数的组件。模块通过它提交命名参数写入，而不是直接触碰 `Animator` ，并且若第二个模块注册了一个已被拥有的参数，则会被拒绝，而不会静默覆盖第一个。
* 该 **facial blendshape compositor** 是唯一会写入 `SkinnedMeshRenderer` blendshapes 以输出具身结果的组件。模块在这一帧内提交按区域的层权重，合成器随后只调用一次进行合成并写入最终结果，来自 `LateUpdate`.

这两个组件都由 `EmbodimentContext` 自动提供，连同它一起——把它们以及 `EmbodimentContext` 本身视为 Convai 为某个角色添加的基础设施，而不是你需要直接配置的内容。

***

### `RuntimeState` 生命周期

运行时会从创建到释放经历以下状态。

| State | 含义                           |
| ----- | ---------------------------- |
| `已创建` | 运行时已构建但尚未启动                  |
| `启动中` | `StartAsync()` 进行中           |
| `运行中` | 完全运行                         |
| `暂停中` | `PauseAsync()` 进行中           |
| `已暂停` | 已暂停；可恢复                      |
| `恢复中` | `ResumeAsync()` 进行中          |
| `停止中` | `StopAsync()` 进行中            |
| `已停止` | 已关闭；无法重启                     |
| `已释放` | `DisposeAsync()` 已调用；所有资源已释放 |

```mermaid
stateDiagram-v2
    [*] --> 已创建
    已创建 --> 启动中 : StartAsync()
    启动中 --> 运行中
    运行中 --> 暂停中 : PauseAsync()
    暂停中 --> 已暂停
    已暂停 --> 恢复中 : ResumeAsync()
    恢复中 --> 运行中
    运行中 --> 停止中 : StopAsync()
    停止中 --> 已停止
    已停止 --> [*] : DisposeAsync()
```

所有状态转换都是封装在 `IConvaiOperation<Unit>`中的异步操作。 `状态` 在继续之前，请检查返回的操作中的

{% hint style="info" %}
`ConvaiManager` 以确认转换已完成。 `DisposeAsync()` 在销毁时会自动处理。如果你使用 `ConvaiRuntimeBuilder` 直接构建自定义宿主，请调用 `DisposeAsync()` 在 `StopAsync()` 来释放所有资源。
{% endhint %}

***

### 下一步

现在你已经了解 Convai 运行时的组成方式，以及每一层中哪些部分可以替换。接下来阅读会话生命周期，了解每个角色的会话是如何创建、持久化和恢复的，然后继续阅读轮次模式和事件系统。

{% content-ref url="/pages/1b034b7638b343540a3b205274bd08beaff9d15e" %}
[会话生命周期](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/core-concepts/session-lifecycle.md)
{% endcontent-ref %}

{% content-ref url="/pages/251e0bf7030a5f742a1182e15529c0604e3ee150" %}
[轮流发言模式](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/core-concepts/turn-taking-modes.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/core-concepts/runtime-architecture.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
