From d3c212edb3b847009bf6ecd5b165b9d77a75148b Mon Sep 17 00:00:00 2001 From: garakmon Date: Wed, 8 Apr 2020 22:12:20 -0400 Subject: [PATCH 1/6] allow TAB key to navigate through encounter table widget --- src/ui/montabwidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/montabwidget.cpp b/src/ui/montabwidget.cpp index f1693bb3..a2aa5fc0 100644 --- a/src/ui/montabwidget.cpp +++ b/src/ui/montabwidget.cpp @@ -30,6 +30,7 @@ void MonTabWidget::populate() { table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->setFocusPolicy(Qt::NoFocus); table->setSelectionMode(QAbstractItemView::NoSelection); + table->setTabKeyNavigation(false); table->clearFocus(); addTab(table, field.name); } From ef5ba968b143d359bf2eddf77f498266546b319d Mon Sep 17 00:00:00 2001 From: garakmon Date: Thu, 9 Apr 2020 15:12:52 -0400 Subject: [PATCH 2/6] do not allow selection of invalid metatiles - also display invalid metatiles as magenta to stand out more --- include/core/tileset.h | 1 + include/ui/metatileselector.h | 1 + src/core/tileset.cpp | 13 +++++++++++++ src/ui/imageproviders.cpp | 4 ++-- src/ui/metatileselector.cpp | 13 ++++++++++--- 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/include/core/tileset.h b/include/core/tileset.h index 37a9ee78..cf0bb67c 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -35,6 +35,7 @@ public: static Metatile* getMetatile(int, Tileset*, Tileset*); static QList> getBlockPalettes(Tileset*, Tileset*); static QList getPalette(int, Tileset*, Tileset*); + static bool metatileIsValid(uint16_t metatileId, Tileset *, Tileset *); bool appendToHeaders(QString headerFile, QString friendlyName); bool appendToGraphics(QString graphicsFile, QString friendlyName, bool primary); diff --git a/include/ui/metatileselector.h b/include/ui/metatileselector.h index 8fc3b9fc..1a8dc59c 100644 --- a/include/ui/metatileselector.h +++ b/include/ui/metatileselector.h @@ -48,6 +48,7 @@ private: void updateSelectedMetatiles(); uint16_t getMetatileId(int x, int y); QPoint getMetatileIdCoords(uint16_t); + bool shouldAcceptEvent(QGraphicsSceneMouseEvent*); signals: void hoveredMetatileSelectionChanged(uint16_t); diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index a6a04229..9b305e15 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -66,6 +66,19 @@ Metatile* Tileset::getMetatile(int index, Tileset *primaryTileset, Tileset *seco return metatile; } +bool Tileset::metatileIsValid(uint16_t metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) { + if (metatileId >= Project::getNumMetatilesTotal()) + return false; + + if (metatileId < Project::getNumMetatilesPrimary() && metatileId >= primaryTileset->metatiles->length()) + return false; + + if (metatileId < Project::getNumMetatilesTotal() && metatileId >= Project::getNumMetatilesPrimary() + secondaryTileset->metatiles->length()) + return false; + + return true; +} + QList> Tileset::getBlockPalettes(Tileset *primaryTileset, Tileset *secondaryTileset) { QList> palettes; for (int i = 0; i < Project::getNumPalettesPrimary(); i++) { diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index 50853793..286badb3 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -18,13 +18,13 @@ QImage getMetatileImage(uint16_t tile, Tileset *primaryTileset, Tileset *seconda Metatile* metatile = Tileset::getMetatile(tile, primaryTileset, secondaryTileset); if (!metatile || !metatile->tiles) { - metatile_image.fill(0xffffffff); + metatile_image.fill(Qt::magenta); return metatile_image; } Tileset* blockTileset = Tileset::getBlockTileset(tile, primaryTileset, secondaryTileset); if (!blockTileset) { - metatile_image.fill(0xffffffff); + metatile_image.fill(Qt::magenta); return metatile_image; } QList> palettes = Tileset::getBlockPalettes(primaryTileset, secondaryTileset); diff --git a/src/ui/metatileselector.cpp b/src/ui/metatileselector.cpp index 54fef4c2..cfe270e5 100644 --- a/src/ui/metatileselector.cpp +++ b/src/ui/metatileselector.cpp @@ -24,6 +24,7 @@ void MetatileSelector::draw() { height_++; } QImage image(this->numMetatilesWide * 16, height_ * 16, QImage::Format_RGBA8888); + image.fill(Qt::magenta); QPainter painter(&image); for (int i = 0; i < length_; i++) { int tile = i; @@ -91,13 +92,20 @@ void MetatileSelector::setExternalSelection(int width, int height, QListgetCellPos(event->pos()); + return Tileset::metatileIsValid(getMetatileId(pos.x(), pos.y()), this->primaryTileset, this->secondaryTileset); +} + void MetatileSelector::mousePressEvent(QGraphicsSceneMouseEvent *event) { + if (!shouldAcceptEvent(event)) return; SelectablePixmapItem::mousePressEvent(event); this->updateSelectedMetatiles(); emit selectedMetatilesChanged(); } void MetatileSelector::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { + if (!shouldAcceptEvent(event)) return; SelectablePixmapItem::mouseMoveEvent(event); this->updateSelectedMetatiles(); @@ -108,6 +116,7 @@ void MetatileSelector::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { } void MetatileSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { + if (!shouldAcceptEvent(event)) return; SelectablePixmapItem::mouseReleaseEvent(event); this->updateSelectedMetatiles(); emit selectedMetatilesChanged(); @@ -147,9 +156,7 @@ uint16_t MetatileSelector::getMetatileId(int x, int y) { } QPoint MetatileSelector::getMetatileIdCoords(uint16_t metatileId) { - if (metatileId >= Project::getNumMetatilesTotal() - || (metatileId < Project::getNumMetatilesPrimary() && metatileId >= this->primaryTileset->metatiles->length()) - || (metatileId < Project::getNumMetatilesTotal() && metatileId >= Project::getNumMetatilesPrimary() + this->secondaryTileset->metatiles->length())) + if (!Tileset::metatileIsValid(metatileId, this->primaryTileset, this->secondaryTileset)) { // Invalid metatile id. return QPoint(0, 0); From 8cb2e6e2ba5292a84c66c296e075fd26ce769034 Mon Sep 17 00:00:00 2001 From: garakmon Date: Thu, 5 Mar 2020 11:23:17 -0500 Subject: [PATCH 3/6] add json library, begin modifications --- include/core/orderedjson.h | 223 +++++++++++ porymap.pro | 2 + src/core/orderedjson.cpp | 769 +++++++++++++++++++++++++++++++++++++ 3 files changed, 994 insertions(+) create mode 100644 include/core/orderedjson.h create mode 100644 src/core/orderedjson.cpp diff --git a/include/core/orderedjson.h b/include/core/orderedjson.h new file mode 100644 index 00000000..ec45d04d --- /dev/null +++ b/include/core/orderedjson.h @@ -0,0 +1,223 @@ +/// Wrappings around QJsonObjects that preserves order +/* poryjson + * + * poryjson is a modified version of json11, which adds support to preserve the key order + * when dumping json objects, and support to Qt classes + * + * json11 is a tiny JSON library for C++11, providing JSON parsing and serialization. + * + * The core object provided by the library is json11::Json. A Json object represents any JSON + * value: null, bool, number (int or double), string (std::string), array (std::vector), or + * object (std::map). + * + * Json objects act like values: they can be assigned, copied, moved, compared for equality or + * order, etc. There are also helper methods Json::dump, to serialize a Json to a string, and + * Json::parse (static) to parse a std::string as a Json object. + * + * Internally, the various types of Json object are represented by the JsonValue class + * hierarchy. + * + * A note on numbers - JSON specifies the syntax of number formatting but not its semantics, + * so some JSON implementations distinguish between integers and floating-point numbers, while + * some don't. In json11, we choose the latter. Because some JSON implementations (namely + * Javascript itself) treat all numbers as the same type, distinguishing the two leads + * to JSON that will be *silently* changed by a round-trip through those implementations. + * Dangerous! To avoid that risk, json11 stores all numbers as double internally, but also + * provides integer helpers. + * + * Fortunately, double-precision IEEE754 ('double') can precisely store any integer in the + * range +/-2^53, which includes every 'int' on most systems. (Timestamps often use int64 + * or long long to avoid the Y2038K problem; a double storing microseconds since some epoch + * will be exact for +/- 275 years.) + */ + +/* Copyright (c) 2013 Dropbox, Inc. + * + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#ifdef _MSC_VER + #if _MSC_VER <= 1800 // VS 2013 + #ifndef noexcept + #define noexcept throw() + #endif + + #ifndef snprintf + #define snprintf _snprintf_s + #endif + #endif +#endif + +namespace poryjson { + +enum JsonParse { + STANDARD, COMMENTS +}; + +class JsonValue; + +class Json final { +public: + // Types + enum Type { + NUL, NUMBER, BOOL, STRING, ARRAY, OBJECT + }; + + // Array and object typedefs + typedef std::vector array; + typedef std::map object; + + // Constructors for the various types of JSON value. + Json() noexcept; // NUL + Json(std::nullptr_t) noexcept; // NUL + Json(double value); // NUMBER + Json(int value); // NUMBER + Json(bool value); // BOOL + Json(const QString &value); // STRING + Json(QString &&value); // STRING + Json(const char * value); // STRING + Json(const array &values); // ARRAY + Json(array &&values); // ARRAY + Json(const object &values); // OBJECT + Json(object &&values); // OBJECT + + // Implicit constructor: anything with a to_json() function. + template + Json(const T & t) : Json(t.to_json()) {} + + // Implicit constructor: map-like objects (std::map, std::unordered_map, etc) + template ().begin()->first)>::value + && std::is_constructible().begin()->second)>::value, + int>::type = 0> + Json(const M & m) : Json(object(m.begin(), m.end())) {} + + // Implicit constructor: vector-like objects (std::list, std::vector, std::set, etc) + template ().begin())>::value, + int>::type = 0> + Json(const V & v) : Json(array(v.begin(), v.end())) {} + + // This prevents Json(some_pointer) from accidentally producing a bool. Use + // Json(bool(some_pointer)) if that behavior is desired. + Json(void *) = delete; + + // Accessors + Type type() const; + + bool is_null() const { return type() == NUL; } + bool is_number() const { return type() == NUMBER; } + bool is_bool() const { return type() == BOOL; } + bool is_string() const { return type() == STRING; } + bool is_array() const { return type() == ARRAY; } + bool is_object() const { return type() == OBJECT; } + + // Return the enclosed value if this is a number, 0 otherwise. Note that poryjson does not + // distinguish between integer and non-integer numbers - number_value() and int_value() + // can both be applied to a NUMBER-typed object. + double number_value() const; + int int_value() const; + + // Return the enclosed value if this is a boolean, false otherwise. + bool bool_value() const; + // Return the enclosed string if this is a string, "" otherwise. + const QString &string_value() const; + // Return the enclosed std::vector if this is an array, or an empty vector otherwise. + const array &array_items() const; + // Return the enclosed std::map if this is an object, or an empty map otherwise. + const object &object_items() const; + + // Return a reference to arr[i] if this is an array, Json() otherwise. + const Json & operator[](unsigned i) const; + // Return a reference to obj[key] if this is an object, Json() otherwise. + const Json & operator[](const QString &key) const; + + // Serialize. + void dump(QString &out) const; + QString dump() const { + QString out; + dump(out); + return out; + } + + // Parse. If parse fails, return Json() and assign an error message to err. + static Json parse(const QString & in, + QString & err, + JsonParse strategy = JsonParse::STANDARD); + static Json parse(const char * in, + QString & err, + JsonParse strategy = JsonParse::STANDARD) { + if (in) { + return parse(QString(in), err, strategy); + } else { + err = "null input"; + return nullptr; + } + } + + bool operator== (const Json &rhs) const; + bool operator< (const Json &rhs) const; + bool operator!= (const Json &rhs) const { return !(*this == rhs); } + bool operator<= (const Json &rhs) const { return !(rhs < *this); } + 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; +}; + +// Internal class hierarchy - JsonValue objects are not exposed to users of this API. +class JsonValue { +protected: + friend class Json; + friend class JsonInt; + friend class JsonDouble; + virtual Json::Type type() const = 0; + virtual bool equals(const JsonValue * other) const = 0; + virtual bool less(const JsonValue * other) const = 0; + virtual void dump(QString &out) const = 0; + virtual double number_value() const; + virtual int int_value() const; + virtual bool bool_value() const; + virtual const QString &string_value() const; + virtual const Json::array &array_items() const; + virtual const Json &operator[](unsigned i) const; + virtual const Json::object &object_items() const; + virtual const Json &operator[](const QString &key) const; + virtual ~JsonValue() {} +}; + +} // namespace poryjson + diff --git a/porymap.pro b/porymap.pro index 8d75df64..fefdf4e3 100644 --- a/porymap.pro +++ b/porymap.pro @@ -30,6 +30,7 @@ SOURCES += src/core/block.cpp \ src/core/tileset.cpp \ src/core/regionmap.cpp \ src/core/wildmoninfo.cpp \ + src/core/orderedjson.cpp \ src/ui/aboutporymap.cpp \ src/ui/bordermetatilespixmapitem.cpp \ src/ui/collisionpixmapitem.cpp \ @@ -91,6 +92,7 @@ HEADERS += include/core/block.h \ include/core/tileset.h \ include/core/regionmap.h \ include/core/wildmoninfo.h \ + include/core/orderedjson.h \ include/ui/aboutporymap.h \ include/ui/bordermetatilespixmapitem.h \ include/ui/collisionpixmapitem.h \ diff --git a/src/core/orderedjson.cpp b/src/core/orderedjson.cpp new file mode 100644 index 00000000..5d2e33e9 --- /dev/null +++ b/src/core/orderedjson.cpp @@ -0,0 +1,769 @@ +/* Copyright (c) 2013 Dropbox, Inc. + * + * 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. + */ + +#include "orderedjson.h" +#include +#include +#include +#include +#include +#include + +namespace poryjson { + +static const int max_depth = 200; + +using string = QString; +using std::vector; +using std::map; +using std::make_shared; +using std::initializer_list; +using std::move; + +/* Helper for representing null - just a do-nothing struct, plus comparison + * operators so the helpers in JsonValue work. We can't use nullptr_t because + * it may not be orderable. + */ +struct NullStruct { + bool operator==(NullStruct) const { return true; } + bool operator<(NullStruct) const { return false; } +}; + +/* * * * * * * * * * * * * * * * * * * * + * Serialization + */ + +static void dump(NullStruct, string &out) { + out += "null"; +} + +static void dump(double value, string &out) { + if (std::isfinite(value)) { + char buf[32]; + snprintf(buf, sizeof buf, "%.17g", value); + out += buf; + } else { + out += "null"; + } +} + +static void dump(int value, string &out) { + char buf[32]; + snprintf(buf, sizeof buf, "%d", value); + out += buf; +} + +static void dump(bool value, string &out) { + out += value ? "true" : "false"; +} + +static void dump(const string &value, string &out) { + out += '"'; + for (int i = 0; i < value.length(); i++) { + const char ch = value[i].unicode(); + if (ch == '\\') { + out += "\\\\"; + } else if (ch == '"') { + out += "\\\""; + } else if (ch == '\b') { + out += "\\b"; + } else if (ch == '\f') { + out += "\\f"; + } else if (ch == '\n') { + out += "\\n"; + } else if (ch == '\r') { + out += "\\r"; + } else if (ch == '\t') { + out += "\\t"; + } else if (static_cast(ch) <= 0x1f) { + char buf[8]; + snprintf(buf, sizeof buf, "\\u%04x", ch); + out += buf; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1].unicode()) == 0x80 + && static_cast(value[i+2].unicode()) == 0xa8) { + out += "\\u2028"; + i += 2; + } else if (static_cast(ch) == 0xe2 && static_cast(value[i+1].unicode()) == 0x80 + && static_cast(value[i+2].unicode()) == 0xa9) { + out += "\\u2029"; + i += 2; + } else { + out += ch; + } + } + out += '"'; +} + +static void dump(const Json::array &values, string &out) { + bool first = true; + out += "["; + for (const auto &value : values) { + if (!first) + out += ", "; + value.dump(out); + first = false; + } + out += "]"; +} + +static void dump(const Json::object &values, string &out) { + bool first = true; + out += "{"; + for (const auto &kv : values) { + if (!first) + out += ", "; + dump(kv.first, out); + out += ": "; + kv.second.dump(out); + first = false; + } + out += "}"; +} + +void Json::dump(string &out) const { + m_ptr->dump(out); +} + +/* * * * * * * * * * * * * * * * * * * * + * Value wrappers + */ + +template +class Value : public JsonValue { +protected: + + // Constructors + explicit Value(const T &value) : m_value(value) {} + explicit Value(T &&value) : m_value(move(value)) {} + + // Get type tag + Json::Type type() const override { + return tag; + } + + // Comparisons + bool equals(const JsonValue * other) const override { + return m_value == static_cast *>(other)->m_value; + } + bool less(const JsonValue * other) const override { + return m_value < static_cast *>(other)->m_value; + } + + const T m_value; + void dump(string &out) const override { poryjson::dump(m_value, out); } +}; + +class JsonDouble final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return static_cast(m_value); } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonDouble(double value) : Value(value) {} +}; + +class JsonInt final : public Value { + double number_value() const override { return m_value; } + int int_value() const override { return m_value; } + bool equals(const JsonValue * other) const override { return m_value == other->number_value(); } + bool less(const JsonValue * other) const override { return m_value < other->number_value(); } +public: + explicit JsonInt(int value) : Value(value) {} +}; + +class JsonBoolean final : public Value { + bool bool_value() const override { return m_value; } +public: + explicit JsonBoolean(bool value) : Value(value) {} +}; + +class JsonString final : public Value { + const string &string_value() const override { return m_value; } +public: + explicit JsonString(const string &value) : Value(value) {} + explicit JsonString(string &&value) : Value(move(value)) {} +}; + +class JsonArray final : public Value { + const Json::array &array_items() const override { return m_value; } + const Json & operator[](unsigned i) const override; +public: + explicit JsonArray(const Json::array &value) : Value(value) {} + explicit JsonArray(Json::array &&value) : Value(move(value)) {} +}; + +class JsonObject final : public Value { + const Json::object &object_items() const override { return m_value; } + const Json & operator[](const string &key) const override; +public: + explicit JsonObject(const Json::object &value) : Value(value) {} + explicit JsonObject(Json::object &&value) : Value(move(value)) {} +}; + +class JsonNull final : public Value { +public: + JsonNull() : Value({}) {} +}; + +/* * * * * * * * * * * * * * * * * * * * + * Static globals - static-init-safe + */ +struct Statics { + const std::shared_ptr null = make_shared(); + const std::shared_ptr t = make_shared(true); + const std::shared_ptr f = make_shared(false); + const string empty_string; + const vector empty_vector; + const map empty_map; + Statics() {} +}; + +static const Statics & statics() { + static const Statics s {}; + return s; +} + +static const Json & static_null() { + // This has to be separate, not in Statics, because Json() accesses statics().null. + static const Json json_null; + return json_null; +} + +/* * * * * * * * * * * * * * * * * * * * + * Constructors + */ + +Json::Json() noexcept : m_ptr(statics().null) {} +Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} +Json::Json(double value) : m_ptr(make_shared(value)) {} +Json::Json(int value) : m_ptr(make_shared(value)) {} +Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} +Json::Json(const string &value) : m_ptr(make_shared(value)) {} +Json::Json(string &&value) : m_ptr(make_shared(move(value))) {} +Json::Json(const char * value) : m_ptr(make_shared(value)) {} +Json::Json(const Json::array &values) : m_ptr(make_shared(values)) {} +Json::Json(Json::array &&values) : m_ptr(make_shared(move(values))) {} +Json::Json(const Json::object &values) : m_ptr(make_shared(values)) {} +Json::Json(Json::object &&values) : m_ptr(make_shared(move(values))) {} + +/* * * * * * * * * * * * * * * * * * * * + * Accessors + */ + +Json::Type Json::type() const { return m_ptr->type(); } +double Json::number_value() const { return m_ptr->number_value(); } +int Json::int_value() const { return m_ptr->int_value(); } +bool Json::bool_value() const { return m_ptr->bool_value(); } +const string & Json::string_value() const { return m_ptr->string_value(); } +const vector & Json::array_items() const { return m_ptr->array_items(); } +const map & Json::object_items() const { return m_ptr->object_items(); } +const Json & Json::operator[] (unsigned i) const { return (*m_ptr)[i]; } +const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } + +double JsonValue::number_value() const { return 0; } +int JsonValue::int_value() const { return 0; } +bool JsonValue::bool_value() const { return false; } +const string & JsonValue::string_value() const { return statics().empty_string; } +const vector & JsonValue::array_items() const { return statics().empty_vector; } +const map & JsonValue::object_items() const { return statics().empty_map; } +const Json & JsonValue::operator[] (unsigned) const { return static_null(); } +const Json & JsonValue::operator[] (const string &) const { return static_null(); } + +const Json & JsonObject::operator[] (const string &key) const { + auto iter = m_value.find(key); + return (iter == m_value.end()) ? static_null() : iter->second; +} +const Json & JsonArray::operator[] (unsigned i) const { + if (i >= m_value.size()) return static_null(); + else return m_value[i]; +} + +/* * * * * * * * * * * * * * * * * * * * + * Comparison + */ + +bool Json::operator== (const Json &other) const { + if (m_ptr == other.m_ptr) + return true; + if (m_ptr->type() != other.m_ptr->type()) + return false; + + return m_ptr->equals(other.m_ptr.get()); +} + +bool Json::operator< (const Json &other) const { + if (m_ptr == other.m_ptr) + return false; + if (m_ptr->type() != other.m_ptr->type()) + return m_ptr->type() < other.m_ptr->type(); + + return m_ptr->less(other.m_ptr.get()); +} + +/* * * * * * * * * * * * * * * * * * * * + * Parsing + */ + +/* esc(c) + * + * Format char c suitable for printing in an error message. + */ +static inline string esc(char c) { + char buf[12]; + if (static_cast(c) >= 0x20 && static_cast(c) <= 0x7f) { + snprintf(buf, sizeof buf, "'%c' (%d)", c, c); + } else { + snprintf(buf, sizeof buf, "(%d)", c); + } + return string(buf); +} + +static inline bool in_range(long x, long lower, long upper) { + return (x >= lower && x <= upper); +} + +namespace { +/* JsonParser + * + * Object that tracks all state of an in-progress parse. + */ +struct JsonParser final { + + /* State + */ + const string &str; + int i; + string &err; + bool failed; + const JsonParse strategy; + + /* fail(msg, err_ret = Json()) + * + * Mark this parse as failed. + */ + Json fail(string &&msg) { + return fail(move(msg), Json()); + } + + template + T fail(string &&msg, const T err_ret) { + if (!failed) + err = std::move(msg); + failed = true; + return err_ret; + } + + /* consume_whitespace() + * + * Advance until the current character is non-whitespace. + */ + void consume_whitespace() { + while (str[i] == ' ' || str[i] == '\r' || str[i] == '\n' || str[i] == '\t') + i++; + } + + /* consume_comment() + * + * Advance comments (c-style inline and multiline). + */ + bool consume_comment() { + bool comment_found = false; + if (str[i] == '/') { + i++; + if (i == str.size()) + return fail("unexpected end of input after start of comment", false); + if (str[i] == '/') { // inline comment + i++; + // advance until next line, or end of input + while (i < str.size() && str[i] != '\n') { + i++; + } + comment_found = true; + } + else if (str[i] == '*') { // multiline comment + i++; + if (i > str.size()-2) + return fail("unexpected end of input inside multi-line comment", false); + // advance until closing tokens + while (!(str[i] == '*' && str[i+1] == '/')) { + i++; + if (i > str.size()-2) + return fail( + "unexpected end of input inside multi-line comment", false); + } + i += 2; + comment_found = true; + } + else + return fail("malformed comment", false); + } + return comment_found; + } + + /* consume_garbage() + * + * Advance until the current character is non-whitespace and non-comment. + */ + void consume_garbage() { + consume_whitespace(); + if(strategy == JsonParse::COMMENTS) { + bool comment_found = false; + do { + comment_found = consume_comment(); + if (failed) return; + consume_whitespace(); + } + while(comment_found); + } + } + + /* get_next_token() + * + * Return the next non-whitespace character. If the end of the input is reached, + * flag an error and return 0. + */ + char get_next_token() { + consume_garbage(); + if (failed) return static_cast(0); + if (i == str.size()) + return fail("unexpected end of input", static_cast(0)); + + return str[i++].unicode(); + } + + /* encode_utf8(pt, out) + * + * Encode pt as UTF-8 and add it to out. + */ + void encode_utf8(long pt, string & out) { + if (pt < 0) + return; + + if (pt < 0x80) { + out += static_cast(pt); + } else if (pt < 0x800) { + out += static_cast((pt >> 6) | 0xC0); + out += static_cast((pt & 0x3F) | 0x80); + } else if (pt < 0x10000) { + out += static_cast((pt >> 12) | 0xE0); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } else { + out += static_cast((pt >> 18) | 0xF0); + out += static_cast(((pt >> 12) & 0x3F) | 0x80); + out += static_cast(((pt >> 6) & 0x3F) | 0x80); + out += static_cast((pt & 0x3F) | 0x80); + } + } + + /* parse_string() + * + * Parse a string, starting at the current position. + */ + string parse_string() { + string out; + long last_escaped_codepoint = -1; + while (true) { + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + char ch = str[i++].unicode(); + + if (ch == '"') { + encode_utf8(last_escaped_codepoint, out); + return out; + } + + if (in_range(ch, 0, 0x1f)) + return fail(QString("unescaped " + esc(ch) + " in string"), QString()); + + // The usual case: non-escaped characters + if (ch != '\\') { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + out += ch; + continue; + } + + // Handle escapes + if (i == str.size()) + return fail("unexpected end of input in string", ""); + + ch = str[i++].unicode(); + + if (ch == 'u') { + // Extract 4-byte escape sequence + string esc = str.right(i).left(4); + // Explicitly check length of the substring. The following loop + // relies on std::string returning the terminating NUL when + // accessing str[length]. Checking here reduces brittleness. + if (esc.length() < 4) { + return fail(QString("bad \\u escape: " + esc), ""); + } + for (unsigned j = 0; j < 4; j++) { + if (!in_range(esc[j].unicode(), 'a', 'f') && !in_range(esc[j].unicode(), 'A', 'F') + && !in_range(esc[j].unicode(), '0', '9')) + return fail(QString("bad \\u escape: " + esc), ""); + } + + long codepoint = esc.toLong(nullptr, 16); + + // JSON specifies that characters outside the BMP shall be encoded as a pair + // of 4-hex-digit \u escapes encoding their surrogate pair components. Check + // whether we're in the middle of such a beast: the previous codepoint was an + // escaped lead (high) surrogate, and this is a trail (low) surrogate. + if (in_range(last_escaped_codepoint, 0xD800, 0xDBFF) + && in_range(codepoint, 0xDC00, 0xDFFF)) { + // Reassemble the two surrogate pairs into one astral-plane character, per + // the UTF-16 algorithm. + encode_utf8((((last_escaped_codepoint - 0xD800) << 10) + | (codepoint - 0xDC00)) + 0x10000, out); + last_escaped_codepoint = -1; + } else { + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = codepoint; + } + + i += 4; + continue; + } + + encode_utf8(last_escaped_codepoint, out); + last_escaped_codepoint = -1; + + if (ch == 'b') { + out += '\b'; + } else if (ch == 'f') { + out += '\f'; + } else if (ch == 'n') { + out += '\n'; + } else if (ch == 'r') { + out += '\r'; + } else if (ch == 't') { + out += '\t'; + } else if (ch == '"' || ch == '\\' || ch == '/') { + out += ch; + } else { + return fail(QString("invalid escape character " + esc(ch)), ""); + } + } + } + + /* parse_number() + * + * Parse a double. + */ + Json parse_number() { + unsigned start_pos = i; + + if (str[i] == '-') + i++; + + // Integer part + if (str[i] == '0') { + i++; + if (in_range(str[i].unicode(), '0', '9')) + return fail("leading 0s not permitted in numbers"); + } else if (in_range(str[i].unicode(), '1', '9')) { + i++; + while (in_range(str[i].unicode(), '0', '9')) + i++; + } else { + return fail(QString("invalid " + esc(str[i].unicode()) + " in number")); + } + + if (str[i] != '.' && str[i] != 'e' && str[i] != 'E' + && (i - start_pos) <= static_cast(std::numeric_limits::digits10)) { + return std::atoi(str.toStdString().c_str() + start_pos); + } + + // Decimal part + if (str[i] == '.') { + i++; + if (!in_range(str[i].unicode(), '0', '9')) + return fail("at least one digit required in fractional part"); + + while (in_range(str[i].unicode(), '0', '9')) + i++; + } + + // Exponent part + if (str[i] == 'e' || str[i] == 'E') { + i++; + + if (str[i] == '+' || str[i] == '-') + i++; + + if (!in_range(str[i].unicode(), '0', '9')) + return fail("at least one digit required in exponent"); + + while (in_range(str[i].unicode(), '0', '9')) + i++; + } + + return std::strtod(str.toStdString().c_str() + start_pos, nullptr); + } + + /* expect(str, res) + * + * Expect that 'str' starts at the character that was just read. If it does, advance + * the input and return res. If not, flag an error. + */ + Json expect(const string &expected, Json res) { + assert(i != 0); + i--; + if (str == expected) { + i += expected.length(); + return res; + } else { + return fail(QString("parse error: expected " + expected + ", got " + str.right(i).left(expected.length()))); + } + } + + /* parse_json() + * + * Parse a JSON object. + */ + Json parse_json(int depth) { + if (depth > max_depth) { + return fail("exceeded maximum nesting depth"); + } + + char ch = get_next_token(); + if (failed) + return Json(); + + if (ch == '-' || (ch >= '0' && ch <= '9')) { + i--; + return parse_number(); + } + + if (ch == 't') + return expect("true", true); + + if (ch == 'f') + return expect("false", false); + + if (ch == 'n') + return expect("null", Json()); + + if (ch == '"') + return parse_string(); + + if (ch == '{') { + map data; + ch = get_next_token(); + if (ch == '}') + return data; + + while (1) { + if (ch != '"') + return fail(QString("expected '\"' in object, got " + esc(ch))); + + string key = parse_string(); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch != ':') + return fail(QString("expected ':' in object, got " + esc(ch))); + + data[std::move(key)] = parse_json(depth + 1); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == '}') + break; + if (ch != ',') + return fail(QString("expected ',' in object, got " + esc(ch))); + + ch = get_next_token(); + } + return data; + } + + if (ch == '[') { + vector data; + ch = get_next_token(); + if (ch == ']') + return data; + + while (1) { + i--; + data.push_back(parse_json(depth + 1)); + if (failed) + return Json(); + + ch = get_next_token(); + if (ch == ']') + break; + if (ch != ',') + return fail(QString("expected ',' in list, got " + esc(ch))); + + ch = get_next_token(); + (void)ch; + } + return data; + } + + return fail(QString("expected value, got " + esc(ch))); + } +}; +}//namespace { + +Json Json::parse(const string &in, string &err, JsonParse strategy) { + JsonParser parser { in, 0, err, false, strategy }; + Json result = parser.parse_json(0); + + // Check for any trailing garbage + parser.consume_garbage(); + if (parser.failed) + return Json(); + if (parser.i != in.size()) + return parser.fail(QString("unexpected trailing " + esc(in[parser.i].unicode()))); + + return result; +} + +/* * * * * * * * * * * * * * * * * * * * + * Shape-checking + */ + +bool Json::has_shape(const shape & types, string & 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 From 7bef1eb1e174c79d1e30c30b180368aa3e9033ee Mon Sep 17 00:00:00 2001 From: garakmon Date: Thu, 5 Mar 2020 11:40:07 -0500 Subject: [PATCH 4/6] convert to use QVector --- include/core/orderedjson.h | 9 +-- src/core/orderedjson.cpp | 109 ++++++++++++++++++------------------- 2 files changed, 58 insertions(+), 60 deletions(-) diff --git a/include/core/orderedjson.h b/include/core/orderedjson.h index ec45d04d..dd74f5f4 100644 --- a/include/core/orderedjson.h +++ b/include/core/orderedjson.h @@ -55,7 +55,8 @@ #pragma once #include -#include +#include + #include #include #include @@ -88,7 +89,7 @@ public: }; // Array and object typedefs - typedef std::vector array; + typedef QVector array; typedef std::map object; // Constructors for the various types of JSON value. @@ -152,7 +153,7 @@ public: const object &object_items() const; // Return a reference to arr[i] if this is an array, Json() otherwise. - const Json & operator[](unsigned i) const; + const Json & operator[](int i) const; // Return a reference to obj[key] if this is an object, Json() otherwise. const Json & operator[](const QString &key) const; @@ -213,7 +214,7 @@ protected: virtual bool bool_value() const; virtual const QString &string_value() const; virtual const Json::array &array_items() const; - virtual const Json &operator[](unsigned i) const; + virtual const Json &operator[](int i) const; virtual const Json::object &object_items() const; virtual const Json &operator[](const QString &key) const; virtual ~JsonValue() {} diff --git a/src/core/orderedjson.cpp b/src/core/orderedjson.cpp index 5d2e33e9..ba9ebab6 100644 --- a/src/core/orderedjson.cpp +++ b/src/core/orderedjson.cpp @@ -20,7 +20,6 @@ */ #include "orderedjson.h" -#include #include #include #include @@ -31,8 +30,6 @@ namespace poryjson { static const int max_depth = 200; -using string = QString; -using std::vector; using std::map; using std::make_shared; using std::initializer_list; @@ -51,11 +48,11 @@ struct NullStruct { * Serialization */ -static void dump(NullStruct, string &out) { +static void dump(NullStruct, QString &out) { out += "null"; } -static void dump(double value, string &out) { +static void dump(double value, QString &out) { if (std::isfinite(value)) { char buf[32]; snprintf(buf, sizeof buf, "%.17g", value); @@ -65,17 +62,17 @@ static void dump(double value, string &out) { } } -static void dump(int value, string &out) { +static void dump(int value, QString &out) { char buf[32]; snprintf(buf, sizeof buf, "%d", value); out += buf; } -static void dump(bool value, string &out) { +static void dump(bool value, QString &out) { out += value ? "true" : "false"; } -static void dump(const string &value, string &out) { +static void dump(const QString &value, QString &out) { out += '"'; for (int i = 0; i < value.length(); i++) { const char ch = value[i].unicode(); @@ -112,7 +109,7 @@ static void dump(const string &value, string &out) { out += '"'; } -static void dump(const Json::array &values, string &out) { +static void dump(const Json::array &values, QString &out) { bool first = true; out += "["; for (const auto &value : values) { @@ -124,7 +121,7 @@ static void dump(const Json::array &values, string &out) { out += "]"; } -static void dump(const Json::object &values, string &out) { +static void dump(const Json::object &values, QString &out) { bool first = true; out += "{"; for (const auto &kv : values) { @@ -138,7 +135,7 @@ static void dump(const Json::object &values, string &out) { out += "}"; } -void Json::dump(string &out) const { +void Json::dump(QString &out) const { m_ptr->dump(out); } @@ -168,7 +165,7 @@ protected: } const T m_value; - void dump(string &out) const override { poryjson::dump(m_value, out); } + void dump(QString &out) const override { poryjson::dump(m_value, out); } }; class JsonDouble final : public Value { @@ -195,16 +192,16 @@ public: explicit JsonBoolean(bool value) : Value(value) {} }; -class JsonString final : public Value { - const string &string_value() const override { return m_value; } +class JsonString final : public Value { + const QString &string_value() const override { return m_value; } public: - explicit JsonString(const string &value) : Value(value) {} - explicit JsonString(string &&value) : Value(move(value)) {} + explicit JsonString(const QString &value) : Value(value) {} + explicit JsonString(QString &&value) : Value(move(value)) {} }; class JsonArray final : public Value { const Json::array &array_items() const override { return m_value; } - const Json & operator[](unsigned i) const override; + const Json & operator[](int i) const override; public: explicit JsonArray(const Json::array &value) : Value(value) {} explicit JsonArray(Json::array &&value) : Value(move(value)) {} @@ -212,7 +209,7 @@ public: class JsonObject final : public Value { const Json::object &object_items() const override { return m_value; } - const Json & operator[](const string &key) const override; + const Json & operator[](const QString &key) const override; public: explicit JsonObject(const Json::object &value) : Value(value) {} explicit JsonObject(Json::object &&value) : Value(move(value)) {} @@ -230,9 +227,9 @@ struct Statics { const std::shared_ptr null = make_shared(); const std::shared_ptr t = make_shared(true); const std::shared_ptr f = make_shared(false); - const string empty_string; - const vector empty_vector; - const map empty_map; + const QString empty_string; + const QVector empty_vector; + const map empty_map; Statics() {} }; @@ -256,8 +253,8 @@ Json::Json(std::nullptr_t) noexcept : m_ptr(statics().null) {} Json::Json(double value) : m_ptr(make_shared(value)) {} Json::Json(int value) : m_ptr(make_shared(value)) {} Json::Json(bool value) : m_ptr(value ? statics().t : statics().f) {} -Json::Json(const string &value) : m_ptr(make_shared(value)) {} -Json::Json(string &&value) : m_ptr(make_shared(move(value))) {} +Json::Json(const QString &value) : m_ptr(make_shared(value)) {} +Json::Json(QString &&value) : m_ptr(make_shared(move(value))) {} Json::Json(const char * value) : m_ptr(make_shared(value)) {} Json::Json(const Json::array &values) : m_ptr(make_shared(values)) {} Json::Json(Json::array &&values) : m_ptr(make_shared(move(values))) {} @@ -272,26 +269,26 @@ Json::Type Json::type() const { return m_ptr->type(); double Json::number_value() const { return m_ptr->number_value(); } int Json::int_value() const { return m_ptr->int_value(); } bool Json::bool_value() const { return m_ptr->bool_value(); } -const string & Json::string_value() const { return m_ptr->string_value(); } -const vector & Json::array_items() const { return m_ptr->array_items(); } -const map & Json::object_items() const { return m_ptr->object_items(); } -const Json & Json::operator[] (unsigned i) const { return (*m_ptr)[i]; } -const Json & Json::operator[] (const string &key) const { return (*m_ptr)[key]; } +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 & Json::operator[] (int i) const { return (*m_ptr)[i]; } +const Json & Json::operator[] (const QString &key) const { return (*m_ptr)[key]; } double JsonValue::number_value() const { return 0; } int JsonValue::int_value() const { return 0; } bool JsonValue::bool_value() const { return false; } -const string & JsonValue::string_value() const { return statics().empty_string; } -const vector & JsonValue::array_items() const { return statics().empty_vector; } -const map & JsonValue::object_items() const { return statics().empty_map; } -const Json & JsonValue::operator[] (unsigned) const { return static_null(); } -const Json & JsonValue::operator[] (const string &) const { return static_null(); } +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 & JsonValue::operator[] (int) const { return static_null(); } +const Json & JsonValue::operator[] (const QString &) const { return static_null(); } -const Json & JsonObject::operator[] (const string &key) const { +const Json & JsonObject::operator[] (const QString &key) const { auto iter = m_value.find(key); return (iter == m_value.end()) ? static_null() : iter->second; } -const Json & JsonArray::operator[] (unsigned i) const { +const Json & JsonArray::operator[] (int i) const { if (i >= m_value.size()) return static_null(); else return m_value[i]; } @@ -326,14 +323,14 @@ bool Json::operator< (const Json &other) const { * * Format char c suitable for printing in an error message. */ -static inline string esc(char c) { +static inline QString esc(char c) { char buf[12]; if (static_cast(c) >= 0x20 && static_cast(c) <= 0x7f) { snprintf(buf, sizeof buf, "'%c' (%d)", c, c); } else { snprintf(buf, sizeof buf, "(%d)", c); } - return string(buf); + return QString(buf); } static inline bool in_range(long x, long lower, long upper) { @@ -349,9 +346,9 @@ struct JsonParser final { /* State */ - const string &str; + const QString &str; int i; - string &err; + QString &err; bool failed; const JsonParse strategy; @@ -359,12 +356,12 @@ struct JsonParser final { * * Mark this parse as failed. */ - Json fail(string &&msg) { + Json fail(QString &&msg) { return fail(move(msg), Json()); } template - T fail(string &&msg, const T err_ret) { + T fail(QString &&msg, const T err_ret) { if (!failed) err = std::move(msg); failed = true; @@ -453,7 +450,7 @@ struct JsonParser final { * * Encode pt as UTF-8 and add it to out. */ - void encode_utf8(long pt, string & out) { + void encode_utf8(long pt, QString & out) { if (pt < 0) return; @@ -476,14 +473,14 @@ struct JsonParser final { /* parse_string() * - * Parse a string, starting at the current position. + * Parse a QString, starting at the current position. */ - string parse_string() { - string out; + QString parse_string() { + QString out; long last_escaped_codepoint = -1; while (true) { if (i == str.size()) - return fail("unexpected end of input in string", ""); + return fail("unexpected end of input in QString", ""); char ch = str[i++].unicode(); @@ -493,7 +490,7 @@ struct JsonParser final { } if (in_range(ch, 0, 0x1f)) - return fail(QString("unescaped " + esc(ch) + " in string"), QString()); + return fail(QString("unescaped " + esc(ch) + " in QString"), QString()); // The usual case: non-escaped characters if (ch != '\\') { @@ -505,15 +502,15 @@ struct JsonParser final { // Handle escapes if (i == str.size()) - return fail("unexpected end of input in string", ""); + return fail("unexpected end of input in QString", ""); ch = str[i++].unicode(); if (ch == 'u') { // Extract 4-byte escape sequence - string esc = str.right(i).left(4); + QString esc = str.right(i).left(4); // Explicitly check length of the substring. The following loop - // relies on std::string returning the terminating NUL when + // relies on std::QString returning the terminating NUL when // accessing str[length]. Checking here reduces brittleness. if (esc.length() < 4) { return fail(QString("bad \\u escape: " + esc), ""); @@ -627,7 +624,7 @@ struct JsonParser final { * Expect that 'str' starts at the character that was just read. If it does, advance * the input and return res. If not, flag an error. */ - Json expect(const string &expected, Json res) { + Json expect(const QString &expected, Json res) { assert(i != 0); i--; if (str == expected) { @@ -669,7 +666,7 @@ struct JsonParser final { return parse_string(); if (ch == '{') { - map data; + map data; ch = get_next_token(); if (ch == '}') return data; @@ -678,7 +675,7 @@ struct JsonParser final { if (ch != '"') return fail(QString("expected '\"' in object, got " + esc(ch))); - string key = parse_string(); + QString key = parse_string(); if (failed) return Json(); @@ -702,7 +699,7 @@ struct JsonParser final { } if (ch == '[') { - vector data; + QVector data; ch = get_next_token(); if (ch == ']') return data; @@ -730,7 +727,7 @@ struct JsonParser final { }; }//namespace { -Json Json::parse(const string &in, string &err, JsonParse strategy) { +Json Json::parse(const QString &in, QString &err, JsonParse strategy) { JsonParser parser { in, 0, err, false, strategy }; Json result = parser.parse_json(0); @@ -748,7 +745,7 @@ Json Json::parse(const string &in, string &err, JsonParse strategy) { * Shape-checking */ -bool Json::has_shape(const shape & types, string & err) const { +bool Json::has_shape(const shape & types, QString & err) const { if (!is_object()) { err = "expected JSON object, got " + dump(); return false; From 12614a174add6e2e4e9f10aadefdcd34e1c92c91 Mon Sep 17 00:00:00 2001 From: garakmon Date: Thu, 5 Mar 2020 16:38:45 -0500 Subject: [PATCH 5/6] modify Json::dump to preserve our format --- include/core/orderedjson.h | 35 +++++++++++++++++++++--- src/core/orderedjson.cpp | 54 +++++++++++++++++++++++--------------- 2 files changed, 64 insertions(+), 25 deletions(-) diff --git a/include/core/orderedjson.h b/include/core/orderedjson.h index dd74f5f4..b035b1d7 100644 --- a/include/core/orderedjson.h +++ b/include/core/orderedjson.h @@ -56,11 +56,16 @@ #include #include +#include +#include #include #include #include +// temp +#include + #ifdef _MSC_VER #if _MSC_VER <= 1800 // VS 2013 #ifndef noexcept @@ -158,10 +163,14 @@ public: const Json & operator[](const QString &key) const; // Serialize. - void dump(QString &out) const; - QString dump() const { + void dump(QString &out, int *) const; + QString dump(int *indent = nullptr) const { QString out; - dump(out); + if (!indent) { + int temp = 0; + indent = &temp; + } + dump(out, indent); return out; } @@ -199,6 +208,24 @@ private: std::shared_ptr m_ptr; }; +class JsonDoc { +public: + JsonDoc(Json *object) { + this->m_obj = object; + this->m_indent = 0; + }; + + void dump(QFile *file) { + QTextStream fileStream(file); + fileStream << m_obj->dump(&m_indent); + fileStream << "\n"; // pad file with newline + } + +private: + Json *m_obj; + int m_indent; +}; + // Internal class hierarchy - JsonValue objects are not exposed to users of this API. class JsonValue { protected: @@ -208,7 +235,7 @@ protected: virtual Json::Type type() const = 0; virtual bool equals(const JsonValue * other) const = 0; virtual bool less(const JsonValue * other) const = 0; - virtual void dump(QString &out) const = 0; + virtual void dump(QString &out, int *indent) const = 0; virtual double number_value() const; virtual int int_value() const; virtual bool bool_value() const; diff --git a/src/core/orderedjson.cpp b/src/core/orderedjson.cpp index ba9ebab6..73e85121 100644 --- a/src/core/orderedjson.cpp +++ b/src/core/orderedjson.cpp @@ -26,6 +26,8 @@ #include #include +#include + namespace poryjson { static const int max_depth = 200; @@ -48,11 +50,11 @@ struct NullStruct { * Serialization */ -static void dump(NullStruct, QString &out) { +static void dump(NullStruct, QString &out, int *) { out += "null"; } -static void dump(double value, QString &out) { +static void dump(double value, QString &out, int *) { if (std::isfinite(value)) { char buf[32]; snprintf(buf, sizeof buf, "%.17g", value); @@ -62,17 +64,17 @@ static void dump(double value, QString &out) { } } -static void dump(int value, QString &out) { +static void dump(int value, QString &out, int *) { char buf[32]; snprintf(buf, sizeof buf, "%d", value); out += buf; } -static void dump(bool value, QString &out) { +static void dump(bool value, QString &out, int *) { out += value ? "true" : "false"; } -static void dump(const QString &value, QString &out) { +static void dump(const QString &value, QString &out, int *) { out += '"'; for (int i = 0; i < value.length(); i++) { const char ch = value[i].unicode(); @@ -109,34 +111,44 @@ static void dump(const QString &value, QString &out) { out += '"'; } -static void dump(const Json::array &values, QString &out) { +static void dump(const Json::array &values, QString &out, int *indent) { bool first = true; - out += "["; + if (!out.endsWith(": ")) out += QString(*indent * 2, ' '); + out += "[\n"; + *indent += 1; for (const auto &value : values) { - if (!first) - out += ", "; - value.dump(out); + if (!first) { + out += ",\n"; + } + value.dump(out, indent); first = false; } - out += "]"; + *indent -= 1; + out += "\n" + QString(*indent * 2, ' ') + "]"; } -static void dump(const Json::object &values, QString &out) { +static void dump(const Json::object &values, QString &out, int *indent) { bool first = true; - out += "{"; + if (!out.endsWith(": ")) out += QString(*indent * 2, ' '); + out += "{\n"; + *indent += 1; for (const auto &kv : values) { - if (!first) - out += ", "; - dump(kv.first, out); + if (!first) { + out += ",\n"; + } + out += QString(*indent * 2, ' '); + dump(kv.first, out, indent); out += ": "; - kv.second.dump(out); + kv.second.dump(out, indent); first = false; } - out += "}"; + *indent -= 1; + out += "\n" + QString(*indent * 2, ' ') + "}"; } -void Json::dump(QString &out) const { - m_ptr->dump(out); + +void Json::dump(QString &out, int *indent) const { + m_ptr->dump(out, indent); } /* * * * * * * * * * * * * * * * * * * * @@ -165,7 +177,7 @@ protected: } const T m_value; - void dump(QString &out) const override { poryjson::dump(m_value, out); } + void dump(QString &out, int *indent) const override { poryjson::dump(m_value, out, indent); } }; class JsonDouble final : public Value { From 799e5537f9eae76cc7536c09afec92193a5419c5 Mon Sep 17 00:00:00 2001 From: garakmon Date: Thu, 5 Mar 2020 22:46:25 -0500 Subject: [PATCH 6/6] add tsl::ordered_map for json objects, update project code to save json files with new ordered object --- .gitignore | 1 + include/core/event.h | 21 +- include/{core => lib}/orderedjson.h | 16 +- include/lib/orderedmap.h | 2406 +++++++++++++++++++++++++++ include/project.h | 3 +- porymap.pro | 6 +- src/core/event.cpp | 30 +- src/{core => lib}/orderedjson.cpp | 60 +- src/project.cpp | 125 +- 9 files changed, 2535 insertions(+), 133 deletions(-) rename include/{core => lib}/orderedjson.h (94%) create mode 100644 include/lib/orderedmap.h rename src/{core => lib}/orderedjson.cpp (94%) 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; }