> 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 为什么要批处理、批次究竟何时发送，以及携带操作配置补丁或注意力对象的更新如何在 SDK 本地信任它们之前先得到确认。

### 为什么动态上下文要批量更新

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

共享批次还需要共享响应决策的原因在于，Convai 只能看到一条消息，因此它也只能应用一种响应模式。当同一时间窗口内的多次调用请求不同 `ConvaiRespondMode` 值时，最强的请求将决定整个批次： `MustRespond` 优先于 `Auto`，以及 `Auto` 优先于 `Silent`。 `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(...)` 默认是较弱的模式。

### 批次窗口及其上限

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
    participant Convai

    Script->>Character: SetState / AddEvent / SetCurrentAttentionObject
    Note over Character: debounce 0.5s per change,<br/>capped at 3s from the first staged change
    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: committed locally only after a matching, successful acknowledgement
    end
```

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

{% hint style="warning" %}
一个待处理的重置并不保证会以“重置”的形式到达 Convai。如果 `SetState`, `SetStates`, `AddEvent`, `RemoveState`, `SetCurrentAttentionObject`，或 `ClearCurrentAttentionObject` 在 `Reset()` 之后、但在批次窗口刷新之前被暂存，那么这个待处理的重置会被丢弃，并替换为仅包含新调用的普通批次。请在 `Flush()` 后立即调用 `Reset()` ，如果必须让重置在任何其他内容覆盖它之前先到达 Convai。
{% endhint %}

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

一次刷新只会发送一条消息，其模式取决于窗口期间发生了哪些变化。如果任何被跟踪的状态文本发生了变化——新增状态、更新值或移除状态——消息模式就是 Replace，它会携带完整的规范化上下文，以及一段简短的 delta 尾部来描述发生了什么变化。如果窗口内唯一的暂存变化只是注意力对象更新，且没有任何状态文本变化，那么消息模式就会改为 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 确认它们，因为文本更新只需要反映最新值即可。而携带操作配置补丁或注意力对象的更新则不同：它们会改变角色实际上能引用和执行的内容，因此在 Convai 确认之前，SDK 不会把它们提交到 `ConvaiCharacter`的已解析操作状态中。

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

只有当确认的状态是 `成功` ，并且其报告的 action、object、character 数量以及当前注意力对象与 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.
