Цю сторінку ще не перекладено — показано англійську версію.
Core
Core — єдиний об’єкт, який ви створюєте самі. Усе інше тримається на ньому. Передаєте йому ключ — і маєте контекст, обліковий запис, типізовані помилки, трасування та пакетні операції. Кожен виклик модуля проходить через Core, і жоден модуль не має власної копії.
Коли це потрібно
- Потрібно знати, хто ви і де працюєте: обліковий запис (identity), ролі, відкриті модулі, проєкт, середовище та регіон.
- Хмарна функція має записувати дані від імені гравця (player). Запис зараховують цьому гравцеві, а в журналі видно і функцію, і гравця.
- Повторний виклик не має спрацювати двічі. Для цього пакетні операції мають ключ ідемпотентності.
- За помилкою треба розгалужувати логіку й шукати її в логах. Кожен виняток — це типізований
Problemзі стабільним кодом. - Core не підходить для повідомлень, викликів і стану — для цього є примітиви: Events, RPC і Data & Subscriptions. До них звертаються через Entity, а кому вони адресовані, визначає Groups. Завантаження й отримання файлів описано на сторінці Files & UGC, підключення модулів і простори імен — на сто рінці Під капотом.
Хто що робить
| Актор | Що робить на цій сторінці |
|---|---|
будь-який актор | через Whoami дізнається свій обліковий запис, контекст і ролі |
backend-service | діє від імені гравця; об’єднує ідемпотентні операції в пакети |
operator | читає трасування невдалих і повторених викликів |
Коротко
Whoami, неявний контекст і пакет, який можна безпечно повторитиvar me = PlayServ.Whoami(); // identity, roles, unlocked modules
var env = PlayServ.Context; // project · env · region
// retries never double-apply: the batch carries an idempotency key
await PlayServ.Batch(key: orderId, b =>
{
b.Inventory.Grant(playerId, "starter.pack");
b.Inventory.Grant(playerId, "starter.emote");
});const me = playserv.whoami(); // identity, roles, unlocked modules
const env = playserv.context; // project · env · region
// retries never double-apply: the batch carries an idempotency key
await playserv.batch(orderId, (b) => {
b.inventory.grant(playerId, 'starter.pack');
b.inventory.grant(playerId, 'starter.emote');
});me = playserv.whoami() # identity, roles, unlocked modules
env = playserv.context # project · env · region
# retries never double-apply: the batch carries an idempotency key
async with playserv.batch(key=order_id) as b:
b.inventory.grant(player_id, "starter.pack")
b.inventory.grant(player_id, "starter.emote")Як оголошувати через атрибути в Go, поки не вирішено (O-1). Приклади з’являться, коли оберуть спосіб запису.
const FPSActor Me = Client->Whoami(); // identity, roles, unlocked modules
const FPSPlatformContext Env = Client->Context(); // project · env · region
// retries never double-apply: each keyed operation is safe to repeat
Client->Commerce->Entitlements->Grant(FPSIdempotencyKey(PackGrantId), PlayerId, PSKeys::Item::StarterPack);
Client->Commerce->Entitlements->Grant(FPSIdempotencyKey(EmoteGrantId), PlayerId, PSKeys::Item::StarterEmote);
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.var me = PlayServ.Whoami(); // identity, roles, unlocked modules
var env = PlayServ.Context; // project · env · region
// retries never double-apply: the batch carries an idempotency key
await PlayServ.Batch(key: orderId, b =>
{
b.Inventory.Grant(playerId, "starter.pack");
b.Inventory.Grant(playerId, "starter.emote");
});Обліковий запис зберігає Core, тому жоден виклик не приймає параметра session чи ctx.
Модель
Що містить кожна помилка.
| Поле | Що це |
|---|---|
code | машинозчитувана назва відмови. Стабільний: використовує наявні коди платформи, а не вигадує нові |
category | клас відмови; з нього видно, чи має сенс повторювати виклик |
ідентифікатор трасування | вказує саме на цей випадок. Є завжди, навіть у локальних помилок, тож підтримці не треба відтворювати проблему |
explanation | текст для людини. Нестабільний: його можуть будь-коли змінити чи перекласти |
помилки за полями | у разі відмови валідації: поле, код і повідомлення для кожного відхиленого поля |
Розгалужуйте логіку за кодом, а не за текстом
У логіці спирайтеся на code і category. explanation написано для людей, і він змінюється без попередження. Якщо біндинг віддає лише повідомлення, це помилка в біндингу.
Три джерела.
| Джерело | Що сталося |
|---|---|
platform | відмовила платформа, код узято з її каталогу |
local | SDK відмовив ще до надсилання, код узято з його власного опублікованого списку |
unknown | виклик надіслано, але відповідь не надійшла, т ому результат невідомий |
Що справджується для будь-якої відмови.
- Відхилена операція нічого не застосувала, тож у гілці помилки не треба перечитувати стан, щоб його виправити. Винятків два, і там, де вони трапляються, про це сказано прямо: таймаут, результат якого невідомий, і пакет, де кожен елемент обробляється окремо.
- Помилка однакова, хоч би яким шляхом вона до вас дійшла. Кине біндинг виняток, поверне результат чи викличе колбек підписки (subscription) — код, категорія і джерело будуть ті самі.
- Таймаут — це джерело
unknown, а не відповідь. Що з ним робити, оголошують для кожної операції окремо.
Помилки
rate_limited ще й повідомляє, коли можна повторитиtry { await PlayServ.Inventory.Grant(playerId, "starter.pack"); }
catch (Problem p) when (p.Code == "rate_limited")
{
Hud.RetryAt(p.RetryAfter);
}try { await playserv.inventory.grant(playerId, 'starter.pack'); }
catch (p) {
if (Problem.code(p) === 'rate_limited') hud.retryAt(p.retryAfter);
else throw p;
}try:
await playserv.inventory.grant(player_id, "starter.pack")
except Problem as p:
if p.code == "rate_limited":
hud.retry_at(p.retry_after)
else:
raiseЯк оголошувати через атрибути в Go, поки не вирішено (O-1). Приклади з’являться, коли оберуть спосіб запису.
// UE builds run without exceptions — the completion carries the result, read explicitly
Client->Commerce->Entitlements->Grant(FPSIdempotencyKey(GrantId), PlayerId, PSKeys::Item::StarterPack,
TPSOnResult<void>::CreateWeakLambda(this, [this](const TPSResult<void>& Result)
{
if (Result.IsRefused() && Result.Refusal().Code == FPSFailureCode::RateLimited)
{
Hud->RetryAt(Result.Refusal().RetryNotBefore); // TOptional<FDateTime> — an instant, not a delay
}
}));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.try { await PlayServ.Inventory.Grant(playerId, "starter.pack"); }
catch (Problem p) when (p.Code == "rate_limited")
{
Hud.RetryAt(p.RetryAfter);
}Хто не має ролі, той отримує відмову, а не урізаний результат. Для гравця чи клієнтської сесії виклики з рядків fn і adm не проходять: діяти від імені гравця, запис трасування і його читання повертають Problem з кодом forbidden. Облікові дані при цьому дійсні, просто їхні ролі не дають такого права. Повтор із тим самим ключем ідемпотентності нічого не змінить.
Обмеження
Не повторюйте прийнятий виклик
Обмеження перевіряють, коли виклик приймають. Прийнятий виклик їх уже пройшов і виконається, хоч скільки б він чекав у черзі. Відмова якомусь пізнішому виклику на нього не впливає, тож якщо повторити прийнятий виклик, роботу буде виконано двічі.
Про обмеження ви дізнаєтеся, лише отримавши відмову. SDK не показує ні чинного значення, ні залишку й не попереджає, що ви вже близько до межі, а гравці обмежень не бачать узагалі. Усе потрібне є в самій відмові:
- категорія — чи має сенс повторювати;
- чиє це було обмеження;
- коли можна повторити і яке вікно воно охоплює.