Esta página ainda não foi traduzida: mostrando a versão em inglês.
Extensibility
Todo cenário da plataforma é uma cadeia de funções registradas: substitua um elo ou envolva-o. Você muda o comportamento da plataforma substituindo os passos dela, e não fazendo um fork do código-fonte.
Quando usar
- Um passo da plataforma precisa rodar a sua lógica: declare a substituição de um elo nomeado com
[Override(…)]. - Você precisa de verificações ou efeitos colaterais em volta de um passo: middleware
Before/Afterem ordem, que pode vetar ou notificar. - O código precisa rodar por agendamento, por evento ou por webhook: os gatilhos (trigger) entregam a você um contexto já interpretado e tipado.
- Você precisa saber o que vai rodar de fato antes do deploy: simule a cadeia (dry-run) e leia a ordem resolvida.
- Dispensável quando a regra diz respeito às escritas de uma única entidade. Um hook de Data & Subscriptions é a forma mais leve.
Quem faz o quê
| Ator | O que faz nesta página |
|---|---|
backend-service | sobrescreve elos, envolve passos com middleware, escreve handlers de gatilhos |
operator | inspeciona cadeias, define a ordem, lê segredos, simula a resolução |
Resumo
SignIn, envolver o grant com middleware, rodar código num cron// gate one named step of the auth scenario — a before hook may refuse, fail-closed
[Before(Auth.SignIn)]
public static Task<Verdict> GateRegion(SignInAttempt a) =>
a.Region == "sanctioned"
? Hook.Reject(Problem.Forbidden, "region not served")
: Hook.Continue(a);
// wrap a step with ordered middleware
PlayServ.Extend.Scenario("commerce.purchase")
.Before("grant", LogPurchaseIntent)
.After("grant", NotifySquad, order: 10);
// customer code on a trigger
[OnSchedule("0 4 * * *")]
public static async Task NightlyCleanup() { ... }// gate one named step of the auth scenario — a before hook may refuse, fail-closed
export const gateRegion = before(Auth.signIn, (a: SignInAttempt) =>
a.region === 'sanctioned'
? Hook.reject(Problem.forbidden, 'region not served')
: Hook.continue(a));
// wrap a step with ordered middleware
playserv.extend.scenario('commerce.purchase')
.before('grant', logPurchaseIntent)
.after('grant', notifySquad, { order: 10 });
// customer code on a trigger
export const nightlyCleanup = onSchedule('0 4 * * *', async () => { /* ... */ });# gate one named step of the auth scenario — a before hook may refuse, fail-closed
@before(auth.sign_in)
async def gate_region(a):
if a.region == "sanctioned":
return hook.reject(problem.FORBIDDEN, "region not served")
return hook.cont(a)
# wrap a step with ordered middleware
playserv.extend.scenario("commerce.purchase") \
.before("grant", log_purchase_intent) \
.after("grant", notify_squad, order=10)
# customer code on a trigger
@on_schedule("0 4 * * *")
async def nightly_cleanup(): ...Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.
Sobrescritas, middleware e gatilhos rodam todos como cloud functions na plataforma, não na engine. Escreva-os em C#, TypeScript ou Python — o Unreal assina os eventos resultantes. Funções escritas na engine estão planejadas: grafos de Blueprint e C++ (traduzido para um formato de script validado pela plataforma). Os dois são analisados e publicados como qualquer declaração; a execução continua na plataforma.
Sobrescritas, middleware e gatilhos rodam todos como cloud functions na plataforma, não na engine. Escreva-os em C#, TypeScript ou Python — o Unity assina os eventos resultantes.
O modelo
Onde a plataforma deixa você se conectar.
| Termo | O que é |
|---|---|
função registrada | um passo da plataforma que pode ser sobrescrito — “criar perfil”, “calcular preço” |
scenario | a cadeia ordenada que um fluxo da plataforma executa: autenticação, entrada, compra |
overridability | se um elo pode ser substituído, só envolvido, ou se é fixo |
middleware | um handler ordenado de antes/depois em volta de um elo |
trigger | o que dispara o seu código: um evento, um agendamento, um webhook |
secret | um valor que o seu handler pode ler |
invocation | uma execução, com o trace dela |
O que um hook declara.
| Declara | O que é |
|---|---|
position | o passo nomeado ao qual ele se conecta |
kind | gatekeeper — uma verificação de admissão ou uma validação, e ele bloqueia em caso de falha: o passo não roda quando o próprio hook quebra; ou observer — um log, uma notificação, um contador, e ele deixa passar em caso de falha: o passo roda, e a falha ainda é reportada em vez de engolida. Não existe padrão |
moment | before — antes da validação, recebe o payload tipado e pode alterá-lo ou rejeitá-lo; ou after — depois que o passo foi confirmado, recebe a requisição e o resultado, só para efeitos colaterais, e nunca pode fazer a operação falhar nem alterar a resposta |
effect | o que o hook faz, não só onde ele fica. Assim a ordem é descrita em termos do trabalho, e não de números, e continua valendo quando outra pessoa adiciona um hook ao lado do seu |
versão e condição | um handler que vale para um ambiente ou público é uma versão declarada daquele hook, e não um desvio dentro do corpo dele; é isso que o interruptor do painel liga e desliga |
Três formas de conectar código.
| Forma | Para que usar |
|---|---|
atributo [Before(Step)] / [After(Step)] | uma regra num passo nomeado — a maioria dos hooks |
atributo [Override(Link)] | substituir por completo a implementação de um elo |
Extend.Scenario("…").Before("link", fn, order: n) | envolver um elo dentro de uma cadeia, quando a ordem em relação a outros middlewares importa |
| Sempre | O que é |
|---|---|
as três | fazem o deploy com playserv push |
as duas formas com atributo | são o que o painel mostra, porque a declaração leva o nome do passo ou do elo para o modelo publicado |
a forma de middleware | leva uma ordem, que é o que uma cadeia precisa |
a atribuição na inicialização | (Scenario.OnX = fn) continua disponível para um handler que não precisa aparecer na árvore do painel de administração |
substituir um elo | deixa os elos dos lados intactos, e nenhum deles sabe qual implementação respondeu — o passo da própria plataforma ou o seu |
Para onde vai uma chamada, e o que vale para toda rota.
| Sempre | O que é |
|---|---|
quatro direções | uma cloud function · o backend externo do estúdio · o servidor do jogo · outra direção declarada |
o roteador | é orientado a mensagens e sinais; request-response é um adaptador sobre ele, e não a natureza dele |
correspondência | é feita pelo nome declarado da operação ou do sinal e por nada mais: nem o formato do payload, nem quem chama, nem a carga |
a direção | não faz parte do contrato da operação: mover um handler de uma direção para outra não é uma mudança que quebra compatibilidade |
“o servidor do jogo” | é definido pelo que ele é, não por quem o hospeda — a nossa frota e a hospedagem própria de um estúdio são uma direção só, e a declaração não traz nenhuma marca de quem é dono da infraestrutura |
RPCs do servidor do jogo | são registrados no mesmo roteador: declarar um já é registrá-lo, e não existe uma segunda forma |
ordem | o middleware roda de cima para baixo e, onde um passo tem mais de uma implementação, o roteador escolhe da esquerda para a direita pela condição; quando nada corresponde, responde a versão marcada como padrão |
Uma cadeia com sobrescritas e middleware pode ser resolvida e lida antes de qualquer execução. A ordem resolvida pode ser inspecionada no painel e pelo código.
O que é uma restrição entre hooks, e quando ela é verificada.
| O que é | |
|---|---|
uma restrição nomeada | um ponto de extensão pode nomear os efeitos que ele restringe — uma verificação anti-cheat precisa vir antes de uma alocação, um recibo exige uma cobrança neste ponto — e não restringir mais nada |
um efeito não nomeado | fica sem restrição, e não é recusado |
uma violação | é um defeito da configuração, e a recusa nomeia os dois hooks e a restrição que eles quebraram — não é um aviso nem uma reordenação silenciosa |
quando é verificada | em todo ato que pode mudar o que roda num ponto: registrar, fazer o deploy, mudar o arranjo. Por isso um arranjo que chega à execução já foi admitido |
nunca reverificada em runtime | um arranjo admitido não é verificado de novo quando executa |
| Sempre | O que é |
|---|---|
handlers | são tipados na entrada e na saída: nada de dynamic, nada de sacolas de contexto. O handle da plataforma está no ambiente, e o contexto da invocação — quem chama, gatilho, trace — chega já interpretado |
o que um build da engine vê | os eventos que o cenário emite depois, porque uma sobrescrita ou um middleware roda na plataforma, e o runtime de uma engine não é lugar para hospedar isso. É o que mostram as abas de engine nos exemplos desta página |
[Rpc("resolve_price", Default = true)]
public static Price ResolvePrice(Sku sku) => Pricing.Base(sku);
[Rpc("resolve_price", When = "env == 'staging'")]
public static Price ResolvePriceStaging(Sku sku) => Pricing.WithDiscount(sku, 0.5f);
// a hook can carry a version too, gated by its own condition
[After("grant", When = "audience == 'beta'")]
public static void NotifySquadBeta(GrantResult r) => Messaging.PingBeta(r.Squad);export const resolvePrice = rpc('resolve_price', { default: true },
(sku: Sku) => Pricing.base(sku));
export const resolvePriceStaging = rpc('resolve_price', { when: "env == 'staging'" },
(sku: Sku) => Pricing.withDiscount(sku, 0.5));
// a hook can carry a version too, gated by its own condition
export const notifySquadBeta = after('grant', { when: "audience == 'beta'" },
(r: GrantResult) => Messaging.pingBeta(r.squad));@rpc("resolve_price", default=True)
def resolve_price(sku: Sku) -> Price:
return pricing.base(sku)
@rpc("resolve_price", when="env == 'staging'")
def resolve_price_staging(sku: Sku) -> Price:
return pricing.with_discount(sku, 0.5)
# a hook can carry a version too, gated by its own condition
@after("grant", when="audience == 'beta'")
def notify_squad_beta(r: GrantResult):
messaging.ping_beta(r.squad)Ainda não está decidido como declarar com atributos em Go (O-1). Os exemplos chegam quando a forma de escrita for escolhida.
Sobrescritas, middleware e gatilhos rodam todos como cloud functions na plataforma, não na engine. Escreva-os em C#, TypeScript ou Python — o Unreal assina os eventos resultantes. Funções escritas na engine estão planejadas: grafos de Blueprint e C++ (traduzido para um formato de script validado pela plataforma). Os dois são analisados e publicados como qualquer declaração; a execução continua na plataforma.
Sobrescritas, middleware e gatilhos rodam todos como cloud functions na plataforma, não na engine. Escreva-os em C#, TypeScript ou Python — o Unity assina os eventos resultantes.
A lógica da própria plataforma pode ser baixada, editada e publicada de novo. São funções, como as suas:
playserv functions pull matchmaking.match # the platform's implementation, as source
# edit: widen the skill window for the weekend event
playserv push # your version registers; the default stays as fallback
O que você personaliza aqui é lógica: as sobrescritas, os middlewares e as versões de que esta página trata.
Não dá para adicionar campos às entidades da plataforma
Um jogador, uma sala, um registro de ranking e um pedido são estado do sistema, com as próprias máquinas de estados, e não o ponto de partida do seu modelo de dados. Os seus dados são uma entidade sua, declarada em Esquema como código e ligada ao estado da plataforma por um predicado: o dono é este jogador, o escopo é esta sala. Isso também garante que eles continuem seus quando o modelo da própria plataforma mudar.
Erros
- Um nome que não corresponde a nenhum handler registrado responde
not found— uma chamada nunca é descartada em silêncio só porque ninguém estava ouvindo. - Um nome registrado duas vezes, e um conjunto com duas implementações para a mesma condição, são recusados quando o conjunto é declarado: no deploy, não na hora da chamada.
- A recusa de um hook traz o código e o motivo do próprio hook, então “uma regra do jogo disse não” nunca chega parecendo uma falha de transporte.
- Um hook que falha se comporta conforme o tipo declarado —
fail-openoufail-closed—, e qual dos dois ele é foi declarado, não deduzido do que aconteceu. - Um hook não pode mudar o que já foi definido: nem o dono, nem o alvo, nem o ranking ou a conversa a que a chamada se dirigia. Ele corrige entradas e devolve um veredito.
Limites
Os números chegam com o capítulo de limites da plataforma; o que acontece em cada teto já está definido.
- O prazo de execução de um hook: passado o prazo, vale o comportamento em caso de falha que o tipo dele declara.
- Hooks numa mesma posição e implementações de um mesmo método: registrar mais um é recusado.
- A profundidade de aninhamento de “um hook invoca uma operação que tem hooks”: uma recusa declarada, nunca esgotamento de recursos.
- O tamanho do contexto passado a um hook: truncar é proibido, então o registro é recusado em vez de um handler receber metade de um contexto.
Lições e receitas: um ranking no Tanks usa os hooks deste módulo; um torneio diário passa por este módulo.