Anchors
C++ library for Incremental Computing
anchor.h
1// anchor.h
2#ifndef ANCHORS_ANCHOR_H
3#define ANCHORS_ANCHOR_H
4
5#include "anchorbase.h"
6
7#include <algorithm>
8#include <boost/uuid/uuid_generators.hpp>
9#include <boost/uuid/uuid_io.hpp>
10#include <functional>
11#include <memory>
12#include <unordered_set>
13#include <vector>
14
15template <>
16struct std::hash<anchors::AnchorBase> {
17 std::size_t operator()(const anchors::AnchorBase& a) const noexcept {
18 return hash_value(a.getId());
19 }
20};
21
22// Used by the priority queue in the Engine class to store Anchors in increasing
23// order.
24template <>
25struct std::greater<std::shared_ptr<anchors::AnchorBase>> {
26 bool operator()(const std::shared_ptr<anchors::AnchorBase>& a1,
27 const std::shared_ptr<anchors::AnchorBase>& a2) const {
28 return a1->getHeight() > a2->getHeight();
29 }
30};
31
32namespace anchors {
33
40template <typename T>
41class AnchorWrap : public AnchorBase {
42 public:
43 virtual ~AnchorWrap(){};
44 virtual T get() const = 0;
45
46 protected:
47 virtual void set(const T& value) = 0;
48
49 friend class Engine;
50};
51
58template <typename T, typename InputType1 = T, typename InputType2 = T>
59class Anchor : public AnchorWrap<T> {
60 public:
65 using SingleInputUpdater = std::function<T(InputType1&)>;
66
71 using DualInputUpdater = std::function<T(InputType1&, InputType2&)>;
72
73 Anchor() = delete;
74
79 explicit Anchor(const T& value);
80
87 explicit Anchor(const std::shared_ptr<AnchorWrap<InputType1>>& input,
88 const SingleInputUpdater& updater);
89
97 explicit Anchor(const std::shared_ptr<AnchorWrap<InputType1>>& firstInput,
98 const std::shared_ptr<AnchorWrap<InputType2>>& secondInput,
99 const DualInputUpdater& updater);
100
101 Anchor(const Anchor& a) = delete;
102
103 ~Anchor() override = default;
104
105 friend class Engine;
106
107 friend std::ostream& operator<<(std::ostream& out, const Anchor& anchor) {
108 out << "[ value=" << anchor.get() << ", height=" << anchor.getHeight(),
109 ", firstDependency=" << anchor.d_firstDependency
110 << ", secondDependency="
111 << anchor.d_secondDependency << " ]";
112
113 return out;
114 }
115
116 private:
117 // PRIVATE MANIPULATORS
118 T get() const override;
119 // Returns the current value of an Anchor.
120
121 void compute(int stabilizationNumber) override;
122 // Computes the value of an Anchor based on its inputs and updater function.
123 // When this function is called by the Engine, it is guaranteed that the
124 // inputs are up-to-date.
125 // This function also sets the recomputeID of the Anchor to the given
126 // stabilizationNumber, and will update the changeId only if the Anchor
127 // value changes after recomputing.
128
129 void markNecessary() override;
130 // Increments the `necessary count` of an Anchor. An Anchor is necessary if
131 // it is a dependency of an observed Anchor, either directly or indirectly.
132
133 bool isNecessary() const override;
134 // Returns true if at least one observed Anchor depends on it, either
135 // directly or indirectly.
136
137 bool isStale() const override;
138 // Returns true if the Anchor is necessary and has never been computed or
139 // its recomputeId is less than the changeId of one of its children.
140
141 void decrementNecessaryCount() override;
142 // Decrements the `necessary count` of an Anchor after a dependant is marked
143 // as unobserved.
144
145 AnchorBase::AnchorId getId() const override;
146 // Returns the generated UUID of the Anchor.
147
148 int getHeight() const override;
149 // Returns the height of an Anchor. An Anchor's height must always be
150 // greater than the heights of its inputs.
151
152 int getRecomputeId() const override;
153 // Returns the ID at which the Anchor was last computed.
154
155 int getChangeId() const override;
156 // Returns the ID at which the value of an Anchor last changed.
157
158 void setChangeId(int changeId) override;
159 // Set the ID at which the value of an Anchor changed.
160
161 void set(const T& value) override;
162 // Set the value of the Anchor.
163
164 void addDependant(const std::shared_ptr<AnchorBase>& dependant) override;
165 // Adds the given Anchor as a dependant of this Anchor. When this Anchor's
166 // value changes, we want to know update its dependants.
167
168 void removeDependant(const std::shared_ptr<AnchorBase>& dependant) override;
169 // Removes the given Anchor from the dependants of this Anchor.
170
171 std::unordered_set<std::shared_ptr<AnchorBase>> getDependants()
172 const override;
173 // Returns the dependants of this Anchor.
174
175 std::vector<std::shared_ptr<AnchorBase>> getDependencies() const override;
176 // Returns the dependencies of this Anchor.
177
178 // PRIVATE DATA
179 boost::uuids::random_generator d_idGenerator;
180
181 boost::uuids::uuid d_id;
182
183 T d_value{};
184
185 int d_height{};
186 // The height of the Anchor. Its value is 0 if it has no dependencies.
187 // Otherwise, its value = Max(Height of Inputs) + 1
188
189 int d_necessary{};
190 // Indicates how many Anchors this node is a dependency of either directly
191 // or indirectly.
192
193 int d_numDependencies{};
194 // Number of dependencies this Anchor has.
195
196 int d_recomputeId{};
197 // The stabilization number at which this Anchor was last recomputed
198
199 int d_changeId{};
200 // The stabilization number at which the value of this Anchor last changed
201
202 bool d_hasNeverBeenComputed;
203
204 const std::shared_ptr<AnchorWrap<InputType1>> d_firstDependency;
205 const std::shared_ptr<AnchorWrap<InputType2>> d_secondDependency;
206
207 std::unordered_set<std::shared_ptr<AnchorBase>> d_dependants;
208 // Anchors that depend on it.
209
210 SingleInputUpdater d_singleInputUpdater;
211
212 DualInputUpdater d_dualInputUpdater;
213};
214
215template <typename T, typename InputType1, typename InputType2>
217 : d_id(d_idGenerator()),
218 d_value(value),
219 d_hasNeverBeenComputed(true),
220 d_dependants() {}
221
222template <typename T, typename InputType1, typename InputType2>
224 const std::shared_ptr<AnchorWrap<InputType1>>& input,
225 const SingleInputUpdater& updater)
226 : d_id(d_idGenerator()),
227 d_height(input->getHeight() + 1),
228 d_numDependencies(1),
229 d_hasNeverBeenComputed(true),
230 d_firstDependency(input),
231 d_dependants(),
232 d_singleInputUpdater(updater) {}
233
234template <typename T, typename InputType1, typename InputType2>
236 const std::shared_ptr<AnchorWrap<InputType1>>& firstInput,
237 const std::shared_ptr<AnchorWrap<InputType2>>& secondInput,
238 const DualInputUpdater& updater)
239 : d_id(d_idGenerator()),
240 d_height(std::max(firstInput->getHeight(), secondInput->getHeight()) + 1),
241 d_numDependencies(2),
242 d_hasNeverBeenComputed(true),
243 d_firstDependency(firstInput),
244 d_secondDependency(secondInput),
245 d_dependants(),
246 d_dualInputUpdater(updater) {}
247
248template <typename T, typename InputType1, typename InputType2>
250 return d_value;
251}
252
253template <typename T, typename InputType1, typename InputType2>
254void Anchor<T, InputType1, InputType2>::compute(int stabilizationNumber) {
255 if (d_recomputeId == stabilizationNumber) {
256 // Don't compute a node more than once in the same cycle
257 return;
258 }
259
260 T newValue;
261 d_recomputeId = stabilizationNumber;
262
263 d_hasNeverBeenComputed = false;
264
265 if (d_numDependencies == 0) {
266 return;
267 }
268
269 if (d_numDependencies == 1) {
270 InputType1 inputVal = d_firstDependency->get();
271 newValue = d_singleInputUpdater(inputVal);
272 } else {
273 InputType1 inputVal = d_firstDependency->get();
274 InputType2 inputVal2 = d_secondDependency->get();
275
276 newValue = d_dualInputUpdater(inputVal, inputVal2);
277 }
278
279 if (newValue != d_value) {
280 d_changeId = stabilizationNumber;
281 d_value = newValue;
282 }
283}
284
285template <typename T, typename InputType1, typename InputType2>
286void Anchor<T, InputType1, InputType2>::markNecessary() {
287 d_necessary++;
288}
289
290template <typename T, typename InputType1, typename InputType2>
291bool Anchor<T, InputType1, InputType2>::isNecessary() const {
292 return d_necessary > 0;
293}
294
295template <typename T, typename InputType1, typename InputType2>
296bool Anchor<T, InputType1, InputType2>::isStale() const {
297 bool recomputeIdLessThanChildChangeId = false;
298
299 if (d_numDependencies >= 1) {
300 recomputeIdLessThanChildChangeId =
301 d_recomputeId < d_firstDependency->getChangeId();
302
303 if (!recomputeIdLessThanChildChangeId && d_numDependencies == 2) {
304 recomputeIdLessThanChildChangeId |=
305 d_recomputeId < d_secondDependency->getChangeId();
306 }
307 }
308
309 return isNecessary() &
310 (d_hasNeverBeenComputed || recomputeIdLessThanChildChangeId);
311}
312
313template <typename T, typename InputType1, typename InputType2>
314void Anchor<T, InputType1, InputType2>::decrementNecessaryCount() {
315 if (d_necessary <= 0) {
316 return;
317 }
318
319 d_necessary--;
320}
321
322template <typename T, typename InputType1, typename InputType2>
323AnchorBase::AnchorId Anchor<T, InputType1, InputType2>::getId() const {
324 return d_id;
325}
326
327template <typename T, typename InputType1, typename InputType2>
328int Anchor<T, InputType1, InputType2>::getHeight() const {
329 return d_height;
330}
331
332template <typename T, typename InputType1, typename InputType2>
333int Anchor<T, InputType1, InputType2>::getRecomputeId() const {
334 return d_recomputeId;
335}
336
337template <typename T, typename InputType1, typename InputType2>
338int Anchor<T, InputType1, InputType2>::getChangeId() const {
339 return d_changeId;
340}
341
342template <typename T, typename InputType1, typename InputType2>
343void Anchor<T, InputType1, InputType2>::setChangeId(int changeId) {
344 d_changeId = changeId;
345}
346
347template <typename T, typename InputType1, typename InputType2>
348void Anchor<T, InputType1, InputType2>::set(const T& value) {
349 d_value = value;
350}
351
352template <typename T, typename InputType1, typename InputType2>
353void Anchor<T, InputType1, InputType2>::addDependant(
354 const std::shared_ptr<AnchorBase>& dependant) {
355 d_dependants.insert(dependant);
356}
357
358template <typename T, typename InputType1, typename InputType2>
359void Anchor<T, InputType1, InputType2>::removeDependant(
360 const std::shared_ptr<AnchorBase>& dependant) {
361 d_dependants.erase(dependant);
362}
363
364template <typename T, typename InputType1, typename InputType2>
365std::unordered_set<std::shared_ptr<AnchorBase>>
366Anchor<T, InputType1, InputType2>::getDependants() const {
367 std::unordered_set<std::shared_ptr<AnchorBase>> result;
368
369 for (auto& parent : d_dependants) {
370 result.insert(parent);
371 }
372
373 return result;
374}
375
376template <typename T, typename InputType1, typename InputType2>
377std::vector<std::shared_ptr<AnchorBase>>
378Anchor<T, InputType1, InputType2>::getDependencies() const {
379 std::vector<std::shared_ptr<AnchorBase>> result;
380
381 if (d_numDependencies >= 1) {
382 result.push_back(d_firstDependency);
383
384 if (d_numDependencies == 2) {
385 result.push_back(d_secondDependency);
386 }
387 }
388
389 return result;
390}
391
392} // namespace anchors
393
394#endif
This class exists to simplify the Anchors API, so that we can pass an Anchor around using only the ty...
Definition: anchor.h:41
A single node in the computation graph containing a value.
Definition: anchor.h:59
std::function< T(InputType1 &, InputType2 &)> DualInputUpdater
Alias for function that accepts inputs of type InputType1 and InputType2 and returns a value of type ...
Definition: anchor.h:71
std::function< T(InputType1 &)> SingleInputUpdater
Alias for function that accepts an input of type InputType1 and returns a value of type T.
Definition: anchor.h:65
Engine is the brain of Anchors, containing the necessary functions and data to retrieve the value of ...
Definition: engine.h:19
Main library namespace.
Definition: anchor.h:32