diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index f6fa7830a0..ff8ff0110c 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -35,6 +35,7 @@ set(CUBOS_CORE_SOURCE "src/cubos/core/reflection/traits/constructible.cpp" "src/cubos/core/reflection/traits/fields.cpp" "src/cubos/core/reflection/traits/array.cpp" + "src/cubos/core/reflection/traits/dictionary.cpp" "src/cubos/core/reflection/external/primitives.cpp" "src/cubos/core/data/serializer.cpp" diff --git a/core/include/cubos/core/reflection/external/map.hpp b/core/include/cubos/core/reflection/external/map.hpp new file mode 100644 index 0000000000..924e5c8f9a --- /dev/null +++ b/core/include/cubos/core/reflection/external/map.hpp @@ -0,0 +1,115 @@ +/// @file +/// @brief Reflection declaration for `std::map`. +/// @ingroup core-reflection + +#pragma once + +#include + +#include +#include +#include + +CUBOS_REFLECT_EXTERNAL_TEMPLATE((typename K, typename V), (std::map)) +{ + using Map = std::map; + + auto dictionaryTrait = DictionaryTrait( + reflect(), reflect(), + [](const void* instance) -> std::size_t { return static_cast(instance)->size(); } /*length*/, + [](uintptr_t instance, bool writeable) -> void* { + if (writeable) + { + return new typename Map::iterator(reinterpret_cast(instance)->begin()); + } + + return new typename Map::const_iterator(reinterpret_cast(instance)->cbegin()); + } /*begin*/, + [](uintptr_t instance, const void* key, bool writeable) -> void* { + if (writeable) + { + auto it = reinterpret_cast(instance)->find(*reinterpret_cast(key)); + if (it == reinterpret_cast(instance)->end()) + { + return nullptr; + } + return new typename Map::iterator(it); + } + + auto it = reinterpret_cast(instance)->find(*reinterpret_cast(key)); + if (it == reinterpret_cast(instance)->end()) + { + return nullptr; + } + return new typename Map::const_iterator(it); + } /*find*/, + [](uintptr_t instance, void* iterator, bool writeable) -> bool { + if (writeable) + { + ++*static_cast(iterator); + return *static_cast(iterator) != reinterpret_cast(instance)->end(); + } + + ++*static_cast(iterator); + return *static_cast(iterator) != + reinterpret_cast(instance)->cend(); + } /*advance*/, + [](void* iterator, bool writeable) { + if (writeable) + { + delete static_cast(iterator); + } + else + { + delete static_cast(iterator); + } + } /*stop*/, + [](const void* iterator, bool writeable) -> const void* { + if (writeable) + { + return &(*static_cast(iterator))->first; + } + + return &(*static_cast(iterator))->first; + } /*key*/, + [](const void* iterator, bool writeable) -> uintptr_t { + if (writeable) + { + return reinterpret_cast(&(*static_cast(iterator))->second); + } + + return reinterpret_cast(&(*static_cast(iterator))->second); + } /*value*/); + + // We supply the insert functions depending on the constructibility of the value type. + + if constexpr (std::is_default_constructible::value) + { + dictionaryTrait.setInsertDefault([](void* instance, const void* key) { + auto* map = static_cast(instance); + map->emplace(std::piecewise_construct, std::make_tuple(*static_cast(key)), std::make_tuple()); + }); + } + + if constexpr (std::is_copy_constructible::value) + { + dictionaryTrait.setInsertCopy([](void* instance, const void* key, const void* value) { + auto* map = static_cast(instance); + map->emplace(*static_cast(key), *static_cast(value)); + }); + } + + // Since V must be moveable for all std::map, we always supply this function. + dictionaryTrait.setInsertMove([](void* instance, const void* key, void* value) { + auto* map = static_cast(instance); + map->emplace(*static_cast(key), std::move(*static_cast(value))); + }); + + dictionaryTrait.setErase([](void* instance, const void* iterator) { + static_cast(instance)->erase(*static_cast(iterator)); + }); + + return Type::create("std::map<" + reflect().name() + ", " + reflect().name() + ">") + .with(dictionaryTrait) + .with(autoConstructibleTrait>()); +} diff --git a/core/include/cubos/core/reflection/external/unordered_map.hpp b/core/include/cubos/core/reflection/external/unordered_map.hpp new file mode 100644 index 0000000000..b9f016e90f --- /dev/null +++ b/core/include/cubos/core/reflection/external/unordered_map.hpp @@ -0,0 +1,115 @@ +/// @file +/// @brief Reflection declaration for `std::unordered_map`. +/// @ingroup core-reflection + +#pragma once + +#include + +#include +#include +#include + +CUBOS_REFLECT_EXTERNAL_TEMPLATE((typename K, typename V), (std::unordered_map)) +{ + using Map = std::unordered_map; + + auto dictionaryTrait = DictionaryTrait( + reflect(), reflect(), + [](const void* instance) -> std::size_t { return static_cast(instance)->size(); } /*length*/, + [](uintptr_t instance, bool writeable) -> void* { + if (writeable) + { + return new typename Map::iterator(reinterpret_cast(instance)->begin()); + } + + return new typename Map::const_iterator(reinterpret_cast(instance)->cbegin()); + } /*begin*/, + [](uintptr_t instance, const void* key, bool writeable) -> void* { + if (writeable) + { + auto it = reinterpret_cast(instance)->find(*reinterpret_cast(key)); + if (it == reinterpret_cast(instance)->end()) + { + return nullptr; + } + return new typename Map::iterator(it); + } + + auto it = reinterpret_cast(instance)->find(*reinterpret_cast(key)); + if (it == reinterpret_cast(instance)->end()) + { + return nullptr; + } + return new typename Map::const_iterator(it); + } /*find*/, + [](uintptr_t instance, void* iterator, bool writeable) { + if (writeable) + { + ++*static_cast(iterator); + return *static_cast(iterator) != reinterpret_cast(instance)->end(); + } + + ++*static_cast(iterator); + return *static_cast(iterator) != + reinterpret_cast(instance)->cend(); + } /*advance*/, + [](void* iterator, bool writeable) { + if (writeable) + { + delete static_cast(iterator); + } + else + { + delete static_cast(iterator); + } + } /*stop*/, + [](const void* iterator, bool writeable) -> const void* { + if (writeable) + { + return &(*static_cast(iterator))->first; + } + + return &(*static_cast(iterator))->first; + } /*key*/, + [](const void* iterator, bool writeable) -> uintptr_t { + if (writeable) + { + return reinterpret_cast(&(*static_cast(iterator))->second); + } + + return reinterpret_cast(&(*static_cast(iterator))->second); + } /*value*/); + + // We supply the insert functions depending on the constructibility of the value type. + + if constexpr (std::is_default_constructible::value) + { + dictionaryTrait.setInsertDefault([](void* instance, const void* key) { + auto* map = static_cast(instance); + map->emplace(std::piecewise_construct, std::make_tuple(*static_cast(key)), std::make_tuple()); + }); + } + + if constexpr (std::is_copy_constructible::value) + { + dictionaryTrait.setInsertCopy([](void* instance, const void* key, const void* value) { + auto* map = static_cast(instance); + map->emplace(*static_cast(key), *static_cast(value)); + }); + } + + // Since V must be moveable for all std::unordered_map, we always supply this function. + dictionaryTrait.setInsertMove([](void* instance, const void* key, void* value) { + auto* map = static_cast(instance); + map->emplace(*static_cast(key), std::move(*static_cast(value))); + }); + + dictionaryTrait.setErase([](void* instance, const void* iterator) { + static_cast(instance)->erase(*static_cast(iterator)); + }); + + return Type::create("std::unordered_map<" + reflect().name() + ", " + reflect().name() + ">") + .with(dictionaryTrait) + .with(autoConstructibleTrait>()); +} diff --git a/core/include/cubos/core/reflection/traits/dictionary.hpp b/core/include/cubos/core/reflection/traits/dictionary.hpp new file mode 100644 index 0000000000..76a51aa2a7 --- /dev/null +++ b/core/include/cubos/core/reflection/traits/dictionary.hpp @@ -0,0 +1,384 @@ +/// @file +/// @brief Class @ref cubos::core::reflection::DictionaryTrait. +/// @ingroup core-reflection + +#pragma once + +#include +#include + +#include + +namespace cubos::core::reflection +{ + /// @brief Exposes dictionary-like functionality of a type. + /// @see See @ref examples-core-reflection-traits-dictionary for an example of using this trait. + /// @ingroup core-reflection + class DictionaryTrait final + { + public: + /// @brief Provides mutable access to a dictionary. + class View; + + /// @brief Provides immutable access to a dictionary. + class ConstView; + + /// @brief Points to a key-value pair in a dictionary, allowing modification of the value. + class Iterator; + + /// @brief Points to a key-value pair in a dictionary. + class ConstIterator; + + /// @brief Function pointer to get the length of a dictionary instance. + /// @param instance Dictionary instance. + /// @return Length. + using Length = std::size_t (*)(const void* instance); + + /// @brief Function pointer to get an iterator to the first key-value pair of a dictionary + /// instance. + /// @note @ref Stop should be called on the returned iterator when it is no longer needed. + /// @param instance Dictionary instance. + /// @param writeable Whether the iterator should provide write access. + /// @return Iterator. + using Begin = void* (*)(uintptr_t instance, bool writeable); + + /// @brief Function pointer to get an iterator to a value in an dictionary instance. + /// @note @ref Stop should be called on the returned iterator when it is no longer needed. + /// @param instance Dictionary instance. + /// @param key Key. + /// @param writeable Whether the iterator should provide write access. + /// @return Iterator to the found key-value pair, or null if not found. + using Find = void* (*)(uintptr_t instance, const void* key, bool writeable); + + /// @brief Function pointer to advance an iterator. + /// @param instance Dictionary instance. + /// @param iterator Iterator. + /// @param writeable Whether the iterator provides write access. + /// @return Whether the iterator is still valid. + using Advance = bool (*)(uintptr_t instance, void* iterator, bool writeable); + + /// @brief Function pointer to destroy an iterator instance. + /// @param iterator Iterator to the key-value pair. + /// @param writeable Whether the iterator provides write access. + using Stop = void (*)(void* iterator, bool writeable); + + /// @brief Function pointer to get the address of the key pointed to by an iterator. + /// @param iterator Iterator. + /// @param writeable Whether the iterator provides write access. + /// @return Key address. + using Key = const void* (*)(const void* iterator, bool writeable); + + /// @brief Function pointer to get the address of the value pointed to by an iterator. + /// @param iterator Iterator. + /// @param writeable Whether the iterator provides write access. + /// @return Value address. + using Value = uintptr_t (*)(const void* iterator, bool writeable); + + /// @brief Function pointer to insert a default value into a dictionary instance. + /// @param instance Dictionary instance. + /// @param key Key. + using InsertDefault = void (*)(void* instance, const void* key); + + /// @brief Function pointer to insert a copy of the given value into a dictionary instance. + /// @param instance Dictionary instance. + /// @param key Key. + /// @param value Value. + using InsertCopy = void (*)(void* instance, const void* key, const void* value); + + /// @brief Function pointer to move the given value into a dictionary instance. + /// @param instance Dictionary instance. + /// @param key Key. + /// @param value Value. + using InsertMove = void (*)(void* instance, const void* key, void* value); + + /// @brief Function pointer to remove a key-value pair of a dictionary instance. + /// @param instance Dictionary instance. + /// @param iterator Iterator to the key-value pair with write access. + using Erase = void (*)(void* instance, const void* iterator); + + /// @brief Constructs. + /// @param keyType Key type of the dictionary. + /// @param valueType Value type of the dictionary. + /// @param length Function used to get the length of a dictionary. + /// @param begin Function used to get an iterator to the first key-value pair of a + /// @param find Function used to find a key-value pair in a dictionary. + /// @param advance Function used to advance an iterator. + /// @param stop Function used to destroy an iterator. + /// @param key Function used to get the address of the key pointed to by an iterator. + /// @param value Function used to get the address of the value pointed to by an iterator. + DictionaryTrait(const Type& keyType, const Type& valueType, Length length, Begin begin, Find find, + Advance advance, Stop stop, Key key, Value value); + + /// @brief Sets the default-construct insert operation of the trait. + /// @param insertDefault Function pointer. + void setInsertDefault(InsertDefault insertDefault); + + /// @brief Sets the copy-construct insert operation of the trait. + /// @param insertCopy Function pointer. + void setInsertCopy(InsertCopy insertCopy); + + /// @brief Sets the move-construct insert operation of the trait. + /// @param insertMove Function pointer. + void setInsertMove(InsertMove insertMove); + + /// @brief Sets the erase operation of the trait. + /// @param erase Function pointer. + void setErase(Erase erase); + + /// @brief Checks if default-construct insert is supported. + /// @return Whether the operation is supported. + bool hasInsertDefault() const; + + /// @brief Checks if copy-construct insert is supported. + /// @return Whether the operation is supported. + bool hasInsertCopy() const; + + /// @brief Checks if move-construct insert is supported. + /// @return Whether the operation is supported. + bool hasInsertMove() const; + + /// @brief Checks if erase is supported. + /// @return Whether the operation is supported. + bool hasErase() const; + + /// @brief Returns the key type of the dictionary. + /// @return Key type. + const Type& keyType() const; + + /// @brief Returns the value type of the dictionary. + /// @return Value type. + const Type& valueType() const; + + /// @brief Returns a view of the given dictionary instance. + /// @param instance Dictionary instance. + /// @return Dictionary view. + View view(void* instance) const; + + /// @copydoc view(void*) const + ConstView view(const void* instance) const; + + private: + friend View; + friend ConstView; + + const Type& mKeyType; + const Type& mValueType; + Length mLength; + Begin mBegin; + Find mFind; + Advance mAdvance; + Stop mStop; + Key mKey; + Value mValue; + InsertDefault mInsertDefault{nullptr}; + InsertCopy mInsertCopy{nullptr}; + InsertMove mInsertMove{nullptr}; + Erase mErase{nullptr}; + }; + + class DictionaryTrait::View + { + public: + /// @brief Used to iterate over the entries of a dictionary. + class Iterator; + + /// @brief Constructs. + /// @param trait Trait. + /// @param instance Instance. + View(const DictionaryTrait& trait, void* instance); + + /// @brief Returns the length of the dictionary. + /// @return Dictionary length. + std::size_t length() const; + + /// @brief Returns an iterator to the first entry. + /// @return Iterator. + Iterator begin() const; + + /// @brief Returns an iterator to the entry after the last entry. + /// @return Iterator. + Iterator end() const; + + /// @brief Returns an iterator to the entry with the given key. + /// @note If no entry with the given key exists, @ref end() is returned. + /// @param key Key. + /// @return Iterator. + Iterator find(const void* key) const; + + /// @brief Inserts a default-constructed value with the given key. + /// @note Aborts if @ref DictionaryTrait::hasInsertDefault() returns false. + /// @param key Key. + void insertDefault(const void* key) const; + + /// @brief Inserts a copy-constructed value with the given key. + /// @note Aborts if @ref DictionaryTrait::hasInsertCopy() returns false. + /// @param key Key. + /// @param value Value. + void insertCopy(const void* key, const void* value) const; + + /// @brief Inserts a move-constructed value with the given key. + /// @note Aborts if @ref DictionaryTrait::hasInsertMove() returns false. + /// @param key Key. + /// @param value Value. + void insertMove(const void* key, void* value) const; + + /// @brief Removes an entry. + /// @note Aborts if @ref DictionaryTrait::hasErase() returns false. + /// @param iterator Iterator. + void erase(Iterator& iterator) const; + + private: + const DictionaryTrait& mTrait; + void* mInstance; + }; + + class DictionaryTrait::ConstView + { + public: + /// @brief Used to iterate over the entries of a dictionary. + class Iterator; + + /// @brief Constructs. + /// @param trait Trait. + /// @param instance Instance. + ConstView(const DictionaryTrait& trait, const void* instance); + + /// @brief Returns the length of the dictionary. + /// @return Dictionary length. + std::size_t length() const; + + /// @brief Returns an iterator to the first entry. + /// @return Iterator. + Iterator begin() const; + + /// @brief Returns an iterator to the entry after the last entry. + /// @return Iterator. + Iterator end() const; + + /// @brief Returns an iterator to the entry with the given key. + /// @note If no entry with the given key exists, @ref end() is returned. + /// @param key Key. + /// @return Iterator. + Iterator find(const void* key) const; + + private: + const DictionaryTrait& mTrait; + const void* mInstance; + }; + + class DictionaryTrait::View::Iterator + { + public: + /// @brief Output structure for the iterator. + struct Entry + { + const void* key; ///< Key. + void* value; ///< Value. + }; + + ~Iterator(); + + /// @brief Copy constructs. + /// @param other Other iterator. + Iterator(const Iterator& other); + + /// @brief Move constructs. + /// @param other Other iterator. + Iterator(Iterator&& other) noexcept; + + /// @brief Compares two iterators. + /// @param other Other iterator. + /// @return Whether the iterators point to the same entry. + bool operator==(const Iterator& other) const; + + /// @brief Compares two iterators. + /// @param other Other iterator. + /// @return Whether the iterators point to different entries. + bool operator!=(const Iterator& /*other*/) const; + + /// @brief Accesses the entry referenced by this iterator. + /// @note Aborts if out of bounds. + /// @return Entry. + const Entry& operator*() const; + + /// @brief Accesses the entry referenced by this iterator. + /// @note Aborts if out of bounds. + /// @return Entry. + const Entry* operator->() const; + + /// @brief Advances the iterator. + /// @note Aborts if out of bounds. + /// @return Reference to this. + Iterator& operator++(); + + private: + friend DictionaryTrait; + + /// @brief Constructs. + /// @param view View. + /// @param inner Inner iterator. + Iterator(const View& view, void* inner); + + const View& mView; + void* mInner{nullptr}; + mutable Entry mEntry; + }; + + class DictionaryTrait::ConstView::Iterator + { + public: + /// @brief Output structure for the iterator. + struct Entry + { + const void* key; ///< Key. + const void* value; ///< Value. + }; + + ~Iterator(); + + /// @brief Copy constructs. + /// @param other Other iterator. + Iterator(const Iterator& other); + + /// @brief Move constructs. + /// @param other Other iterator. + Iterator(Iterator&& other) noexcept; + + /// @brief Compares two iterators. + /// @param other Other iterator. + /// @return Whether the iterators point to the same entry. + bool operator==(const Iterator& other) const; + + /// @brief Compares two iterators. + /// @param other Other iterator. + /// @return Whether the iterators point to different entries. + bool operator!=(const Iterator& /*other*/) const; + + /// @brief Accesses the entry referenced by this iterator. + /// @note Aborts if out of bounds. + /// @return Entry. + const Entry& operator*() const; + + /// @brief Accesses the entry referenced by this iterator. + /// @note Aborts if out of bounds. + /// @return Entry. + const Entry* operator->() const; + + /// @brief Advances the iterator. + /// @note Aborts if out of bounds. + /// @return Reference to this. + Iterator& operator++(); + + private: + friend DictionaryTrait; + + /// @brief Constructs. + /// @param view View. + /// @param inner Inner iterator. + Iterator(const ConstView& view, void* inner); + + const ConstView& mView; + void* mInner{nullptr}; + mutable Entry mEntry; + }; +} // namespace cubos::core::reflection diff --git a/core/samples/CMakeLists.txt b/core/samples/CMakeLists.txt index d4461a27ee..fcb31042d8 100644 --- a/core/samples/CMakeLists.txt +++ b/core/samples/CMakeLists.txt @@ -31,6 +31,7 @@ make_sample(DIR "reflection/basic") make_sample(DIR "reflection/traits/constructible") make_sample(DIR "reflection/traits/fields") make_sample(DIR "reflection/traits/array") +make_sample(DIR "reflection/traits/dictionary") make_sample(DIR "data/fs/embedded_archive" SOURCES "embed.cpp") make_sample(DIR "data/fs/standard_archive") make_sample(DIR "data/serialization") diff --git a/core/samples/reflection/page.md b/core/samples/reflection/page.md index 2fa17114ae..bb1808e921 100644 --- a/core/samples/reflection/page.md +++ b/core/samples/reflection/page.md @@ -6,3 +6,4 @@ - @subpage examples-core-reflection-traits-constructible - @copybrief examples-core-reflection-traits-constructible - @subpage examples-core-reflection-traits-fields - @copybrief examples-core-reflection-traits-fields - @subpage examples-core-reflection-traits-array - @copybrief examples-core-reflection-traits-array +- @subpage examples-core-reflection-traits-dictionary - @copybrief examples-core-reflection-traits-dictionary diff --git a/core/samples/reflection/traits/dictionary/main.cpp b/core/samples/reflection/traits/dictionary/main.cpp new file mode 100644 index 0000000000..c451bf7149 --- /dev/null +++ b/core/samples/reflection/traits/dictionary/main.cpp @@ -0,0 +1,67 @@ +#include + +/// [Printing any dictionary] +#include +#include + +using cubos::core::reflection::DictionaryTrait; +using cubos::core::reflection::Type; + +void printDictionary(const Type& type, const void* instance) +{ + const auto& dictionaryTrait = type.get(); + /// [Printing any dictionary] + + /// [Getting dictionary length and types] + auto dictionaryView = dictionaryTrait.view(instance); + CUBOS_INFO("Dictionary with {} entries of key type {} and value type {}", dictionaryView.length(), + dictionaryTrait.keyType().name(), dictionaryTrait.valueType().name()); + /// [Getting dictionary length and types] + + /// [Getting dictionary entries] + if (!dictionaryTrait.keyType().is() || !dictionaryTrait.valueType().is()) + { + CUBOS_INFO("This function does not support printing dictionary with key and value types other than int32_t"); + return; + } + + for (auto [key, value] : dictionaryView) + { + CUBOS_INFO("{} -> {}", *static_cast(key), *static_cast(value)); + } +} +/// [Getting dictionary entries] + +/// [Typed wrapper] +template +void printDictionary(const T& dictionary) +{ + using cubos::core::reflection::reflect; + + printDictionary(reflect(), &dictionary); +} +/// [Typed wrapper] + +/// [Usage] +#include +#include + +int main() +{ + std::map map = { + {1, 2}, + {2, 4}, + {3, 6}, + {4, 8}, + }; + printDictionary(map); + + /// [Expected output] + // Dictionary with 4 entries of key type int32_t and value type int32_t + // 1 -> 2 + // 2 -> 4 + // 3 -> 6 + // 4 -> 8 + /// [Expected output] +} +/// [Usage] diff --git a/core/samples/reflection/traits/dictionary/page.md b/core/samples/reflection/traits/dictionary/page.md new file mode 100644 index 0000000000..b7d9af7f52 --- /dev/null +++ b/core/samples/reflection/traits/dictionary/page.md @@ -0,0 +1,39 @@ +# Dictionary Trait {#examples-core-reflection-traits-dictionary} + +@brief Exposing and using dictionary functionality of a type. + +The @ref cubos::core::reflection::DictionaryTrait "DictionaryTrait" trait is +used to expose the dictionary functionality of a type. In this example, we will +write a function which takes a type and an instance of that type, and prints +its entries: + +@snippet reflection/traits/dictionary/main.cpp Printing any dictionary + +Through the trait, we can access the size of the dictionary and its key and +value types: + +@snippet reflection/traits/dictionary/main.cpp Getting dictionary length and types + +We can also iterate over the entries of a dictionary and access them: + +@snippet reflection/traits/dictionary/main.cpp Getting dictionary entries + +In this example, we're only supporting dictionaris which map `int32_t`s, but we +could for example implement a printing function which supports all primitive +types. + +To make calling our function easier, we can add a convenience typed wrapper: + +@snippet reflection/traits/dictionary/main.cpp Typed wrapper + +Using this function is now as simple as: + +@snippet reflection/traits/dictionary/main.cpp Usage + +Its important to note that both the includes above are necessary, as we're +reflecting the type `std::map`, which also means reflecting +`int32_t`. + +Executing the sample should output: + +@snippet reflection/traits/dictionary/main.cpp Expected output diff --git a/core/src/cubos/core/reflection/traits/dictionary.cpp b/core/src/cubos/core/reflection/traits/dictionary.cpp new file mode 100644 index 0000000000..71f7a82860 --- /dev/null +++ b/core/src/cubos/core/reflection/traits/dictionary.cpp @@ -0,0 +1,293 @@ +#include +#include + +using cubos::core::reflection::DictionaryTrait; +using cubos::core::reflection::Type; + +DictionaryTrait::DictionaryTrait(const Type& keyType, const Type& valueType, Length length, Begin begin, Find find, + Advance advance, Stop stop, Key key, Value value) + : mKeyType(keyType) + , mValueType(valueType) + , mLength(length) + , mBegin(begin) + , mFind(find) + , mAdvance(advance) + , mStop(stop) + , mKey(key) + , mValue(value) +{ +} + +void DictionaryTrait::setInsertDefault(InsertDefault insertDefault) +{ + CUBOS_ASSERT(!mInsertDefault, "Insert default already set"); + mInsertDefault = insertDefault; +} + +void DictionaryTrait::setInsertCopy(InsertCopy insertCopy) +{ + CUBOS_ASSERT(!mInsertCopy, "Insert copy already set"); + mInsertCopy = insertCopy; +} + +void DictionaryTrait::setInsertMove(InsertMove insertMove) +{ + CUBOS_ASSERT(!mInsertMove, "Insert move already set"); + mInsertMove = insertMove; +} + +void DictionaryTrait::setErase(Erase erase) +{ + CUBOS_ASSERT(!mErase, "Erase already set"); + mErase = erase; +} + +bool DictionaryTrait::hasInsertDefault() const +{ + return mInsertDefault != nullptr; +} + +bool DictionaryTrait::hasInsertCopy() const +{ + return mInsertCopy != nullptr; +} + +bool DictionaryTrait::hasInsertMove() const +{ + return mInsertMove != nullptr; +} + +bool DictionaryTrait::hasErase() const +{ + return mErase != nullptr; +} + +const Type& DictionaryTrait::keyType() const +{ + return mKeyType; +} + +const Type& DictionaryTrait::valueType() const +{ + return mValueType; +} + +DictionaryTrait::View DictionaryTrait::view(void* instance) const +{ + return View{*this, instance}; +} + +DictionaryTrait::ConstView DictionaryTrait::view(const void* instance) const +{ + return ConstView{*this, instance}; +} + +DictionaryTrait::View::View(const DictionaryTrait& trait, void* instance) + : mTrait(trait) + , mInstance(instance) +{ +} + +std::size_t DictionaryTrait::View::length() const +{ + return mTrait.mLength(mInstance); +} + +DictionaryTrait::View::Iterator DictionaryTrait::View::begin() const +{ + return Iterator{*this, mTrait.mBegin(reinterpret_cast(mInstance), true)}; +} + +DictionaryTrait::View::Iterator DictionaryTrait::View::end() const +{ + return Iterator{*this, nullptr}; +} + +DictionaryTrait::View::Iterator DictionaryTrait::View::find(const void* key) const +{ + return Iterator{*this, mTrait.mFind(reinterpret_cast(mInstance), key, true)}; +} + +void DictionaryTrait::View::insertDefault(const void* key) const +{ + CUBOS_ASSERT(mTrait.hasInsertDefault(), "Insert default not supported"); + mTrait.mInsertDefault(mInstance, key); +} + +void DictionaryTrait::View::insertCopy(const void* key, const void* value) const +{ + CUBOS_ASSERT(mTrait.hasInsertCopy(), "Insert copy not supported"); + mTrait.mInsertCopy(mInstance, key, value); +} + +void DictionaryTrait::View::insertMove(const void* key, void* value) const +{ + CUBOS_ASSERT(mTrait.hasInsertMove(), "Insert move not supported"); + mTrait.mInsertMove(mInstance, key, value); +} + +void DictionaryTrait::View::erase(Iterator& iterator) const +{ + CUBOS_ASSERT(mTrait.hasErase(), "Erase not supported"); + mTrait.mErase(mInstance, iterator.mInner); + mTrait.mStop(iterator.mInner, true); + iterator.mInner = nullptr; +} + +DictionaryTrait::ConstView::ConstView(const DictionaryTrait& trait, const void* instance) + : mTrait(trait) + , mInstance(instance) +{ +} + +std::size_t DictionaryTrait::ConstView::length() const +{ + return mTrait.mLength(mInstance); +} + +DictionaryTrait::ConstView::Iterator DictionaryTrait::ConstView::begin() const +{ + return Iterator{*this, mTrait.mBegin(reinterpret_cast(mInstance), false)}; +} + +DictionaryTrait::ConstView::Iterator DictionaryTrait::ConstView::end() const +{ + return Iterator{*this, nullptr}; +} + +DictionaryTrait::ConstView::Iterator DictionaryTrait::ConstView::find(const void* key) const +{ + return Iterator{*this, mTrait.mFind(reinterpret_cast(mInstance), key, false)}; +} + +DictionaryTrait::View::Iterator::~Iterator() +{ + if (mInner != nullptr) + { + mView.mTrait.mStop(mInner, true); + } +} + +DictionaryTrait::View::Iterator::Iterator(const View& view, void* inner) + : mView(view) + , mInner(inner) +{ +} + +DictionaryTrait::View::Iterator::Iterator(const Iterator& other) + : mView(other.mView) +{ + if (other.mInner != nullptr) + { + mInner = mView.mTrait.mFind(reinterpret_cast(mView.mInstance), other->key, true); + } +} + +DictionaryTrait::View::Iterator::Iterator(Iterator&& other) noexcept + : mView(other.mView) + , mInner(other.mInner) +{ + other.mInner = nullptr; +} + +bool DictionaryTrait::View::Iterator::operator==(const Iterator& other) const +{ + return mInner == other.mInner || (mInner != nullptr && other.mInner != nullptr && (*this)->value == other->value); +} + +bool DictionaryTrait::View::Iterator::operator!=(const Iterator& other) const +{ + return !(*this == other); +} + +const DictionaryTrait::View::Iterator::Entry& DictionaryTrait::View::Iterator::operator*() const +{ + CUBOS_ASSERT(mInner != nullptr, "Iterator out of bounds"); + mEntry.key = mView.mTrait.mKey(mInner, true); + mEntry.value = reinterpret_cast(mView.mTrait.mValue(mInner, true)); + return mEntry; +} + +const DictionaryTrait::View::Iterator::Entry* DictionaryTrait::View::Iterator::operator->() const +{ + return &this->operator*(); +} + +DictionaryTrait::View::Iterator& DictionaryTrait::View::Iterator::operator++() +{ + CUBOS_ASSERT(mInner != nullptr, "Iterator out of bounds"); + + if (!mView.mTrait.mAdvance(reinterpret_cast(mView.mInstance), mInner, true)) + { + mView.mTrait.mStop(mInner, true); + mInner = nullptr; + } + + return *this; +} + +DictionaryTrait::ConstView::Iterator::~Iterator() +{ + if (mInner != nullptr) + { + mView.mTrait.mStop(mInner, false); + } +} + +DictionaryTrait::ConstView::Iterator::Iterator(const ConstView& view, void* inner) + : mView(view) + , mInner(inner) +{ +} + +DictionaryTrait::ConstView::Iterator::Iterator(const Iterator& other) + : mView(other.mView) +{ + if (other.mInner != nullptr) + { + mInner = mView.mTrait.mFind(reinterpret_cast(mView.mInstance), other->key, false); + } +} + +DictionaryTrait::ConstView::Iterator::Iterator(Iterator&& other) noexcept + : mView(other.mView) + , mInner(other.mInner) +{ + other.mInner = nullptr; +} + +bool DictionaryTrait::ConstView::Iterator::operator==(const Iterator& other) const +{ + return mInner == other.mInner || (mInner != nullptr && other.mInner != nullptr && (*this)->value == other->value); +} + +bool DictionaryTrait::ConstView::Iterator::operator!=(const Iterator& other) const +{ + return !(*this == other); +} + +const DictionaryTrait::ConstView::Iterator::Entry& DictionaryTrait::ConstView::Iterator::operator*() const +{ + CUBOS_ASSERT(mInner != nullptr, "Iterator out of bounds"); + mEntry.key = mView.mTrait.mKey(mInner, false); + mEntry.value = reinterpret_cast(mView.mTrait.mValue(mInner, false)); + return mEntry; +} + +const DictionaryTrait::ConstView::Iterator::Entry* DictionaryTrait::ConstView::Iterator::operator->() const +{ + return &this->operator*(); +} + +DictionaryTrait::ConstView::Iterator& DictionaryTrait::ConstView::Iterator::operator++() +{ + CUBOS_ASSERT(mInner != nullptr, "Iterator out of bounds"); + + if (!mView.mTrait.mAdvance(reinterpret_cast(mView.mInstance), mInner, true)) + { + mView.mTrait.mStop(mInner, true); + mInner = nullptr; + } + + return *this; +} diff --git a/core/tests/CMakeLists.txt b/core/tests/CMakeLists.txt index a5bd316d06..c6ae33a0f5 100644 --- a/core/tests/CMakeLists.txt +++ b/core/tests/CMakeLists.txt @@ -13,6 +13,8 @@ add_executable( reflection/traits/fields.cpp reflection/external/primitives.cpp reflection/external/vector.cpp + reflection/external/map.cpp + reflection/external/unordered_map.cpp data/fs/embedded_archive.cpp data/fs/standard_archive.cpp diff --git a/core/tests/reflection/external/map.cpp b/core/tests/reflection/external/map.cpp new file mode 100644 index 0000000000..a4ee6a3c28 --- /dev/null +++ b/core/tests/reflection/external/map.cpp @@ -0,0 +1,25 @@ +#include + +#include +#include + +#include "../traits/constructible.hpp" +#include "../traits/dictionary.hpp" + +template +static void test(const char* name, std::map map, K insertedKey, V insertedValue) +{ + CHECK(reflect>().name() == name); + testDictionary, K, V>(map, map.size(), &insertedKey, &insertedValue); + testConstructible>(&map); +} + +TEST_CASE("reflection::reflect>()") +{ + test("std::map", {{1, 2}, {2, 4}}, 3, 6); + test>("std::map>", + { + {'H', {{'i', true}, {'o', false}}}, + }, + 'A', {{'h', true}}); +} diff --git a/core/tests/reflection/external/unordered_map.cpp b/core/tests/reflection/external/unordered_map.cpp new file mode 100644 index 0000000000..8b722aea5d --- /dev/null +++ b/core/tests/reflection/external/unordered_map.cpp @@ -0,0 +1,25 @@ +#include + +#include +#include + +#include "../traits/constructible.hpp" +#include "../traits/dictionary.hpp" + +template +static void test(const char* name, std::unordered_map map, K insertedKey, V insertedValue) +{ + CHECK(reflect>().name() == name); + testDictionary, K, V>(map, map.size(), &insertedKey, &insertedValue); + testConstructible>(&map); +} + +TEST_CASE("reflection::reflect>()") +{ + test("std::unordered_map", {{1, 2}, {2, 4}}, 3, 6); + test>("std::unordered_map>", + { + {'H', {{'i', true}, {'o', false}}}, + }, + 'A', {{'h', true}}); +} diff --git a/core/tests/reflection/traits/dictionary.hpp b/core/tests/reflection/traits/dictionary.hpp new file mode 100644 index 0000000000..672ea9f3ae --- /dev/null +++ b/core/tests/reflection/traits/dictionary.hpp @@ -0,0 +1,117 @@ +#include + +#include + +#include +#include + +using cubos::core::reflection::DictionaryTrait; +using cubos::core::reflection::reflect; +using cubos::core::reflection::Type; + +/// @brief Checks if a type's DictionaryTrait works as expected. +/// @tparam T Type. +/// @tparam E Element type. +/// @param value Value to test. +/// @param length Initial length. +/// @param inserted Optional value to insert. +template +void testDictionary(T& value, std::size_t length, const K* insertedKey, V* insertedValue) +{ + (void)insertedValue; + + const Type& type = reflect(); + REQUIRE(type.has()); + const DictionaryTrait& trait = type.get(); + + REQUIRE(trait.keyType().is()); + REQUIRE(trait.valueType().is()); + + auto view = trait.view(&value); + auto constView = trait.view(static_cast(&value)); + REQUIRE(view.length() == length); + REQUIRE(constView.length() == length); + + // Check that both the normal and const iterators count the expected number of elements. + + std::size_t i = 0; + for (auto entry : view) + { + (void)entry; + i += 1; + } + REQUIRE(i == length); + + i = 0; + for (auto entry : constView) + { + (void)entry; + i += 1; + } + REQUIRE(i == length); + + if (trait.hasInsertDefault()) + { + + REQUIRE(constView.find(insertedKey) == constView.end()); + view.insertDefault(insertedKey); + auto it = view.find(insertedKey); + REQUIRE(it != view.end()); + + if constexpr (std::equality_comparable) + { + CHECK(*static_cast(it->key) == *insertedKey); + } + + if constexpr (std::equality_comparable && std::default_initializable) + { + CHECK(*static_cast(it->value) == V{}); + } + + REQUIRE(view.length() == length + 1); + view.erase(it); + REQUIRE(view.length() == length); + REQUIRE(view.find(insertedKey) == view.end()); + } + + if (trait.hasInsertCopy()) + { + REQUIRE(view.find(insertedKey) == view.end()); + view.insertCopy(insertedKey, insertedValue); + auto it = view.find(insertedKey); + REQUIRE(it != view.end()); + + if constexpr (std::equality_comparable) + { + CHECK(*static_cast(it->key) == *insertedKey); + } + + if constexpr (std::equality_comparable) + { + CHECK(*static_cast(it->value) == *static_cast(insertedValue)); + } + + REQUIRE(view.length() == length + 1); + view.erase(it); + REQUIRE(view.length() == length); + REQUIRE(view.find(insertedKey) == view.end()); + } + + if (trait.hasInsertMove()) + { + REQUIRE(view.find(insertedKey) == view.end()); + view.insertMove(insertedKey, insertedValue); + auto it = view.find(insertedKey); + REQUIRE(it != view.end()); + + if constexpr (std::equality_comparable) + { + CHECK(*static_cast(it->key) == *insertedKey); + } + + REQUIRE(view.length() == length + 1); + view.erase(it); + REQUIRE(view.length() == length); + REQUIRE(view.find(insertedKey) == view.end()); + } +}