DetectorGraph  2.0
counterwithreset.cpp
Go to the documentation of this file.
1 // Copyright 2017 Nest Labs, Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://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,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "graph.hpp"
16 #include "detector.hpp"
17 #include "futurepublisher.hpp"
18 #include "sharedptr.hpp"
19 #include "processorcontainer.hpp"
20 
21 #include <iostream>
22 
23 using std::cout;
24 using std::endl;
25 
26 /**
27  * @file counterwithreset.cpp
28  * @brief Basic Counter with a FuturePublisher-based loop.
29  *
30  * @section ex-cwr-introduction Introduction
31  * This examples cover the most basic way to close a loop in a graph - using a
32  * DetectorGraph::FuturePublisher. Unmarked loops are forbidden by the Framework
33  * as a Topographical sort is only possible in Directed Acyclic Graphs.
34  *
35  * Intuitively, if there were cycles in a graph it would be impossible for the
36  * Framework to provide it's dependency guarantee:
37  * > For any Detector, all its dependencies will be evaluated before its own
38  * > evaluation.
39  *
40  * To construct closed loops the graph designer must explicitly define, per
41  * cycle/loop, which dependency (i.e. Topic) the above restriction will
42  * be waived.
43  *
44  * @section ex-cwr-arch Architecture
45  *
46  * The example is composed of two Detectors. The first counts `EventHappened`
47  * and the second detects the condition at which the counter should be reset.
48  *
49  * @dot "CounterWithReset"
50 digraph GraphAnalyzer {
51  rankdir = "LR";
52  node[fontname=Helvetica];
53  size="12,5";
54  "EventHappened" [label="0:EventHappened",style=filled, shape=box, color=lightblue];
55  "EventHappened" -> "EventCounter";
56  "EventCounter" -> "EventCount";
57  "EventCounter" [label="1:EventCounter", color=blue];
58  "EventCount" [label="2:EventCount",style=filled, shape=box, color=red];
59  "EventCount" -> "ResetDetector";
60  "ResetDetector" -> "Reset" [style=dotted, color=red, constraint=false];
61  "ResetDetector" [label="3:ResetDetector", color=blue];
62  "Reset" [label="4:Reset",style=filled, shape=box, color=red];
63  "Reset" -> "EventCounter";
64 }
65  * @enddot
66  *
67  * @section ex-cwr-fpvslag FuturePublisher vs. Lag<T>
68  * The framework offers two ways of closing loops in a graph,
69  * DetectorGraph::FuturePublisher and DetectorGraph::Lag - this example
70  * employs the former.
71  *
72  * In this graph it is clear that the TopicState used as a graph _output_ is
73  * `EventCount` and that `Reset` is produced purely for the feedback path. It
74  * is also clear to the the designer of `ResetDetecor` that the published
75  * `Reset` TopicState should be evaluated in the next Evaluation pass and not
76  * in the current one. In such cases it is reasonable to put the responsibility
77  * for closing the loop on the writer of `ResetDetector`:
78  * @snippetlineno counterwithreset.cpp Reset Detector
79  *
80  * In cases where downstream parts of the graph also subscribe to the
81  * TopicState used in the feedback loop, DetectorGraph::Lag should be used
82  * instead. For more info on that see other [Feedback Loop examples](
83  * @ref feedback-loops) (e.g. [Robot Localization](@ref robotlocalization.cpp))
84  *
85  * @section ex-cwr-graph Graph
86  * The graph instantiated and evaluation code is unaffected. Both
87  @snippetlineno counterwithreset.cpp CounterWithResetGraph
88  * and
89  @snippetlineno counterwithreset.cpp main
90  * work in the same way as with simple graphs.
91  *
92  * Running the program produces:
93  \verbatim
94 DetectorGraph: Graph Initialized
95 EventCount.count = 1
96 EventCount.count = 2
97 EventCount.count = 3
98 EventCount.count = 4
99 EventCount.count = 5
100 EventCount.count = 0
101 EventCount.count = 1
102 EventCount.count = 2
103  \endverbatim
104  *
105  * One important thing to note in this case is that ProcessOutput is called 8
106  * times even though `main` only pushes 7 TopicStates into the graph - this is
107  * by design and allows all graph outputs to continue to be inspected exactly
108  * once per-evaluation.
109  * This is the case for all graphs with closed loops.
110  *
111  * @cond DO_NOT_DOCUMENT
112  */
113 
114 //! [EventHappened]
115 struct EventHappened : public DetectorGraph::TopicState
116 {
117 };
118 //! [EventHappened]
119 
120 //! [EventCount]
121 struct EventCount : public DetectorGraph::TopicState
122 {
123  EventCount(int aCount = 0) : count(aCount) {}
124  int count;
125 };
126 //! [EventCount]
127 
128 //! [Reset]
129 struct Reset : public DetectorGraph::TopicState
130 {
131 };
132 //! [Reset]
133 
134 //![EventCountDetector]
135 class EventCountDetector : public DetectorGraph::Detector
136 , public DetectorGraph::SubscriberInterface<EventHappened>
138 , public DetectorGraph::Publisher<EventCount>
139 {
140 public:
141  EventCountDetector(DetectorGraph::Graph* graph) : DetectorGraph::Detector(graph), mEventCount(0)
142  {
143  Subscribe<EventHappened>(this);
144  Subscribe<Reset>(this);
145  SetupPublishing<EventCount>(this);
146  }
147 
148  void Evaluate(const EventHappened&)
149  {
150  mEventCount.count++;
151  }
152  void Evaluate(const Reset&)
153  {
154  mEventCount.count = 0;
155  }
156  void CompleteEvaluation()
157  {
158  Publish(mEventCount);
159  }
160 
161 private:
162  EventCount mEventCount;
163 };
164 //![EventCountDetector]
165 
166 //![UnitTest-Count-1]
167 void Test_Count()
168 {
169  DetectorGraph::Graph graph;
170  EventCountDetector detector(&graph);
171  auto outTopic = graph.ResolveTopic<EventCount>();
172 
173  graph.PushData(EventHappened());
174  graph.EvaluateGraph();
175 
176  DG_ASSERT(outTopic->HasNewValue());
177  DG_ASSERT(outTopic->GetNewValue().count == 1);
178 
179  //![UnitTest-Count-1]
180  //![UnitTest-Count-2]
181  graph.PushData(EventHappened());
182  graph.EvaluateGraph();
183 
184  DG_ASSERT(outTopic->HasNewValue());
185  DG_ASSERT(outTopic->GetNewValue().count == 2);
186 }
187 //![UnitTest-Count-2]
188 
189 //![UnitTest-ResetCount-1]
190 void Test_ResetCount()
191 {
192  // Arrange
193  DetectorGraph::Graph graph;
194  EventCountDetector detector(&graph);
195  auto outTopic = graph.ResolveTopic<EventCount>();
196  graph.PushData(EventHappened());
197  graph.EvaluateGraph();
198 
199  //![UnitTest-ResetCount-1]
200  //![UnitTest-ResetCount-2]
201  graph.PushData(Reset());
202  graph.EvaluateGraph();
203 
204  DG_ASSERT(outTopic->HasNewValue());
205  DG_ASSERT(outTopic->GetNewValue().count == 0);
206 }
207 //![UnitTest-ResetCount-2]
208 
209 //![Reset Detector]
210 class ResetDetector : public DetectorGraph::Detector
211 , public DetectorGraph::SubscriberInterface<EventCount>
212 , public DetectorGraph::FuturePublisher<Reset>
213 {
214 public:
215  ResetDetector(DetectorGraph::Graph* graph) : DetectorGraph::Detector(graph)
216  {
217  Subscribe<EventCount>(this);
218  SetupFuturePublishing<Reset>(this);
219  }
220 
221  void Evaluate(const EventCount& aEventCount)
222  {
223  if (aEventCount.count >= kMaxCount)
224  {
225  PublishOnFutureEvaluation(Reset());
226  }
227  }
228 
229  static const int kMaxCount = 5;
230 };
231 //![Reset Detector]
232 
233 //![UnitTest-ResetDetected-1]
234 void Test_ResetDetected()
235 {
236  // Arrange
237  DetectorGraph::Graph graph;
238  ResetDetector detector(&graph);
239  auto outTopic = graph.ResolveTopic<Reset>();
240  graph.PushData(EventCount(ResetDetector::kMaxCount));
241  graph.EvaluateGraph();
242 
243  //![UnitTest-ResetDetected-1]
244  //![UnitTest-ResetDetected-2]
245  DG_ASSERT(!outTopic->HasNewValue());
246 
247  graph.EvaluateGraph();
248 
249  DG_ASSERT(outTopic->HasNewValue());
250 }
251 //![UnitTest-ResetDetected-2]
252 
253 //![UnitTest-NoResetDetected-1]
254 void Test_NoResetDetected()
255 {
256  // Arrange
257  DetectorGraph::Graph graph;
258  ResetDetector detector(&graph);
259  auto outTopic = graph.ResolveTopic<Reset>();
260  graph.PushData(EventCount(ResetDetector::kMaxCount-1));
261  graph.EvaluateGraph();
262 
263  DG_ASSERT(!outTopic->HasNewValue());
264 
265  graph.EvaluateGraph();
266  DG_ASSERT(!outTopic->HasNewValue());
267 }
268 //![UnitTest-NoResetDetected-1]
269 
270 //![CounterWithResetGraph]
271 class CounterWithResetGraph : public DetectorGraph::ProcessorContainer
272 {
273 public:
274  CounterWithResetGraph()
275  : mEventCountDetector(&mGraph)
276  , mResetDetector(&mGraph)
277  , mEventCountTopic(mGraph.ResolveTopic<EventCount>())
278  {
279  }
280 
281  EventCountDetector mEventCountDetector;
282  ResetDetector mResetDetector;
283  DetectorGraph::Topic<EventCount>* mEventCountTopic;
284 
285  virtual void ProcessOutput()
286  {
287  if (mEventCountTopic->HasNewValue())
288  {
289  const EventCount& eventCount = mEventCountTopic->GetNewValue();
290  cout << "EventCount.count = " << eventCount.count << endl;
291  }
292  }
293 };
294 //![CounterWithResetGraph]
295 
296 //![UnitTest-CounterResetIntegration]
297 void Test_CounterResetIntegration()
298 {
299  DetectorGraph::Graph graph;
300  EventCountDetector counterDetector(&graph);
301  ResetDetector resetDetector(&graph);
302  auto outTopic = graph.ResolveTopic<EventCount>();
303 
304  for (int i = 1; i <= ResetDetector::kMaxCount; i++)
305  {
306  graph.PushData(EventHappened());
307  graph.EvaluateGraph();
308  DG_ASSERT(outTopic->HasNewValue());
309  DG_ASSERT(outTopic->GetNewValue().count == i);
310 
311  while(graph.EvaluateIfHasDataPending()) {} // See also GraphTestUtils::Flush()
312  }
313 
314  graph.PushData(EventHappened());
315  graph.EvaluateGraph();
316  DG_ASSERT(outTopic->HasNewValue());
317  DG_ASSERT(outTopic->GetNewValue().count == 1);
318 }
319 //![UnitTest-CounterResetIntegration]
320 
321 //![main]
322 int main()
323 {
324  CounterWithResetGraph counterGraph;
325  for (int i = 0; i < 7; ++i)
326  {
327  counterGraph.ProcessData(EventHappened());
328  }
329 
330  Test_Count();
331  Test_ResetCount();
332  Test_ResetDetected();
333  Test_NoResetDetected();
334  Test_CounterResetIntegration();
335 }
336 //![main]
337 
338 /// @endcond DO_NOT_DOCUMENT
Implements a graph of Topics & Detectors with Input/Output APIs.
Definition: graph.hpp:127
Publish data to the graph for future evaluation.
void PushData(const TTopicState &aTopicState)
Push data to a specific topic in the graph.
Definition: graph.hpp:187
bool HasNewValue() const
Returns true if the new Data is available.
Definition: topic.hpp:166
A Base class for a basic Graph container.
Manage data and its handler.
Definition: topic.hpp:84
bool EvaluateIfHasDataPending()
Evaluates Graph if data is pending and returns true if so.
Definition: graph.cpp:80
Topic< TTopicState > * ResolveTopic()
Find/add a topic in the detector graph.
Definition: graph.hpp:160
const T & GetNewValue() const
Returns reference to the new/latest value in the topic.
Definition: topic.hpp:179
Base struct for topic data types.
Definition: topicstate.hpp:52
Base class that implements a Publisher behavior.
Definition: publisher.hpp:66
A unit of logic in a DetectorGraph.
Definition: detector.hpp:68
A Pure interface that declares the Subscriber behavior.
ErrorType EvaluateGraph()
Evaluate the whole graph.
Definition: graph.cpp:133
#define DG_ASSERT(condition)
Definition: dgassert.hpp:20