constraint.h
Go to the documentation of this file.
1 #ifndef TTG_CONSTRAINT_H
2 #define TTG_CONSTRAINT_H
3 
4 #include <functional>
5 #include <atomic>
6 #include <cstdint>
7 #include <mutex>
8 #include <map>
9 
10 #include "ttg/util/span.h"
11 
12 #ifdef TTG_USE_BUNDLED_BOOST_CALLABLE_TRAITS
14 #else
16 #endif
17 
18 namespace ttg {
19 
20  template<typename Key>
21  struct ConstraintBase {
22  using key_type = Key;
23  using listener_t = std::function<void(const ttg::span<key_type>&)>;
24 
26  { }
27 
29  : m_listeners(std::move(cb.m_listeners))
30  { }
31 
33  : m_listeners(cb.m_listeners)
34  {}
35 
37  m_listeners = std::move(cb.m_listeners);
38  }
40  m_listeners = cb.m_listeners;
41  }
42 
43  virtual ~ConstraintBase() = default;
44 
46  auto g = this->lock_guard();
47  m_listeners.insert_or_assign(tt, std::move(l));
48  }
49 
50  void notify_listener(const ttg::span<key_type>& keys, ttg::TTBase* tt) {
51  auto& release = m_listeners[tt];
52  release(keys);
53  }
54 
55  protected:
56 
57  auto lock_guard() {
58  return std::lock_guard{m_mtx};
59  }
60 
61  private:
62  std::map<ttg::TTBase*, listener_t> m_listeners;
63  std::mutex m_mtx;
64  };
65 
66  template<typename Key,
67  typename Ordinal = std::size_t,
68  typename Compare = std::less<Ordinal>,
69  typename Mapper = ttg::Void>
70  struct SequencedKeysConstraint : public ConstraintBase<Key> {
71 
72  using key_type = std::conditional_t<ttg::meta::is_void_v<Key>, ttg::Void, Key>;
73  using ordinal_type = Ordinal;
74  using keymap_t = std::function<Ordinal(const key_type&)>;
75  using compare_t = Compare;
77 
78  static_assert((!Compare{}(Ordinal{}, Ordinal{})), "Comparator must provide strict ordering.");
79 
80  protected:
81  struct sequence_elem_t {
82  std::map<ttg::TTBase*, std::vector<key_type>> m_keys;
83 
84  sequence_elem_t() = default;
86  sequence_elem_t(const sequence_elem_t&) = default;
89 
90  void add_key(const key_type& key, ttg::TTBase* tt) {
91  auto it = m_keys.find(tt);
92  if (it == m_keys.end()) {
93  m_keys.insert(std::make_pair(tt, std::vector<key_type>{key}));
94  } else {
95  it->second.push_back(key);
96  }
97  }
98  };
99 
100  bool comp_equal(const Ordinal& a, const Ordinal& b) const {
101  return (!m_order(a, b) && !m_order(b, a));
102  }
103 
104  bool eligible(const Ordinal& ord) const {
105  return m_order(ord, m_current) || comp_equal(ord, m_current);
106  }
107 
108  bool check_key_impl(const key_type& key, Ordinal ord, ttg::TTBase *tt) {
109  if (!m_stopped) {
110  if (eligible(ord)) {
111  // key should be executed
112  if (m_auto_release) { // only needed for auto-release
113  m_active.fetch_add(1, std::memory_order_relaxed);
114  // revert the current ordinal to the lower ordinal
115  m_current = ord;
116  }
117  return true;
118  } else if (m_sequence.empty() && m_auto_release && 0 == m_active.load(std::memory_order_relaxed)) {
119  // there are no keys (active or blocked) so we execute to avoid a deadlock
120  // we don't change the current ordinal because there may be lower ordinals coming in later
121  // NOTE: there is a race condition between the check here and the increment above.
122  // This is mostly benign as it can lead to out-of-sequence released tasks.
123  // Avoiding this race would incur significant overheads.
124  m_active.fetch_add(1, std::memory_order_relaxed);
125  return true;
126  }
127  }
128  // key should be deferred
129  auto g = this->lock_guard();
130  if (!m_stopped && eligible(ord)) {
131  // someone released this ordinal while we took the lock
132  return true;
133  }
134  auto it = m_sequence.find(ord);
135  if (it == m_sequence.end()) {
136  auto [iter, success] = m_sequence.insert(std::make_pair(ord, sequence_elem_t{}));
137  assert(success);
138  it = iter;
139  }
140  it->second.add_key(key, tt);
141  return false;
142  }
143 
144 
146  if (m_auto_release) {
147  auto active = m_active.fetch_sub(1, std::memory_order_relaxed) - 1;
148  if (0 == active) {
149  release_next();
150  }
151  }
152  }
153 
154  // used in the auto case
155  void release_next() {
156  if (this->m_stopped) {
157  // don't release tasks if we're stopped
158  return;
159  }
160  // trigger the next sequence
161  sequence_elem_t elem;
162  {
163  // extract the next sequence
164  auto g = this->lock_guard();
165  auto it = this->m_sequence.begin(); // sequence is ordered by ordinal
166  if (it == this->m_sequence.end()) {
167  return; // nothing to be done
168  }
169  this->m_current = it->first;
170  elem = std::move(it->second);
171  this->m_sequence.erase(it);
172  }
173 
174  for (auto& seq : elem.m_keys) {
175  // account for the newly active keys
176  this->m_active.fetch_add(seq.second.size(), std::memory_order_relaxed);
177  this->notify_listener(ttg::span<key_type>(seq.second.data(), seq.second.size()), seq.first);
178  }
179  }
180 
181 
182  // used in the non-auto case
183  void release_next(ordinal_type ord, bool force_check = false) {
184  if (this->m_stopped) {
185  // don't release tasks if we're stopped but remember that this was released
186  this->m_current = ord;
187  return;
188  }
189  if (!force_check && eligible(ord)) {
190  return; // already at the provided ordinal, nothing to be done
191  }
192  // trigger the next sequence(s) (m_sequence is ordered by ordinal)
193  std::vector<sequence_elem_t> seqs;
194  {
195  auto g = this->lock_guard();
196  // set current ordinal
197  this->m_current = ord;
198  for (auto it = this->m_sequence.begin(); it != this->m_sequence.end();) {
199  if (!eligible(it->first)) break;
200  // extract the next sequence
201  this->m_current = it->first;
202  seqs.push_back(std::move(it->second));
203  it = this->m_sequence.erase(it);
204  }
205  }
206  for (auto& elem : seqs) {
207  for (auto& e : elem.m_keys) {
208  // account for the newly active keys
209  this->notify_listener(ttg::span<key_type>(e.second.data(), e.second.size()), e.first);
210  }
211  }
212  }
213 
214  public:
215 
220  : base_t()
222  { }
223 
224  template<typename Mapper_>
225  requires(std::is_invocable_v<Mapper_, Key>)
226  SequencedKeysConstraint(Mapper_&& map, bool auto_release = false)
227  : base_t()
228  , m_map(std::forward<Mapper_>(map))
230  { }
231 
233 
235 
237 
239 
240  virtual ~SequencedKeysConstraint() = default;
241 
242  /* Check whether the key may be executed.
243  * Returns true if the key may be executed.
244  * Otherwise, returns false and */
245  template<typename Key_ = key_type, typename Mapper_ = Mapper>
246  std::enable_if_t<!ttg::meta::is_void_v<Key_> && !ttg::meta::is_void_v<Mapper_>, bool>
247  check(const key_type& key, ttg::TTBase *tt) {
248  ordinal_type ord = m_map(key);
249  return this->check_key_impl(key, ord, tt);
250  }
251 
252  template<typename Key_ = key_type, typename Mapper_ = Mapper>
253  std::enable_if_t<!ttg::meta::is_void_v<Key_> && ttg::meta::is_void_v<Mapper_>, bool>
254  check(const key_type& key, Ordinal ord, ttg::TTBase *tt) {
255  return this->check_key_impl(key, ord, tt);
256  }
257 
258  template<typename Key_ = key_type, typename Mapper_ = Mapper>
259  std::enable_if_t<ttg::meta::is_void_v<Key_> && !ttg::meta::is_void_v<Mapper_>, bool>
261  return this->check_key_impl(ttg::Void{}, m_map(), tt);
262  }
263 
264  template<typename Key_ = key_type, typename Mapper_ = Mapper>
265  std::enable_if_t<ttg::meta::is_void_v<Key_> && ttg::meta::is_void_v<Mapper_>, bool>
267  return this->check_key_impl(ttg::Void{}, ord, tt);
268  }
269 
270  template<typename Key_ = key_type, typename Mapper_ = Mapper>
271  std::enable_if_t<!ttg::meta::is_void_v<Key_> && !ttg::meta::is_void_v<Mapper_>>
272  complete(const key_type& key, ttg::TTBase *tt) {
273  this->complete_key_impl();
274  }
275 
276  template<typename Key_ = key_type, typename Mapper_ = Mapper>
277  std::enable_if_t<!ttg::meta::is_void_v<Key_> && ttg::meta::is_void_v<Mapper_>>
278  complete(const key_type& key, Ordinal ord, ttg::TTBase *tt) {
279  this->complete_key_impl();
280  }
281 
282  template<typename Key_ = key_type, typename Mapper_ = Mapper>
283  std::enable_if_t<!ttg::meta::is_void_v<Key_> && ttg::meta::is_void_v<Mapper_>>
284  complete(Ordinal ord, ttg::TTBase *tt) {
285  this->complete_key_impl();
286  }
287 
288  template<typename Key_ = key_type, typename Mapper_ = Mapper>
289  std::enable_if_t<!ttg::meta::is_void_v<Key_> && !ttg::meta::is_void_v<Mapper_>>
291  this->complete_key_impl();
292  }
293 
299  void stop() {
300  m_stopped = true;
301  }
302 
308  void start() {
309  if (m_stopped) {
310  m_stopped = false;
311  if (m_auto_release) {
312  release_next();
313  } else {
314  auto ord = m_current;
315  // release the first set of available keys if none were set explicitly
316  if (ord == std::numeric_limits<ordinal_type>::min() &&
317  this->m_sequence.begin() != this->m_sequence.end()) {
318  ord = this->m_sequence.begin()->first;
319  }
320  release_next(ord, true); // force the check for a next release even if the current ordinal hasn't changed
321  }
322  }
323  }
324 
328  void release(ordinal_type ord = 0) {
329  if (m_auto_release) {
330  // last key for this ordinal, release the next
331  // the provided ordinal is ignored
332  release_next();
333  } else {
334  release_next(ord);
335  }
336  }
337 
338  bool is_auto() const {
339  return m_auto_release;
340  }
341 
342 
343  protected:
344  std::map<ordinal_type, sequence_elem_t, compare_t> m_sequence;
345  ordinal_type m_current = std::numeric_limits<ordinal_type>::min();
346  [[no_unique_address]]
347  Mapper m_map;
348  [[no_unique_address]]
350  std::atomic<std::size_t> m_active;
351  bool m_stopped = false;
352  bool m_auto_release = false;
353  };
354 
355  // deduction guides: take type of first argument to Mapper as the key type
356  // TODO: can we use the TTG callable_args instead?
357  template<typename Mapper, typename = std::enable_if_t<std::is_invocable_v<Mapper, std::decay_t<std::tuple_element_t<0, boost::callable_traits::args_t<Mapper>>>>>>
360  std::decay_t<std::tuple_element_t<0, boost::callable_traits::args_t<Mapper>>>,
361  std::decay_t<boost::callable_traits::return_type_t<Mapper>>,
362  std::less<std::decay_t<boost::callable_traits::return_type_t<Mapper>>>,
363  std::enable_if_t<std::is_invocable_v<Mapper, std::decay_t<std::tuple_element_t<0, boost::callable_traits::args_t<Mapper>>>>, Mapper>
364  >;
365 
366  template<typename Mapper, typename = std::enable_if_t<std::is_invocable_v<Mapper, std::decay_t<std::tuple_element_t<0, boost::callable_traits::args_t<Mapper>>>>>>
367  SequencedKeysConstraint(Mapper&&, bool)
369  std::decay_t<std::tuple_element_t<0, boost::callable_traits::args_t<Mapper>>>,
370  std::decay_t<boost::callable_traits::return_type_t<Mapper>>,
371  std::less<std::decay_t<boost::callable_traits::return_type_t<Mapper>>>,
372  std::enable_if_t<std::is_invocable_v<Mapper, std::decay_t<std::tuple_element_t<0, boost::callable_traits::args_t<Mapper>>>>, Mapper>
373  >;
374 
375  template<typename Key, typename Ordinal, typename Compare, typename Mapper>
378 
379  template<typename Key, typename Ordinal, typename Compare, typename Mapper>
382 
397  template<template<typename...> typename Constraint, typename... Args>
398  auto make_shared_constraint(Args&&... args) {
399  return std::make_shared<decltype(Constraint(std::forward<Args>(args)...))>(std::forward<Args>(args)...);
400  }
401 
406  template<typename Constraint, typename... Args>
407  auto make_shared_constraint(Args&&... args) {
408  return std::make_shared<Constraint>(std::forward<Args>(args)...);
409  }
410 
411 
412 
413 } // namespace ttg
414 
415 #endif // TTG_CONSTRAINT_H
A base class for all template tasks.
Definition: tt.h:30
A complete version of void.
Definition: void.h:11
top-level TTG namespace contains runtime-neutral functionality
Definition: keymap.h:8
SequencedKeysConstraint(Mapper &&) -> SequencedKeysConstraint< std::decay_t< std::tuple_element_t< 0, boost::callable_traits::args_t< Mapper >>>, std::decay_t< boost::callable_traits::return_type_t< Mapper >>, std::less< std::decay_t< boost::callable_traits::return_type_t< Mapper >>>, std::enable_if_t< std::is_invocable_v< Mapper, std::decay_t< std::tuple_element_t< 0, boost::callable_traits::args_t< Mapper >>>>, Mapper > >
auto make_shared_constraint(Args &&... args)
Definition: constraint.h:398
void notify_listener(const ttg::span< key_type > &keys, ttg::TTBase *tt)
Definition: constraint.h:50
ConstraintBase(ConstraintBase &&cb)
Definition: constraint.h:28
ConstraintBase & operator=(ConstraintBase &&cb)
Definition: constraint.h:36
virtual ~ConstraintBase()=default
ConstraintBase(const ConstraintBase &cb)
Definition: constraint.h:32
std::function< void(const ttg::span< key_type > &)> listener_t
Definition: constraint.h:23
void add_listener(listener_t l, ttg::TTBase *tt)
Definition: constraint.h:45
ConstraintBase & operator=(const ConstraintBase &cb)
Definition: constraint.h:39
std::map< ttg::TTBase *, std::vector< key_type > > m_keys
Definition: constraint.h:82
sequence_elem_t(sequence_elem_t &&)=default
sequence_elem_t(const sequence_elem_t &)=default
sequence_elem_t & operator=(const sequence_elem_t &)=default
void add_key(const key_type &key, ttg::TTBase *tt)
Definition: constraint.h:90
sequence_elem_t & operator=(sequence_elem_t &&)=default
SequencedKeysConstraint(bool auto_release=false)
Definition: constraint.h:219
std::enable_if_t<!ttg::meta::is_void_v< Key_ > &&ttg::meta::is_void_v< Mapper_ > > complete(const key_type &key, Ordinal ord, ttg::TTBase *tt)
Definition: constraint.h:278
bool check_key_impl(const key_type &key, Ordinal ord, ttg::TTBase *tt)
Definition: constraint.h:108
std::atomic< std::size_t > m_active
Definition: constraint.h:350
requires(std::is_invocable_v< Mapper_, Key >) SequencedKeysConstraint(Mapper_ &&map
bool comp_equal(const Ordinal &a, const Ordinal &b) const
Definition: constraint.h:100
SequencedKeysConstraint & operator=(SequencedKeysConstraint &&skc)=default
std::enable_if_t<!ttg::meta::is_void_v< Key_ > &&ttg::meta::is_void_v< Mapper_ >, bool > check(const key_type &key, Ordinal ord, ttg::TTBase *tt)
Definition: constraint.h:254
SequencedKeysConstraint & operator=(const SequencedKeysConstraint &skc)=default
virtual ~SequencedKeysConstraint()=default
void release(ordinal_type ord=0)
Definition: constraint.h:328
std::conditional_t< ttg::meta::is_void_v< Key >, ttg::Void, Key > key_type
Definition: constraint.h:72
std::enable_if_t< ttg::meta::is_void_v< Key_ > &&!ttg::meta::is_void_v< Mapper_ >, bool > check(ttg::TTBase *tt)
Definition: constraint.h:260
std::map< ordinal_type, sequence_elem_t, compare_t > m_sequence
Definition: constraint.h:344
std::enable_if_t<!ttg::meta::is_void_v< Key_ > &&ttg::meta::is_void_v< Mapper_ > > complete(Ordinal ord, ttg::TTBase *tt)
Definition: constraint.h:284
std::enable_if_t< ttg::meta::is_void_v< Key_ > &&ttg::meta::is_void_v< Mapper_ >, bool > check(ordinal_type ord, ttg::TTBase *tt)
Definition: constraint.h:266
void release_next(ordinal_type ord, bool force_check=false)
Definition: constraint.h:183
SequencedKeysConstraint(const SequencedKeysConstraint &skc)=default
std::function< Ordinal(const key_type &)> keymap_t
Definition: constraint.h:74
std::enable_if_t<!ttg::meta::is_void_v< Key_ > &&!ttg::meta::is_void_v< Mapper_ > > complete(ttg::TTBase *tt)
Definition: constraint.h:290
std::enable_if_t<!ttg::meta::is_void_v< Key_ > &&!ttg::meta::is_void_v< Mapper_ > > complete(const key_type &key, ttg::TTBase *tt)
Definition: constraint.h:272
std::enable_if_t<!ttg::meta::is_void_v< Key_ > &&!ttg::meta::is_void_v< Mapper_ >, bool > check(const key_type &key, ttg::TTBase *tt)
Definition: constraint.h:247
bool eligible(const Ordinal &ord) const
Definition: constraint.h:104