Эта страница ещё не переведена — показана английская версия.
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: вызов возвращает описание работы, а результат читают отдельно или получают по подписке. Режим объявляется заранее, а не выбирается во время работы в зависимости от нагрузки: вызывающий код рассчитывает на определённую форму ответа |
streaming | приходят ли входные и выходные данные частями и обрабатываются ли по мере поступления, а не целиком |
idempotency | у одностороннего RPC тоже есть ключ идемпотентности: отсутствие ответа не означает, что повторной доставки не будет |
overridability | объявляется на самом методе. Без объявления метод переопределить нельзя |
context | где RPC объявлен. RPC, объявленный внутри сущности, — часть этой сущности, и вне её его нет. Объявить RPC на игровом сервере — это и есть зарегистрировать его в маршрутизаторе, другого способа добавить RPC нет |
Если нужен результат, не делайте вызов односторонним
Односторонний вызов сообщает вызывающему только о локальной ошибке отправки. Если нужно знать, что произошло, объявите ответ: неизвестный результат обходится дороже, чем известный отказ.
Что передаётся при вызове.
| Что передаётся | Что это |
|---|---|
arguments | только то, что должен выбрать вызывающий |
неявный контекст | получатель, вызывающий и окружающий контекст. Они подставляются до первого параметра, который вы пишете сами, поэтому методу сущности никогда не нужно передавать идентификатор этой сущности |
references | объект SDK, переданный аргументом, передаётся как типизированный Ref — идентификатор или курсор, а не копия содержимого |
outcome | значение объявленного выходного типа или типизированный Problem |
Ссылка — это адрес, а не разрешение
Получатель разрешает Ref от своего имени, со своими разрешениями (permission) и предикатами. Передав ссылку, вы не даёте доступа к тому, на что она указывает.
Что хранит описание работы отложенного вызова.
| Что хранит | Что это |
|---|---|
state | accepted → running → completed или failed; последние два — конечные |
lifetime | время жизни объявляется заранее. Когда оно истекло, результат недоступен, и запрос результата получает отказ, а не пустой ответ |
cancel | идемпотентна. Это просьба, а не приказ: вы увидите то конечное состояние, completed или failed, до которого успела дойти работа |
Что верно для любого RPC.
| Всегда | Что это |
|---|---|
один обработчик | логический обработчик ровно один. RPC просит выполнить действие, и обработчик у него обязан быть. Событие (event) констатирует факт, и получателей у него может не быть вовсе. Поэтому односторонний RPC — не событие, а обращение к группе — это N вызовов, а не один: адреса даёт Groups, а ответы возвращаются потоком данных, и каждый привязан к участнику, который его прислал |
нет машины состояний | её нет ни у объявления, ни у немедленного вызова: вызов либо вернул результат, либо нет, и тогда действуют правила таймаута. Наблюдаемые состояния есть только у отложенного вызова |
поток данных | не атомарен. Потоковый вывод ничего не обещает о результате целиком, поэтому получатель должен обрабатывать обрыв и отличать «завершено» от «прервано» |
запись без предиката | ни одна запись не принимает на вход предикат: «сделай это для всех, кто подходит под условие» — не операция. Массовое действие выражают перечислением: прочитайте множество, а затем передайте список пакетной операции с объявленным поведением при частичном сбое |
[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 сущности вызвали на экземпляре, который скрыт предикатом строк вызывающего, ответ будет точно таким же, как при чтении этого экземпляра. Поэтому по отказу вызывающий ничего не узнает о том, что существует.
- Нет обработчика — это unavailable, а не not found. RPC объявлен, значит, существует, не хватает только маршрута. Повторить вызов стоит: игровой сервер может вернуться. А not found сказало бы вызывающему, что пробовать дальше не нужно.
- Отказ из хука несёт собственный код и причину хука, поэтому «отклонено правилом игры» никогда не будет выглядеть как «сломался транспорт».
- Таймаут — это не результат. Для отложенного вызова прочитайте описание работы. Для немедленного повтор безопасен благодаря объявленному ключу идемпотентности.
Ограничения
Конкретные числа появятся вместе с главой об ограничениях платформы, а поведение на каждой границе уже определено.
- Размер входных данных: вызов отклоняется до выполнения.
- Размер выходных данных: ответ отклоняется, а не обрезается, потому что обрезанный ответ не отличить от полного.
- Частота вызовов от одного актора: отказ по частоте с указанием, когда можно повторить.
- Число одновременных отложенных вызовов на актора: новый вызов отклоняется, уже запущенные доходят до конца.
- Время жизни описания работы: после него результат недоступен, и запрос получает отказ.
- Глубина цепочки вызовов: при превышении — объявленный отказ, а не исчерпание ресурсов или молчаливый обрыв.