Cette page n’est pas encore traduite : la version anglaise est affichée.
RPC
Un appel typé dont le corps vit ailleurs. Déclarez la procédure là où elle a sa place, sur un module ou dans une entité, et chaque binding reçoit une méthode générée que l’on peut attendre (await). Le verbe est invoke ; le sens unique est un mode de la déclaration, pas un verbe à part.
Quand l’utiliser
- L’appelant a besoin d’une réponse : requête/réponse avec un retour typé.
- L’appelant signale quelque chose et passe à la suite : un RPC déclaré à sens unique, rien ne revient.
- Le travail dure plus longtemps que l’appel : un RPC déclaré différé renvoie un descripteur de tâche au lieu d’expirer.
- Une question, plusieurs répondants : un appel de groupe représente N appels, et chaque réponse arrive rattachée au membre qui l’a envoyée.
- Le verbe appartient à un objet : déclarez-le dans l’entité, là où il vit et nulle part ailleurs (Entity montre la déclaration).
- Inutile quand personne n’est chargé d’agir. Un fait auquel les autres se contentent de réagir est un événement.
Qui fait quoi
| Acteur | Ce qu’il fait sur cette page |
|---|---|
schema-author | déclare les RPC, leurs modes et qui a le droit de les appeler |
tout acteur | invoque un appel avec réponse ou à sens unique, là où la déclaration le permet |
membre du groupe | répond à un appel diffusé au groupe ; chaque membre renvoie une réponse |
En bref
[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)La déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.
// 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);L’endroit où le corps s’exécute — fonction cloud, client, client maître ou serveur de jeu — relève du routage, déclaré méthode par méthode ; Extensibility traite des surcharges et du middleware. Un appel qui échoue lève un Problem typé (Core).
Le modèle
Ce que déclare un RPC.
| Déclare | Ce que c’est |
|---|---|
name | tiré du vocabulaire des verbes |
input | les arguments que l’appelant doit choisir |
output | exactement un type déclaré. Une réponse plus courte est un type déclaré à part entière, pas le même type avec des champs en moins ; sinon « non demandé », « absent » et « masqué par le masque d’accès » se ressembleraient |
mode de réponse | with a reply : une valeur du type de sortie déclaré ou un refus typé. one-way : pas de réponse ; l’appelant n’apprend qu’un échec d’envoi local |
mode d'exécution | immediate : le résultat revient dans l’appel. deferred : l’appel renvoie un descripteur de tâche, et le résultat se lit ou arrive par abonnement. Déclaré, et non choisi à l’exécution selon la charge, car les appelants dépendent de la forme de la réponse |
streaming | indique si l’entrée et la sortie arrivent par morceaux et sont traitées au fil de l’eau, plutôt qu’en un bloc |
idempotency | un RPC à sens unique porte lui aussi une clé d’idempotence : pas de réponse ne veut pas dire pas de relivraison |
overridability | déclarée sur la méthode elle-même. Sans déclaration, une méthode ne peut pas être surchargée |
context | l’endroit où il est déclaré. Un RPC déclaré dans une entité fait partie de cette entité et n’existe pas en dehors. Le déclarer dans le serveur de jeu, c’est l’enregistrer dans le routeur — il n’existe pas d’autre moyen d’en ajouter un |
N’utilisez pas le sens unique si le résultat vous importe
Un appel à sens unique ne signale à l’appelant qu’un échec d’envoi local. Si vous devez savoir ce qui s’est passé, déclarez une réponse : un résultat inconnu coûte plus cher qu’un refus connu.
Ce que transporte une invocation.
| Transporte | Ce que c’est |
|---|---|
arguments | uniquement ce que l’appelant doit choisir |
contexte implicite | le destinataire, l’appelant et le contexte ambiant, liés avant votre premier paramètre écrit — on ne demande jamais à la méthode d’une entité l’identifiant de cette entité |
references | un objet du SDK passé en argument voyage sous la forme d’une Ref typée : un identifiant ou un curseur, pas une copie de son contenu |
outcome | une valeur du type de sortie déclaré, ou un Problem typé |
Une référence est une adresse, pas une permission
Le destinataire résout une Ref pour son propre compte, avec ses propres permissions et prédicats. Passer une référence ne donne pas accès à ce qu’elle désigne.
Ce que contient le descripteur d’un appel différé.
| Contient | Ce que c’est |
|---|---|
state | accepted → running → completed ou failed, les deux derniers étant terminaux |
lifetime | déclarée ; passé ce délai, le résultat n’est plus disponible et le demander provoque un refus, pas une réponse vide |
cancel | idempotent. C’est une demande : l’état terminal que vous observez est celui des deux, completed ou failed, que la tâche a atteint |
Vrai pour tout RPC.
| Toujours | Ce que c’est |
|---|---|
un seul handler | exactement un handler logique. Un RPC demande une action, et son handler doit exister ; un événement énonce un fait et peut n’avoir aucun destinataire. Un RPC à sens unique n’est donc pas un événement, et s’adresser à un groupe représente N appels, pas un seul : Groups fournit les adresses, et les réponses reviennent sous forme de flux, chacune rattachée au membre qui l’a envoyée |
pas de machine à états | ni une déclaration ni un appel immédiat n’en ont : l’appel a renvoyé un résultat ou non, et les règles de timeout s’appliquent ensuite. Seul un appel différé a des états observables |
un flux | n’est pas atomique. Une sortie en streaming ne garantit rien sur l’ensemble : le destinataire doit gérer une interruption et distinguer « terminé » de « interrompu » |
pas de prédicat en écriture | aucune écriture n’accepte de prédicat en entrée : « faire ceci pour tous ceux qui remplissent cette condition » n’est pas une opération. Une action de masse s’exprime par énumération : lisez l’ensemble, puis passez la liste à une opération par lot dont la sémantique d’échec partiel est déclarée |
[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 ranLa déclaration par attributs en Go n’est pas encore tranchée (O-1). Les exemples arriveront quand la forme d’écriture sera choisie.
// 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 ranErreurs
- Le droit porte sur le RPC, jamais sur la primitive. Il n’existe pas de « peut invoquer » global : chaque déclaration nomme la permission atomique que doit détenir son appelant. Un appelant qui ne l’a pas reçoit un refus typé
forbidden, pas un abandon silencieux. - Une instance masquée se lit comme not found. Un RPC d’entité invoqué sur une instance que le prédicat de lignes de l’appelant masque répond exactement comme la lecture de cette instance : le refus n’apprend rien à l’appelant sur ce qui existe.
- L’absence de handler, c’est unavailable, pas not found. Le RPC est déclaré, il existe donc ; ce qui manque, c’est une route. Réessayer vaut la peine, car un serveur de jeu peut revenir ; not found dirait à l’appelant d’arrêter.
- Un refus de hook porte le code et le motif propres au hook : « rejeté par une règle du jeu » n’arrive donc jamais sous l’apparence de « le transport a lâché ».
- Un timeout n’est pas un résultat. Pour un appel différé, vous lisez le descripteur ; pour un appel immédiat, la clé d’idempotence déclarée permet de réessayer sans risque.
Limites
Les valeurs chiffrées arriveront avec le chapitre sur les limites de la plateforme ; ce qui se passe à chaque plafond est déjà fixé.
- Taille de l’entrée : l’appel est refusé avant l’exécution.
- Taille de la sortie : refusée plutôt que tronquée, car on ne distingue pas une réponse rognée d’une réponse complète.
- Débit d’appels par acteur : un refus pour limite de débit, qui indique quand réessayer.
- Appels différés simultanés par acteur : le nouveau est refusé, ceux en cours se terminent.
- Durée de vie du descripteur : passé ce délai, le résultat n’est plus disponible, et le demander provoque un refus.
- Profondeur de la chaîne d’appels : un refus déclaré en cas de dépassement, pas un épuisement des ressources ni une rupture silencieuse.