CallbackList: Add support for removing receivers

Bug: webrtc:11943
Change-Id: I7a646729dd1e4f5abe20900412f4105414e1a98f
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/195332
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Commit-Queue: Karl Wiberg <kwiberg@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#32700}
diff --git a/rtc_base/callback_list.cc b/rtc_base/callback_list.cc
index ac947e2..88d0b6f 100644
--- a/rtc_base/callback_list.cc
+++ b/rtc_base/callback_list.cc
@@ -21,17 +21,76 @@
   RTC_CHECK(!send_in_progress_);
 }
 
+void CallbackListReceivers::RemoveReceivers(const void* removal_tag) {
+  RTC_CHECK(!send_in_progress_);
+  RTC_DCHECK(removal_tag != nullptr);
+
+  // We divide the receivers_ vector into three regions: from right to left, the
+  // "keep" region, the "todo" region, and the "remove" region. The "todo"
+  // region initially covers the whole vector.
+  size_t first_todo = 0;                    // First element of the "todo"
+                                            // region.
+  size_t first_remove = receivers_.size();  // First element of the "remove"
+                                            // region.
+
+  // Loop until the "todo" region is empty.
+  while (first_todo != first_remove) {
+    if (receivers_[first_todo].removal_tag != removal_tag) {
+      // The first element of the "todo" region should be kept. Move the
+      // "keep"/"todo" boundary.
+      ++first_todo;
+    } else if (receivers_[first_remove - 1].removal_tag == removal_tag) {
+      // The last element of the "todo" region should be removed. Move the
+      // "todo"/"remove" boundary.
+      --first_remove;
+    } else {
+      // The first element of the "todo" region should be removed, and the last
+      // element of the "todo" region should be kept. Swap them, and then shrink
+      // the "todo" region from both ends.
+      RTC_DCHECK_NE(first_todo, first_remove - 1);
+      using std::swap;
+      swap(receivers_[first_todo], receivers_[first_remove - 1]);
+      RTC_DCHECK_NE(receivers_[first_todo].removal_tag, removal_tag);
+      ++first_todo;
+      RTC_DCHECK_EQ(receivers_[first_remove - 1].removal_tag, removal_tag);
+      --first_remove;
+    }
+  }
+
+  // Discard the remove region.
+  receivers_.resize(first_remove);
+}
+
 void CallbackListReceivers::Foreach(
     rtc::FunctionView<void(UntypedFunction&)> fv) {
   RTC_CHECK(!send_in_progress_);
   send_in_progress_ = true;
   for (auto& r : receivers_) {
-    fv(r);
+    fv(r.function);
   }
   send_in_progress_ = false;
 }
 
 template void CallbackListReceivers::AddReceiver(
+    const void*,
+    UntypedFunction::TrivialUntypedFunctionArgs<1>);
+template void CallbackListReceivers::AddReceiver(
+    const void*,
+    UntypedFunction::TrivialUntypedFunctionArgs<2>);
+template void CallbackListReceivers::AddReceiver(
+    const void*,
+    UntypedFunction::TrivialUntypedFunctionArgs<3>);
+template void CallbackListReceivers::AddReceiver(
+    const void*,
+    UntypedFunction::TrivialUntypedFunctionArgs<4>);
+template void CallbackListReceivers::AddReceiver(
+    const void*,
+    UntypedFunction::NontrivialUntypedFunctionArgs);
+template void CallbackListReceivers::AddReceiver(
+    const void*,
+    UntypedFunction::FunctionPointerUntypedFunctionArgs);
+
+template void CallbackListReceivers::AddReceiver(
     UntypedFunction::TrivialUntypedFunctionArgs<1>);
 template void CallbackListReceivers::AddReceiver(
     UntypedFunction::TrivialUntypedFunctionArgs<2>);