상세 컨텐츠

본문 제목

1205 TIL - 이벤트 버스

스파르타 코딩캠프/'24 Today I Learned

by lucar 2024. 12. 5. 21:29

본문

어제 대충 코드만 올린거 살짝 설명해보려고한다.

 

public class EventManager : SingletonDDOL<EventManager>
{
    public Dictionary<Channel, List<Delegate>> Events = new Dictionary<Channel, List<Delegate>>();

    public void Subscribe(Channel channel, UnityAction listener)
    {
        if(Events?.ContainsKey(channel) == false)
        {
            Events[channel] = new List<Delegate>();
        }

        Events?[channel].Add(listener);
    }

    public void Subscribe<T>(Channel channel, UnityAction<T> listener) where T : IEventObject
    {
        if (Events?.ContainsKey(channel) == false)
        {
            Events[channel] = new List<Delegate>();
        }

        Events?[channel].Add(listener);
    }

    public void Unsubscribe(Channel channel, UnityAction listener)
    {
        if(Events.TryGetValue(channel, out var UnityActions))
        {
            UnityActions.Remove(listener);
        }
    }

    public void Unsubscribe<T>(Channel channel, UnityAction<T> listener) where T : IEventObject
    {
        if (Events.TryGetValue(channel, out var UnityActions))
        {
            UnityActions.Remove(listener);
        }
    }

    public void Publish<T>(Channel channel, T parameter)
    {
        if (Events?.ContainsKey(channel) == false) return;

        var UnityActions = Events?[channel];
        if (UnityActions == null) return;

        foreach (var action in UnityActions)
        {
            if(action.GetType() == typeof(UnityAction<T>))
            {
                ((UnityAction<T>)action)(parameter);
            }
        }
    }
}

 

우선 이벤트 버스이다.

 

이벤트 버스는 크게 3가지 부분으로 나누어지는데 머리, 몸통 배 

Subscribe, Unsubscribe, Publish이다.

 

Subscibe를 통해 구독 또는 Unsubscribe를 통해 구독 취소를 할 수 있으며

Publish를 통해 출판한 이벤트를 구독한 클래스에서 실행하게 된다.

 

출처 : 마이크로소프트 https://learn.microsoft.com/ko-kr/dotnet/architecture/microservices/multi-container-microservice-net-applications/integration-event-based-microservice-communications

 

이벤트 버스의 장점으로는

컴포넌트간의 의존성을 줄이고 느슨한 결합을 만들어줄수 있으며 (객체지향)

새로운 이벤트가 추가될 때에 구독만 해주면 되기 때문에 확장성이 좋다. (높은 확장성)

 

하지만 이벤트버스를 과용하게 되면 어디서 발생한 이벤트가 어디서 처리되는지 알 수 없게되고 (높은 복잡성)

이벤트 처리가 너무 많은 경우 구독 중인 모든 컴포넌트에게 Publish하기 때문에 성능 문제가 발생할 수 있다.(성능 이슈)

 

이제 위에 코드를 좀 뜯어보자.

 

우선 필요없는 정보까지 다 나눠줄 필요는 없으니 열거형 타입을 사용해 수신 채널을 나눠주도록 하자.

이번 도전과제 구현 중에 사용한 열거형들이다.

public enum Channel
{
    None,
    Achievement
}

public enum AchievementType
{
    None,
    KillMonster,
    ClearStage,
    CollectGold,
    CollectSoul,
    Time
}


public enum ActionType
{
    None,
    Kill,
    Gold,
    Stage,
    Soul,
    Time
}

 

그 후에 Dictionary 타입으로 Delegate를 담아줄 컨테이너를 만들고

public Dictionary<Channel, List<Delegate>> Events = new Dictionary<Channel, List<Delegate>>();

Delegate형식으로 리스트를 만들면 Func든 Action이든 UnityAction이든 들어가긴한다.

 

구독을 구현해준다.

public void Subscribe<T>(Channel channel, UnityAction<T> listener) where T : IEventObject
    {
        if (Events?.ContainsKey(channel) == false)
        {
            Events[channel] = new List<Delegate>();
        }

        Events?[channel].Add(listener);
    }

대충 구독신청을 해서 있으면 리스트에 추가, 없으면 새 리스트를 만들어주는 로직이다.

 

구독 해제는 반대다.

public void Unsubscribe<T>(Channel channel, UnityAction<T> listener) where T : IEventObject
    {
        if (Events.TryGetValue(channel, out var UnityActions))
        {
            UnityActions.Remove(listener);
        }
    }

있든 없든 Remove를 시도하는 것 자체는 에러를 발생시키지 않는다.

 

마지막으로 퍼블리쉬를 작성해준다.

public void Publish<T>(Channel channel, T parameter)
    {
        if (Events?.ContainsKey(channel) == false) return;

        var UnityActions = Events?[channel];
        if (UnityActions == null) return;

        foreach (var action in UnityActions)
        {
            if(action.GetType() == typeof(UnityAction<T>))
            {
                ((UnityAction<T>)action)(parameter);
            }
        }
    }

 

사용법은 Channel을 지정해주고 (위의 열거형)

이벤트를 발생시킬 변수를 넣어주면 된다.

 

예시로 도전과제 구현 중에 사용한 걸 가져왔다.

 


private void Start()
{
	EventManager.Instance.Subscribe<AchieveEvent>(Channel.Achievement, OnTriggerAction);
}

public void OnTriggerAction(AchieveEvent data)
{
    aDatas.Clear();
    if ((int)data.Action == 0 || (int)data.Type == 0) return;
    foreach (AchieveData aData in EventToData(data.Action, data.Type))
    {
        aData.AddProgress(data.Value);
    }
}
public void Publish()
{
    EventManager.Instance.Publish<AchieveEvent>(Channel.Achievement,
        new AchieveEvent(AchievementType.KillMonster, ActionType.Kill, 1));
}

 

OnTriggerAction은 보시다시피 매개변수로 AchieveEvent타입을 가지고 있고

변수가 있는 Action형이기 때문에 Publish할때에 제네릭 T에 해당 변수형을 넣어준다.

 

참고로 AchieveEvent는 아래와 같다.

public interface IEventObject
{

}

public class AchieveEvent : IEventObject
{
    public ActionType Action;
    public AchievementType Type;
    public int Value;

    /// <summary>
    /// ex)new AchieveEvent(AchievementType.KillMonster, ActionType.Kill, 1)
    /// </summary>
    /// <param name="action">enums ActionType</param>
    /// <param name="type">enums AchievementType</param>
    /// <param name="value">이벤트 발생 시 증가할 양</param>
    public AchieveEvent(AchievementType type, ActionType action, int value)
    {
        Action = action;
        Type = type;
        Value = value;
    }
}

 

생성자를 통해 AchieveEvent가 전달할 내용을 입력해줄 수 있게끔 만들어줬다.

 

TTE

 

'스파르타 코딩캠프 > '24 Today I Learned' 카테고리의 다른 글

1209 TIL - 가챠 연출  (0) 2024.12.09
1206 TIL - 날아가버리고 말았어요  (0) 2024.12.06
1203 TIL - 에셋 탐방  (1) 2024.12.03
1202 TIL - 재사용 스크롤  (0) 2024.12.02
1129 TIL - 스크롤 뷰  (1) 2024.11.29

관련글 더보기