> 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).

# 同步行为与时序

了解 SDK 如何在发送动态上下文更新前对其进行批处理，以及 Convai 如何确认动作和注意力变化。

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

### 为什么动态上下文会批处理更新

一次游戏帧可以同时调用多个受跟踪的方法——例如一个危险状态、一次位置变更，以及一个事件在一次物理更新中同时触发。发送每次调用各自独立的 `上下文更新` 消息会增加网络流量，并且可能让 Convai 在本帧其余变更尚未落地之前就对一个中间状态作出反应。相反， `ConvaiCharacter` 会将每次调用先暂存到 `SetState`, `SetStates`, `AddEvent`, `RemoveState`, `SetCurrentAttentionObject`，以及 `ClearCurrentAttentionObject` 的一个待处理批次中，并把该批次作为一条消息刷新发送。

共享批次也需要共享反应决策的原因是，Convai 只会看到一条消息，因此它只能应用一种 respond 模式。当同一时间窗口中的多次调用请求不同的 `ConvaiRespondMode` 值时，整个批次中最强的请求会胜出： `MustRespond` 优先于 `自动`，以及 `自动` 优先于 `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");
    }
}
```

**预期结果：** 这三个调用都会落入同一个批次，并生成一条 `上下文更新` 消息。因为 `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，它会携带完整的规范上下文，以及一个简短的差异尾部来描述变更内容。如果窗口内唯一暂存的变更只是注意对象更新，且没有状态文本变化，那么消息模式则为 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`时，刷新才会发送。在对话开始前暂存的调用，或在掉线后重连期间暂存的调用，都会保留在跟踪器中排队，而不会被丢弃。

当会话变为 `Disconnected` 或 `Error`时，SDK 会对跟踪器当前持有的内容进行一次规范重同步暂存，而不是重放生成这些内容的单个调用。断开连接时之所以要重同步完整的规范上下文，而不是重放历史记录，是因为 Convai 只需要角色当前的状态，而不需要它离线期间经历过的中间值序列。

当角色收到就绪信号时——无论是初次连接还是重连之后——SDK 都会立即刷新任何待处理批次，而不会等待防抖窗口。这就是把在对话尚不存在时就已暂存的上下文发送出去的方式：它不会被丢弃，而且一旦角色就绪，也不会再等待新的 0.5 秒计时器结束。

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

普通的状态和事件更新在刷新后就是一次性发送——SDK 不会等待 Convai 确认它们，因为文本更新只需要反映最新值即可。携带动作配置补丁或注意对象的更新则不同：它们会改变角色实际可引用和可执行的内容，因此 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 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/features/dynamic-context/sync-behavior-and-timing.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.
