Saltar al contenido principal
Servicios alrededor del juego

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

Profile

Un perfil es una vista, y casi nada de él le pertenece a la plataforma. De un jugador, la plataforma guarda el player_id y el perfil de sistema que hay detrás: identidades, sesiones, vínculos con proveedores, todo eso en Auth & Players. Todo lo que un jugador tiene es una entidad tuya, cuyo dueño es ese jugador. Un perfil es el conjunto de esas entidades que declara tu proyecto, leído para un solo dueño en una sola pasada.

Cuándo usarlo

  • Una pantalla necesita los datos de un jugador (player) en una sola llamada: la lectura del conjunto declarado recorre todas las entidades que le pertenecen, y el cliente no tiene que combinar varias consultas.
  • Las interfaces de la plataforma tienen que mostrar a una persona, no un identificador: una tabla de clasificación, una cola de moderación y un ticket de soporte guardan un player_id y nada más, hasta que el proyecto indica qué registro representa a un jugador en pantalla.
  • Hace falta la tarjeta de otro jugador: es la misma lectura para otro dueño (owner), acotada por el predicado de filas y la máscara de columnas que ya declaraste en Access & Roles.
  • El HUD tiene que reflejar en vivo el estado que le pertenece al jugador: la lectura es una selección, y a una selección te puedes suscribir.
  • No lo uses si los datos no le pertenecen a un jugador. Las filas compartidas y globales se leen con una selección normal de Entity: no tienen un dueño desde el cual recorrer los datos.

Quién hace qué

ActorQué hace en esta página
schema-authormarca entidades como propiedad del jugador y declara cuáles forman el perfil
playerlee su propio perfil; escribe directamente en las entidades
room-visitorlee el perfil de otro jugador, hasta donde lo permiten el predicado y la máscara de ese jugador

En resumen

La pertenencia al perfil se declara por entidad, no por campo. La entidad indica que forma parte del perfil; lo que otro jugador puede ver de ella lo decide la máscara de columnas del rol que la lee (Access & Roles).

loadout y progress marcadas como propiedad del jugador e incluidas en el conjunto del perfil
[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
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.

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 — los mismos atributos compilan en Unity con la versión base 2021.3: aquí no hay sintaxis de C# 12
[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 lectura es una selección acotada a un dueño: la interfaz de consultas de Entity, con el dueño fijo y la lista de entidades tomada de la declaración. Profile es el nombre de esa lectura, no un módulo aparte que la respalde: mismos permisos, mismos predicados, mismos filtros, misma suscripción, porque es la misma operación.

Una pasada en vivo sobre mis propias filas; después, las de un rival, hasta donde lo permite la máscara
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
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.

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

El modelo

ConceptoQué es
player_idtodo lo que la plataforma entiende por jugador, más el perfil de sistema que hay detrás: Auth & Players
conjunto del perfillas entidades propiedad del jugador que el proyecto declara como su perfil; declararlo es opcional
selección por dueñola lectura en sí: entra un dueño y salen sus filas de todo el conjunto, con los mismos permisos, predicados, filtros y suscripción que cualquier selección de Entity
lectura públicaesa misma selección para otro dueño, acotada por el predicado de filas y la máscara de columnas del rol que lee (Access & Roles)

Lo que se cumple en toda lectura del perfil.

SiempreQué es
no existe un registro de perfilno tiene identificador propio, ni revisión, ni historial, ni ciclo de vida, porque es una vista (view) sobre filas que sí tienen las cuatro cosas
las escrituras van adonde viven los datosmodifica la fila progress, y cada lectura del perfil que la incluye ve el valor nuevo en su siguiente pasada
no hay escritura públicauna vista no tiene dónde escribir, y el estado compartido que se puede modificar pasa por código de servidor
la propiedad es un predicadono una columna que agrega la plataforma (Access & Roles): owner == caller.player es un caso del mecanismo, y “uno de los participantes” es otro
un campo derivado le corresponde a un hookel que escribe Progress.Title cuando Level supera un umbral es un observador que se ejecuta después del cambio. Va detrás de la escritura; no puede rechazarla

Errores

  • Un proyecto sin conjunto de perfil declarado no tiene lectura de perfil, y la llamada se rechaza como unavailable. No se responde con un resultado vacío, porque eso diría que el jugador tiene un perfil y que simplemente está en blanco.
  • Una fila que un predicado oculta responde not found, igual que una que no existe: una lectura pública nunca se convierte en una forma de averiguar qué existe pero no está visible.
  • Un campo que queda fuera de la máscara del rol que lee no aparece en la respuesta; no llega vacío.
  • Una escritura en nombre de un jugador que no indica de qué jugador se trata es un rechazo de validación.
  • Declarar el conjunto del perfil es un cambio de esquema: le corresponde a fn o adm. Si lo intenta una clave de jugador, recibe forbidden, y la subida se rechaza completa en lugar de aplicarse en parte.

Límites

Los números llegarán con el capítulo sobre los límites de la plataforma; lo que pasa al alcanzar cada tope ya está definido.

  • Tamaño de página de la selección por dueño: se recorta al tope y el flag “hay más” sigue en verdadero; está prohibido devolver menos filas sin ese flag.
  • Tamaño de un conjunto incluido por fila: se recorta con la misma regla, con el flag en la inclusión.
  • Frecuencia de cambios de una misma instancia: un rechazo por límite de frecuencia, con plazo. La lectura del perfil es una selección como cualquier otra y hereda los topes de las entidades en lugar de declarar los suyos.