> 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/features/dynamic-context/sync-behavior-and-timing.md).

# 同步行为和时序

动态上下文更新不会在你调用时立即到达 Convai `SetState`, `AddEvent`，或任何其他受跟踪的方法。SDK 会将每次调用暂存到一个短暂存在的批次中，并发送一条 `context-update` 每个批次一条消息，而不是每次调用一条。本页解释 SDK 为什么要批处理、批次究竟何时发送，以及携带动作配置补丁或 attention 对象的更新如何在 SDK 本地信任它们之前先得到确认。

### 动态上下文为何会批量处理更新

单个游戏帧可以同时调用多个受跟踪的方法——例如危险状态、位置变化，以及在一次物理更新中同时触发的事件。发送每个 `context-update` 调用一条消息会成倍增加网络流量，并且可能让 Convai 在该帧其余更改到达之前就对中间状态做出反应。相反， `ConvaiCharacter` 会将每次调用暂存到 `SetState`, `SetStates`, `AddEvent`, `RemoveState`, `SetCurrentAttentionObject`，以及 `ClearCurrentAttentionObject` 一个待处理批次中，并将该批次作为单条消息刷新发送。

共享批次也需要共享反应决策的原因在于，Convai 只会看到一条消息，因此只能应用一种 respond 模式。当同一时间窗口内的多次调用请求不同的 `ConvaiRespondMode` 值时，最强的请求会对整个批次生效： `MustRespond` 优先于 `自动`，以及 `自动` 优先于 `静默`。 `MustRespond` 在一批原本静默的更新中，只要有一次调用就足以让角色做出反应。

```csharp
using Convai.Runtime;
using Convai.Runtime.Components;
using Convai.Runtime.DynamicContext;
using UnityEngine;

public sealed class HazardZoneContext : MonoBehaviour
{
    [SerializeField] private ConvaiCharacter character;

    public void OnHazardTriggered()
    {
        character.DynamicContext.SetState("Station", "Bay 7");
        character.DynamicContext.SetState("HazardLevel", "Extreme", ConvaiRespondMode.MustRespond);
        character.DynamicContext.AddEvent("Operator bypassed interlock");
    }
}
```

**预期结果：** 这三次调用都会落入同一个批次，并生成一条 `context-update` 消息。因为 `HazardLevel` 请求了 `MustRespond`，整个批次会以 `MustRespond`发送，尽管 `SetState("Station", ...)` 和 `AddEvent(...)`\
&#x20;默认会使用更弱的模式。

### 批处理窗口及其上限

SDK 会对暂存的更改进行防抖，持续 `ConvaiCharacter.DynamicContextBatchDelaySeconds` ——一个固定的 `0.5` 秒。在该窗口期间每新增一项暂存更改，都会将刷新时间再推迟 `0.5` 秒，从它被暂存的那一刻起算，以 Unity 上次处理更改的时间为准。

理论上，持续不断的更改流可能会把刷新无限推迟，因此 SDK 还设置了上限：等待时间从窗口内第一项暂存更改算起，绝不能超过 3 秒。无论先达到哪个限制——0.5 秒的防抖稳定时间，还是 3 秒上限——都会触发刷新。

| 时序行为        | 值                                        | 效果               |
| ----------- | ---------------------------------------- | ---------------- |
| 按次防抖        | 0.5 秒（`DynamicContextBatchDelaySeconds`) | 每一项暂存更改都会重置刷新倒计时 |
| 每个窗口的最长等待时间 | 3 秒                                      | 即使更改持续到来，刷新也会触发  |

```mermaid
sequenceDiagram
    participant Script
    participant Character as ConvaiCharacter
    参与者 Convai

    Script->>Character: SetState / AddEvent / SetCurrentAttentionObject
    Note over Character: 每次更改防抖 0.5 秒，<br/>从第一项暂存更改起最多 3 秒
    Character->>Convai: context-update (Replace or Append)
    opt Update carries an action config patch or attention object
        Convai-->>Character: DynamicContextUpdateResultReceived (update_id)
        Note over Character: 仅在收到匹配且成功的确认后才会在本地提交
    end
```

`Reset()` 会经过相同的防抖窗口，而不是立即发送——调用 `Reset()` 不会绕过 0.5 秒的等待。

{% hint style="warning" %}
待处理的重置不一定会以重置的形式到达 Convai。如果 `SetState`, `SetStates`, `AddEvent`, `RemoveState`, `SetCurrentAttentionObject`，或 `ClearCurrentAttentionObject` 在……之后被暂存 `Reset()` 但在批处理窗口刷新之前，待处理的重置会被丢弃，并被仅包含新调用的普通批次替代。请在 `Flush()` 之后立即调用 `Reset()` ，如果该重置必须在任何其他内容取代它之前到达 Convai。
{% endhint %}

### 一次刷新会发送什么

一次刷新只会发送一条消息，其模式取决于该窗口内发生了什么变化。如果任何受跟踪的状态文本发生了变化——新增状态、更新值或删除状态——消息模式就是 Replace，携带完整的规范上下文以及简短的 delta 尾部，描述发生了什么变化。如果该窗口内唯一暂存的更改只是 attention 对象更新，且没有状态文本变化，那么消息模式则改为 Append。

