rpcsx/rpcs3/util/init_mutex.hpp
DH 62ad27d1e2 split rpcs3 and hle libraries
merge rpcs3 utilities
2025-04-08 19:46:57 +03:00

341 lines
6.6 KiB
C++

#pragma once
#include "util/atomic.hpp"
namespace stx
{
// Mutex designed to support 3 categories of concurrent access to protected resource (initialization, finalization and normal use) while holding the "init" state bit
class init_mutex
{
// Set after initialization and removed before finalization
static constexpr u32 c_init_bit = 0x8000'0000;
// Contains "reader" count and init bit
atomic_t<u32> m_state = 0;
public:
constexpr init_mutex() noexcept = default;
class init_lock final
{
init_mutex* _this;
template <typename Func, typename... Args>
void invoke_callback(int invoke_count, Func&& func, Args&&... args) const
{
std::invoke(func, invoke_count, *this, std::forward<Args>(args)...);
}
public:
template <typename Forced, typename... FAndArgs>
explicit init_lock(init_mutex& mtx, Forced&&, FAndArgs&&... args) noexcept
: _this(&mtx)
{
bool invoked_func = false;
while (true)
{
auto [val, ok] = _this->m_state.fetch_op([](u32& value)
{
if (value == 0)
{
value = 1;
return true;
}
if constexpr (Forced()())
{
if (value & c_init_bit)
{
value -= c_init_bit - 1;
return true;
}
}
return false;
});
if (val == 0)
{
// Success: obtained "init lock"
break;
}
if (val & c_init_bit)
{
if constexpr (Forced()())
{
// Forced reset
val -= c_init_bit - 1;
while (val != 1)
{
if constexpr (sizeof...(FAndArgs))
{
if (!invoked_func)
{
invoke_callback(0, std::forward<FAndArgs>(args)...);
invoked_func = true;
}
}
// Wait for other users to finish their work
_this->m_state.wait(val);
val = _this->m_state;
}
}
// Failure
_this = nullptr;
break;
}
if constexpr (sizeof...(FAndArgs))
{
if (!invoked_func)
{
invoke_callback(0, std::forward<FAndArgs>(args)...);
invoked_func = true;
}
}
_this->m_state.wait(val);
}
// Finalization of wait callback
if constexpr (sizeof...(FAndArgs))
{
if (invoked_func)
{
invoke_callback(1, std::forward<FAndArgs>(args)...);
}
}
}
init_lock(const init_lock&) = delete;
init_lock(init_lock&& lock) noexcept
: _this(std::exchange(lock._this, nullptr))
{
}
init_lock& operator=(const init_lock&) = delete;
~init_lock()
{
if (_this)
{
// Set initialized state and remove "init lock"
_this->m_state += c_init_bit - 1;
_this->m_state.notify_all();
}
}
explicit operator bool() const& noexcept
{
return _this != nullptr;
}
explicit operator bool() && = delete;
void force_lock(init_mutex* mtx)
{
_this = mtx;
}
void cancel() & noexcept
{
if (_this)
{
// Abandon "init lock"
_this->m_state -= 1;
_this->m_state.notify_all();
_this = nullptr;
}
}
init_mutex* get_mutex()
{
return _this;
}
};
// Obtain exclusive lock to initialize protected resource. Waits for ongoing initialization or finalization. Fails if already initialized.
template <typename... FAndArgs>
[[nodiscard]] init_lock init(FAndArgs&&... args) noexcept
{
return init_lock(*this, std::false_type{}, std::forward<FAndArgs>(args)...);
}
// Same as init, but never fails, and executes provided `on_reset` function if already initialized.
template <typename F, typename... Args>
[[nodiscard]] init_lock init_always(F on_reset, Args&&... args) noexcept
{
init_lock lock(*this, std::true_type{});
if (!lock)
{
lock.force_lock(this);
std::invoke(std::forward<F>(on_reset), std::forward<Args>(args)...);
}
return lock;
}
class reset_lock final
{
init_lock _lock;
public:
explicit reset_lock(init_lock&& lock) noexcept
: _lock(std::move(lock))
{
}
reset_lock(const reset_lock&) = delete;
reset_lock& operator=(const reset_lock&) = delete;
~reset_lock()
{
if (_lock)
{
// Set uninitialized state and remove "init lock"
_lock.cancel();
}
}
explicit operator bool() const& noexcept
{
return !!_lock;
}
explicit operator bool() && = delete;
void set_init() & noexcept
{
if (_lock)
{
// Set initialized state (TODO?)
_lock.get_mutex()->m_state |= c_init_bit;
_lock.get_mutex()->m_state.notify_all();
}
}
};
// Obtain exclusive lock to finalize protected resource. Waits for ongoing use. Fails if not initialized.
template <typename F, typename... Args>
[[nodiscard]] reset_lock reset(F on_reset, Args&&... args) noexcept
{
init_lock lock(*this, std::true_type{}, std::forward<F>(on_reset), std::forward<Args>(args)...);
if (!lock)
{
lock.force_lock(this);
}
else
{
lock.cancel();
}
return reset_lock(std::move(lock));
}
[[nodiscard]] reset_lock reset() noexcept
{
init_lock lock(*this, std::true_type{});
if (!lock)
{
lock.force_lock(this);
}
else
{
lock.cancel();
}
return reset_lock(std::move(lock));
}
class access_lock final
{
init_mutex* _this;
public:
explicit access_lock(init_mutex& mtx) noexcept
: _this(&mtx)
{
auto [val, ok] = _this->m_state.fetch_op([](u32& value)
{
if (value & c_init_bit)
{
// Add "access lock"
value += 1;
return true;
}
return false;
});
if (!ok)
{
_this = nullptr;
return;
}
}
access_lock(const access_lock&) = delete;
access_lock& operator=(const access_lock&) = delete;
~access_lock()
{
if (_this)
{
// TODO: check condition
if (--_this->m_state <= 1)
{
_this->m_state.notify_all();
}
}
}
explicit operator bool() const& noexcept
{
return _this != nullptr;
}
explicit operator bool() && = delete;
};
// Obtain shared lock to use protected resource. Fails if not initialized.
[[nodiscard]] access_lock access() noexcept
{
return access_lock(*this);
}
// Simple state test. Hard to use, easy to misuse.
bool volatile_is_initialized() const noexcept
{
return (m_state & c_init_bit) != 0;
}
// Wait for access()
void wait_for_initialized() const noexcept
{
const u32 state = m_state;
if (state & c_init_bit)
{
return;
}
m_state.wait(state);
}
};
using init_lock = init_mutex::init_lock;
using reset_lock = init_mutex::reset_lock;
using access_lock = init_mutex::access_lock;
} // namespace stx