3

Is it possible to invoke a MulticastDelegate and process the return value of every attached handler without allocating any memory?

Background

In the scheme of normal things, the Delegate[] allocated by MulticastDelegate.GetInvocationList() is negligible. However in certain cases, it is important to minimise allocations. For example, during gameplay in Xbox games running on the compact framework, every 1MB allocated will trigger a collection and nasty framerate hitch.

After using CLR Profiler to hunt down and eliminate most allocations during gameplay, I'm left with 50% of the garbage being caused by Farseer Physics collision callback invocations. It needs to iterate the full invocation list since the user can return false to cancel the collision response from any of the attached handlers (and invoking the delegate without using GetInvocationList() simply returns the result of the last attached handler).

For reference, here is the Farseer code in question:

// Report the collision to both participants. Track which ones returned true so we can
// later call OnSeparation if the contact is disabled for a different reason.
if (FixtureA.OnCollision != null)
    foreach (OnCollisionEventHandler handler in FixtureA.OnCollision.GetInvocationList())
        enabledA = handler(FixtureA, FixtureB, this) && enabledA;
Aranda
  • 855
  • 8
  • 17
  • I'm pretty sure that most of the time the invocation list will be just a single function, so if there was at least a `Delegate.Count` function we could optimise it by not iterating the list in that case. – Aranda Jun 24 '14 at 12:42
  • Whoever down-voted the question, at least have the decency to explain why. – Aranda Jun 26 '14 at 16:19
  • 1
    You should ideally redesign your program so that events aren't expected to return a value, precisely because getting values returned from an event is cumbersome. Have your handlers call a method of the instance firing the event, or mutate a class passed as a parameter to the event handler, instead of returning a value. – Servy Jun 26 '14 at 17:19
  • Thanks Servy, that makes sense. Might not be easy to convince Farseer devs to change their API in this way, but then it could be the only way to avoid the garbage. – Aranda Jun 26 '14 at 17:21
  • 1
    You can take a look at the implementation of `GetInvocationList`[here](http://referencesource.microsoft.com/#mscorlib/system/multicastdelegate.cs#461). It looks like you will have to use Reflection to access `_invocationCount` and `_invocationList`, but after accessing those it should be fairly trivial. – jakobbotsch Jun 26 '14 at 18:06
  • Of course! Thanks Janiels for the reminder that the private fields are available as last resort. I'd consider this the answer. – Aranda Jun 26 '14 at 18:21

1 Answers1

0

I found a solution for me in the form of replacing a MulticastDelegate with a List<Delegate>.

I needed to call GetInvocationList() for several frames every frame MulticastDelegate.

private Action[] _actions;

public event Action ActionsEvent
{
    add
    {
        if (_actions == null)
            _actions = new Action[] { value };
        else
        {
            int lastIndex = _actions.Length;

            Array.Resize(ref _actions, _actions.Length + 1);

            _actions[lastIndex] = value;
        }
    }
    remove
    {
        if (_actions == null)
            return;

        int index = Array.IndexOf(_actions, value);

        if (index < 0)
            return false;

        int newLength = _actions.Length - 1;

        if (newLength > 0)
        {
            Action[] newActions = new Action[newLength];

            if (index > 0)
                Array.Copy(_actions, 0, newActions, 0, index);

            if (index < newLength)
                Array.Copy(_actions, index + 1, newActions, index, newLength - index);

            _actions = newActions;
        }
        else
            _actions = null;
    }
}

Or an optimized version (but has significant differences from the standard Delegate). If you need a Delegate-like behavior, then use the first option.

private List<Action> _actions;

public event Action ActionsEvent
{
    add
    {
        if (_actions == null)
            _actions = new List<Action>();

        _actions.Add(value);
    }
    remove
    {
        if (_actions != null)
        {
            _actions.Remove(value);

            if (_actions.Count == 0)
                _actions= null;
        }
    }
}