Company
교육 철학

EventPool 가이드

EventPool<T> = 게임 안의 이벤트 버스.
이벤트를 구독/발행하고, Update() 때 큐에서 꺼내 핸들러를 호출해줘. 이벤트 타입은 BaseEventArgs를 상속하고 Id(이벤트 번호)로 라우팅돼.

1) 큐잉 & 처리 (Fire vs FireNow)

비유

큐(queue) = “할 일 바구니”.
Fire(sender, e)는 이벤트 쪽지를 바구니에 넣기만 해.
Update()에서 한 장씩 꺼내 읽고(핸들러 호출) 처리해.

왜 이렇게 해?

게임은 한 프레임 안에서 순서대로 일이 처리되어야 안전해.
Fire는 어디(다른 스레드/비동기)에서 불러도 안전하게 “일단 넣고”다음 프레임에 메인 루프에서 처리하게 함 (스레드-세이프).

그럼 FireNow는?

FireNow는 바구니에 넣지 않고 바로 읽어버리는 것.
그래서 지금 이 순간 즉시 실행되지만, 메인 스레드가 아닐 때 위험(스레드-세이프 아님).
타임라인 예) 프레임 N: 네트워크 스레드 → Fire(이벤트) // 바구니에 넣음(OK) 메인 스레드 → Update() // 바구니에서 꺼내 핸들러 호출 프레임 N: 네트워크 스레드 → FireNow(...) // ⚠ 즉시 실행. 메인 스레드가 아니면 위험
Plain Text
복사

2) 구독/해지 (Subscribe / Unsubscribe)

비유

Subscribe(구독) = “이 번호(이벤트 ID)의 쪽지 오면 나한테도 알려줘!”
Unsubscribe(해지) = “이제 그 쪽지 안 받아요.”

코드 감

events.Subscribe(PlayerDamagedEventArgs.EventId, OnPlayerDamaged); events.Unsubscribe(PlayerDamagedEventArgs.EventId, OnPlayerDamaged);
C#
복사
같은 이벤트 번호로 여러 핸들러를 붙일 수도 있고(모드에 따라), 하나만 허용할 수도 있어.

3) 모드 (규칙 세트)

이벤트 버스가 어떤 규칙으로 운영될지 미리 정하는 스위치야.
Default
한 이벤트 ID에 핸들러 1개만 허용. (중복 등록하면 오류)
AllowNoHandler
구독자가 없어도 조용히 무시. (오류 없음)
AllowMultiHandler
같은 ID에 여러 핸들러 허용. (등록 순서대로 호출)
AllowDuplicateHandler
똑같은 핸들러중복 등록도 허용. (보통은 실수라서 잘 안 씀)
팀 스타일에 맞춰 켜면 돼.
예: 실수 줄이려면 Default(중복 금지), 브로드캐스트 많이 쓰면 AllowMultiHandler.

4) 기본 핸들러 (SetDefaultHandler)

비유

구독자가 아무도 없을 때 대신 받는 수신함.

동작

어떤 이벤트가 왔는데 그 ID에 구독자가 없다?
기본 핸들러가 등록돼 있으면 그걸 호출.
기본 핸들러도 없고 AllowNoHandler도 꺼져 있으면 예외(개발 중 버그 찾기 좋음).
events.SetDefaultHandler((sender, e) => { Debug.LogWarning($"Unhandled event: {e.Id}"); });
C#
복사

5) 풀링 (ReferencePool) – “재활용 상자”

비유

이벤트 객체와 이벤트 인자(e)는 종이컵 같은 거야.
매번 새로 만들고 버리면 쓰레기(GC)가 많아져서 프레임이 끊길 수 있어.
그래서 **재활용 상자(ReferencePool)**에 넣었다가 다시 꺼내 쓰기.

중요한 규칙 (진짜 중요!)

핸들러가 끝나면 e(이벤트 인자)는 재활용 상자에 돌려보내짐.
그러니까 핸들러 밖에서 e를 보관하지 마. 필요한 값만 복사해서 들고 있어.
void OnPlayerDamaged(object sender, PlayerDamagedEventArgs e) { // ❌ 이렇게 저장하면 나중에 e가 재활용되어 엉뚱해질 수 있음 // _lastEvent = e; // ✅ 필요한 값만 복사 lastDamage = e.Damage; lastHp = e.HpAfter; }
C#
복사

전체 그림 요약

다른 스레드/시스템 ── Fire(이벤트) ──▶ [큐] ── Update() ──▶ 구독한 핸들러들 호출 ▲ FireNow(즉시호출, 메인 스레드에서만 안전) 모드: 중복/다중/무구독 허용 규칙 기본 핸들러: 구독자 없으면 대신 처리 풀링: 이벤트/인자 재사용(핸들러 밖 보관 금지)
Plain Text
복사

작은 실전 예

// 1) 이벤트 정의 public sealed class PlayerDamagedEventArgs : BaseEventArgs { public static readonly int EventId = 1001; public override int Id => EventId; public int Damage, HpAfter; public static PlayerDamagedEventArgs Create(int dmg, int hp) { var e = ReferencePool.Acquire<PlayerDamagedEventArgs>(); e.Damage = dmg; e.HpAfter = hp; return e; } public override void Clear() { Damage = 0; HpAfter = 0; } } // 2) 풀 만들기 & 구독 var events = new EventPool<PlayerDamagedEventArgs>( EventPoolMode.AllowMultiHandler | EventPoolMode.AllowNoHandler); events.Subscribe(PlayerDamagedEventArgs.EventId, (s, e) => ui.ShowHit(e.Damage)); events.SetDefaultHandler((s, e) => Debug.Log($"Unhandled: {e.Id}")); // 3) 매 프레임 처리 void Update() => events.Update(Time.deltaTime, Time.unscaledDeltaTime); // 4) 발행 void ApplyDamage(int dmg) { hp -= dmg; events.Fire(this, PlayerDamagedEventArgs.Create(dmg, hp)); // 큐에 넣고 다음 프레임 처리 // events.FireNow(this, PlayerDamagedEventArgs.Create(dmg, hp)); // 즉시 호출(메인에서만) }
C#
복사

한 문장으로 다시 정리

Fire= 안전하게 “다음 프레임에 처리”, FireNow= 지금 즉시(메인 전용)
Subscribe/Unsubscribe= 누가 어떤 쪽지를 받을지 등록/해지
모드= 중복/다중/무구독 허용 규칙
기본 핸들러= 아무도 안 받으면 여기로
풀링= 이벤트/인자 재사용 → 핸들러 밖에서 e 보관 금지