Here’s a simple test to repro:
void runTest() final
{
beginTest("Removing listeners during callback");
struct Listener
{
void callback()
{
onCallback();
}
std::function<void()> onCallback;
};
juce::ListenerList<Listener> listeners;
juce::String result;
// First listener appends "x" to the result.
Listener x;
x.onCallback = [&] {
result += "x";
};
listeners.add(&x);
// Second listener appends "y" to the result, and
// adds/removes a temporary listener
Listener y;
y.onCallback = [&] {
result += "y";
Listener nested;
listeners.add(&nested);
listeners.remove(&nested);
};
listeners.add(&y);
// Final listener appends "z" to the result
Listener z;
z.onCallback = [&] {
result += "z";
};
listeners.add(&z);
listeners.call(&Listener::callback);
expectEquals<juce::String>(result, "xyz");
}
[juce::ListenerList Test / Removing listeners during callback] Test 1 failed: Expected value: xyz, Actual value: xy
Stepping through juce::ListenerList::callCheckedExcluding(), it seems that the iterator is correctly initialised to {0, 3}, the first iteration executes as expected and the iterator is updated to {1, 3}, however after after the second iteration, the iterator becomes {1, 2} and is then incremented to {2, 2} which causes the loop to end and so the final listener is not called!
It looks like remove() decrements the end of the current iterator to account for existing listeners being removed during callbacks, however add() doesn’t first increment the current iterator to account for listeners being added during callback.
