Pular para o conteúdo principal
Blocos de construção

Esta página ainda não foi traduzida: mostrando a versão em inglês.

Core

Core é o único objeto que você cria, e todo o resto depende dele. Você passa uma chave e recebe contexto, identidade, falhas tipadas, rastreamento e lotes. Toda chamada de módulo passa por ele; nenhum módulo tem uma cópia própria.

Quando usar

  • Você precisa saber quem você é e onde está: identidade, papéis (role), módulos liberados, projeto, ambiente e região.
  • Uma cloud function precisa gravar em nome de um jogador. A gravação é atribuída a esse jogador, e o registro indica tanto a função quanto o jogador.
  • Uma nova tentativa não pode ser aplicada duas vezes: operações em lote (batch) levam uma chave de idempotência.
  • Você quer tratar cada falha por tipo e conseguir encontrá-la depois: toda exceção é um Problem tipado, com um código estável.
  • Dispensável para mensagens, chamadas e estado. Isso fica com as primitivas: Events, RPC e Data & Subscriptions, endereçadas por meio de Entity; os públicos delas são os Groups. Uploads e downloads ficam em Files & UGC; montagem e namespaces, em Por baixo dos panos.

Quem faz o quê

AtorO que faz nesta página
qualquer atorlê identidade, contexto e papéis via Whoami
backend-serviceage como um jogador; agrupa operações idempotentes em lotes
operatorlê os traces de chamadas que falharam ou foram repetidas

Resumo

Whoami, o contexto do ambiente e um lote que pode ser repetido com segurança
var me  = PlayServ.Whoami();              // identity, roles, unlocked modules
var env = PlayServ.Context;               // project · env · region

// retries never double-apply: the batch carries an idempotency key
await PlayServ.Batch(key: orderId, b =>
{
    b.Inventory.Grant(playerId, "starter.pack");
    b.Inventory.Grant(playerId, "starter.emote");
});
const me = playserv.whoami();             // identity, roles, unlocked modules
const env = playserv.context;             // project · env · region

// retries never double-apply: the batch carries an idempotency key
await playserv.batch(orderId, (b) => {
  b.inventory.grant(playerId, 'starter.pack');
  b.inventory.grant(playerId, 'starter.emote');
});
me = playserv.whoami()                    # identity, roles, unlocked modules
env = playserv.context                    # project · env · region

# retries never double-apply: the batch carries an idempotency key
async with playserv.batch(key=order_id) as b:
    b.inventory.grant(player_id, "starter.pack")
    b.inventory.grant(player_id, "starter.emote")
Em breve — Go

Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.

const FPSActor Me = Client->Whoami();               // identity, roles, unlocked modules
const FPSPlatformContext Env = Client->Context();   // project · env · region

// retries never double-apply: each keyed operation is safe to repeat
Client->Commerce->Entitlements->Grant(FPSIdempotencyKey(PackGrantId), PlayerId, PSKeys::Item::StarterPack);
Client->Commerce->Entitlements->Grant(FPSIdempotencyKey(EmoteGrantId), PlayerId, PSKeys::Item::StarterEmote);
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.
Unity C# is the same C# API here — roda sem alterações no Unity com os tipos gerados
var me  = PlayServ.Whoami();              // identity, roles, unlocked modules
var env = PlayServ.Context;               // project · env · region

// retries never double-apply: the batch carries an idempotency key
await PlayServ.Batch(key: orderId, b =>
{
    b.Inventory.Grant(playerId, "starter.pack");
    b.Inventory.Grant(playerId, "starter.emote");
});

A identidade fica no Core, por isso nenhuma chamada recebe um parâmetro session ou ctx.

O modelo

O que todo erro traz.

CampoO que é
codeo nome da recusa, legível por máquina. Estável: reaproveita os códigos que a plataforma já tem em vez de inventar novos
categorya classe da recusa; diz se faz sentido tentar de novo
identificador de traceo id desta ocorrência. Sempre presente, inclusive em erros locais, então o suporte nunca precisa reproduzir o problema
explanationtexto para uma pessoa ler. Não é estável: muda e é localizado a qualquer momento
erros por campoem recusas de validação: campo, código e mensagem de cada campo rejeitado

Decida pelo código, não pelo texto

Use code e category na sua lógica. explanation é para pessoas e muda sem aviso. Um binding que expõe só a mensagem tem um bug.

Três origens.

OrigemO que aconteceu
platforma plataforma recusou, com um código do catálogo dela
localo SDK recusou antes de enviar, com um código da própria lista publicada
unknowna chamada foi enviada e nenhuma resposta voltou, então o resultado é desconhecido

Vale para toda recusa.

  • Uma operação recusada não aplicou nada, então o tratamento do erro não precisa de uma leitura para compensar. As duas exceções avisam onde aparecem: um timeout, cujo resultado é desconhecido, e um lote com semântica por elemento.
  • O erro é o mesmo, chegue como chegar: mesmo código, mesma categoria e mesma origem, seja o binding lançando uma exceção, devolvendo um resultado ou chamando um callback de uma assinatura.
  • Um timeout é a origem unknown, não uma resposta. O que fazer nesse caso é declarado em cada operação.

Erros

A mesma chamada, recusada. rate_limited também diz quando é permitido tentar de novo
try { await PlayServ.Inventory.Grant(playerId, "starter.pack"); }
catch (Problem p) when (p.Code == "rate_limited")
{
    Hud.RetryAt(p.RetryAfter);
}
try { await playserv.inventory.grant(playerId, 'starter.pack'); }
catch (p) {
  if (Problem.code(p) === 'rate_limited') hud.retryAt(p.retryAfter);
  else throw p;
}
try:
    await playserv.inventory.grant(player_id, "starter.pack")
except Problem as p:
    if p.code == "rate_limited":
        hud.retry_at(p.retry_after)
    else:
        raise
Em breve — Go

Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.

// UE builds run without exceptions — the completion carries the result, read explicitly
Client->Commerce->Entitlements->Grant(FPSIdempotencyKey(GrantId), PlayerId, PSKeys::Item::StarterPack,
    TPSOnResult<void>::CreateWeakLambda(this, [this](const TPSResult<void>& Result)
    {
        if (Result.IsRefused() && Result.Refusal().Code == FPSFailureCode::RateLimited)
        {
            Hud->RetryAt(Result.Refusal().RetryNotBefore);   // TOptional<FDateTime> — an instant, not a delay
        }
    }));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.
Unity C# is the same C# API here — roda sem alterações no Unity com os tipos gerados
try { await PlayServ.Inventory.Grant(playerId, "starter.pack"); }
catch (Problem p) when (p.Code == "rate_limited")
{
    Hud.RetryAt(p.RetryAfter);
}

Quem chama sem o papel é recusado, não atendido pela metade. Para uma sessão de jogador ou de cliente, as linhas fn e adm falham: chamar act as player, emitir um trace ou ler um devolve um Problem com o código forbidden. A credencial é válida; os papéis dela é que não dão esse direito. Tentar de novo com a mesma chave de idempotência não muda nada.

Limites

Não repita uma chamada aceita

Os limites valem na admissão. Uma chamada aceita já passou pelo limite e vai rodar, por mais que espere. A recusa de uma chamada posterior não a afeta, então repetir a chamada aceita faz o trabalho duas vezes.

Você descobre um limite quando é recusado. O SDK não expõe o valor em vigor, a folga que resta nem avisa que você está perto, e os jogadores nunca veem limites. A recusa traz o que você precisa:

  • a categoria, que diz se faz sentido tentar de novo
  • de quem era o limite
  • quando uma nova tentativa é permitida, e em qual janela