Troubleshooting

Purpose

This page covers common issues, known limitations, and frequently asked questions about Reactive SO.


Common issues

Events not firing

Symptom: You call RaiseEvent() but nothing happens.

Diagnostic flowchart

graph TB
    A["Events not firing"] --> B{"Event Channel<br/>assigned?"}
    B -->|NO| C["Set asset<br/>in Inspector"]
    B -->|YES| D{"Using<br/>?.RaiseEvent()?"}
    D -->|NO| E["Add null-conditional<br/>operator"]
    D -->|YES| F["Open Event Monitor"]
    F --> G{"Event<br/>appearing?"}
    G -->|NO| H["Publisher issue<br/>Check RaiseEvent call"]
    G -->|YES| I{"Subscribers<br/>in #L column?"}
    I -->|NO| J["No subscribers<br/>Check OnEnable"]
    I -->|YES| K["Subscriber issue<br/>Check handler"]

Checklist

  1. Check whether the event channel is assigned. Look for None in the Inspector and drag the correct ScriptableObject asset.

  2. Check whether you are using the null-conditional operator.
    // Good
    eventChannel?.RaiseEvent();
    
    // Bad - throws NullReferenceException if not assigned
    eventChannel.RaiseEvent();
    
  3. Check whether anyone is subscribed. Open Event Monitor and check the #L column, use Subscribers List to see listeners, and verify the subscriber’s GameObject is active.

  4. Check whether the event is actually raising. Open Event Monitor. If the event appears, the problem is in subscribers. If the event does not appear, check the publisher code.

Memory leaks / duplicate events

Symptom: Events fire multiple times or memory grows after scene transitions.

Cause: Forgot to unsubscribe in OnDisable.

Fix

private void OnEnable()
{
    eventChannel.OnEventRaised += HandleEvent;
}

private void OnDisable()
{
    eventChannel.OnEventRaised -= HandleEvent;  // Required!
}

Diagnosis: Use Subscribers List before and after scene transitions. If destroyed objects remain in the list, you have a leak.

Events fire too early

Symptom: Subscriber’s Start() has not run when the event fires.

Solutions

Option 1 - Delay the event

private IEnumerator Start()
{
    yield return null;  // Wait one frame
    onGameStarted?.RaiseEvent();
}

Option 2 - Subscribe earlier

private void Awake()
{
    eventChannel.OnEventRaised += HandleEvent;
}

private void OnDisable()
{
    eventChannel.OnEventRaised -= HandleEvent;
}

Wrong event channel referenced

Symptom: Multiple event channels have similar names and you reference the wrong one.

Fix Use clear names and organize in folders:

ScriptableObjects/Events/
├── Player/
│   ├── OnPlayerDeath.asset
│   └── OnPlayerHealthChanged.asset
├── UI/
│   └── OnMenuOpened.asset
└── Game/
    └── OnGamePaused.asset

Subscribers not updating

Symptom: Event fires but subscriber does not react.

Checklist

  1. Is the GameObject active?
  2. Is the MonoBehaviour enabled?
  3. Did OnEnable() run?
  4. Is the subscription correct?
    // Correct
    eventChannel.OnEventRaised += HandleEvent;
    
    // Wrong - overwrites other subscribers
    eventChannel.OnEventRaised = HandleEvent;
    
  5. Does the method signature match?
    // For IntEventChannelSO
    void HandleEvent(int value) { }  // Correct
    void HandleEvent() { }           // Wrong signature
    

Known limitations

Caller information with UnityEvents

Caller information only works for direct code calls. It does not work when events are raised via UnityEvent (e.g., Button.OnClick in Inspector).

Works

public void OnButtonClick()
{
    onButtonPressed?.RaiseEvent();  // Caller: "OnButtonClick"
}

Does not work

Inspector: Button.OnClick → EventChannel.RaiseEvent
Caller shows: "-"

Workaround: Create a wrapper method instead of calling RaiseEvent directly from Inspector.

Manual Trigger custom types

Manual Trigger only supports the 12 built-in event types. Custom types display a help message instead of a trigger button.

Subscribers List scope

Subscribers List only shows MonoBehaviour subscribers. Other subscribers (ScriptableObjects, static classes) appear in the #L count but not in the list.

Custom types with non-serializable fields

When using custom types with Variables or Event Channels, only serializable fields persist across scene transitions or domain reloads.

Unsupported types in custom structs/classes

