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