这与 [动态上下文脚本 API](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/dynamic-context/dynamic-context-scripting-api.md) 页面中记录的单次调用行为一致，不同之处在于：SDK 现在会将窗口内的每次调用合并为这一条消息，而不是每次调用发送一条消息。

### 在窗口关闭前强制刷新

调用 `Flush()` 在 `IConvaiDynamicContext` 以立即发送待处理批次，而不是等待防抖窗口结束。在某项更改必须在脚本对话的下一行播放之前到达 Convai 时使用它，而不要指望最长 3 秒的窗口能及时稳定下来。

```csharp
character.DynamicContext.SetState("Player location", "market square");
character.DynamicContext.Flush();
```

`Flush()` 仅在角色 `IsInConversation`。如果角色未连接，暂存的批次会保持待处理状态，不会被强制发送——有关其后续处理，请参见下一节。

### 连接、重连与断开连接时的时序

刷新仅在角色 `IsInConversation`。在对话开始前，或在掉线后重连期间暂存的调用，会保留在跟踪器队列中，而不会被丢弃。

当会话转到 `已断开` 或 `错误`时，SDK 会对跟踪器当前持有的内容暂存一次规范化重同步，而不是回放生成这些内容的单次调用。断开连接时重同步完整规范上下文而不是回放历史的原因在于，Convai 只需要角色当前的状态，而不需要它离线期间经过的中间值序列。

当角色收到就绪信号——在初次连接或重连之后——SDK 会立即刷新任何待处理批次，而不会等待防抖窗口结束。这就是如何传递在对话尚未存在时暂存的上下文：它不会被丢弃，并且在角色就绪后也不会再额外等待一个新的 0.5 秒计时器。

### 动作和注意力更新的确认时机

普通的状态和事件更新在刷新后就是一次性发送——SDK 不会等待 Convai 确认它们，因为文本更新只需要反映最新值。携带动作配置补丁或 attention 对象的更新则不同：它们会改变角色实际能够引用和执行的内容，因此 SDK 不会将它们提交到 `ConvaiCharacter`的已解析动作状态，直到 Convai 确认它们。

每个此类更新都会随一个 `update_id` 并在等待匹配的 `DynamicContextUpdateResultReceived` 事件一起发送。SDK 每秒检查一次是否匹配，如果 30 秒内都没有匹配的确认，就会放弃单个更新，丢弃待处理的变更并记录警告，而不是重试。待处理的更新按发送顺序提交——等待确认的旧更新会阻止更新的提交，即使较新的更新确认先到也不行。

只有当确认的状态为 `success` 且其报告的动作、对象和角色数量以及当前注意对象与 SDK 期望一致时，确认才会提交。与之不匹配或格式错误的确认会像超时一样被丢弃，并记录警告，而不是应用更新。

订阅该事件以直接观察确认结果：

```csharp
using Convai.Domain.DomainEvents.Runtime;
using Convai.Runtime.Components;
using UnityEngine;

ConvaiManager.ActiveManager.Events.OnDynamicContextUpdateResultReceived += result =>
{
    Debug.Log($"Dynamic context {result.Status}: revision {result.ContextRevision}");
};
```

如果某个确认报告其动作生成策略状态为 `requires_reconnect`，SDK 会呈现该状态，但不会自动重连——由调用代码决定是否以及何时重连。

### Apply() 会绕过批处理

`Apply()` 会直接将其更新发送到传输层，跳过跟踪器和上文所述的防抖窗口。但它不会绕过确认跟踪：一个 `Apply()` 携带动作配置补丁或当前注意对象的调用，仍会加入同一个待处理更新队列，并遵循上文“确认时机”中所述的相同 30 秒超时和 1 秒轮询间隔。

{% hint style="danger" %}
如果角色在以下情况下不处于活动对话中： `Apply()` 被调用，更新会立即丢弃——它不会被暂存，也不会在角色准备就绪时稍后刷新。请使用 `SetState`, `SetStates`, `AddEvent`, `RemoveState`, `SetCurrentAttentionObject`，或 `ClearCurrentAttentionObject` 来处理任何必须在对话开始前调用仍需保留的内容。
{% endhint %}

`Apply()` 适用于那些已经自行生成规范上下文文本的调用方——例如外部状态机——并且需要直接控制具体发送什么以及何时发送，而不希望 SDK 将其重塑为一个批次。

### 下一步

{% content-ref url="/pages/5c3f9bcc544ac6f441fe54139ac1c670eeb5c958" %}
[动态上下文脚本 API](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/dynamic-context/dynamic-context-scripting-api.md)
{% endcontent-ref %}

{% content-ref url="/pages/531d80b1af7cb4aba63c4979e0a4c0f8e029bc95" %}
[排查动态上下文故障](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/dynamic-context/troubleshoot-dynamic-context.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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/dynamic-context/sync-behavior-and-timing.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
