/* * SPDX-FileCopyrightText: Dora "cat" * SPDX-License-Identifier: MPL-2.0 * * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can * obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef GUARD_TOURMALINE_DUALKEYMAP_H #define GUARD_TOURMALINE_DUALKEYMAP_H #include "../Systems/Logging.hpp" #include "Hashing.hpp" #include #include #include #include #include #include #include #include #include namespace Tourmaline::Containers { template class DualkeyMap { public: using ResultPair = std::pair, std::reference_wrapper>, Value &>; DualkeyMap() { hashList.reserve(baseReservation); } ~DualkeyMap() { // I'm sure there is a better way to do this for (DualkeyHash *hash : hashList) { if (hash != nullptr) [[likely]] { delete hash; } } } void insert(AKey firstKey, BKey secondKey, Value value) { std::size_t firstKeyHash = std::hash{}(firstKey); std::size_t secondKeyHash = std::hash{}(secondKey); DualkeyHash *hash = new DualkeyHash(firstKeyHash, std::move(firstKey), secondKeyHash, std::move(secondKey), std::move(value)); if (graveyard.empty()) { hashList.push_back(hash); } else { std::size_t tombstone = graveyard.front(); graveyard.pop(); hashList[tombstone] = hash; } } std::size_t remove(std::optional firstKey, std::optional secondKey) { bool isFirstKeyGiven = firstKey.has_value(); bool isSecondKeyGiven = secondKey.has_value(); if (!isFirstKeyGiven && !isSecondKeyGiven) [[unlikely]] { Systems::Logging::Log("Failed to Delete! Dualkey maps require at least 1 " "key to be given, doing nothing.", "Dualkey Map", Systems::Logging::LogLevel::Warning); return 0; } std::size_t firstKeyHash = isFirstKeyGiven ? std::hash{}(firstKey.value()) : 0; std::size_t secondKeyHash = isSecondKeyGiven ? std::hash{}(secondKey.value()) : 0; std::size_t index = 0, amountDeleted = 0; uint8_t stateOfIndexing = isFirstKeyGiven + (isSecondKeyGiven << 1); for (DualkeyHash *hash : hashList) { // Tombstone if (hash == nullptr) [[unlikely]] { continue; } switch (stateOfIndexing) { case 1: // Only first key is given if (firstKeyHash == hash->firstKeyHash && firstKey.value() == hash->firstKey) { delete hash; hashList[index] = nullptr; graveyard.push(index); ++amountDeleted; } break; case 2: // Only second key is given if (secondKeyHash == hash->secondKeyHash && secondKey.value() == hash->secondKey) { delete hash; hashList[index] = nullptr; graveyard.push(index); ++amountDeleted; } break; case 3: if (firstKeyHash == hash->firstKeyHash && secondKeyHash == hash->secondKeyHash && firstKey.value() == hash->firstKey && secondKey.value() == hash->secondKey) { delete hash; hashList[index] = nullptr; graveyard.push(index); return 1; } break; } ++index; } return amountDeleted; } [[nodiscard("Discarding an expensive operation's result!")]] std::vector query(std::optional firstKey, std::optional secondKey) { bool isFirstKeyGiven = firstKey.has_value(); bool isSecondKeyGiven = secondKey.has_value(); if (!isFirstKeyGiven && !isSecondKeyGiven) [[unlikely]] { Systems::Logging::Log("Failed to Query! Dualkey maps require at least 1 " "key to be given, returning an empty vector.", "Dualkey Map", Systems::Logging::LogLevel::Warning); return {}; } std::size_t firstKeyHash = isFirstKeyGiven ? std::hash{}(firstKey.value()) : 0; std::size_t secondKeyHash = isSecondKeyGiven ? std::hash{}(secondKey.value()) : 0; std::vector finishedQuery{}; uint8_t stateOfIndexing = isFirstKeyGiven + (isSecondKeyGiven << 1); // Putting hash checks first to benefit from short circuits for (DualkeyHash *hash : hashList) { // Tombstone if (hash == nullptr) [[unlikely]] { continue; } switch (stateOfIndexing) { case 1: // Only first key is given if (firstKeyHash == hash->firstKeyHash && firstKey.value() == hash->firstKey) { finishedQuery.emplace_back(std::cref(hash->secondKey), hash->value); } continue; case 2: // Only second key is given if (secondKeyHash == hash->secondKeyHash && secondKey.value() == hash->secondKey) { finishedQuery.emplace_back(std::cref(hash->firstKey), hash->value); } continue; case 3: // Both are given if (firstKeyHash == hash->firstKeyHash && secondKeyHash == hash->secondKeyHash && firstKey.value() == hash->firstKey && secondKey.value() == hash->secondKey) { finishedQuery.emplace_back(std::monostate{}, hash->value); break; } continue; } break; } return finishedQuery; } [[nodiscard]] std::size_t count() { return hashList.size() - graveyard.size(); } // No copying, No moving. Moving may be valid in the future. // However as of now it is not a wise way to use this map. DualkeyMap(const DualkeyMap &) = delete; DualkeyMap(DualkeyMap &&) = delete; DualkeyMap &operator=(const DualkeyMap &) = delete; DualkeyMap &operator=(DualkeyMap &&) = delete; private: struct DualkeyHash { DualkeyHash(std::size_t firstKeyHash, AKey &&firstKey, std::size_t secondKeyHash, BKey &&secondKey, Value &&value) : firstKeyHash(firstKeyHash), firstKey(std::move(firstKey)), secondKeyHash(secondKeyHash), secondKey(std::move(secondKey)), value(std::move(value)) {} std::size_t firstKeyHash = 0; std::size_t secondKeyHash = 0; const AKey firstKey; const BKey secondKey; mutable Value value; }; // It makes more sense to store the individual hash std::vector hashList; std::queue graveyard; }; } // namespace Tourmaline::Containers #endif