Cette page n’est pas encore traduite : la version anglaise est affichée.
Core
Core est le seul objet que vous créez ; tout le reste en découle. Vous lui passez une clé, et vous disposez du contexte, de l’identité, d’erreurs typées, du traçage et des lots. Chaque appel de module passe par lui ; aucun module n’en possède sa propre copie.
Quand l’utiliser
- Vous devez savoir qui vous êtes et où vous êtes : identité, rôles, modules débloqués, projet, environnement et région.
- Une fonction cloud doit écrire en tant que joueur. L’écriture est attribuée à ce joueur, et l’enregistrement mentionne à la fois la fonction et le joueur.
- Une nouvelle tentative ne doit pas s’appliquer deux fois : les opérations par lot (batch) portent une clé d’idempotence.
- Vous voulez aiguiller votre code selon l’erreur et pouvoir la retrouver : chaque exception est un
Problemtypé, avec un code stable. - Inutile si vous cherchez la messagerie, les appels ou l’état. Ce sont les primitives : Events, RPC et Data & Subscriptions, qui s’adressent à une Entity ; leurs audiences sont les Groups. Les envois et téléchargements de fichiers relèvent de Files & UGC, le montage et les espaces de noms de Sous le capot.
Qui fait quoi
| Acteur | Ce qu’il fait sur cette page |
|---|---|
tout acteur | lit son identité, le contexte et ses rôles via Whoami |
backend-service | agit en tant que joueur ; regroupe des opérations idempotentes en lots |
operator | lit les traces des appels échoués ou relancés |
En bref
Whoami, le contexte ambiant et un lot qu’on peut relancer sans risquevar 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")La déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.
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");
});L’identité vit dans Core : aucun appel ne prend de paramètre session ou ctx.
Le modèle
Ce que contient chaque erreur.
| Champ | Ce que c’est |
|---|---|
code | le nom du refus, lisible par une machine. Stable : il reprend les codes existants de la plateforme au lieu d’en inventer de nouveaux |
category | la classe du refus ; elle vous dit si une nouvelle tentative a un sens |
identifiant de trace | l’id de cette occurrence précise. Toujours présent, erreurs locales comprises : le support n’a jamais besoin de reproduire le problème |
explanation | un texte destiné à un humain. Pas stable : il peut changer ou être traduit à tout moment |
erreurs par champ | pour les refus de validation : le champ, le code et le message de chaque champ rejeté |
Décidez selon le code, pas selon le texte
Mettez code et category dans votre logique. explanation s’adresse aux humains et change sans préavis. Un binding qui n’expose que le message est bogué.
Trois origines.
| Origine | Ce qui s’est passé |
|---|---|
platform | la plateforme a refusé, avec un code tiré de son catalogue |
local | le SDK a refusé avant l’envoi, avec un code tiré de sa propre liste publiée |
unknown | l’appel a été envoyé et aucune réponse n’est revenue : le résultat est inconnu |
Vrai pour tout refus.
- Une opération refusée n’a rien appliqué : une branche d’erreur n’a donc pas besoin de relire l’état pour compenser. Les deux exceptions le signalent là où elles se produisent : un timeout, dont le résultat est inconnu, et un lot dont la sémantique s’applique élément par élément.
- L’erreur est la même quel que soit le chemin par lequel elle vous parvient : même code, même catégorie, même origine, que le binding lève une exception, renvoie un résultat ou rappelle un abonnement.
- Un timeout correspond à l’origine
unknown, pas à une réponse. La conduite à tenir est déclarée opération par opération.
Erreurs
rate_limited indique aussi quand une nouvelle tentative est permisetry { 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:
raiseLa déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.
// 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);
}Un appelant sans le rôle est refusé, pas servi a minima. Pour une session joueur ou client, les lignes fn et adm échouent : appeler act as player, émettre une trace ou en lire une renvoie un Problem de code forbidden. L’identifiant est valide ; ce sont ses rôles qui ne donnent pas ce droit. Relancer avec la même clé d’idempotence n’y change rien.
Limites
Ne relancez pas un appel accepté
Les limites s’appliquent à l’admission. Un appel accepté a passé la limite et s’exécutera, quel que soit son temps d’attente. Le refus d’un appel ultérieur ne l’affecte pas : relancer l’appel accepté fait le travail deux fois.
Vous découvrez une limite en étant refusé. Le SDK n’expose ni la valeur en vigueur, ni la marge restante, ni d’avertissement à l’approche du seuil, et les joueurs ne voient jamais les limites. Le refus contient ce qu’il vous faut :
- la catégorie, qui dit si une nouvelle tentative a un sens
- à qui appartenait la limite
- quand une nouvelle tentative est permise, et sur quelle fenêtre