Skip to content

Commit

Permalink
Centralize transparent string hashing to TransparentStringViewHasher
Browse files Browse the repository at this point in the history
adamkewley committed Jul 15, 2024
1 parent 9467b80 commit bb8d313
Showing 8 changed files with 94 additions and 38 deletions.
1 change: 1 addition & 0 deletions src/oscar/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -402,6 +402,7 @@ add_library(oscar STATIC
Utils/TemporaryFile.cpp
Utils/TemporaryFile.h
Utils/TemporaryFileParameters.h
Utils/TransparentStringViewHasher.h
Utils/Typelist.h
Utils/UID.cpp
Utils/UID.h
13 changes: 2 additions & 11 deletions src/oscar/Graphics/GraphicsImplementation.cpp
Original file line number Diff line number Diff line change
@@ -75,6 +75,7 @@
#include <oscar/Utils/ObjectRepresentation.h>
#include <oscar/Utils/Perf.h>
#include <oscar/Utils/StdVariantHelpers.h>
#include <oscar/Utils/TransparentStringViewHasher.h>
#include <oscar/Utils/UID.h>

#include <GL/glew.h>
@@ -523,21 +524,11 @@ namespace
o << "ShadeElement(name = " << name << ", location = " << se.location << ", shader_type = " << se.shader_type << ", size = " << se.size << ')';
}

// see: ankerl/unordered_dense documentation for heterogeneous lookups
struct transparent_string_hash final {
using is_transparent = void;
using is_avalanching = void;

[[nodiscard]] auto operator()(std::string_view str) const -> uint64_t {
return ankerl::unordered_dense::hash<std::string_view>{}(str);
}
};

template<typename Value>
using FastStringHashtable = ankerl::unordered_dense::map<
std::string,
Value,
transparent_string_hash,
TransparentStringViewHasher,
std::equal_to<>
>;
}
13 changes: 2 additions & 11 deletions src/oscar/Platform/AppSettings.cpp
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
#include <oscar/Utils/EnumHelpers.h>
#include <oscar/Utils/HashHelpers.h>
#include <oscar/Utils/SynchronizedValue.h>
#include <oscar/Utils/TransparentStringViewHasher.h>
#include <oscar/Variant/Variant.h>
#include <oscar/Variant/VariantType.h>

@@ -98,20 +99,10 @@ R"(# configuration options
auto end() const { return hashmap_.end(); }
private:

// see: ankerl/unordered_dense documentation for heterogeneous lookups
struct transparent_string_hash final {
using is_transparent = void;
using is_avalanching = void;

[[nodiscard]] auto operator()(std::string_view str) const -> uint64_t {
return ankerl::unordered_dense::hash<std::string_view>{}(str);
}
};

using Storage = ankerl::unordered_dense::map<
std::string,
AppSettingsLookupValue,
transparent_string_hash,
TransparentStringViewHasher,
std::equal_to<>
>;
Storage hashmap_;
1 change: 1 addition & 0 deletions src/oscar/Utils.h
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@
#include <oscar/Utils/SynchronizedValueGuard.h>
#include <oscar/Utils/TemporaryFile.h>
#include <oscar/Utils/TemporaryFileParameters.h>
#include <oscar/Utils/TransparentStringViewHasher.h>
#include <oscar/Utils/Typelist.h>
#include <oscar/Utils/UID.h>
#include <oscar/Utils/UndoRedo.h>
21 changes: 6 additions & 15 deletions src/oscar/Utils/StringName.cpp
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
#include <ankerl/unordered_dense.h>

#include <oscar/Utils/SynchronizedValue.h>
#include <oscar/Utils/TransparentStringViewHasher.h>

#include <concepts>
#include <functional>
@@ -41,28 +42,18 @@ namespace
{
return *ptr_;
}
private:
std::unique_ptr<StringNameData> ptr_;
};

