Aller au contenu principal
Services autour du jeu

Cette page n’est pas encore traduite : la version anglaise est affichée.

Profile

Un profil est une vue, et la plateforme n’en possède presque rien. Ce que la plateforme conserve d’un joueur, c’est le player_id et le profil système qui se trouve derrière — identités, sessions, liaisons aux fournisseurs, tout ce que décrit Auth & Players. Tout ce qu’un joueur possède est une entité à vous, dont ce joueur est propriétaire. Un profil, c’est l’ensemble de ces entités que votre projet déclare, lu pour un seul propriétaire en une seule passe.

Quand l’utiliser

  • Un écran a besoin de toute la part d’un joueur en un seul appel : la lecture parcourt l’ensemble déclaré sur toutes les entités qu’il possède, au lieu que le client assemble plusieurs requêtes.
  • Les écrans de la plateforme doivent montrer une personne, pas un identifiant : un classement (leaderboard), une file de modération ou un ticket de support ne contiennent qu’un player_id tant que le projet n’a pas désigné l’enregistrement qui sert à afficher un joueur.
  • Un autre joueur a besoin d’une fiche : c’est la même lecture pour un autre propriétaire, restreinte par le prédicat de lignes et le masque de colonnes déjà déclarés dans Access & Roles.
  • Un HUD doit suivre en direct l’état que possède le joueur : la lecture est une sélection, et on peut s’abonner à une sélection.
  • Inutile quand les données n’appartiennent pas à un joueur. Les lignes partagées et globales relèvent d’une sélection Entity ordinaire, sans propriétaire à partir duquel parcourir quoi que ce soit.

Qui fait quoi

ActeurCe qu’il fait sur cette page
schema-authormarque des entités comme appartenant au joueur et déclare lesquelles forment le profil
playerlit son propre profil ; les écritures vont directement aux entités
room-visitorlit le profil d’un autre joueur, dans la mesure où le prédicat et le masque de ce joueur le permettent

En bref

L’appartenance au profil se déclare par entité, pas par champ. L’entité indique qu’elle fait partie du profil ; ce qu’un autre joueur peut en voir relève du masque de colonnes du rôle qui la lit (Access & Roles).

loadout et progress marqués comme appartenant au joueur et inclus dans l’ensemble du profil
[Entity("loadout"), OwnedBy(Owner.Player), InProfile]
public class Loadout { public string Primary = ""; }

[Entity("progress"), OwnedBy(Owner.Player), InProfile]
public class Progress
{
    public int Level;
    public string Title = "";
    public int SecretMmr;                 // no reading role's mask names it: it stays server-side
}
@Entity('loadout') @OwnedBy(Owner.player) @InProfile()
export class Loadout { primary = ''; }

@Entity('progress') @OwnedBy(Owner.player) @InProfile()
export class Progress {
  level = 0;
  title = '';
  secretMmr = 0;                          // no reading role's mask names it: it stays server-side
}
@entity("loadout")
@owned_by(Owner.PLAYER)
@in_profile
class Loadout:
    primary: str = ""

@entity("progress")
@owned_by(Owner.PLAYER)
@in_profile
class Progress:
    level: int = 0
    title: str = ""
    secret_mmr: int = 0                   # no reading role's mask names it: it stays server-side
Bientôt — Go

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.

UCLASS(PSEntity = "loadout", PSOwnedBy = "Player", PSInProfile)
class ULoadout : public UObject
{
    GENERATED_BODY()

    UPROPERTY() FString Primary;
};

UCLASS(PSEntity = "progress", PSOwnedBy = "Player", PSInProfile)
class UProgress : public UObject
{
    GENERATED_BODY()

    UPROPERTY() int32   Level;
    UPROPERTY() FString Title;
    UPROPERTY() int32   SecretMmr;        // no reading role's mask names it: it stays server-side
};
// declarations compile into the same pushed model — playserv push from the UE project or CI
Unity C# is the same C# API here — les mêmes attributs compilent dans Unity, sur la base 2021.3 — sans syntaxe C# 12 ici
[Entity("loadout"), OwnedBy(Owner.Player), InProfile]
public class Loadout { public string Primary = ""; }

[Entity("progress"), OwnedBy(Owner.Player), InProfile]
public class Progress
{
    public int Level;
    public string Title = "";
    public int SecretMmr;                 // no reading role's mask names it: it stays server-side
}

La lecture est une sélection limitée à un propriétaire : l’interface de requête d’Entity, avec le propriétaire fixé et la liste d’entités tirée de la déclaration. Profile est le nom de cette lecture, pas un module qui se cacherait derrière : mêmes droits, mêmes prédicats, mêmes filtres, même abonnement, puisque c’est la même opération.

Une passe sur mes propres lignes, en direct ; puis sur celles d’un rival, dans la limite du masque
var mine = playserv.Profile.Mine();                       // a selection, not a record
var rows = await mine.Query();                            // loadout + progress, one pass
mine.Subscribe(changed => Hud.Refresh(changed));          // the selection stays live

var rival = await playserv.Profile.Of(rivalId).Query();   // only what the mask leaves
const mine = playserv.profile.mine();                     // a selection, not a record
const rows = await mine.query();                          // loadout + progress, one pass
mine.subscribe((changed) => hud.refresh(changed));        // the selection stays live

const rival = await playserv.profile.of(rivalId).query(); // only what the mask leaves
mine = playserv.profile.mine()                            # a selection, not a record
rows = await mine.query()                                 # loadout + progress, one pass
mine.subscribe(lambda changed: hud.refresh(changed))      # the selection stays live

rival = await playserv.profile.of(rival_id).query()       # only what the mask leaves
Bientôt — Go

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.

TPSSelection<UPSProfile> MyProfile = Client->Entities->Of<UPSProfile>()->Select().GetMine();   // a selection, not a record
MyProfile.Then(TPSOnResult<FPSProfileRows>::CreateWeakLambda(this, [this](const TPSResult<FPSProfileRows>& Result)
{
    if (!Result.HasValue()) { return; }
    Hud->ShowProfile(Result.Value());          // loadout + progress, one pass
}));
TPSSubscription ProfileWatch = MyProfile.Subscribe(
    [this](const FPSProfileChange& Changed) { Hud->Refresh(Changed); });

Client->Entities->Of<UPSProfile>()->Get(RivalId,
    TPSOnResult<FPSProfileRows>::CreateWeakLambda(this, [this](const TPSResult<FPSProfileRows>& Rival)
    {
        if (!Rival.HasValue()) { return; }
        Hud->ShowRival(Rival.Value());
    }));
// 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 — s’exécute tel quel dans Unity avec les types générés
var mine = playserv.Profile.Mine();                       // a selection, not a record
var rows = await mine.Query();                            // loadout + progress, one pass
mine.Subscribe(changed => Hud.Refresh(changed));          // the selection stays live

var rival = await playserv.Profile.Of(rivalId).Query();   // only what the mask leaves

Le modèle

NotionCe que c’est
player_idtout ce que la plateforme entend par joueur, plus le profil système qui se trouve derrière — Auth & Players
ensemble du profilles entités appartenant au joueur que le projet déclare comme son profil ; cette déclaration est facultative
sélection par propriétairela lecture : un propriétaire en entrée, ses lignes sur tout l’ensemble en sortie — mêmes droits, prédicats, filtres et abonnement que n’importe quelle sélection Entity
lecture publiquecette même sélection pour un autre propriétaire, restreinte par le prédicat de lignes et le masque de colonnes du rôle qui lit (Access & Roles)

Ce qui vaut pour toute lecture de profil.

ToujoursCe que c’est
il n'existe pas d'enregistrement de profilil n’a ni identifiant propre, ni révision, ni historique, ni cycle de vie, car c’est une vue (view) sur des lignes qui, elles, ont tout cela
les écritures vont là où vivent les donnéesmodifiez la ligne progress, et chaque lecture de profil qui l’inclut voit la nouvelle valeur à sa passe suivante
il n'y a pas d'écriture publiqueune vue n’offre rien où écrire, et l’état partagé modifiable passe par du code serveur
la propriété est un prédicatet non une colonne que la plateforme ajoute (Access & Roles) : owner == caller.player est une instance de ce mécanisme, « l’un des participants » en est une autre
un champ dérivé relève d'un hookc’est un observateur après modification qui renseigne Progress.Title quand Level franchit un seuil. Il suit l’écriture ; il ne peut pas la refuser

Erreurs

  • Un projet sans ensemble de profil déclaré n’a pas de lecture de profil, et l’appel est refusé comme unavailable — plutôt qu’un résultat vide, qui laisserait entendre que le joueur a un profil et qu’il se trouve être vide.
  • Une ligne masquée par un prédicat répond not found, tout comme une ligne qui n’existe pas : une lecture publique ne devient jamais un moyen d’apprendre ce qui existe sans être visible.
  • Un champ hors du masque du rôle lecteur est absent de la réponse, et non présent et vide.
  • Une écriture au nom d’un joueur qui ne désigne pas ce joueur est un refus de validation.
  • Déclarer l’ensemble du profil est un acte de schéma — fn ou adm. Une clé joueur qui s’y essaie est refusée avec forbidden, et la publication est rejetée en bloc plutôt que déclarée en partie.

Limites

Les valeurs chiffrées arriveront avec le chapitre sur les limites de la plateforme ; ce qui se passe à chaque plafond est déjà fixé.

  • Taille de page de la sélection par propriétaire : tronquée au plafond, avec l’indicateur « il y en a d’autres » toujours à vrai ; renvoyer moins de résultats sans l’indicateur est interdit.
  • Taille d’un ensemble inclus par ligne : tronquée selon la même règle, avec l’indicateur sur l’inclusion.
  • Fréquence des modifications d’une même instance : un refus pour limitation de débit (rate limit), avec un délai. La lecture de profil est une sélection comme une autre : elle hérite des plafonds d’Entity au lieu de déclarer les siens.