herald  2.0.0
runner.h
1 // Copyright 2021 Herald Project Contributors
2 // SPDX-License-Identifier: Apache-2.0
3 //
4 
5 #ifndef HERALD_ANALYSIS_RUNNER_H
6 #define HERALD_ANALYSIS_RUNNER_H
7 
8 #include "sampling.h"
9 
10 #include <variant>
11 #include <array>
12 
13 // debug only
14 // #include <iostream>
15 
16 namespace herald {
17 namespace analysis {
18 
19 using namespace sampling;
20 
22 template <typename ValT, std::size_t Size>
23 struct ListManager {
24  using value_type = ValT;
25  static constexpr std::size_t max_size = Size;
26 
27  ListManager() = default;
28  ~ListManager() = default;
29 
30  SampleList<Sample<ValT>,Size>& list(const SampledID sampled) {
31  auto iter = lists.try_emplace(sampled).first;
32  return lists.at(sampled);
33  }
34 
35  void remove(const SampledID listFor) {
36  lists.erase(listFor);
37  }
38 
39  const std::size_t size() const {
40  return lists.size();
41  }
42 
43  decltype(auto) begin() {
44  return lists.begin();
45  }
46 
47  decltype(auto) end() {
48  return lists.end();
49  }
50 
51 private:
52  std::map<SampledID,SampleList<Sample<ValT>,Size>> lists;
53 };
54 
57 template <typename... ValTs>
58 struct VariantSet {
59  static constexpr std::size_t Size = sizeof...(ValTs);
60 
61  VariantSet() : variants() {
62  createInstances<ValTs...>(0);
63  }; // Instantiate each type instance in the array
64  ~VariantSet() = default;
65 
67  template <typename ValT>
68  ValT& get() {
69  for (auto& v : variants) {
70  if (auto pval = std::get_if<ValT>(&v)) {
71  return *pval;
72  }
73  }
74  throw std::bad_variant_access();
75  }
76 
77  const std::size_t size() const {
78  return variants.size();
79  }
80 
81  decltype(auto) begin() {
82  return variants.begin();
83  }
84 
85  decltype(auto) end() {
86  return variants.end();
87  }
88 
89 private:
90  std::array<std::variant<ValTs...>,Size> variants;
91  template <typename LastT>
92  void createInstances(int pos) {
93  variants[pos].template emplace<LastT>();
94  }
95 
96  template <typename FirstT, typename SecondT, typename... RestT>
97  void createInstances(int pos) {
98  variants[pos].template emplace<FirstT>();
99  createInstances<SecondT,RestT...>(pos + 1);
100  }
101 };
102 
104 template <typename... DelegateTypes>
106  AnalysisDelegateManager(DelegateTypes... dts) : delegates() {
107  addDelegates(0,dts...);
108  }
109  ~AnalysisDelegateManager() = default;
110 
111  template <typename ValT>
112  void notify(SampledID sampled, Sample<ValT> sample) {
113  for (auto& delegateV : delegates) {
114  std::visit([sampled,sample](auto&& arg) {
115  using noref = typename std::remove_reference<decltype(arg)>::type;
116  if constexpr (std::is_same_v<ValT,typename noref::value_type>) {
117  ((decltype(arg))arg).newSample(sampled,sample); // cast to call derived class function
118  }
119  }, delegateV);
120  }
121  }
122 
124  template <typename DelegateT>
125  DelegateT& get() {
126  for (auto& v : delegates) {
127  if (auto pval = std::get_if<DelegateT>(&v)) {
128  return *pval;
129  }
130  }
131  throw std::bad_variant_access();
132  }
133 
134 private:
135  std::array<std::variant<DelegateTypes...>,sizeof...(DelegateTypes)> delegates;
136 
137  template <typename LastT>
138  constexpr void addDelegates(int nextPos,LastT&& last) {
139  delegates[nextPos] = (std::move(last));
140  }
141 
142  template <typename FirstT, typename SecondT, typename... RestT>
143  constexpr void addDelegates(int nextPos,FirstT&& first, SecondT&& second, RestT&&... rest) {
144  delegates[nextPos] = std::move(first);
145  ++nextPos;
146  addDelegates(nextPos,second,rest...);
147  }
148 };
149 
151 template <typename... ProviderTypes>
153  // Set for GCC 8/9. See https://stackoverflow.com/questions/63812165/stdvariant-requires-default-constructor-in-gcc-8-and-9-and-not-require-in-gcc
154  AnalysisProviderManager() = delete;
155  AnalysisProviderManager(ProviderTypes... prvs) : providers() {
156  addProviders(0, prvs...);
157  }
158  ~AnalysisProviderManager() = default;
159 
160  template <typename InputValT, std::size_t SrcSz, typename SourceType, std::size_t ListSize, typename CallableForNewSample>
161  bool analyse(Date timeNow, SampledID sampled, SampleList<Sample<InputValT>,SrcSz>& src, ListManager<SourceType,ListSize>& lists, CallableForNewSample& callable) {
162  bool generated = false;
163  for (auto& providerV : providers) {
164  std::visit([&timeNow,&sampled,&src,&lists,&generated,&callable](auto&& arg) {
165  using noref = typename std::remove_reference<decltype(arg)>::type;
166  // Ensure our calee supports the types we have
167  if constexpr (std::is_same_v<InputValT, typename noref::input_value_type>) {
168  auto& listRef = lists.list(sampled);
169  generated = generated | ((decltype(arg))arg).analyse(timeNow,sampled,src,listRef,callable);
170  }
171  }, providerV);
172  }
173  return generated;
174  }
175 
177  template <typename ProviderT>
178  ProviderT& get() {
179  for (auto& v : providers) {
180  if (auto pval = std::get_if<ProviderT>(&v)) {
181  return *pval;
182  }
183  }
184  throw std::bad_variant_access();
185  }
186 
187  template <typename InputT,typename OutputT>
188  constexpr bool hasMatchingAnalyser() noexcept {
189  bool match = false;
190  for (auto& providerV : providers) {
191  std::visit([&match] (auto&& provider) {
192  using InputValT = typename InputT::value_type;
193  using InT = typename std::remove_reference_t<decltype(provider)>::input_value_type;
194  using OutT = typename std::remove_reference_t<decltype(provider)>::output_value_type;
195  // std::cout << " Provider being checked " << typeid(provider).name() << std::endl;
196  // InT inInstance;
197  // OutT outInstance;
198  // std::cout << " In type " << typeid(inInstance).name() << ", out type " << typeid(outInstance).name() << std::endl;
199  // InputValT inputInstance;
200  // OutputT outputInstance;
201  // std::cout << " Input type " << typeid(inputInstance).name() << ", output type " << typeid(outputInstance).name() << std::endl;
202  if constexpr (std::is_same_v<InputValT,InT> && std::is_same_v<OutputT,OutT>) {
203  match = true;
204  // std::cout << " MATCHED!" << std::endl;
205  }
206  }, providerV);
207  }
208  return match;
209  }
210 
211 private:
212  std::array<std::variant<ProviderTypes...>,sizeof...(ProviderTypes)> providers;
213 
214  template <typename LastT>
215  constexpr void addProviders(int nextPos, LastT&& last) {
216  providers[nextPos] = std::move(last);
217  }
218 
219  template <typename FirstT, typename SecondT, typename... RestT>
220  constexpr void addProviders(int nextPos, FirstT&& first, SecondT&& second, RestT&&... rest) {
221  providers[nextPos] = std::move(first);
222  ++nextPos;
223  addProviders(nextPos,second,rest...);
224  }
225 };
226 
232 template <typename AnalysisDelegateManagerT, typename AnalysisProviderManagerT, typename... SourceTypes> // TODO derive SourceTypes from providers and delegates // TODO parameterise type lengths somehow (traits template?)
234  static constexpr std::size_t ListSize = 25; // TODO make this external somehow for each type (trait?)
235  // using valueTypes = (typename SourceTypes::value_type)...;
236 
237  AnalysisRunner(AnalysisDelegateManagerT& adm, AnalysisProviderManagerT& provds) : lists(), delegates(adm), runners(provds) /*, hasNewData(false)*/ {}
238  ~AnalysisRunner() = default;
239 
241  template <typename ValT>
243  // incoming sample. Pass to correct list
244  lists.template get<ListManager<ValT,ListSize>>().list(sampled).push(sample); // TODO get ListSize dynamically
245  // inform delegates
246  delegates.notify(sampled,sample);
247  }
248 
249  template <typename ValT>
250  void operator()(SampledID sampled,sampling::Sample<ValT> sample) {
251  newSample(sampled,sample);
252  }
253 
255  void run(Date timeNow) {
256  // call analyse(dateNow,srcList,dstDelegate) for all delegates with the correct list each, for each sampled
257 
258  // DO NOT USE Performance enhancement - 'dirty' sample lists only (ones with new data)
259  // The reason this is commented out is because for some analysers producing a new value based on no new data may be valid.
260  // if (!hasNewData) {
261  // // This also prevents 'new' conversions even if no new data has arrived, skewing analysis results
262  // return;
263  // }
264  for (auto& listManager : lists) { // For each input list
265  std::visit([timeNow,this] (auto&& arg) { // Visit each of our list managers (that may be used as an output list)
266  for (auto& mgrPair : arg) { // For each output list instance // arg = ListManager<SampleList<InputValueT>,SrcSz>
267  // Derived Input type and size from 'list' input list
268 
269  auto& sampled = mgrPair.first;
270  auto& list = mgrPair.second;
271 
272  for (auto& outputListManagerV : lists) {
273  std::visit([timeNow, &list, &sampled, this] (auto&& outputListManager) { // Visit each of our list managers (that may be used as an output list)
274  using InputValT = typename std::remove_reference_t<decltype(list)>::value_type;
275  using LMValT = typename std::remove_reference_t<decltype(outputListManager)>::value_type;
276  // std::cout << "Trying to call analysers for source type " << typeid(list).name() << " to output type " << typeid(outputListManager).name() << std::endl;
277 
278  // Check for presence of an analyser that converts from InputValT to LMValT
279  if (runners.template hasMatchingAnalyser<InputValT,LMValT>()) {
280  // std::cout << "Found matching analyser!" << std::endl;
281  /*bool newDataGenerated =*/ runners.template analyse(timeNow,sampled,list,outputListManager, *this); // <InputValT,InputSz,LMValT,LMSz>
282  }
283  }, outputListManagerV);
284  }
285  }
286  }, listManager);
287  }
288  // hasNewData = false;
289  }
290 
291 private:
292  // TODO make sizes a parameterised list derived from template parameters
293  VariantSet<ListManager<SourceTypes,ListSize>...> lists; // exactly one list manager per value type
294  AnalysisDelegateManagerT& delegates;
295  AnalysisProviderManagerT& runners;
296  // bool hasNewData;
297 };
298 
299 }
300 }
301 
302 #endif
Definition: date.h:18
std::size_t SampledID
Definition: sampling.h:24
Acts as a non-global memory arena for arbitrary classes.
Definition: aggregates.h:15
Convenience wrapper for all AnalysisDelegate types used by the analysis API.
Definition: runner.h:105
DelegateT & get()
CAN THROW std::bad_variant_access.
Definition: runner.h:125
Convenience wrapper for all AnalysisProvider types used by the analysis API.
Definition: runner.h:152
ProviderT & get()
CAN THROW std::bad_variant_access.
Definition: runner.h:178
Manages all sample lists, sources, sinks, and analysis instances for all data generated within a syst...
Definition: runner.h:233
void newSample(SampledID sampled, sampling::Sample< ValT > sample)
We are an analysis delegate ourselves - this is used by Source types, and by producers (analysis runn...
Definition: runner.h:242
void run(Date timeNow)
Run the relevant analyses given the current time point.
Definition: runner.h:255
Manages a set of lists for a particular Sample Value Type.
Definition: runner.h:23
A fixed size set that holds exactly one instance of the std::variant for each of the specified ValTs ...
Definition: runner.h:58
ValT & get()
CAN THROW std::bad_variant_access.
Definition: runner.h:68
The Sample taken from an object with ID of type SampledID.
Definition: sampling.h:28
Definition: sampling.h:102