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

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

RPC

Типізований виклик, тіло якого виконується деінде. Оголосіть процедуру там, де їй місце, — у модулі чи всередині сутності, — і кожен біндинг отримає згенерований асинхронний метод. Дієслово одне — invoke; односторонній виклик — це режим оголошення, а не окреме дієслово.

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

  • Тому, хто викликає, потрібна відповідь: запит і відповідь із типізованим результатом.
  • Той, хто викликає, лише повідомляє і йде далі: оголошений односторонній RPC, назад нічого не повертається.
  • Робота триває довше за виклик: оголошений відкладений RPC повертає опис роботи (work descriptor), а не завершується таймаутом.
  • Одне питання, багато тих, хто відповідає: виклик на групу (group) — це N викликів, і кожна відповідь приходить прив’язаною до учасника, який її надіслав.
  • Дія належить конкретному об’єкту: оголосіть її всередині сутності (entity) — там вона й існує, і більше ніде (оголошення показано на сторінці Entity).
  • RPC не підходить, якщо нікого не просять щось зробити. Факт, на який інші лише реагують, — це подія.

Хто що робить

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

Коротко

Оголошуємо RPC із відповіддю та односторонній, викликаємо обидва, а потім розсилаємо виклик групі
[Rpc]                                        // answering, immediate, not overridable — the bare defaults
public static ScoreVerdict SubmitScore(ScoreReport report) => Scores.Judge(report);

[Rpc(OneWay = true)]                         // declared one-way: nothing travels back
public static void ReportPing(PingSample sample) => Metrics.Add(sample);

// invoking — generated, typed, awaitable
var verdict = await playserv.Rpc.Invoke.SubmitScore(report);
playserv.Rpc.Invoke.ReportPing(sample);      // one-way by declaration, not by call site

// group fan-out: N calls, one answer bound to each member
await foreach (var answer in squad.Invoke.ReadyCheck())
    Hud.Mark(answer.Member, answer.Ready);
export class MatchRpcs {
  @Rpc()                                     // answering, immediate, not overridable — the bare defaults
  static submitScore(report: ScoreReport): ScoreVerdict { return Scores.judge(report); }

  @Rpc({ oneWay: true })                     // declared one-way: nothing travels back
  static reportPing(sample: PingSample): void { Metrics.add(sample); }
}

// invoking — generated, typed, awaitable
const verdict = await playserv.rpc.invoke.submitScore(report);
playserv.rpc.invoke.reportPing(sample);      // one-way by declaration, not by call site

// group fan-out: N calls, one answer bound to each member
for await (const answer of squad.invoke.readyCheck())
  hud.mark(answer.member, answer.ready);
@rpc()                                       # answering, immediate, not overridable — the bare defaults
def submit_score(report: ScoreReport) -> ScoreVerdict:
    return scores.judge(report)

@rpc(one_way=True)                           # declared one-way: nothing travels back
def report_ping(sample: PingSample):
    metrics.add(sample)

# invoking — generated, typed, awaitable
verdict = await playserv.rpc.invoke.submit_score(report)
playserv.rpc.invoke.report_ping(sample)      # one-way by declaration, not by call site

# group fan-out: N calls, one answer bound to each member
async for answer in squad.invoke.ready_check():
    hud.mark(answer.member, answer.ready)
Незабаром — Go

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

// invoking — generated, typed (the client surface; bodies live where routing sends them)
Client->Rpc->Call->SubmitScore(Report,
    TPSOnResult<FScoreVerdict>::CreateWeakLambda(this, [this](const TPSResult<FScoreVerdict>& Result)
    {
        if (!Result.HasValue()) { return; }
        Hud->ShowVerdict(Result.Value());
    }));
Client->Rpc->CallOneWay->ReportPing(Sample);   // one-way by declaration, not by call site

// group fan-out: one call, one answer bound to each member
Squad->Call->ReadyCheck(TPSOnResult<FReadyAnswer>::CreateWeakLambda(this,
    [this](const TPSResult<FReadyAnswer>& Answer)
    {
        if (!Answer.HasValue()) { return; }
        Hud->Mark(Answer.Value().Member, Answer.Value().Ready);   // the delegate fires once per member
    }));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.
// Unity invokes; RPC bodies execute on the platform or a host — engines are not a handler runtime
var verdict = await playserv.Rpc.Invoke.SubmitScore(report);
playserv.Rpc.Invoke.ReportPing(sample);      // one-way by declaration, not by call site

