1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 
15 #pragma once
16 
17 #include <lib/fidl/cpp/clone.h>
18 
19 #include <optional>
20 #include <queue>
21 
22 #include "pw_assert/assert.h"
23 #include "pw_function/function.h"
24 
25 namespace bt_lib_fidl {
26 
27 // HangingGetter generalizes the consumer/producer pattern often involved in
28 // FIDL hanging get implementations.
29 //
30 // USAGE:
31 //
32 // Use `Set()` to update the state watched by the FIDL method:
33 //
34 //   HangingGetter<int> foo;
35 //   ...
36 //   void OnFooUpdated(int new_foo) {
37 //     foo.Set(new_foo);
38 //   }
39 //
40 // Use `Watch()` to invoke the response callback with any updates to the state
41 // or defer it until the update happens later:
42 //
43 //   void GetFoo(GetFooCallback callback) {
44 //     foo.Watch(std::move(callback));
45 //   }
46 //
47 // A specialization is provided for state that is a growing collection of state
48 // updates:
49 //
50 //   HangingVectorGetter<int> foo;
51 //   ...
52 //   void OnFooUpdated(int new_foo) {
53 //     foo.Add(new_foo);
54 //   }
55 
56 template <typename T, typename C = void(T)>
57 class HangingGetterBase {
58  public:
59   using Callback = pw::Callback<C>;
60   using Mutator = pw::Function<T(T)>;
61 
62   // Returns true if a callback is already assigned to this getter.
armed()63   bool armed() const { return !callbacks_.empty(); }
64 
65   // Assign |value| to the stored state and notify any pending Watch callbacks.
Set(T value)66   void Set(T value) {
67     value_.emplace(std::move(value));
68     WatchInternal();
69   }
70 
71   // Mutably access the stored state via function |f| and notify any pending
72   // Watch callbacks. The Mutator function |f| will receive the current value
73   // and should the return the new value after applying any transformations to
74   // it.
75   //
76   // This is useful for value types that accumulate data. To directly assign a
77   // value, use the non-mutator overload instead.
Transform(Mutator f)78   void Transform(Mutator f) {
79     PW_DASSERT(f);
80 
81     // Initialize the value if it's not dirty so that the mutator can work with
82     // it.
83     if (!value_) {
84       value_.emplace();
85     }
86 
87     value_.emplace(f(std::move(value_.value())));
88     WatchInternal();
89   }
90 
91   // Invoke |callback| with any updates to the state. If the state has been
92   // accessed via one of the Set() functions since the last call to Watch(),
93   // then |callback| will run immediately. Otherwise, |callback| is run next
94   // time the state is accessed.
95   //
96   // Once |callback| runs, the store state becomes cleared. The next call to one
97   // of the Set() functions will default-construct a new state.
98   //
99   // Multiple callbacks can be queued to be notified simultaneously next time
100   // the state gets updated. All callbacks will get invoked with a separate copy
101   // of the new state, however there is no requirement that the state type T
102   // itself be copiable. How the value is passed to the callbacks is left up to
103   // the Notify() member function implementation.
Watch(Callback callback)104   void Watch(Callback callback) {
105     callbacks_.push(std::move(callback));
106     WatchInternal();
107   }
108 
109  protected:
110   // This member function is called when a value update triggers a registered
111   // watcher Callback to be notified. This is abstract to allow HangingGetter
112   // variants to apply any necessary transformations between the stored value
113   // type "T" and the parameter type of the callback type "C".
114   //
115   // For example, this is useful when the stored type is a custom accumulator
116   // type. A HangingGetterBase implementation can, for example, implement a
117   // custom mapping from the stored accumulator to a FIDL struct type that the
118   // callback expects.
119   virtual void Notify(std::queue<Callback> callbacks, T value) = 0;
120 
121  private:
WatchInternal()122   void WatchInternal() {
123     if (!callbacks_.empty() && value_) {
124       auto q = std::move(callbacks_);
125       auto v = std::move(*value_);
126       value_.reset();
127 
128       Notify(std::move(q), std::move(v));
129     }
130   }
131 
132   std::optional<T> value_;
133   std::queue<Callback> callbacks_;
134 };
135 
136 // HangingGetter type where the stored type is identical to the callback
137 // parameter type. T must be clonable by fidl::Clone().
138 template <typename T>
139 class HangingGetter : public HangingGetterBase<T> {
140  protected:
Notify(std::queue<typename HangingGetter<T>::Callback> callbacks,T value)141   void Notify(std::queue<typename HangingGetter<T>::Callback> callbacks,
142               T value) override {
143     while (!callbacks.empty()) {
144       auto f = std::move(callbacks.front());
145       callbacks.pop();
146       f(fidl::Clone(value));
147     }
148   }
149 };
150 
151 template <typename T>
152 class HangingVectorGetter : public HangingGetter<std::vector<T>> {
153  public:
154   // Insert |value| to the vector and notify any pending Watch callbacks.
Add(T && value)155   void Add(T&& value) {
156     this->Transform([&](auto current) {
157       current.push_back(std::forward<T>(value));
158       return current;
159     });
160   }
161 };
162 
163 }  // namespace bt_lib_fidl
164