Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework the I/O handling for relations #673

Merged
merged 8 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions include/podio/detail/RelationIOHelpers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#ifndef PODIO_DETAIL_RELATIONIOHELPERS_H
#define PODIO_DETAIL_RELATIONIOHELPERS_H

#include "podio/utilities/TypeHelpers.h"
#include <podio/CollectionBase.h>

#include <tuple>
#include <vector>

namespace podio::detail {

/// Function template for handling interface types in OneToMultiRelations
///
/// Effectively this function checks whether the passed collection can be
/// dynamically cast to the collection type of the concrete type and if that is
/// true uses it to construct an interface type and add it to relElements. The
/// function on its own doesn't do anything too meaningful, it is meant to be
/// used in a call to std::apply that goes over all the interfaced types of an
/// interface type.
///
/// @tparam T The concrete type inside the interface that should be checked.
/// This effectively is mainly used for tag-dispatch and overload
/// selection in this context
/// @tparam InterfaceType The interface type (that can be used to interface T)
///
/// @param relElements The vector to which the interface objects should be added
/// @param coll The collection that holds the actual element
/// @param id The ObjectID of the element that we are currently looking for
template <typename T, typename InterfaceType>
void tryAddTo(T, std::vector<InterfaceType>& relElements, const podio::CollectionBase* coll, const podio::ObjectID id) {
if (auto typedColl = dynamic_cast<const typename T::collection_type*>(coll)) {
const T tmp = (*typedColl)[id.index];
relElements.emplace_back(tmp);
}
}

/// Helper function for handling interface type relations in OneToManyRelations
///
/// This function tries all types that are interfaced by the InterfaceType and
/// adds the one that matches to the relations. The main work happens in
/// tryAddTo, this simply wraps everything in a std::apply over all
/// interfaced_types.
///
/// @tparam InterfaceType The interface type of the Relation
///
/// @param relElements The vector to which the interface objects should be added
/// @param coll The collection that holds the actual element
/// @param id The ObjectID of the element that we are currently looking for
template <typename InterfaceType>
void addInterfaceToMultiRelation(std::vector<InterfaceType>& relElements, const podio::CollectionBase* coll,
const podio::ObjectID id) {
std::apply([&](auto... t) { (tryAddTo(t, relElements, coll, id), ...); }, typename InterfaceType::interfaced_types{});
}

/// Helper function for adding an object to the OneToManyRelations container
/// when reading back collections
///
/// This function does the necessary type casting of the passed collection to
/// retrieve the desired object and also takes care of adding the object to the
/// container that holds them for later usage. It handles relations to regular
/// types as well as interface types.
///
/// This functionality has been lifted from the jinja2 templates, where we now
/// only call it, because we need a template deduction context for making if
/// constexpr work as expected, such that we can dispatch to different
/// implementatoins depending on whether the relation is to an interface type or
/// to a regular type.
///
/// @note It is expected that the following pre-conditions are met:
/// - The passed collection is valid (i.e. not a nullptr)
/// - the collectionID of the passed collection is the same as the one in
/// the passed ObjectID
/// - The collection can by casted to the relation type or any of the
/// interfaced types of the relation
///
/// @tparam RelType The type of the OneToManyRelation
///
/// @param relElements The container that holds the objects for the relation and
/// which will be used for adding an element from the passed
/// collection
/// @param coll The collection from which the object will be obtained after the
/// necessary type casting
/// @param id The ObjectID of the object that should be retrieved and added.
template <typename RelType>
void addMultiRelation(std::vector<RelType>& relElements, const podio::CollectionBase* coll, const podio::ObjectID id) {
if constexpr (podio::detail::isInterfaceType<RelType>) {
addInterfaceToMultiRelation(relElements, coll, id);
} else {
const auto* typeColl = static_cast<const typename RelType::collection_type*>(coll);
relElements.emplace_back((*typeColl)[id.index]);
}
}

/// Function template for handling interface types in OneToOneRelations
///
/// Effectively this function checks whether the passed collection can be
/// dynamically cast to the collection type of the concrete type and if that is
/// true uses it to assign to the passed interface object The function on its
/// own doesn't do anything too meaningful, it is meant to be used in a call to
/// std::apply that goes over all the interfaced types of an interface type.
///
/// @tparam T The concrete type inside the interface that should be checked.
/// This effectively is mainly used for tag-dispatch and overload
/// selection in this context
/// @tparam InterfaceType The interface type (that can be used to interface T)
///
/// @param relation The object to which the interface object should be assigned
/// to
/// @param coll The collection that holds the actual element
/// @param id The ObjectID of the element that we are currently looking for
template <typename T, typename InterfaceType>
void tryAssignTo(T, InterfaceType*& relation, const podio::CollectionBase* coll, const podio::ObjectID id) {
if (const auto* typeColl = dynamic_cast<const typename T::collection_type*>(coll)) {
relation = new InterfaceType((*typeColl)[id.index]);
}
}

/// Helper function for handling interface type relations in OneToOneRelations
///
/// This function tries all types that are interfaced by the InterfaceType and
/// that assigns the one thta matches to the relation. The main work happens in
/// tryAssignTo, this simply wraps everything in a std::apply over all
/// interfaced_types.
///
/// @tparam InterfaceType The interface type of the Relation
///
/// @param relation The object to which the interface object should be assigned
/// to
/// @param coll The collection that holds the actual element
/// @param id The ObjectID of the element that we are currently looking for
template <typename InterfaceType>
void addInterfaceToSingleRelation(InterfaceType*& relation, const podio::CollectionBase* coll,
const podio::ObjectID id) {
std::apply([&](auto... t) { (tryAssignTo(t, relation, coll, id), ...); }, typename InterfaceType::interfaced_types{});
}

/// Helper function for assigning the related object in a OneToOneRelation
///
/// This function does the necessary type casting of the passed collection to
/// retrieve the desired object and also takes care of assigning the object to
/// the passed pointer that hold it for later usage. It handles relations
/// to regular types as well as interface types.
///
/// This functionality has been lifted from the jinja2 templates, where we now
/// only call it, because we need a template deduction context for making if
/// constexpr work as expected, such that we can dispatch to different
/// implementatoins depending on whether the relation is to an interface type or
/// to a regular type.
///
/// @note It is expected that the following pre-conditions are met:
/// - The passed collection is valid (i.e. not a nullptr)
/// - the collectionID of the passed collection is the same as the one in
/// the passed ObjectID
/// - The collection can by casted to the relation type or any of the
/// interfaced types of the relation
///
/// @tparam RelType The type of the OneToManyRelation
///
/// @param relation The pointer to which we should assign the related object
/// that is retrieved from the passed collection
/// @param coll The collection from which the object will be obtained after the
/// necessary type casting
/// @param id The ObjectID of the object that should be retrieved and added.
template <typename RelType>
void addSingleRelation(RelType*& relation, const podio::CollectionBase* coll, const podio::ObjectID id) {
if constexpr (podio::detail::isInterfaceType<RelType>) {
addInterfaceToSingleRelation(relation, coll, id);
} else {
const auto* typeColl = static_cast<const typename RelType::collection_type*>(coll);
relation = new RelType((*typeColl)[id.index]);
}
}

} // namespace podio::detail

