> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# PlayFab Services SDK - 事件管道

> 使用 PlayFab Services SDK 事件管道发送批量遥测和 PlayStream 事件:身份验证类型、配置、GZIP 压缩和处理程序。

# 事件管道

事件管道是 PlayFab Services SDK 的一部分,其主要目的是允许游戏开发人员发送要存储在 PlayFab Insights 中的事件。它允许开发人员指定批处理大小、发送频率以及适当遥测解决方案的其他方面。

它有助于减轻游戏开发人员的负担并代表他们处理所有这些方面。它支持可配置的事件管道。一个游戏可以包含一个或多个此类管道,每个管道都可以使用自己的属性进行配置。

让我们来看看管道的一些基本概念。

## 管道类型

管道类型与管道发出的事件类型直接相关。开发人员可以实例化两种不同类型的管道:

* **遥测事件管道**:只能发送遥测事件,并使用 [Write Telemetry Events](https://learn.microsoft.com/en-us/rest/api/playfab/events/play-stream-events/write-telemetry-events) REST API。

* **PlayStream 事件管道**:只能发送 PlayStream 事件,并使用 [Write Events](https://learn.microsoft.com/en-us/rest/api/playfab/events/play-stream-events/write-events) REST API。

你可以拥有具有不同类型和配置的多个管道,但值得一提的是,管道类型在创建后**不能**更改,需要重新实例化管道。

## 身份验证类型

管道创建的另一个重要部分是身份验证类型。PlayFab 事件支持两种身份验证机制——实体身份验证和遥测密钥身份验证。

当游戏开发人员想要将他们的事件链接到特定实体以便进一步聚合或分析时,可使用实体身份验证。例如,游戏开发人员可以记录与玩家操作相关的事件,以进一步分析游戏玩家的行为,以便进行分段。

当游戏开发人员想要记录不一定需要链接到实体的事件时,可使用遥测密钥身份验证。例如,在玩家登录之前,游戏可以开始发送与性能或希望收集以供进一步分析的特定指标相关的事件。这可以在无需通过常规实体身份验证过程的情况下完成。

**支持的身份验证类型/事件类型组合**

|                   | **实体身份验证** | **遥测密钥身份验证** |
| ----------------- | :--------: | :----------: |
| **遥测事件**          |     有效     |      有效      |
| **PlayStream 事件** |     有效     |      无效      |

### 实体身份验证

此身份验证类型是常规且最常见的 PlayFab 身份验证方法。它与特定实体紧密相关,并要求你调用相应的 PlayFab 登录 API 才能获得实体令牌以用于任何后续调用。以下列表表示可用于实体身份验证的不同类型的实体。

* **namespace**:namespace 实体是指工作室内所有游戏的全部全局信息。
* **title**:title 实体是指该游戏的所有全局信息。
* **master\_player\_account**:master\_player\_account 是工作室内所有游戏共享的玩家实体。
* **title\_player\_account**:对于大多数开发人员,title\_player\_account 以最传统的方式表示玩家。
* **character**:character 实体是 title\_player\_account 的子实体。
* **group**:group 实体是其他实体的容器。它目前仅限于玩家和角色。

有关不同实体类型的更多信息,请参阅[内置实体类型](/services/playfab/live-service-management/game-configuration/entities/available-built-in-entity-types)。

此外,可以通过提供有效的 **PFEntityHandle**,在管道创建后更新管道实体。因此,游戏开发人员能够添加实体以开始将事件链接到它(*请参阅[切换到实体身份验证或更新实体](#switching-to-entity-auth-or-updating-entity)部分*),甚至可以在他们想要记录与实体无关的内容时删除它(*请参阅[切换到遥测密钥身份验证](#switching-to-telemetry-key-auth)部分*)。

如果管道上存在实体,它始终优先于遥测密钥身份验证。

### 遥测密钥身份验证

遥测密钥身份验证不需要实体令牌,因此它不与任何特定实体绑定。

如果使用遥测密钥身份验证,则在管道创建期间需要 **PFEventPipelineTelemetryKeyConfig** 结构体。此结构体有两个主要部分:

1. 一个遥测密钥,由通过 PlayFab Game Manager 创建和管理的字符串组成。

2. 一个 **PFServiceConfigHandle**,让 SDK 知道哪个服务配置是用于上传事件的正确配置。服务配置句柄是在 SDK 初始化期间通过调用 [**PFServiceConfigCreateHandle**](/services/playfab/api-references/c/pfserviceconfig/functions/pfserviceconfigcreatehandle) 创建的。

如果开发人员想要使用遥测密钥,重要的是在管道创建时提供它,因为管道实例化后就无法添加遥测密钥。

值得一提的是,**遥测密钥身份验证仅适用于遥测事件**;它不支持 PlayStream 事件。

## 事件管道配置

如前所述,事件管道具有一些可配置的属性,可以在管道创建后通过 **PFEventPipelineConfig** 结构体参数提供。

可配置的属性有:

* **maxEventsPerBatch**:在写入 PlayFab 之前分批处理的最大事件数。
* **maxWaitTimeInSeconds**:管道在发送不完整批次之前等待的最长时间。
* **pollDelayInMs**:管道清空事件缓冲区后再次读取该缓冲区的等待时长。
* **compressionLevel**:定义压缩算法所使用的压缩级别。有关压缩的更多详细信息,请参阅 *[GZIP 压缩](#gzip-compression)*。
* **retryOnDisconnect**:事件管道将重试因连接丢失而失败的事件发送。仅适用于遥测事件管道。
* **bufferSize**:管道缓冲区中的事件数量限制。

### 默认值

| 属性                       | 遥测管道   | PlayStream 管道 |
| ------------------------ | ------ | ------------- |
| **maxEventsPerBatch**    | 5      | 5             |
| **maxWaitTimeInSeconds** | 3      | 3             |
| **pollDelayInMs**        | 3000   | 10            |
| **compressionLevel**     | 无(已禁用) | 无(已禁用)        |
| **retryOnDisconnect**    | true   | 不适用           |
| **bufferSize**           | 1024   | 1024          |

如果 [**PFEventPipelineConfig**](/services/playfab/api-references/c/pfeventpipeline/structs/pfeventpipelineconfig) 只指定了部分属性,那些为空的属性将被覆盖并使用默认值。

有关如何更新事件管道的任何这些属性的示例,请参阅 *[更新管道配置](#update-pipeline-configuration)*。

#### **GZIP 压缩**

事件管道提供了使用 GZIP 压缩标准压缩正文有效负载的选项。

可以在 **PFEventPipelineConfig** 结构体内指定所需的压缩级别,该结构体是 [**PFEventPipelineUpdateConfiguration**](/services/playfab/api-references/c/pfeventpipeline/functions/pfeventpipelineupdateconfiguration) API 参数的一部分。

较低的压缩级别可实现较小的压缩,但速度最快;较高的压缩级别可实现更好的压缩率,但压缩速度最慢。压缩率的差异都取决于发送的数据类型。根据数据的大小和随机性,压缩率甚至在不同级别上都可能相同。

**权衡:**

由于运行压缩算法增加了复杂性,使用压缩会增加 CPU 时间,但另一方面,网络正文有效负载会显著减少。因此,根据游戏需求和资源,是否应使用压缩由游戏开发人员自己决定。

根据内部验证,CPU 时间平均增加 20%,有效负载正文大小平均减少 91%。这些百分比可能会根据被压缩数据的负载大小和复杂性而有很大差异。

## 事件处理程序

作为管道创建的一部分,游戏开发人员可以提供两个可选的事件处理程序,它们会在事件上传时被调用。

但是,如果游戏开发人员想要"即发即忘"的体验,可以省略提供事件处理程序。

可以提供的事件处理程序如下:

* [**PFEventPipelineBatchUploadSucceededEventHandler**](/services/playfab/api-references/c/pfeventpipeline/functions/pfeventpipelinebatchuploadsucceededeventhandler):顾名思义,它接收所有成功上传到 PlayFab 的事件。

* [**PFEventPipelineBatchUploadFailedEventHandler**](/services/playfab/api-references/c/pfeventpipeline/functions/pfeventpipelinebatchuploadfailedeventhandler):它将接收经过管道重试逻辑后所有失败的事件。

### 管道创建示例

下面你可以找到基于前面讨论的主题实例化事件管道的不同示例。

1. **使用实体身份验证创建遥测事件管道**

   如果开发人员想要发送遥测事件并且不需要使用遥测密钥身份验证,则 [**PFEventPipelineCreateTelemetryPipelineHandleWithEntity**](/services/playfab/api-references/c/pfeventpipeline/functions/pfeventpipelinecreatetelemetrypipelinehandlewithentity) API 可用于此目的,如以下示例所示:

   ```cpp theme={null}
   void EventPipelineCreation(PFEntityHandle entityHandle, XTaskQueueHandle taskQueueHandle)
   {
       PFEventPipelineHandle handle;

       HRESULT hr = PFEventPipelineCreateTelemetryPipelineHandleWithEntity(
           entityHandle,                       // entityHandle
           taskQueueHandle,                    // queue
           nullptr,                            // eventPipelineBatchUploadedEventHandler
           nullptr,                            // eventPipelineBatchFailedEventHandler
           nullptr,                            // handlerContext
           &handle                             // eventPipelineHandle
       );

       if (FAILED(hr))
       {
           printf("Failed creating event pipeline: 0x%x\r\n", hr);
           return;
       }
   }
   ```

   此示例展示了如何创建使用实体身份验证且没有处理程序的遥测事件管道。这意味着管道发送事件后就不再关心结果。

2. **使用遥测密钥身份验证创建遥测事件管道**

   如果开发人员想要发送遥测事件并需要使用遥测密钥身份验证,则 PFEventPipelineCreateTelemetryPipelineHandleWithKey API 可用于此目的,如以下示例所示:

   ```cpp theme={null}
   void EventPipelineCreation(PFServiceConfigHandle serviceConfigHandle, XTaskQueueHandle taskQueueHandle)
   {
       PFEventPipelineHandle handle;

       PFEventPipelineTelemetryKeyConfig telemetryKeyConfig
       {
           "myTelemetryKey",                   // telemetryKey
           serviceConfigHandle,                // serviceConfigHandle
       };

       HRESULT hr = PFEventPipelineCreateTelemetryPipelineHandleWithKey(
           &telemetryKeyConfig,                // eventPipelineTelemetryKeyConfig
           taskQueueHandle,                    // queue
           nullptr,                            // eventPipelineBatchUploadedEventHandler
           nullptr,                            // eventPipelineBatchFailedEventHandler
           nullptr,                            // handlerContext
           &handle                             // eventPipelineHandle
       );

       if (FAILED(hr))
       {
           printf("Failed creating event pipeline: 0x%x\r\n", hr);
           return;
       }
   }
   ```

   此示例展示了如何创建不使用实体身份验证的遥测事件管道。

   事件管道开始使用遥测密钥上传事件,并且以后可以添加实体。请参阅[切换到实体身份验证或更新实体](#switching-to-entity-auth-or-updating-entity)

3. **PlayStream 事件管道创建**

   如果开发人员想要发送 PlayStream 事件,则 PFEventPipelineCreatePlayStreamPipelineHandle API 可用于此目的,如以下示例所示:

   ```cpp theme={null}
   void EventPipelineCreation(PFEntityHandle entityHandle, XTaskQueueHandle taskQueueHandle)
   {
       PFEventPipelineHandle handle;

       HRESULT hr = PFEventPipelineCreatePlayStreamPipelineHandle(
           entityHandle,                       // entityHandle
           taskQueueHandle,                    // queue
           nullptr,                            // eventPipelineBatchUploadedEventHandler
           nullptr,                            // eventPipelineBatchFailedEventHandler
           nullptr,                            // handlerContext
           &handle                             // eventPipelineHandle
       );

       if (FAILED(hr))
       {
           printf("Failed creating event pipeline: 0x%x\r\n", hr);
           return;
       }
   }
   ```

   此示例展示了如何创建使用实体身份验证且没有处理程序的 PlayStream 事件管道。这意味着管道发送事件后就不再关心结果。

4. **使用事件处理程序的事件管道**

   此示例展示了如何将事件处理程序提供给管道创建 API。

   ```cpp theme={null}
   void CALLBACK OnBatchUploadedHandler(void* context, PFUploadedEvent const* const* events, size_t eventsCount)
   {
       // Handle response
   }

   void CALLBACK OnBatchUploadFailedHandler(void* context, HRESULT hr, const char* errorMessage, PFEvent const* const* events, size_t eventsCount)
   {
       // Handle response
   }

   void EventPipelineCreation(PFEntityHandle entityHandle, XTaskQueueHandle taskQueueHandle)
   {
       PFEventPipelineHandle handle;

       HRESULT hr = PFEventPipelineCreateTelemetryPipelineHandleWithEntity(
           entityHandle,                       // entityHandle
           taskQueueHandle,                    // queue
           OnBatchUploadedHandler,             // eventPipelineBatchUploadedEventHandler
           OnBatchUploadFailedHandler,         // eventPipelineBatchFailedEventHandler
           nullptr,                            // handlerContext
           &handle                             // eventPipelineHandle
       );

       if (FAILED(hr))
       {
           printf("Failed creating event pipeline: 0x%x\r\n", hr);
           return;
       }
   }
   ```

如示例所示,***OnBatchUploadedHandler*** 和 ***OnBatchUploadFailedHandler*** 被声明为两个不同的回调函数,它们在创建事件管道时被传入。与事件相关的每个结果都在这两个函数之一上返回,具体取决于结果是成功还是失败。如何处理结果由游戏开发人员决定。

### 发送事件

事件管道创建后,发送事件是一项简单的操作。游戏开发人员应调用 [**PFEventPipelineEmitEvent**](/services/playfab/api-references/c/pfeventpipeline/functions/pfeventpipelineemitevent) API,该 API 接收现有的事件管道句柄和他们想要发送的事件。

```cpp theme={null}
void EmitEvent(PFEventPipelineHandle handle)
{
    PFEvent myEvent
    {
        nullptr,
        "custom.playfab.events.PlayFab.Test.TelemetryEventPipelineTests",
        "TelemetryEvent",
        nullptr,
        "{}"
    };

    HRESULT hr = PFEventPipelineEmitEvent(
        handle,
        &myEvent
    );

    if (FAILED(hr))
    {
        printf("Failed emitting event: 0x%x\r\n", hr);
        return;
    }
}
```

需要注意的是,当使用遥测密钥身份验证时,作为 **PFEntityKey** 一部分的 entityType 的唯一有效值为 **"external"**。任何其他实体类型都会被拒绝,事件也不会被上传。

使用遥测密钥身份验证时,有效的 PFEntityKey 示例:

```cpp theme={null}
void EmitEvent(PFEventPipelineHandle handle)
{
    PFEntityKey pfEntityKey{ "my-unique-ID", "external" };  // Note the "external" value

    PFEvent myEvent
    {
        &pfEntityKey,
        "custom.playfab.events.PlayFab.Test.EventsWithTelemetryKey",
        "TelemetryEvent",
        nullptr,
        "{}"
    };

    HRESULT hr = PFEventPipelineEmitEvent(
        handle,
        &myEvent
    );

    if (FAILED(hr))
    {
        printf("Failed emitting event: 0x%x\r\n", hr);
        return;
    }
}
```

如果发送事件时超出缓冲区大小限制,你将从 SDK 收到如下错误:

* `E_PF_API_CLIENT_REQUEST_RATE_LIMIT_EXCEEDED (0x892354dd)`

确保根据你的需求设置足够的缓冲区大小。

### 切换到实体身份验证或更新实体

如果一个管道只使用遥测密钥配置创建,则可以切换到实体身份验证,或者如果你想要更新你的管道以使用不同的实体。

通过使用 [**PFEventPipelineAddUploadingEntity**](/services/playfab/api-references/c/pfeventpipeline/functions/pfeventpipelineadduploadingentity),可以将实体附加到正在运行的管道,而无需重新初始化它。它还会替换现有实体(如果有)。

示例:

在游戏执行开始时使用遥测密钥配置创建遥测事件管道。这样做的可能原因是尚无玩家身份或者我们还不想记录与玩家相关的任何内容。

```cpp theme={null}
void EventPipelineCreation(PFServiceConfigHandle serviceConfigHandle, XTaskQueueHandle taskQueueHandle)
{
    PFEventPipelineHandle handle;

    PFEventPipelineTelemetryKeyConfig telemetryKeyConfig
    {
        "myTelemetryKey",                   // telemetryKey
        serviceConfigHandle,                // serviceConfigHandle
    };

    HRESULT hr = PFEventPipelineCreateTelemetryPipelineHandleWithKey(
        &telemetryKeyConfig,                // eventPipelineTelemetryKeyConfig
        taskQueueHandle,                    // queue
        nullptr,                            // eventPipelineBatchUploadedEventHandler
        nullptr,                            // eventPipelineBatchFailedEventHandler
        nullptr,                            // handlerContext
        &handle                             // eventPipelineHandle
    );

    if (FAILED(hr))
    {
        printf("Failed creating event pipeline: 0x%x\r\n", hr);
        return;
    }
}
```

然后,玩家登录或我们只想开始发送附加到特定实体的事件,因此我们可以使用现有的事件管道句柄和所需的实体句柄调用 **PFEventPipelineAddUploadingEntity**,如下所示:

```cpp theme={null}
void EventPipelineUpdateEntity(PFEventPipelineHandle handle)
{
    HRESULT hr = PFEventPipelineAddUploadingEntity(
        handle,                 // eventPipelineHandle
        entityHandle            // entityHandle
    );

    if (FAILED(hr))
    {
        printf("Failed adding uploading entity: 0x%x\r\n", hr);
        return;
    }
}
```

此调用之后,管道将开始记录与该实体链接的任何后续事件。

### 切换到遥测密钥身份验证

在前一个方案的基础上,如果开发人员想要切换回遥测身份验证并将事件记录与实体分离,他们可以调用 [**PFEventPipelineRemoveUploadingEntity**](/services/playfab/api-references/c/pfeventpipeline/functions/pfeventpipelineremoveuploadingentity) 并像这样传入事件管道句柄:

```cpp theme={null}
void EventPipelineRemoveEntity(PFEventPipelineHandle handle)
{
    HRESULT hr = PFEventPipelineRemoveUploadingEntity(
        handle                  // eventPipelineHandle
    );

    if (FAILED(hr))
    {
        printf("Failed removing uploading entity: 0x%x\r\n", hr);
        return;
    }
}
```

此调用移除实体,并有效地将所有后续事件切换回遥测密钥身份验证。

### **更新管道配置**

可以使用 [**PFEventPipelineUpdateConfiguration**](/services/playfab/api-references/c/pfeventpipeline/functions/pfeventpipelineupdateconfiguration) API 轻松更新管道,该 API 接收现有的事件管道句柄和一个新的配置结构体,如下所示:

```cpp theme={null}
void EventPipelineUpdateConfiguration(PFEventPipelineHandle handle)
{
    uint32_t maxEvents = 10;
    uint32_t maxWaitTime = 5;
    uint32_t pollDelay = 50;
    PFHCCompressionLevel compressionLevel = PFHCCompressionLevel::Medium;
    bool retryOnDisconnect = true;
    size_t bufferSize = 2048;

    PFEventPipelineConfig eventPipelineConfig
    {
        &maxEvents,             // maxEventsPerBatch
        &maxWaitTime,           // maxWaitTimeInSeconds
        &pollDelay,             // pollDelayInMs
        &compressionLevel,      // compressionLevel
        &retryOnDisconnect,     // retryOnDisconnect
        &bufferSize,            // bufferSize
    };

    HRESULT hr = PFEventPipelineUpdateConfiguration(
        handle,                 // eventPipelineHandle
        eventPipelineConfig     // eventPipelineConfig
    );

    if (FAILED(hr))
    {
        printf("Failed updating event pipeline configuration: 0x%x\r\n", hr);
        return;
    };
}
```

***提醒**:任何空或 null 属性都会用默认值覆盖现有配置值。*

## 无效/停用的遥测密钥

如果在管道创建时提供的遥测密钥被停用或无效,任何使用该遥测密钥运行的事件管道将在发现密钥无效或被停用后立即开始失败。

在客户重新激活密钥的最终情况下,管道不会意识到这一点,并将继续通过失败事件处理程序发送失败信息。值得一提的是,此行为是基于会话的,这意味着如果游戏重启,将创建一个新的管道,并能够再次上传事件。

## 管道句柄生存期

与其他 PlayFab 句柄一样,事件管道句柄使用重复/关闭模式:

* [**PFEventPipelineCloseHandle**](/services/playfab/api-references/c/pfeventpipeline/functions/pfeventpipelineclosehandle):关闭管道句柄。当管道的最后一个句柄被关闭时,管道将被销毁,任何剩余的缓冲事件都会被刷新。
* [**PFEventPipelineDuplicateHandle**](/services/playfab/api-references/c/pfeventpipeline/functions/pfeventpipelineduplicatehandle):复制管道句柄。原始句柄和复制的句柄必须使用 [**PFEventPipelineCloseHandle**](/services/playfab/api-references/c/pfeventpipeline/functions/pfeventpipelineclosehandle) 独立关闭。

```cpp theme={null}
// Duplicate a handle for use in another component
PFEventPipelineHandle duplicatedHandle;
HRESULT hr = PFEventPipelineDuplicateHandle(originalHandle, &duplicatedHandle);

// When done, close both handles independently
PFEventPipelineCloseHandle(duplicatedHandle);
PFEventPipelineCloseHandle(originalHandle);
```

## 另请参阅

* [事件管道教程](/services/playfab/sdks/c/event-pipeline/eventpipeline-tutorial)


## Related topics

- [事件管道教程](/zh-CN/services/playfab/sdks/c/event-pipeline/eventpipeline-tutorial.md)
- [PlayFab Services C/C++ SDK 发行说明](/zh-CN/services/playfab/release-notes/playfab-c/index.md)
- [PlayFab Services SDK](/zh-CN/services/playfab/sdks/c/index.md)
- [PlayFab 支持的语言](/zh-CN/services/playfab/sdks/languages/index.md)
- [Services C API overview - PFEventPipeline.h](/zh-CN/services/playfab/api-references/c/pfeventpipeline/pfeventpipeline_members.md)
