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

Frame in memory interface #278

Closed
wants to merge 15 commits into from
Closed
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
218 changes: 218 additions & 0 deletions include/podio/Frame.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
#ifndef PODIO_FRAME_H
#define PODIO_FRAME_H

#include "podio/CollectionBase.h"
#include "podio/GenericParameters.h"
#include "podio/utilities/TypeHelpers.h"

#include <initializer_list>
#include <memory>
#include <mutex>
#include <string>
#include <type_traits>
#include <unordered_map>

namespace podio {

/// Alias template for enabling overloads only for Collections
template <typename T>
using EnableIfCollection = typename std::enable_if_t<isCollection<T>>;

/// Alias template for enabling overloads only for Collection r-values
template <typename T>
using EnableIfCollectionRValue = typename std::enable_if_t<isCollection<T> && !std::is_lvalue_reference_v<T>>;

/**
* Frame class that serves as a container of collection and meta data.
*/
class Frame {
/**
* Internal abstract interface for the type-erased implementation of the Frame
* class
*/
struct FrameConcept {
virtual ~FrameConcept() = default;
virtual const podio::CollectionBase* get(const std::string& name) const = 0;
virtual const podio::CollectionBase* put(std::unique_ptr<podio::CollectionBase> coll, const std::string& name) = 0;
virtual podio::GenericParameters& parameters() = 0;
virtual const podio::GenericParameters& parameters() const = 0;
};

/**
* The interface implementation of the abstract FrameConcept that is necessary
* for a type-erased implementation of the Frame class
*/
struct FrameModel final : FrameConcept {

FrameModel();
~FrameModel() = default;
FrameModel(const FrameModel&) = delete;
FrameModel& operator=(const FrameModel&) = delete;
FrameModel(FrameModel&&) = default;
FrameModel& operator=(FrameModel&&) = default;

/** Try and get the collection from the internal storage and return a
* pointer to it if found. Otherwise return a nullptr
*/
const podio::CollectionBase* get(const std::string& name) const final;

/** Try and place the collection into the internal storage and return a
* pointer to it. If a collection already exists or insertion fails, return
* a nullptr
*/
const podio::CollectionBase* put(std::unique_ptr<CollectionBase> coll, const std::string& name) final;

/** Get a reference to the internally used GenericParameters
*/
podio::GenericParameters& parameters() override {
return m_parameters;
}
/** Get a const reference to the internally used GenericParameters
*/
const podio::GenericParameters& parameters() const override {
return m_parameters;
};

private:
using CollectionMapT = std::unordered_map<std::string, std::unique_ptr<podio::CollectionBase>>;

CollectionMapT m_collections{}; ///< The internal map for storing unpacked collections
podio::GenericParameters m_parameters{}; ///< The generic parameter store for this frame
mutable std::unique_ptr<std::mutex> m_mapMutex{nullptr}; ///< The mutex for guarding the internal collection map
};

std::unique_ptr<FrameConcept> m_self; ///< The internal concept pointer through which all the work is done

public:
/** Empty Frame constructor
*/
Frame();

// The frame is a non-copyable type
Frame(const Frame&) = delete;
Frame& operator=(const Frame&) = delete;

Frame(Frame&&) = default;
Frame& operator=(Frame&&) = default;

/** Frame destructor */
~Frame() = default;

/** Get a collection from the Frame
*/
template <typename CollT, typename = EnableIfCollection<CollT>>
const CollT& get(const std::string& name) const;

/** (Destructively) move a collection into the Frame and get a const reference
* back for further use
*/
template <typename CollT, typename = EnableIfCollectionRValue<CollT>>
const CollT& put(CollT&& coll, const std::string& name);

/** Move a collection into the Frame handing over ownership to the Frame
*/
void put(std::unique_ptr<podio::CollectionBase> coll, const std::string& name);

/** Add a value to the parameters of the Frame (if the type is supported).
* Copy the value into the internal store
*/
template <typename T, typename = podio::EnableIfValidGenericDataType<T>>
void putParameter(const std::string& key, T value) {
m_self->parameters().setValue(key, value);
}

/** Add a string value to the parameters of the Frame by copying it. Dedicated
* overload for enabling the on-the-fly conversion on the string literals.
*/
void putParameter(const std::string& key, std::string value) {
putParameter<std::string>(key, std::move(value));
}

/** Add a vector of strings to the parameters of the Frame (via copy).
* Dedicated overload for enabling on-the-fly conversions of initializer_list
* of string literals.
*/
void putParameter(const std::string& key, std::vector<std::string> values) {
putParameter<std::vector<std::string>>(key, std::move(values));
}

/** Add a vector of values into the parameters of the Frame. Overload for
* catching on-the-fly conversions of initializer_lists of values.
*/
template <typename T, typename = std::enable_if_t<detail::isInTuple<T, SupportedGenericDataTypes>>>
void putParameter(const std::string& key, std::initializer_list<T>&& values) {
putParameter<std::vector<T>>(key, std::move(values));
}

/** Retrieve parameters via key from the internal store. Return type will
* either by a const reference or a value depending on the desired type.
*/
template <typename T, typename = podio::EnableIfValidGenericDataType<T>>
podio::GenericDataReturnType<T> getParameter(const std::string& key) const {
return m_self->parameters().getValue<T>(key);
}
};

// implementations below

Frame::Frame() : m_self(std::make_unique<FrameModel>()) {
}

template <typename CollT, typename>
const CollT& Frame::get(const std::string& name) const {
const auto* coll = dynamic_cast<const CollT*>(m_self->get(name));
if (coll) {
return *coll;
}
// TODO: Handle non-existing collections
static const auto emptyColl = CollT();
return emptyColl;
}

void Frame::put(std::unique_ptr<podio::CollectionBase> coll, const std::string& name) {
const auto* retColl = m_self->put(std::move(coll), name);
if (!retColl) {
// TODO: Handle collisions
}
}

template <typename CollT, typename>
const CollT& Frame::put(CollT&& coll, const std::string& name) {
const auto* retColl = static_cast<const CollT*>(m_self->put(std::make_unique<CollT>(std::move(coll)), name));
if (retColl) {
return *retColl;
}
// TODO: Handle collision case
static const auto emptyColl = CollT();
return emptyColl;
}

Frame::FrameModel::FrameModel() : m_mapMutex(std::make_unique<std::mutex>()) {
}

const podio::CollectionBase* Frame::FrameModel::get(const std::string& name) const {
{
std::lock_guard lock{*m_mapMutex};
if (const auto it = m_collections.find(name); it != m_collections.end()) {
return it->second.get();
}
}

return nullptr;
}

const podio::CollectionBase* Frame::FrameModel::put(std::unique_ptr<podio::CollectionBase> coll,
const std::string& name) {
{
std::lock_guard lock{*m_mapMutex};
auto [it, success] = m_collections.try_emplace(name, std::move(coll));
if (success) {
return it->second.get();
}
}

return nullptr;
}
} // namespace podio

