[CallbackList] Allow removal from within callback.
This makes moving from sigslot to CallbackList slightly simpler in some situations. Bug: webrtc:11943 Change-Id: I5c6dafb8ac597a119b90b64f369fa9e6316e38da Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/258560 Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org> Commit-Queue: Tomas Gunnarsson <tommi@webrtc.org> Cr-Commit-Position: refs/heads/main@{#36515}
This commit is contained in:
parent
06e4fc548c
commit
8bd5d484fe
@ -22,8 +22,7 @@ CallbackListReceivers::~CallbackListReceivers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CallbackListReceivers::RemoveReceivers(const void* removal_tag) {
|
void CallbackListReceivers::RemoveReceivers(const void* removal_tag) {
|
||||||
RTC_CHECK(!send_in_progress_);
|
RTC_DCHECK(removal_tag);
|
||||||
RTC_DCHECK(removal_tag != nullptr);
|
|
||||||
|
|
||||||
// We divide the receivers_ vector into three regions: from right to left, the
|
// We divide the receivers_ vector into three regions: from right to left, the
|
||||||
// "keep" region, the "todo" region, and the "remove" region. The "todo"
|
// "keep" region, the "todo" region, and the "remove" region. The "todo"
|
||||||
@ -42,8 +41,13 @@ void CallbackListReceivers::RemoveReceivers(const void* removal_tag) {
|
|||||||
} else if (receivers_[first_remove - 1].removal_tag == removal_tag) {
|
} else if (receivers_[first_remove - 1].removal_tag == removal_tag) {
|
||||||
// The last element of the "todo" region should be removed. Move the
|
// The last element of the "todo" region should be removed. Move the
|
||||||
// "todo"/"remove" boundary.
|
// "todo"/"remove" boundary.
|
||||||
|
if (send_in_progress_) {
|
||||||
|
// Tag this receiver for removal, which will be done when `ForEach`
|
||||||
|
// has completed.
|
||||||
|
receivers_[first_remove - 1].removal_tag = pending_removal_tag();
|
||||||
|
}
|
||||||
--first_remove;
|
--first_remove;
|
||||||
} else {
|
} else if (!send_in_progress_) {
|
||||||
// The first element of the "todo" region should be removed, and the last
|
// 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
|
// element of the "todo" region should be kept. Swap them, and then shrink
|
||||||
// the "todo" region from both ends.
|
// the "todo" region from both ends.
|
||||||
@ -57,18 +61,28 @@ void CallbackListReceivers::RemoveReceivers(const void* removal_tag) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!send_in_progress_) {
|
||||||
// Discard the remove region.
|
// Discard the remove region.
|
||||||
receivers_.resize(first_remove);
|
receivers_.resize(first_remove);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CallbackListReceivers::Foreach(
|
void CallbackListReceivers::Foreach(
|
||||||
rtc::FunctionView<void(UntypedFunction&)> fv) {
|
rtc::FunctionView<void(UntypedFunction&)> fv) {
|
||||||
RTC_CHECK(!send_in_progress_);
|
RTC_CHECK(!send_in_progress_);
|
||||||
|
bool removals_detected = false;
|
||||||
send_in_progress_ = true;
|
send_in_progress_ = true;
|
||||||
for (auto& r : receivers_) {
|
for (auto& r : receivers_) {
|
||||||
|
RTC_DCHECK_NE(r.removal_tag, pending_removal_tag());
|
||||||
fv(r.function);
|
fv(r.function);
|
||||||
|
if (r.removal_tag == pending_removal_tag()) {
|
||||||
|
removals_detected = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
send_in_progress_ = false;
|
send_in_progress_ = false;
|
||||||
|
if (removals_detected) {
|
||||||
|
RemoveReceivers(pending_removal_tag());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template void CallbackListReceivers::AddReceiver(
|
template void CallbackListReceivers::AddReceiver(
|
||||||
|
|||||||
@ -51,10 +51,18 @@ class CallbackListReceivers {
|
|||||||
void Foreach(rtc::FunctionView<void(UntypedFunction&)> fv);
|
void Foreach(rtc::FunctionView<void(UntypedFunction&)> fv);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Special protected pointer value that's used as a removal_tag for
|
||||||
|
// receivers that want to unsubscribe from within a callback.
|
||||||
|
// Note we could use `&receivers_` too, but since it's the first member
|
||||||
|
// variable of the class, its address will be the same as the instance
|
||||||
|
// CallbackList instance, so we take an extra step to avoid collision.
|
||||||
|
const void* pending_removal_tag() const { return &send_in_progress_; }
|
||||||
|
|
||||||
struct Callback {
|
struct Callback {
|
||||||
const void* removal_tag;
|
const void* removal_tag;
|
||||||
UntypedFunction function;
|
UntypedFunction function;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Callback> receivers_;
|
std::vector<Callback> receivers_;
|
||||||
bool send_in_progress_ = false;
|
bool send_in_progress_ = false;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -252,5 +252,18 @@ TEST(CallbackList, RemoveManyReceivers) {
|
|||||||
EXPECT_EQ(accumulator, 1212);
|
EXPECT_EQ(accumulator, 1212);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(CallbackList, RemoveFromSend) {
|
||||||
|
int removal_tag = 0;
|
||||||
|
CallbackList<> c;
|
||||||
|
c.AddReceiver(&removal_tag, [&] {
|
||||||
|
c.RemoveReceivers(&removal_tag);
|
||||||
|
// Do after RemoveReceivers to make sure the lambda is still valid.
|
||||||
|
++removal_tag;
|
||||||
|
});
|
||||||
|
c.Send();
|
||||||
|
c.Send();
|
||||||
|
EXPECT_EQ(removal_tag, 1);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace webrtc
|
} // namespace webrtc
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user