#endif // PODIO_DETAIL_RELATIONIOHELPERS_H
10 changes: 10 additions & 0 deletions include/podio/utilities/TypeHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,16 @@ namespace detail {
template <typename Tuple>
using TupleOfMutableTypes = typename ToTupleOfTemplateHelper<GetMutableHandleType, Tuple>::type;

/// Detector for checking for the existence of an interfaced_type type member
template <typename T>
using hasInterface_t = typename T::interfaced_types;

/// Variable template for checking whether the passed type T is an interface
/// type.
///
/// @note: This simply checks whether T has an interfaced_types type member.
template <typename T>
constexpr static bool isInterfaceType = det::is_detected_v<hasInterface_t, T>;
} // namespace detail

// forward declaration to be able to use it below
Expand Down
2 changes: 2 additions & 0 deletions python/templates/CollectionData.cc.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
{{ include }}
{% endfor %}

#include <podio/detail/RelationIOHelpers.h>

{{ utils.namespace_open(class.namespace) }}
{% with class_type = class.bare_type + 'CollectionData' %}

Expand Down
11 changes: 7 additions & 4 deletions python/templates/Interface.h.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@

{{ common_macros.class_description(class.bare_type, Description, Author) }}
class {{ class.bare_type }} {
public:
/// type alias containing all the types this interface should work for in a
/// tuple
using interfaced_types = std::tuple<{{ Types | join(", ")}}>;

/// type alias containing all the types this interface should work for.
using InterfacedTypes = std::tuple<{{ Types | join(", ")}}>;
private:
/// type alias containing all the mutable types that can be used to initialize
/// this interface
using InterfacedMutableTypes = podio::detail::TupleOfMutableTypes<InterfacedTypes>;
using InterfacedMutableTypes = podio::detail::TupleOfMutableTypes<interfaced_types>;

/// template variable for determining whether type T is a valid interface type
template<typename T>
constexpr static bool isInterfacedType = podio::detail::isInTuple<T, InterfacedTypes>;
constexpr static bool isInterfacedType = podio::detail::isInTuple<T, interfaced_types>;

/// template variable for determining whether type T can be used to initialize
/// this interface
Expand Down
27 changes: 2 additions & 25 deletions python/templates/macros/collections.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,7 @@ std::vector<{{ member.full_type }}> {{ class.bare_type }}Collection::{{ member.n
m_rel_{{ relation.name }}->emplace_back({{ relation.full_type }}::makeEmpty());
continue;
}
{% if relation.interface_types %}
// We need the concrete collection type to assign it to an InferenceWrapper
{% set else = joiner("else") %}
{% for int_type in relation.interface_types %}
{{ else() }} if (auto {{ int_type.bare_type }}Coll = dynamic_cast<{{ int_type.full_type }}Collection*>(coll)) {
const auto tmp = (*{{ int_type.bare_type }}Coll)[id.index];
m_rel_{{ relation.name }}->emplace_back(tmp);
}
{% endfor %}
{% else %}
{{ relation.full_type }}Collection* tmp_coll = static_cast<{{ relation.full_type }}Collection*>(coll);
const auto tmp = (*tmp_coll)[id.index];
m_rel_{{ relation.name }}->emplace_back(tmp);
{% endif %}
podio::detail::addMultiRelation(*m_rel_{{ relation.name }}, coll, id);
} else {
m_rel_{{ relation.name }}->emplace_back({{ relation.full_type }}::makeEmpty());
}
Expand All @@ -102,17 +89,7 @@ std::vector<{{ member.full_type }}> {{ class.bare_type }}Collection::{{ member.n
entries[i]->m_{{ relation.name }} = nullptr;
continue;
}
{% if relation.interface_types %}
{% set else = joiner("else") %}
{% for int_type in relation.interface_types %}
{{ else() }} if (auto {{ int_type.bare_type }}Coll = dynamic_cast<{{ int_type.full_type }}Collection*>(coll)) {
entries[i]->m_{{ relation.name }} = new {{ relation.full_type }}((*{{ int_type.bare_type }}Coll)[id.index]);
}
{% endfor %}
{% else %}
{{ relation.full_type }}Collection* tmp_coll = static_cast<{{ relation.full_type }}Collection*>(coll);
entries[i]->m_{{ relation.name }} = new {{ relation.full_type }}((*tmp_coll)[id.index]);
{% endif %}
podio::detail::addSingleRelation(entries[i]->m_{{ relation.name }}, coll, id);
} else {
entries[i]->m_{{ relation.name }} = nullptr;
}
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ install(FILES ${headers_necessary}
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/podio
)
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/podio/utilities
${PROJECT_SOURCE_DIR}/include/podio/detail
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/podio
)

Expand Down
9 changes: 7 additions & 2 deletions tests/unittests/interface_types.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#include "catch2/catch_test_macros.hpp"

#include "podio/ObjectID.h"

#include "datamodel/ExampleHitCollection.h"
#include "datamodel/MutableExampleCluster.h"
#include "datamodel/TypeWithEnergy.h"

#include "podio/ObjectID.h"
#include "podio/utilities/TypeHelpers.h"

#include <map>
#include <stdexcept>

Expand Down Expand Up @@ -45,6 +46,10 @@ TEST_CASE("InterfaceTypes basic functionality", "[interface-types][basics]") {
REQUIRE(wrapper1.id() == podio::ObjectID{0, 42});
}

TEST_CASE("InterfaceTypes static checks", "[interface-types][static-checks]") {
STATIC_REQUIRE(podio::detail::isInterfaceType<TypeWithEnergy>);
}

TEST_CASE("InterfaceTypes STL usage", "[interface-types][basics]") {
// Make sure that interface types can be used with STL map and set
std::map<TypeWithEnergy, int> counterMap{};
Expand Down
Loading