struct StringNameLutHasher final {
using is_transparent = void;
using is_avalanching = void;

[[nodiscard]] auto operator()(std::string_view str) const noexcept -> uint64_t
operator std::string_view () const
{
return ankerl::unordered_dense::hash<std::string_view>{}(str);
}

[[nodiscard]] auto operator()(const StringNameDataPtr& ptr) const noexcept -> uint64_t
{
return ankerl::unordered_dense::hash<std::string_view>{}(ptr->value());
return ptr_->value();
}
private:
std::unique_ptr<StringNameData> ptr_;
};

using StringNameLookup = ankerl::unordered_dense::set<
StringNameDataPtr,
StringNameLutHasher,
TransparentStringViewHasher,
std::equal_to<>
>;

23 changes: 23 additions & 0 deletions src/oscar/Utils/TransparentStringViewHasher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

#include <cstddef>
#include <string_view>

namespace osc
{
// a `std::hash`-like object that can transparently hash any object that is
// implicitly convertible to a `std::string_view`
struct TransparentStringViewHasher final {

// important: this is how associative containers check that the hasher
// doesn't need to create keys at runtime (e.g. using a `std::string_view`
// to `.find` an `std::unordered_map<std::string, T>` won't require
// constructing a `std::string`)
using is_transparent = void;

size_t operator()(std::string_view sv) const noexcept
{
return std::hash<std::string_view>{}(sv);
}
};
}
3 changes: 2 additions & 1 deletion tests/testoscar/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -96,6 +96,7 @@ add_executable(testoscar
Utils/TestStringHelpers.cpp
Utils/TestStringName.cpp
Utils/TestTemporaryFile.cpp
Utils/TestTransparentStringViewHasher.cpp
Utils/TestTypelist.cpp

Variant/TestVariant.cpp
@@ -104,7 +105,7 @@ add_executable(testoscar
TestingHelpers.cpp
TestingHelpers.h
testoscar.cpp # entry point
)
)


configure_file(
57 changes: 57 additions & 0 deletions tests/testoscar/Utils/TestTransparentStringViewHasher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include <oscar/Utils/TransparentStringViewHasher.h>

#include <oscar/Utils/CStringView.h>
#include <oscar/Utils/StringName.h>
#include <gtest/gtest.h>

#include <algorithm>
#include <array>
#include <functional>
#include <string>
#include <string_view>
#include <unordered_map>

using namespace osc;
namespace rgs = std::ranges;

namespace
{
using TransparentMap = std::unordered_map<std::string, int, TransparentStringViewHasher, std::equal_to<>>;
}

TEST(TransparentStringViewHasher, can_construct_std_unordered_map_that_uses_transparent_string_hasher)
{
[[maybe_unused]] TransparentMap map; // this should work
}

TEST(TransparentStringViewHasher, transparent_unordered_map_enables_std_string_view_lookups)
{
TransparentMap map;
[[maybe_unused]] const auto it = map.find(std::string_view{"i don't need to be converted into a std::string :)"});
}

TEST(TransparentStringViewHasher, transparent_unordered_map_enables_CStringView_lookups)
{
TransparentMap map;
[[maybe_unused]] const auto it = map.find(CStringView{"i don't need to be converted into a std::string :)"});
}

TEST(TransparentStringViewHasher, transparent_unordered_map_enables_StringName_lookups)
{
TransparentMap map;
[[maybe_unused]] const auto it = map.find(StringName{"i don't need to be converted into a std::string :)"});
}

TEST(TransparentStringViewHasher, produces_same_hash_for_all_of_OSCs_string_types)
{
for (const char* str : {"", "some string", "why not try three?"}) {
const auto hashes = std::to_array({
TransparentStringViewHasher{}(str),
TransparentStringViewHasher{}(std::string_view{str}),
TransparentStringViewHasher{}(CStringView{str}),
TransparentStringViewHasher{}(std::string{str}),
TransparentStringViewHasher{}(StringName{str}),
});
ASSERT_TRUE(rgs::equal_range(hashes, hashes.front()));
}
}

0 comments on commit bb8d313

Please sign in to comment.