Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://mooseframework.inl.gov
3 : //*
4 : //* All rights reserved, see COPYRIGHT for full restrictions
5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 : //*
7 : //* Licensed under LGPL 2.1, please see LICENSE for details
8 : //* https://www.gnu.org/licenses/lgpl-2.1.html
9 :
10 : #pragma once
11 :
12 : #include <map>
13 : #include <string>
14 : #include <vector>
15 : #include <unordered_map>
16 : #include <iostream>
17 : #include <mutex>
18 : #include <type_traits>
19 :
20 : #include "MooseObject.h"
21 : #include "MooseHashing.h"
22 : #include "MooseUtils.h"
23 :
24 : class MooseObject;
25 : class WarehouseStorage;
26 : class TheWarehouse;
27 :
28 : /// Attribute is an abstract class that can be implemented in order to track custom metadata about
29 : /// MooseObject instances - enabling warehouse queries over the attribute. Attribute subclasses
30 : /// must be registered with the warehouse (i.e. via TheWarehouse::registerAttribute) where they will
31 : /// be used *before* objects are added to that warehouse. Specific Attribute instances cannot (and
32 : /// should not) generally be created before the class is registered with a warehouse.
33 : ///
34 : /// In order to work with QueryCache objects, attribute classes should include
35 : /// a public typedef for a Key type, as well as a setFromKey function that
36 : /// takes this type as an argument:
37 : ///
38 : /// @begincode
39 : /// class FooAttribute : public Attribute
40 : /// {
41 : /// public:
42 : /// typedef [type-for-foo] Key;
43 : ///
44 : /// void
45 : /// setFrom(Key k)
46 : /// {
47 : /// // code to mutate/reinitialize FooAttribute using k
48 : /// }
49 : /// }
50 : /// @endcode
51 : class Attribute
52 : {
53 : public:
54 : /// Constructs/initializes a new attribute with the specified name for use in warehouse w. The
55 : /// attribute must have been previously registered with w prior to calling this constructor.
56 : Attribute(TheWarehouse & w, const std::string name);
57 320317144 : virtual ~Attribute() {}
58 :
59 239239086 : inline bool operator==(const Attribute & other) const
60 : {
61 239239086 : return _id == other._id && isEqual(other);
62 : }
63 : inline bool operator!=(const Attribute & other) const { return !(*this == other); }
64 :
65 : /// returns the unique attribute ID associated with all attributes that have the same (mose
66 : /// derived) class as this object. This ID is determined at construction time
67 : /// this
68 317244369 : inline unsigned int id() const { return _id; }
69 :
70 : /// This function must return a deterministic value that is uniquely determined by
71 : /// the data the attribute holds (i.e. is initialized with). Ideally, the data should be
72 : /// uniformly and randomly distributed across the domain of size_t values - e.g. 1 and 2 should
73 : /// hash to completely unrelated values. Use of std::hash for POD is encouraged. A convenience
74 : /// hash_combine function is also provided to combine the results an existing hash with one or
75 : /// more other values.
76 : virtual std::size_t hash() const = 0;
77 :
78 : /// initFrom reads and stores the desired meta-data from obj for later matching comparisons.
79 : virtual void initFrom(const MooseObject * obj) = 0;
80 : /// isMatch returns true if the meta-data stored in this attribute is equivalent to that
81 : /// stored in other. This is is for query matching - not exact equivalence. isMatch does not need
82 : /// to check/compare the values from the instances' id() functions.
83 : virtual bool isMatch(const Attribute & other) const = 0;
84 : /// isEqual returns true if the meta-data stored in this attribute is identical to that
85 : /// stored in other. isEqual does not need to check/compare the values from the instances' id()
86 : /// functions.
87 : virtual bool isEqual(const Attribute & other) const = 0;
88 : /// clone creates and returns and identical (deep) copy of this attribute - i.e. the result of
89 : /// clone should return true if passed into isMatch.
90 : virtual std::unique_ptr<Attribute> clone() const = 0;
91 :
92 : private:
93 : int _id = -1;
94 : };
95 :
96 : #define clonefunc(T) \
97 : virtual std::unique_ptr<Attribute> clone() const override \
98 : { \
99 : return std::unique_ptr<Attribute>(new T(*this)); \
100 : }
101 :
102 : #define hashfunc(...) \
103 : virtual std::size_t hash() const override \
104 : { \
105 : std::size_t h = 0; \
106 : Moose::hash_combine(h, __VA_ARGS__); \
107 : return h; \
108 : }
109 :
110 : /**
111 : * This attribute describes sorting state
112 : */
113 : class AttribSorted : public Attribute
114 : {
115 : public:
116 : typedef bool Key;
117 : void setFrom(const Key & k) { _val = k; }
118 :
119 201894 : AttribSorted(TheWarehouse & w) : Attribute(w, "sorted"), _val(false), _initd(false) {}
120 54326472 : AttribSorted(TheWarehouse & w, bool is_sorted)
121 105945936 : : Attribute(w, "sorted"), _val(is_sorted), _initd(true)
122 : {
123 53649720 : }
124 4783226 : AttribSorted(const AttribSorted &) = default;
125 : AttribSorted(AttribSorted &&) = default;
126 : AttribSorted & operator=(const AttribSorted &) = default;
127 : AttribSorted & operator=(AttribSorted &&) = default;
128 :
129 : virtual void initFrom(const MooseObject * obj) override;
130 : virtual bool isMatch(const Attribute & other) const override;
131 : virtual bool isEqual(const Attribute & other) const override;
132 57876544 : hashfunc(_val);
133 4783226 : clonefunc(AttribSorted);
134 :
135 : private:
136 : bool _val;
137 : bool _initd;
138 : };
139 :
140 : #undef clonefunc
141 : #undef hashfunc
142 :
143 : /// TheWarehouse uses this operator function for indexing and caching queries. So this is
144 : /// important even though you don't see it being called (directly) anywhere - it *IS* being used.
145 : bool operator==(const std::unique_ptr<Attribute> & lhs, const std::unique_ptr<Attribute> & rhs);
146 :
147 : namespace std
148 : {
149 : /// This template specialization allows Attributes to be used as unordered map key.
150 : template <>
151 : struct hash<Attribute>
152 : {
153 : public:
154 278379466 : std::size_t operator()(const Attribute & attrib) const
155 : {
156 278379466 : std::size_t h = attrib.hash();
157 278379466 : Moose::hash_combine(h, attrib.id());
158 278379466 : return h;
159 : }
160 : };
161 :
162 : /// This template specialization allows vector<Attribute> to be used as unordered map key.
163 : template <>
164 : struct hash<std::vector<std::unique_ptr<Attribute>>>
165 : {
166 : public:
167 58286851 : std::size_t operator()(const std::vector<std::unique_ptr<Attribute>> & attribs) const
168 : {
169 58286851 : std::size_t h = 0;
170 336666317 : for (auto & attrib : attribs)
171 278379466 : Moose::hash_combine(h, *attrib);
172 58286851 : return h;
173 : }
174 : };
175 : }
176 :
177 : /// TheWarehouse is a container for MooseObjects that allows querying/filtering over various
178 : /// customizeable attributes. The meta-data about the objects is read/stored when the objects are
179 : /// added to the warehouse - updates to objects' state will not be reflected in query
180 : /// results unless the object is explicitly updated through the warehouse interface. The
181 : /// warehouse object can safely be queried concurrently from multiple threads.
182 : ///
183 : /// Once Query and Attribute objects have been constructed, they are tied to the specific
184 : /// warehouse they were created with. They must not be used for different warehouses or the
185 : /// attribute ID they store internally will be wrong and that is bad.
186 : class TheWarehouse
187 : {
188 : public:
189 : template <typename T>
190 : using KeyType = typename T::Key;
191 : template <typename T>
192 : using AttribType = T *;
193 :
194 : /// QueryCache is a convenient way to construct and pass around (possible
195 : /// partially constructed) warehouse queries. The warehouse's "query()" or
196 : /// "queryCache(...)" functions should generally be used to create new Query
197 : /// objects rather than constructing them directly. A Query object holds a
198 : /// list of persistent conditions used to filter/select objects from the
199 : /// warehouse. When the query is executed/run, results are filtered by
200 : /// "and"ing each condition together - i.e. only objects that match *all*
201 : /// conditions are returned.
202 : ///
203 : ///
204 : /// Template arguments (i.e. Attribs) are used to specify parametrized query
205 : /// conditions. The passed template parameters should be the Attribute
206 : /// classes you want to use for parameterization (i.e. the values that will
207 : /// change from query to query).
208 : template <typename... Attribs>
209 : class QueryCache
210 : {
211 : public:
212 : typedef std::tuple<KeyType<Attribs>...> KeyTuple;
213 : typedef std::tuple<AttribType<Attribs>...> AttribTuple;
214 :
215 21684 : QueryCache() {}
216 :
217 : /// Creates a new query operating on the given warehouse w. You should generally use
218 : /// TheWarehouse::query() instead.
219 39212620 : QueryCache(TheWarehouse & w) : _w(&w)
220 : {
221 39212620 : addAttribs<0, Attribs...>();
222 39212620 : _attribs.reserve(5);
223 39212620 : }
224 :
225 : template <typename T>
226 413608 : QueryCache(const T & q) : _w(&q.warehouse())
227 : {
228 413608 : addAttribs<0, Attribs...>();
229 413608 : _attribs.reserve(5);
230 :
231 2119942 : for (auto & attrib : q.attributes())
232 1706334 : _attribs.push_back(attrib->clone());
233 413608 : }
234 :
235 21684 : QueryCache & operator=(const QueryCache & other)
236 : {
237 21684 : if (this == &other)
238 0 : return *this;
239 :
240 21684 : _attribs.clear();
241 21684 : _w = other._w;
242 : // MUST have own pointers to attribs to avoid data races - don't copy _key_attribs.
243 : // initialize parametrized attributes and tuple:
244 21684 : addAttribs<0, Attribs...>();
245 : _key_tup = other._key_tup;
246 : // do NOT copy the cache.
247 :
248 : // only copy over non-parametrized attributes
249 43368 : for (std::size_t i = std::tuple_size<AttribTuple>::value; i < other._attribs.size(); i++)
250 21684 : _attribs.push_back(other._attribs[i]->clone());
251 21684 : return *this;
252 : }
253 :
254 : /// Copy constructor from another Query
255 : template <typename T>
256 : QueryCache & operator=(const T & q)
257 : {
258 : _attribs.clear();
259 : _w = &q.warehouse();
260 :
261 : addAttribs<0, Attribs...>();
262 : _attribs.reserve(5);
263 :
264 : for (auto & attrib : q.attributes())
265 : _attribs.push_back(attrib->clone());
266 :
267 : return *this;
268 : }
269 :
270 34179560 : QueryCache(const QueryCache & other) : _w(other._w), _key_tup(other._key_tup)
271 : {
272 : // do NOT copy the cache.
273 :
274 : // initialize parametrized attributes and tuple:
275 34179560 : addAttribs<0, Attribs...>(); // MUST have own pointers to attribs to avoid data races.
276 117204573 : for (std::size_t i = std::tuple_size<AttribTuple>::value; i < other._attribs.size(); i++)
277 83025013 : _attribs.push_back(other._attribs[i]->clone());
278 34179560 : }
279 :
280 : /// Adds a new condition to the query. The template parameter T is the Attribute class of
281 : /// interest and args are forwarded to T's constructor to build+add the attribute in-situ.
282 : /// Conditions represent persistent query conditions that do not change
283 : /// from query to query for a particular QueryCache instance.
284 : template <typename T, typename... Args>
285 154401803 : QueryCache & condition(Args &&... args)
286 : {
287 172473088 : _attribs.emplace_back(new T(*_w, std::forward<Args>(args)...));
288 154401803 : _cache.clear(); // invalidate cache if base query changes.
289 154401803 : return *this;
290 : }
291 :
292 : /// clone creates and returns an independent copy of the query in its current state.
293 12307946 : QueryCache clone() const { return Query(*this); }
294 : /// count returns the number of results that match the query (this requires actually running
295 : /// the query).
296 331115 : std::size_t count() { return _w->count(_attribs); }
297 :
298 413608 : TheWarehouse & warehouse() const { return *_w; }
299 :
300 : /// attribs returns a copy of the constructed Attribute list for the query in its current state.
301 415361 : std::vector<std::unique_ptr<Attribute>> attributes() const { return clone()._attribs; }
302 :
303 : /// queryInto executes the query and stores the results in the given
304 : /// vector. For parametrized queries (i.e. QueryCaches created with more
305 : /// than zero template arguments) args must contain one value for each
306 : /// parametrized query attribute - the types of args should be equal to the
307 : /// ::Key typedef specified for each corresponding parametrized attribute.
308 : /// All results must be castable to the templated type T. If the objects
309 : /// we are querying into inherit from the dependency resolver interface, then
310 : /// they will be sorted
311 : template <typename T, typename... Args>
312 92216284 : std::vector<T *> & queryInto(std::vector<T *> & results, Args &&... args)
313 : {
314 92216284 : return queryIntoHelper(results, true, args...);
315 : }
316 :
317 : /// queryInto executes the query and stores the results in the given
318 : /// vector. For parametrized queries (i.e. QueryCaches created with more
319 : /// than zero template arguments) args must contain one value for each
320 : /// parametrized query attribute - the types of args should be equal to the
321 : /// ::Key typedef specified for each corresponding parametrized attribute.
322 : /// All results must be castable to the templated type T. These objects
323 : /// will not be sorted
324 : template <typename T, typename... Args>
325 24339996 : std::vector<T *> & queryIntoUnsorted(std::vector<T *> & results, Args &&... args)
326 : {
327 24339996 : return queryIntoHelper(results, false, args...);
328 : }
329 :
330 : /// Gets the number of attributes associated with the cached query
331 : std::size_t numAttribs() const { return _attribs.size(); }
332 :
333 : private:
334 : /// queryInto executes the query and stores the results in the given
335 : /// vector. For parametrized queries (i.e. QueryCaches created with more
336 : /// than zero template arguments) args must contain one value for each
337 : /// parametrized query attribute - the types of args should be equal to the
338 : /// ::Key typedef specified for each corresponding parametrized attribute.
339 : /// All results must be castable to the templated type T. If the objects
340 : /// we are querying into inherit from the dependency resolver interface, then
341 : /// they will be sorted if \p sort is true
342 : template <typename T, typename... Args>
343 116556280 : std::vector<T *> & queryIntoHelper(std::vector<T *> & results, const bool sort, Args &&... args)
344 : {
345 116556280 : std::lock_guard<std::mutex> lock(_cache_mutex);
346 116556280 : setKeysInner<0, KeyType<Attribs>...>(args...);
347 :
348 : std::size_t query_id;
349 116556280 : const auto entry = _cache.find(std::make_pair(sort, _key_tup));
350 116556280 : if (entry == _cache.end())
351 : {
352 53600834 : setAttribsInner<0, KeyType<Attribs>...>(args...);
353 : // add the sort attribute. No need (I think) to clear the cache because the base query is
354 : // not changing
355 53600834 : _attribs.emplace_back(new AttribSorted(*_w, sort));
356 53600834 : query_id = _w->queryID(_attribs);
357 53600834 : _cache[std::make_pair(sort, _key_tup)] = query_id;
358 : // remove the sort attribute
359 53600834 : _attribs.pop_back();
360 : }
361 : else
362 62955446 : query_id = entry->second;
363 :
364 233112560 : return _w->queryInto(query_id, results);
365 116556280 : }
366 :
367 : template <int Index, typename A, typename... As>
368 1241202 : void addAttribs()
369 : {
370 1241202 : std::get<Index>(_attrib_tup) = new A(*_w);
371 1241202 : _attribs.emplace_back(std::get<Index>(_attrib_tup));
372 1241202 : addAttribs<Index + 1, As...>();
373 1241202 : }
374 : template <int Index>
375 73827472 : void addAttribs()
376 : {
377 73827472 : }
378 :
379 : template <int Index, typename K, typename... Args>
380 26131971 : void setKeysInner(K & k, Args &... args)
381 : {
382 26131971 : std::get<Index>(_key_tup) = k;
383 26131971 : setKeysInner<Index + 1, Args...>(args...);
384 26131971 : }
385 : template <int Index>
386 116556280 : void setKeysInner()
387 : {
388 116556280 : }
389 :
390 : template <int Index, typename K, typename... Args>
391 5639652 : void setAttribsInner(K k, Args &... args)
392 : {
393 5639652 : std::get<Index>(_attrib_tup)->setFrom(k);
394 5639652 : setAttribsInner<Index + 1, Args...>(args...);
395 5639652 : }
396 : template <int Index>
397 53600834 : void setAttribsInner()
398 : {
399 53600834 : }
400 :
401 : TheWarehouse * _w = nullptr;
402 : std::vector<std::unique_ptr<Attribute>> _attribs;
403 :
404 : KeyTuple _key_tup;
405 : AttribTuple _attrib_tup;
406 : std::map<std::pair<bool, KeyTuple>, std::size_t> _cache;
407 : std::mutex _cache_mutex;
408 : };
409 :
410 : using Query = QueryCache<>;
411 :
412 : TheWarehouse();
413 : ~TheWarehouse();
414 :
415 : /// registers a new "tracked" attribute of type T for the warehouse. args are all arguments
416 : /// necessary to create an instance of the T class excluding the warehouse reference/pointer
417 : /// which is assumed to be first and automatically inserted. An instance of every registered
418 : /// attribute will be created for and initialized to each object added to the warehouse allowing
419 : /// queries to be executed over specific values the attribute may take on. Attributes must be
420 : /// registered *before* objects are added to the warehouse. A unique ID associated with the
421 : /// registered attribute is returned - which is generally not needed used by users.
422 : ///
423 : /// As an example, to register a class with the constructor "YourAttribute(TheWarehouse& w, int
424 : /// foo)", you would call "registerAttribute<YourAttribute>("your_attrib_name", constructor_arg1,
425 : /// ...)". Custom attribute classes are required to pass an attribute name (i.e.
426 : /// "your_attrib_name") to the Attribute base class. The dummy args are forwarded to the attrib
427 : /// class' constructor. The name passed here into registerAttribute
428 : /// must be the same string as the name passed to the Attribute base class's constructor.
429 : template <typename T, typename... Args>
430 1211286 : unsigned int registerAttribute(const std::string & name, Args... dummy_args)
431 : {
432 1211286 : auto it = _attrib_ids.find(name);
433 1211286 : if (it != _attrib_ids.end())
434 0 : return it->second;
435 :
436 1211286 : _attrib_ids[name] = _attrib_list.size();
437 1345870 : _attrib_list.push_back(std::unique_ptr<Attribute>(new T(*this, dummy_args...)));
438 1211286 : return _attrib_list.size() - 1;
439 : }
440 :
441 : /// Returns a unique ID associated with the given attribute name - i.e. an attribute and name
442 : /// that were previously registered via calls to registerAttribute. Users should generally *not*
443 : /// need to use this function.
444 210580297 : inline unsigned int attribID(const std::string & name)
445 : {
446 210580297 : auto it = _attrib_ids.find(name);
447 210580297 : if (it != _attrib_ids.end())
448 421160594 : return it->second;
449 0 : mooseError("no ID exists for unregistered attribute '", name, "'");
450 : }
451 :
452 : /// add adds a new object to the warehouse and stores attributes/metadata about it for running
453 : /// queries/filtering. The warehouse will maintain a pointer to the object indefinitely.
454 : void add(std::shared_ptr<MooseObject> obj);
455 :
456 : /// update updates the metadata/attribute-info stored for the given object obj that must already
457 : /// exists in the warehouse. Call this if an object's state has changed in such a way that its
458 : /// warehouse attributes have become stale/incorrect.
459 : void update(MooseObject * obj);
460 : /// update updates the metadata/attribute-info stored for the given object obj that must already
461 : /// exists in the warehouse. Call this if an object's state has changed in such a way that its
462 : /// warehouse attributes have become stale/incorrect.
463 : /// Any attribute specified in extra overwrites/trumps one read from the object's current state.
464 : void update(MooseObject * obj, const Attribute & extra);
465 : /// query creates and returns an initialized a query object for querying objects from the
466 : /// warehouse.
467 39212620 : Query query() { return Query(*this); }
468 : /// count returns the number of objects that match the provided query conditions. This requires
469 : /// executing a full query operation (i.e. as if calling queryInto). A Query object should
470 : /// generally be used via the query() member function instead.
471 : std::size_t count(const std::vector<std::unique_ptr<Attribute>> & conds);
472 : /// queryInto takes the given conditions (i.e. Attributes holding the values to filter/match
473 : /// over) and filters all objects in the warehouse that match all conditions (i.e. "and"ing the
474 : /// conditions together) and stores them in the results vector. All result objects must be
475 : /// castable to the templated type T. This function filters out disabled objects on the fly -
476 : /// only returning enabled ones.
477 : template <typename T>
478 : std::vector<T *> & queryInto(const std::vector<std::unique_ptr<Attribute>> & conds,
479 : std::vector<T *> & results)
480 : {
481 : return queryInto(queryID(conds), results);
482 : }
483 :
484 : std::size_t queryID(const std::vector<std::unique_ptr<Attribute>> & conds);
485 :
486 : template <typename T>
487 116558033 : std::vector<T *> & queryInto(int query_id, std::vector<T *> & results, bool show_all = false)
488 : {
489 116558033 : std::lock_guard<std::mutex> lock(_obj_cache_mutex);
490 116558033 : auto & objs = query(query_id);
491 116558033 : results.clear();
492 116558033 : results.reserve(objs.size());
493 186780927 : for (auto & obj : objs)
494 : {
495 : mooseAssert(obj, "Null object");
496 : T * cast_obj;
497 : // We've been using dynamic_cast plus an assert that it
498 : // succeeds, but if we know the correct type T then often we can
499 : // use a much-cheaper static_cast. libMesh::cast_ptr will still
500 : // use a dynamic_cast + assertion in devel/dbg modes.
501 : if constexpr (std::is_convertible_v<MooseObject *, std::remove_cv_t<T> *> ||
502 : std::is_convertible_v<std::remove_cv_t<T> *, MooseObject *>)
503 : {
504 69839851 : cast_obj = cast_ptr<T *>(obj);
505 : }
506 : // Side-casts or virtual inheritence are always expensive.
507 : // Someone should figure out how to get rid of this code path.
508 : else
509 : {
510 383043 : cast_obj = dynamic_cast<T *>(obj);
511 : mooseAssert(cast_obj,
512 : "Queried object " + obj->typeAndName() + " has incompatible c++ type with " +
513 : MooseUtils::prettyCppType<T>());
514 : }
515 :
516 : mooseAssert(std::find(results.begin(), results.end(), cast_obj) == results.end(),
517 : "Duplicate object");
518 70222894 : if (show_all || obj->enabled())
519 70206131 : results.push_back(cast_obj);
520 : }
521 116558033 : return results;
522 116558033 : }
523 :
524 : private:
525 : /// prepares a query and returns an associated query_id (i.e. for use with the query function).
526 : int prepare(std::vector<std::unique_ptr<Attribute>> conds);
527 :
528 : /// callers of this function must lock _obj_cache_mutex as long as a reference to the returned
529 : /// vector is being used.
530 : const std::vector<MooseObject *> & query(int query_id);
531 :
532 : void readAttribs(const MooseObject * obj, std::vector<std::unique_ptr<Attribute>> & attribs);
533 :
534 : std::unique_ptr<WarehouseStorage> _store;
535 : std::vector<std::shared_ptr<MooseObject>> _objects;
536 : std::unordered_map<MooseObject *, std::size_t> _obj_ids;
537 :
538 : // Results from queries are cached here. The outer vector index is the query id as stored by the
539 : // _query_cache data structure. A list objects that match each query id are stored.
540 : std::vector<std::vector<MooseObject *>> _obj_cache;
541 : // This stores a query id for every query keyed by the query conditions/attributes.
542 : // User-initiated queries check this map to see if a queries results have already been cached.
543 : // The query id is an index into the _obj_cache data structure.
544 : std::unordered_map<std::vector<std::unique_ptr<Attribute>>, int> _query_cache;
545 :
546 : std::unordered_map<std::string, unsigned int> _attrib_ids;
547 : std::vector<std::unique_ptr<Attribute>> _attrib_list;
548 :
549 : std::mutex _obj_mutex;
550 : std::mutex _query_cache_mutex;
551 : std::mutex _obj_cache_mutex;
552 : };
|