blob: 58f653d910432f9f93d2335b93c24fc48d2a0350 [file] [log] [blame]
// Copyright 2022 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef SRC_TINT_UTILS_CONTAINERS_HASHMAP_H_
#define SRC_TINT_UTILS_CONTAINERS_HASHMAP_H_
#include <functional>
#include <optional>
#include <utility>
#include "src/tint/utils/containers/hashmap_base.h"
#include "src/tint/utils/containers/vector.h"
#include "src/tint/utils/ice/ice.h"
#include "src/tint/utils/math/hash.h"
namespace tint {
/// HashmapEntry is a key-value pair used by Hashmap as the Entry datatype.
template <typename KEY, typename VALUE>
struct HashmapEntry {
/// The key type
using Key = KEY;
/// The value type
using Value = VALUE;
/// @param entry a HashmapEntry
/// @return `entry.key`
static const Key& KeyOf(const HashmapEntry& entry) { return entry.key; }
/// The key
Key key;
/// The value
Value value;
};
/// Equality operator for HashmapEntry
/// @param lhs the LHS HashmapEntry
/// @param rhs the RHS HashmapEntry
/// @return true if both entries have equal keys and values.
template <typename K1, typename V1, typename K2, typename V2>
inline static bool operator==(const HashmapEntry<K1, V1>& lhs, const HashmapEntry<K2, V2>& rhs) {
return lhs.key == rhs.key && lhs.value == rhs.value;
}
/// Writes the HashmapEntry to the stream.
/// @param out the stream to write to
/// @param key_value the HashmapEntry to write
/// @returns out so calls can be chained
template <typename STREAM,
typename KEY,
typename VALUE,
typename = traits::EnableIfIsOStream<STREAM>>
auto& operator<<(STREAM& out, const HashmapEntry<KEY, VALUE>& key_value) {
return out << "[" << key_value.key << ": " << key_value.value << "]";
}
/// The return value of Hashmap::Get(Key).
/// GetResult supports equality operators and acts similarly to std::optional, but does not make a
/// copy.
template <typename T>
struct GetResult {
/// The value found in the map, or null if the entry was not found.
/// This pointer is guaranteed to be valid until the owning entry is removed, the map is
/// cleared, or the map is destructed.
T* value = nullptr;
/// @returns `true` if #value is not null.
operator bool() const { return value; }
/// @returns the dereferenced value, which must not be null.
T& operator*() const { return *value; }
/// @returns the pointer to the value, which must not be null.
T* operator->() const { return value; }
/// @param other the value to compare against the object that #value points to.
/// @returns true if #value is not null and the object that #value points to is equal to @p
/// other.
template <typename O>
bool operator==(const O& other) const {
return value && *value == other;
}
/// @param other the value to compare against the object that #value points to.
/// @returns true if #value is null or the object that #value points to is not equal to @p
/// other.
template <typename O>
bool operator!=(const O& other) const {
return !value || *value != other;
}
};
/// The return value of Hashmap::Add(Key, Value)
template <typename T>
struct AddResult {
/// A reference to the value of the entry with the given key.
/// If an existing entry was found with the key, then this is the value of the existing entry,
/// otherwise the value of the newly inserted entry.
/// This reference is guaranteed to be valid until the owning entry is removed, the map is
/// cleared, or the map is destructed.
T& value;
/// True if an entry did not already exist in the map with the given key.
bool added = false;
/// @returns #added
operator bool() const { return added; }
};
/// An unordered hashmap, with a fixed-size capacity that avoids heap allocations.
template <typename KEY,
typename VALUE,
size_t N,
typename HASH = Hasher<KEY>,
typename EQUAL = EqualTo<KEY>>
class Hashmap : public HashmapBase<HashmapEntry<HashmapKey<KEY, HASH, EQUAL>, VALUE>, N> {
using Base = HashmapBase<HashmapEntry<HashmapKey<KEY, HASH, EQUAL>, VALUE>, N>;
public:
/// The key type
using Key = typename Base::Key;
/// The value type
using Value = VALUE;
/// The key-value type for a map entry
using Entry = HashmapEntry<Key, Value>;
/// Add attempts to insert a new entry into the map.
/// If an existing entry exists with the given key, then the entry is not replaced.
/// @param key the new entry's key
/// @param value the new entry's value
/// @return an AddResult.
template <typename K, typename V>
AddResult<Value> Add(K&& key, V&& value) {
if (auto idx = this->EditAt(key); idx.entry) {
return {idx.entry->value, /* added */ false};
} else {
idx.Insert(std::forward<K>(key), std::forward<V>(value));
return {idx.entry->value, /* added */ true};
}
}
/// Inserts a new entry into the map or updates an existing entry.
/// @param key the new entry's key
/// @param value the new entry's value
template <typename K, typename V>
void Replace(K&& key, V&& value) {
if (auto idx = this->EditAt(key); idx.entry) {
idx.Replace(std::forward<K>(key), std::forward<V>(value));
} else {
idx.Insert(std::forward<K>(key), std::forward<V>(value));
}
}
/// Looks up an entry with the given key.
/// @param key the entry's key to search for.
/// @returns a GetResult holding the found entry's value, or null if the entry was not found.
template <typename K>
GetResult<Value> Get(K&& key) {
if (auto* entry = this->GetEntry(key)) {
return {&entry->value};
}
return {nullptr};
}
/// Looks up an entry with the given key.
/// @param key the entry's key to search for.
/// @returns a GetResult holding the found entry's value, or null if the entry was not found.
template <typename K>
GetResult<const Value> Get(K&& key) const {
if (auto* entry = this->GetEntry(key)) {
return {&entry->value};
}
return {nullptr};
}
/// Searches for an entry with the given key value returning that value if found, otherwise
/// returns @p not_found.
/// @param key the entry's key value to search for.
/// @param not_found the value to return if a node is not found.
/// @returns the a reference to the value of the entry, if found otherwise @p not_found.
/// @note The returned reference is guaranteed to be valid until the owning entry is removed,
/// the map is cleared, or the map is destructed.
template <typename K>
const Value& GetOr(K&& key, const Value& not_found) const {
if (auto* entry = this->GetEntry(key)) {
return entry->value;
}
return not_found;
}
/// Searches for an entry with the given key, returning a reference to that entry if found,
/// otherwise a reference to a newly added entry with the key @p key and the value from calling
/// @p create.
/// @note: Before calling `create`, the map will insert a zero-initialized value for the given
/// key, which will be replaced with the value returned by @p create. If @p create adds an entry
/// with @p key to this map, it will be replaced.
/// @param key the entry's key value to search for.
/// @param create the create function to call if the map does not contain the key. Must have the
/// signature `Key()`.
/// @returns a reference to the existing entry, or the newly added entry.
/// @note The returned reference is guaranteed to be valid until the owning entry is removed,
/// the map is cleared, or the map is destructed.
template <typename K, typename CREATE>
Entry& GetOrAddEntry(K&& key, CREATE&& create) {
auto idx = this->EditAt(key);
if (!idx.entry) {
idx.Insert(std::forward<K>(key), Value{});
idx.entry->value = create();
}
return *idx.entry;
}
/// Searches for an entry with the given key, returning a reference to that entry if found,
/// otherwise a reference to a newly added entry with the key @p key and a zero value.
/// @param key the entry's key value to search for.
/// @returns a reference to the existing entry, or the newly added entry.
/// @note The returned reference is guaranteed to be valid until the owning entry is removed,
/// the map is cleared, or the map is destructed.
template <typename K>
Entry& GetOrAddZeroEntry(K&& key) {
auto idx = this->EditAt(key);
if (!idx.entry) {
idx.Insert(std::forward<K>(key), Value{});
}
return *idx.entry;
}
/// Searches for an entry with the given key, adding and returning the result of calling
/// @p create if the entry was not found.
/// @note: Before calling `create`, the map will insert a zero-initialized value for the given
/// key, which will be replaced with the value returned by @p create. If @p create adds an entry
/// with @p key to this map, it will be replaced.
/// @param key the entry's key value to search for.
/// @param create the create function to call if the map does not contain the key.
/// @returns the value of the entry.
/// @note The returned reference is guaranteed to be valid until the owning entry is removed,
/// the map is cleared, or the map is destructed.
template <typename K, typename CREATE>
Value& GetOrAdd(K&& key, CREATE&& create) {
return GetOrAddEntry(std::forward<K>(key), std::forward<CREATE>(create)).value;
}
/// Searches for an entry with the given key value, adding and returning a newly created
/// zero-initialized value if the entry was not found.
/// @param key the entry's key value to search for.
/// @returns the value of the entry.
/// @note The returned reference is guaranteed to be valid until the owning entry is removed,
/// the map is cleared, or the map is destructed.
template <typename K>
Value& GetOrAddZero(K&& key) {
return GetOrAddZeroEntry(std::forward<K>(key)).value;
}
/// @returns the keys of the map as a vector.
/// @note the order of the returned vector is non-deterministic between compilers.
template <size_t N2 = N>
Vector<KEY, N2> Keys() const {
Vector<KEY, N2> out;
out.Reserve(this->Count());
for (auto& it : *this) {
out.Push(it.key.Value());
}
return out;
}
/// @returns the values of the map as a vector
/// @note the order of the returned vector is non-deterministic between compilers.
template <size_t N2 = N>
Vector<Value, N2> Values() const {
Vector<Value, N2> out;
out.Reserve(this->Count());
for (auto& it : *this) {
out.Push(it.value);
}
return out;
}
/// Equality operator
/// @param other the other Hashmap to compare this Hashmap to
/// @returns true if this Hashmap has the same key and value pairs as @p other
template <typename K, typename V, size_t N2>
bool operator==(const Hashmap<K, V, N2>& other) const {
if (this->Count() != other.Count()) {
return false;
}
for (auto& it : *this) {
auto other_val = other.Get(it.key.Value());
if (!other_val || it.value != *other_val) {
return false;
}
}
return true;
}
/// Inequality operator
/// @param other the other Hashmap to compare this Hashmap to
/// @returns false if this Hashmap has the same key and value pairs as @p other
template <typename K, typename V, size_t N2>
bool operator!=(const Hashmap<K, V, N2>& other) const {
return !(*this == other);
}
};
/// Hasher specialization for Hashmap
template <typename K, typename V, size_t N, typename HASH, typename EQUAL>
struct Hasher<Hashmap<K, V, N, HASH, EQUAL>> {
/// @param map the Hashmap to hash
/// @returns a hash of the map
size_t operator()(const Hashmap<K, V, N, HASH, EQUAL>& map) const {
auto hash = Hash(map.Count());
for (auto it : map) {
// Use an XOR to ensure that the non-deterministic ordering of the map still produces
// the same hash value for the same entries.
hash ^= Hash(it.key.Value(), it.value);
}
return hash;
}
};
/// Writes the Hashmap to the stream.
/// @param out the stream to write to
/// @param map the Hashmap to write
/// @returns out so calls can be chained
template <typename STREAM,
typename KEY,
typename VALUE,
size_t N,
typename HASH,
typename EQUAL,
typename = traits::EnableIfIsOStream<STREAM>>
auto& operator<<(STREAM& out, const Hashmap<KEY, VALUE, N, HASH, EQUAL>& map) {
out << "Hashmap{";
bool first = true;
for (auto it : map) {
if (!first) {
out << ", ";
}
first = false;
out << it;
}
out << "}";
return out;
}
} // namespace tint
#endif // SRC_TINT_UTILS_CONTAINERS_HASHMAP_H_