Type Status
Dictionary<K,V> Not serialized
Multidimensional arrays (int[,]) Not serialized
Delegates / Events Not serialized
Interfaces Not serialized
HashSet<T>, Queue<T>, Stack<T> Not serialized

Example of problematic custom type

[System.Serializable]
public struct GameProgress
{
    public int level;                              // OK
    public Dictionary<string, bool> achievements;  // NOT serialized!
}

// VariableSO<GameProgress> will lose achievements data

Solution: Use serializable alternatives.

[System.Serializable]
public struct GameProgress
{
    public int level;
    public List<string> achievementKeys;    // OK
    public List<bool> achievementValues;    // OK
}

ScriptableObject data loss on scene transition

Runtime data in ScriptableObjects may be lost if the SO is unloaded from memory. This can happen in the following cases.

  • Editor: SO not selected in Inspector during scene transition
  • Build: SO not referenced by any loaded scene

Affected components

  • ReactiveEntitySetSO runtime data (entities)
  • RuntimeSetSO items
  • Custom non-serialized fields

Not affected

  • VariableSO values (serialized by default)
  • Event channel subscriptions are expected to re-subscribe in OnEnable

Workaround: Ensure critical SOs are referenced by a persistent object (e.g., a DontDestroyOnLoad manager).


FAQ

Can I use Reactive SO in builds?

Yes. All features work in builds with zero overhead. Debugging tools (Event Monitor, Manual Trigger, Subscribers List, Caller Info) are Editor-only and automatically stripped from builds.

What is the performance cost?

Nearly zero. Event channels use standard C# events internally. Debugging features are completely removed from builds.

Can I use event channels with coroutines?

Yes.

private void OnEnable()
{
    eventChannel.OnEventRaised += HandleEvent;
}

private void HandleEvent()
{
    StartCoroutine(HandleEventCoroutine());
}

private IEnumerator HandleEventCoroutine()
{
    // Your coroutine logic
}

Can I pass multiple values in one event?

Not directly. Use one of these approaches.

Option 1 - Use a struct

[System.Serializable]
public struct PlayerDeathData
{
    public Vector3 position;
    public string killerName;
    public int score;
}

public class PlayerDeathEventChannelSO : EventChannelSO<PlayerDeathData> { }

Option 2 - Use multiple events

onPlayerDeath?.RaiseEvent();
onDeathPosition?.RaiseEvent(position);
onFinalScore?.RaiseEvent(score);

Can I subscribe from ScriptableObjects?

Yes.

public class GameSettings : ScriptableObject
{
    [SerializeField] private VoidEventChannelSO onGameStarted;

    private void OnEnable()
    {
        onGameStarted.OnEventRaised += HandleGameStarted;
    }

    private void OnDisable()
    {
        onGameStarted.OnEventRaised -= HandleGameStarted;
    }

    private void HandleGameStarted()
    {
        // Initialize settings
    }
}

Note: ScriptableObject subscribers do not appear in Subscribers List but are counted in the #L column.

How do I unit test event channels?

using NUnit.Framework;
using Tang3cko.ReactiveSO;

public class EventChannelTests
{
    [Test]
    public void EventRaisesCorrectly()
    {
        // Arrange
        var eventChannel = ScriptableObject.CreateInstance<IntEventChannelSO>();
        int receivedValue = 0;
        eventChannel.OnEventRaised += (value) => receivedValue = value;

        // Act
        eventChannel.RaiseEvent(42);

        // Assert
        Assert.AreEqual(42, receivedValue);
    }
}

Can I use event channels for networking?

Event channels work locally within a single Unity instance. For networked games, consider the following approach.

  • Use event channels for local game logic
  • Use your networking solution for network communication
  • Raise event channels in response to network events

Here is an example of raising local events from network callbacks.

[ClientRpc]
void RpcPlayerDeath()
{
    // Runs on all clients
    onPlayerDeath?.RaiseEvent();  // Local event for UI/audio
}

Still having issues?

If you are experiencing issues not covered here, try the following steps.

  1. Check Event Monitor to verify event flow
  2. Use Subscribers List to verify subscriptions
  3. Try Manual Trigger to isolate publisher vs subscriber issues
  4. Add Debug.Log in OnEnable, OnDisable, and handlers
  5. Review Getting Started for basic setup

Reporting issues

When reporting issues, include the following information.

  • Unity version
  • Reactive SO version
  • Steps to reproduce
  • Event Monitor output
  • Subscribers List screenshot

For support, use the following channels.


References


This site uses Just the Docs, a documentation theme for Jekyll.