Перейти до основного вмісту
Сервіси навколо гри

Цю сторінку ще не перекладено — показано англійську версію.

Profile

Профіль — це подання, і платформі в ньому майже нічого не належить. Про гравця платформа зберігає лише player_id і системний профіль за ним: облікові записи, сесії, прив’язки до провайдерів — усе це в Auth & Players. Усе, що гравець має, — це ваші власні сутності, якими він володіє. Профіль — це набір таких сутностей, який оголошує ваш проєкт і який читають для одного власника за один прохід.

Коли це потрібно

  • Екрану потрібні дані одного гравця за один виклик. Оголошений набір охоплює всі сутності (entity) цього гравця, тож клієнтові не доводиться зшивати кілька запитів.
  • Екрани платформи мають показувати людину, а не ідентифікатор: таблиця лідерів, черга модерації й тікет підтримки зберігають лише player_id, доки проєкт не вкаже запис, яким відображати гравця.
  • Потрібна картка іншого гравця. Це те саме читання, але для іншого власника, звужене предикатом рядків (row predicate) і маскою полів, які вже оголошено в Access & Roles.
  • HUD має стежити за станом гравця наживо: читання — це вибірка (selection), а на вибірку можна підписатися.
  • Не використовуйте його, якщо дані не належать гравцеві. Спільні й глобальні рядки читають звичайною вибіркою Entity: у них немає власника, від якого можна було б розгорнути читання.

Хто що робить

АкторЩо робить на цій сторінці
schema-authorпозначає, які сутності належать гравцеві, і оголошує, які з них входять до профілю
playerчитає власний профіль, а записує безпосередньо в самі сутності
room-visitorчитає профіль іншого гравця в межах, які дозволяють предикат і маска цього гравця

Коротко

Належність до профілю оголошують для сутності цілком, а не для окремих полів. Сутність повідомляє, що входить до профілю, а що з неї побачить інший гравець, визначає маска полів (column mask) ролі, яка її читає (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

Що справджується для будь-якого читання профілю.

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

Помилки

  • Якщо проєкт не оголосив набір профілю, читати профіль нема чого, і виклик відхиляють як unavailable. Порожнього результату не буде: він означав би, що профіль у гравця є, просто порожній.
  • Рядок, який приховує предикат, дає not found, так само як рядок, якого не існує: публічне читання ніколи не допоможе дізнатися, що саме існує, але приховане.
  • Поля поза маскою ролі, яка читає, у відповіді просто немає — воно не приходить порожнім.
  • Запис від імені гравця, у якому гравця не вказано, — відмова валідації.
  • Оголосити набір профілю — це дія над схемою (schema): fn або adm. Спробу з ключем гравця (player key) відхиляють як forbidden, а завантаження не приймають навіть частково — його відхиляють цілком.

Обмеження

Конкретні числа з’являться разом із розділом про ліміти платформи, а що відбувається на кожній стелі, визначено вже зараз.

  • Розмір сторінки вибірки за власником: результат обрізають до ліміту, а прапорець «є ще» лишається ввімкненим. Повертати менше без цього прапорця заборонено.
  • Розмір вкладеного набору на рядок: обрізають за тим самим правилом, а прапорець ставлять на вкладенні.
  • Частота змін одного екземпляра: відмова з категорії rate limit із часом, після якого можна повторити. Читання профілю — така сама вибірка, як будь-яка інша, тож воно успадковує ліміти сутності, а не оголошує власні.