Перейти до основного вмісту
Будівельні блоки

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

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

Як оголошувати через атрибути в 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.
Unity C# is the same C# API here — працює в Unity без змін, зі згенерованими типами
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відмовила платформа, код узято з її каталогу
localSDK відмовив ще до надсилання, код узято з його власного опублікованого списку
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

Як оголошувати через атрибути в 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.
Unity C# is the same C# API here — працює в Unity без змін, зі згенерованими типами
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 не показує ні чинного значення, ні залишку й не попереджає, що ви вже близько до межі, а гравці обмежень не бачать узагалі. Усе потрібне є в самій відмові:

  • категорія — чи має сенс повторювати;
  • чиє це було обмеження;
  • коли можна повторити і яке вікно воно охоплює.