Эта страница ещё не переведена — показана английская версия.
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, пока не решено (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[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 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 leavesКак объявлять через атрибуты в 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.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, и вся загрузка отклоняется целиком, а не применяется частично.
Ограничения
Конкретные числа появятся вместе с главой об ограничениях платформы, а поведение на каждой границе уже определено.
- Размер страницы выборки по владельцу: результат обрезается до предела, а флаг «есть ещё» остаётся выставленным. Вернуть меньше строк без этого флага запрещено.
- Размер включённого набора в одной строке: обрезается по тому же правилу, с флагом на включении.
- Частота изменений одного экземпляра: отказ по частоте со сроком повтора. Чтение профиля — такая же выборка, как любая другая, поэтому оно наследует ограничения сущностей, а не объявляет свои.