Pular para o conteúdo principal
Serviços em torno do jogo

Esta página ainda não foi traduzida: mostrando a versão em inglês.

Profile

Um perfil é uma visão, e quase nada nele pertence à plataforma. Sobre um jogador, a plataforma guarda o player_id e o perfil de sistema por trás dele — identidades, sessões, vínculos com provedores, tudo isso em Auth & Players. Tudo o que o jogador tem é uma entidade sua, que pertence a esse jogador. O perfil é o conjunto dessas entidades que o seu projeto declara, lido para um dono de uma só vez.

Quando usar

  • Uma tela precisa da fatia de um jogador numa só chamada: o conjunto declarado se distribui pelas entidades desse jogador, em vez de o cliente costurar várias consultas.
  • As telas da plataforma precisam mostrar uma pessoa, não um identificador: um ranking (leaderboard), uma fila de moderação e um ticket de suporte guardam só um player_id até o projeto indicar qual registro exibe o jogador.
  • Outro jogador precisa de um cartão: é a mesma leitura para outro dono, restringida pelo predicado de linhas e pela máscara de campos já declarados em Access & Roles.
  • Um HUD precisa acompanhar em tempo real o estado do jogador: a leitura é uma seleção, e uma seleção faz assinatura (subscription).
  • Dispensável quando os dados não pertencem a um jogador. Linhas compartilhadas e globais são uma seleção comum de Entity, sem um dono a partir do qual distribuir a leitura.

Quem faz o quê

AtorO que faz nesta página
schema-authormarca entidades como pertencentes ao jogador e declara quais delas formam o perfil
playerlê o próprio perfil; as escritas vão direto para as entidades
room-visitorlê o perfil de outro jogador, até onde o predicado e a máscara desse jogador permitem

Resumo

A participação no perfil é declarada por entidade, não por campo. A entidade diz que faz parte do perfil; o que outro jogador pode ver dela é definido pela máscara de campos do papel (role) que faz a leitura (Access & Roles).

loadout e progress marcados como pertencentes ao jogador e incluídos no conjunto do 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
Em breve — Go

Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.

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 — os mesmos atributos compilam na Unity, a partir da versão base 2021.3 — sem sintaxe de C# 12 aqui
[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
}

A leitura é uma seleção restrita a um dono: a interface de consulta de Entity, com o dono fixo e a lista de entidades tirada da declaração. Profile é o nome dessa leitura, não um módulo por trás dela: os mesmos direitos, os mesmos predicados, os mesmos filtros, a mesma assinatura, porque é a mesma operação.

Uma leitura das minhas próprias linhas, em tempo real; depois das de um rival, até onde a máscara permite
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
Em breve — Go

Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.

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 — roda sem alterações na Unity com os tipos gerados
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

O modelo

ConceitoO que é
player_idtudo o que a plataforma entende por jogador, mais o perfil de sistema por trás dele — Auth & Players
conjunto do perfilas entidades do jogador que o projeto declara como perfil; declarar esse conjunto é opcional
seleção por donoa leitura: entra um dono, saem as linhas dele em todo o conjunto — com os mesmos direitos, predicados, filtros e assinatura de qualquer seleção de Entity
leitura públicaessa mesma seleção para outro dono, restringida pelo predicado de linhas e pela máscara de campos do papel que lê (Access & Roles)

O que vale para toda leitura de perfil.

SempreO que é
não existe registro de perfilo perfil não tem identificador próprio, revisão, histórico nem ciclo de vida, porque é uma visão (view) sobre linhas que têm tudo isso
a escrita vai para onde os dados estãoaltere a linha progress, e toda leitura de perfil que a inclui vê o novo valor na próxima vez
não existe escrita públicauma visão não tem onde receber escrita, e o estado compartilhado que pode ser alterado passa pelo código do servidor
a posse é um predicadoe não uma coluna que a plataforma acrescenta (Access & Roles): owner == caller.player é um caso desse mecanismo, “um dos participantes” é outro
campo derivado é trabalho de hooké um observador pós-alteração que grava Progress.Title quando Level passa de um limite. Ele vem depois da escrita; não pode recusá-la

Erros

  • Um projeto sem conjunto de perfil declarado não tem leitura de perfil, e a chamada é recusada como unavailable — e não respondida com um resultado vazio, que daria a entender que o jogador tem um perfil e ele só está em branco.
  • Uma linha escondida por um predicado responde not found, assim como uma que não existe: a leitura pública nunca vira um jeito de descobrir o que existe mas não é visível.
  • Um campo fora da máscara do papel que lê fica ausente da resposta, e não presente e vazio.
  • Uma escrita em nome de um jogador que não indica o jogador é uma recusa de validação.
  • Declarar o conjunto do perfil é um ato de esquema — fn ou adm. Se uma chave de jogador tentar fazer isso, recebe forbidden, e a publicação inteira é rejeitada, em vez de ser declarada pela metade.

Limites

Os números chegam com o capítulo de limites da plataforma; o que acontece em cada teto já está definido.

  • Tamanho da página da seleção por dono: cortado no teto, com a flag “há mais” ainda verdadeira; devolver menos sem a flag é proibido.
  • Tamanho de um conjunto incluído por linha: cortado pela mesma regra, com a flag na inclusão.
  • Frequência de alterações numa instância: uma recusa de rate limit com prazo. A leitura de perfil é uma seleção como qualquer outra e herda os tetos de Entity, em vez de declarar os próprios.