await foreach (var answer in squad.Invoke.ReadyCheck())
    Hud.Mark(answer.Member, answer.Ready);

Де виконується тіло — у хмарній функції, на клієнті, майстер-клієнті чи ігровому сервері, — вирішує маршрутизація, яку оголошують для кожного методу. Перевизначення і проміжні обробники описано на сторінці Extensibility. Невдалий виклик кидає типізований Problem (Core).

Модель

Що оголошує RPC.

ОголошуєЩо це
nameназва зі словника дієслів
inputаргументи, які має обрати той, хто викликає
outputрівно один оголошений тип. Скорочена відповідь — це окремий оголошений тип, а не той самий тип із пропущеними полями. Інакше «не запитували», «відсутнє» і «приховане маскою доступу» виглядали б однаково
режим відповідіwith a reply: значення оголошеного вихідного типу або типізована відмова. one-way: відповіді немає, той, хто викликає, дізнається лише про локальну помилку надсилання
режим виконанняimmediate: результат приходить просто у відповідь на виклик. deferred: виклик повертає опис роботи, а результат читають окремо або отримують через підписку (subscription). Режим оголошують заздалегідь, а не обирають під час роботи залежно від навантаження, бо код, що викликає, розраховує на певну форму відповіді
streamingчи надходять вхідні й вихідні дані частинами, які обробляють у міру надходження, а не цілком
idempotencyодносторонній RPC теж має ключ ідемпотентності: відсутність відповіді не означає, що повторної доставки не буде
overridabilityоголошують на самому методі. Без оголошення метод не можна перевизначити
contextде RPC оголошено. RPC, оголошений усередині сутності, — частина цієї сутності й поза нею не існує. Оголосити RPC на ігровому сервері й означає зареєструвати його в маршрутизаторі — іншого способу додати його немає
with a reply · immediatewith a reply · deferredone-wayinvokethe outcome, or a typed refusalinvokea work descriptor, at oncethe outcome, laterinvokeno reply within the callread it, or subscribeonly a local send failure
Що отримує той, хто викликає: результат у межах виклику, опис роботи зараз і результат пізніше або жодної відповіді

Не робіть виклик одностороннім, якщо вам потрібен результат

Односторонній виклик повідомляє лише про локальну помилку надсилання. Якщо треба знати, що сталося, оголосіть відповідь: невідомий результат обходиться дорожче, ніж відома відмова.

Що несе виклик.

НесеЩо це
argumentsлише те, що має обрати той, хто викликає
неявний контекстодержувач, той, хто викликає, і навколишній контекст. Їх прив’язують ще до першого параметра, який ви пишете, тож методові сутності ніколи не треба передавати її ідентифікатор
referencesоб’єкт SDK, переданий як аргумент, іде як типізований Ref: ідентифікатор або курсор, а не копія вмісту
outcomeзначення оголошеного вихідного типу або типізований Problem

Посилання — це адреса, а не дозвіл

Одержувач розіменовує Ref від власного імені, зі своїми дозволами (permission) і предикатами. Передати посилання не означає дати доступ до того, на що воно вказує.

Що містить опис відкладеного виклику.

МіститьЩо це
stateaccepted → running → completed або failed; два останні стани кінцеві
lifetimeоголошений час життя; після нього результат недоступний, і на запит про нього приходить відмова, а не порожня відповідь
cancelідемпотентне. Це прохання: ви побачите той кінцевий стан, completed чи failed, якого встигла дійти робота

Що справджується для будь-якого RPC.

ЗавждиЩо це
один обробникрівно один логічний обробник. RPC просить виконати дію, і обробник для неї мусить існувати; подія констатує факт і може не мати жодного одержувача. Тому односторонній RPC — не подія, а виклик на групу — це N викликів, а не один: адреси дає Groups, а відповіді повертаються потоком даних (stream), кожна прив’язана до учасника, який її надіслав
немає машини станівїї немає ні в оголошення, ні в негайного виклику: виклик або повернув результат, або ні, і тоді діють правила таймауту. Стани, які можна спостерігати, має лише відкладений виклик
потік данихне атомарний. Потоковий вихід нічого не гарантує щодо цілого, тож одержувач має обробляти обрив і відрізняти «завершено» від «перервано»
запис без предикатажоден запис не приймає предикат на вході: «зроби це для всіх, хто відповідає умові» — не операція. Масову дію описують переліком: прочитайте множину, а потім передайте список пакетній операції з оголошеною поведінкою на випадок часткової невдачі
Відкладений RPC: виклик повертає опис роботи, а результат приходить за ним
[Rpc(Execution = Execution.Deferred)]        // minutes of work — an answer inside the call would be a timeout
public static MatchReport BuildMatchReport(MatchId match) => Reports.Build(match);

