Last Updated on April 20, 2025 by chase
This C# CallbackEventHandler
enables the registration and dispatching of event categories and specific events across multiple submodules. Within a different submodule, you can create, register, and dispatch events within a designated category, allowing systems to receive and handle events as needed. While this implementation utilizes Unity for log output, it can be adapted for general-purpose use.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
using System; using System.Collections.Generic; using UnityEngine; namespace Chazix { using EventCallbackMap = Dictionary<CallbackEventHandler.EventCategory, Dictionary<Enum, List<CallbackEventHandler.RegisteredEventInfo> > >; public sealed class CallbackEventHandler { internal enum EventCategoryType { e_global, e_game } // base class to inherit from for sending and handling event information public class EventInfo { } // register your own EventCategory in your own submodule using the CreateEventCategory function public class EventCategory { public int Id { get; set; } } // allows being able to keep track of registered events as needed public class RegisteredEventInfo : IEquatable<RegisteredEventInfo> { public int m_id { get; set; } public Action<EventInfo> m_callback; public bool Equals(RegisteredEventInfo other) { return m_id == other.m_id; } } private static CallbackEventHandler s_callbackEventHandler = null; internal static CallbackEventHandler Get() { if (s_callbackEventHandler == null) { s_callbackEventHandler = new CallbackEventHandler(); } return s_callbackEventHandler; } // used to communicate specific events per match private static EventCallbackMap s_eventCallbacks = new EventCallbackMap(); // used to communicate specific events for the entire app session private static EventCallbackMap s_globalEventCallbacks = new EventCallbackMap(); private static int s_eventCategoryIdCounter = 0; private static int s_eventIdCounter = 0; // ---------------------------------------------------------------------- private EventCallbackMap Callbacks(EventCategoryType callbackType) { switch (callbackType) { case EventCategoryType.e_global: return s_globalEventCallbacks; case EventCategoryType.e_game: return s_eventCallbacks; default: break; } throw new NotImplementedException( string.Format( "invalid {1}: {0}", nameof(EventCategoryType), callbackType) ); } internal EventCategory CreateEventCategory(EventCategoryType callbackType = EventCategoryType.e_game) { EventCategory eventCategory = new EventCategory { Id = s_eventCategoryIdCounter++ }; Callbacks(callbackType).Add(eventCategory, new Dictionary<Enum, List<RegisteredEventInfo>>()); return eventCategory; } // call back registration and execution // note: make sure to keep in mind if only the owner needs to Register the event internal List<RegisteredEventInfo> RegisterEvent( EventCategory eventCategory, Enum eventMask, Action<EventInfo> callback = null, EventCategoryType callbackType = EventCategoryType.e_game ) { var eventCallbacks = Callbacks(callbackType); if (eventCallbacks.ContainsKey(eventCategory) == false) { Debug.LogWarning(string.Format( "{0} => invalid {1} passed in: {2}", nameof(RegisterEvent), typeof(EventCategory).ToString(), eventCategory) ); return null; } List<RegisteredEventInfo> registeredEvents = new List<RegisteredEventInfo>(); var categoryEventCallbacks = eventCallbacks[eventCategory]; foreach (Enum e in Enum.GetValues(eventMask.GetType())) { if (eventMask.HasFlag(e) == false) { continue; } Enum eventType = e; if (categoryEventCallbacks.ContainsKey(eventType) == false) { List<RegisteredEventInfo> callbackEvents = new List<RegisteredEventInfo>(); categoryEventCallbacks.Add(eventType, callbackEvents); categoryEventCallbacks[eventType] = callbackEvents; } // such as in the case when we are initializing the event if (callback == null) { continue; } RegisteredEventInfo registeredEvent = new RegisteredEventInfo { m_id = s_eventIdCounter++, m_callback = callback }; categoryEventCallbacks[eventType].Add(registeredEvent); // allow the user to keep track of registered events as needed registeredEvents.Add(registeredEvent); } return registeredEvents; } // note: make sure to keep in mind if only the owner needs to Unregister the event internal void UnregisterEvent( EventCategory eventCategory, Enum eventMask, RegisteredEventInfo toUnregister = null, EventCategoryType callbackType = EventCategoryType.e_game ) { var eventCallbacks = Callbacks(callbackType); if (eventCallbacks.ContainsKey(eventCategory) == false) { Debug.LogWarning(string.Format( "{0} => invalid {1} passed in: {2} -> callbackType: {3}", nameof(UnregisterEvent), typeof(EventCategory).ToString(), eventCategory.Id, callbackType) ); return; } var categoryEventCallbacks = eventCallbacks[eventCategory]; foreach (Enum e in Enum.GetValues(eventMask.GetType())) { if (eventMask.HasFlag(e) == false) { continue; } Enum eventType = e; // no events were added if (categoryEventCallbacks.ContainsKey(eventType) == false) { continue; } // will remove all callbacks for the event if none are specified if (toUnregister == null) { categoryEventCallbacks[eventType].Clear(); continue; } // will remove a specific callback for the event categoryEventCallbacks[eventType].Remove( categoryEventCallbacks[eventType].Find(rei => rei.Equals(toUnregister)) ); } } // unregisters the entire event category internal void UnregisterEvents( EventCategory eventCategory = null, EventCategoryType callbackType = EventCategoryType.e_game ) { var eventCallbacks = Callbacks(callbackType); // clear everything if this is null if (eventCategory == null) { foreach (var category in eventCallbacks) { foreach (var events in category.Value) { events.Value.Clear(); } category.Value.Clear(); } return; } if (eventCallbacks.ContainsKey(eventCategory) == false) { Debug.LogWarning(string.Format( "{0} => invalid {1} passed in: {2}", nameof(UnregisterEvents), typeof(EventCategory).ToString(), eventCategory) ); return; } var categoryEventCallbacks = eventCallbacks[eventCategory]; foreach (var events in categoryEventCallbacks) { events.Value.Clear(); } categoryEventCallbacks.Clear(); } internal void DispatchEvent( EventCategory eventCategory, Enum eventMask, EventInfo eventInfo, EventCategoryType callbackType = EventCategoryType.e_game ) { var eventCallbacks = Callbacks(callbackType); if (eventCallbacks.ContainsKey(eventCategory) == false) { Debug.LogWarning(string.Format( "{0} => invalid {1} passed in: {2}", nameof(DispatchEvent), typeof(EventCategory).ToString(), eventCategory) ); return; } var categoryEventCallbacks = eventCallbacks[eventCategory]; foreach (Enum e in Enum.GetValues(eventMask.GetType())) { if (eventMask.HasFlag(e) == false) { continue; } Enum eventType = e; if (categoryEventCallbacks.ContainsKey(eventType) == false) { continue; } foreach (var registeredEvent in categoryEventCallbacks[eventType]) { registeredEvent.m_callback(eventInfo); } } } // ---------------------------------------------------------------------- } } |
Here is an example use case:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
namespace Chazix { internal sealed class EventSampleManager { // -------------------------------------------------------------------------------- internal enum EventType { e_success = 1 << 0, e_error = 1 << 1 } internal class SuccessEventInfo : CallbackEventHandler.EventInfo { internal string SuccessResult { get; set; } } internal class ErrorEventInfo : CallbackEventHandler.EventInfo { internal string ErrorReport { get; set; } } internal static readonly CallbackEventHandler.EventCategory c_eventSampleCategory = CallbackEventHandler.Get().CreateEventCategory(CallbackEventHandler.EventCategoryType.e_global); // -------------------------------------------------------------------------------- private static readonly System.Lazy<EventSampleManager> s_eventSampleManager = new(() => new EventSampleManager()); internal static EventSampleManager Get() => s_eventSampleManager.Value; private EventSampleManager() { CallbackEventHandler.Get().RegisterEvent( c_eventSampleCategory, EventType.e_success | EventType.e_error, callbackType: CallbackEventHandler.EventCategoryType.e_global ); } internal void SampleSuccessFunc(string success) { CallbackEventHandler.Get().DispatchEvent( c_eventSampleCategory, EventType.e_success, new SuccessEventInfo { SuccessResult = success }, callbackType: CallbackEventHandler.EventCategoryType.e_global ); } internal void SampleErrorFunc(string error) { CallbackEventHandler.Get().DispatchEvent( c_eventSampleCategory, EventType.e_error, new ErrorEventInfo { ErrorReport = error }, callbackType: CallbackEventHandler.EventCategoryType.e_global ); } } } |
Then in any other module, internal in this example case – we can register to this event for independent handling:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
namespace Chazix { internal class EventReceiverHandlerSample { internal EventReceiverHandlerSample() { CallbackEventHandler.Get().RegisterEvent( EventSampleManager.c_eventSampleCategory, EventSampleManager.EventType.e_success, OnSuccessEventReceived, callbackType: CallbackEventHandler.EventCategoryType.e_global ); CallbackEventHandler.Get().RegisterEvent( EventSampleManager.c_eventSampleCategory, EventSampleManager.EventType.e_error, OnErrorEventReceived, callbackType: CallbackEventHandler.EventCategoryType.e_global ); } protected void OnSuccessEventReceived(CallbackEventHandler.EventInfo eventInfo) { if (eventInfo is not EventSampleManager.SuccessEventInfo successInfo) return; Console.WriteLine(successInfo.SuccessResult); } protected void OnErrorEventReceived(CallbackEventHandler.EventInfo eventInfo) { if (eventInfo is not EventSampleManager.ErrorEventInfo errorInfo) return; Console.WriteLine(errorInfo.ErrorReport); } } } |