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
Problemtipado, 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ê
| Ator | O que faz nesta página |
|---|---|
qualquer ator | lê identidade, contexto e papéis via Whoami |
backend-service | age como um jogador; agrupa operações idempotentes em lotes |
operator | lê os traces de chamadas que falharam ou foram repetidas |
Resumo
Whoami, o contexto do ambiente e um lote que pode ser repetido com segurançavar 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")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.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.
| Campo | O que é |
|---|---|
code | o nome da recusa, legível por máquina. Estável: reaproveita os códigos que a plataforma já tem em vez de inventar novos |
category | a classe da recusa; diz se faz sentido tentar de novo |
identificador de trace | o id desta ocorrência. Sempre presente, inclusive em erros locais, então o suporte nunca precisa reproduzir o problema |
explanation | texto para uma pessoa ler. Não é estável: muda e é localizado a qualquer momento |
erros por campo | em 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.
| Origem | O que aconteceu |
|---|---|
platform | a plataforma recusou, com um código do catálogo dela |
local | o SDK recusou antes de enviar, com um código da própria lista publicada |
unknown | a 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
rate_limited também diz quando é permitido tentar de novotry { 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:
raiseAinda 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.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