|
// Copyright 2026 Fred Emmott <fred@fredemmott.com> |
|
// SPDX-License-Identifier: MIT |
|
#pragma once |
|
|
|
#include <shared_mutex> |
|
#include <thread> |
|
#include <unordered_map> |
|
|
|
#include "FredEmmott/GUI/assert.hpp" |
|
|
|
namespace FredEmmott::utility { |
|
|
|
class shared_recursive_mutex { |
|
std::shared_mutex mSharedMutex; |
|
std::atomic<std::thread::id> mWriteOwner {}; |
|
std::size_t mWriteDepth {}; |
|
|
|
static inline thread_local std:: |
|
unordered_map<const shared_recursive_mutex*, std::size_t> |
|
mReaderDepths {}; |
|
|
|
[[nodiscard]] |
|
bool try_lock_recursive() { |
|
FUI_ASSERT( |
|
!mReaderDepths.contains(this), |
|
"READ to WRITE promotion deadlock detected"); |
|
|
|
if ( |
|
mWriteOwner.load(std::memory_order_relaxed) |
|
== std::this_thread::get_id()) { |
|
++mWriteDepth; |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
[[nodiscard]] |
|
bool try_lock_shared_recursive() { |
|
const auto it = mReaderDepths.find(this); |
|
if (it == mReaderDepths.end()) { |
|
return false; |
|
} |
|
|
|
auto& depth = it->second; |
|
FUI_ASSERT(depth > 0); |
|
++depth; |
|
return true; |
|
} |
|
|
|
public: |
|
[[nodiscard]] |
|
bool try_lock() { |
|
if (try_lock_recursive()) { |
|
return true; |
|
} |
|
if (!mSharedMutex.try_lock()) { |
|
return false; |
|
} |
|
|
|
mWriteOwner.store(std::this_thread::get_id(), std::memory_order_relaxed); |
|
FUI_ASSERT(mWriteDepth == 0); |
|
++mWriteDepth; |
|
return true; |
|
} |
|
|
|
void lock() { |
|
if (try_lock_recursive()) { |
|
return; |
|
} |
|
|
|
mSharedMutex.lock(); |
|
mWriteOwner.store(std::this_thread::get_id(), std::memory_order_release); |
|
FUI_ASSERT(mWriteDepth == 0); |
|
++mWriteDepth; |
|
} |
|
|
|
void unlock() { |
|
FUI_ASSERT(mWriteOwner == std::this_thread::get_id()); |
|
--mWriteDepth; |
|
if (mWriteDepth > 0) { |
|
return; |
|
} |
|
mWriteOwner.store({}, std::memory_order_release); |
|
mSharedMutex.unlock(); |
|
} |
|
|
|
[[nodiscard]] |
|
bool try_lock_shared() { |
|
if (try_lock_shared_recursive()) { |
|
return true; |
|
} |
|
if (!mSharedMutex.try_lock_shared()) { |
|
return false; |
|
} |
|
|
|
mReaderDepths.emplace(this, 1); |
|
return true; |
|
} |
|
|
|
void lock_shared() { |
|
if (try_lock_shared_recursive()) { |
|
return; |
|
} |
|
|
|
mSharedMutex.lock_shared(); |
|
mReaderDepths.emplace(this, 1); |
|
} |
|
|
|
void unlock_shared() { |
|
const auto it = mReaderDepths.find(this); |
|
FUI_ASSERT(it != mReaderDepths.end()); |
|
auto& depth = it->second; |
|
if (--depth > 0) { |
|
return; |
|
} |
|
|
|
mReaderDepths.erase(it); |
|
mSharedMutex.unlock_shared(); |
|
} |
|
}; |
|
|
|
}// namespace FredEmmott::utility |