mirror of https://github.com/CGAL/cgal
631 lines
20 KiB
C++
631 lines
20 KiB
C++
// Copyright (c) 2023 INRIA
|
|
// All rights reserved.
|
|
//
|
|
// This file is part of CGAL (www.cgal.org)
|
|
//
|
|
// $URL$
|
|
// $Id$
|
|
// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial
|
|
//
|
|
// Author(s) : Jackson Campolattaro
|
|
|
|
#ifndef CGAL_PROPERTY_CONTAINTER_H
|
|
#define CGAL_PROPERTY_CONTAINTER_H
|
|
|
|
#include <CGAL/assertions.h>
|
|
|
|
#include <map>
|
|
#include <optional>
|
|
#include <iterator>
|
|
|
|
#include <boost/property_map/property_map.hpp>
|
|
|
|
#ifndef DOXYGEN_RUNNING
|
|
|
|
namespace CGAL::Properties::Experimental {
|
|
|
|
template <typename Index>
|
|
class Property_array_base {
|
|
public:
|
|
|
|
Property_array_base() = default;
|
|
|
|
Property_array_base(const Property_array_base<Index>& rhs) = delete;
|
|
|
|
virtual ~Property_array_base() = default;
|
|
|
|
// Declare virtual functions here, for things which need to be done within the Property container
|
|
// todo: these should mostly be private, and made available using friend
|
|
|
|
virtual std::shared_ptr<Property_array_base<Index>> empty_clone(const std::vector<bool>& active_indices) = 0;
|
|
|
|
virtual std::shared_ptr<Property_array_base<Index>> clone(const std::vector<bool>& active_indices) = 0;
|
|
|
|
virtual void copy(const Property_array_base<Index>& other) = 0;
|
|
|
|
// desactived as MSVC 2017 as an issue with that but it is not currently used.
|
|
#if 0
|
|
virtual void move(Property_array_base<Index>&& other) = 0;
|
|
#endif
|
|
|
|
virtual void append(const Property_array_base<Index>& other) = 0;
|
|
|
|
virtual void reserve(std::size_t n) = 0;
|
|
|
|
virtual void shrink_to_fit() = 0;
|
|
|
|
virtual void swap(Index a, Index b) = 0;
|
|
|
|
virtual void reset(Index i) = 0;
|
|
|
|
virtual const std::type_info& type() const = 0;
|
|
|
|
virtual void transfer_from(const Property_array_base<Index>& other_base, Index other_index, Index this_index) = 0;
|
|
|
|
};
|
|
|
|
/*!
|
|
* \brief Indexed storage for arbitrary types
|
|
*
|
|
* todo: make this effectively private, prioritize the use of Property_array_handle
|
|
*
|
|
* @tparam T
|
|
*/
|
|
template <typename Index, typename T>
|
|
class Property_array : public Property_array_base<Index> {
|
|
|
|
std::vector<T> m_data;
|
|
const std::vector<bool>& m_active_indices;
|
|
T m_default_value;
|
|
|
|
public:
|
|
|
|
using value_type = T;
|
|
using reference = typename std::vector<T>::reference;
|
|
using const_reference = typename std::vector<T>::const_reference;
|
|
using iterator = typename std::vector<T>::iterator;
|
|
using const_iterator = typename std::vector<T>::const_iterator;
|
|
|
|
Property_array(const std::vector<bool>& active_indices, const T& default_value) :
|
|
m_data(), m_active_indices(active_indices), m_default_value(default_value) {
|
|
|
|
m_data.reserve(active_indices.capacity());
|
|
m_data.resize(active_indices.size(), m_default_value);
|
|
}
|
|
|
|
virtual std::shared_ptr<Property_array_base<Index>> empty_clone(const std::vector<bool>& active_indices) override {
|
|
return std::make_shared<Property_array<Index, T>>(active_indices, m_default_value);
|
|
}
|
|
|
|
virtual std::shared_ptr<Property_array_base<Index>> clone(const std::vector<bool>& active_indices) override {
|
|
auto new_array = std::make_shared<Property_array<Index, T>>(active_indices, m_default_value);
|
|
new_array->m_data = m_data;
|
|
return new_array;
|
|
}
|
|
|
|
virtual void copy(const Property_array_base<Index>& other_base) override {
|
|
auto& other = dynamic_cast<const Property_array<Index, T>&>(other_base);
|
|
m_data = other.m_data;
|
|
CGAL_precondition(m_active_indices.size() == m_data.size());
|
|
}
|
|
|
|
// deactived as MSVC 2017 as an issue with that but it is not currently used.
|
|
#if 0
|
|
virtual void move(Property_array_base<Index>&& other_base) override {
|
|
auto&& other = static_cast<Property_array<Index, T>&&>(other_base);
|
|
m_data = std::move(other.m_data);
|
|
CGAL_precondition(m_active_indices.size() == m_data.size());
|
|
}
|
|
#endif
|
|
|
|
virtual void append(const Property_array_base<Index>& other_base) override {
|
|
auto& other = dynamic_cast<const Property_array<Index, T>&>(other_base);
|
|
CGAL_precondition(m_data.size() + other.m_data.size() == m_active_indices.size());
|
|
m_data.insert(m_data.end(), other.m_data.begin(), other.m_data.end());
|
|
}
|
|
|
|
virtual void reserve(std::size_t n) override {
|
|
CGAL_precondition(m_active_indices.size() == n);
|
|
m_data.resize(n, m_default_value);
|
|
};
|
|
|
|
virtual void shrink_to_fit() override {
|
|
m_data.shrink_to_fit();
|
|
}
|
|
|
|
virtual void swap(Index a, Index b) override {
|
|
// todo: maybe cast to index, instead of casting index to size?
|
|
CGAL_precondition(std::size_t(a) < m_data.size() && std::size_t(b) < m_data.size());
|
|
std::iter_swap(m_data.begin() + a, m_data.begin() + b);
|
|
};
|
|
|
|
virtual void reset(Index i) override {
|
|
CGAL_precondition(std::size_t(i) < m_data.size());
|
|
m_data[std::size_t(i)] = m_default_value;
|
|
};
|
|
|
|
virtual const std::type_info& type() const override { return typeid(T); };
|
|
|
|
virtual void transfer_from(const Property_array_base<Index>& other_base,
|
|
Index other_index, Index this_index) override {
|
|
|
|
CGAL_precondition(other_base.type() == type());
|
|
auto& other = dynamic_cast<const Property_array<Index, T>&>(other_base);
|
|
CGAL_precondition(std::size_t(other_index) < other.capacity() && std::size_t(this_index) < capacity());
|
|
m_data[this_index] = other.m_data[other_index];
|
|
}
|
|
|
|
public:
|
|
|
|
// todo: there's not really a good reason to use these, maybe they should be removed
|
|
|
|
[[nodiscard]] std::size_t size() const { return std::count(m_active_indices.begin(), m_active_indices.end(), true); }
|
|
|
|
[[nodiscard]] std::size_t capacity() const { return m_data.size(); }
|
|
|
|
const_reference operator[](Index i) const {
|
|
CGAL_precondition(std::size_t(i) < m_data.size());
|
|
return m_data[std::size_t(i)];
|
|
}
|
|
|
|
reference operator[](Index i) {
|
|
CGAL_precondition(std::size_t(i) < m_data.size());
|
|
return m_data[std::size_t(i)];
|
|
}
|
|
|
|
iterator begin() { return m_data.begin(); }
|
|
|
|
iterator end() { return m_data.end(); }
|
|
|
|
const_iterator begin() const { return m_data.begin(); }
|
|
|
|
const_iterator end() const { return m_data.end(); }
|
|
|
|
public:
|
|
|
|
bool operator==(const Property_array<Index, T>& other) const {
|
|
return &other == this;
|
|
}
|
|
|
|
bool operator!=(const Property_array<Index, T>& other) const { return !operator==(other); }
|
|
|
|
};
|
|
|
|
// todo: property maps/array handles should go in their own file
|
|
|
|
// todo: add const/read-only handle
|
|
template <typename Index, typename T>
|
|
class Property_array_handle {
|
|
|
|
std::reference_wrapper<Property_array<Index, T>> m_array;
|
|
|
|
public:
|
|
|
|
// Necessary for use as a boost::property_type
|
|
using key_type = Index;
|
|
using value_type = T;
|
|
using reference = typename std::vector<T>::reference;
|
|
using const_reference = typename std::vector<T>::const_reference;
|
|
using category = boost::lvalue_property_map_tag;
|
|
|
|
using iterator = typename std::vector<T>::iterator;
|
|
using const_iterator = typename std::vector<T>::const_iterator;
|
|
|
|
Property_array_handle(Property_array<Index, T>& array) : m_array(array) {}
|
|
|
|
[[nodiscard]] std::size_t size() const { return m_array.get().size(); }
|
|
|
|
[[nodiscard]] std::size_t capacity() const { return m_array.get().capacity(); }
|
|
|
|
Property_array<Index, T>& array() const { return m_array.get(); }
|
|
|
|
// todo: This might not be needed, if the other operator[] is made const
|
|
const_reference operator[](Index i) const { return m_array.get()[i]; }
|
|
|
|
reference operator[](Index i) { return m_array.get()[i]; }
|
|
|
|
// todo: maybe these can be const, in an lvalue property map?
|
|
iterator begin() { return m_array.get().begin(); }
|
|
|
|
iterator end() { return m_array.get().end(); }
|
|
|
|
const_iterator begin() const { return m_array.get().begin(); }
|
|
|
|
const_iterator end() const { return m_array.get().end(); }
|
|
|
|
bool operator==(const Property_array_handle<Index, T>& other) const { return other.m_array.get() == m_array.get(); }
|
|
|
|
bool operator!=(const Property_array_handle<Index, T>& other) const { return !operator==(other); }
|
|
|
|
inline friend reference get(Property_array_handle<Index, T> p, const Index& i) { return p[i]; }
|
|
|
|
inline friend void put(Property_array_handle<Index, T> p, const Index& i, const T& v) { p[i] = v; }
|
|
|
|
};
|
|
|
|
template <typename Index = std::size_t>
|
|
class Property_container {
|
|
|
|
std::multimap<std::string, std::shared_ptr<Property_array_base<Index>>> m_properties;
|
|
std::vector<bool> m_active_indices{};
|
|
|
|
public:
|
|
|
|
template<typename T>
|
|
using Array = Property_array<Index, T>;
|
|
|
|
Property_container() = default;
|
|
|
|
Property_container(const Property_container<Index>& other) {
|
|
m_active_indices = other.m_active_indices;
|
|
|
|
for (auto [name, array] : other.m_properties) {
|
|
// todo: this could probably be made faster using emplace_hint
|
|
m_properties.emplace(
|
|
name,
|
|
array->clone(m_active_indices)
|
|
);
|
|
}
|
|
}
|
|
|
|
Property_container(Property_container<Index>&& other) { *this = std::move(other); }
|
|
|
|
// This is not exactly an assignment as existing unique properties are kept.
|
|
Property_container<Index>& operator=(const Property_container<Index>& other) {
|
|
m_active_indices = other.m_active_indices;
|
|
|
|
for (auto [name, array] : other.m_properties) {
|
|
// search if property already exists
|
|
auto range = m_properties.equal_range(name);
|
|
auto it = range.first;
|
|
for (; it != range.second; it++) {
|
|
if (typeid(*array) == typeid((*it->second)))
|
|
break;
|
|
}
|
|
|
|
if (it != range.second)
|
|
it->second->copy(*array);
|
|
else
|
|
m_properties.emplace(name, array->clone(m_active_indices));
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
// This is not exactly an assignment as existing unique properties are kept.
|
|
Property_container<Index>& operator=(Property_container<Index>&& other) {
|
|
m_active_indices = std::move(other.m_active_indices);
|
|
|
|
for (auto [name, array] : other.m_properties) {
|
|
// search if property already exists
|
|
auto range = m_properties.equal_range(name);
|
|
auto it = range.first;
|
|
for (; it != range.second; it++) {
|
|
if (typeid(*array) == typeid((*it->second)))
|
|
break;
|
|
}
|
|
|
|
if (it != range.second)
|
|
it->second->copy(std::move(*array));
|
|
else
|
|
m_properties.emplace(name, array->clone(m_active_indices));
|
|
}
|
|
|
|
// The moved-from property map should retain all of its properties, but contain 0 elements
|
|
other.reserve(0);
|
|
return *this;
|
|
}
|
|
|
|
template <typename T>
|
|
std::pair<std::reference_wrapper<Property_array<Index, T>>, bool>
|
|
get_or_add_property(const std::string& name, const T default_value = T()) {
|
|
auto range = m_properties.equal_range(name);
|
|
for (auto it = range.first; it != range.second; it++) {
|
|
Property_array<Index, T>* typed_array_ptr = dynamic_cast<Property_array<Index, T>*>(it->second.get());
|
|
if (typed_array_ptr != nullptr)
|
|
return { {*typed_array_ptr}, false };
|
|
}
|
|
|
|
auto it = m_properties.emplace(
|
|
name,
|
|
std::make_shared<Property_array<Index, T>>(
|
|
m_active_indices,
|
|
default_value
|
|
)
|
|
);
|
|
|
|
return {{*dynamic_cast<Property_array<Index, T>*>(it->second.get())}, true};
|
|
}
|
|
|
|
template <typename T>
|
|
Property_array<Index, T>& add_property(const std::string& name, const T default_value = T()) {
|
|
// todo: I'm not settled on the naming, but it's really convenient to have a function like this
|
|
auto [array, created] = get_or_add_property(name, default_value);
|
|
CGAL_precondition(created);
|
|
return array.get();
|
|
}
|
|
|
|
/*
|
|
// todo: misleading name, maybe it could be add_same_properties?
|
|
void copy_properties(const Property_container<Index>& other) {
|
|
for (auto [name, other_array]: other.m_properties) {
|
|
// If this container doesn't have any property by this name, add it (with the same type as in other)
|
|
if (!property_exists(name))
|
|
m_property_arrays.emplace(name, other_array->empty_clone(m_active_indices));
|
|
}
|
|
}*/
|
|
|
|
template <typename T>
|
|
const Property_array<Index, T>& get_property(const std::string& name) const {
|
|
return *(get_property_if_exists<T>(name));
|
|
}
|
|
|
|
template <typename T>
|
|
Property_array<Index, T>& get_property(const std::string& name) {
|
|
return *(get_property_if_exists<T>(name));
|
|
}
|
|
|
|
template <typename T>
|
|
std::optional<std::reference_wrapper<Property_array<Index, T>>> get_property_if_exists(const std::string& name) const {
|
|
auto range = m_properties.equal_range(name);
|
|
for (auto it = range.first; it != range.second; it++) {
|
|
Property_array<Index, T>* typed_array_ptr = dynamic_cast<Property_array<Index, T>*>(it->second.get());
|
|
if (typed_array_ptr != nullptr)
|
|
return *typed_array_ptr;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
template <typename T>
|
|
bool property_exists(const std::string& name) const {
|
|
auto range = m_properties.equal_range(name);
|
|
|
|
for (auto it = range.first; it != range.second; it++) {
|
|
Property_array<Index, T>* typed_array_ptr = dynamic_cast<Property_array<Index, T>*>(it->second.get());
|
|
if (typed_array_ptr != nullptr)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*!
|
|
* Removes all properties with the name from the container.
|
|
*
|
|
* @param name
|
|
* @return number of removed properties.
|
|
*/
|
|
std::size_t remove_properties(const std::string& name) { return m_properties.erase(name); }
|
|
|
|
template <typename T>
|
|
bool remove_property(const Property_array<Index, T>& arrayToRemove) {
|
|
const Property_array_base<Index>* ref = dynamic_cast<const Property_array_base<Index>*>(&arrayToRemove);
|
|
for (auto it = m_properties.begin(); it != m_properties.end(); it++) {
|
|
auto const& [name, array] = *it;
|
|
if (array.get() == ref) {
|
|
m_properties.erase(it);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void remove_all_properties_except(const std::vector<std::string>& preserved_names) {
|
|
// todo: if this is used often, it should take a parameter pack instead of a vector
|
|
// A fold expression could then be used in place of std::find for better performance
|
|
for (auto it = m_properties.begin(); it != m_properties.end();) {
|
|
auto const& [name, array] = *it;
|
|
if (std::find(preserved_names.begin(), preserved_names.end(), name) == preserved_names.end())
|
|
it = m_properties.erase(it);
|
|
else
|
|
it++;
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> properties() const {
|
|
std::vector<std::string> property_names{};
|
|
for (auto const& [name, _]: m_properties)
|
|
property_names.emplace_back(name);
|
|
return property_names;
|
|
}
|
|
|
|
std::size_t num_properties() const { return m_properties.size(); }
|
|
|
|
/* Deactivated as there may be several Property_maps with different types but the same name.
|
|
const std::type_info& property_type(const std::string& name) const {
|
|
if (auto it = m_property_arrays.find(name); it != m_property_arrays.end())
|
|
return it->second->type();
|
|
else
|
|
return typeid(void);
|
|
}*/
|
|
|
|
public:
|
|
|
|
void reserve(std::size_t n) {
|
|
m_active_indices.resize(n);
|
|
for (auto [name, array]: m_properties)
|
|
array->reserve(n);
|
|
}
|
|
|
|
void resize(std::size_t n) {
|
|
reserve(n);
|
|
std::fill(m_active_indices.begin(), m_active_indices.end(), true);
|
|
}
|
|
|
|
[[nodiscard]] std::size_t size() const { return std::count(m_active_indices.begin(), m_active_indices.end(), true); }
|
|
|
|
[[nodiscard]] std::size_t capacity() const { return m_active_indices.size(); }
|
|
|
|
Index emplace_back() {
|
|
|
|
// Expand the storage and return the last element
|
|
reserve(capacity() + 1);
|
|
m_active_indices.back() = true;
|
|
auto first_new_index = Index(capacity() - 1);
|
|
reset(first_new_index);
|
|
return first_new_index;
|
|
}
|
|
|
|
Index emplace() {
|
|
|
|
// If there are empty slots, return the index of one of them and mark it as full
|
|
auto first_unused = std::find_if(m_active_indices.begin(), m_active_indices.end(), [](bool used) { return !used; });
|
|
if (first_unused != m_active_indices.end()) {
|
|
*first_unused = true;
|
|
auto index = Index(std::distance(m_active_indices.begin(), first_unused));
|
|
reset(index);
|
|
return index;
|
|
}
|
|
|
|
return emplace_back();
|
|
}
|
|
|
|
Index emplace_group_back(std::size_t n) {
|
|
|
|
// Expand the storage and return the start of the new region
|
|
reserve(capacity() + n);
|
|
for (auto it = m_active_indices.end() - n; it < m_active_indices.end(); ++it)
|
|
*it = true;
|
|
return Index(capacity() - n);
|
|
}
|
|
|
|
Index emplace_group(std::size_t n) {
|
|
|
|
auto search_start = m_active_indices.begin();
|
|
while (search_start != m_active_indices.end()) {
|
|
|
|
// Find the first unused cell
|
|
auto unused_begin = std::find_if(
|
|
search_start, m_active_indices.end(),
|
|
[](bool used) { return !used; }
|
|
);
|
|
|
|
auto unused_end = unused_begin;
|
|
|
|
// Determine if the group fits
|
|
if (std::distance(unused_begin, m_active_indices.end()) >= static_cast<typename std::iterator_traits<std::vector<bool>::iterator>::difference_type>(n))
|
|
unused_end = std::find_if(
|
|
unused_begin, (std::min)(unused_begin + n, m_active_indices.end()),
|
|
[](bool used) { return used; }
|
|
);
|
|
|
|
// If the discovered range was large enough
|
|
if (std::distance(unused_begin, unused_end) >= static_cast<typename std::iterator_traits<std::vector<bool>::iterator>::difference_type>(n)) {
|
|
|
|
// Mark the indices as used, and reset the properties of each of them
|
|
// todo: it would be better to provide a function to set a range
|
|
for (auto it = unused_begin; it < unused_end; ++it) {
|
|
*it = true;
|
|
reset(Index(std::distance(m_active_indices.begin(), it)));
|
|
}
|
|
|
|
// Return the first index of the range
|
|
return Index(std::distance(m_active_indices.begin(), unused_begin));
|
|
}
|
|
|
|
// If we didn't find a large enough region, continue our search after the end
|
|
search_start = unused_end;
|
|
}
|
|
|
|
// If no empty regions were found, expand the storage
|
|
return emplace_group_back(n);
|
|
}
|
|
|
|
void swap(Index a, Index b) {
|
|
for (auto [name, array]: m_properties)
|
|
array->swap(a, b);
|
|
}
|
|
|
|
void reset(Index i) {
|
|
for (auto [name, array]: m_properties)
|
|
array->reset(i);
|
|
}
|
|
|
|
void erase(Index i) {
|
|
m_active_indices[i] = false;
|
|
for (auto [name, array]: m_properties)
|
|
array->reset(i);
|
|
}
|
|
|
|
bool is_erased(Index i) const {
|
|
return !m_active_indices[i];
|
|
}
|
|
|
|
// todo: I'd prefer to eliminate this, if possible
|
|
void mark_active(Index i) {
|
|
return m_active_indices[i] = true;
|
|
}
|
|
|
|
void mark_inactive(Index i) {
|
|
return m_active_indices[i] = false;
|
|
}
|
|
|
|
std::vector<Index> active_list() const {
|
|
std::vector<Index> indices;
|
|
for (std::size_t i = 0; i < m_active_indices.size(); ++i)
|
|
if (m_active_indices[i]) indices.emplace_back(i);
|
|
return indices;
|
|
}
|
|
|
|
std::vector<Index> inactive_list() const {
|
|
std::vector<Index> indices;
|
|
for (std::size_t i = 0; i < m_active_indices.size(); ++i)
|
|
if (!m_active_indices[i]) indices.emplace_back(i);
|
|
return indices;
|
|
}
|
|
|
|
void shrink_to_fit() {
|
|
for (auto [name, array]: m_properties)
|
|
array->shrink_to_fit();
|
|
}
|
|
|
|
/*!
|
|
* Adds the elements of the other container to this container for each property which is present in this container.
|
|
*
|
|
* Gaps in both containers are preserved, and all elements of the other container are guaranteed
|
|
* to appear after the elements of this container.
|
|
* Properties in this container which don't appear in the other container are extended with default values.
|
|
* Properties in the other container which don't appear in this one are not included.
|
|
* todo: merge() would be useful as well, but could break contiguous regions in the other container
|
|
*
|
|
* @param other
|
|
*/
|
|
void append(const Property_container<Index>& other) {
|
|
|
|
m_active_indices.insert(m_active_indices.end(), other.m_active_indices.begin(), other.m_active_indices.end());
|
|
for (auto [name, array]: m_properties) {
|
|
|
|
auto range = other.m_properties.equal_range(name);
|
|
auto it = range.first;
|
|
for (; it != range.second; it++) {
|
|
if (typeid(array.get()) == typeid((it->second.get())))
|
|
break;
|
|
}
|
|
|
|
if (it != range.second)
|
|
array->append(*it->second.get());
|
|
else
|
|
array->reserve(m_active_indices.size());
|
|
}
|
|
}
|
|
|
|
/*
|
|
// todo: maybe should be renamed to transfer_from, but I'd rather remove this functionality entirely
|
|
void transfer(const Property_container<Index>& other, Index other_index, Index this_index) {
|
|
CGAL_precondition(other.m_property_arrays.size() == m_property_arrays.size());
|
|
for (auto [name, array]: m_property_arrays) {
|
|
auto other_array = other.m_property_arrays.at(name);
|
|
array->transfer_from(*other_array, other_index, this_index);
|
|
}
|
|
}*/
|
|
|
|
// todo: maybe a compress() method?
|
|
};
|
|
|
|
}
|
|
|
|
#endif // DOXYGEN_RUNNING
|
|
|
|
#endif //CGAL_PROPERTY_CONTAINTER_H
|