blob: 1f22ad54b615a43e694c35292674c1d2389f8522 [file] [log] [blame]
// Copyright 2016 The Android Open Source Project
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#pragma once
#include "android/base/Compiler.h"
#include <functional>
#include <list>
#include <memory>
#include <assert.h>
namespace android {
namespace base {
// Any object that intends to notify certain subscribers on the event loop can
// use the SubscriberList to keep track of all active clients. The clients in
// turn, must keep the returned SubscriptionToken alive for as long as they want
// to be notified of updates.
//
// Once a SubscriptionToken is destroyed, the SubscriptionList removes that
// client from future notifications.
// If the SubscriptionList is destroyed while some tokens are still active,
// destryong them no longer has any effect.
//
// See SubscriberList_unittest.cpp -- TestSubscription and TestClient -- for an
// example of how to use this.
//
// These objects are not thread safe. These operations MUST be done on the same
// thread:
// - |insert| / |emplace| new subscriptions.
// - Destroy a returned SubscriptionToken.
// - Destroy the SubscriberList.
namespace internal {
// Helper class that stores a callable object and invokes it upon destruction
// (unless invalidate() has been called).
class SubscriptionTokenImpl {
public:
using Callback = std::function<void()>;
explicit SubscriptionTokenImpl(const Callback& cb) : mCb(cb) {}
~SubscriptionTokenImpl() {
if (mCb)
mCb();
}
// If this method is called, the stored callable object
// will not be invoked upon destruction.
void invalidate() { mCb = Callback(); }
private:
Callback mCb;
DISALLOW_COPY_ASSIGN_AND_MOVE(SubscriptionTokenImpl);
};
} // namespace internal
using SubscriptionToken = std::unique_ptr<internal::SubscriptionTokenImpl>;
template <class SubscriberInfo>
class SubscriberList {
private:
// DO NOT CHANGE TO std::vector (or any other type of container
// that invalidates iterators on removal)!
// A list of objects that our clients care about.
using SubscriberInfoList = std::list<SubscriberInfo>;
using SubscriberInfoIterator = typename SubscriberInfoList::iterator;
// A list for internal bookkeeping.
typedef struct {
internal::SubscriptionTokenImpl* token;
SubscriberInfoIterator infoIterator;
} MetaData;
using MetaDataList = std::list<MetaData>;
using MetaDataListIterator = typename MetaDataList::iterator;
public:
using const_iterator = typename SubscriberInfoList::const_iterator;
SubscriberList() = default;
template <class... Args>
SubscriptionToken emplace(Args&&... args) {
mSubscriberInfos.emplace_back(std::forward<Args>(args)...);
return insertInternal(std::prev(mSubscriberInfos.end()));
}
SubscriptionToken insert(SubscriberInfo&& info) {
mSubscriberInfos.push_back(std::move(info));
return insertInternal(std::prev(mSubscriberInfos.end()));
}
SubscriptionToken insert(const SubscriberInfo& info) {
mSubscriberInfos.push_back(info);
return insertInternal(std::prev(mSubscriberInfos.end()));
}
// Your gateway to the subscribers.
// These iterators can be invalidated as subscribers are destroyed. Do not
// capture the iterators for asynchronous use.
const_iterator begin() const { return mSubscriberInfos.begin(); }
const_iterator end() const { return mSubscriberInfos.end(); }
size_t size() const { return mSubscriberInfos.size(); }
~SubscriberList() {
// Invalidate all the tokens dispensed by this instance. Failing to do
// that would cause the tokens to try and call into this instance upon
// destruction, resulting in undefined behavior.
for (const auto& subscriber : mMetaData) {
// No way the token can be NULL.
assert(subscriber.token);
subscriber.token->invalidate();
}
}
private:
void unsubscribe(MetaDataListIterator iterator) {
mSubscriberInfos.erase(iterator->infoIterator);
mMetaData.erase(iterator);
}
SubscriptionToken insertInternal(SubscriberInfoIterator infoIterator) {
// Add an entry with information needed for subscriber book-keeping.
mMetaData.emplace_back();
auto datum = std::prev(mMetaData.end());
datum->infoIterator = infoIterator;
// Create a new token that calls |unsubscribe| upon destruction.
SubscriptionToken token(new internal::SubscriptionTokenImpl(
[this, datum] { this->unsubscribe(datum); }));
// Ensure that this instance can invalidate the token.
datum->token = token.get();
// Give the token to the subscriber.
// The token will remain alive for as long as the subscriber that is
// holding on to it is alive.
// If the token is destroyed before the SubscriptionList is destroyed,
// it will notify us, and the corresponding subscriber entry will be
// removed.
// When the SubscriptionList is destroyed, it will go through all the
// remaining subscriber entries and invalidate their tokens, so that
// they don't try notifying the "dead" SubscriptionList.
return token;
}
SubscriberInfoList mSubscriberInfos;
MetaDataList mMetaData;
DISALLOW_COPY_ASSIGN_AND_MOVE(SubscriberList);
};
} // namespace base
} // namespace android