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 보관 금지