0d9a357373
This commit introduce three new `til` features: * "til/latch.h": A std::latch clone, until we're on C++20. * "til/mutex.h": A safe mutex wrapper, which only allows you access to the protected data after locking it. No more forgetting to lock mutexes! * "til/throttled_func.h": Function invocation throttling used to be available as the `ThrottledFunc` class already. But this class is vastly more efficient and doesn't rely on any WinRT types. This PR also adds a `til::ends_with` string helper which is `til::starts_with` counterpart. ## Validation Steps Performed * Scrollbar throttling still works as it used to ✔️ * No performance regressions when printing big.txt ✔️ Closes #10393
109 lines
3.4 KiB
C++
109 lines
3.4 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
namespace til
|
|
{
|
|
namespace details
|
|
{
|
|
template<typename T, typename Lock>
|
|
class shared_mutex_guard
|
|
{
|
|
public:
|
|
shared_mutex_guard(T& data, std::shared_mutex& mutex) :
|
|
_data{ data },
|
|
_lock{ mutex }
|
|
{
|
|
}
|
|
|
|
shared_mutex_guard(const shared_mutex_guard&) = delete;
|
|
shared_mutex_guard& operator=(const shared_mutex_guard&) = delete;
|
|
|
|
shared_mutex_guard(shared_mutex_guard&&) = default;
|
|
shared_mutex_guard& operator=(shared_mutex_guard&&) = default;
|
|
|
|
[[nodiscard]] constexpr T* operator->() const
|
|
{
|
|
return &_data;
|
|
}
|
|
|
|
[[nodiscard]] constexpr T& operator*() const&
|
|
{
|
|
return _data;
|
|
}
|
|
|
|
[[nodiscard]] constexpr T&& operator*() const&&
|
|
{
|
|
return std::move(_data);
|
|
}
|
|
|
|
private:
|
|
// We could reduce this to a single pointer member,
|
|
// by storing a reference to the til::shared_mutex& class
|
|
// and accessing its private members as a friend class.
|
|
// But MSVC doesn't support strict aliasing. Nice!
|
|
//
|
|
// For instance if we had:
|
|
// struct foo { int a, b; };
|
|
// struct bar { foo& f; };
|
|
//
|
|
// void test(bar& b) {
|
|
// b.f.a = 123;
|
|
// b.f.b = 456;
|
|
// }
|
|
//
|
|
// This would generate the following suboptimal assembly despite /O2:
|
|
// mov rax, QWORD PTR [rcx]
|
|
// mov DWORD PTR [rax], 123
|
|
//
|
|
// mov rax, QWORD PTR [rcx]
|
|
// mov DWORD PTR [rax+4], 456
|
|
T& _data;
|
|
Lock _lock;
|
|
};
|
|
} // namespace details
|
|
|
|
// shared_mutex is a std::shared_mutex which also contains the data it's protecting.
|
|
// It only allows access to the underlying data by locking the mutex and thus
|
|
// ensures you don't forget to do so, unlike with std::mutex/std::shared_mutex.
|
|
template<typename T>
|
|
class shared_mutex
|
|
{
|
|
public:
|
|
// An exclusive, read/write reference to a til::shared_mutex's underlying data.
|
|
// If you drop the guard the mutex is unlocked.
|
|
using guard = details::shared_mutex_guard<T, std::unique_lock<std::shared_mutex>>;
|
|
|
|
// A shared, read-only reference to a til::shared_mutex's underlying data.
|
|
// If you drop the shared_guard the mutex is unlocked.
|
|
using shared_guard = details::shared_mutex_guard<const T, std::shared_lock<std::shared_mutex>>;
|
|
|
|
shared_mutex() = default;
|
|
|
|
template<typename... Args>
|
|
shared_mutex(Args&&... args) :
|
|
_data{ std::forward<Args>(args)... }
|
|
{
|
|
}
|
|
|
|
// Acquire an exclusive, read/write reference to T.
|
|
// For instance:
|
|
// .lock()->foo = bar;
|
|
[[nodiscard]] guard lock() const noexcept
|
|
{
|
|
return { _data, _mutex };
|
|
}
|
|
|
|
// Acquire a shared, read-only reference to T.
|
|
// For instance:
|
|
// bar = .lock_shared()->foo;
|
|
[[nodiscard]] shared_guard lock_shared() const noexcept
|
|
{
|
|
return { _data, _mutex };
|
|
}
|
|
|
|
private:
|
|
mutable T _data{};
|
|
mutable std::shared_mutex _mutex;
|
|
};
|
|
} // namespace til
|