Introduce til::hasher for sucessive hashing of structs (#11887)
This commit serves two purposes: * Simplify construction of hashes for non-trivial structs This is especially helpful for ActionArgs * Improve hash quality by not needlessly throwing away entropy `til::hasher` is modeled after Rust's `std:#️⃣:Hasher` and works similar. The idea is simple: A stateful hash function can hash multiple unrelated fields, without loosing entropy by running a finalizer after hashing each interim field. This is especially useful for modern hash functions, which often have a wider internal state than the output width. Additionally this improves performance for hash functions with complex finalizers. Most of this is of course a bit moot right now, considering that `til::hasher` is still based on STL's FNV1a algorithm, which offers a very poor hash quality. But counterintuitively, FNV1a actually benefits most from this PR: Since it lacks a finalizer entirely, this commit greatly improves hash quality as it encodes more data into FNV's state and thus improves randomness. ## PR Checklist * [x] I work here * [x] Tests added/passed ## Validation Steps Performed * No unusual behavior ✅
This commit is contained in:
parent
91ad17d439
commit
00fe2b02bf
|
@ -189,7 +189,6 @@ cacafire
|
|||
callee
|
||||
capslock
|
||||
CARETBLINKINGENABLED
|
||||
carlos
|
||||
CARRIAGERETURN
|
||||
cascadia
|
||||
cassert
|
||||
|
@ -635,7 +634,7 @@ doskey
|
|||
dotnet
|
||||
doubleclick
|
||||
downlevel
|
||||
dpg
|
||||
DPG
|
||||
dpi
|
||||
DPIAPI
|
||||
DPICHANGE
|
||||
|
@ -793,6 +792,7 @@ FIXEDCONVERTED
|
|||
FIXEDFILEINFO
|
||||
Flg
|
||||
flyout
|
||||
fmix
|
||||
fmodern
|
||||
fmtarg
|
||||
fmtid
|
||||
|
@ -1471,7 +1471,6 @@ msix
|
|||
msrc
|
||||
msvcrt
|
||||
MSVCRTD
|
||||
MSVS
|
||||
msys
|
||||
msysgit
|
||||
MTSM
|
||||
|
@ -2174,7 +2173,6 @@ SHOWNOACTIVATE
|
|||
SHOWNORMAL
|
||||
SHOWWINDOW
|
||||
SHRT
|
||||
sid
|
||||
sidebyside
|
||||
SIF
|
||||
SIGDN
|
||||
|
@ -2205,7 +2203,6 @@ somefile
|
|||
SOURCEBRANCH
|
||||
sourced
|
||||
SOURCESDIRECTORY
|
||||
SPACEBAR
|
||||
spammy
|
||||
spand
|
||||
sprintf
|
||||
|
@ -2510,7 +2507,6 @@ unmark
|
|||
UNORM
|
||||
unparseable
|
||||
unpause
|
||||
Unregister
|
||||
unregistering
|
||||
untests
|
||||
untextured
|
||||
|
@ -2523,7 +2519,6 @@ upvote
|
|||
uri
|
||||
url
|
||||
urlencoded
|
||||
Urxvt
|
||||
USASCII
|
||||
usebackq
|
||||
USECALLBACK
|
||||
|
@ -2568,7 +2563,6 @@ Vcount
|
|||
vcpkg
|
||||
vcprintf
|
||||
vcproj
|
||||
vcvarsall
|
||||
vcxitems
|
||||
vcxproj
|
||||
vec
|
||||
|
@ -2825,7 +2819,6 @@ xes
|
|||
xff
|
||||
XFile
|
||||
XFORM
|
||||
xIcon
|
||||
XManifest
|
||||
XMath
|
||||
XMFLOAT
|
||||
|
@ -2858,7 +2851,6 @@ YCast
|
|||
YCENTER
|
||||
YCount
|
||||
YDPI
|
||||
yIcon
|
||||
yml
|
||||
YOffset
|
||||
YPosition
|
||||
|
@ -2866,7 +2858,6 @@ YSize
|
|||
YSubstantial
|
||||
YVIRTUALSCREEN
|
||||
YWalk
|
||||
zamora
|
||||
ZCmd
|
||||
ZCtrl
|
||||
zsh
|
||||
|
|
|
@ -14,9 +14,11 @@ Author(s):
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <climits>
|
||||
#include <vector>
|
||||
|
||||
#include <til/bit.h>
|
||||
#include <til/hash.h>
|
||||
|
||||
// std::unordered_map needs help to know how to hash a COORD
|
||||
namespace std
|
||||
|
@ -33,10 +35,7 @@ namespace std
|
|||
// - the hashed coord
|
||||
constexpr size_t operator()(const COORD& coord) const noexcept
|
||||
{
|
||||
size_t retVal = coord.Y;
|
||||
const size_t xCoord = coord.X;
|
||||
retVal |= xCoord << (sizeof(coord.Y) * CHAR_BIT);
|
||||
return retVal;
|
||||
return til::hash(til::bit_cast<uint32_t>(coord));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2447,7 +2447,7 @@ uint16_t TextBuffer::GetHyperlinkId(std::wstring_view uri, std::wstring_view id)
|
|||
// assign _currentHyperlinkId if the custom id does not already exist
|
||||
std::wstring newId{ id };
|
||||
// hash the URL and add it to the custom ID - GH#7698
|
||||
newId += L"%" + std::to_wstring(std::hash<std::wstring_view>{}(uri));
|
||||
newId += L"%" + std::to_wstring(til::hash(uri));
|
||||
const auto result = _hyperlinkCustomIdMap.emplace(newId, _currentHyperlinkId);
|
||||
if (result.second)
|
||||
{
|
||||
|
|
|
@ -62,27 +62,6 @@ private:
|
|||
// * ActionEventArgs holds a single IActionArgs. For events that don't need
|
||||
// additional args, this can be nullptr.
|
||||
|
||||
template<>
|
||||
constexpr size_t Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(const winrt::Microsoft::Terminal::Settings::Model::IActionArgs& args)
|
||||
{
|
||||
return gsl::narrow_cast<size_t>(args.Hash());
|
||||
}
|
||||
|
||||
template<>
|
||||
constexpr size_t Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(const winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs& args)
|
||||
{
|
||||
return gsl::narrow_cast<size_t>(args.Hash());
|
||||
}
|
||||
|
||||
// Retrieves the hash value for an empty-constructed object.
|
||||
template<typename T>
|
||||
static size_t EmptyHash()
|
||||
{
|
||||
// cache the value of the empty hash
|
||||
static const size_t cachedHash = winrt::make_self<T>()->Hash();
|
||||
return cachedHash;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN X-MACRO MADNESS
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -324,9 +303,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
copy->_ColorScheme = _ColorScheme;
|
||||
return *copy;
|
||||
}
|
||||
size_t Hash() const
|
||||
size_t Hash(uint64_t hasherState) const
|
||||
{
|
||||
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(Commandline(), StartingDirectory(), TabTitle(), TabColor(), ProfileIndex(), Profile(), SuppressApplicationTitle(), ColorScheme());
|
||||
til::hasher h{ gsl::narrow_cast<size_t>(hasherState) };
|
||||
h.write(Commandline());
|
||||
h.write(StartingDirectory());
|
||||
h.write(TabTitle());
|
||||
h.write(TabColor());
|
||||
h.write(ProfileIndex());
|
||||
h.write(Profile());
|
||||
h.write(SuppressApplicationTitle());
|
||||
h.write(ColorScheme());
|
||||
return h.finalize();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -375,9 +363,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
copy->_TerminalArgs = _TerminalArgs.Copy();
|
||||
return *copy;
|
||||
}
|
||||
size_t Hash() const
|
||||
size_t Hash(uint64_t hasherState) const
|
||||
{
|
||||
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(TerminalArgs());
|
||||
til::hasher h{ gsl::narrow_cast<size_t>(hasherState) };
|
||||
h.write(TerminalArgs());
|
||||
return h.finalize();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -459,9 +449,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
copy->_SplitSize = _SplitSize;
|
||||
return *copy;
|
||||
}
|
||||
size_t Hash() const
|
||||
size_t Hash(uint64_t hasherState) const
|
||||
{
|
||||
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(SplitDirection(), TerminalArgs(), SplitMode(), SplitSize());
|
||||
til::hasher h{ gsl::narrow_cast<size_t>(hasherState) };
|
||||
h.write(SplitDirection());
|
||||
h.write(TerminalArgs());
|
||||
h.write(SplitMode());
|
||||
h.write(SplitSize());
|
||||
return h.finalize();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -506,9 +501,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
copy->_TerminalArgs = _TerminalArgs.Copy();
|
||||
return *copy;
|
||||
}
|
||||
size_t Hash() const
|
||||
size_t Hash(uint64_t hasherState) const
|
||||
{
|
||||
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(TerminalArgs());
|
||||
til::hasher h{ gsl::narrow_cast<size_t>(hasherState) };
|
||||
h.write(TerminalArgs());
|
||||
return h.finalize();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -625,9 +622,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
copy->_Actions = _Actions;
|
||||
return *copy;
|
||||
}
|
||||
size_t Hash() const
|
||||
size_t Hash(uint64_t hasherState) const
|
||||
{
|
||||
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Actions);
|
||||
til::hasher h{ gsl::narrow_cast<size_t>(hasherState) };
|
||||
h.write(winrt::get_abi(_Actions));
|
||||
return h.finalize();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
Boolean Equals(IActionArgs other);
|
||||
String GenerateName();
|
||||
IActionArgs Copy();
|
||||
UInt64 Hash();
|
||||
UInt64 Hash(UInt64 hasher);
|
||||
};
|
||||
|
||||
interface IActionEventArgs
|
||||
|
@ -128,7 +128,7 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
Boolean Equals(NewTerminalArgs other);
|
||||
String GenerateName();
|
||||
String ToCommandline();
|
||||
UInt64 Hash();
|
||||
UInt64 Hash(UInt64 hasher);
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionEventArgs : IActionEventArgs
|
||||
|
|
|
@ -17,6 +17,8 @@ Author(s):
|
|||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// MACRO HACKS
|
||||
//
|
||||
// We want to have code that looks like:
|
||||
|
@ -96,7 +98,7 @@ struct InitListPlaceholder
|
|||
// definition of Hash() below, that's to deal with trailing commas (or in this
|
||||
// case, leading.)
|
||||
#define HASH_ARGS(type, name, jsonKey, required, ...) \
|
||||
, name()
|
||||
h.write(name());
|
||||
|
||||
// Use ACTION_ARGS_STRUCT when you've got no other customizing to do.
|
||||
#define ACTION_ARGS_STRUCT(className, argsMacro) \
|
||||
|
@ -109,52 +111,53 @@ struct InitListPlaceholder
|
|||
// * NewTerminalArgs has a ToCommandline method it needs to additionally declare.
|
||||
// * GlobalSummonArgs has the QuakeModeFromJson helper
|
||||
|
||||
#define ACTION_ARG_BODY(className, argsMacro) \
|
||||
className() = default; \
|
||||
className( \
|
||||
argsMacro(CTOR_PARAMS) InitListPlaceholder = {}) : \
|
||||
argsMacro(CTOR_INIT) _placeholder{} {}; \
|
||||
argsMacro(DECLARE_ARGS); \
|
||||
\
|
||||
private: \
|
||||
InitListPlaceholder _placeholder; \
|
||||
\
|
||||
public: \
|
||||
hstring GenerateName() const; \
|
||||
bool Equals(const IActionArgs& other) \
|
||||
{ \
|
||||
auto otherAsUs = other.try_as<className>(); \
|
||||
if (otherAsUs) \
|
||||
{ \
|
||||
return true argsMacro(EQUALS_ARGS); \
|
||||
} \
|
||||
return false; \
|
||||
}; \
|
||||
static FromJsonResult FromJson(const Json::Value& json) \
|
||||
{ \
|
||||
auto args = winrt::make_self<className>(); \
|
||||
argsMacro(FROM_JSON_ARGS); \
|
||||
return { *args, {} }; \
|
||||
} \
|
||||
static Json::Value ToJson(const IActionArgs& val) \
|
||||
{ \
|
||||
if (!val) \
|
||||
{ \
|
||||
return {}; \
|
||||
} \
|
||||
Json::Value json{ Json::ValueType::objectValue }; \
|
||||
const auto args{ get_self<className>(val) }; \
|
||||
argsMacro(TO_JSON_ARGS); \
|
||||
return json; \
|
||||
} \
|
||||
IActionArgs Copy() const \
|
||||
{ \
|
||||
auto copy{ winrt::make_self<className>() }; \
|
||||
argsMacro(COPY_ARGS); \
|
||||
return *copy; \
|
||||
} \
|
||||
size_t Hash() const \
|
||||
{ \
|
||||
return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty( \
|
||||
0 argsMacro(HASH_ARGS)); \
|
||||
#define ACTION_ARG_BODY(className, argsMacro) \
|
||||
className() = default; \
|
||||
className( \
|
||||
argsMacro(CTOR_PARAMS) InitListPlaceholder = {}) : \
|
||||
argsMacro(CTOR_INIT) _placeholder{} {}; \
|
||||
argsMacro(DECLARE_ARGS); \
|
||||
\
|
||||
private: \
|
||||
InitListPlaceholder _placeholder; \
|
||||
\
|
||||
public: \
|
||||
hstring GenerateName() const; \
|
||||
bool Equals(const IActionArgs& other) \
|
||||
{ \
|
||||
auto otherAsUs = other.try_as<className>(); \
|
||||
if (otherAsUs) \
|
||||
{ \
|
||||
return true argsMacro(EQUALS_ARGS); \
|
||||
} \
|
||||
return false; \
|
||||
}; \
|
||||
static FromJsonResult FromJson(const Json::Value& json) \
|
||||
{ \
|
||||
auto args = winrt::make_self<className>(); \
|
||||
argsMacro(FROM_JSON_ARGS); \
|
||||
return { *args, {} }; \
|
||||
} \
|
||||
static Json::Value ToJson(const IActionArgs& val) \
|
||||
{ \
|
||||
if (!val) \
|
||||
{ \
|
||||
return {}; \
|
||||
} \
|
||||
Json::Value json{ Json::ValueType::objectValue }; \
|
||||
const auto args{ get_self<className>(val) }; \
|
||||
argsMacro(TO_JSON_ARGS); \
|
||||
return json; \
|
||||
} \
|
||||
IActionArgs Copy() const \
|
||||
{ \
|
||||
auto copy{ winrt::make_self<className>() }; \
|
||||
argsMacro(COPY_ARGS); \
|
||||
return *copy; \
|
||||
} \
|
||||
size_t Hash(uint64_t hasherState) const \
|
||||
{ \
|
||||
til::hasher h{ gsl::narrow_cast<size_t>(hasherState) }; \
|
||||
argsMacro(HASH_ARGS); \
|
||||
return h.finalize(); \
|
||||
}
|
||||
|
|
|
@ -15,36 +15,42 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
{
|
||||
static InternalActionID Hash(const Model::ActionAndArgs& actionAndArgs)
|
||||
{
|
||||
size_t hashedAction{ HashUtils::HashProperty(actionAndArgs.Action()) };
|
||||
static constexpr auto initialHash = til::hasher{}.finalize();
|
||||
const auto action = actionAndArgs.Action();
|
||||
til::hasher hasher;
|
||||
|
||||
size_t hashedArgs{};
|
||||
if (const auto& args{ actionAndArgs.Args() })
|
||||
if (const auto args = actionAndArgs.Args())
|
||||
{
|
||||
// Args are defined, so hash them
|
||||
hashedArgs = gsl::narrow_cast<size_t>(args.Hash());
|
||||
hasher = til::hasher{ gsl::narrow_cast<size_t>(args.Hash(initialHash)) };
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t hash = 0;
|
||||
|
||||
// Args are not defined.
|
||||
// Check if the ShortcutAction supports args.
|
||||
switch (actionAndArgs.Action())
|
||||
{
|
||||
#define ON_ALL_ACTIONS_WITH_ARGS(action) \
|
||||
case ShortcutAction::action: \
|
||||
/* If it does, hash the default values for the args.*/ \
|
||||
hashedArgs = EmptyHash<implementation::action##Args>(); \
|
||||
break;
|
||||
#define ON_ALL_ACTIONS_WITH_ARGS(action) \
|
||||
case ShortcutAction::action: \
|
||||
{ \
|
||||
/* If it does, hash the default values for the args. */ \
|
||||
static const auto cachedHash = gsl::narrow_cast<size_t>( \
|
||||
winrt::make_self<implementation::action##Args>()->Hash(initialHash)); \
|
||||
hash = cachedHash; \
|
||||
break; \
|
||||
}
|
||||
ALL_SHORTCUT_ACTIONS_WITH_ARGS
|
||||
#undef ON_ALL_ACTIONS_WITH_ARGS
|
||||
default:
|
||||
{
|
||||
// Otherwise, hash nullptr.
|
||||
std::hash<IActionArgs> argsHash;
|
||||
hashedArgs = argsHash(nullptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
hasher = til::hasher{ hash };
|
||||
}
|
||||
return hashedAction ^ hashedArgs;
|
||||
|
||||
hasher.write(action);
|
||||
return hasher.finalize();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
|
|
@ -17,54 +17,37 @@ Revision History:
|
|||
|
||||
#pragma once
|
||||
|
||||
namespace Microsoft::Terminal::Settings::Model::HashUtils
|
||||
#include <til/hash.h>
|
||||
|
||||
namespace til
|
||||
{
|
||||
// This is a helper template function for hashing multiple variables in conjunction to each other.
|
||||
template<typename T>
|
||||
constexpr size_t HashProperty(const T& val)
|
||||
struct hash_trait<winrt::Windows::Foundation::IReference<T>>
|
||||
{
|
||||
std::hash<T> hashFunc;
|
||||
return hashFunc(val);
|
||||
}
|
||||
|
||||
template<typename T, typename... Args>
|
||||
constexpr size_t HashProperty(const T& val, Args&&... more)
|
||||
{
|
||||
// Inspired by boost::hash_combine, which causes this effect...
|
||||
// seed ^= hash_value(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||
// Source: https://www.boost.org/doc/libs/1_35_0/doc/html/boost/hash_combine_id241013.html
|
||||
const auto seed{ HashProperty(val) };
|
||||
return seed ^ (0x9e3779b9 + (seed << 6) + (seed >> 2) + HashProperty(std::forward<Args>(more)...));
|
||||
}
|
||||
constexpr void operator()(hasher& h, const winrt::Windows::Foundation::IReference<T>& v) const noexcept
|
||||
{
|
||||
if (v)
|
||||
{
|
||||
h.write(v.Value());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
constexpr size_t HashProperty(const til::color& val)
|
||||
struct hash_trait<winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs>
|
||||
{
|
||||
return HashProperty(val.a, val.r, val.g, val.b);
|
||||
}
|
||||
void operator()(hasher& h, const winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs& value) const noexcept
|
||||
{
|
||||
h.write(winrt::get_abi(value));
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef WINRT_Windows_Foundation_H
|
||||
template<typename T>
|
||||
constexpr size_t HashProperty(const winrt::Windows::Foundation::IReference<T>& val)
|
||||
{
|
||||
return val ? HashProperty(val.Value()) : 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WINRT_Windows_UI_H
|
||||
template<>
|
||||
constexpr size_t HashProperty(const winrt::Windows::UI::Color& val)
|
||||
struct hash_trait<winrt::hstring>
|
||||
{
|
||||
return HashProperty(til::color{ val });
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WINRT_Microsoft_Terminal_Core_H
|
||||
template<>
|
||||
constexpr size_t HashProperty(const winrt::Microsoft::Terminal::Core::Color& val)
|
||||
{
|
||||
return HashProperty(til::color{ val });
|
||||
}
|
||||
#endif
|
||||
|
||||
};
|
||||
void operator()(hasher& h, const winrt::hstring& value) const noexcept
|
||||
{
|
||||
h.write(reinterpret_cast<const uint8_t*>(value.data()), value.size() * sizeof(wchar_t));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -93,6 +93,7 @@
|
|||
<ClInclude Include="VsSetupConfiguration.h">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ActionArgsMagic.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="ActionArgs.idl" />
|
||||
|
|
|
@ -2,19 +2,14 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "alias.h"
|
||||
|
||||
#include "_output.h"
|
||||
#include <til/hash.h>
|
||||
|
||||
#include "output.h"
|
||||
#include "stream.h"
|
||||
#include "_stream.h"
|
||||
#include "dbcs.h"
|
||||
#include "handle.h"
|
||||
#include "misc.h"
|
||||
#include "../types/inc/convert.hpp"
|
||||
#include "srvinit.h"
|
||||
#include "resource.h"
|
||||
|
||||
#include "ApiRoutines.h"
|
||||
|
||||
|
@ -28,10 +23,12 @@ struct case_insensitive_hash
|
|||
{
|
||||
std::size_t operator()(const std::wstring& key) const
|
||||
{
|
||||
std::wstring lower(key);
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(), ::towlower);
|
||||
std::hash<std::wstring> hash;
|
||||
return hash(lower);
|
||||
til::hasher h;
|
||||
for (const auto& ch : key)
|
||||
{
|
||||
h.write(::towlower(ch));
|
||||
}
|
||||
return h.finalize();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -2583,10 +2583,10 @@ void TextBufferTests::HyperlinkTrim()
|
|||
const TextAttribute attr{ 0x7f };
|
||||
auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
|
||||
|
||||
const auto url = L"test.url";
|
||||
const auto otherUrl = L"other.url";
|
||||
const auto customId = L"CustomId";
|
||||
const auto otherCustomId = L"OtherCustomId";
|
||||
static constexpr std::wstring_view url{ L"test.url" };
|
||||
static constexpr std::wstring_view otherUrl{ L"other.url" };
|
||||
static constexpr std::wstring_view customId{ L"CustomId" };
|
||||
static constexpr std::wstring_view otherCustomId{ L"OtherCustomId" };
|
||||
|
||||
// Set a hyperlink id in the first row and add a hyperlink to our map
|
||||
const COORD pos{ 70, 0 };
|
||||
|
@ -2606,8 +2606,8 @@ void TextBufferTests::HyperlinkTrim()
|
|||
// Increment the circular buffer
|
||||
_buffer->IncrementCircularBuffer();
|
||||
|
||||
const auto finalCustomId = fmt::format(L"{}%{}", customId, std::hash<std::wstring_view>{}(url));
|
||||
const auto finalOtherCustomId = fmt::format(L"{}%{}", otherCustomId, std::hash<std::wstring_view>{}(otherUrl));
|
||||
const auto finalCustomId = fmt::format(L"{}%{}", customId, til::hash(url));
|
||||
const auto finalOtherCustomId = fmt::format(L"{}%{}", otherCustomId, til::hash(otherUrl));
|
||||
|
||||
// The hyperlink reference that was only in the first row should be deleted from the map
|
||||
VERIFY_ARE_EQUAL(_buffer->_hyperlinkMap.find(id), _buffer->_hyperlinkMap.end());
|
||||
|
@ -2629,8 +2629,8 @@ void TextBufferTests::NoHyperlinkTrim()
|
|||
const TextAttribute attr{ 0x7f };
|
||||
auto _buffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, _renderTarget);
|
||||
|
||||
const auto url = L"test.url";
|
||||
const auto customId = L"CustomId";
|
||||
static constexpr std::wstring_view url{ L"test.url" };
|
||||
static constexpr std::wstring_view customId{ L"CustomId" };
|
||||
|
||||
// Set a hyperlink id in the first row and add a hyperlink to our map
|
||||
const COORD pos{ 70, 0 };
|
||||
|
@ -2647,7 +2647,7 @@ void TextBufferTests::NoHyperlinkTrim()
|
|||
// Increment the circular buffer
|
||||
_buffer->IncrementCircularBuffer();
|
||||
|
||||
const auto finalCustomId = fmt::format(L"{}%{}", customId, std::hash<std::wstring_view>{}(url));
|
||||
const auto finalCustomId = fmt::format(L"{}%{}", customId, til::hash(url));
|
||||
|
||||
// The hyperlink reference should not be deleted from the map since it is still present in the buffer
|
||||
VERIFY_ARE_EQUAL(_buffer->GetHyperlinkUriFromId(id), url);
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace til
|
||||
{
|
||||
template<typename T>
|
||||
struct hash_trait;
|
||||
|
||||
struct hasher
|
||||
{
|
||||
explicit constexpr hasher(size_t state = FNV_offset_basis) noexcept :
|
||||
_hash{ state } {}
|
||||
|
||||
template<typename T>
|
||||
constexpr void write(const T& v) noexcept
|
||||
{
|
||||
hash_trait<T>{}(*this, v);
|
||||
}
|
||||
|
||||
template<typename T, typename = std::enable_if_t<std::has_unique_object_representations_v<T>>>
|
||||
constexpr void write(const T* data, size_t count) noexcept
|
||||
{
|
||||
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
|
||||
write(reinterpret_cast<const uint8_t*>(data), sizeof(T) * count);
|
||||
}
|
||||
|
||||
#pragma warning(suppress : 26429) // Symbol 'data' is never tested for nullness, it can be marked as not_null (f.23).
|
||||
constexpr void write(const uint8_t* data, size_t count) noexcept
|
||||
{
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
#pragma warning(suppress : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
|
||||
_hash ^= static_cast<size_t>(data[i]);
|
||||
_hash *= FNV_prime;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr size_t finalize() const noexcept
|
||||
{
|
||||
return _hash;
|
||||
}
|
||||
|
||||
private:
|
||||
#if defined(_WIN64)
|
||||
static constexpr size_t FNV_offset_basis = 14695981039346656037ULL;
|
||||
static constexpr size_t FNV_prime = 1099511628211ULL;
|
||||
#else
|
||||
static constexpr size_t FNV_offset_basis = 2166136261U;
|
||||
static constexpr size_t FNV_prime = 16777619U;
|
||||
#endif
|
||||
|
||||
size_t _hash = FNV_offset_basis;
|
||||
};
|
||||
|
||||
namespace details
|
||||
{
|
||||
template<typename T, bool enable>
|
||||
struct conditionally_enabled_hash_trait
|
||||
{
|
||||
constexpr void operator()(hasher& h, const T& v) const noexcept
|
||||
{
|
||||
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
|
||||
h.write(reinterpret_cast<const uint8_t*>(&v), sizeof(T));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct conditionally_enabled_hash_trait<T, false>
|
||||
{
|
||||
conditionally_enabled_hash_trait() = delete;
|
||||
conditionally_enabled_hash_trait(const conditionally_enabled_hash_trait&) = delete;
|
||||
conditionally_enabled_hash_trait(conditionally_enabled_hash_trait&&) = delete;
|
||||
conditionally_enabled_hash_trait& operator=(const conditionally_enabled_hash_trait&) = delete;
|
||||
conditionally_enabled_hash_trait& operator=(conditionally_enabled_hash_trait&&) = delete;
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct hash_trait : details::conditionally_enabled_hash_trait<T, std::has_unique_object_representations_v<T>>
|
||||
{
|
||||
};
|
||||
|
||||
template<>
|
||||
struct hash_trait<float>
|
||||
{
|
||||
constexpr void operator()(hasher& h, float v) const noexcept
|
||||
{
|
||||
v = v == 0.0f ? 0.0f : v; // map -0 to 0
|
||||
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
|
||||
h.write(reinterpret_cast<const uint8_t*>(&v), sizeof(v));
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct hash_trait<double>
|
||||
{
|
||||
constexpr void operator()(hasher& h, double v) const noexcept
|
||||
{
|
||||
v = v == 0.0 ? 0.0 : v; // map -0 to 0
|
||||
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
|
||||
h.write(reinterpret_cast<const uint8_t*>(&v), sizeof(v));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename CharTraits, typename Allocator>
|
||||
struct hash_trait<std::basic_string<T, CharTraits, Allocator>>
|
||||
{
|
||||
constexpr void operator()(hasher& h, const std::basic_string<T, CharTraits, Allocator>& v) const noexcept
|
||||
{
|
||||
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
|
||||
h.write(reinterpret_cast<const uint8_t*>(v.data()), sizeof(T) * v.size());
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename CharTraits>
|
||||
struct hash_trait<std::basic_string_view<T, CharTraits>>
|
||||
{
|
||||
constexpr void operator()(hasher& h, const std::basic_string_view<T, CharTraits>& v) const noexcept
|
||||
{
|
||||
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
|
||||
h.write(reinterpret_cast<const uint8_t*>(v.data()), sizeof(T) * v.size());
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename = std::enable_if_t<std::is_integral_v<T> || std::is_enum_v<T>>>
|
||||
constexpr size_t hash(const T v) noexcept
|
||||
{
|
||||
// This runs murmurhash3's finalizer (fmix32/fmix64) on a single integer.
|
||||
// It's fast, public domain and produces good results.
|
||||
auto h = static_cast<size_t>(v);
|
||||
if constexpr (sizeof(size_t) == 4)
|
||||
{
|
||||
h ^= h >> 16;
|
||||
h *= UINT32_C(0x85ebca6b);
|
||||
h ^= h >> 13;
|
||||
h *= UINT32_C(0xc2b2ae35);
|
||||
h ^= h >> 16;
|
||||
}
|
||||
else
|
||||
{
|
||||
h ^= h >> 33;
|
||||
h *= UINT64_C(0xff51afd7ed558ccd);
|
||||
h ^= h >> 33;
|
||||
h *= UINT64_C(0xc4ceb9fe1a85ec53);
|
||||
h ^= h >> 33;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
template<typename T, typename = std::enable_if_t<!(std::is_integral_v<T> || std::is_enum_v<T>)>>
|
||||
constexpr size_t hash(const T& v) noexcept
|
||||
{
|
||||
hasher h;
|
||||
h.write(v);
|
||||
return h.finalize();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue