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_idtant 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
| Acteur | Ce qu’il fait sur cette page |
|---|---|
schema-author | marque des entités comme appartenant au joueur et déclare lesquelles forment le profil |
player | lit son propre profil ; les écritures vont directement aux entités |
room-visitor | lit 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-sideLa 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[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.
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 leavesconst 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 leavesmine = 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 leavesLa 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.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 leavesLe modèle
| Notion | Ce que c’est |
|---|---|
player_id | tout ce que la plateforme entend par joueur, plus le profil système qui se trouve derrière — Auth & Players |
| ensemble du profil | les entités appartenant au joueur que le projet déclare comme son profil ; cette déclaration est facultative |
| sélection par propriétaire | la 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 publique | cette 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.
| Toujours | Ce que c’est |
|---|---|
il n'existe pas d'enregistrement de profil | il 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ées | modifiez 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 publique | une vue n’offre rien où écrire, et l’état partagé modifiable passe par du code serveur |
la propriété est un prédicat | et 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 hook | c’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 —
fnouadm. 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.