Перейти к основному содержимому
Сервисы вокруг игры

Эта страница ещё не переведена — показана английская версия.

Profile

Профиль — это представление, и платформе в нём почти ничего не принадлежит. Об игроке платформа хранит только player_id и системный профиль за ним: учётные записи, сессии, привязки к провайдерам — всё это описано в Auth & Players. Всё, что у игрока есть, — ваши собственные сущности, которыми владеет этот игрок. Профиль — это набор таких сущностей, объявленный в проекте и прочитанный для одного владельца за один проход.

Когда это нужно

  • Экрану нужен срез данных одного игрока (player) за один вызов. Чтение по объявленному набору само обходит все принадлежащие игроку сущности (entity), и клиенту не приходится склеивать несколько запросов.
  • Интерфейсы платформы должны показывать человека, а не идентификатор. Таблица лидеров, очередь модерации и тикет поддержки хранят только player_id, пока проект не укажет запись, по которой игрок отображается.
  • Нужна карточка другого игрока. Это то же чтение, но для другого владельца, суженное предикатом строк и маской столбцов, которые уже объявлены в Access & Roles.
  • HUD должен следить за состоянием игрока вживую: чтение профиля — это выборка (selection), а на выборку можно подписаться.
  • Не нужен, если данные не принадлежат игроку. Общие и глобальные строки читаются обычной выборкой Entity: у них нет владельца, от которого можно было бы начать обход.

Кто что делает

АкторЧто делает на этой странице
schema-authorпомечает сущности как принадлежащие игроку и объявляет, какие из них составляют профиль
playerчитает свой профиль, а записывает напрямую в сами сущности
room-visitorчитает профиль другого игрока в тех пределах, которые позволяют предикат и маска этого игрока

Коротко

Принадлежность к профилю объявляется для сущности целиком, а не для отдельных полей. Сущность сообщает, что входит в профиль, а что из неё увидит другой игрок, решает маска столбцов у читающей роли (Access & Roles).

loadout и progress помечены как принадлежащие игроку и включены в набор профиля
[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
Скоро — Go

Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.

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 — те же атрибуты компилируются в Unity на базовой версии 2021.3 — синтаксиса 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
}

Чтение профиля — это выборка по владельцу: тот же интерфейс запросов Entity, только владелец зафиксирован, а список сущностей взят из объявления. Profile — имя этого чтения, а не отдельный модуль за ним. Права, предикаты, фильтры и подписка (subscription) здесь те же самые, потому что и операция та же.

Один проход по своим строкам с живым обновлением, затем строки соперника — в пределах маски
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
Скоро — Go

Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.

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 — работает в Unity без изменений, со сгенерированными типами
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

Модель

ПонятиеЧто это
player_idвсё, что платформа знает об игроке, плюс системный профиль за ним — см. Auth & Players
набор профиляпринадлежащие игроку сущности, которые проект объявил своим профилем. Объявлять набор необязательно
выборка по владельцусамо чтение: на входе один владелец, на выходе его строки по всему набору. Права, предикаты, фильтры и подписка те же, что у любой выборки Entity
публичное чтениета же выборка для другого владельца, суженная предикатом строк и маской столбцов, которые задаёт читающая роль (role), — см. Access & Roles

Что верно для любого чтения профиля.

ВсегдаЧто это
записи профиля не существуету профиля нет собственного идентификатора, ревизии, истории и жизненного цикла, потому что это представление над строками, у которых всё это есть
запись идёт туда, где лежат данныеобновите строку progress — и каждое чтение профиля, в которое она входит, увидит новое значение при следующем проходе
публичной записи нетв представление нечего записывать, а общее изменяемое состояние меняют через серверный код
владение — это предиката не столбец, который добавляет платформа (Access & Roles). owner == caller.player — лишь один вариант этого механизма, «один из участников» — другой
производное поле — забота хукакогда Level переходит порог, Progress.Title проставляет наблюдатель, который срабатывает после изменения. Он идёт следом за записью и отклонить её не может

Ошибки

  • Если в проекте не объявлен набор профиля, то и чтения профиля нет: вызов отклоняется как unavailable. Пустой результат здесь не возвращается — он означал бы, что профиль у игрока есть, просто пустой.
  • Строка, скрытая предикатом, отвечает not found — так же, как несуществующая. Иначе через публичное чтение можно было бы узнать, что существует, но не видно.
  • Поле, не попавшее в маску читающей роли, в ответе отсутствует, а не приходит пустым.
  • Запись от имени игрока, в которой игрок не указан, — отказ валидации.
  • Объявить набор профиля — значит изменить схему (schema), для этого нужен fn или adm. Попытка с ключом игрока отклоняется как forbidden, и вся загрузка отклоняется целиком, а не применяется частично.

Ограничения

Конкретные числа появятся вместе с главой об ограничениях платформы, а поведение на каждой границе уже определено.

  • Размер страницы выборки по владельцу: результат обрезается до предела, а флаг «есть ещё» остаётся выставленным. Вернуть меньше строк без этого флага запрещено.
  • Размер включённого набора в одной строке: обрезается по тому же правилу, с флагом на включении.
  • Частота изменений одного экземпляра: отказ по частоте со сроком повтора. Чтение профиля — такая же выборка, как любая другая, поэтому оно наследует ограничения сущностей, а не объявляет свои.