Debug mode - SixWays/Relay GitHub Wiki
Debug mode helps diagnose "dangling" or "lapsed" listeners, a common garbage collection issue in event-driven architecture. It tracks all AddListener
calls and outputs a log of all existing relays and their listeners on demand. It also eats performance for breakfast so don't turn it on in production code.
To enable debug mode, define SIGTRAP_RELAY_DBG
. Each AddListener
call will be automatically tracked. Relay.LogRelays()
returns a log of all relays (of all generic types) and their persistent listeners (one-time listeners are ignored), ordered by observer (the owner of the listener). Alternatively, Relay.LogRelays(object observer)
returns a log for just one observer. A Unity menu item [Relay/Log Listeners] will print this to Debug.Log
.
Debug mode introduces crazy-big CPU and GC overhead on AddListener
, and a moderate CPU overhead on RemoveListener
and RemoveAllListeners
. Toggle Relay.recordDebugData
to only log what you're interested in and not incur AddListener
overhead constantly. This still incurs the RemoveListener
and RemoveAllListeners
overhead.
3 current relay listeners for Main Camera (MyClass):
[0] Relay<Dictionary<Int32, Single>>
Void MyMethod(Dictionary<Int32, Single>) (1 copies)
AddListener traces: (including for listeners which have since been removed!)
[0]:
MyClass::Start() (at Assets\MyClass.cs:11)
[1] Relay<Dictionary<Int32, Single>>
Void MyMethod(Dictionary<Int32, Single>) (1 copies)
AddListener traces: (including for listeners which have since been removed!)
[0]:
MyClass::Start() (at Assets\MyClass.cs:12)
[2] Relay
Void MyMethod2() (1 copies)
AddListener traces: (including for listeners which have since been removed!)
[0]:
MyClass::Start() (at Assets\MyClass.cs:13)
1 current relay listeners for anonymous methods:
[0] Relay
Void <Start>m__0() (1 copies)
AddListener traces: (including for listeners which have since been removed!)
[0]:
MyClass::Start() (at Assets\MyClass.cs:14)
Output is
- Observer
- Relay
- Listener
- Relevant
AddListener
stacktraces
- Relevant
- Listener
- Relay
Note that there's no way to get the "name" of a Relay
or which object "owns" it, as each Relay
is just an object on the heap with no knowledge of which object declared it*. To aid identification of which Relay
is which, the full type of the Relay
is printed and a stack trace is logged when AddListener
is called (subsequent duplicate traces are ignored). For instance in the above example, the first two items differ only by the line number on the stack trace.
Trace lists include all AddListener
calls for that relay and that delegate. There's no way to relate a specific RemoveListener
call to a specific AddListener
call, so all traces are retained unless there are no listeners left on the Relay
. This complication will only arise with duplicate listeners.
This is a running theme. It's also impossible to relate anonymous methods / lambdas to the object that declared them (though I could be wrong on this!), so they all get grouped together separately. Again, the stack trace should help identification. Also note the compiler-generated method name, which includes in <>
the name of the method (but not the type) in which they were declared.
*I may at some point add stack trace logging to Relay
constructors and include this in the output to really drill down into exactly which is which.