var work = await playserv.Rpc.Invoke.BuildMatchReport(matchId);   // the descriptor, not the report
work.OnOutcome(report => Hud.ShowReport(report));                 // or read it later, by descriptor
await work.Cancel();                                              // a request, not a promise nothing ran
export class ReportRpcs {
  @Rpc({ execution: Execution.Deferred })    // minutes of work — an answer inside the call would be a timeout
  static buildMatchReport(match: MatchId): MatchReport { return Reports.build(match); }
}

const work = await playserv.rpc.invoke.buildMatchReport(matchId);  // the descriptor, not the report
work.onOutcome((report) => hud.showReport(report));                // or read it later, by descriptor
await work.cancel();                                               // a request, not a promise nothing ran
@rpc(execution=Execution.DEFERRED)           # minutes of work — an answer inside the call would be a timeout
def build_match_report(match: MatchId) -> MatchReport:
    return reports.build(match)

work = await playserv.rpc.invoke.build_match_report(match_id)   # the descriptor, not the report
work.on_outcome(lambda report: hud.show_report(report))         # or read it later, by descriptor
await work.cancel()                                             # a request, not a promise nothing ran
Незабаром — Go

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

// the client side of a deferred call: a descriptor now, the outcome against it later
Client->Rpc->Call->BuildMatchReport(MatchId,
    TPSOnResult<FPSDeferredHandle>::CreateWeakLambda(this, [this](const TPSResult<FPSDeferredHandle>& Result)
    {
        if (!Result.HasValue()) { return; }
        const FPSDeferredHandle Work = Result.Value();
        TPSSubscription ReportWatch = Client->Rpc->Deferred->Subscribe(Work,
            [this](const FMatchReport& Report) { Hud->ShowReport(Report); });
        Client->Rpc->Deferred->Cancel(Work);   // a request, not a promise nothing ran
    }));
// co_await support ships later: a built-in SDK coroutine wrapper that respects Unreal's GC and threading.
// the client side of a deferred call: a descriptor now, the outcome against it later
var work = await playserv.Rpc.Invoke.BuildMatchReport(matchId);
work.OnOutcome(report => Hud.ShowReport(report));
await work.Cancel();                         // a request, not a promise nothing ran

Помилки

  • Право задають на RPC, а не на примітиві. Загального права «може викликати» немає: кожне оголошення називає атомарний дозвіл, який має мати той, хто викликає. Без нього виклик отримує типізовану відмову forbidden, а не зникає мовчки.
  • Прихований екземпляр виглядає як not found. Якщо RPC сутності викликали на екземплярі, який приховує предикат рядків (row predicate) того, хто викликає, відповідь буде така сама, як на читання цього екземпляра. Тож відмова нічого не каже про те, що існує.
  • Немає обробника — це unavailable, а не not found. RPC оголошено, отже, він існує, бракує лише маршруту. Повторити варто, бо ігровий сервер може повернутися, а not found сказав би тому, хто викликає, що час зупинитися.
  • Відмова хука несе власний код і причину хука, тож «відхилено правилом гри» ніколи не виглядає як «зламався транспорт».
  • Таймаут — це не результат. Для відкладеного виклику прочитайте опис роботи. Для негайного повтор безпечний завдяки оголошеному ключу ідемпотентності.

Обмеження

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

  • Розмір вхідних даних: виклик відхиляють ще до виконання.
  • Розмір вихідних даних: відповідь відхиляють, а не обрізають, бо обрізану відповідь не відрізниш від повної.
  • Частота викликів від одного актора: відмова через ліміт частоти, у якій указано, коли можна повторити.
  • Одночасні відкладені виклики від одного актора: новий відхиляють, а ті, що вже виконуються, завершуються.
  • Час життя опису роботи: після нього результат недоступний, і на запит приходить відмова.
  • Глибина ланцюжка викликів: у разі перевищення — оголошена відмова, а не вичерпані ресурси чи мовчазний обрив.