diff --git a/.gitignore b/.gitignore index 7b48d224..9157e62c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ porymap.pro.user *.autosave *.stash *.o +*.DS_Store porymap.app* porymap porymap.cfg diff --git a/include/core/event.h b/include/core/event.h index cd7da8b4..a49a3c00 100644 --- a/include/core/event.h +++ b/include/core/event.h @@ -4,7 +4,10 @@ #include #include #include -#include + +#include "orderedjson.h" + +using OrderedJson = poryjson::Json; class EventType { @@ -67,19 +70,19 @@ public: static Event* createNewHiddenItemEvent(Project*); static Event* createNewSecretBaseEvent(Project*); - QJsonObject buildObjectEventJSON(); - QJsonObject buildWarpEventJSON(QMap*); - QJsonObject buildTriggerEventJSON(); - QJsonObject buildWeatherTriggerEventJSON(); - QJsonObject buildSignEventJSON(); - QJsonObject buildHiddenItemEventJSON(); - QJsonObject buildSecretBaseEventJSON(); + OrderedJson::object buildObjectEventJSON(); + OrderedJson::object buildWarpEventJSON(QMap*); + OrderedJson::object buildTriggerEventJSON(); + OrderedJson::object buildWeatherTriggerEventJSON(); + OrderedJson::object buildSignEventJSON(); + OrderedJson::object buildHiddenItemEventJSON(); + OrderedJson::object buildSecretBaseEventJSON(); void setPixmapFromSpritesheet(QImage, int, int, int, bool); int getPixelX(); int getPixelY(); QMap getExpectedFields(); void readCustomValues(QJsonObject values); - void addCustomValuesTo(QJsonObject *obj); + void addCustomValuesTo(OrderedJson::object *obj); void setFrameFromMovement(QString); QMap values; diff --git a/include/core/orderedjson.h b/include/lib/orderedjson.h similarity index 94% rename from include/core/orderedjson.h rename to include/lib/orderedjson.h index b035b1d7..645b4336 100644 --- a/include/core/orderedjson.h +++ b/include/lib/orderedjson.h @@ -1,4 +1,3 @@ -/// Wrappings around QJsonObjects that preserves order /* poryjson * * poryjson is a modified version of json11, which adds support to preserve the key order @@ -56,15 +55,14 @@ #include #include +#include #include #include -#include #include #include -// temp -#include +#include "orderedmap.h" #ifdef _MSC_VER #if _MSC_VER <= 1800 // VS 2013 @@ -95,7 +93,7 @@ public: // Array and object typedefs typedef QVector array; - typedef std::map object; + typedef tsl::ordered_map object; // Constructors for the various types of JSON value. Json() noexcept; // NUL @@ -196,14 +194,6 @@ public: bool operator> (const Json &rhs) const { return (rhs < *this); } bool operator>= (const Json &rhs) const { return !(*this < rhs); } - /* has_shape(types, err) - * - * Return true if this is a JSON object and, for each item in types, has a field of - * the given type. If not, return false and set err to a descriptive message. - */ - typedef std::initializer_list> shape; - bool has_shape(const shape & types, QString & err) const; - private: std::shared_ptr m_ptr; }; diff --git a/include/lib/orderedmap.h b/include/lib/orderedmap.h new file mode 100644 index 00000000..df94d861 --- /dev/null +++ b/include/lib/orderedmap.h @@ -0,0 +1,2406 @@ +/** + * MIT License + * + * Copyright (c) 2017 Tessil + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_ORDERED_MAP_H +#define TSL_ORDERED_MAP_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/** + * Macros for compatibility with GCC 4.8 + */ +#if (defined(__GNUC__) && (__GNUC__ == 4) && (__GNUC_MINOR__ < 9)) +# define TSL_OH_NO_CONTAINER_ERASE_CONST_ITERATOR +# define TSL_OH_NO_CONTAINER_EMPLACE_CONST_ITERATOR +#endif + +/** + * Only activate tsl_oh_assert if TSL_DEBUG is defined. + * This way we avoid the performance hit when NDEBUG is not defined with assert as tsl_oh_assert is used a lot + * (people usually compile with "-O3" and not "-O3 -DNDEBUG"). + */ +#ifdef TSL_DEBUG +# define tsl_oh_assert(expr) assert(expr) +#else +# define tsl_oh_assert(expr) (static_cast(0)) +#endif + +/** + * If exceptions are enabled, throw the exception passed in parameter, otherwise call std::terminate. + */ +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (defined (_MSC_VER) && defined (_CPPUNWIND))) && !defined(TSL_NO_EXCEPTIONS) +# define TSL_OH_THROW_OR_TERMINATE(ex, msg) throw ex(msg) +#else +# define TSL_OH_NO_EXCEPTIONS +# ifdef NDEBUG +# define TSL_OH_THROW_OR_TERMINATE(ex, msg) std::terminate() +# else +# include +# define TSL_OH_THROW_OR_TERMINATE(ex, msg) do { std::cerr << msg << std::endl; std::terminate(); } while(0) +# endif +#endif + + +namespace tsl { + +namespace detail_ordered_hash { + +template +struct make_void { + using type = void; +}; + +template +struct has_is_transparent: std::false_type { +}; + +template +struct has_is_transparent::type>: std::true_type { +}; + + +template +struct is_vector: std::false_type { +}; + +template +struct is_vector>::value + >::type>: std::true_type { +}; + +template +static T numeric_cast(U value, const char* error_message = "numeric_cast() failed.") { + T ret = static_cast(value); + if(static_cast(ret) != value) { + TSL_OH_THROW_OR_TERMINATE(std::runtime_error, error_message); + } + + const bool is_same_signedness = (std::is_unsigned::value && std::is_unsigned::value) || + (std::is_signed::value && std::is_signed::value); + if(!is_same_signedness && (ret < T{}) != (value < U{})) { + TSL_OH_THROW_OR_TERMINATE(std::runtime_error, error_message); + } + + return ret; +} + + +/** + * Fixed size type used to represent size_type values on serialization. Need to be big enough + * to represent a std::size_t on 32 and 64 bits platforms, and must be the same size on both platforms. + */ +using slz_size_type = std::uint64_t; +static_assert(std::numeric_limits::max() >= std::numeric_limits::max(), + "slz_size_type must be >= std::size_t"); + +template +static T deserialize_value(Deserializer& deserializer) { + // MSVC < 2017 is not conformant, circumvent the problem by removing the template keyword +#if defined (_MSC_VER) && _MSC_VER < 1910 + return deserializer.Deserializer::operator()(); +#else + return deserializer.Deserializer::template operator()(); +#endif +} + + +/** + * Each bucket entry stores an index which is the index in m_values corresponding to the bucket's value + * and a hash (which may be truncated to 32 bits depending on IndexType) corresponding to the hash of the value. + * + * The size of IndexType limits the size of the hash table to std::numeric_limits::max() - 1 elements (-1 due to + * a reserved value used to mark a bucket as empty). + */ +template +class bucket_entry { + static_assert(std::is_unsigned::value, "IndexType must be an unsigned value."); + static_assert(std::numeric_limits::max() <= std::numeric_limits::max(), + "std::numeric_limits::max() must be <= std::numeric_limits::max()."); + +public: + using index_type = IndexType; + using truncated_hash_type = typename std::conditional::max() <= + std::numeric_limits::max(), + std::uint_least32_t, + std::size_t>::type; + + bucket_entry() noexcept: m_index(EMPTY_MARKER_INDEX), m_hash(0) { + } + + bool empty() const noexcept { + return m_index == EMPTY_MARKER_INDEX; + } + + void clear() noexcept { + m_index = EMPTY_MARKER_INDEX; + } + + index_type index() const noexcept { + tsl_oh_assert(!empty()); + return m_index; + } + + index_type& index_ref() noexcept { + tsl_oh_assert(!empty()); + return m_index; + } + + void set_index(index_type index) noexcept { + tsl_oh_assert(index <= max_size()); + + m_index = index; + } + + truncated_hash_type truncated_hash() const noexcept { + tsl_oh_assert(!empty()); + return m_hash; + } + + truncated_hash_type& truncated_hash_ref() noexcept { + tsl_oh_assert(!empty()); + return m_hash; + } + + void set_hash(std::size_t hash) noexcept { + m_hash = truncate_hash(hash); + } + + template + void serialize(Serializer& serializer) const { + const slz_size_type index = m_index; + serializer(index); + + const slz_size_type hash = m_hash; + serializer(hash); + } + + template + static bucket_entry deserialize(Deserializer& deserializer) { + const slz_size_type index = deserialize_value(deserializer); + const slz_size_type hash = deserialize_value(deserializer); + + bucket_entry bentry; + bentry.m_index = numeric_cast(index, "Deserialized index is too big."); + bentry.m_hash = numeric_cast(hash, "Deserialized hash is too big."); + + return bentry; + } + + + + static truncated_hash_type truncate_hash(std::size_t hash) noexcept { + return truncated_hash_type(hash); + } + + static std::size_t max_size() noexcept { + return static_cast(std::numeric_limits::max()) - NB_RESERVED_INDEXES; + } + +private: + static const index_type EMPTY_MARKER_INDEX = std::numeric_limits::max(); + static const std::size_t NB_RESERVED_INDEXES = 1; + + index_type m_index; + truncated_hash_type m_hash; +}; + + + +/** + * Internal common class used by ordered_map and ordered_set. + * + * ValueType is what will be stored by ordered_hash (usually std::pair for map and Key for set). + * + * KeySelect should be a FunctionObject which takes a ValueType in parameter and return a reference to the key. + * + * ValueSelect should be a FunctionObject which takes a ValueType in parameter and return a reference to the value. + * ValueSelect should be void if there is no value (in set for example). + * + * ValueTypeContainer is the container which will be used to store ValueType values. + * Usually a std::deque or std::vector. + * + * + * + * The orderd_hash structure is a hash table which preserves the order of insertion of the elements. + * To do so, it stores the values in the ValueTypeContainer (m_values) using emplace_back at each + * insertion of a new element. Another structure (m_buckets of type std::vector) will + * serve as buckets array for the hash table part. Each bucket stores an index which corresponds to + * the index in m_values where the bucket's value is and the (truncated) hash of this value. An index + * is used instead of a pointer to the value to reduce the size of each bucket entry. + * + * To resolve collisions in the buckets array, the structures use robin hood linear probing with + * backward shift deletion. + */ +template +class ordered_hash: private Hash, private KeyEqual { +private: + template + using has_mapped_type = typename std::integral_constant::value>; + + static_assert(std::is_same::value, + "ValueTypeContainer::value_type != ValueType. " + "Check that the ValueTypeContainer has 'Key' as type for a set or 'std::pair' as type for a map."); + + static_assert(std::is_same::value, + "ValueTypeContainer::allocator_type != Allocator. " + "Check that the allocator for ValueTypeContainer is the same as Allocator."); + + static_assert(std::is_same::value, + "Allocator::value_type != ValueType. " + "Check that the allocator has 'Key' as type for a set or 'std::pair' as type for a map."); + + +public: + template + class ordered_iterator; + + using key_type = typename KeySelect::key_type; + using value_type = ValueType; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using hasher = Hash; + using key_equal = KeyEqual; + using allocator_type = Allocator; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = ordered_iterator; + using const_iterator = ordered_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + using values_container_type = ValueTypeContainer; + +public: + template + class ordered_iterator { + friend class ordered_hash; + + private: + using iterator = typename std::conditional::type; + + + ordered_iterator(iterator it) noexcept: m_iterator(it) { + } + + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = const typename ordered_hash::value_type; + using difference_type = typename iterator::difference_type; + using reference = value_type&; + using pointer = value_type*; + + + ordered_iterator() noexcept { + } + + // Copy constructor from iterator to const_iterator. + template::type* = nullptr> + ordered_iterator(const ordered_iterator& other) noexcept: m_iterator(other.m_iterator) { + } + + ordered_iterator(const ordered_iterator& other) = default; + ordered_iterator(ordered_iterator&& other) = default; + ordered_iterator& operator=(const ordered_iterator& other) = default; + ordered_iterator& operator=(ordered_iterator&& other) = default; + + const typename ordered_hash::key_type& key() const { + return KeySelect()(*m_iterator); + } + + template::value && IsConst>::type* = nullptr> + const typename U::value_type& value() const { + return U()(*m_iterator); + } + + template::value && !IsConst>::type* = nullptr> + typename U::value_type& value() { + return U()(*m_iterator); + } + + reference operator*() const { return *m_iterator; } + pointer operator->() const { return m_iterator.operator->(); } + + ordered_iterator& operator++() { ++m_iterator; return *this; } + ordered_iterator& operator--() { --m_iterator; return *this; } + + ordered_iterator operator++(int) { ordered_iterator tmp(*this); ++(*this); return tmp; } + ordered_iterator operator--(int) { ordered_iterator tmp(*this); --(*this); return tmp; } + + reference operator[](difference_type n) const { return m_iterator[n]; } + + ordered_iterator& operator+=(difference_type n) { m_iterator += n; return *this; } + ordered_iterator& operator-=(difference_type n) { m_iterator -= n; return *this; } + + ordered_iterator operator+(difference_type n) { ordered_iterator tmp(*this); tmp += n; return tmp; } + ordered_iterator operator-(difference_type n) { ordered_iterator tmp(*this); tmp -= n; return tmp; } + + friend bool operator==(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator == rhs.m_iterator; + } + + friend bool operator!=(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator != rhs.m_iterator; + } + + friend bool operator<(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator < rhs.m_iterator; + } + + friend bool operator>(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator > rhs.m_iterator; + } + + friend bool operator<=(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator <= rhs.m_iterator; + } + + friend bool operator>=(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator >= rhs.m_iterator; + } + + friend ordered_iterator operator+(difference_type n, const ordered_iterator& it) { + return n + it.m_iterator; + } + + friend difference_type operator-(const ordered_iterator& lhs, const ordered_iterator& rhs) { + return lhs.m_iterator - rhs.m_iterator; + } + + private: + iterator m_iterator; + }; + + +private: + using bucket_entry = tsl::detail_ordered_hash::bucket_entry; + + using buckets_container_allocator = typename + std::allocator_traits::template rebind_alloc; + + using buckets_container_type = std::vector; + + + using truncated_hash_type = typename bucket_entry::truncated_hash_type; + using index_type = typename bucket_entry::index_type; + +public: + ordered_hash(size_type bucket_count, + const Hash& hash, + const KeyEqual& equal, + const Allocator& alloc, + float max_load_factor): Hash(hash), + KeyEqual(equal), + m_buckets_data(alloc), + m_buckets(static_empty_bucket_ptr()), + m_mask(0), + m_values(alloc), + m_grow_on_next_insert(false) + { + if(bucket_count > max_bucket_count()) { + TSL_OH_THROW_OR_TERMINATE(std::length_error, "The map exceeds its maxmimum size."); + } + + if(bucket_count > 0) { + bucket_count = round_up_to_power_of_two(bucket_count); + + m_buckets_data.resize(bucket_count); + m_buckets = m_buckets_data.data(), + m_mask = bucket_count - 1; + } + + this->max_load_factor(max_load_factor); + } + + ordered_hash(const ordered_hash& other): Hash(other), + KeyEqual(other), + m_buckets_data(other.m_buckets_data), + m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr(): + m_buckets_data.data()), + m_mask(other.m_mask), + m_values(other.m_values), + m_grow_on_next_insert(other.m_grow_on_next_insert), + m_max_load_factor(other.m_max_load_factor), + m_load_threshold(other.m_load_threshold) + { + } + + ordered_hash(ordered_hash&& other) noexcept(std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value) + : Hash(std::move(static_cast(other))), + KeyEqual(std::move(static_cast(other))), + m_buckets_data(std::move(other.m_buckets_data)), + m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr(): + m_buckets_data.data()), + m_mask(other.m_mask), + m_values(std::move(other.m_values)), + m_grow_on_next_insert(other.m_grow_on_next_insert), + m_max_load_factor(other.m_max_load_factor), + m_load_threshold(other.m_load_threshold) + { + other.m_buckets_data.clear(); + other.m_buckets = static_empty_bucket_ptr(); + other.m_mask = 0; + other.m_values.clear(); + other.m_grow_on_next_insert = false; + other.m_load_threshold = 0; + } + + ordered_hash& operator=(const ordered_hash& other) { + if(&other != this) { + Hash::operator=(other); + KeyEqual::operator=(other); + + m_buckets_data = other.m_buckets_data; + m_buckets = m_buckets_data.empty()?static_empty_bucket_ptr(): + m_buckets_data.data(); + + m_mask = other.m_mask; + m_values = other.m_values; + m_grow_on_next_insert = other.m_grow_on_next_insert; + m_max_load_factor = other.m_max_load_factor; + m_load_threshold = other.m_load_threshold; + } + + return *this; + } + + ordered_hash& operator=(ordered_hash&& other) { + other.swap(*this); + other.clear(); + + return *this; + } + + allocator_type get_allocator() const { + return m_values.get_allocator(); + } + + + /* + * Iterators + */ + iterator begin() noexcept { + return iterator(m_values.begin()); + } + + const_iterator begin() const noexcept { + return cbegin(); + } + + const_iterator cbegin() const noexcept { + return const_iterator(m_values.cbegin()); + } + + iterator end() noexcept { + return iterator(m_values.end()); + } + + const_iterator end() const noexcept { + return cend(); + } + + const_iterator cend() const noexcept { + return const_iterator(m_values.cend()); + } + + + reverse_iterator rbegin() noexcept { + return reverse_iterator(m_values.end()); + } + + const_reverse_iterator rbegin() const noexcept { + return rcbegin(); + } + + const_reverse_iterator rcbegin() const noexcept { + return const_reverse_iterator(m_values.cend()); + } + + reverse_iterator rend() noexcept { + return reverse_iterator(m_values.begin()); + } + + const_reverse_iterator rend() const noexcept { + return rcend(); + } + + const_reverse_iterator rcend() const noexcept { + return const_reverse_iterator(m_values.cbegin()); + } + + + /* + * Capacity + */ + bool empty() const noexcept { + return m_values.empty(); + } + + size_type size() const noexcept { + return m_values.size(); + } + + size_type max_size() const noexcept { + return std::min(bucket_entry::max_size(), m_values.max_size()); + } + + + /* + * Modifiers + */ + void clear() noexcept { + for(auto& bucket: m_buckets_data) { + bucket.clear(); + } + + m_values.clear(); + m_grow_on_next_insert = false; + } + + template + std::pair insert(P&& value) { + return insert_impl(KeySelect()(value), std::forward

