Saltar al contenido principal
Bloques de construcción

Esta página aún no está traducida: se muestra la versión en inglés.

Core

Core es el único objeto que creas tú, y todo lo demás cuelga de él. Le pasas una clave y ya tienes contexto, identidad, errores tipados, trazas y lotes. Cada llamada a un módulo pasa por Core; ningún módulo tiene su propia copia.

Cuándo usarlo

  • Necesitas saber quién eres y dónde estás: identidad, roles, módulos desbloqueados, proyecto, entorno y región.
  • Una función en la nube tiene que escribir como un jugador (player). La escritura se atribuye a ese jugador, y el registro menciona tanto a la función como al jugador.
  • Un reintento no debe aplicarse dos veces: las operaciones por lotes llevan una clave de idempotencia (idempotency key).
  • Quieres ramificar la lógica según el error y poder buscarlo: cada excepción es un Problem tipado con un código estable.
  • No lo uses para mensajes, llamadas ni estado. Para eso están las primitivas: Events, RPC y Data & Subscriptions, a las que accedes a través de Entity; sus destinatarios son los Groups. Las subidas y descargas de archivos están en Files & UGC, y el montaje y los espacios de nombres, en Bajo el capó.

Quién hace qué

ActorQué hace en esta página
cualquier actorconsulta su identidad, el contexto y sus roles con Whoami
backend-serviceactúa como un jugador; agrupa operaciones idempotentes en lotes
operatorlee las trazas de las llamadas fallidas o reintentadas

En resumen

Whoami, el contexto implícito y un lote que se puede reintentar sin riesgo
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")
Próximamente — Go

Todavía no está decidido cómo declarar con atributos en Go (O-1). Los ejemplos llegarán cuando se elija la forma de escribirlo.

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 — funciona tal cual en Unity con los tipos generados
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");
});

La identidad vive en Core, así que ninguna llamada recibe un parámetro session o ctx.

El modelo

Qué trae cada error.

CampoQué es
codeel nombre del rechazo, legible por máquina. Estable: reutiliza los códigos que la plataforma ya tiene en lugar de inventar nuevos
categoryla clase del rechazo; te dice si tiene sentido reintentar
identificador de trazael id de esta ocurrencia concreta. Siempre está presente, también en los errores locales, así que soporte nunca necesita reproducir el problema
explanationtexto para que lo lea una persona. No es estable: puede cambiar o traducirse en cualquier momento
errores por campoen los rechazos de validación: campo, código y mensaje de cada campo rechazado

Ramifica según el código, no según el texto

Usa code y category en tu lógica. explanation es para personas y cambia sin aviso. Si un binding expone solo el mensaje, ese binding tiene un bug.

Tres orígenes.

OrigenQué pasó
platformla plataforma rechazó la llamada, con un código de su catálogo
localel SDK la rechazó antes de enviarla, con un código de su propia lista publicada
unknownla llamada se envió y no llegó respuesta, así que el resultado es desconocido

Lo que vale para cualquier rechazo.

  • Una operación rechazada no aplicó nada, así que la rama de error no necesita una lectura para compensar. Hay dos excepciones, y se indican donde aparecen: un timeout, cuyo resultado es desconocido, y un lote que procesa cada elemento por separado.
  • El error es el mismo sin importar cómo te llegue: mismo código, categoría y origen, ya sea que el binding lance una excepción, devuelva un resultado o invoque el callback de una suscripción.
  • Un timeout es el origen unknown, no una respuesta. Qué hacer ante él se declara en cada operación.

Errores

La misma llamada, rechazada. rate_limited también dice cuándo se permite reintentar
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
Próximamente — Go

Todavía no está decidido cómo declarar con atributos en Go (O-1). Los ejemplos llegarán cuando se elija la forma de escribirlo.

// 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 — funciona tal cual en Unity con los tipos generados
try { await PlayServ.Inventory.Grant(playerId, "starter.pack"); }
catch (Problem p) when (p.Code == "rate_limited")
{
    Hud.RetryAt(p.RetryAfter);
}

Quien llama sin el rol recibe un rechazo, no un servicio reducido. Para un jugador o una sesión de cliente, las filas fn y adm fallan: llamar a actuar como jugador, emitir una traza o leerla devuelve un Problem con el código forbidden. La credencial es válida; lo que pasa es que sus roles no incluyen ese derecho. Reintentar con la misma clave de idempotencia no cambia nada.

Límites

No reintentes una llamada aceptada

Los límites se aplican en la admisión. Una llamada aceptada ya pasó el límite y se va a ejecutar, por mucho que espere. El rechazo de una llamada posterior no la afecta, así que reintentar la llamada aceptada hace el trabajo dos veces.

Te enteras de un límite cuando te rechazan. El SDK no expone el valor vigente, el margen que te queda ni un aviso de que estás cerca, y los jugadores nunca ven los límites. El rechazo trae lo que necesitas:

  • la categoría, que dice si tiene sentido reintentar
  • de quién era el límite
  • cuándo se permite reintentar, y en qué ventana se cuenta