Эта страница ещё не переведена — показана английская версия.
Extensibility
Любой сценарий платформы — это цепочка зарегистрированных функций, и любое звено можно заменить или обе рнуть. Чтобы изменить поведение платформы, вы заменяете её шаги, а не форкаете исходники.
Когда это нужно
- Шаг платформы должен выполнять вашу логику. Объявите замену именованного звена через
[Override(…)]. - Вокруг шага нужны проверки или побочные эффекты. Для этого есть упорядоченные промежуточные обработчики (middleware)
Before/After: они могут запретить шаг или уведомить о нём. - Код должен запускаться по расписанию, по событию или по вебхуку. Триггеры передают вам уже разобранный типизированный контекст.
- До деплоя нужно знать, что на самом деле выполнится. Сделайте пробный прогон цепочки и посмотрите итоговый порядок.
- Не нужно, если правило касается записей одной сущности. Для этого есть более лёгкий вариант — хук Data & Subscriptions.
Кто что делает
| Актор | Что делает на этой странице |
|---|---|
backend-service | переопределяет звенья, оборачивает шаги промежуточными обрабо тчиками, пишет обработчики триггеров |
operator | просматривает цепочки, задаёт порядок, читает секреты, делает пробный прогон разрешения цепочки |
Коротко
SignIn, промежуточный обработчик вокруг grant и код по 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(): ...Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.
Переопределения, промежуточные обработчики и триггеры выполняются как облачные функции на платформе, а не в движке. Напишите их на C#, TypeScript или Python, а Unreal подпишется на события, которые они порождают. В планах — функции, написанные прямо в движке: графы Blueprint и C++, который будет транслироваться в скрипт, проверяемый платформой. Их будут анализировать и загружать так же, как любые объявления, а выполняться они по-прежнему будут на платформе.
Переопределения, промежуточные обработчики и триггеры выполняются как облачные функции на платформе, а не в движке. Напишите их на C#, TypeScript или Python, а Unity подпишется на события, которые они порождают.
Модель
К чему можно подключиться.
| Термин | Что это |
|---|---|
зарегистрированная функция | один переопределяемый шаг платформы — «создать профиль», «определить цену» |
scenario | упорядоченная цепочка, которую выполняет процесс платформы: авторизация, вход, покупка |
overridability | можно ли звено заменить, только обернуть или его нельзя трогать |
middleware | упорядоченный обработчик до или после звена |
trigger | то, что запускает ваш код: событие, расписание, вебхук |
secret | значение, которое может читать ваш обработчик |
invocation | один запуск вместе с его трассировкой |
Что объявляет хук.
| Что объявляет | Что это |
|---|---|
position | именованный шаг, к которому подключается хук |
kind | gatekeeper — проверка допуска или валидация. При сбое он блокирует: если сломался сам хук, шаг не выполняется. observer — журнал, уведомление, счётчик. При сбое он пропускает: шаг выполняется, но об ошибке всё равно сообщается, а не замалчивается. Значения по умолчанию нет |
moment | before — до валидации: хук получает типизированные данные и может их изменить или отклонить. after — когда шаг уже зафиксирован: хук получает запрос и результат и может только добавить побочные эффекты. Сорвать операцию или изменить ответ он не может |
effect | что хук делает, а не только где он стоит. Тогда порядок описывается через саму работу, а не через числа, и не ломается, когда кто-то добавит хук рядом с вашим |
версия и условие | обработчик, который действует только для одного окружения (environment) или одной аудитории, — это объявленная версия хука, а не ветка внутри его тела. Именно её включает и выключает переключатель в панели |
Три способа подключить код.
| Способ | Для чего |
|---|---|
атрибут [Before(Step)] / [After(Step)] | одно правило на одном именованном шаге — так пишется большинство хуков |
атрибут [Override(Link)] | полная замена реализации звена |
Extend.Scenario("…").Before("link", fn, order: n) | обёртка звена внутри цепочки, когда важен порядок относительно других промежуточных обработчиков |
| Всегда | Что это |
|---|---|
все три способа | деплоятся через playserv push |
два способа с атрибутами | их показывает панель, потому что объявление переносит имя шага или звена в загруженную модель |
способ с промежуточным обработчиком | вместо имени несёт порядок — а цепочке нужен именно он |
присваивание при запуске | (Scenario.OnX = fn) по-прежнему доступно для обработчика, которому не нужно появляться в дереве админ-панели |
замена одного звена | соседние звенья остаются нетронутыми, и ни одно из них не знает, какая реализация ответила — собственный шаг платформы или ваш |
Куда уходит вызов и что верно для любого маршрута.
| Всегда | Что это |
|---|---|
четыре направления | облачная функция · внешний бэкенд потребителя · игровой сервер · другое объявленное направление |
маршрутизатор | работает на сообщениях и сигналах; request-response — лишь один из адаптеров к нему, а не его природа |
сопоставление | идёт по объявленному имени операции или сигнала и больше ни по чему: не по форме данных, не по вызывающему, не по нагрузке |
направление | не входит в контракт операции: перенос обработчика в другое направление не ломает совместимость |
«игровой сервер» | определяется тем, что он делает, а не тем, кто его хостит. Наш парк серверов и собственный хостинг студии — одно направление, и в объявлении нет пометки о том, чья это инфраструктура |
RPC игрового сервера | регистрируются в том же маршрутизаторе: объявить такой RPC — это и есть его зарегистрировать, второго способа нет |
порядок | промежуточные обработчики выполняются сверху вниз. Если у шага несколько реализаций, маршрутизатор выбирает слева направо по условию, а если ничего не подошло, отвечает версия, помеченная как версия по умолчанию |
Цепочку с переопределениями и промежуточными обработчиками можно разрешить и прочитать до того, как что-либо запустится. Итоговый порядок виден и в панели, и из кода.
Что такое ограничение между хуками и когда оно проверяется.
| Что это | |
|---|---|
названное ограничение | точка расширения может назвать эффекты, которые она ограничивает: проверка античита должна идти перед размещением, чек требует списания в этой точке. Больше она ничего не ограничивает |
неназванный эффект | ничем не ограничен, но и не запрещён |
нарушение | дефект конфигурации. В отказе названы оба хука и ограничение, которое они нарушили. Это не предупреждение и не молчаливая перестановка |
когда проверяется | при каждом действии, которое может изменить то, что выполняется в точке: регистрации, деплое, изменении расстановки. Поэтому расстановка, дошедшая до выполнения, уже прошла проверку |
без повторной проверки при выполнении | принятую расстановку при выполнении повторно не проверяют |
| Всегда | Что это |
|---|---|
обработчики | типизированы на входе и выходе: никаких dynamic и мешков с контекстом. Доступ к платформе есть неявно, а контекст запуска — вызывающий, триггер, трассировка — приходит уже разобранным |
что видит сборка движка | события, которые сценарий отправляет после шага. Переопределения и промежуточные обработчики выполняются на платформе, и среде выполнения движка они не место. Именно это и показывают вкладки движков в примерах на этой странице |
[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)Как объявлять через атрибуты в Go, пока не решено (O-1). Примеры появятся, когда выберут способ записи.
Переопределения, промежуточные обработчики и триггеры выполняются как облачные функции на платформе, а не в движке. Напишите их на C#, TypeScript или Python, а Unreal подпишется на события, которые они порождают. В планах — функции, написанные прямо в движке: графы Blueprint и C++, который будет транслироваться в скрипт, проверяемый платформой. Их будут анализировать и загружать так же, как любые объявления, а выполняться они по-прежнему будут на платформе.
Переопределения, промежуточные обработчики и триггеры выполняются как облачные функции на платформе, а не в движке. Напишите их на C#, TypeScript или Python, а Unity подпишется на события, которые они порождают.
Собственную логику платформы можно выгрузить, изменить и задеплоить заново. Это такие же функции, как ваши:
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
Настраиваете вы здесь именно логику: переопределения, промежуточные обработчики и версии, о которых эта страница.
В сущности платформы нельзя добавлять поля
Игрок, комната, запись в таблице лидеров и заказ — это системное состояние со своими машинами состояний, а не заготовка для вашей модели данных. Ваши данные — это ваша собственная сущность (entity). Её объявляют так, как описано на странице Схема в коде, и привязывают к состоянию платформы предикатом: владелец — этот игрок, область — эта комната. Так данные остаются вашими, даже когда меняется модель самой платформы.
Ошибки
- Если имени не соответствует ни один зарегистрированный обработчик, ответ —
not found. Вызов никогда не пропадает молча из-за того, что его никто не слушал. - Имя, зарегистрированное дважды, и набор, где две реализации претендуют на одно и то же условие, отклоняются в момент объявления набора — при деплое, а не при вызове.
- Отказ хука несёт собственный код и причину хука, поэтому «правило игры сказало нет» никогда не будет выглядеть как сбой транспорта.
- Хук, который упал, ведёт себя по объявленному виду —
fail-openилиfail-closed. Какой именно, объявлено заранее, а не выводится из того, что произошло. - Хук не может менять то, что уже заявлено: ни владельца, ни цель, ни таблицу или беседу, которым адресован вызов. Он исправляет входные данные и возвращает вердикт.
Ограничения
Конкретные числа появятся вместе с главой об ограничениях платформы, а поведение на каждой границе уже определено.
- Срок выпо лнения хука: если он превышен, хук ведёт себя так, как объявлено для сбоя в его виде.
- Число хуков на одном шаге и число реализаций одного метода: регистрация сверх предела отклоняется.
- Глубина вложенности вида «хук вызывает операцию, у которой есть хуки»: объявленный отказ, а не исчерпание ресурсов.
- Размер контекста, передаваемого хуку: обрезать его запрещено, поэтому отклоняется регистрация, а не обработчик получает половину контекста.
Уроки и рецепты: таблица лидеров в Tanks использует хуки этого модуля, а ежедневный турнир целиком построен на нём.