Bertini 2 d882924 (2026-10-06 19:56 UTC)
Numerical algebraic geometry library (C++ core)
Loading...
Searching...
No Matches
observable.hpp
Go to the documentation of this file.
1//This file is part of Bertini 2.
2//
3//bertini2/detail/observable.hpp is free software: you can redistribute it and/or modify
4//it under the terms of the GNU General Public License as published by
5//the Free Software Foundation, either version 3 of the License, or
6//(at your option) any later version.
7//
8//bertini2/detail/observable.hpp is distributed in the hope that it will be useful,
9//but WITHOUT ANY WARRANTY; without even the implied warranty of
10//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11//GNU General Public License for more details.
12//
13//You should have received a copy of the GNU General Public License
14//along with bertini2/detail/observable.hpp. If not, see <http://www.gnu.org/licenses/>.
15//
16// Copyright(C) Bertini2 Development Team
17//
18// See <http://www.gnu.org/licenses/> for a copy of the license,
19// as well as COPYING. Bertini2 is provided with permitted
20// additional terms in the b2/licenses/ directory.
21
29
30#ifndef BERTINI_DETAIL_OBSERVABLE_HPP
31#define BERTINI_DETAIL_OBSERVABLE_HPP
32
35
36#include <algorithm>
37#include <memory>
38#include <mutex>
39#include <stdexcept>
40#include <typeindex>
41#include <typeinfo>
42#include <unordered_map>
43#include <utility>
44#include <vector>
45
46#include <boost/core/demangle.hpp>
47
48namespace bertini{
49
55 struct IncompatibleObserver : public std::runtime_error
56 {
57 using std::runtime_error::runtime_error;
58 };
59
66 {
67 public:
68
69 virtual ~Observable() = default;
70
71 Observable() = default;
72
81 Observable& operator=(Observable const&) { return *this; }
82
83
106 void AddObserver(AnyObserver& new_observer) const
107 {
108 std::lock_guard<std::recursive_mutex> lock(notify_mutex_);
109 RejectIfIncompatible(new_observer);
110 Watcher w;
111 w.raw = &new_observer; // non-owning: owned stays null
112 EnqueueOrApplyAdd(std::move(w));
113 }
114
127 void AddObserver(std::shared_ptr<AnyObserver> const& new_observer) const
128 {
129 std::lock_guard<std::recursive_mutex> lock(notify_mutex_);
130 RejectIfIncompatible(*new_observer);
131 Watcher w;
132 w.owned = new_observer; // owning: keeps the observer alive
133 w.raw = new_observer.get();
134 EnqueueOrApplyAdd(std::move(w));
135 }
136
143 void RemoveObserver(AnyObserver& observer) const
144 {
145 std::lock_guard<std::recursive_mutex> lock(notify_mutex_);
146 if (dispatch_depth_ > 0)
147 {
148 PendingOp op;
149 op.kind = PendingOp::Remove;
150 op.watcher.raw = &observer;
151 pending_ops_.push_back(std::move(op));
152 }
153 else
154 RemoveWatcher(&observer);
155 }
156
166 virtual bool ObservableIsA(std::type_index t) const
167 {
168 return t == std::type_index(typeid(void))
169 || t == std::type_index(typeid(*this));
170 }
171
172 protected:
173
185 void NotifyObservers(AnyEvent const& e) const
186 {
187 DispatchEvent(e);
188 }
189
192 {
193 DispatchEvent(e);
194 }
195
204 bool HasObservers() const
205 {
206 if (!untyped_watchers_.empty())
207 return true;
208 for (auto const& kv : typed_watchers_)
209 if (!kv.second.empty())
210 return true;
211 return false;
212 }
213
214 private:
215
222 struct Watcher
223 {
224 std::shared_ptr<AnyObserver> owned; // non-null iff owning
225 AnyObserver* raw = nullptr; // identity + the pointer events go to
226 };
227
228 using ObserverList = std::vector<Watcher>;
229
230 struct PendingOp
231 {
232 enum Kind { Add, Remove } kind;
233 Watcher watcher; // Add: the watcher (carries `owned` for owning adds); Remove: identity in .raw
234 };
235
236 void RejectIfIncompatible(AnyObserver const& obs) const
237 {
238 if (!ObservableIsA(obs.ObservedKind()))
239 throw IncompatibleObserver(
240 "this observable (" + boost::core::demangle(typeid(*this).name())
241 + ") does not accept an observer for "
242 + boost::core::demangle(obs.ObservedKind().name()));
243 }
244
245 void EnqueueOrApplyAdd(Watcher w) const
246 {
247 if (dispatch_depth_ > 0)
248 {
249 PendingOp op;
250 op.kind = PendingOp::Add;
251 op.watcher = std::move(w); // carries `owned`, so the observer stays alive until drained
252 pending_ops_.push_back(std::move(op));
253 }
254 else
255 AddWatcher(std::move(w));
256 }
257
258 void AddWatcher(Watcher const& w) const
259 {
260 auto present_in = [&](ObserverList const& c) {
261 return std::find_if(c.begin(), c.end(),
262 [&](Watcher const& x){ return x.raw == w.raw; }) != c.end();
263 };
264
265 auto types = w.raw->SubscribedEventTypes();
266 if (types.empty())
267 {
268 if (!present_in(untyped_watchers_))
269 untyped_watchers_.push_back(w);
270 }
271 else
272 {
273 for (auto& ti : types)
274 {
275 auto& bucket = typed_watchers_[ti];
276 if (std::find_if(bucket.begin(), bucket.end(),
277 [&](Watcher const& x){ return x.raw == w.raw; }) == bucket.end())
278 bucket.push_back(w);
279 }
280 }
281 }
282
283 void RemoveWatcher(AnyObserver* who) const
284 {
285 auto erase_from = [&](ObserverList& c) {
286 c.erase(std::remove_if(c.begin(), c.end(),
287 [&](Watcher const& x){ return x.raw == who; }),
288 c.end());
289 };
290 erase_from(untyped_watchers_);
291 for (auto& [ti, bucket] : typed_watchers_)
292 erase_from(bucket);
293 }
294
295 void DrainPendingOps() const
296 {
297 // Apply in request order so that, within one notification, a remove
298 // followed by a re-add (or vice versa) lands on the intended state.
299 for (auto& op : pending_ops_)
300 {
301 if (op.kind == PendingOp::Add)
302 AddWatcher(op.watcher);
303 else
304 RemoveWatcher(op.watcher.raw);
305 }
306 pending_ops_.clear();
307 }
308
309 void DispatchEvent(AnyEvent const& e) const
310 {
311 std::lock_guard<std::recursive_mutex> lock(notify_mutex_);
312 ++dispatch_depth_;
313
314 auto run = [&](ObserverList& bucket) {
315 for (auto& w : bucket)
316 {
317 // w.raw stays valid for the whole call: owning entries hold a
318 // shared_ptr, non-owning ones are the caller's responsibility.
319 if (w.raw->Observe(e) == ObserveResult::Unsubscribe)
320 {
321 PendingOp op;
322 op.kind = PendingOp::Remove;
323 op.watcher.raw = w.raw;
324 pending_ops_.push_back(std::move(op));
325 }
326 }
327 };
328
329 auto it = typed_watchers_.find(std::type_index(typeid(e)));
330 if (it != typed_watchers_.end())
331 run(it->second);
332 run(untyped_watchers_);
333
334 --dispatch_depth_;
335 if (dispatch_depth_ == 0)
336 DrainPendingOps();
337 }
338
339 mutable std::unordered_map<std::type_index, ObserverList> typed_watchers_;
340 mutable ObserverList untyped_watchers_;
341
342 mutable unsigned dispatch_depth_ = 0;
343 mutable std::vector<PendingOp> pending_ops_;
344
345 // Serializes notification and watcher-list mutation so the same observable can be safely
346 // notified from several worker threads at once (the MPI-less threaded solve fires per-path
347 // events from each tracking thread onto one shared solver). Recursive because an observer's
348 // Observe() may itself Add/RemoveObserver on this same observable during a dispatch (the
349 // "meta-observer" pattern) -- that re-entrant call re-locks on the same thread and is routed
350 // through the deferred pending_ops_ path via dispatch_depth_. Held across Observe() calls,
351 // so observer callbacks are serialized; the heavy numerical work runs outside this lock.
352 // Single-threaded callers pay one uncontended recursive lock per event -- negligible.
353 mutable std::recursive_mutex notify_mutex_;
354 };
355
356} // namespace bertini
357
358
359#endif
Strawman Event type, enabling polymorphism.
Definition events.hpp:57
Strawman base class for Observer objects.
Definition observer.hpp:68
virtual bool ObservableIsA(std::type_index t) const
Whether this observable may be observed as the given type.
Definition observable.hpp:166
void RemoveObserver(AnyObserver &observer) const
Remove an observer from this observable.
Definition observable.hpp:143
bool HasObservers() const
Whether anyone is currently observing (conservative).
Definition observable.hpp:204
void AddObserver(AnyObserver &new_observer) const
Add an observer (non-owning) to observe this observable.
Definition observable.hpp:106
virtual ~Observable()=default
Observable & operator=(Observable const &)
Copy-assignment: leaves the target's own watcher list untouched (see the copy ctor note).
Definition observable.hpp:81
void AddObserver(std::shared_ptr< AnyObserver > const &new_observer) const
Add an observer whose lifetime is co-owned by this observable.
Definition observable.hpp:127
void NotifyObservers(AnyEvent const &e) const
Sends an event to observers that subscribed to its exact dynamic type, then to all catch-all (untyped...
Definition observable.hpp:185
void NotifyObservers(AnyEvent &e) const
Notify all observers of a (mutable) event.
Definition observable.hpp:191
Observable(Observable const &)
Copies do NOT inherit the watcher list: observers subscribed to the source object,...
Definition observable.hpp:79
Contains the detail/events base types.
Definition algorithm_builder.hpp:36
@ Unsubscribe
Definition observer.hpp:59
@ Add
Definition straight_line_program.hpp:170
Contains the observer base types.
Thrown when an observer is attached to an observable it cannot observe.
Definition observable.hpp:56