With Unity Events, just like C# events, invocation stops if one of listeners throws an exception.
This is undesired behavior in some places. For example, Imagine an unity event with 3 subscribers. One that updates the UI, one that plays a sound, and one that does some logic.
Now if some bug happens in UI listener, Event stops invocation. Then no sound plays and logic code doesn’t run.
One of the ways which we solved this problem was to use a list of delegates instead of Unity Events and wrap each invocation in try-catch block.
This solution worked well but the problem is that we don’t have the same inspector as Unity Events.
Is it possible to use Unity Events in such a way that exception in one listener doesn’t affect other listeners?
Not that I know of, sorry.
Personally, I find Unity still trying to keep running the game when an exception is thrown a bad thing. An exception unwinds the stack at the point where it happens, leaving your game in an undefined state because the code after that did did not execute. Also, when building with IL2CPP you get a performance boost by disabling null and array bound checks (crucial on consoles) and will cause your game to outright crash if any of those happen. You really should fix your game if those happen commonly in it.
I was curious about this so I constructed a little experiment, but I was lazy and used SendMessage() instead of wading through the reflection API… I’ll leave that to the reader. It turns out SendMessage() just lets it except and does not stop parental execution (the callsite). I tinkered and kinda lost interest; I think I’ll just go with @Neto_Kokku 's approach and fix my exceptions.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class ManualSafeEventRunner : MonoBehaviour
{
public UnityEvent ue;
void Start()
{
// just firing them in coroutines so each doesn't interfere with the other
StartCoroutine( RunInvokely());
StartCoroutine( RunManually());
}
IEnumerator RunInvokely()
{
yield return null;
Debug.Log( "RunInvokely();");
ue.Invoke();
}
IEnumerator RunManually()
{
yield return null;
Debug.Log( "RunManually();");
int count = ue.GetPersistentEventCount();
Debug.Log( "PersistentEventCount:" + count.ToString());
for (int i = 0; i < count; i++)
{
var o = ue.GetPersistentTarget(i);
var func = ue.GetPersistentMethodName(i);
Debug.Log( "entry " + i + ": " + o.name + " - " + func);
// TODO: do actual reflection and figure out what to call;
// This is left as an exercise to the reader...
// I'm gonna be lazy and call good old .SendMessage()!
GameObject go = o as GameObject;
if (go)
{
Debug.Log( "GameObject: SendMessage()-ing " + func);
try
{
go.SendMessage( func);
Debug.Log( "Success on " + func);
}
catch(System.Exception e) // caution: pokemon
{
Debug.LogError( "Failed on " + func + ", reason:" + e.ToString());
}
}
MonoBehaviour mb = o as MonoBehaviour;
if (mb)
{
Debug.Log( "MonoBehavior: SendMessage()-ing " + func);
try
{
mb.SendMessage( func);
Debug.Log( "Success on " + func);
}
catch(System.Exception e) // caution: pokemon
{
Debug.LogError( "Failed on " + func + ", reason:" + e.ToString());
}
}
}
}
public void BlowChunks()
{
Debug.Log( "Gonna blow chunks... here goes!!");
GameObject intentionalNull = null;
intentionalNull.SetActive(true);
Debug.Log( "Should never execute this.");
}
}
Full project attached below too.
Well, I think it is a good thing that Unity keeps the game running.
If, for example, There is a bug in UI of a specific item in the game, I much prefer player to experience just a temporary visual glitch rather than a total crash.
Even though it is a valid solution, I think it’s too heavy to use reflection or SendMessage whenever an event is called.
But, for anyone who is interested in this, this is possibly the lightest solution to this problem.
I checked UnityEvent source code and a whole lot of inner working classes are marked as “internal”. So you cannot access the delegates from outside unless you use reflection.
Anyway, I think I’ll use a list of delegates as an event. I lose editability from inspector but at least I get top performance + exception safe events.
Depending on how far down the rabbit hole of Editor Scripting you feel like going, you probably could make a generic MonoBehavior that internally reflects and sets up its own delegates based on your own custom inspector window… This generic sender would go on anything that can create events and be capable of broadcasting them safely.
Eh, probably FAR beyond where I would consider it time well spent. I’d just use a delegate and fix any nullrefs, or within the handlers themselves guard against things that might be null.
That’s a false savior, in my experience.
Just as you might just get a harmless visual glitch you can also, for example, get a MonoBehavioru that only executed half of it’s Awake method, did not set some variables which are later used by your save game system, resulting in a corrupt save file being produced, which completely destroys all of that player’s progress. The bug will be reported as “my game doesn’t load anymore” instead of “my game crashed when I clicked this button”, and you’ll have a much harder time finding the actual cause of the problem and actually fixing it with only that corrupted save as a starting point, instead of a dump created by a hard crash.