(value)); + } + + template + iterator insert_hint(const_iterator hint, P&& value) { + if(hint != cend() && compare_keys(KeySelect()(*hint), KeySelect()(value))) { + return mutable_iterator(hint); + } + + return insert(std::forward

(value)).first; + } + + template + void insert(InputIt first, InputIt last) { + if(std::is_base_of::iterator_category>::value) + { + const auto nb_elements_insert = std::distance(first, last); + const size_type nb_free_buckets = m_load_threshold - size(); + tsl_oh_assert(m_load_threshold >= size()); + + if(nb_elements_insert > 0 && nb_free_buckets < size_type(nb_elements_insert)) { + reserve(size() + size_type(nb_elements_insert)); + } + } + + for(; first != last; ++first) { + insert(*first); + } + } + + + + template + std::pair insert_or_assign(K&& key, M&& value) { + auto it = try_emplace(std::forward(key), std::forward(value)); + if(!it.second) { + it.first.value() = std::forward(value); + } + + return it; + } + + template + iterator insert_or_assign(const_iterator hint, K&& key, M&& obj) { + if(hint != cend() && compare_keys(KeySelect()(*hint), key)) { + auto it = mutable_iterator(hint); + it.value() = std::forward(obj); + + return it; + } + + return insert_or_assign(std::forward(key), std::forward(obj)).first; + } + + + + template + std::pair emplace(Args&&... args) { + return insert(value_type(std::forward(args)...)); + } + + template + iterator emplace_hint(const_iterator hint, Args&&... args) { + return insert_hint(hint, value_type(std::forward(args)...)); + } + + + + template + std::pair try_emplace(K&& key, Args&&... value_args) { + return insert_impl(key, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(value_args)...)); + } + + template + iterator try_emplace_hint(const_iterator hint, K&& key, Args&&... args) { + if(hint != cend() && compare_keys(KeySelect()(*hint), key)) { + return mutable_iterator(hint); + } + + return try_emplace(std::forward(key), std::forward(args)...).first; + } + + + + /** + * Here to avoid `template size_type erase(const K& key)` being used when + * we use an `iterator` instead of a `const_iterator`. + */ + iterator erase(iterator pos) { + return erase(const_iterator(pos)); + } + + iterator erase(const_iterator pos) { + tsl_oh_assert(pos != cend()); + + const std::size_t index_erase = iterator_to_index(pos); + + auto it_bucket = find_key(pos.key(), hash_key(pos.key())); + tsl_oh_assert(it_bucket != m_buckets_data.end()); + + erase_value_from_bucket(it_bucket); + + /* + * One element was removed from m_values, due to the left shift the next element + * is now at the position of the previous element (or end if none). + */ + return begin() + index_erase; + } + + iterator erase(const_iterator first, const_iterator last) { + if(first == last) { + return mutable_iterator(first); + } + + tsl_oh_assert(std::distance(first, last) > 0); + const std::size_t start_index = iterator_to_index(first); + const std::size_t nb_values = std::size_t(std::distance(first, last)); + const std::size_t end_index = start_index + nb_values; + + // Delete all values +#ifdef TSL_OH_NO_CONTAINER_ERASE_CONST_ITERATOR + auto next_it = m_values.erase(mutable_iterator(first).m_iterator, mutable_iterator(last).m_iterator); +#else + auto next_it = m_values.erase(first.m_iterator, last.m_iterator); +#endif + + /* + * Mark the buckets corresponding to the values as empty and do a backward shift. + * + * Also, the erase operation on m_values has shifted all the values on the right of last.m_iterator. + * Adapt the indexes for these values. + */ + std::size_t ibucket = 0; + while(ibucket < m_buckets_data.size()) { + if(m_buckets[ibucket].empty()) { + ibucket++; + } + else if(m_buckets[ibucket].index() >= start_index && m_buckets[ibucket].index() < end_index) { + m_buckets[ibucket].clear(); + backward_shift(ibucket); + // Don't increment ibucket, backward_shift may have replaced current bucket. + } + else if(m_buckets[ibucket].index() >= end_index) { + m_buckets[ibucket].set_index(index_type(m_buckets[ibucket].index() - nb_values)); + ibucket++; + } + else { + ibucket++; + } + } + + return iterator(next_it); + } + + + template + size_type erase(const K& key) { + return erase(key, hash_key(key)); + } + + template + size_type erase(const K& key, std::size_t hash) { + return erase_impl(key, hash); + } + + void swap(ordered_hash& other) { + using std::swap; + + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), static_cast(other)); + swap(m_buckets_data, other.m_buckets_data); + swap(m_buckets, other.m_buckets); + swap(m_mask, other.m_mask); + swap(m_values, other.m_values); + swap(m_grow_on_next_insert, other.m_grow_on_next_insert); + swap(m_max_load_factor, other.m_max_load_factor); + swap(m_load_threshold, other.m_load_threshold); + } + + + + + /* + * Lookup + */ + template::value>::type* = nullptr> + typename U::value_type& at(const K& key) { + return at(key, hash_key(key)); + } + + template::value>::type* = nullptr> + typename U::value_type& at(const K& key, std::size_t hash) { + return const_cast(static_cast(this)->at(key, hash)); + } + + template::value>::type* = nullptr> + const typename U::value_type& at(const K& key) const { + return at(key, hash_key(key)); + } + + template::value>::type* = nullptr> + const typename U::value_type& at(const K& key, std::size_t hash) const { + auto it = find(key, hash); + if(it != end()) { + return it.value(); + } + else { + TSL_OH_THROW_OR_TERMINATE(std::out_of_range, "Couldn't find the key."); + } + } + + + template::value>::type* = nullptr> + typename U::value_type& operator[](K&& key) { + return try_emplace(std::forward(key)).first.value(); + } + + + template + size_type count(const K& key) const { + return count(key, hash_key(key)); + } + + template + size_type count(const K& key, std::size_t hash) const { + if(find(key, hash) == cend()) { + return 0; + } + else { + return 1; + } + } + + template + iterator find(const K& key) { + return find(key, hash_key(key)); + } + + template + iterator find(const K& key, std::size_t hash) { + auto it_bucket = find_key(key, hash); + return (it_bucket != m_buckets_data.end())?iterator(m_values.begin() + it_bucket->index()):end(); + } + + template + const_iterator find(const K& key) const { + return find(key, hash_key(key)); + } + + template + const_iterator find(const K& key, std::size_t hash) const { + auto it_bucket = find_key(key, hash); + return (it_bucket != m_buckets_data.cend())?const_iterator(m_values.begin() + it_bucket->index()):end(); + } + + + template + std::pair equal_range(const K& key) { + return equal_range(key, hash_key(key)); + } + + template + std::pair equal_range(const K& key, std::size_t hash) { + iterator it = find(key, hash); + return std::make_pair(it, (it == end())?it:std::next(it)); + } + + template + std::pair equal_range(const K& key) const { + return equal_range(key, hash_key(key)); + } + + template + std::pair equal_range(const K& key, std::size_t hash) const { + const_iterator it = find(key, hash); + return std::make_pair(it, (it == cend())?it:std::next(it)); + } + + + /* + * Bucket interface + */ + size_type bucket_count() const { + return m_buckets_data.size(); + } + + size_type max_bucket_count() const { + return m_buckets_data.max_size(); + } + + /* + * Hash policy + */ + float load_factor() const { + if(bucket_count() == 0) { + return 0; + } + + return float(size())/float(bucket_count()); + } + + float max_load_factor() const { + return m_max_load_factor; + } + + void max_load_factor(float ml) { + if(ml < MAX_LOAD_FACTOR__MINIMUM) { + ml = MAX_LOAD_FACTOR__MINIMUM; + } + else if(ml > MAX_LOAD_FACTOR__MAXIMUM) { + ml = MAX_LOAD_FACTOR__MAXIMUM; + } + + m_max_load_factor = ml; + m_load_threshold = size_type(float(bucket_count())*m_max_load_factor); + } + + void rehash(size_type count) { + count = std::max(count, size_type(std::ceil(float(size())/max_load_factor()))); + rehash_impl(count); + } + + void reserve(size_type count) { + reserve_space_for_values(count); + + count = size_type(std::ceil(float(count)/max_load_factor())); + rehash(count); + } + + + /* + * Observers + */ + hasher hash_function() const { + return static_cast(*this); + } + + key_equal key_eq() const { + return static_cast(*this); + } + + + /* + * Other + */ + iterator mutable_iterator(const_iterator pos) { + return iterator(m_values.begin() + iterator_to_index(pos)); + } + + iterator nth(size_type index) { + tsl_oh_assert(index <= size()); + return iterator(m_values.begin() + index); + } + + const_iterator nth(size_type index) const { + tsl_oh_assert(index <= size()); + return const_iterator(m_values.cbegin() + index); + } + + const_reference front() const { + tsl_oh_assert(!empty()); + return m_values.front(); + } + + const_reference back() const { + tsl_oh_assert(!empty()); + return m_values.back(); + } + + const values_container_type& values_container() const noexcept { + return m_values; + } + + template::value>::type* = nullptr> + const typename values_container_type::value_type* data() const noexcept { + return m_values.data(); + } + + template::value>::type* = nullptr> + size_type capacity() const noexcept { + return m_values.capacity(); + } + + void shrink_to_fit() { + m_values.shrink_to_fit(); + } + + + template + std::pair insert_at_position(const_iterator pos, P&& value) { + return insert_at_position_impl(pos.m_iterator, KeySelect()(value), std::forward

(value)); + } + + template + std::pair emplace_at_position(const_iterator pos, Args&&... args) { + return insert_at_position(pos, value_type(std::forward(args)...)); + } + + template + std::pair try_emplace_at_position(const_iterator pos, K&& key, Args&&... value_args) { + return insert_at_position_impl(pos.m_iterator, key, + std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(value_args)...)); + } + + + void pop_back() { + tsl_oh_assert(!empty()); + erase(std::prev(end())); + } + + + /** + * Here to avoid `template size_type unordered_erase(const K& key)` being used when + * we use a iterator instead of a const_iterator. + */ + iterator unordered_erase(iterator pos) { + return unordered_erase(const_iterator(pos)); + } + + iterator unordered_erase(const_iterator pos) { + const std::size_t index_erase = iterator_to_index(pos); + unordered_erase(pos.key()); + + /* + * One element was deleted, index_erase now points to the next element as the elements after + * the deleted value were shifted to the left in m_values (will be end() if we deleted the last element). + */ + return begin() + index_erase; + } + + template + size_type unordered_erase(const K& key) { + return unordered_erase(key, hash_key(key)); + } + + template + size_type unordered_erase(const K& key, std::size_t hash) { + auto it_bucket_key = find_key(key, hash); + if(it_bucket_key == m_buckets_data.end()) { + return 0; + } + + /** + * If we are not erasing the last element in m_values, we swap + * the element we are erasing with the last element. We then would + * just have to do a pop_back() in m_values. + */ + if(!compare_keys(key, KeySelect()(back()))) { + auto it_bucket_last_elem = find_key(KeySelect()(back()), hash_key(KeySelect()(back()))); + tsl_oh_assert(it_bucket_last_elem != m_buckets_data.end()); + tsl_oh_assert(it_bucket_last_elem->index() == m_values.size() - 1); + + using std::swap; + swap(m_values[it_bucket_key->index()], m_values[it_bucket_last_elem->index()]); + swap(it_bucket_key->index_ref(), it_bucket_last_elem->index_ref()); + } + + erase_value_from_bucket(it_bucket_key); + + return 1; + } + + template + void serialize(Serializer& serializer) const { + serialize_impl(serializer); + } + + template + void deserialize(Deserializer& deserializer, bool hash_compatible) { + deserialize_impl(deserializer, hash_compatible); + } + + friend bool operator==(const ordered_hash& lhs, const ordered_hash& rhs) { + return lhs.m_values == rhs.m_values; + } + + friend bool operator!=(const ordered_hash& lhs, const ordered_hash& rhs) { + return lhs.m_values != rhs.m_values; + } + + friend bool operator<(const ordered_hash& lhs, const ordered_hash& rhs) { + return lhs.m_values < rhs.m_values; + } + + friend bool operator<=(const ordered_hash& lhs, const ordered_hash& rhs) { + return lhs.m_values <= rhs.m_values; + } + + friend bool operator>(const ordered_hash& lhs, const ordered_hash& rhs) { + return lhs.m_values > rhs.m_values; + } + + friend bool operator>=(const ordered_hash& lhs, const ordered_hash& rhs) { + return lhs.m_values >= rhs.m_values; + } + + +private: + template + std::size_t hash_key(const K& key) const { + return Hash::operator()(key); + } + + template + bool compare_keys(const K1& key1, const K2& key2) const { + return KeyEqual::operator()(key1, key2); + } + + template + typename buckets_container_type::iterator find_key(const K& key, std::size_t hash) { + auto it = static_cast(this)->find_key(key, hash); + return m_buckets_data.begin() + std::distance(m_buckets_data.cbegin(), it); + } + + /** + * Return bucket which has the key 'key' or m_buckets_data.end() if none. + * + * From the bucket_for_hash, search for the value until we either find an empty bucket + * or a bucket which has a value with a distance from its ideal bucket longer + * than the probe length for the value we are looking for. + */ + template + typename buckets_container_type::const_iterator find_key(const K& key, std::size_t hash) const { + for(std::size_t ibucket = bucket_for_hash(hash), dist_from_ideal_bucket = 0; ; + ibucket = next_bucket(ibucket), dist_from_ideal_bucket++) + { + if(m_buckets[ibucket].empty()) { + return m_buckets_data.end(); + } + else if(m_buckets[ibucket].truncated_hash() == bucket_entry::truncate_hash(hash) && + compare_keys(key, KeySelect()(m_values[m_buckets[ibucket].index()]))) + { + return m_buckets_data.begin() + ibucket; + } + else if(dist_from_ideal_bucket > distance_from_ideal_bucket(ibucket)) { + return m_buckets_data.end(); + } + } + } + + void rehash_impl(size_type bucket_count) { + tsl_oh_assert(bucket_count >= size_type(std::ceil(float(size())/max_load_factor()))); + + if(bucket_count > max_bucket_count()) { + TSL_OH_THROW_OR_TERMINATE(std::length_error, "The map exceeds its maxmimum size."); + } + + if(bucket_count > 0) { + bucket_count = round_up_to_power_of_two(bucket_count); + } + + if(bucket_count == this->bucket_count()) { + return; + } + + + buckets_container_type old_buckets(bucket_count); + m_buckets_data.swap(old_buckets); + m_buckets = m_buckets_data.empty()?static_empty_bucket_ptr(): + m_buckets_data.data(); + // Everything should be noexcept from here. + + m_mask = (bucket_count > 0)?(bucket_count - 1):0; + this->max_load_factor(m_max_load_factor); + m_grow_on_next_insert = false; + + + + for(const bucket_entry& old_bucket: old_buckets) { + if(old_bucket.empty()) { + continue; + } + + truncated_hash_type insert_hash = old_bucket.truncated_hash(); + index_type insert_index = old_bucket.index(); + + for(std::size_t ibucket = bucket_for_hash(insert_hash), dist_from_ideal_bucket = 0; ; + ibucket = next_bucket(ibucket), dist_from_ideal_bucket++) + { + if(m_buckets[ibucket].empty()) { + m_buckets[ibucket].set_index(insert_index); + m_buckets[ibucket].set_hash(insert_hash); + break; + } + + const std::size_t distance = distance_from_ideal_bucket(ibucket); + if(dist_from_ideal_bucket > distance) { + std::swap(insert_index, m_buckets[ibucket].index_ref()); + std::swap(insert_hash, m_buckets[ibucket].truncated_hash_ref()); + dist_from_ideal_bucket = distance; + } + } + } + } + + template::value>::type* = nullptr> + void reserve_space_for_values(size_type count) { + m_values.reserve(count); + } + + template::value>::type* = nullptr> + void reserve_space_for_values(size_type /*count*/) { + } + + /** + * Swap the empty bucket with the values on its right until we cross another empty bucket + * or if the other bucket has a distance_from_ideal_bucket == 0. + */ + void backward_shift(std::size_t empty_ibucket) noexcept { + tsl_oh_assert(m_buckets[empty_ibucket].empty()); + + std::size_t previous_ibucket = empty_ibucket; + for(std::size_t current_ibucket = next_bucket(previous_ibucket); + !m_buckets[current_ibucket].empty() && distance_from_ideal_bucket(current_ibucket) > 0; + previous_ibucket = current_ibucket, current_ibucket = next_bucket(current_ibucket)) + { + std::swap(m_buckets[current_ibucket], m_buckets[previous_ibucket]); + } + } + + void erase_value_from_bucket(typename buckets_container_type::iterator it_bucket) { + tsl_oh_assert(it_bucket != m_buckets_data.end() && !it_bucket->empty()); + + m_values.erase(m_values.begin() + it_bucket->index()); + + /* + * m_values.erase shifted all the values on the right of the erased value, + * shift the indexes by -1 in the buckets array for these values. + */ + if(it_bucket->index() != m_values.size()) { + shift_indexes_in_buckets(it_bucket->index(), -1); + } + + // Mark the bucket as empty and do a backward shift of the values on the right + it_bucket->clear(); + backward_shift(std::size_t(std::distance(m_buckets_data.begin(), it_bucket))); + } + + /** + * Go through each value from [from_ivalue, m_values.size()) in m_values and for each + * bucket corresponding to the value, shift the index by delta. + * + * delta must be equal to 1 or -1. + */ + void shift_indexes_in_buckets(index_type from_ivalue, int delta) noexcept { + tsl_oh_assert(delta == 1 || delta == -1); + + for(std::size_t ivalue = from_ivalue; ivalue < m_values.size(); ivalue++) { + // All the values in m_values have been shifted by delta. Find the bucket corresponding + // to the value m_values[ivalue] + const index_type old_index = static_cast(ivalue - delta); + + std::size_t ibucket = bucket_for_hash(hash_key(KeySelect()(m_values[ivalue]))); + while(m_buckets[ibucket].index() != old_index) { + ibucket = next_bucket(ibucket); + } + + m_buckets[ibucket].set_index(index_type(ivalue)); + } + } + + template + size_type erase_impl(const K& key, std::size_t hash) { + auto it_bucket = find_key(key, hash); + if(it_bucket != m_buckets_data.end()) { + erase_value_from_bucket(it_bucket); + + return 1; + } + else { + return 0; + } + } + + /** + * Insert the element at the end. + */ + template + std::pair insert_impl(const K& key, Args&&... value_type_args) { + const std::size_t hash = hash_key(key); + + std::size_t ibucket = bucket_for_hash(hash); + std::size_t dist_from_ideal_bucket = 0; + + while(!m_buckets[ibucket].empty() && dist_from_ideal_bucket <= distance_from_ideal_bucket(ibucket)) { + if(m_buckets[ibucket].truncated_hash() == bucket_entry::truncate_hash(hash) && + compare_keys(key, KeySelect()(m_values[m_buckets[ibucket].index()]))) + { + return std::make_pair(begin() + m_buckets[ibucket].index(), false); + } + + ibucket = next_bucket(ibucket); + dist_from_ideal_bucket++; + } + + if(size() >= max_size()) { + TSL_OH_THROW_OR_TERMINATE(std::length_error, "We reached the maximum size for the hash table."); + } + + + if(grow_on_high_load()) { + ibucket = bucket_for_hash(hash); + dist_from_ideal_bucket = 0; + } + + + m_values.emplace_back(std::forward(value_type_args)...); + insert_index(ibucket, dist_from_ideal_bucket, + index_type(m_values.size() - 1), bucket_entry::truncate_hash(hash)); + + + return std::make_pair(std::prev(end()), true); + } + + /** + * Insert the element before insert_position. + */ + template + std::pair insert_at_position_impl(typename values_container_type::const_iterator insert_position, + const K& key, Args&&... value_type_args) + { + const std::size_t hash = hash_key(key); + + std::size_t ibucket = bucket_for_hash(hash); + std::size_t dist_from_ideal_bucket = 0; + + while(!m_buckets[ibucket].empty() && dist_from_ideal_bucket <= distance_from_ideal_bucket(ibucket)) { + if(m_buckets[ibucket].truncated_hash() == bucket_entry::truncate_hash(hash) && + compare_keys(key, KeySelect()(m_values[m_buckets[ibucket].index()]))) + { + return std::make_pair(begin() + m_buckets[ibucket].index(), false); + } + + ibucket = next_bucket(ibucket); + dist_from_ideal_bucket++; + } + + if(size() >= max_size()) { + TSL_OH_THROW_OR_TERMINATE(std::length_error, "We reached the maximum size for the hash table."); + } + + + if(grow_on_high_load()) { + ibucket = bucket_for_hash(hash); + dist_from_ideal_bucket = 0; + } + + + const index_type index_insert_position = index_type(std::distance(m_values.cbegin(), insert_position)); + +#ifdef TSL_OH_NO_CONTAINER_EMPLACE_CONST_ITERATOR + m_values.emplace(m_values.begin() + std::distance(m_values.cbegin(), insert_position), std::forward(value_type_args)...); +#else + m_values.emplace(insert_position, std::forward(value_type_args)...); +#endif + + insert_index(ibucket, dist_from_ideal_bucket, + index_insert_position, bucket_entry::truncate_hash(hash)); + + /* + * The insertion didn't happend at the end of the m_values container, + * we need to shift the indexes in m_buckets_data. + */ + if(index_insert_position != m_values.size() - 1) { + shift_indexes_in_buckets(index_insert_position + 1, 1); + } + + return std::make_pair(iterator(m_values.begin() + index_insert_position), true); + } + + void insert_index(std::size_t ibucket, std::size_t dist_from_ideal_bucket, + index_type index_insert, truncated_hash_type hash_insert) noexcept + { + while(!m_buckets[ibucket].empty()) { + const std::size_t distance = distance_from_ideal_bucket(ibucket); + if(dist_from_ideal_bucket > distance) { + std::swap(index_insert, m_buckets[ibucket].index_ref()); + std::swap(hash_insert, m_buckets[ibucket].truncated_hash_ref()); + + dist_from_ideal_bucket = distance; + } + + + ibucket = next_bucket(ibucket); + dist_from_ideal_bucket++; + + + if(dist_from_ideal_bucket > REHASH_ON_HIGH_NB_PROBES__NPROBES && !m_grow_on_next_insert && + load_factor() >= REHASH_ON_HIGH_NB_PROBES__MIN_LOAD_FACTOR) + { + // We don't want to grow the map now as we need this method to be noexcept. + // Do it on next insert. + m_grow_on_next_insert = true; + } + } + + + m_buckets[ibucket].set_index(index_insert); + m_buckets[ibucket].set_hash(hash_insert); + } + + std::size_t distance_from_ideal_bucket(std::size_t ibucket) const noexcept { + const std::size_t ideal_bucket = bucket_for_hash(m_buckets[ibucket].truncated_hash()); + + if(ibucket >= ideal_bucket) { + return ibucket - ideal_bucket; + } + // If the bucket is smaller than the ideal bucket for the value, there was a wrapping at the end of the + // bucket array due to the modulo. + else { + return (bucket_count() + ibucket) - ideal_bucket; + } + } + + std::size_t next_bucket(std::size_t index) const noexcept { + tsl_oh_assert(index < m_buckets_data.size()); + + index++; + return (index < m_buckets_data.size())?index:0; + } + + std::size_t bucket_for_hash(std::size_t hash) const noexcept { + return hash & m_mask; + } + + std::size_t iterator_to_index(const_iterator it) const noexcept { + const auto dist = std::distance(cbegin(), it); + tsl_oh_assert(dist >= 0); + + return std::size_t(dist); + } + + /** + * Return true if the map has been rehashed. + */ + bool grow_on_high_load() { + if(m_grow_on_next_insert || size() >= m_load_threshold) { + rehash_impl(std::max(size_type(1), bucket_count() * 2)); + m_grow_on_next_insert = false; + + return true; + } + else { + return false; + } + } + + template + void serialize_impl(Serializer& serializer) const { + const slz_size_type version = SERIALIZATION_PROTOCOL_VERSION; + serializer(version); + + const slz_size_type nb_elements = m_values.size(); + serializer(nb_elements); + + const slz_size_type bucket_count = m_buckets_data.size(); + serializer(bucket_count); + + const float max_load_factor = m_max_load_factor; + serializer(max_load_factor); + + + for(const value_type& value: m_values) { + serializer(value); + } + + for(const bucket_entry& bucket: m_buckets_data) { + bucket.serialize(serializer); + } + } + + template + void deserialize_impl(Deserializer& deserializer, bool hash_compatible) { + tsl_oh_assert(m_buckets_data.empty()); // Current hash table must be empty + + const slz_size_type version = deserialize_value(deserializer); + // For now we only have one version of the serialization protocol. + // If it doesn't match there is a problem with the file. + if(version != SERIALIZATION_PROTOCOL_VERSION) { + TSL_OH_THROW_OR_TERMINATE(std::runtime_error, "Can't deserialize the ordered_map/set. " + "The protocol version header is invalid."); + } + + const slz_size_type nb_elements = deserialize_value(deserializer); + const slz_size_type bucket_count_ds = deserialize_value(deserializer); + const float max_load_factor = deserialize_value(deserializer); + + if(max_load_factor < MAX_LOAD_FACTOR__MINIMUM || max_load_factor > MAX_LOAD_FACTOR__MAXIMUM) { + TSL_OH_THROW_OR_TERMINATE(std::runtime_error, "Invalid max_load_factor. Check that the serializer " + "and deserializer supports floats correctly as they " + "can be converted implicitly to ints."); + } + + + this->max_load_factor(max_load_factor); + + if(bucket_count_ds == 0) { + tsl_oh_assert(nb_elements == 0); + return; + } + + + if(!hash_compatible) { + reserve(numeric_cast(nb_elements, "Deserialized nb_elements is too big.")); + for(slz_size_type el = 0; el < nb_elements; el++) { + insert(deserialize_value(deserializer)); + } + } + else { + m_buckets_data.reserve(numeric_cast(bucket_count_ds, "Deserialized bucket_count is too big.")); + m_buckets = m_buckets_data.data(), + m_mask = m_buckets_data.capacity() - 1; + + reserve_space_for_values(numeric_cast(nb_elements, "Deserialized nb_elements is too big.")); + for(slz_size_type el = 0; el < nb_elements; el++) { + m_values.push_back(deserialize_value(deserializer)); + } + + for(slz_size_type b = 0; b < bucket_count_ds; b++) { + m_buckets_data.push_back(bucket_entry::deserialize(deserializer)); + } + } + } + + static std::size_t round_up_to_power_of_two(std::size_t value) { + if(is_power_of_two(value)) { + return value; + } + + if(value == 0) { + return 1; + } + + --value; + for(std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { + value |= value >> i; + } + + return value + 1; + } + + static constexpr bool is_power_of_two(std::size_t value) { + return value != 0 && (value & (value - 1)) == 0; + } + + +public: + static const size_type DEFAULT_INIT_BUCKETS_SIZE = 0; + static constexpr float DEFAULT_MAX_LOAD_FACTOR = 0.75f; + +private: + static constexpr float MAX_LOAD_FACTOR__MINIMUM = 0.1f; + static constexpr float MAX_LOAD_FACTOR__MAXIMUM = 0.95f; + + static const size_type REHASH_ON_HIGH_NB_PROBES__NPROBES = 128; + static constexpr float REHASH_ON_HIGH_NB_PROBES__MIN_LOAD_FACTOR = 0.15f; + + /** + * Protocol version currenlty used for serialization. + */ + static const slz_size_type SERIALIZATION_PROTOCOL_VERSION = 1; + + /** + * Return an always valid pointer to an static empty bucket_entry with last_bucket() == true. + */ + bucket_entry* static_empty_bucket_ptr() { + static bucket_entry empty_bucket; + return &empty_bucket; + } + +private: + buckets_container_type m_buckets_data; + + /** + * Points to m_buckets_data.data() if !m_buckets_data.empty() otherwise points to static_empty_bucket_ptr. + * This variable is useful to avoid the cost of checking if m_buckets_data is empty when trying + * to find an element. + * + * TODO Remove m_buckets_data and only use a pointer+size instead of a pointer+vector to save some space in the ordered_hash object. + */ + bucket_entry* m_buckets; + + size_type m_mask; + + values_container_type m_values; + + bool m_grow_on_next_insert; + float m_max_load_factor; + size_type m_load_threshold; +}; + + +} // end namespace detail_ordered_hash + + + +/** + * Implementation of an hash map using open adressing with robin hood with backshift delete to resolve collisions. + * + * The particularity of this hash map is that it remembers the order in which the elements were added and + * provide a way to access the structure which stores these values through the 'values_container()' method. + * The used container is defined by ValueTypeContainer, by default a std::deque is used (grows faster) but + * a std::vector may be used. In this case the map provides a 'data()' method which give a direct access + * to the memory used to store the values (which can be usefull to communicate with C API's). + * + * The Key and T must be copy constructible and/or move constructible. To use `unordered_erase` they both + * must be swappable. + * + * The behaviour of the hash map is undefinded if the destructor of Key or T throws an exception. + * + * By default the maximum size of a map is limited to 2^32 - 1 values, if needed this can be changed through + * the IndexType template parameter. Using an `uint64_t` will raise this limit to 2^64 - 1 values but each + * bucket will use 16 bytes instead of 8 bytes in addition to the space needed to store the values. + * + * Iterators invalidation: + * - clear, operator=, reserve, rehash: always invalidate the iterators (also invalidate end()). + * - insert, emplace, emplace_hint, operator[]: when a std::vector is used as ValueTypeContainer + * and if size() < capacity(), only end(). + * Otherwise all the iterators are invalidated if an insert occurs. + * - erase, unordered_erase: when a std::vector is used as ValueTypeContainer invalidate the iterator of + * the erased element and all the ones after the erased element (including end()). + * Otherwise all the iterators are invalidated if an erase occurs. + */ +template, + class KeyEqual = std::equal_to, + class Allocator = std::allocator>, + class ValueTypeContainer = std::deque, Allocator>, + class IndexType = std::uint_least32_t> +class ordered_map { +private: + template + using has_is_transparent = tsl::detail_ordered_hash::has_is_transparent; + + class KeySelect { + public: + using key_type = Key; + + const key_type& operator()(const std::pair& key_value) const noexcept { + return key_value.first; + } + + key_type& operator()(std::pair& key_value) noexcept { + return key_value.first; + } + }; + + class ValueSelect { + public: + using value_type = T; + + const value_type& operator()(const std::pair& key_value) const noexcept { + return key_value.second; + } + + value_type& operator()(std::pair& key_value) noexcept { + return key_value.second; + } + }; + + using ht = detail_ordered_hash::ordered_hash, KeySelect, ValueSelect, + Hash, KeyEqual, Allocator, ValueTypeContainer, IndexType>; + +public: + using key_type = typename ht::key_type; + using mapped_type = T; + using value_type = typename ht::value_type; + using size_type = typename ht::size_type; + using difference_type = typename ht::difference_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using allocator_type = typename ht::allocator_type; + using reference = typename ht::reference; + using const_reference = typename ht::const_reference; + using pointer = typename ht::pointer; + using const_pointer = typename ht::const_pointer; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + using reverse_iterator = typename ht::reverse_iterator; + using const_reverse_iterator = typename ht::const_reverse_iterator; + + using values_container_type = typename ht::values_container_type; + + + /* + * Constructors + */ + ordered_map(): ordered_map(ht::DEFAULT_INIT_BUCKETS_SIZE) { + } + + explicit ordered_map(size_type bucket_count, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()): + m_ht(bucket_count, hash, equal, alloc, ht::DEFAULT_MAX_LOAD_FACTOR) + { + } + + ordered_map(size_type bucket_count, + const Allocator& alloc): ordered_map(bucket_count, Hash(), KeyEqual(), alloc) + { + } + + ordered_map(size_type bucket_count, + const Hash& hash, + const Allocator& alloc): ordered_map(bucket_count, hash, KeyEqual(), alloc) + { + } + + explicit ordered_map(const Allocator& alloc): ordered_map(ht::DEFAULT_INIT_BUCKETS_SIZE, alloc) { + } + + template + ordered_map(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()): ordered_map(bucket_count, hash, equal, alloc) + { + insert(first, last); + } + + template + ordered_map(InputIt first, InputIt last, + size_type bucket_count, + const Allocator& alloc): ordered_map(first, last, bucket_count, Hash(), KeyEqual(), alloc) + { + } + + template + ordered_map(InputIt first, InputIt last, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc): ordered_map(first, last, bucket_count, hash, KeyEqual(), alloc) + { + } + + ordered_map(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKETS_SIZE, + const Hash& hash = Hash(), + const KeyEqual& equal = KeyEqual(), + const Allocator& alloc = Allocator()): + ordered_map(init.begin(), init.end(), bucket_count, hash, equal, alloc) + { + } + + ordered_map(std::initializer_list init, + size_type bucket_count, + const Allocator& alloc): + ordered_map(init.begin(), init.end(), bucket_count, Hash(), KeyEqual(), alloc) + { + } + + ordered_map(std::initializer_list init, + size_type bucket_count, + const Hash& hash, + const Allocator& alloc): + ordered_map(init.begin(), init.end(), bucket_count, hash, KeyEqual(), alloc) + { + } + + + ordered_map& operator=(std::initializer_list ilist) { + m_ht.clear(); + + m_ht.reserve(ilist.size()); + m_ht.insert(ilist.begin(), ilist.end()); + + return *this; + } + + allocator_type get_allocator() const { return m_ht.get_allocator(); } + + + + /* + * Iterators + */ + iterator begin() noexcept { return m_ht.begin(); } + const_iterator begin() const noexcept { return m_ht.begin(); } + const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + + iterator end() noexcept { return m_ht.end(); } + const_iterator end() const noexcept { return m_ht.end(); } + const_iterator cend() const noexcept { return m_ht.cend(); } + + reverse_iterator rbegin() noexcept { return m_ht.rbegin(); } + const_reverse_iterator rbegin() const noexcept { return m_ht.rbegin(); } + const_reverse_iterator rcbegin() const noexcept { return m_ht.rcbegin(); } + + reverse_iterator rend() noexcept { return m_ht.rend(); } + const_reverse_iterator rend() const noexcept { return m_ht.rend(); } + const_reverse_iterator rcend() const noexcept { return m_ht.rcend(); } + + + /* + * Capacity + */ + bool empty() const noexcept { return m_ht.empty(); } + size_type size() const noexcept { return m_ht.size(); } + size_type max_size() const noexcept { return m_ht.max_size(); } + + /* + * Modifiers + */ + void clear() noexcept { m_ht.clear(); } + + + + std::pair insert(const value_type& value) { return m_ht.insert(value); } + + template::value>::type* = nullptr> + std::pair insert(P&& value) { return m_ht.emplace(std::forward

(value)); } + + std::pair insert(value_type&& value) { return m_ht.insert(std::move(value)); } + + + iterator insert(const_iterator hint, const value_type& value) { + return m_ht.insert_hint(hint, value); + } + + template::value>::type* = nullptr> + iterator insert(const_iterator hint, P&& value) { + return m_ht.emplace_hint(hint, std::forward

(value)); + } + + iterator insert(const_iterator hint, value_type&& value) { + return m_ht.insert_hint(hint, std::move(value)); + } + + + template + void insert(InputIt first, InputIt last) { m_ht.insert(first, last); } + void insert(std::initializer_list ilist) { m_ht.insert(ilist.begin(), ilist.end()); } + + + + + template + std::pair insert_or_assign(const key_type& k, M&& obj) { + return m_ht.insert_or_assign(k, std::forward(obj)); + } + + template + std::pair insert_or_assign(key_type&& k, M&& obj) { + return m_ht.insert_or_assign(std::move(k), std::forward(obj)); + } + + + template + iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj) { + return m_ht.insert_or_assign(hint, k, std::forward(obj)); + } + + template + iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj) { + return m_ht.insert_or_assign(hint, std::move(k), std::forward(obj)); + } + + /** + * Due to the way elements are stored, emplace will need to move or copy the key-value once. + * The method is equivalent to insert(value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + std::pair emplace(Args&&... args) { return m_ht.emplace(std::forward(args)...); } + + /** + * Due to the way elements are stored, emplace_hint will need to move or copy the key-value once. + * The method is equivalent to insert(hint, value_type(std::forward(args)...)); + * + * Mainly here for compatibility with the std::unordered_map interface. + */ + template + iterator emplace_hint(const_iterator hint, Args&&... args) { + return m_ht.emplace_hint(hint, std::forward(args)...); + } + + + + + template + std::pair try_emplace(const key_type& k, Args&&... args) { + return m_ht.try_emplace(k, std::forward(args)...); + } + + template + std::pair try_emplace(key_type&& k, Args&&... args) { + return m_ht.try_emplace(std::move(k), std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args) { + return m_ht.try_emplace_hint(hint, k, std::forward(args)...); + } + + template + iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args) { + return m_ht.try_emplace_hint(hint, std::move(k), std::forward(args)...); + } + + + + + /** + * When erasing an element, the insert order will be preserved and no holes will be present in the container + * returned by 'values_container()'. + * + * The method is in O(n), if the order is not important 'unordered_erase(...)' method is faster with an O(1) + * average complexity. + */ + iterator erase(iterator pos) { return m_ht.erase(pos); } + + /** + * @copydoc erase(iterator pos) + */ + iterator erase(const_iterator pos) { return m_ht.erase(pos); } + + /** + * @copydoc erase(iterator pos) + */ + iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } + + /** + * @copydoc erase(iterator pos) + */ + size_type erase(const key_type& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(iterator pos) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup to the value if you already have the hash. + */ + size_type erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + /** + * @copydoc erase(iterator pos) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type erase(const K& key) { return m_ht.erase(key); } + + /** + * @copydoc erase(const key_type& key, std::size_t precalculated_hash) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type erase(const K& key, std::size_t precalculated_hash) { + return m_ht.erase(key, precalculated_hash); + } + + + + void swap(ordered_map& other) { other.m_ht.swap(m_ht); } + + /* + * Lookup + */ + T& at(const Key& key) { return m_ht.at(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + T& at(const Key& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } + + + const T& at(const Key& key) const { return m_ht.at(key); } + + /** + * @copydoc at(const Key& key, std::size_t precalculated_hash) + */ + const T& at(const Key& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } + + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + T& at(const K& key) { return m_ht.at(key); } + + /** + * @copydoc at(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + T& at(const K& key, std::size_t precalculated_hash) { return m_ht.at(key, precalculated_hash); } + + /** + * @copydoc at(const K& key) + */ + template::value>::type* = nullptr> + const T& at(const K& key) const { return m_ht.at(key); } + + /** + * @copydoc at(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + const T& at(const K& key, std::size_t precalculated_hash) const { return m_ht.at(key, precalculated_hash); } + + + + T& operator[](const Key& key) { return m_ht[key]; } + T& operator[](Key&& key) { return m_ht[std::move(key)]; } + + + + size_type count(const Key& key) const { return m_ht.count(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type count(const Key& key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type count(const K& key) const { return m_ht.count(key); } + + /** + * @copydoc count(const K& key) const + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + size_type count(const K& key, std::size_t precalculated_hash) const { + return m_ht.count(key, precalculated_hash); + } + + + + iterator find(const Key& key) { return m_ht.find(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + iterator find(const Key& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + const_iterator find(const Key& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const Key& key, std::size_t precalculated_hash) + */ + const_iterator find(const Key& key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + iterator find(const K& key) { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + iterator find(const K& key, std::size_t precalculated_hash) { return m_ht.find(key, precalculated_hash); } + + /** + * @copydoc find(const K& key) + */ + template::value>::type* = nullptr> + const_iterator find(const K& key) const { return m_ht.find(key); } + + /** + * @copydoc find(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template + const_iterator find(const K& key, std::size_t precalculated_hash) const { + return m_ht.find(key, precalculated_hash); + } + + bool contains(const Key& key) const {return find(key) != this->end();}; + + std::pair equal_range(const Key& key) { return m_ht.equal_range(key); } + + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + std::pair equal_range(const Key& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const Key& key, std::size_t precalculated_hash) + */ + std::pair equal_range(const Key& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, precalculated_hash); + } + + /** + * @copydoc equal_range(const K& key) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key) const { return m_ht.equal_range(key); } + + /** + * @copydoc equal_range(const K& key, std::size_t precalculated_hash) + */ + template::value>::type* = nullptr> + std::pair equal_range(const K& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, precalculated_hash); + } + + + + /* + * Bucket interface + */ + size_type bucket_count() const { return m_ht.bucket_count(); } + size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + + + /* + * Hash policy + */ + float load_factor() const { return m_ht.load_factor(); } + float max_load_factor() const { return m_ht.max_load_factor(); } + void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + + void rehash(size_type count) { m_ht.rehash(count); } + void reserve(size_type count) { m_ht.reserve(count); } + + + /* + * Observers + */ + hasher hash_function() const { return m_ht.hash_function(); } + key_equal key_eq() const { return m_ht.key_eq(); } + + + + /* + * Other + */ + + /** + * Convert a const_iterator to an iterator. + */ + iterator mutable_iterator(const_iterator pos) { + return m_ht.mutable_iterator(pos); + } + + /** + * Requires index <= size(). + * + * Return an iterator to the element at index. Return end() if index == size(). + */ + iterator nth(size_type index) { return m_ht.nth(index); } + + /** + * @copydoc nth(size_type index) + */ + const_iterator nth(size_type index) const { return m_ht.nth(index); } + + + /** + * Return const_reference to the first element. Requires the container to not be empty. + */ + const_reference front() const { return m_ht.front(); } + + /** + * Return const_reference to the last element. Requires the container to not be empty. + */ + const_reference back() const { return m_ht.back(); } + + + /** + * Only available if ValueTypeContainer is a std::vector. Same as calling 'values_container().data()'. + */ + template::value>::type* = nullptr> + const typename values_container_type::value_type* data() const noexcept { return m_ht.data(); } + + /** + * Return the container in which the values are stored. The values are in the same order as the insertion order + * and are contiguous in the structure, no holes (size() == values_container().size()). + */ + const values_container_type& values_container() const noexcept { return m_ht.values_container(); } + + template::value>::type* = nullptr> + size_type capacity() const noexcept { return m_ht.capacity(); } + + void shrink_to_fit() { m_ht.shrink_to_fit(); } + + + + /** + * Insert the value before pos shifting all the elements on the right of pos (including pos) one position + * to the right. + * + * Amortized linear time-complexity in the distance between pos and end(). + */ + std::pair insert_at_position(const_iterator pos, const value_type& value) { + return m_ht.insert_at_position(pos, value); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + */ + std::pair insert_at_position(const_iterator pos, value_type&& value) { + return m_ht.insert_at_position(pos, std::move(value)); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + * + * Same as insert_at_position(pos, value_type(std::forward(args)...), mainly + * here for coherence. + */ + template + std::pair emplace_at_position(const_iterator pos, Args&&... args) { + return m_ht.emplace_at_position(pos, std::forward(args)...); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + */ + template + std::pair try_emplace_at_position(const_iterator pos, const key_type& k, Args&&... args) { + return m_ht.try_emplace_at_position(pos, k, std::forward(args)...); + } + + /** + * @copydoc insert_at_position(const_iterator pos, const value_type& value) + */ + template + std::pair try_emplace_at_position(const_iterator pos, key_type&& k, Args&&... args) { + return m_ht.try_emplace_at_position(pos, std::move(k), std::forward(args)...); + } + + + + void pop_back() { m_ht.pop_back(); } + + /** + * Faster erase operation with an O(1) average complexity but it doesn't preserve the insertion order. + * + * If an erasure occurs, the last element of the map will take the place of the erased element. + */ + iterator unordered_erase(iterator pos) { return m_ht.unordered_erase(pos); } + + /** + * @copydoc unordered_erase(iterator pos) + */ + iterator unordered_erase(const_iterator pos) { return m_ht.unordered_erase(pos); } + + /** + * @copydoc unordered_erase(iterator pos) + */ + size_type unordered_erase(const key_type& key) { return m_ht.unordered_erase(key); } + + /** + * @copydoc unordered_erase(iterator pos) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + size_type unordered_erase(const key_type& key, std::size_t precalculated_hash) { + return m_ht.unordered_erase(key, precalculated_hash); + } + + /** + * @copydoc unordered_erase(iterator pos) + * + * This overload only participates in the overload resolution if the typedef KeyEqual::is_transparent exists. + * If so, K must be hashable and comparable to Key. + */ + template::value>::type* = nullptr> + size_type unordered_erase(const K& key) { return m_ht.unordered_erase(key); } + + /** + * @copydoc unordered_erase(const K& key) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Usefull to speed-up the lookup if you already have the hash. + */ + template::value>::type* = nullptr> + size_type unordered_erase(const K& key, std::size_t precalculated_hash) { + return m_ht.unordered_erase(key, precalculated_hash); + } + + /** + * Serialize the map through the `serializer` parameter. + * + * The `serializer` parameter must be a function object that supports the following call: + * - `template void operator()(const U& value);` where the types `std::uint64_t`, `float` and `std::pair` must be supported for U. + * + * The implementation leaves binary compatibilty (endianness, IEEE 754 for floats, ...) of the types it serializes + * in the hands of the `Serializer` function object if compatibilty is required. + */ + template + void serialize(Serializer& serializer) const { + m_ht.serialize(serializer); + } + + /** + * Deserialize a previouly serialized map through the `deserializer` parameter. + * + * The `deserializer` parameter must be a function object that supports the following calls: + * - `template U operator()();` where the types `std::uint64_t`, `float` and `std::pair` must be supported for U. + * + * If the deserialized hash map type is hash compatible with the serialized map, the deserialization process can be + * sped up by setting `hash_compatible` to true. To be hash compatible, the Hash and KeyEqual must behave the same way + * than the ones used on the serialized map. The `std::size_t` must also be of the same size as the one on the platform used + * to serialize the map, the same apply for `IndexType`. If these criteria are not met, the behaviour is undefined with + * `hash_compatible` sets to true. + * + * The behaviour is undefined if the type `Key` and `T` of the `ordered_map` are not the same as the + * types used during serialization. + * + * The implementation leaves binary compatibilty (endianness, IEEE 754 for floats, size of int, ...) of the types it + * deserializes in the hands of the `Deserializer` function object if compatibilty is required. + */ + template + static ordered_map deserialize(Deserializer& deserializer, bool hash_compatible = false) { + ordered_map map(0); + map.m_ht.deserialize(deserializer, hash_compatible); + + return map; + } + + + + friend bool operator==(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht == rhs.m_ht; } + friend bool operator!=(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht != rhs.m_ht; } + friend bool operator<(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht < rhs.m_ht; } + friend bool operator<=(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht <= rhs.m_ht; } + friend bool operator>(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht > rhs.m_ht; } + friend bool operator>=(const ordered_map& lhs, const ordered_map& rhs) { return lhs.m_ht >= rhs.m_ht; } + + friend void swap(ordered_map& lhs, ordered_map& rhs) { lhs.swap(rhs); } + +private: + ht m_ht; +}; + +} // end namespace tsl + +#endif diff --git a/include/project.h b/include/project.h index 4948b7e0..e3e3b21c 100644 --- a/include/project.h +++ b/include/project.h @@ -7,6 +7,7 @@ #include "event.h" #include "wildmoninfo.h" #include "parseutil.h" +#include "orderedjson.h" #include #include @@ -93,7 +94,7 @@ public: QMap> wildMonData; QVector wildMonFields; QVector encounterGroupLabels; - QMap extraEncounterGroups; + QVector extraEncounterGroups; bool readSpeciesIconPaths(); QMap speciesToIconPath; diff --git a/porymap.pro b/porymap.pro index fefdf4e3..0bfb5550 100644 --- a/porymap.pro +++ b/porymap.pro @@ -30,7 +30,7 @@ SOURCES += src/core/block.cpp \ src/core/tileset.cpp \ src/core/regionmap.cpp \ src/core/wildmoninfo.cpp \ - src/core/orderedjson.cpp \ + src/lib/orderedjson.cpp \ src/ui/aboutporymap.cpp \ src/ui/bordermetatilespixmapitem.cpp \ src/ui/collisionpixmapitem.cpp \ @@ -92,7 +92,8 @@ HEADERS += include/core/block.h \ include/core/tileset.h \ include/core/regionmap.h \ include/core/wildmoninfo.h \ - include/core/orderedjson.h \ + include/lib/orderedmap.h \ + include/lib/orderedjson.h \ include/ui/aboutporymap.h \ include/ui/bordermetatilespixmapitem.h \ include/ui/collisionpixmapitem.h \ @@ -152,3 +153,4 @@ RESOURCES += \ INCLUDEPATH += include INCLUDEPATH += include/core INCLUDEPATH += include/ui +INCLUDEPATH += include/lib diff --git a/src/core/event.cpp b/src/core/event.cpp index 1015cc5e..b90fb773 100644 --- a/src/core/event.cpp +++ b/src/core/event.cpp @@ -239,7 +239,7 @@ void Event::readCustomValues(QJsonObject values) } } -void Event::addCustomValuesTo(QJsonObject *obj) +void Event::addCustomValuesTo(OrderedJson::object *obj) { for (QString key : this->customValues.keys()) { if (!obj->contains(key)) { @@ -248,9 +248,9 @@ void Event::addCustomValuesTo(QJsonObject *obj) } } -QJsonObject Event::buildObjectEventJSON() +OrderedJson::object Event::buildObjectEventJSON() { - QJsonObject eventObj; + OrderedJson::object eventObj; eventObj["graphics_id"] = this->get("sprite"); eventObj["x"] = this->getU16("x"); eventObj["y"] = this->getU16("y"); @@ -267,9 +267,9 @@ QJsonObject Event::buildObjectEventJSON() return eventObj; } -QJsonObject Event::buildWarpEventJSON(QMap *mapNamesToMapConstants) +OrderedJson::object Event::buildWarpEventJSON(QMap *mapNamesToMapConstants) { - QJsonObject warpObj; + OrderedJson::object warpObj; warpObj["x"] = this->getU16("x"); warpObj["y"] = this->getU16("y"); warpObj["elevation"] = this->getInt("elevation"); @@ -280,9 +280,9 @@ QJsonObject Event::buildWarpEventJSON(QMap *mapNamesToMapConst return warpObj; } -QJsonObject Event::buildTriggerEventJSON() +OrderedJson::object Event::buildTriggerEventJSON() { - QJsonObject triggerObj; + OrderedJson::object triggerObj; triggerObj["type"] = "trigger"; triggerObj["x"] = this->getU16("x"); triggerObj["y"] = this->getU16("y"); @@ -295,9 +295,9 @@ QJsonObject Event::buildTriggerEventJSON() return triggerObj; } -QJsonObject Event::buildWeatherTriggerEventJSON() +OrderedJson::object Event::buildWeatherTriggerEventJSON() { - QJsonObject weatherObj; + OrderedJson::object weatherObj; weatherObj["type"] = "weather"; weatherObj["x"] = this->getU16("x"); weatherObj["y"] = this->getU16("y"); @@ -308,9 +308,9 @@ QJsonObject Event::buildWeatherTriggerEventJSON() return weatherObj; } -QJsonObject Event::buildSignEventJSON() +OrderedJson::object Event::buildSignEventJSON() { - QJsonObject signObj; + OrderedJson::object signObj; signObj["type"] = "sign"; signObj["x"] = this->getU16("x"); signObj["y"] = this->getU16("y"); @@ -322,9 +322,9 @@ QJsonObject Event::buildSignEventJSON() return signObj; } -QJsonObject Event::buildHiddenItemEventJSON() +OrderedJson::object Event::buildHiddenItemEventJSON() { - QJsonObject hiddenItemObj; + OrderedJson::object hiddenItemObj; hiddenItemObj["type"] = "hidden_item"; hiddenItemObj["x"] = this->getU16("x"); hiddenItemObj["y"] = this->getU16("y"); @@ -336,9 +336,9 @@ QJsonObject Event::buildHiddenItemEventJSON() return hiddenItemObj; } -QJsonObject Event::buildSecretBaseEventJSON() +OrderedJson::object Event::buildSecretBaseEventJSON() { - QJsonObject secretBaseObj; + OrderedJson::object secretBaseObj; secretBaseObj["type"] = "secret_base"; secretBaseObj["x"] = this->getU16("x"); secretBaseObj["y"] = this->getU16("y"); diff --git a/src/core/orderedjson.cpp b/src/lib/orderedjson.cpp similarity index 94% rename from src/core/orderedjson.cpp rename to src/lib/orderedjson.cpp index 73e85121..699c8d12 100644 --- a/src/core/orderedjson.cpp +++ b/src/lib/orderedjson.cpp @@ -26,8 +26,6 @@ #include #include -#include - namespace poryjson { static const int max_depth = 200; @@ -50,11 +48,13 @@ struct NullStruct { * Serialization */ -static void dump(NullStruct, QString &out, int *) { +static void dump(NullStruct, QString &out, int *indent) { + if (!out.endsWith(": ")) out += QString(*indent * 2, ' '); out += "null"; } -static void dump(double value, QString &out, int *) { +static void dump(double value, QString &out, int *indent) { + if (!out.endsWith(": ")) out += QString(*indent * 2, ' '); if (std::isfinite(value)) { char buf[32]; snprintf(buf, sizeof buf, "%.17g", value); @@ -64,17 +64,20 @@ static void dump(double value, QString &out, int *) { } } -static void dump(int value, QString &out, int *) { +static void dump(int value, QString &out, int *indent) { + if (!out.endsWith(": ")) out += QString(*indent * 2, ' '); char buf[32]; snprintf(buf, sizeof buf, "%d", value); out += buf; } -static void dump(bool value, QString &out, int *) { +static void dump(bool value, QString &out, int *indent) { + if (!out.endsWith(": ")) out += QString(*indent * 2, ' '); out += value ? "true" : "false"; } -static void dump(const QString &value, QString &out, int *) { +static void dump(const QString &value, QString &out, int *indent, bool isKey = false) { + if (!isKey && !out.endsWith(": ")) out += QString(*indent * 2, ' '); out += '"'; for (int i = 0; i < value.length(); i++) { const char ch = value[i].unicode(); @@ -132,12 +135,12 @@ static void dump(const Json::object &values, QString &out, int *indent) { if (!out.endsWith(": ")) out += QString(*indent * 2, ' '); out += "{\n"; *indent += 1; - for (const auto &kv : values) { + for (auto kv : values) { if (!first) { out += ",\n"; } out += QString(*indent * 2, ' '); - dump(kv.first, out, indent); + dump(kv.first, out, indent, true); out += ": "; kv.second.dump(out, indent); first = false; @@ -241,7 +244,7 @@ struct Statics { const std::shared_ptr f = make_shared(false); const QString empty_string; const QVector empty_vector; - const map empty_map; + const Json::object empty_map; Statics() {} }; @@ -283,7 +286,7 @@ int Json::int_value() const { return m_ptr->int_valu bool Json::bool_value() const { return m_ptr->bool_value(); } const QString & Json::string_value() const { return m_ptr->string_value(); } const QVector & Json::array_items() const { return m_ptr->array_items(); } -const map & Json::object_items() const { return m_ptr->object_items(); } +const Json::object & Json::object_items() const { return m_ptr->object_items(); } const Json & Json::operator[] (int i) const { return (*m_ptr)[i]; } const Json & Json::operator[] (const QString &key) const { return (*m_ptr)[key]; } @@ -292,13 +295,13 @@ int JsonValue::int_value() const { return bool JsonValue::bool_value() const { return false; } const QString & JsonValue::string_value() const { return statics().empty_string; } const QVector & JsonValue::array_items() const { return statics().empty_vector; } -const map & JsonValue::object_items() const { return statics().empty_map; } +const Json::object & JsonValue::object_items() const { return statics().empty_map; } const Json & JsonValue::operator[] (int) const { return static_null(); } const Json & JsonValue::operator[] (const QString &) const { return static_null(); } const Json & JsonObject::operator[] (const QString &key) const { - auto iter = m_value.find(key); - return (iter == m_value.end()) ? static_null() : iter->second; + static auto iter = m_value.find(key); + return (iter == m_value.end()) ? static_null() : (*iter).second; } const Json & JsonArray::operator[] (int i) const { if (i >= m_value.size()) return static_null(); @@ -385,7 +388,7 @@ struct JsonParser final { * Advance until the current character is non-whitespace. */ void consume_whitespace() { - while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') + while (i < str.length() && (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t')) i++; } @@ -639,7 +642,8 @@ struct JsonParser final { Json expect(const QString &expected, Json res) { assert(i != 0); i--; - if (str == expected) { + QString result = str.right(str.size() - i).left(expected.length()); + if (result == expected) { i += expected.length(); return res; } else { @@ -678,7 +682,7 @@ struct JsonParser final { return parse_string(); if (ch == '{') { - map data; + Json::object data; ch = get_next_token(); if (ch == '}') return data; @@ -753,26 +757,4 @@ Json Json::parse(const QString &in, QString &err, JsonParse strategy) { return result; } -/* * * * * * * * * * * * * * * * * * * * - * Shape-checking - */ - -bool Json::has_shape(const shape & types, QString & err) const { - if (!is_object()) { - err = "expected JSON object, got " + dump(); - return false; - } - - const auto& obj_items = object_items(); - for (auto & item : types) { - const auto it = obj_items.find(item.first); - if (it == obj_items.cend() || it->second.type() != item.second) { - err = "bad type for " + item.first + " in " + dump(); - return false; - } - } - - return true; -} - } // namespace poryjson diff --git a/src/project.cpp b/src/project.cpp index 06bcd001..4033faf0 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -9,6 +9,8 @@ #include "tileset.h" #include "imageexport.h" +#include "orderedjson.h" + #include #include #include @@ -21,6 +23,9 @@ #include #include +using OrderedJson = poryjson::Json; +using OrderedJsonDoc = poryjson::JsonDoc; + int Project::num_tiles_primary = 512; int Project::num_tiles_total = 1024; int Project::num_metatiles_primary = 512; @@ -513,13 +518,13 @@ void Project::saveMapLayouts() { return; } - QJsonObject layoutsObj; + OrderedJson::object layoutsObj; layoutsObj["layouts_table_label"] = layoutsLabel; - QJsonArray layoutsArr; + OrderedJson::array layoutsArr; for (QString layoutId : mapLayoutsTableMaster) { MapLayout *layout = mapLayouts.value(layoutId); - QJsonObject layoutObj; + OrderedJson::object layoutObj; layoutObj["id"] = layout->id; layoutObj["name"] = layout->name; layoutObj["width"] = layout->width.toInt(nullptr, 0); @@ -528,12 +533,14 @@ void Project::saveMapLayouts() { layoutObj["secondary_tileset"] = layout->tileset_secondary_label; layoutObj["border_filepath"] = layout->border_path; layoutObj["blockdata_filepath"] = layout->blockdata_path; - layoutsArr.append(layoutObj); + layoutsArr.push_back(layoutObj); } layoutsObj["layouts"] = layoutsArr; - QJsonDocument layoutsDoc(layoutsObj); - layoutsFile.write(layoutsDoc.toJson()); + OrderedJson layoutJson(layoutsObj); + OrderedJsonDoc jsonDoc(&layoutJson); + jsonDoc.dump(&layoutsFile); + layoutsFile.close(); } void Project::setNewMapLayout(Map* map) { @@ -562,27 +569,29 @@ void Project::saveMapGroups() { return; } - QJsonObject mapGroupsObj; + OrderedJson::object mapGroupsObj; mapGroupsObj["layouts_table_label"] = layoutsLabel; - QJsonArray groupNamesArr; + OrderedJson::array groupNamesArr; for (QString groupName : *this->groupNames) { - groupNamesArr.append(groupName); + groupNamesArr.push_back(groupName); } mapGroupsObj["group_order"] = groupNamesArr; int groupNum = 0; for (QStringList mapNames : groupedMapNames) { - QJsonArray groupArr; + OrderedJson::array groupArr; for (QString mapName : mapNames) { - groupArr.append(mapName); + groupArr.push_back(mapName); } mapGroupsObj[this->groupNames->at(groupNum)] = groupArr; groupNum++; } - QJsonDocument mapGroupsDoc(mapGroupsObj); - mapGroupsFile.write(mapGroupsDoc.toJson()); + OrderedJson mapGroupJson(mapGroupsObj); + OrderedJsonDoc jsonDoc(&mapGroupJson); + jsonDoc.dump(&mapGroupsFile); + mapGroupsFile.close(); } void Project::saveWildMonData() { @@ -595,78 +604,79 @@ void Project::saveWildMonData() { return; } - QJsonObject wildEncountersObject; - QJsonArray wildEncounterGroups = QJsonArray(); + OrderedJson::object wildEncountersObject; + OrderedJson::array wildEncounterGroups; // gWildMonHeaders label is not mutable - QJsonObject monHeadersObject; + OrderedJson::object monHeadersObject; monHeadersObject["label"] = "gWildMonHeaders"; monHeadersObject["for_maps"] = true; - QJsonArray fieldsInfoArray; + OrderedJson::array fieldsInfoArray; for (EncounterField fieldInfo : wildMonFields) { - QJsonObject fieldObject; - QJsonArray rateArray; + OrderedJson::object fieldObject; + OrderedJson::array rateArray; for (int rate : fieldInfo.encounterRates) { - rateArray.append(rate); + rateArray.push_back(rate); } fieldObject["type"] = fieldInfo.name; fieldObject["encounter_rates"] = rateArray; - QJsonObject groupsObject; + OrderedJson::object groupsObject; for (QString groupName : fieldInfo.groups.keys()) { - QJsonArray subGroupIndices; + OrderedJson::array subGroupIndices; std::sort(fieldInfo.groups[groupName].begin(), fieldInfo.groups[groupName].end()); for (int slotIndex : fieldInfo.groups[groupName]) { - subGroupIndices.append(slotIndex); + subGroupIndices.push_back(slotIndex); } groupsObject[groupName] = subGroupIndices; } - if (!groupsObject.isEmpty()) fieldObject["groups"] = groupsObject; + if (!groupsObject.empty()) fieldObject["groups"] = groupsObject; fieldsInfoArray.append(fieldObject); } monHeadersObject["fields"] = fieldsInfoArray; - QJsonArray encountersArray = QJsonArray(); + OrderedJson::array encountersArray; for (QString key : wildMonData.keys()) { for (QString groupLabel : wildMonData.value(key).keys()) { - QJsonObject encounterObject; + OrderedJson::object encounterObject; encounterObject["map"] = key; encounterObject["base_label"] = groupLabel; WildPokemonHeader encounterHeader = wildMonData.value(key).value(groupLabel); for (QString fieldName : encounterHeader.wildMons.keys()) { - QJsonObject fieldObject; + OrderedJson::object fieldObject; WildMonInfo monInfo = encounterHeader.wildMons.value(fieldName); fieldObject["encounter_rate"] = monInfo.encounterRate; - QJsonArray monArray; + OrderedJson::array monArray; for (WildPokemon wildMon : monInfo.wildPokemon) { - QJsonObject monEntry; + OrderedJson::object monEntry; monEntry["min_level"] = wildMon.minLevel; monEntry["max_level"] = wildMon.maxLevel; monEntry["species"] = wildMon.species; - monArray.append(monEntry); + monArray.push_back(monEntry); } fieldObject["mons"] = monArray; encounterObject[fieldName] = fieldObject; } - encountersArray.append(encounterObject); + encountersArray.push_back(encounterObject); } } monHeadersObject["encounters"] = encountersArray; - wildEncounterGroups.append(monHeadersObject); + wildEncounterGroups.push_back(monHeadersObject); // add extra Json objects that are not associated with maps to the file - for (QString label : extraEncounterGroups.keys()) { - wildEncounterGroups.append(extraEncounterGroups[label]); + for (auto extraObject : extraEncounterGroups) { + wildEncounterGroups.push_back(extraObject); } wildEncountersObject["wild_encounter_groups"] = wildEncounterGroups; - QJsonDocument wildEncountersDoc(wildEncountersObject); - wildEncountersFile.write(wildEncountersDoc.toJson()); + OrderedJson encounterJson(wildEncountersObject); + OrderedJsonDoc jsonDoc(&encounterJson); + jsonDoc.dump(&wildEncountersFile); wildEncountersFile.close(); } @@ -1105,7 +1115,7 @@ void Project::saveMap(Map *map) { return; } - QJsonObject mapObj; + OrderedJson::object mapObj; // Header values. mapObj["id"] = map->constantName; mapObj["name"] = map->name; @@ -1123,10 +1133,10 @@ void Project::saveMap(Map *map) { // Connections if (map->connections.length() > 0) { - QJsonArray connectionsArr; + OrderedJson::array connectionsArr; for (MapConnection* connection : map->connections) { if (mapNamesToMapConstants->contains(connection->map_name)) { - QJsonObject connectionObj; + OrderedJson::object connectionObj; connectionObj["direction"] = connection->direction; connectionObj["offset"] = connection->offset.toInt(); connectionObj["map"] = this->mapNamesToMapConstants->value(connection->map_name); @@ -1142,51 +1152,51 @@ void Project::saveMap(Map *map) { if (map->sharedEventsMap.isEmpty()) { // Object events - QJsonArray objectEventsArr; + OrderedJson::array objectEventsArr; for (int i = 0; i < map->events["object_event_group"].length(); i++) { Event *object_event = map->events["object_event_group"].value(i); - QJsonObject eventObj = object_event->buildObjectEventJSON(); - objectEventsArr.append(eventObj); + OrderedJson::object eventObj = object_event->buildObjectEventJSON(); + objectEventsArr.push_back(eventObj); } mapObj["object_events"] = objectEventsArr; // Warp events - QJsonArray warpEventsArr; + OrderedJson::array warpEventsArr; for (int i = 0; i < map->events["warp_event_group"].length(); i++) { Event *warp_event = map->events["warp_event_group"].value(i); - QJsonObject warpObj = warp_event->buildWarpEventJSON(mapNamesToMapConstants); + OrderedJson::object warpObj = warp_event->buildWarpEventJSON(mapNamesToMapConstants); warpEventsArr.append(warpObj); } mapObj["warp_events"] = warpEventsArr; // Coord events - QJsonArray coordEventsArr; + OrderedJson::array coordEventsArr; for (int i = 0; i < map->events["coord_event_group"].length(); i++) { Event *event = map->events["coord_event_group"].value(i); QString event_type = event->get("event_type"); if (event_type == EventType::Trigger) { - QJsonObject triggerObj = event->buildTriggerEventJSON(); + OrderedJson::object triggerObj = event->buildTriggerEventJSON(); coordEventsArr.append(triggerObj); } else if (event_type == EventType::WeatherTrigger) { - QJsonObject weatherObj = event->buildWeatherTriggerEventJSON(); + OrderedJson::object weatherObj = event->buildWeatherTriggerEventJSON(); coordEventsArr.append(weatherObj); } } mapObj["coord_events"] = coordEventsArr; // Bg Events - QJsonArray bgEventsArr; + OrderedJson::array bgEventsArr; for (int i = 0; i < map->events["bg_event_group"].length(); i++) { Event *event = map->events["bg_event_group"].value(i); QString event_type = event->get("event_type"); if (event_type == EventType::Sign) { - QJsonObject signObj = event->buildSignEventJSON(); + OrderedJson::object signObj = event->buildSignEventJSON(); bgEventsArr.append(signObj); } else if (event_type == EventType::HiddenItem) { - QJsonObject hiddenItemObj = event->buildHiddenItemEventJSON(); + OrderedJson::object hiddenItemObj = event->buildHiddenItemEventJSON(); bgEventsArr.append(hiddenItemObj); } else if (event_type == EventType::SecretBase) { - QJsonObject secretBaseObj = event->buildSecretBaseEventJSON(); + OrderedJson::object secretBaseObj = event->buildSecretBaseEventJSON(); bgEventsArr.append(secretBaseObj); } } @@ -1204,8 +1214,9 @@ void Project::saveMap(Map *map) { mapObj[key] = map->customHeaders[key]; } - QJsonDocument mapDoc(mapObj); - mapFile.write(mapDoc.toJson()); + OrderedJson mapJson(mapObj); + OrderedJsonDoc jsonDoc(&mapJson); + jsonDoc.dump(&mapFile); mapFile.close(); saveLayoutBorder(map); @@ -1501,7 +1512,13 @@ bool Project::readWildMonData() { for (auto subObjectRef : wildMonObj["wild_encounter_groups"].toArray()) { QJsonObject subObject = subObjectRef.toObject(); if (!subObject["for_maps"].toBool()) { - extraEncounterGroups.insert(subObject["label"].toString(), subObject); + QString err; + QString subObjson = QJsonDocument(subObject).toJson(); + OrderedJson::object orderedSubObject = OrderedJson::parse(subObjson, err).object_items(); + extraEncounterGroups.push_back(orderedSubObject); + if (!err.isEmpty()) { + logWarn(QString("Encountered a problem while parsing extra encounter groups: %1").arg(err)); + } continue; }