#endif // PODIO_FRAME_H
53 changes: 49 additions & 4 deletions include/podio/GenericParameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <algorithm>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <vector>

Expand Down Expand Up @@ -77,8 +79,24 @@ class GenericParameters {
using IntMap = MapType<int>;
using FloatMap = MapType<float>;
using StringMap = MapType<std::string>;
// need mutex pointers for having the possibility to copy/move GenericParameters
using MutexPtr = std::unique_ptr<std::mutex>;

public:
GenericParameters() = default;

/// GenericParameters are copyable
/// NOTE: This is currently mainly done to keep the ROOT I/O happy, because
/// that needs a copy constructor
GenericParameters(const GenericParameters&);
GenericParameters& operator=(const GenericParameters&) = delete;

/// GenericParameters are default moveable
GenericParameters(GenericParameters&&) = default;
GenericParameters& operator=(GenericParameters&&) = default;

~GenericParameters() = default;

/// Get the value that is stored under the given key, by const reference or by
/// value depending on the desired type
template <typename T, typename = EnableIfValidGenericDataType<T>>
Expand Down Expand Up @@ -237,15 +255,32 @@ class GenericParameters {
}
}

/// Get the mutex that guards the map for the given type
template <typename T>
std::mutex& getMutex() const {
if constexpr (std::is_same_v<detail::GetVectorType<T>, int>) {
return *(m_intMtx.get());
} else if constexpr (std::is_same_v<detail::GetVectorType<T>, float>) {
return *(m_floatMtx.get());
} else {
return *(m_stringMtx.get());
}
}

