KDBindings API Documentation  1.0.95
connection_evaluator.h
Go to the documentation of this file.
1 /*
2  This file is part of KDBindings.
3 
4  SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5  Author: Shivam Kunwar <shivam.kunwar@kdab.com>
6 
7  SPDX-License-Identifier: MIT
8 
9  Contact KDAB at <info@kdab.com> for commercial licensing options.
10 */
11 #pragma once
12 
13 #include <algorithm>
14 #include <functional>
15 #include <mutex>
16 
18 
19 namespace KDBindings {
20 
33 {
34 
35 public:
37  ConnectionEvaluator() = default;
38 
40  // As it is designed to manage connections,
41  // and copying it could lead to unexpected behavior, including duplication of connections and issues
42  // related to connection lifetimes. Therefore, it is intentionally made non-copyable.
43  ConnectionEvaluator(const ConnectionEvaluator &) noexcept = delete;
44 
45  ConnectionEvaluator &operator=(const ConnectionEvaluator &) noexcept = delete;
46 
48  // As they are captures by-reference
49  // by the Signal, so moving them would lead to a dangling reference.
50  ConnectionEvaluator(ConnectionEvaluator &&other) noexcept = delete;
51 
52  ConnectionEvaluator &operator=(ConnectionEvaluator &&other) noexcept = delete;
53 
54  virtual ~ConnectionEvaluator() = default;
55 
65  {
66  std::lock_guard<std::recursive_mutex> lock(m_slotInvocationMutex);
67 
68  if (m_isEvaluating) {
69  // We're already evaluating, so we don't want to re-enter this function.
70  return;
71  }
72  m_isEvaluating = true;
73 
74  // Current best-effort error handling will remove any further invocations that were queued.
75  // We could use a queue and use a `while(!empty) { pop_front() }` loop instead to avoid this.
76  // However, we would then ideally use a ring-buffer to avoid excessive allocations, which isn't in the STL.
77  try {
78  for (auto &pair : m_deferredSlotInvocations) {
79  pair.second();
80  }
81  } catch (...) {
82  // Best-effort: Reset the ConnectionEvaluator so that it at least doesn't execute the same erroneous slot multiple times.
83  m_deferredSlotInvocations.clear();
84  m_isEvaluating = false;
85  throw;
86  }
87 
88  m_deferredSlotInvocations.clear();
89  m_isEvaluating = false;
90  }
91 
92 protected:
108  virtual void onInvocationAdded() { }
109 
110 private:
111  template<typename...>
112  friend class Signal;
113 
114  void enqueueSlotInvocation(const ConnectionHandle &handle, const std::function<void()> &slotInvocation)
115  {
116  {
117  std::lock_guard<std::recursive_mutex> lock(m_slotInvocationMutex);
118  m_deferredSlotInvocations.push_back({ handle, std::move(slotInvocation) });
119  }
121  }
122 
123  // Note: This function is marked with noexcept but may theoretically encounter an exception and terminate the program if locking the mutex fails.
124  // If this does happen though, there's likely something very wrong, so std::terminate is actually a reasonable way to handle this.
125  //
126  // In addition, we do need to use a recursive_mutex, as otherwise a slot from `enqueueSlotInvocation` may theoretically call this function and cause undefined behavior.
127  void dequeueSlotInvocation(const ConnectionHandle &handle) noexcept
128  {
129  std::lock_guard<std::recursive_mutex> lock(m_slotInvocationMutex);
130 
131  if (m_isEvaluating) {
132  // It's too late, we're already evaluating the deferred connections.
133  // We can't remove the invocation now, as it might be currently evaluated.
134  // And removing any invocations would be undefined behavior as we would invalidate
135  // the loop indices in `evaluateDeferredConnections`.
136  return;
137  }
138 
139  auto handleMatches = [&handle](const auto &invocationPair) {
140  return invocationPair.first == handle;
141  };
142 
143  // Remove all invocations that match the handle
144  m_deferredSlotInvocations.erase(
145  std::remove_if(m_deferredSlotInvocations.begin(), m_deferredSlotInvocations.end(), handleMatches),
146  m_deferredSlotInvocations.end());
147  }
148 
149  std::vector<std::pair<ConnectionHandle, std::function<void()>>> m_deferredSlotInvocations;
150  // We need to use a recursive mutex here, as `evaluateDeferredConnections` executes arbitrary user code.
151  // This may end up in a call to dequeueSlotInvocation, which locks the same mutex.
152  // We'll also need to add a flag to make sure we don't actually dequeue invocations while we're evaluating them.
153  std::recursive_mutex m_slotInvocationMutex;
154  bool m_isEvaluating = false;
155 };
156 } // namespace KDBindings
Manages and evaluates deferred Signal connections.
ConnectionEvaluator(ConnectionEvaluator &&other) noexcept=delete
virtual ~ConnectionEvaluator()=default
virtual void onInvocationAdded()
Called when a new slot invocation is added.
ConnectionEvaluator(const ConnectionEvaluator &) noexcept=delete
ConnectionEvaluator & operator=(const ConnectionEvaluator &) noexcept=delete
void evaluateDeferredConnections()
Evaluate the deferred connections.
ConnectionEvaluator & operator=(ConnectionEvaluator &&other) noexcept=delete
A ConnectionHandle represents the connection of a Signal to a slot (i.e. a function that is called wh...
The main namespace of the KDBindings library.
Definition: binding.h:21

© Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDBindings
Reactive programming & data binding in C++
https://github.com/KDAB/KDBindings/
Generated by doxygen 1.9.1