Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-10-25 09:28:05

0001 #ifndef PODIO_DETAIL_LINKCOLLECTIONIMPL_H
0002 #define PODIO_DETAIL_LINKCOLLECTIONIMPL_H
0003 
0004 #include "podio/ObjectID.h"
0005 #include "podio/detail/Link.h"
0006 #include "podio/detail/LinkCollectionData.h"
0007 #include "podio/detail/LinkCollectionIterator.h"
0008 #include "podio/detail/LinkFwd.h"
0009 #include "podio/detail/LinkObj.h"
0010 
0011 #include "podio/CollectionBase.h"
0012 #include "podio/CollectionBufferFactory.h"
0013 #include "podio/CollectionBuffers.h"
0014 #include "podio/DatamodelRegistry.h"
0015 #include "podio/ICollectionProvider.h"
0016 #include "podio/SchemaEvolution.h"
0017 #include "podio/detail/Pythonizations.h"
0018 #include "podio/utilities/MaybeSharedPtr.h"
0019 #include "podio/utilities/StaticConcatenate.h"
0020 #include "podio/utilities/TypeHelpers.h"
0021 
0022 #include <algorithm>
0023 #include <iterator>
0024 #include <ranges>
0025 #include <string_view>
0026 
0027 #ifdef PODIO_JSON_OUTPUT
0028   #include "nlohmann/json.hpp"
0029 #endif
0030 
0031 #include <iomanip>
0032 #include <memory>
0033 #include <mutex>
0034 #include <ostream>
0035 #include <string>
0036 #include <type_traits>
0037 #include <utility>
0038 
0039 namespace podio {
0040 
0041 template <typename FromT, typename ToT>
0042 class LinkCollection : public podio::CollectionBase {
0043   static_assert(std::is_same_v<FromT, detail::GetDefaultHandleType<FromT>>,
0044                 "Links need to be instantiated with the default types!");
0045   static_assert(std::is_same_v<ToT, detail::GetDefaultHandleType<ToT>>,
0046                 "Links need to be instantiated with the default types!");
0047 
0048   // convenience typedefs
0049   using CollectionDataT = podio::LinkCollectionData<FromT, ToT>;
0050 
0051 public:
0052   using from_type = FromT;
0053   using to_type = ToT;
0054   using value_type = Link<FromT, ToT>;
0055   using mutable_type = MutableLink<FromT, ToT>;
0056   using const_iterator = LinkCollectionIterator<FromT, ToT>;
0057   using iterator = LinkMutableCollectionIterator<FromT, ToT>;
0058   using difference_type = ptrdiff_t;
0059   using size_type = size_t;
0060   using const_reverse_iterator = std::reverse_iterator<const_iterator>;
0061   using reverse_iterator = std::reverse_iterator<iterator>;
0062 
0063   LinkCollection() = default;
0064 
0065   LinkCollection(CollectionDataT&& data, bool isSubsetColl) :
0066       m_isSubsetColl(isSubsetColl), m_collectionID(0), m_storage(std::move(data)) {
0067   }
0068 
0069   // Move-only type
0070   LinkCollection(const LinkCollection&) = delete;
0071   LinkCollection& operator=(const LinkCollection&) = delete;
0072   LinkCollection(LinkCollection&&) = default;
0073   LinkCollection& operator=(LinkCollection&&) = default;
0074 #if defined(__cpp_lib_containers_ranges)
0075   /// Constructor from range in order to enable the use of std::ranges::to see
0076   /// @ref from.
0077   template <detail::RangeConvertibleTo<value_type> R>
0078   LinkCollection(std::from_range_t, R&& range) : LinkCollection() {
0079     if constexpr (detail::RangeOf<R, value_type>) {
0080       setSubsetCollection();
0081     }
0082 
0083     for (auto&& elem : range) {
0084       push_back(std::forward<decltype(elem)>(elem));
0085     }
0086   }
0087 #endif
0088 
0089   ~LinkCollection() override {
0090     // Need to tell the storage how to clean up
0091     m_storage.clear(m_isSubsetColl);
0092   }
0093 
0094   /// Construct a LinkCollection from a range of mutable or immutable links
0095   ///
0096   /// If the range is over mutable Link handles, the collection will (try) to
0097   /// take ownership of the handles. If the range is over immutable Link handles
0098   /// the collection will be a subset collection and not take ownership of the
0099   /// handles.
0100   ///
0101   /// @tparam R A range type (at least std::input_range) that contains for which
0102   ///           the range_value_t is convertible to an immutable link.
0103   /// @param  range The range from which the collection should be constructed
0104   ///
0105   /// @returns  A LinkCollection populated with the values from the range
0106   ///
0107   /// @throws std::invalid_argument if a range of immutable handles that are not
0108   ///           yet owned by a collection
0109   template <detail::RangeConvertibleTo<value_type> R>
0110   static LinkCollection from(R&& range) {
0111     LinkCollection coll;
0112     if constexpr (detail::RangeOf<R, value_type>) {
0113       coll.setSubsetCollection();
0114     }
0115 
0116     // NOTE: We cannot use ranges::copy here, because that will always resolve
0117     // to only one of the push_back overloads, specifically the one taking
0118     // value_type. See, e.g.: https://godbolt.org/z/seqdh79rr
0119     // Additionally, we want no reference in the loop variable to make sure we
0120     // get the correct handle type depending on the range we pass in
0121     for (auto&& elem : range) {
0122       coll.push_back(std::forward<decltype(elem)>(elem));
0123     }
0124 
0125     return coll;
0126   }
0127 
0128   /// Append a new link to the collection and return this object
0129   mutable_type create() {
0130     if (m_isSubsetColl) {
0131       throw std::logic_error("Cannot create new elements on a subset collection");
0132     }
0133 
0134     auto obj = m_storage.entries.emplace_back(new LinkObj<FromT, ToT>());
0135     obj->id = {int(m_storage.entries.size() - 1), m_collectionID};
0136     return mutable_type(podio::utils::MaybeSharedPtr(obj));
0137   }
0138 
0139   /// Append a new link to the collection, linking the two passed objects, and
0140   /// return this object
0141   ///
0142   /// If FromT and ToT are different types, the two objects may be passed in any
0143   /// order and the direction of the link is deduced at compile time. If both
0144   /// orders are possible (e.g. because interface types are involved) the
0145   /// canonical order, i.e. *from first, to second*, takes precedence. If FromT
0146   /// and ToT are the same type, the canonical order is the only possible one.
0147   ///
0148   /// @tparam FromU the type of the first object (**inferred!**)
0149   /// @tparam ToU   the type of the second object (**inferred!**)
0150   ///
0151   /// @param from   the object to link (the *from* object in canonical order)
0152   /// @param to     the object to link (the *to* object in canonical order)
0153   /// @param weight the weight of the link (defaults to 1)
0154   ///
0155   /// @returns the newly created (mutable) link
0156   ///
0157   /// @throws std::logic_error if this is a subset collection
0158   template <typename FromU, typename ToU>
0159     requires detail::LinkInitializableFrom<FromT, ToT, FromU, ToU> ||
0160       detail::LinkInitializableFrom<ToT, FromT, FromU, ToU>
0161   mutable_type create(FromU&& from, ToU&& to, float weight = 1.0f) {
0162     auto link = create();
0163     link.setWeight(weight);
0164     // The canonical order takes precedence if both orders are possible
0165     if constexpr (detail::LinkInitializableFrom<FromT, ToT, FromU, ToU>) {
0166       link.setFrom(std::forward<FromU>(from));
0167       link.setTo(std::forward<ToU>(to));
0168     } else {
0169       link.setTo(std::forward<FromU>(from));
0170       link.setFrom(std::forward<ToU>(to));
0171     }
0172     return link;
0173   }
0174 
0175   /// Returns the immutable object of given index
0176   value_type operator[](std::size_t index) const {
0177     return value_type(m_storage.entries[index]);
0178   }
0179   /// Returns the mutable object of given index
0180   mutable_type operator[](std::size_t index) {
0181     return mutable_type(podio::utils::MaybeSharedPtr(m_storage.entries[index]));
0182   }
0183   /// Returns the immutable object of given index
0184   value_type at(std::size_t index) const {
0185     return value_type(m_storage.entries.at(index));
0186   }
0187   /// Returns the mutable object of given index
0188   mutable_type at(std::size_t index) {
0189     return mutable_type(podio::utils::MaybeSharedPtr(m_storage.entries.at(index)));
0190   }
0191 
0192   void push_back(mutable_type object) {
0193     // We have to do different things here depending on whether this is a
0194     // subset collection or not. A normal collection cannot collect objects
0195     // that are already part of another collection, while a subset collection
0196     // can only collect such objects
0197     if (!m_isSubsetColl) {
0198       auto obj = object.m_obj;
0199       if (obj->id.index == podio::ObjectID::untracked) {
0200         obj->id = {static_cast<int>(m_storage.entries.size()), m_collectionID};
0201         m_storage.entries.push_back(obj.release());
0202       } else {
0203         throw std::invalid_argument("Object already in a collection. Cannot add it to a second collection");
0204       }
0205 
0206     } else {
0207       push_back(value_type(object));
0208     }
0209   }
0210 
0211   void push_back(value_type object) {
0212     if (!m_isSubsetColl) {
0213       throw std::invalid_argument("Can only add immutable objects to subset collections");
0214     }
0215     auto obj = object.m_obj;
0216     if (obj->id.index < 0) {
0217       throw std::invalid_argument(
0218           "Object needs to be tracked by another collection in order for it to be storable in a subset collection");
0219     }
0220     m_storage.entries.push_back(obj.release());
0221   }
0222 
0223   /// Number of elements in the collection
0224   size_t size() const override {
0225     return m_storage.entries.size();
0226   }
0227 
0228   /// maximal number of elements in the collection
0229   std::size_t max_size() const override {
0230     const auto maxStorage = m_storage.entries.max_size();
0231     if (!m_isSubsetColl) {
0232       // non-subset collections shouldn't have more elements than the maximum index of ObjectID
0233       const auto maxIndex = std::numeric_limits<decltype(ObjectID::index)>::max();
0234       return std::min<size_t>(maxIndex, maxStorage);
0235     }
0236     return maxStorage;
0237   }
0238 
0239   /// Is the collection empty
0240   bool empty() const override {
0241     return m_storage.entries.empty();
0242   }
0243 
0244   void clear() override {
0245     m_storage.clear(m_isSubsetColl);
0246     m_isPrepared = false;
0247   }
0248 
0249   void print(std::ostream& os = std::cout, bool flush = true) const override {
0250     os << *this;
0251     if (flush) {
0252       os.flush();
0253     }
0254   }
0255 
0256   // support for the iterator protocol
0257   const_iterator begin() const {
0258     return const_iterator(0, &m_storage.entries);
0259   }
0260   const_iterator cbegin() const {
0261     return begin();
0262   }
0263   const_iterator end() const {
0264     return const_iterator(m_storage.entries.size(), &m_storage.entries);
0265   }
0266   const_iterator cend() const {
0267     return end();
0268   }
0269   iterator begin() {
0270     return iterator(0, &m_storage.entries);
0271   }
0272   iterator end() {
0273     return iterator(m_storage.entries.size(), &m_storage.entries);
0274   }
0275   // reverse iterators
0276   reverse_iterator rbegin() {
0277     return reverse_iterator(end());
0278   }
0279   const_reverse_iterator rbegin() const {
0280     return const_reverse_iterator(end());
0281   }
0282   const_reverse_iterator crbegin() const {
0283     return rbegin();
0284   }
0285   reverse_iterator rend() {
0286     return reverse_iterator(begin());
0287   }
0288   const_reverse_iterator rend() const {
0289     return const_reverse_iterator(begin());
0290   }
0291   const_reverse_iterator crend() const {
0292     return rend();
0293   }
0294 
0295   /// check if the collection has a valid ID
0296   bool hasID() const override {
0297     return getID() != static_cast<uint32_t>(podio::ObjectID::untracked) &&
0298         getID() != static_cast<uint32_t>(podio::ObjectID::invalid);
0299   }
0300 
0301   [[deprecated("isValid will be removed, use hasID() if you want to check if it has an ID, otherwise assume the "
0302                "collection is valid")]]
0303   bool isValid() const override {
0304     return hasID();
0305   }
0306 
0307   podio::CollectionWriteBuffers getBuffers() override {
0308     return m_storage.getCollectionBuffers(m_isSubsetColl);
0309   }
0310 
0311   constexpr static std::string_view typeName =
0312       podio::utils::static_concatenate_v<podio::detail::link_coll_name_prefix, FromT::typeName,
0313                                          podio::detail::link_name_infix, ToT::typeName,
0314                                          podio::detail::link_name_suffix>;
0315 
0316   constexpr static std::string_view valueTypeName = value_type::typeName;
0317   constexpr static std::string_view dataTypeName = "podio::LinkData";
0318 
0319   const std::string_view getTypeName() const override {
0320     return typeName;
0321   }
0322 
0323   const std::string_view getValueTypeName() const override {
0324     return valueTypeName;
0325   }
0326 
0327   const std::string_view getDataTypeName() const override {
0328     return dataTypeName;
0329   }
0330 
0331   /// Cppyy protocol to setup the pythonizations for this class. Not to be called directly.
0332   static void __cppyy_pythonize__(PyObject* klass, const std::string& name) {
0333     podio::detail::pythonizations::pythonize_subscript(klass, name);
0334   }
0335 
0336   bool isSubsetCollection() const override {
0337     return m_isSubsetColl;
0338   }
0339 
0340   void setSubsetCollection(bool setSubset = true) override {
0341     if (m_isSubsetColl != setSubset && !m_storage.entries.empty()) {
0342       throw std::logic_error("Cannot change the character of a collection that already contains elements");
0343     }
0344 
0345     if (setSubset) {
0346       m_storage.makeSubsetCollection();
0347     }
0348     m_isSubsetColl = setSubset;
0349   }
0350 
0351   void setID(uint32_t id) override {
0352     m_collectionID = id;
0353     if (!m_isSubsetColl) {
0354       std::ranges::for_each(m_storage.entries, [id](auto* obj) { obj->id = {obj->id.index, id}; });
0355     }
0356   }
0357 
0358   uint32_t getID() const override {
0359     return m_collectionID;
0360   }
0361 
0362   void prepareForWrite() const override {
0363     std::lock_guard lock{*m_storageMtx};
0364     // by the time we acquire the lock another thread might have already
0365     // succeeded, so we need to check again now
0366     if (m_isPrepared) {
0367       return;
0368     }
0369     m_storage.prepareForWrite(m_isSubsetColl);
0370     m_isPrepared = true;
0371   }
0372 
0373   void prepareAfterRead() override {
0374     // No need to go through this again if we have already done it
0375     if (m_isPrepared) {
0376       return;
0377     }
0378 
0379     if (!m_isSubsetColl) {
0380       // Subset collections do not store any data that would require post-processing
0381       m_storage.prepareAfterRead(m_collectionID);
0382     }
0383     // Preparing a collection doesn't affect the underlying I/O buffers, so this
0384     // collection is still prepared
0385     m_isPrepared = true;
0386   }
0387 
0388   bool setReferences(const ICollectionProvider* collectionProvider) override {
0389     return m_storage.setReferences(collectionProvider, m_isSubsetColl);
0390   }
0391 
0392   static constexpr SchemaVersionT schemaVersion = 1;
0393 
0394   SchemaVersionT getSchemaVersion() const override {
0395     return schemaVersion;
0396   }
0397 
0398   size_t getDatamodelRegistryIndex() const override {
0399     return podio::DatamodelRegistry::NoDefinitionNecessary;
0400   }
0401 
0402 private:
0403   // For setReferences, we need to give our own CollectionData access to our
0404   // private entries. Otherwise we would need to expose a public member function
0405   // that gives access to the Obj* which is definitely not what we want
0406   friend CollectionDataT;
0407 
0408   mutable bool m_isPrepared{false};
0409   bool m_isSubsetColl{false};
0410   uint32_t m_collectionID{0};
0411   mutable std::unique_ptr<std::mutex> m_storageMtx{std::make_unique<std::mutex>()};
0412   mutable CollectionDataT m_storage{};
0413 };
0414 
0415 template <typename FromT, typename ToT>
0416 std::ostream& operator<<(std::ostream& o, const LinkCollection<FromT, ToT>& v) {
0417   const auto old_flags = o.flags();
0418   o << "          id:      weight:" << '\n';
0419   for (const auto&& el : v) {
0420     o << std::scientific << std::showpos << std::setw(12) << el.id() << " " << std::setw(12) << " " << el.getWeight()
0421       << '\n';
0422 
0423     o << "     from : ";
0424     o << el.getFrom().id() << std::endl;
0425     o << "       to : ";
0426     o << el.getTo().id() << std::endl;
0427   }
0428 
0429   o.flags(old_flags);
0430   return o;
0431 }
0432 
0433 namespace detail {
0434   template <typename FromT, typename ToT>
0435   podio::CollectionReadBuffers createLinkBuffers(bool subsetColl) {
0436     auto readBuffers = podio::CollectionReadBuffers{};
0437     readBuffers.type = podio::LinkCollection<FromT, ToT>::typeName;
0438     readBuffers.schemaVersion = podio::LinkCollection<FromT, ToT>::schemaVersion;
0439     readBuffers.data = subsetColl ? nullptr : new LinkDataContainer();
0440 
0441     // Either it is a subset collection or we have two relations
0442     const auto nRefs = subsetColl ? 1 : 2;
0443     readBuffers.references = new podio::CollRefCollection(nRefs);
0444     for (auto& ref : *readBuffers.references) {
0445       // Make sure to place usable buffer pointers here
0446       ref = std::make_unique<std::vector<podio::ObjectID>>();
0447     }
0448 
0449     readBuffers.createCollection = [](podio::CollectionReadBuffers&& buffers, bool isSubsetColl) {
0450       LinkCollectionData<FromT, ToT> data(std::move(buffers), isSubsetColl);
0451       return std::make_unique<LinkCollection<FromT, ToT>>(std::move(data), isSubsetColl);
0452     };
0453 
0454     readBuffers.deleteBuffers = [](podio::CollectionReadBuffers& buffers) {
0455       if (buffers.data) {
0456         // If we have data then we are not a subset collection and we have
0457         // to clean up all type erased buffers by casting them back to
0458         // something that we can delete
0459         delete static_cast<LinkDataContainer*>(buffers.data);
0460         buffers.data = nullptr;
0461       }
0462       delete buffers.references;
0463       buffers.references = nullptr;
0464       delete buffers.vectorMembers;
0465       buffers.vectorMembers = nullptr;
0466     };
0467 
0468     return readBuffers;
0469   }
0470 
0471   template <typename FromT, typename ToT>
0472   bool registerLinkCollection(const std::string_view linkTypeName) {
0473     const static auto reg = [&linkTypeName]() {
0474       const auto schemaVersion = LinkCollection<FromT, ToT>::schemaVersion;
0475 
0476       auto& factory = CollectionBufferFactory::mutInstance();
0477       factory.registerCreationFunc(std::string(linkTypeName), schemaVersion, createLinkBuffers<FromT, ToT>);
0478 
0479       // For now passing the same schema version for from and current version
0480       // simply to make SchemaEvolution aware of LinkCollections
0481       podio::SchemaEvolution::mutInstance().registerEvolutionFunc(std::string(linkTypeName), schemaVersion,
0482                                                                   schemaVersion, SchemaEvolution::noOpSchemaEvolution,
0483                                                                   SchemaEvolution::Priority::AutoGenerated);
0484 
0485       return true;
0486     }();
0487     return reg;
0488   }
0489 } // namespace detail
0490 
0491 #if defined(PODIO_JSON_OUTPUT) && !defined(__CLING__)
0492 template <typename FromT, typename ToT>
0493 void to_json(nlohmann::json& j, const podio::LinkCollection<FromT, ToT>& collection) {
0494   j = nlohmann::json::array();
0495   for (auto&& elem : collection) {
0496     j.emplace_back(elem);
0497   }
0498 }
0499 #endif
0500 
0501 } // namespace podio
0502 
0503 #endif // PODIO_DETAIL_LINKCOLLECTIONIMPL_H