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>
— отправка события и ожидание всех слушателей.
-
Disposable
dispose(): 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
создаёт новую запись в списке слушателей.