2020-12-06 17:34:16 +01:00
# pragma once
2019-09-19 00:36:21 +02:00
# 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
2022-09-29 11:04:38 +02:00
static constexpr u32 c_init_bit = 0x8000'0000 ;
2019-09-19 00:36:21 +02:00
// 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 ;
2024-01-04 08:30:11 +01:00
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 ) . . . ) ;
}
2019-09-19 00:36:21 +02:00
public :
2024-01-04 08:30:11 +01:00
template < typename Forced , typename . . . FAndArgs >
explicit init_lock ( init_mutex & mtx , Forced & & , FAndArgs & & . . . args ) noexcept
2019-09-19 00:36:21 +02:00
: _this ( & mtx )
{
2024-01-04 08:30:11 +01:00
bool invoked_func = false ;
2019-09-19 00:36:21 +02:00
while ( true )
{
2019-10-19 13:01:34 +02:00
auto [ val , ok ] = _this - > m_state . fetch_op ( [ ] ( u32 & value )
{
2025-04-05 21:50:45 +02:00
if ( value = = 0 )
2019-10-19 13:01:34 +02:00
{
2025-04-05 21:50:45 +02:00
value = 1 ;
2019-10-19 13:01:34 +02:00
return true ;
}
2025-04-05 21:50:45 +02:00
if constexpr ( Forced ( ) ( ) )
{
if ( value & c_init_bit )
{
value - = c_init_bit - 1 ;
return true ;
}
}
return false ;
} ) ;
2019-09-19 00:36:21 +02:00
if ( val = = 0 )
{
// Success: obtained "init lock"
break ;
}
if ( val & c_init_bit )
{
2024-01-04 08:30:11 +01:00
if constexpr ( Forced ( ) ( ) )
2019-10-19 13:01:34 +02:00
{
// Forced reset
val - = c_init_bit - 1 ;
while ( val ! = 1 )
{
2024-03-06 18:27:58 +01:00
if constexpr ( sizeof . . . ( FAndArgs ) )
{
if ( ! invoked_func )
{
invoke_callback ( 0 , std : : forward < FAndArgs > ( args ) . . . ) ;
invoked_func = true ;
}
}
2019-10-19 13:01:34 +02:00
// Wait for other users to finish their work
_this - > m_state . wait ( val ) ;
val = _this - > m_state ;
}
2024-01-04 08:30:11 +01:00
}
2024-03-06 18:27:58 +01:00
// Failure
_this = nullptr ;
2019-09-19 00:36:21 +02:00
break ;
}
2024-01-04 08:30:11 +01:00
if constexpr ( sizeof . . . ( FAndArgs ) )
{
if ( ! invoked_func )
{
invoke_callback ( 0 , std : : forward < FAndArgs > ( args ) . . . ) ;
invoked_func = true ;
}
}
2019-09-19 00:36:21 +02:00
_this - > m_state . wait ( val ) ;
}
2024-03-06 13:57:42 +01:00
// Finalization of wait callback
if constexpr ( sizeof . . . ( FAndArgs ) )
{
if ( invoked_func )
{
invoke_callback ( 1 , std : : forward < FAndArgs > ( args ) . . . ) ;
}
}
2019-09-19 00:36:21 +02:00
}
init_lock ( const init_lock & ) = delete ;
2024-01-04 08:30:11 +01:00
init_lock ( init_lock & & lock ) noexcept
: _this ( std : : exchange ( lock . _this , nullptr ) )
{
}
2019-09-19 00:36:21 +02:00
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 ;
2024-01-04 08:30:11 +01:00
void force_lock ( init_mutex * mtx )
{
_this = mtx ;
}
2019-09-19 00:36:21 +02:00
void cancel ( ) & noexcept
{
if ( _this )
{
// Abandon "init lock"
_this - > m_state - = 1 ;
_this - > m_state . notify_all ( ) ;
_this = nullptr ;
}
}
2024-01-04 08:30:11 +01:00
init_mutex * get_mutex ( )
{
return _this ;
}
2019-09-19 00:36:21 +02:00
} ;
// Obtain exclusive lock to initialize protected resource. Waits for ongoing initialization or finalization. Fails if already initialized.
2024-01-04 08:30:11 +01:00
template < typename . . . FAndArgs >
[[nodiscard]] init_lock init ( FAndArgs & & . . . args ) noexcept
2019-09-19 00:36:21 +02:00
{
2024-01-04 08:30:11 +01:00
return init_lock ( * this , std : : false_type { } , std : : forward < FAndArgs > ( args ) . . . ) ;
2019-09-19 00:36:21 +02:00
}
2019-10-19 13:01:34 +02:00
// 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
{
2024-01-04 08:30:11 +01:00
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 ;
2019-10-19 13:01:34 +02:00
}
2019-09-19 00:36:21 +02:00
class reset_lock final
{
2024-01-04 08:30:11 +01:00
init_lock _lock ;
2019-09-19 00:36:21 +02:00
public :
2024-01-04 08:30:11 +01:00
explicit reset_lock ( init_lock & & lock ) noexcept
: _lock ( std : : move ( lock ) )
2019-09-19 00:36:21 +02:00
{
}
reset_lock ( const reset_lock & ) = delete ;
reset_lock & operator = ( const reset_lock & ) = delete ;
~ reset_lock ( )
{
2024-01-04 08:30:11 +01:00
if ( _lock )
2019-09-19 00:36:21 +02:00
{
// Set uninitialized state and remove "init lock"
2024-01-04 08:30:11 +01:00
_lock . cancel ( ) ;
2019-09-19 00:36:21 +02:00
}
}
explicit operator bool ( ) const & noexcept
{
2024-01-04 08:30:11 +01:00
return ! ! _lock ;
2019-09-19 00:36:21 +02:00
}
explicit operator bool ( ) & & = delete ;
2019-10-19 13:01:34 +02:00
void set_init ( ) & noexcept
{
2024-01-04 08:30:11 +01:00
if ( _lock )
2019-10-19 13:01:34 +02:00
{
// Set initialized state (TODO?)
2024-01-04 08:30:11 +01:00
_lock . get_mutex ( ) - > m_state | = c_init_bit ;
_lock . get_mutex ( ) - > m_state . notify_all ( ) ;
2019-10-19 13:01:34 +02:00
}
}
2019-09-19 00:36:21 +02:00
} ;
// Obtain exclusive lock to finalize protected resource. Waits for ongoing use. Fails if not initialized.
2024-01-04 08:30:11 +01:00
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 ) ) ;
}
2019-09-19 00:36:21 +02:00
[[nodiscard]] reset_lock reset ( ) noexcept
{
2024-01-04 08:30:11 +01:00
init_lock lock ( * this , std : : true_type { } ) ;
if ( ! lock )
{
lock . force_lock ( this ) ;
}
else
{
lock . cancel ( ) ;
}
return reset_lock ( std : : move ( lock ) ) ;
2019-09-19 00:36:21 +02:00
}
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 )
{
2025-04-05 21:50:45 +02:00
if ( value & c_init_bit )
{
// Add "access lock"
value + = 1 ;
return true ;
}
2019-09-19 00:36:21 +02:00
2025-04-05 21:50:45 +02:00
return false ;
} ) ;
2019-09-19 00:36:21 +02:00
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 ;
}
2019-11-10 23:10:23 +01:00
// Wait for access()
void wait_for_initialized ( ) const noexcept
{
const u32 state = m_state ;
if ( state & c_init_bit )
{
return ;
}
m_state . wait ( state ) ;
}
2019-09-19 00:36:21 +02:00
} ;
2024-01-04 08:30:11 +01:00
using init_lock = init_mutex : : init_lock ;
using reset_lock = init_mutex : : reset_lock ;
using access_lock = init_mutex : : access_lock ;
2025-04-05 21:50:45 +02:00
} // namespace stx