mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-14 18:15:56 +08:00
Add StaticSet and StaticMap to allow global constant dictionaries
This commit is contained in:
parent
42e7dac1ca
commit
23b041e222
@ -500,6 +500,7 @@ set(SLIC3R_SOURCES
|
||||
Arachne/SkeletalTrapezoidationJoint.hpp
|
||||
Arachne/WallToolPaths.hpp
|
||||
Arachne/WallToolPaths.cpp
|
||||
StaticMap.hpp
|
||||
)
|
||||
|
||||
add_library(libslic3r STATIC ${SLIC3R_SOURCES})
|
||||
|
322
src/libslic3r/StaticMap.hpp
Normal file
322
src/libslic3r/StaticMap.hpp
Normal file
@ -0,0 +1,322 @@
|
||||
#ifndef PRUSASLICER_STATICMAP_HPP
|
||||
#define PRUSASLICER_STATICMAP_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <array>
|
||||
#include <string_view>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// This module provides std::map and std::set like structures with fixed number
|
||||
// of elements and usable at compile or in time constexpr contexts without
|
||||
// any memory allocations.
|
||||
|
||||
// C++20 emulation utilities to get the missing constexpr functionality in C++17
|
||||
namespace static_set_detail {
|
||||
|
||||
// Simple bubble sort but constexpr
|
||||
template<class T, size_t N, class Cmp = std::less<T>>
|
||||
constexpr void sort_array(std::array<T, N> &arr, Cmp cmp = {})
|
||||
{
|
||||
// A bubble sort will do the job, C++20 will have constexpr std::sort
|
||||
for (size_t i = 0; i < N - 1; ++i)
|
||||
{
|
||||
for (size_t j = 0; j < N - i - 1; ++j)
|
||||
{
|
||||
if (!cmp(arr[j], arr[j + 1])) {
|
||||
T temp = arr[j];
|
||||
arr[j] = arr[j + 1];
|
||||
arr[j + 1] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simple emulation of lower_bound with constexpr
|
||||
template<class It, class V, class Cmp = std::less<V>>
|
||||
constexpr auto array_lower_bound(It from, It to, const V &val, Cmp cmp)
|
||||
{
|
||||
auto N = std::distance(from, to);
|
||||
std::size_t middle = N / 2;
|
||||
|
||||
if (N == 0) {
|
||||
return from; // Key not found, return the beginning of the array
|
||||
} else if (cmp(val, *(from + middle))) {
|
||||
return array_lower_bound(from, from + middle, val, cmp);
|
||||
} else if (cmp(*(from + middle), val)) {
|
||||
return array_lower_bound(from + middle + 1, to, val, cmp);
|
||||
} else {
|
||||
return from + middle; // Key found, return an iterator to it
|
||||
}
|
||||
|
||||
return to;
|
||||
}
|
||||
|
||||
template<class T, size_t N, class Cmp = std::less<T>>
|
||||
constexpr auto array_lower_bound(const std::array<T, N> &arr,
|
||||
const T &val,
|
||||
Cmp cmp = {})
|
||||
{
|
||||
return array_lower_bound(arr.begin(), arr.end(), val, cmp);
|
||||
}
|
||||
|
||||
template<class T, std::size_t N, std::size_t... I>
|
||||
constexpr std::array<std::remove_cv_t<T>, N>
|
||||
to_array_impl(T (&a)[N], std::index_sequence<I...>)
|
||||
{
|
||||
return {{a[I]...}};
|
||||
}
|
||||
|
||||
template<class T, std::size_t N>
|
||||
constexpr std::array<std::remove_cv_t<T>, N> to_array(T (&a)[N])
|
||||
{
|
||||
return to_array_impl(a, std::make_index_sequence<N>{});
|
||||
}
|
||||
|
||||
// Emulating constexpr std::pair
|
||||
template<class K, class V>
|
||||
struct StaticMapElement {
|
||||
using KeyType = K;
|
||||
|
||||
constexpr StaticMapElement(const K &k, const V &v): first{k}, second{v} {}
|
||||
|
||||
K first;
|
||||
V second;
|
||||
};
|
||||
|
||||
} // namespace static_set_detail
|
||||
} // namespace Slic3r
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// std::set like set structure
|
||||
template<class T, size_t N, class Cmp = std::less<T>>
|
||||
class StaticSet {
|
||||
std::array<T, N> m_vals; // building on top of std::array
|
||||
Cmp m_cmp;
|
||||
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
constexpr StaticSet(const std::array<T, N> &arr, Cmp cmp = {})
|
||||
: m_vals{arr}, m_cmp{cmp}
|
||||
{
|
||||
// TODO: C++20 can use std::sort(vals.begin(), vals.end())
|
||||
static_set_detail::sort_array(m_vals, m_cmp);
|
||||
}
|
||||
|
||||
template<class...Ts>
|
||||
constexpr StaticSet(Ts &&...args): m_vals{std::forward<Ts>(args)...}
|
||||
{
|
||||
static_set_detail::sort_array(m_vals, m_cmp);
|
||||
}
|
||||
|
||||
template<class...Ts>
|
||||
constexpr StaticSet(Cmp cmp, Ts &&...args)
|
||||
: m_vals{std::forward<Ts>(args)...}, m_cmp{cmp}
|
||||
{
|
||||
static_set_detail::sort_array(m_vals, m_cmp);
|
||||
}
|
||||
|
||||
constexpr auto find(const T &val) const
|
||||
{
|
||||
// TODO: C++20 can use std::lower_bound
|
||||
auto it = static_set_detail::array_lower_bound(m_vals, val, m_cmp);
|
||||
if (it != m_vals.end() && ! m_cmp(*it, val) && !m_cmp(val, *it) )
|
||||
return it;
|
||||
|
||||
return m_vals.cend();
|
||||
}
|
||||
|
||||
constexpr bool empty() const { return m_vals.empty(); }
|
||||
constexpr size_t size() const { return m_vals.size(); }
|
||||
|
||||
// Can be iterated over
|
||||
constexpr auto begin() const { return m_vals.begin(); }
|
||||
constexpr auto end() const { return m_vals.end(); }
|
||||
};
|
||||
|
||||
// These are "deduction guides", a C++17 feature.
|
||||
// Reason is to be able to deduce template arguments from constructor arguments
|
||||
// e.g.: StaticSet{1, 2, 3} is deduced as StaticSet<int, 3, std::less<int>>, no
|
||||
// need to state the template types explicitly.
|
||||
template<class T, class...Vals>
|
||||
StaticSet(T, Vals...) ->
|
||||
StaticSet<std::enable_if_t<(std::is_same_v<T, Vals> && ...), T>,
|
||||
1 + sizeof...(Vals)>;
|
||||
|
||||
// Same as above, only with the first argument being a comparison functor
|
||||
template<class Cmp, class T, class...Vals>
|
||||
StaticSet(Cmp, T, Vals...) ->
|
||||
StaticSet<std::enable_if_t<(std::is_same_v<T, Vals> && ...), T>,
|
||||
1 + sizeof...(Vals),
|
||||
std::enable_if_t<std::is_invocable_r_v<bool, Cmp, T, T>, Cmp>>;
|
||||
|
||||
// Specialization for the empty set case.
|
||||
template<class T>
|
||||
class StaticSet<T, size_t{0}> {
|
||||
public:
|
||||
constexpr StaticSet() = default;
|
||||
constexpr auto find(const T &val) const { return nullptr; }
|
||||
constexpr bool empty() const { return true; }
|
||||
constexpr size_t size() const { return 0; }
|
||||
constexpr auto begin() const { return nullptr; }
|
||||
constexpr auto end() const { return nullptr; }
|
||||
};
|
||||
|
||||
// Constructor with no arguments need to be deduced as the specialization for
|
||||
// empty sets (see above)
|
||||
StaticSet() -> StaticSet<int, 0>;
|
||||
|
||||
|
||||
|
||||
// StaticMap definition:
|
||||
|
||||
template<class K, class V>
|
||||
using SMapEl = static_set_detail::StaticMapElement<K, V>;
|
||||
|
||||
template<class K, class V>
|
||||
struct DefaultCmp {
|
||||
constexpr bool operator() (const SMapEl<K, V> &el1, const SMapEl<K, V> &el2) const
|
||||
{
|
||||
return std::less<K>{}(el1.first, el2.first);
|
||||
}
|
||||
};
|
||||
|
||||
// Overriding the default comparison for C style strings, as std::less<const char*>
|
||||
// doesn't do the lexicographic comparisons, only the pointer values would be
|
||||
// compared. Fortunately we can wrap the C style strings with string_views and
|
||||
// do the comparison with those.
|
||||
template<class V>
|
||||
struct DefaultCmp<const char *, V> {
|
||||
constexpr bool operator() (const SMapEl<const char *, V> &el1,
|
||||
const SMapEl<const char *, V> &el2) const
|
||||
{
|
||||
return std::string_view{el1.first} < std::string_view{el2.first};
|
||||
}
|
||||
};
|
||||
|
||||
template<class K, class V, size_t N, class Cmp = DefaultCmp<K, V>>
|
||||
class StaticMap {
|
||||
std::array<SMapEl<K, V>, N> m_vals;
|
||||
Cmp m_cmp;
|
||||
|
||||
public:
|
||||
using value_type = SMapEl<K, V>;
|
||||
|
||||
constexpr StaticMap(const std::array<SMapEl<K, V>, N> &arr, Cmp cmp = {})
|
||||
: m_vals{arr}, m_cmp{cmp}
|
||||
{
|
||||
static_set_detail::sort_array(m_vals, cmp);
|
||||
}
|
||||
|
||||
constexpr auto find(const K &key) const
|
||||
{
|
||||
auto ret = m_vals.end();
|
||||
|
||||
SMapEl<K, V> vkey{key, V{}};
|
||||
|
||||
auto it = static_set_detail::array_lower_bound(
|
||||
std::begin(m_vals), std::end(m_vals), vkey, m_cmp
|
||||
);
|
||||
|
||||
if (it != std::end(m_vals) && ! m_cmp(*it, vkey) && !m_cmp(vkey, *it))
|
||||
ret = it;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
constexpr const V& at(const K& key) const
|
||||
{
|
||||
if (auto it = find(key); it != end())
|
||||
return it->second;
|
||||
|
||||
throw std::out_of_range{"No such element"};
|
||||
}
|
||||
|
||||
constexpr bool empty() const { return m_vals.empty(); }
|
||||
constexpr size_t size() const { return m_vals.size(); }
|
||||
|
||||
constexpr auto begin() const { return m_vals.begin(); }
|
||||
constexpr auto end() const { return m_vals.end(); }
|
||||
};
|
||||
|
||||
template<class K, class V>
|
||||
class StaticMap<K, V, size_t{0}> {
|
||||
public:
|
||||
constexpr StaticMap() = default;
|
||||
constexpr auto find(const K &key) const { return nullptr; }
|
||||
constexpr bool empty() const { return true; }
|
||||
constexpr size_t size() const { return 0; }
|
||||
[[noreturn]] constexpr const V& at(const K &) const { throw std::out_of_range{"Map is empty"}; }
|
||||
constexpr auto begin() const { return nullptr; }
|
||||
constexpr auto end() const { return nullptr; }
|
||||
};
|
||||
|
||||
// Deducing template arguments from the StaticMap constructors is not easy,
|
||||
// so there is a helper "make" function to be used instead:
|
||||
// e.g.: auto map = make_staticmap<const char*, int>({ {"one", 1}, {"two", 2}})
|
||||
// will work, and only the key and value type needs to be specified. No need
|
||||
// to state the number of elements, that is deduced automatically.
|
||||
template<class K, class V, size_t N, class Cmp = DefaultCmp<K, V>>
|
||||
constexpr auto make_staticmap(const SMapEl<K, V> (&arr) [N], Cmp cmp = {})
|
||||
{
|
||||
return StaticMap<K, V, N, Cmp>{static_set_detail ::to_array(arr), cmp};
|
||||
}
|
||||
|
||||
// Override for empty maps
|
||||
template<class K, class V, class Cmp = DefaultCmp<K, V>>
|
||||
constexpr auto make_staticmap()
|
||||
{
|
||||
return StaticMap<K, V, 0, Cmp>{};
|
||||
}
|
||||
|
||||
// Override which uses a c++ array as the initializer
|
||||
template<class K, class V, size_t N, class Cmp = DefaultCmp<K, V>>
|
||||
constexpr auto make_staticmap(const std::array<SMapEl<K, V>, N> &arr, Cmp cmp = {})
|
||||
{
|
||||
return StaticMap<K, V, N, Cmp>{arr, cmp};
|
||||
}
|
||||
|
||||
// Helper function to get a specific element from a set, returning a std::optional
|
||||
// which is more convinient than working with iterators
|
||||
template<class V, size_t N, class Cmp, class T>
|
||||
constexpr std::enable_if_t<std::is_convertible_v<T, V>, std::optional<V>>
|
||||
query(const StaticSet<V, N, Cmp> &sset, const T &val)
|
||||
{
|
||||
std::optional<V> ret;
|
||||
if (auto it = sset.find(val); it != sset.end())
|
||||
ret = *it;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class K, class V, size_t N, class Cmp, class KeyT>
|
||||
constexpr std::enable_if_t<std::is_convertible_v<K, KeyT>, std::optional<V>>
|
||||
query(const StaticMap<K, V, N, Cmp> &sset, const KeyT &val)
|
||||
{
|
||||
std::optional<V> ret;
|
||||
|
||||
if (auto it = sset.find(val); it != sset.end())
|
||||
ret = it->second;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<class V, size_t N, class Cmp, class T>
|
||||
constexpr std::enable_if_t<std::is_convertible_v<T, V>, bool>
|
||||
contains(const StaticSet<V, N, Cmp> &sset, const T &val)
|
||||
{
|
||||
return sset.find(val) != sset.end();
|
||||
}
|
||||
|
||||
template<class K, class V, size_t N, class Cmp, class KeyT>
|
||||
constexpr std::enable_if_t<std::is_convertible_v<K, KeyT>, bool>
|
||||
contains(const StaticMap<K, V, N, Cmp> &smap, const KeyT &key)
|
||||
{
|
||||
return smap.find(key) != smap.end();
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // STATICMAP_HPP
|
@ -42,6 +42,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_support_spots_generator.cpp
|
||||
../data/prusaparts.cpp
|
||||
../data/prusaparts.hpp
|
||||
test_static_map.cpp
|
||||
)
|
||||
|
||||
if (TARGET OpenVDB::openvdb)
|
||||
|
98
tests/libslic3r/test_static_map.cpp
Normal file
98
tests/libslic3r/test_static_map.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <string_view>
|
||||
|
||||
#include "libslic3r/StaticMap.hpp"
|
||||
|
||||
TEST_CASE("Empty static map should be possible to create and should be empty", "[StaticMap]")
|
||||
{
|
||||
using namespace Slic3r;
|
||||
|
||||
static const constexpr StaticSet EmptySet;
|
||||
|
||||
static const constexpr auto EmptyMap = make_staticmap<int, int>();
|
||||
|
||||
constexpr bool is_map_empty = EmptyMap.empty();
|
||||
constexpr bool is_set_empty = EmptySet.empty();
|
||||
|
||||
REQUIRE(is_map_empty);
|
||||
REQUIRE(is_set_empty);
|
||||
}
|
||||
|
||||
TEST_CASE("StaticSet should derive it's type from the initializer", "[StaticMap]") {
|
||||
using namespace Slic3r;
|
||||
static const constexpr StaticSet iOneSet = { 1 };
|
||||
static constexpr size_t iOneSetSize = iOneSet.size();
|
||||
|
||||
REQUIRE(iOneSetSize == 1);
|
||||
|
||||
static const constexpr StaticSet iManySet = { 1, 3, 5, 80, 40 };
|
||||
static constexpr size_t iManySetSize = iManySet.size();
|
||||
|
||||
REQUIRE(iManySetSize == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("StaticMap should derive it's type using make_staticmap", "[StaticMap]") {
|
||||
using namespace Slic3r;
|
||||
static const constexpr auto ciOneMap = make_staticmap<char, int>({
|
||||
{'a', 1},
|
||||
});
|
||||
|
||||
static constexpr size_t ciOneMapSize = ciOneMap.size();
|
||||
static constexpr bool ciOneMapValid = query(ciOneMap, 'a').value_or(0) == 1;
|
||||
|
||||
REQUIRE(ciOneMapSize == 1);
|
||||
REQUIRE(ciOneMapValid);
|
||||
|
||||
static const constexpr auto ciManyMap = make_staticmap<char, int>({
|
||||
{'a', 1}, {'b', 2}, {'A', 10}
|
||||
});
|
||||
|
||||
static constexpr size_t ciManyMapSize = ciManyMap.size();
|
||||
static constexpr bool ciManyMapValid =
|
||||
query(ciManyMap, 'a').value_or(0) == 1 &&
|
||||
query(ciManyMap, 'b').value_or(0) == 2 &&
|
||||
query(ciManyMap, 'A').value_or(0) == 10 &&
|
||||
!contains(ciManyMap, 'B') &&
|
||||
!query(ciManyMap, 'c').has_value();
|
||||
|
||||
REQUIRE(ciManyMapSize == 3);
|
||||
REQUIRE(ciManyMapValid);
|
||||
|
||||
for (auto &[k, v] : ciManyMap) {
|
||||
auto val = query(ciManyMap, k);
|
||||
REQUIRE(val.has_value());
|
||||
REQUIRE(val.value() == v);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("StaticSet should be able to find contained values", "[StaticMap]")
|
||||
{
|
||||
using namespace Slic3r;
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
auto cmp = [](const char *a, const char *b) constexpr {
|
||||
return std::string_view{a} < std::string_view{b};
|
||||
};
|
||||
|
||||
static constexpr StaticSet CStrSet = {cmp, "One", "Two", "Three"};
|
||||
static constexpr StaticSet StringSet = {"One"sv, "Two"sv, "Three"sv};
|
||||
|
||||
static constexpr bool CStrSetValid = query(CStrSet, "One").has_value() &&
|
||||
contains(CStrSet, "Two") &&
|
||||
contains(CStrSet, "Three") &&
|
||||
!contains(CStrSet, "one") &&
|
||||
!contains(CStrSet, "two") &&
|
||||
!contains(CStrSet, "three");
|
||||
|
||||
static constexpr bool StringSetValid = contains(StringSet, "One"sv) &&
|
||||
contains(StringSet, "Two"sv) &&
|
||||
contains(StringSet, "Three"sv) &&
|
||||
!contains(StringSet, "one"sv) &&
|
||||
!contains(StringSet, "two"sv) &&
|
||||
!contains(StringSet, "three"sv);
|
||||
|
||||
REQUIRE(CStrSetValid);
|
||||
REQUIRE(StringSetValid);
|
||||
REQUIRE(CStrSet.size() == 3);
|
||||
REQUIRE(StringSet.size() == 3);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user