# Шлюз Playfab

Это руководство покажет вам, как настроить игру с PlayFab, чтобы автоматически развертывать сервер на Edgegap после того, как игроки будут сопоставлены через матчмейкер PlayFab. Клиенты будут получать информацию для подключения к серверу через PlayFab Player Data, которые сервер обновит, когда будет готов. Перед началом мы предполагаем, что:

* Вы в настоящее время используете PlayFab;
* Вы уже [создали](/ru/learn/orkestraciya/application-and-versions.md) и [развернули](/ru/learn/orkestraciya.md) приложение с Edgegap ранее.

Вы можете найти окончательный пример проекта на нашем [GitHub](https://github.com/edgegap/sample-playfab-matchmaker-fishnet). Этот пример был протестирован с использованием Unity v2021.3.10 и Fishnet v3.11.14 в качестве сетевого кода.

### Настройка PlayFab Cloud Script

#### Создать новую функцию

Войдите в свою учетную запись PlayFab, затем перейдите на страницу обзора вашей игры на панели управления. В `Automation` вкладке создайте новую функцию Cloud Script, которая будет отправлять запрос к API Edgegap для развертывания нового сервера.

В запросе вам нужно передать список широты/долготы игроков, чтобы сервер мог быть развернут в наилучшей возможной локации для всех.

Вам также нужно передать некоторые пользовательские переменные окружения в виде пар ключ-значение, а именно:

* the `Match ID` сгенерированный матчмейкером PlayFab;
* a PlayFab `Title Secret Key`, который можно найти в настройках вашего титула;
* a `list` каждого игрока `Master Player Account Id`.

Эти переменные будут использоваться для обновления PlayFab `Player Data` игроков информацией, необходимой для подключения к серверу. Запоминайте ключи, которые вы используете для установки этих переменных; в этом примере они: `"PLAYFAB_MATCH_ID"`, `"PLAYFAB_TITLE_SECRET"`, и `"PLAYFAB_MATCH_PLAYERS_ID_LIST"`.

{% hint style="info" %}
Самый простой способ создать эту функцию — развернуть новое ревизию в `Revisions (Legacy)` вкладке, добавив в неё следующий код. Обязательно обновите `Edgegap_AppName`, `Edgegap_AppVersion`, `Edgegap_ApiToken` и `PlayFab_TitleSecret` переменные вверху вашими собственными значениями сначала.

Название приложения и версия будут использоваться позже при контейнеризации игрового сервера на Edgegap.

Список ID игроков хранится здесь как одна строка, где каждый ID разделяется запятой.
{% endhint %}

```javascript
var Edgegap_AppName = "APP_NAME";
var Edgegap_AppVersion = "APP_VERSION";
var Edgegap_ApiToken = "token XXXX"; //include the "token" keyword
var PlayFab_TitleSecret = "TITLE_SECRET";

handlers.StartEdgegapDeployment = function (args, context) {
  var QueueName = context.playStreamEvent.Payload.QueueName;
  var MatchId = context.playStreamEvent.Payload.MatchId;

  var GeoIpList = [];
  var PlayerIds = "";

  var MatchMember = multiplayer.GetMatch({
    QueueName: QueueName,
    MatchId: MatchId,
    EscapeObject: false,
    ReturnMemberAttributes: false,
  }).Members;

  MatchMember.forEach((Member) => {
    var Profile = entity.GetProfile({
      Entity: Member.Entity,
    });

    var Location = server.GetPlayerProfile({
      PlayFabId: Profile.Profile.Lineage.MasterPlayerAccountId,
      ProfileConstraints: {
        ShowLocations: true,
      },
    });

    GeoIpList.push({
      ip: "1.2.3.4",
      latitude: Location.PlayerProfile.Locations[0].Latitude,
      longitude: Location.PlayerProfile.Locations[0].Longitude,
    });

    PlayerIds += Profile.Profile.Lineage.MasterPlayerAccountId + ",";
  });

  PlayerIds = PlayerIds.slice(0, -1);

  var response = JSON.parse(
    http.request(
      "https://api.edgegap.com/v1/deploy",
      "post",
      JSON.stringify({
        app_name: Edgegap_AppName,
        version_name: Edgegap_AppVersion,
        geo_ip_list: [...GeoIpList],
        tags: [MatchId.slice(0, 20)],
        env_vars: [
          {
            key: "PLAYFAB_MATCH_ID",
            value: MatchId,
          },
          {
            key: "PLAYFAB_TITLE_SECRET",
            value: TitleSecret,
          },
          {
            key: "PLAYFAB_MATCH_PLAYERS_ID_LIST",
            value: PlayerIds,
          },
        ],
      }),
      "application/json",
      { authorization: Edgegap_ApiToken }
    )
  );

  log.info("response", response);
};
```

{% hint style="info" %}
Эта функция Cloud Script создаёт интерфейс между игровым клиентом и API Edgegap, который скрывает ваши `Edgegap API token` и `PlayFab Title Secret` от игроков.
{% endhint %}

### Правило автоматизации

Все ещё в `Automation`, перейдите на вкладку `Rules` вкладку. Создайте новое правило со следующими настройками:

* Установите `Event Type` к `playfab.matchmaking.match_found`;
* Добавьте новое `Action`:
  * Установите `Type` к `Execute Entity Cloud Script`;
  * Установите `Cloud Script Function` к `StartEdgegapDeployment`.

<figure><img src="/files/e31fc732b864547be8513d0fe4ad2de298a4618e" alt=""><figcaption></figcaption></figure>

### Очередь матчмейкера и игроки

В `Multiplayer` вкладке, в разделе `Matchmaking`убедитесь, что у вас есть доступная очередь, иначе создайте новую. Для быстрой проверки настройте её так, чтобы она просто сопоставляла любые двух игроков вместе.

Вам потребуется достаточное количество игроков, зарегистрированных в вашем титуле PlayFab, чтобы удовлетворить правила матчмейкера, поэтому в данном случае подойдут только двое. При необходимости вы можете создать новых на вкладке `Players` . Для этого примера проекта обязательно запоминайте `Custom ID` и `Title Player Account ID` этих игроков для последующего тестирования.

### Редактирование игры

#### Сервер

После открытия примера игры в редакторе Unity мы добавили новый пустой gameObject в `BattleScene` найденную в `Assets/SpaceEdge/Scenes`. Затем мы прикрепили к нему новый скрипт C# под названием `PlayerDataUpdateService` . Он содержит функцию обратного вызова, которая будет выполнена, как только сервер будет готов.

Мы также создали второй скрипт под названием `PlayfabRestApiService`, который содержит различные запросы, которые необходимо отправлять к различным API PlayFab.

В `PlayerDataUpdateService.cs`мы получаем несколько переменных окружения, внедрённых в развертывании:

* Пользовательские переменные:
  * `PLAYFAB_MATCH_ID`;
  * `PLAYFAB_TITLE_SECRET`;
  * `PLAYFAB_MATCH_PLAYERS_ID_LIST`
* Стандартные переменные Edgegap:
  * `ARBITRIUM_PUBLIC_IP`;
  * `ARBITRIUM_PORTS_MAPPING`;

В `ARBITRIUM_PORTS_MAPPING`, нам нужно получить конкретное внешнее `порт` значение, которое позволит клиенту подключиться к серверу; в этом примере это значение с именем `"Game Port"`.

Используя `PLAYFAB_TITLE_SECRET` в качестве аутентификации, мы отправляем запрос к Server API PlayFab, чтобы обновить `Player Data` для каждого соответствующего ID в `PLAYFAB_MATCH_PLAYERS_ID_LIST` следующей парой ключ-значение:

* Ключ: `PLAYFAB_MATCH_ID`;
* Значение: `"{ARBITRIUM_PUBLIC_IP}:{port}"`;

#### PlayerDataUpdateService

```cs
public class PlayerDataUpdateService : NetworkBehaviour
{
    private PlayfabRestApiService _playfabService;

    [SerializeField] private string KeyMatchId = "PLAYFAB_MATCH_ID";
    [SerializeField] private string KeyTitleSecret = "PLAYFAB_TITLE_SECRET";
    [SerializeField] private string KeyPlayerIds = "PLAYFAB_MATCH_PLAYERS_ID_LIST";
    [SerializeField] private string ServerPortName = "Game Port";

    private string matchID;
    private string titleSecretKey;
    private string[] playerIdList;

    public override void OnStartServer()
    {
        _playfabService = FindObjectOfType<PlayfabRestApiService>();

        matchID = Environment.GetEnvironmentVariable(KeyMatchId);
        titleSecretKey = Environment.GetEnvironmentVariable(KeyTitleSecret);
        playerIdList = Environment.GetEnvironmentVariable(KeyPlayerIds)?.Split(',');

        string address = Environment.GetEnvironmentVariable("ARBITRIUM_PUBLIC_IP");
        string portsMapping = Environment.GetEnvironmentVariable("ARBITRIUM_PORTS_MAPPING");

        if (!string.IsNullOrEmpty(portsMapping))
        {
            int port = JSON.ParseString(portsMapping).GetJSON("ports").GetJSON(ServerPortName).GetInt("external");

            foreach (string playerId in playerIdList)
            {
                if (!string.IsNullOrEmpty(playerId))
                {
                    _playfabService.UpdatePlayerData(titleSecretKey, playerId, matchID, $"{address}:{port}");
                }
            }
        }
        else
        {
            Debug.Log("Port Mapping Unavailable");
        }
    }

    ...
}
```

#### PlayfabRestApiService

```cs
public class PlayfabRestApiService : MonoBehaviour
{
    ...

    [SerializeField] private string TitleID = "TITLE_ID";

    private const string TypeHeaderValue = "application/json";
    private readonly HttpClient _playfabHttpClient = new();
    private HttpResponseMessage _request;
    private StringContent _requestData;
    private string _response;

    ...

    public async void UpdatePlayerData(string secretKey, string playerID, string key, string value)
    {
        if (!_playfabHttpClient.DefaultRequestHeaders.Contains("X-SecretKey"))
        {
            _playfabHttpClient.DefaultRequestHeaders.Add("X-SecretKey", secretKey);
        }

        var dataProperty = new JSON();
        dataProperty.Add(key, value);

        var requestJson = new JSON();
        requestJson.Add("PlayFabId", playerID);
        requestJson.Add("Data", dataProperty);

        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);
        _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Server/UpdateUserData", _requestData);
        _response = await _request.Content.ReadAsStringAsync();

        if (!_request.IsSuccessStatusCode)
        {
            Debug.Log($"Could Not Update PlayerData of ID {playerID}, Error {(int)_request.StatusCode} With Message: \n{_response}");
        }
    }
}
```

#### Клиент

Настройка на стороне клиента будет управляться в `MainMenu` сцене, найденной в `Assets/SpaceEdge/Scenes`. Мы создали новый скрипт под названием `MainMenuService` для управления интерфейсом сцены и отображения различных сообщений игроку, и добавили новые функции в `PlayfabRestApiService.cs`. Мы также прикрепили этот `PlayfabRestApiService` скрипт к новому gameObject в этой сцене.

Первое, что должен сделать игрок — войти через PlayFab, что позволит ему отправлять дополнительные запросы к Client и Multiplayer API PlayFab.

{% hint style="info" %}
Мы используем `LoginWithCustomID` эндпоинт для простоты, однако существуют более подходящие способы реализации входа в зависимости от ситуации (например: для мобильных игр).
{% endhint %}

После входа игрока и корректной установки заголовков `X-Authorization` и `X-EntityToken` для последующих запросов, он может создать новый билет на матчмейкинг, используя кнопку UI `Start Game` . Это отправит запрос к мультиплеерному API PlayFab и сохранит `ticket ID` для последующего использования.

После этого игровой клиент периодически будет проверять статус билета каждые несколько секунд, пока матч не будет найден или билет не будет отменён. Как только матч найден, `match ID` сохраняется как переменная.

Клиент затем периодически будет посылать запросы к Client API PlayFab, чтобы получить Title игрока `Player Data`, используя `match ID` в качестве ключа для конкретных данных, которые необходимо получить. Как только данные найдены, он устанавливает `адрес` сервера `порт` и внешнее `значение в` компоненте транспорта сетевого кода, затем подключается к серверу.

Когда клиент подключится и корректно запустится, будет отправлен запрос на удаление информации о подключении из PlayFab этого игрока `Player Data` благодаря функции обратного вызова.

{% hint style="info" %}
Важно убедиться, что эти пары ключ-значение удаляются, желательно до завершения развертывания. Это необходимо, чтобы избежать засорения `Player Data` слишком большим количеством ненужных записей.
{% endhint %}

#### MainMenuService

```cs
public class MainMenuService : MonoBehaviour
{
    [SerializeField] private Button startGameButton;
    [SerializeField] private TMP_InputField playerNameInput;
    [SerializeField] private TMP_Text serverStatus;

    private PlayfabRestApiService _playfabService;

    private void Start()
    {
        if (!InstanceFinder.ServerManager.GetStartOnHeadless())
        {
            _playfabService = FindObjectOfType<PlayfabRestApiService>();
            _playfabService.OnStatusUpdate += ServerStatusUpdate;
            _playfabService.OnUpdateButton += UpdateButtonState;

            startGameButton.onClick.AddListener(StartGame);
            playerNameInput.onValueChanged.AddListener(NameChanged);
            if (PlayerPrefs.HasKey("PlayerName"))
                playerNameInput.text = PlayerPrefs.GetString("PlayerName");

            _playfabService.LoginPlayer();
        }
    }

    private void OnDestroy()
    {
        if (!InstanceFinder.ServerManager.GetStartOnHeadless())
        {
            _playfabService.OnStatusUpdate -= ServerStatusUpdate;
            _playfabService.OnUpdateButton -= UpdateButtonState;
        }
    }

    private void StartGame()
    {
        startGameButton.interactable = false;
        serverStatus.text = "";
        _playfabService.CreateTicket();
    }

    private void ServerStatusUpdate(string status, bool isError)
    {
        serverStatus.text += "\n" + status;
        serverStatus.color = isError ? Color.red : Color.green;
        Debug.Log(status);
    }

    private void UpdateButtonState(bool state)
    {
        startGameButton.interactable = state;
        serverStatus.text = "";
    }

    private void NameChanged(string text) => PlayerPrefs.SetString("PlayerName", text);
}
```

#### PlayfabRestApiService

```cs
public class PlayfabRestApiService : MonoBehaviour
{
    [SerializeField] private string PlayerCustomID = "PLAYER_CUSTOM_ID";
    [SerializeField] private string PlayerTitleID = "PLAYER_TITLE_ID";
    [SerializeField] private string TitleID = "TITLE_ID";
    [SerializeField] private string QueueName = "QUEUE_NAME";

    private const string TypeHeaderValue = "application/json";
    private readonly HttpClient _playfabHttpClient = new();
    private HttpResponseMessage _request;
    private StringContent _requestData;
    private string _response;
    private string _ticketID = "";
    private string _matchID = "";

    public Action<string, bool> OnStatusUpdate;
    public Action<bool> OnUpdateButton;

    private static PlayfabRestApiService _instance = null;

    private void Awake()
    {
        _playfabHttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(TypeHeaderValue));

        if (_instance is null)
        {
            DontDestroyOnLoad(this);
            _instance = this;
        }
        else
        {
            Destroy(this);
        }
    }

    public async void LoginPlayer()
    {
        var requestJson = new JSON();
        requestJson.Add("CustomID", PlayerCustomID);
        requestJson.Add("CreateAccount", false);
        requestJson.Add("TitleID", TitleID);
        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Client/LoginWithCustomID", _requestData);
        _response = await _request.Content.ReadAsStringAsync();

        if (_request.IsSuccessStatusCode)
        {
            var responseJson = JSON.ParseString(_response);

            string token = responseJson.GetJSON("data").GetJSON("EntityToken").GetString("EntityToken");
            _playfabHttpClient.DefaultRequestHeaders.Add("X-EntityToken", token);

            string sessionTicket = responseJson.GetJSON("data").GetString("SessionTicket");
            _playfabHttpClient.DefaultRequestHeaders.Add("X-Authorization", sessionTicket);

            OnUpdateButton?.Invoke(true);
            OnStatusUpdate?.Invoke("Login Successful", false);
        }
        else
        {
            OnStatusUpdate?.Invoke($"Unable To Login Player, Error {(int)_request.StatusCode} With Message: \n{_response}", true);
        }
    }

    public async void CreateTicket()
    {
        var requestJson = new JSON();

        var entityProperty = new JSON();
        entityProperty.Add("Id", PlayerTitleID);
        entityProperty.Add("Type", "title_player_account");

        var creatorProperty = new JSON();
        creatorProperty.Add("Entity", entityProperty);

        requestJson.Add("Creator", creatorProperty);
        requestJson.Add("GiveUpAfterSeconds", 90);
        requestJson.Add("QueueName", QueueName);
        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Match/CreateMatchmakingTicket", _requestData);
        _response = await _request.Content.ReadAsStringAsync();

        if (_request.IsSuccessStatusCode)
        {
            var responseJson = JSON.ParseString(_response);
            _ticketID = responseJson.GetJSON("data").GetString("TicketId");

            OnStatusUpdate?.Invoke($"Ticket Created, ID #{_ticketID}", false);

            GetMatchIdAttempt();
        }
        else
        {
            OnUpdateButton?.Invoke(true);
            OnStatusUpdate?.Invoke($"Unable To Create Ticket, Error {(int)_request.StatusCode} With Message: \n{_response}", true);
        }
    }

    private async void GetMatchIdAttempt()
    {
        bool isMatchFound = false;
        var requestJson = new JSON();
        requestJson.Add("EscapeObject", true);
        requestJson.Add("QueueName", QueueName);
        requestJson.Add("TicketId", _ticketID);
        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        while (!isMatchFound)
        {
            _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Match/GetMatchmakingTicket", _requestData);
            _response = await _request.Content.ReadAsStringAsync();

            if (_request.IsSuccessStatusCode)
            {
                var responseJson = JSON.ParseString(_response);
                string ticketStatus = responseJson.GetJSON("data").GetString("Status");
                isMatchFound = ticketStatus == "Matched";

                if (isMatchFound)
                {
                    _matchID = responseJson.GetJSON("data").GetString("MatchId");
                    OnStatusUpdate?.Invoke($"Ticket Has Been Matched, ID #{_matchID}", false);

                    StartConnectionAttempt();
                }
                else if (ticketStatus == "Canceled")
                {
                    isMatchFound = true;
                    string reason = responseJson.GetJSON("data").GetString("CancellationReason");

                    OnUpdateButton?.Invoke(true);
                    OnStatusUpdate?.Invoke($"Ticket Has Been Cancelled Due To {reason}", false);
                }
                else
                {
                    OnStatusUpdate?.Invoke("Match Not Found, Retrying In 10 Seconds...", false);
                    await Task.Delay(10000);
                }

            }
            else
            {
                isMatchFound = true;
                OnUpdateButton?.Invoke(true);
                OnStatusUpdate?.Invoke($"Could Not Get Ticket Info, Error {(int)_request.StatusCode} With Message: \n{_response}", true);
            }
        }
    }

    private async void StartConnectionAttempt()
    {
        bool gotConnectionInfo = false;

        var requestJson = new JSON();
        requestJson.Add("Keys", new List<string> { _matchID });

        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);

        while (!gotConnectionInfo)
        {
            _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Client/GetUserData", _requestData);
            _response = await _request.Content.ReadAsStringAsync();

            if (_request.IsSuccessStatusCode)
            {
                var responseJson = JSON.ParseString(_response);
                var dataObject = responseJson.GetJSON("data").GetJSON("Data");

                if (dataObject.Count > 0)
                {
                    gotConnectionInfo = true;
                    string connectionInfo = dataObject.GetJSON(_matchID).GetString("Value");
                    string serverAddress = connectionInfo.Split(':')[0];

                    if (ushort.TryParse(connectionInfo.Split(':')[1], out ushort serverPort))
                    {
                        InstanceFinder.TransportManager.Transport.SetPort(serverPort);
                        InstanceFinder.TransportManager.Transport.SetClientAddress(serverAddress);
                        Debug.Log($"Connecting To Server Using Port {serverPort} And IP {serverAddress}");
                        InstanceFinder.ClientManager.StartConnection();
                    }
                    else
                    {
                        OnUpdateButton?.Invoke(true);
                        OnStatusUpdate?.Invoke($"Error While Parsing Server Port Value", true);
                    }
                }
                else
                {
                    OnStatusUpdate?.Invoke("No Connection Data Found, Retrying In 10 Seconds...", false);
                    await Task.Delay(10000);
                }
            }
            else
            {
                gotConnectionInfo = true;
                OnUpdateButton?.Invoke(true);
                OnStatusUpdate?.Invoke($"Could Not Get Player Data, Error {(int)_request.StatusCode} With Message: \n{_response}", true);
            }
        }
    }

    public async void RemovePlayerData()
    {
        var requestJson = new JSON();
        requestJson.Add("KeysToRemove", new string[] { _matchID });

        _requestData = new StringContent(requestJson.CreateString(), Encoding.UTF8, TypeHeaderValue);
        _request = await _playfabHttpClient.PostAsync($"https://{TitleID}.playfabapi.com/Client/UpdateUserData", _requestData);
        _response = await _request.Content.ReadAsStringAsync();

        if (!_request.IsSuccessStatusCode)
        {
            Debug.Log($"Could Not Update PlayerData, Error {(int)_request.StatusCode} With Message: \n{_response}");
        }
    }

    ...
}
```

#### PlayerDataUpdateService

```cs
public class PlayerDataUpdateService : NetworkBehaviour
{
    private PlayfabRestApiService _playfabService;

    ...

    public override void OnStartClient()
    {
        _playfabService = FindObjectOfType<PlayfabRestApiService>();
        _playfabService.RemovePlayerData();
    }
}
```

### Сборка игрового сервера и контейнеризация

Перед контейнеризацией сборки сервера убедитесь, что включена опция `Start On Headless` в `NetworkManager` gameObject.

Откройте плагин Edgegap через меню панели инструментов `Tools/Edgegap Hosting` . Проверьте ваш `Edgegap API Token` и либо создайте, либо загрузите приложение для игры. Убедитесь, что `порт` значение совпадает со значением компонента Tugboat у `NetworkManager` gameObject. Выберите `UDP` protocol, затем введите `New Version Tag`. Убедитесь, что и название приложения, и версия совпадают со значениями, которые использовались при настройке Cloud Script.

Когда всё будет правильно настроено, нажмите `Build and Push`, что автоматически контейнеризирует ваш игровой сервер и создаст новую версию приложения на панели Edgegap после короткого ожидания.

{% hint style="info" %}
При создании версии приложения плагин по умолчанию назовёт порт \`"Game Port"\`.

Если вы решите вручную контейнеризировать и создать версию приложения вместо использования плагина, убедитесь, что используете это имя при выборе порта.
{% endhint %}

### Тестирование

Сначала убедитесь, что отключили `Start On Headless` в `NetworkManager` gameObject. Установите `PlayfabRestApiService` скрипт со значением `Custom ID` и `Title ID` вашего первого игрока, а также `Title ID` и `Queue Name`вашей игры. `В`Build Settings

создайте новую клиентскую сборку с этими настройками. `Custom ID` и `Title ID` После завершения сборки измените

значения на значения вашего второго игрока. При желании вы можете создать отдельную клиентскую сборку, хотя редактор Unity в режиме Play также должен работать. `Start Game` Если оба клиента корректно войдут при запуске, нажмите на кнопку


---

# Agent Instructions: 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:

```
GET https://docs.edgegap.com/ru/docs/tools-and-integrations/playfab-bridge.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
