Signals Module
Signals
Module
구성 요소 간 결합도를 제거하는 타입 안전 이벤트 버스입니다.
발신자(Publisher)와 수신자(Subscriber)는 서로를 전혀 알지 못합니다.
SignalHub가 유일한 중개자이며, 제네릭 타입 또는 정수 ID 두 가지 방식으로
시그널을 발행·구독할 수 있습니다.
구독은 IDisposable을 반환하므로 using으로 생명주기를 자동 관리합니다.
흐름 구조
Publisher와 Subscriber는 SignalHub를 통해서만 연결됩니다. 양측은 서로의 존재를 알 필요가 없습니다.
SignalHub.Publish(signal) Subscribe<TSignal>(handler) Subscribe(signalId, handler) SignalBus는 리스너를 Type과 int SignalId 두 딕셔너리로 동시에 관리합니다.
발행 시 두 인덱스 모두에 dispatch되므로, 타입 구독자와 ID 구독자가 동시에 알림을 받습니다.
dispatch 전 리스너 목록을 스냅샷하여 콜백 중 구독 해제가 안전하게 동작합니다.
핵심 클래스
각 클래스의 역할과 계층 구조입니다.
모든 시그널의 기본 계약. SignalId()로 정수 ID를 제공하며, ISignal<T>는 Payload()와 Publish()를 추가합니다.
구현 기반 클래스. Signal은 페이로드 없는 이벤트, Signal<T>는 값을 전달합니다. 정적 팩토리 메서드 Publish<TSignal>(payload)로 생성·발행을 한 번에 처리합니다.
전역 정적 퍼사드. 내부적으로 싱글턴 SignalBus를 감쌉니다. 팩토리 기반 발행도 지원합니다 — ISignalFactory로 복잡한 페이로드 변환을 분리할 수 있습니다.
Signal ID(int) ↔ Type 매핑 레지스트리. ID만 알고 있을 때 런타임에 시그널을 생성하거나 타입을 조회할 수 있습니다. Flow 노드와 에디터 도구에서 ID 기반 dispatch에 사용됩니다.
런타임과 에디터 진입 시 자동 초기화됩니다. [RuntimeInitializeOnLoadMethod]와 [InitializeOnLoadMethod]로 수동 호출 없이 등록이 완료됩니다. 여러 ISignalsBootstrap을 BootstrapKey로 중복 없이 관리합니다.
ScriptableObject 기반 팩토리. 데이터 모델 → 시그널 변환 로직을 에셋으로 분리합니다. SignalHub.Publish(payload, factory)와 함께 사용됩니다.
사용 패턴
페이로드가 없으면 Signal, 값을 전달하면 Signal<T>를 상속합니다. Id는 고유한 정수값으로 지정합니다. [SignalId] 어트리뷰트를 인스펙터 필드에 붙이면 에디터에서 문자열 → FNV-1a 해시 ID로 자동 동기화됩니다.
런타임 초기화 시 Signal.Register<T>()로 시그널을 SignalTypeRegistry에 등록합니다. SignalBootstrap이 자동 호출하므로 수동 초기화 코드가 필요하지 않습니다.
반환값 IDisposable을 보관하고 있다가 Dispose()로 해제합니다. using var 문법으로 자동 해제도 가능합니다. 두 가지 방식 모두 동일한 버스를 공유합니다.
페이로드가 있는 경우 정적 팩토리 메서드를 권장합니다. 내부에서 인스턴스를 생성하고 즉시 발행합니다.
Flow 노드 연동
Signals 모듈은 Flow 시스템의 SendSignalNode와 WaitForSignalNode에 통합되어 있습니다. 노드 인스펙터에서 [SignalId] 필드로 시그널을 선택합니다.
SignalTypeRegistry.TryCreateSignal(signalId)로 시그널 인스턴스를 생성한 뒤 즉시 SignalHub.Publish()합니다. Flow 진입 즉시 실행되며 다음 노드로 즉각 이동합니다.
지정한 시그널이 도착할 때까지 Flow 실행을 일시 중단합니다. TaskCompletionSource로 대기하다가 시그널 수신 시 완료 처리됩니다. 구독은 IDisposable로 자동 정리됩니다.
에디터 지원
int 필드에 붙이면 인스펙터에서 문자열로 입력 가능합니다. SignalIdUtil.HashId() (FNV-1a)로 정수 ID를 자동 계산하여 동기화합니다. 휴먼 리더블한 이름으로 시그널을 관리할 수 있습니다.
커스텀 시그널 등록 로직을 에셋으로 분리합니다. Register()를 오버라이드하여 조건부 등록이나 런타임 주입 패턴에 활용합니다.