private:
IntMap _intMap{};
FloatMap _floatMap{};
StringMap _stringMap{};
IntMap _intMap{}; ///< The map storing the integer values
mutable MutexPtr m_intMtx{std::make_unique<std::mutex>()}; ///< The mutex guarding the integer map
FloatMap _floatMap{}; ///< The map storing the float values
mutable MutexPtr m_floatMtx{std::make_unique<std::mutex>()}; ///< The mutex guarding the float map
StringMap _stringMap{}; ///< The map storing the double values
mutable MutexPtr m_stringMtx{std::make_unique<std::mutex>()}; ///< The mutex guarding the float map
};

template <typename T, typename>
GenericDataReturnType<T> GenericParameters::getValue(const std::string& key) const {
const auto& map = getMap<T>();
auto& mtx = getMutex<T>();
std::lock_guard lock{mtx};
const auto it = map.find(key);
// If there is no entry to the key, we just return an empty default
// TODO: make this case detectable from the outside
Expand All @@ -266,18 +301,24 @@ GenericDataReturnType<T> GenericParameters::getValue(const std::string& key) con
template <typename T, typename>
void GenericParameters::setValue(const std::string& key, T value) {
auto& map = getMap<T>();
auto& mtx = getMutex<T>();

if constexpr (detail::isVector<T>) {
std::lock_guard lock{mtx};
map.insert_or_assign(key, std::move(value));
} else {
// Wrap the value into a vector with exactly one entry and store that
std::vector<T> v = {value};
std::lock_guard lock{mtx};
map.insert_or_assign(key, std::move(v));
}
}

template <typename T, typename>
size_t GenericParameters::getN(const std::string& key) const {
const auto& map = getMap<T>();
auto& mtx = getMutex<T>();
std::lock_guard lock{mtx};
if (const auto it = map.find(key); it != map.end()) {
return it->second.size();
}
Expand All @@ -289,7 +330,11 @@ std::vector<std::string> GenericParameters::getKeys() const {
std::vector<std::string> keys;
const auto& map = getMap<T>();
keys.reserve(map.size());
std::transform(map.begin(), map.end(), std::back_inserter(keys), [](const auto& pair) { return pair.first; });
{
auto& mtx = getMutex<T>();
std::lock_guard lock{mtx};
std::transform(map.begin(), map.end(), std::back_inserter(keys), [](const auto& pair) { return pair.first; });
}

return keys;
}
Expand Down
10 changes: 10 additions & 0 deletions include/podio/utilities/TypeHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ namespace detail {
static constexpr bool isVector = IsVectorHelper<T>::value;

} // namespace detail

// forward declaration to be able to use it below
class CollectionBase;

/**
* Alias template for checking whether a passed type T inherits from podio::CollectionBase
*/
template <typename T>
static constexpr bool isCollection = std::is_base_of_v<CollectionBase, T>;

} // namespace podio

#endif // PODIO_UTILITIES_TYPEHELPERS_H
17 changes: 17 additions & 0 deletions src/GenericParameters.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@

namespace podio {

GenericParameters::GenericParameters(const GenericParameters& other) :
m_intMtx(std::make_unique<std::mutex>()),
m_floatMtx(std::make_unique<std::mutex>()),
m_stringMtx(std::make_unique<std::mutex>()) {
{
// acquire all three locks at once to make sure all three internal maps are
// copied at the same "state" of the GenericParameters
auto& intMtx = other.getMutex<int>();
auto& floatMtx = other.getMutex<float>();
auto& stringMtx = other.getMutex<std::string>();
std::scoped_lock lock(intMtx, floatMtx, stringMtx);
_intMap = other._intMap;
_floatMap = other._floatMap;
_stringMap = other._stringMap;
}
}

int GenericParameters::getIntVal(const std::string& key) const {
return getValue<int>(key);
}
Expand Down
2 changes: 1 addition & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ set_property(TEST pyunittest PROPERTY DEPENDS write)
configure_file(CTestCustom.cmake ${CMAKE_BINARY_DIR}/CTestCustom.cmake)

find_package(Threads REQUIRED)
add_executable(unittest unittest.cpp)
add_executable(unittest unittest.cpp frame.cpp)
target_link_libraries(unittest PUBLIC TestDataModel PRIVATE Catch2::Catch2WithMain Threads::Threads)

# The unittests are a bit better and they are labelled so we can put together a
Expand Down
Loading