Skip to main content
File: Assets/_Molca/_Core/Events/EventDispatcher.cs
Subsystem: registered with RuntimeManager like other services

When to use

Use EventDispatcher when you need decoupled, cross-component communication without direct references:
  • Cross-system notifications (e.g. language changed, app initialized, session events) where publisher and subscriber live in different assemblies or scenes.
  • String-keyed events with optional typed payloads — avoids coupling to concrete event classes.
  • Prefer UnityEvent for Inspector-wired, single-object callbacks. Prefer C# event for tightly-coupled same-class patterns. Use EventDispatcher for everything else.

API

MethodSignatureDescription
RegisterEventvoid RegisterEvent(string eventName, Action callback)Subscribe to a parameterless event
RegisterEvent<T>void RegisterEvent<T>(string eventName, Action<T> callback)Subscribe to a typed-payload event
UnregisterEventvoid UnregisterEvent(string eventName, Action callback)Unsubscribe parameterless — call from OnDisable
UnregisterEvent<T>void UnregisterEvent<T>(string eventName, Action<T> callback)Unsubscribe typed — call from OnDisable
DispatchEventvoid DispatchEvent(string eventName)Publish a parameterless event
DispatchEvent<T>void DispatchEvent<T>(string eventName, T eventData)Publish a typed-payload event
ClearEventvoid ClearEvent(string eventName)Remove all handlers for a specific event
ClearAllEventsvoid ClearAllEvents()Remove all handlers (called automatically on Shutdown)

TypedEvents

File: Events/TypedEvents.cs — wraps common event names. Prefer these over magic strings where entries exist (scene load, modals, etc.).

Code

Use a normal MonoBehaviour with an injected EventDispatcher, subscribe in OnEnable, and always unsubscribe in OnDisable.
using UnityEngine;
using Molca;
using Molca.Events;

public class SessionStatusBanner : MonoBehaviour
{
    [SerializeField] private string status;

    [Inject] private EventDispatcher _eventDispatcher;

    private async void Start()
    {
        await RuntimeManager.WaitForInitialization();
        if (_eventDispatcher == null)
            RuntimeManager.InjectDependencies(this);
    }

    private void OnEnable()
    {
        if (_eventDispatcher == null) return;

        // Typed/event-constant style
        _eventDispatcher.RegisterEvent(EventConstants.Application.Initialized, OnAppInitialized);

        // Generic payload style
        _eventDispatcher.RegisterEvent<string>(EventConstants.UI.LanguageChanged, OnLanguageChanged);
    }

    private void OnDisable()
    {
        if (_eventDispatcher == null) return;
        _eventDispatcher.UnregisterEvent(EventConstants.Application.Initialized, OnAppInitialized);
        _eventDispatcher.UnregisterEvent<string>(EventConstants.UI.LanguageChanged, OnLanguageChanged);
    }

    public void MarkReady()
    {
        // Publish event to other systems
        _eventDispatcher?.DispatchEvent(EventConstants.Application.Initialized);
    }

    private void OnAppInitialized()
    {
        status = "Initialized";
    }

    private void OnLanguageChanged(string languageCode)
    {
        status = $"Language: {languageCode}";
    }
}

Troubleshooting

  • Event not received: confirm RegisterEvent runs before the first DispatchEvent. If subscription is in OnEnable but dispatch fires during RuntimeManager init, the handler may not be registered yet — move subscription to Start with await RuntimeManager.WaitForInitialization().
  • Duplicate callbacks firing: calling RegisterEvent twice with the same callback adds it twice via Delegate.Combine. Always pair with UnregisterEvent in OnDisable to prevent stacking across enable/disable cycles.
  • Missing unsubscribe causes errors after scene unload: destroyed objects’ callbacks remain registered and throw MissingReferenceException. Always call UnregisterEvent in OnDisable or OnDestroy.
  • Wrong type parameter on DispatchEvent<T>: the type key must match exactly between publisher and subscriber. DispatchEvent<int> will not reach RegisterEvent<float> handlers even with the same event name.