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