Цю сторінку ще не перекладено — показано англійську версію.
RPC
Типізований виклик, тіло якого виконується деінде. Оголосіть процедуру там, де їй місце, — у модулі чи всередині сутності, — і кожен біндинг отримає згенерований асинхронний метод. Дієслово одне — invoke; односторонній виклик — це режим оголошення, а не окреме дієслово.
Коли це потрібно
- Тому, хто викликає, потрібна відповідь: запит і відповідь із типізованим результатом.
- Той, хто викликає, лише повідомляє і йде далі: оголошений односторонній RPC, назад нічого не повертається.
- Робота триває довше за виклик: оголошений відкладений RPC повертає опис роботи (work descriptor), а не завершується таймаутом.
- Одне питання, багато тих, хто відповідає: виклик на групу (group) — це N викликів, і кожна відповідь приходить прив’язаною до учасника, який її надіслав.
- Дія належить конкретному об’єкту: оголосіть її всередині сутності (entity) — там вона й існує, і більше ніде (оголошення показано на сторінці Entity).
- RPC не підходить, якщо нікого не просять щось зробити. Факт, на який інші лише реагують, — це подія.
Хто що робить
| Актор | Що робить на цій сторінці |
|---|---|
schema-author | оголошує 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, поки не вирішено (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 на ігровому сервері й означає зареєструвати його в маршрутизаторі — іншого способу додати його немає |
Не робіть виклик одностороннім, якщо вам потрібен результат
Односторонній виклик повідомляє лише про локальну помилку надсилання. Якщо треба знати, що сталося, оголосіть відповідь: невідомий результат обходиться дорожче, ніж відома відмова.
Що несе виклик.
| Несе | Що це |
|---|---|
arguments | лише те, що має обрати той, хто викликає |
неявний контекст | одержувач, той, хто викликає, і навколишній контекст. Їх прив’язують ще до першого параметра, який ви пишете, тож методові сутності ніколи не треба передавати її ідентифікатор |
references | об’єкт SDK, переданий як аргумент, іде як типізований Ref: ідентифікатор або курсор, а не копія вмісту |
outcome | значення оголошеного вихідного типу або типізований Problem |
Посилання — це адреса, а не дозвіл
Одержувач розіменовує Ref від власного імені, зі своїми дозволами (permission) і предикатами. Передати посилання не означає дати доступ до того, на що воно вказує.
Що містить опис відкладеного виклику.
| Містить | Що це |
|---|---|
state | accepted → running → completed або failed; два останні стани кінцеві |
lifetime | оголошений час життя; після нього результат недоступний, і на запит про нього приходить відмова, а не порожня відповідь |
cancel | ідемпотентне. Це прохання: ви побачите той кінцевий стан, completed чи failed, якого встигла дійти робота |
Що справджується для будь-якого RPC.
| Завжди | Що це |
|---|---|
один обробник | рівно один логічний обробник. RPC просить виконати дію, і обробник для неї мусить існувати; подія констатує факт і може не мати жодного одержувача. Тому односторонній RPC — не подія, а виклик на групу — це N викликів, а не один: адреси дає Groups, а відповіді повертаються потоком даних (stream), кожна прив’язана до учасника, який її надіслав |
немає машини станів | її немає ні в оголошення, ні в негайного виклику: виклик або повернув результат, або ні, і тоді діють правила таймауту. Стани, які можна спостерігати, має лише відкладений виклик |
потік даних | не атомарний. Потоковий вихід нічого не гарантує щодо цілого, тож одержувач має обробляти обрив і відрізняти «завершено» від «перервано» |
запис без предиката | жоден запис не приймає предикат на вході: «зроби це для всіх, хто відповідає умові» — не операція. Масову дію описують перел іком: прочитайте множину, а потім передайте список пакетній операції з оголошеною поведінкою на випадок часткової невдачі |
[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 ranexport 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, поки не вирішено (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 сказав би тому, хто викликає, що час зупинитися.
- Відмова хука несе власний код і причину хука, тож «відхилено правилом гри» ніколи не виглядає як «зламався транспорт».
- Таймаут — це не результат. Для відкладеного виклику прочитайте опис роботи. Для негайного повтор безпечний завдяки оголошеному ключу ідемпотентності.
Обмеження
Конкретні числа з’являться разом із розділом про обмеження платформи, а що стається на кожній межі, визначено вже зараз.
- Розмір вхідних даних: виклик відхиляють ще до виконання.
- Розмір вихідних даних: відповідь відхиляють, а не обрізають, бо обрізану відповідь не відрізниш від повної.
- Частота викликів від одного актора: відмова через ліміт частоти, у якій указано, коли можна повторити.
- Одночасні відкладені виклики від одного актора: новий відхиляють, а ті, що вже виконуються, завершуються.
- Час життя опису роботи: після нього результат недоступний, і на запит приходить відмова.
- Глибина ланцюжка викликів: у разі перевищення — оголошена відмова, а не вичерпані ресурси чи мовчазний обрив.