Signal
Описание
Модуль Signal обеспечивает механизм событийного взаимодействия между различными частями приложения. Позволяет декуплировать отправителей и получателей событий, поддерживает как синхронные, так и асинхронные слушатели, обеспечивая завершение всех асинхронных операций до окончания вызова dispatch.
Назначение
- Решаемые проблемы:
- Упрощает передачу данных и событий между компонентами без жёсткой связи.
- Гарантированная обработка асинхронных операций перед завершением рассылки события.
- Возможность одноразовой подписки на событие.
- Ключевые кейсы использования:
- Внутренний шина событий в мелкомодульных системах.
- Передача уведомлений и команд между UI-компонентами.
- Организация одноразовых хуков (одиночных реакций на событие).
- Преимущества и ограничения:
-
- Поддержка async/await для слушателей.
-
- Гарантированное ожидание всех промисов.
-
- Простая отписка через
Disposable.
- Простая отписка через
- – Нет встроенной обработки ошибок слушателей (исключения не перехватываются).
- – Отсутствует приоритетизация или категория событий.
-
Архитектура
- Основные классы и интерфейсы:
Signal<T>— главный класс для создания и работы с сигналом.ISignal<T>— интерфейс с методамиsubscribe,once,unsubscribe,dispatch.Disposable— интерфейс для управления жизненным циклом подписки.Utils— вспомогательные утилиты (например, генерация UUID).
- Взаимодействие с другими модулями:
- Использует
Utils.uuid()для генерации уникального идентификатора. - Имплементирует
ISignalиз модуля моделей (./models).
- Использует
Использование
Основные сценарии
- Подписка на события:
const sig = new Signal<number>('MyEvent');
const disposable = sig.subscribe(data => console.log(data)); - Одноразовая подписка:
sig.once(async data => { /* ... */ }); - Диспатч события:
await sig.dispatch(42); - Отписка:
disposable.dispose(); - Лучшие практики:
- Всегда сохранять
Disposableдля своевременной отписки. - Не создавать бесконечные цепочки асинхронных слушателей без контроля ошибок.
- Всегда сохранять
Примеры
Простые примеры
const signal = new Signal<string>('Greeting');
signal.subscribe(msg => console.log('Получено:', msg));
signal.dispatch('Привет!');
Продвинутые примеры
const signal = new Signal<{ id: number, text: string }>('Message');
// Асинхронная подписка и одноразовая подписка
signal.subscribe(async payload => {
await saveToDB(payload);
});
signal.once(({ id }) => console.log(`Первое сообщение с id=${id}`));
await signal.dispatch({ id: 1, text: 'Hello' });
// После этого одноразовый слушатель отписан
await signal.dispatch({ id: 2, text: 'World' });
API
Классы и интерфейсы
-
Signal<T>constructor(name?: string)— создаёт сигнал с именем (по умолчанию "Signal").get name(): string | undefined— возвращает имя сигнала.get uuid(): string— уникальный идентификатор сигнала.subscribe(callback): Disposable— подписаться на событие.once(callback): Disposable— одноразовая подписка.unsubscribe(callback): void— отписка по функции.dispatch(data): Promise<void>— отправка события и ожидание всех слушателей.
-
Disposabledispose(): void— метод для отписки.
-
ISignal<T>- Интерфейс, описывающий сигнатуру всех публичных методов
Signal<T>.
- Интерфейс, описывающий сигнатуру всех публичных методов
Конфигурация
- Параметры конструктора:
name(string) — пользовательское имя сигнала.
Интеграция с ECS
Основное применение сигналов в фреймворке - это связывание Групп Систем с определёнными событиями через SignalsController. Это позволяет запускать выполнение систем в нужные моменты.
Пример использования с SignalsController
import { SignalsController } from '@shared/signals';
import { OnUpdateSignal } from '@flow/lifecycle';
// Создаём группы систем
class PhysicsGroup extends SystemGroup {
// ...
}
class RenderGroup extends SystemGroup {
// ...
}
// Создаём конфигурацию связей
const signalBindings = [
{
signal: OnUpdateSignal, // Сигнал жизненного цикла
groups: [PhysicsGroup] // Группы для выполнения
},
{
signal: OnRenderSignal,
groups: [RenderGroup]
}
];
// Регистрируем связи в контроллере
const signalsController = new SignalsController(executionController);
signalsController.listen(signalBindings);
// Теперь при отправке сигнала будут выполняться привязанные группы
await OnUpdateSignal.dispatch({ deltaTime: 16.67 });
// Выполнятся все системы в PhysicsGroup
Особенности работы с SignalsController
-
Порядок выполнения:
- Группы выполняются в порядке их регистрации в конфигурации
- Системы внутри группы выполняются согласно их приоритету
-
Асинхронность:
- Все системы в группе выполняются асинхронно
- SignalsController дожидается завершения всех систем
-
Управление выполнением:
- Можно приостанавливать и возобновлять выполнение групп
- Поддерживается отвязка групп от сигналов
Встроенные сигналы
Фреймворк предоставляет набор встроенных сигналов для типовых событий:
OnStartSignal- запуск приложенияOnUpdateSignal- обновление каждый кадрOnPauseSignal- приостановкаOnResumeSignal- возобновлениеOnStopSignal- остановка
Частые вопросы
- Что происходит при ошибке в слушателе?
Исключения в синхронных слушателях приведут к падению
dispatch, асинхронные — к отклонению промиса. - Как отписаться от одноразового слушателя?
После первого вызова слушатель автоматически удаляется, но можно вызвать
dispose(). - Можно ли использовать один и тот же callback несколько раз?
Да, каждый вызов
subscribeсоздаёт новую запись в списке слушателей.