Batch requests

You can batch your requests when you call UGS services so they are executed in parallel.

You can use the Task.WhenAll method to call UGS services in parallel. This method returns a Task that completes when all the tasks in the argument list complete.

Note: All the failed requests are logged. You can find them in the Cloud Code logs in the Unity Cloud Dashboard. To learn more about Logging, refer to Logging.

Note: If some requests fail, the successful requests are not rolled back.

Call and return results

You can use the sample below to call services and return the results of the requests.

The sample below makes calls to the Leaderboards and Economy services. These calls fail if you don't have a leaderboard or currency configured in your project. This allows you to test the error handling in your Cloud Code modules.

The method returns a dictionary with the results of the requests with the signature of the method that made the request as the key, and the result of the request as the value. If a request fails, it notes which request failed. These failures are also logged.

C#

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;
using Unity.Services.CloudSave.Model;
using Unity.Services.Economy.Model;

namespace BatchingRequests;

public class BatchingRequests
{
    private const string ExperienceKey = "experience";
    private const string ProgressXpKey = "progressXP";
    private const string FailedMessage = "Failed. Check logs.";

    private readonly ILogger<BatchingRequests> _logger;
    public BatchingRequests(ILogger<BatchingRequests> logger)
    {
        _logger = logger;
    }

    [CloudCodeFunction("HandleMultipleRequests")]
    public async Task<Dictionary<string, object>> HandleMultipleRequests(IGameApiClient gameApiClient, IExecutionContext ctx)
    {
        async Task<object>  GetCloudSaveData()
        {
           var res = await gameApiClient.CloudSaveData.GetItemsAsync(ctx, ctx.AccessToken, ctx.ProjectId, ctx.PlayerId, new List<string> { ExperienceKey, ProgressXpKey });
           return res.Data;
        }

        async Task<object>  SetCloudSaveData()
        {
            var res = await gameApiClient.CloudSaveData.SetItemAsync(ctx, ctx.AccessToken, ctx.ProjectId, ctx.PlayerId, new SetItemBody(ExperienceKey, 1));
            return res.Data;
        }
        async Task<object>  GetLeaderboardScore()
        {
            const string leaderboardId = "leaderboard";
            var res = await gameApiClient.Leaderboards.GetLeaderboardPlayerScoreAsync(ctx, ctx.AccessToken, Guid.Parse(ctx.ProjectId), leaderboardId, ctx.PlayerId);
            return res.Data;
        }

        async Task<object>  GetEconomyConfig()
        {
            var res = await gameApiClient.EconomyConfiguration.GetPlayerConfigurationAsync(ctx, ctx.AccessToken, ctx.ProjectId, ctx.PlayerId);
            return res.Data;
        }

        async Task<object> SetEconomyConfig()
        {
            const string currencyId = "NOT_REAL";
            var res = await gameApiClient.EconomyCurrencies.IncrementPlayerCurrencyBalanceAsync(ctx, ctx.AccessToken, ctx.ProjectId, ctx.PlayerId, currencyId, new CurrencyModifyBalanceRequest(currencyId, 1));
            return res.Data;
        }

        // Create a list of UGS tasks that return data
        var tasksWithObjects = new List<Func<Task<object>>>() { GetCloudSaveData, GetLeaderboardScore, SetCloudSaveData, GetEconomyConfig, SetEconomyConfig };
        var results = await AwaitBatch(tasksFns);

        foreach (var item in results)
        {
            if (item is Exception)
            {
                _logger.LogError($"AwaitBatch failed status {item}");
            }
            else if (item is CurrencyBalanceResponse)
            {
                _logger.LogInformation($"AwaitBatch CurrencyBalanceResponse status {item.GetType()} = {item}");
            }
            else
            {
                _logger.LogInformation($"AwaitBatch success status {item.GetType()} = {item}");
            }
        }
        // Alternatively to looping through all results, you may access function results directly
        // The order of function results follows the order of the function input e.g.
        // - GetCloudSaveData corresponds to results[0]: GetItemsResponse || Exception
        // - GetLeaderboardScore corresponds to results[1]: LeaderboardEntryWithUpdatedTime || Exception
        // ... etc
        // This can also help ascertain where the Exception originated from
    }

    private async Task<object[]> AwaitBatch(List<Func<Task<object>>> tasks)
    {
        var tasksToRun = tasks.Select(Run);
        var results = await Task.WhenAll(tasksToRun);
        return results;
    }

    async Task<object> Run(Func<Task<object>> fn)
    {
        try
        {
            return await fn();
        }
        catch (Exception e)
        {
            return e;
        }
    }
}

public class ModuleConfig : ICloudCodeSetup
{
    public void Setup(ICloudCodeConfig config)
    {
        config.Dependencies.AddSingleton(GameApiClient.Create());
    }
}