You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
6.1 KiB
165 lines
6.1 KiB
/* Copyright (c) 2015-2016 The Khronos Group Inc.
|
|
* Copyright (c) 2015-2016 Valve Corporation
|
|
* Copyright (c) 2015-2016 LunarG, Inc.
|
|
* Copyright (C) 2015-2016 Google Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* Author: John Zulauf <jzulauf@lunarg.com>
|
|
*/
|
|
#ifndef HASH_UTIL_H_
|
|
#define HASH_UTIL_H_
|
|
|
|
#define NOMINMAX
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <type_traits>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
// Hash and equality utilities for supporting hashing containers (e.g. unordered_set, unordered_map)
|
|
namespace hash_util {
|
|
|
|
// True iff both pointers are null or both are non-null
|
|
template <typename T>
|
|
bool similar_for_nullity(const T *const lhs, const T *const rhs) {
|
|
return ((lhs != nullptr) && (rhs != nullptr)) || ((lhs == nullptr) && (rhs == nullptr));
|
|
}
|
|
|
|
// Wrap std hash to avoid manual casts for the holes in std::hash (in C++11)
|
|
template <typename Value>
|
|
size_t HashWithUnderlying(Value value, typename std::enable_if<!std::is_enum<Value>::value, void *>::type = nullptr) {
|
|
return std::hash<Value>()(value);
|
|
}
|
|
|
|
template <typename Value>
|
|
size_t HashWithUnderlying(Value value, typename std::enable_if<std::is_enum<Value>::value, void *>::type = nullptr) {
|
|
using Underlying = typename std::underlying_type<Value>::type;
|
|
return std::hash<Underlying>()(static_cast<const Underlying &>(value));
|
|
}
|
|
|
|
class HashCombiner {
|
|
public:
|
|
using Key = size_t;
|
|
|
|
template <typename Value>
|
|
struct WrappedHash {
|
|
size_t operator()(const Value &value) const { return HashWithUnderlying(value); }
|
|
};
|
|
|
|
HashCombiner(Key combined = 0) : combined_(combined) {}
|
|
// magic and combination algorithm based on boost::hash_combine
|
|
// http://www.boost.org/doc/libs/1_43_0/doc/html/hash/reference.html#boost.hash_combine
|
|
// Magic value is 2^size / ((1-sqrt(5)/2)
|
|
static const uint64_t kMagic = sizeof(Key) > 4 ? Key(0x9e3779b97f4a7c16UL) : Key(0x9e3779b9U);
|
|
|
|
// If you need to override the default hash
|
|
template <typename Value, typename Hasher = WrappedHash<Value>>
|
|
HashCombiner &Combine(const Value &value) {
|
|
combined_ ^= Hasher()(value) + kMagic + (combined_ << 6) + (combined_ >> 2);
|
|
return *this;
|
|
}
|
|
|
|
template <typename Iterator, typename Hasher = WrappedHash<typename std::iterator_traits<Iterator>::value_type>>
|
|
HashCombiner &Combine(Iterator first, Iterator end) {
|
|
using Value = typename std::iterator_traits<Iterator>::value_type;
|
|
auto current = first;
|
|
for (; current != end; ++current) {
|
|
Combine<Value, Hasher>(*current);
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
template <typename Value, typename Hasher = WrappedHash<Value>>
|
|
HashCombiner &Combine(const std::vector<Value> &vector) {
|
|
return Combine(vector.cbegin(), vector.cend());
|
|
}
|
|
|
|
template <typename Value>
|
|
HashCombiner &operator<<(const Value &value) {
|
|
return Combine(value);
|
|
}
|
|
|
|
Key Value() const { return combined_; }
|
|
void Reset(Key combined = 0) { combined_ = combined; }
|
|
|
|
private:
|
|
Key combined_;
|
|
};
|
|
|
|
// A template to inherit std::hash overloads from when T::hash() is defined
|
|
template <typename T>
|
|
struct HasHashMember {
|
|
size_t operator()(const T &value) const { return value.hash(); }
|
|
};
|
|
|
|
// A template to inherit std::hash overloads from when is an *ordered* constainer
|
|
template <typename T>
|
|
struct IsOrderedContainer {
|
|
size_t operator()(const T &value) const { return HashCombiner().Combine(value.cbegin(), value.cend()).Value(); }
|
|
};
|
|
|
|
// The dictionary provides a way of referencing canonical/reference
|
|
// data by id, such that the id's are invariant with dictionary
|
|
// resize/insert and that no entries point to identical data. This
|
|
// approach uses the address of the unique data and as the unique
|
|
// ID for a give value of T.
|
|
//
|
|
// Note: This ID is unique for a given application execution, neither
|
|
// globally unique, invariant, nor repeatable from execution to
|
|
// execution.
|
|
//
|
|
// The entries of the dictionary are shared_pointers (the contents of
|
|
// which are invariant with resize/insert), with the hash and equality
|
|
// template arguments wrapped in a shared pointer dereferencing
|
|
// function object
|
|
template <typename T, typename Hasher = std::hash<T>, typename KeyEqual = std::equal_to<T>>
|
|
class Dictionary {
|
|
public:
|
|
using Def = T;
|
|
using Id = std::shared_ptr<const Def>;
|
|
|
|
// Find the unique entry match the provided value, adding if needed
|
|
// TODO: segregate lookup from insert, using reader/write locks to reduce contention -- if needed
|
|
template <typename U = T>
|
|
Id look_up(U &&value) {
|
|
// We create an Id from the value, which will either be retained by dict (if new) or deleted on return (if extant)
|
|
Id from_input = std::make_shared<T>(std::forward<U>(value));
|
|
|
|
// Insert takes care of the "unique" id part by rejecting the insert if a key matching by_value exists, but returning us
|
|
// the Id of the extant shared_pointer(id->def) instead.
|
|
// return the value of the Iterator from the <Iterator, bool> pair returned by insert
|
|
Guard g(lock); // Dict isn't thread safe, and use is presumed to be multi-threaded
|
|
return *dict.insert(from_input).first;
|
|
}
|
|
|
|
private:
|
|
struct HashKeyValue {
|
|
size_t operator()(const Id &value) const { return Hasher()(*value); }
|
|
};
|
|
struct KeyValueEqual {
|
|
bool operator()(const Id &lhs, const Id &rhs) const { return KeyEqual()(*lhs, *rhs); }
|
|
};
|
|
using Dict = std::unordered_set<Id, HashKeyValue, KeyValueEqual>;
|
|
using Lock = std::mutex;
|
|
using Guard = std::lock_guard<Lock>;
|
|
Lock lock;
|
|
Dict dict;
|
|
};
|
|
} // namespace hash_util
|
|
|
|
#endif // HASH_UTILS_H_
|