blob: 1501141793af17212863b01af92b01c5a4166039 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_CONNECTIVITY_BLUETOOTH_LIB_FIDL_HANGING_GETTER_H_
#define SRC_CONNECTIVITY_BLUETOOTH_LIB_FIDL_HANGING_GETTER_H_
#include <lib/fidl/cpp/clone.h>
#include <lib/fit/function.h>
#include <zircon/assert.h>
#include <optional>
#include <queue>
namespace bt_lib_fidl {
// HangingGetter generalizes the consumer/producer pattern often involved in FIDL hanging get
// implementations.
//
// USAGE:
//
// Use `Set()` to update the state watched by the FIDL method:
//
// HangingGetter<int> foo;
// ...
// void OnFooUpdated(int new_foo) {
// foo.Set(new_foo);
// }
//
// Use `Watch()` to invoke the response callback with any updates to the state or defer it until the
// update happens later:
//
// void GetFoo(GetFooCallback callback) {
// foo.Watch(std::move(callback));
// }
//
// A specialization is provided for state that is a growing collection of state updates:
//
// HangingVectorGetter<int> foo;
// ...
// void OnFooUpdated(int new_foo) {
// foo.Add(new_foo);
// }
template <typename T, typename C = void(T)>
class HangingGetterBase {
public:
using Callback = fit::callback<C>;
using Mutator = fit::function<T(T)>;
// Returns true if a callback is already assigned to this getter.
bool armed() const { return !callbacks_.empty(); }
// Assign |value| to the stored state and notify any pending Watch callbacks.
void Set(T value) {
value_.emplace(std::move(value));
WatchInternal();
}
// Mutably access the stored state via function |f| and notify any pending Watch callbacks. The
// Mutator function |f| will receive the current value and should the return the new value after
// applying any transformations to it.
//
// This is useful for value types that accumulate data. To directly assign a value, use the
// non-mutator overload instead.
void Transform(Mutator f) {
ZX_DEBUG_ASSERT(f);
// Initialize the value if it's not dirty so that the mutator can work with it.
if (!value_) {
value_.emplace();
}
value_.emplace(f(std::move(value_.value())));
WatchInternal();
}
// Invoke |callback| with any updates to the state. If the state has been accessed via one of the
// Set() functions since the last call to Watch(), then |callback| will run immediately.
// Otherwise, |callback| is run next time the state is accessed.
//
// Once |callback| runs, the store state becomes cleared. The next call to one of the Set()
// functions will default-construct a new state.
//
// Multiple callbacks can be queued to be notified simultaneously next time the state gets
// updated. All callbacks will get invoked with a separate copy of the new state, however there is
// no requirement that the state type T itself be copiable. How the value is passed to the
// callbacks is left up to the Notify() member function implementation.
void Watch(Callback callback) {
callbacks_.push(std::move(callback));
WatchInternal();
}
protected:
// This member function is called when a value update triggers a registered watcher Callback to be
// notified. This is abstract to allow HangingGetter variants to apply any necessary
// transformations between the stored value type "T" and the parameter type of the callback type
// "C".
//
// For example, this is useful when the stored type is a custom accumulator type. A
// HangingGetterBase implementation can, for example, implement a custom mapping from the stored
// accumulator to a FIDL struct type that the callback expects.
virtual void Notify(std::queue<Callback> callbacks, T value) = 0;
private:
void WatchInternal() {
if (!callbacks_.empty() && value_) {
auto q = std::move(callbacks_);
auto v = std::move(*value_);
value_.reset();
Notify(std::move(q), std::move(v));
}
}
std::optional<T> value_;
std::queue<Callback> callbacks_;
};
// HangingGetter type where the stored type is identical to the callback parameter type. T must be
// clonable by fidl::Clone().
template <typename T>
class HangingGetter : public HangingGetterBase<T> {
protected:
void Notify(std::queue<typename HangingGetter<T>::Callback> callbacks, T value) override {
while (!callbacks.empty()) {
auto f = std::move(callbacks.front());
callbacks.pop();
f(fidl::Clone(value));
}
}
};
template <typename T>
class HangingVectorGetter : public HangingGetter<std::vector<T>> {
public:
// Insert |value| to the vector and notify any pending Watch callbacks.
void Add(T&& value) {
this->Transform([&](auto current) {
current.push_back(std::forward<T>(value));
return current;
});
}
};
} // namespace bt_lib_fidl
#endif // SRC_CONNECTIVITY_BLUETOOTH_LIB_FIDL_HANGING_GETTER_H_