Files
fennec/include/fennec/threading/atomic.h

442 lines
16 KiB
C++

// =====================================================================================================================
// fennec, a free and open source game engine
// Copyright © 2025 Medusa Slockbower
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================
///
/// \file atomic.h
/// \brief
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///
#ifndef FENNEC_THREADING_ATOMIC_H
#define FENNEC_THREADING_ATOMIC_H
#include <fennec/threading/detail/_atomic.h>
#include <fennec/lang/type_traits.h>
namespace fennec
{
using ::memory_order;
using ::memory_order_relaxed;
using ::memory_order_consume;
using ::memory_order_acquire;
using ::memory_order_release;
using ::memory_order_acq_rel;
using ::memory_order_seq_cst;
///
/// \brief Wrapper for atomic variables
/// \tparam T The type of the atomic variable
template<typename T>
struct atomic {
// Assertions ==========================================================================================================
public:
static_assert(is_integral_v<T>, "fennec::atomic not defined for the provided type. Default implementation"
"only supports integral types.");
// Definitions =========================================================================================================
public:
using value_t = T;
// Constructors ========================================================================================================
///
/// \brief Default Constructor
///
/// \details Initializes the held atomic variable with `0`.
constexpr atomic() noexcept
: _value(static_cast<T>(0)) {
}
///
/// \brief Value Constructor
///
/// \details Initializes the held atomic variable with `value`.
/// \param value The value to initialize the atomic variable with.
constexpr atomic(const T value) noexcept
: _value(value) {
}
atomic(const atomic&) = delete;
// Assignment ==========================================================================================================
atomic& operator=(const atomic&) = delete;
atomic& operator=(const atomic&) volatile = delete;
///
/// \details Atomically assigns `x` to the atomic variable.
/// \param x The value to assign
/// \returns `x`
T operator=(const T x) noexcept {
this->store(x);
return x;
}
///
/// \brief Stores a value into an atomic object
///
/// \details Atomically assigns `x` to the atomic variable.
/// \param x The value to assign
/// \returns `x`
T operator=(const T x) volatile noexcept {
this->store(x);
return x;
}
// Modifiers ===========================================================================================================
///
/// \details Atomically replaces the current value with `x`. Memory is affected according to the value of `order`.
/// \param x The value to store into the atomic variable
/// \param order Memory order constraints to enforce
void store(const T x, memory_order order = memory_order_seq_cst) noexcept {
::atomic_store_explicit(&_value, x, order);
}
///
/// \brief Atomically replaces the value of the atomic object with a non-atomic argument
///
/// \details Atomically replaces the current value with `x`. Memory is affected according to the value of `order`.
/// \param x The value to store into the atomic variable
/// \param order Memory order constraints to enforce
void store(const T x, memory_order order = memory_order_seq_cst) volatile noexcept {
::atomic_store_explicit(&_value, x, order);
}
///
/// \details Atomically loads and returns the current value of the atomic variable.
/// Memory is affected according to the value of `order`.
/// \param order Memory order constraints to enforce
/// \returns The current value of the atomic variable.
T load(memory_order order = memory_order_seq_cst) const noexcept {
return ::atomic_load_explicit(&_value, order);
}
///
/// \brief Atomically obtains the value of the atomic object
///
/// \details Atomically loads and returns the current value of the atomic variable.
/// Memory is affected according to the value of `order`.
/// \param order Memory order constraints to enforce
/// \returns The current value of the atomic variable.
T load(memory_order order = memory_order_seq_cst) const volatile noexcept {
return ::atomic_load_explicit(&_value, order);
}
///
/// \details Atomically loads and returns the current value of the atomic variable. Equivalent to `load()`.
operator T() const noexcept {
return load();
}
///
/// \brief Loads a value from an atomic object
///
/// \details Atomically loads and returns the current value of the atomic variable. Equivalent to `load()`.
operator T() const volatile noexcept {
return load();
}
///
/// \details Atomically replaces the underlying value with `x` (a read-modify-write operation).
/// Memory is affected according to the value of `order`.
/// \param x Value to assign
/// \param order Memory order constraints to enforce
/// \return The value of the atomic variable before the call.
T exchange(const T x, memory_order order = memory_order_seq_cst) noexcept {
return ::atomic_exchange_explicit(&_value, x, order);
}
///
/// \brief Atomically replaces the value of the atomic object and obtains the value held previously
///
/// \details Atomically replaces the underlying value with `x` (a read-modify-write operation).
/// Memory is affected according to the value of `order`.
/// \param x Value to assign
/// \param order Memory order constraints to enforce
/// \return The value of the atomic variable before the call.
T exchange(const T x, memory_order order = memory_order_seq_cst) volatile noexcept {
return ::atomic_exchange_explicit(&_value, x, order);
}
///
/// \details Atomically compares the value of `*this` with that of `exp`.
/// If they are equal, replaces the former with `x` (performs read-modify-write operation).
/// Otherwise, loads the actual value stored in *this into `exp` (performs load operation).
/// \param exp Reference to the value expected to be found in the atomic object.
/// \param x The value to store in the atomic object if it is as expected.
/// \param succ The memory synchronization ordering for the read-modify-write operation if the comparison succeeds.
/// \param fail The memory synchronization ordering for the load operation if the comparison fails.
/// \returns `true` if the underlying atomic value was successfully changed, `false` otherwise.
bool compare_exchange_weak(T& exp, T x, memory_order succ, memory_order fail) noexcept {
return ::atomic_compare_exchange_weak_explicit(&_value, &exp, x, succ, fail);
}
///
/// \details Atomically compares the value of `*this` with that of `exp`.
/// If they are equal, replaces the former with `x` (performs read-modify-write operation).
/// Otherwise, loads the actual value stored in *this into `exp` (performs load operation).
/// \param exp Reference to the value expected to be found in the atomic object.
/// \param x The value to store in the atomic object if it is as expected.
/// \param succ The memory synchronization ordering for the read-modify-write operation if the comparison succeeds.
/// \param fail The memory synchronization ordering for the load operation if the comparison fails.
/// \returns `true` if the underlying atomic value was successfully changed, `false` otherwise.
bool compare_exchange_weak(T& exp, T x, memory_order succ, memory_order fail) volatile noexcept {
return ::atomic_compare_exchange_weak_explicit(&_value, &exp, x, succ, fail);
}
///
/// \details Atomically compares the value of `*this` with that of `exp`.
/// If they are equal, replaces the former with `x` (performs read-modify-write operation).
/// Otherwise, loads the actual value stored in *this into `exp` (performs load operation).
/// \param exp Reference to the value expected to be found in the atomic object.
/// \param x The value to store in the atomic object if it is as expected.
/// \param order The memory synchronization ordering for both operations.
/// \returns `true` if the underlying atomic value was successfully changed, `false` otherwise.
bool compare_exchange_weak(T& exp, T x, memory_order order = memory_order_seq_cst) noexcept {
return ::atomic_compare_exchange_weak_explicit(&_value, &exp, x, order, order);
}
///
/// \brief Atomically compares the value of the atomic object with non-atomic argument and performs atomic exchange if equal or atomic load if not
///
/// \details Atomically compares the value of `*this` with that of `exp`.
/// If they are equal, replaces the former with `x` (performs read-modify-write operation).
/// Otherwise, loads the actual value stored in *this into `exp` (performs load operation).
/// \param exp Reference to the value expected to be found in the atomic object.
/// \param x The value to store in the atomic object if it is as expected.
/// \param order The memory synchronization ordering for both operations.
/// \returns `true` if the underlying atomic value was successfully changed, `false` otherwise.
bool compare_exchange_weak(T& exp, T x, memory_order order = memory_order_seq_cst) volatile noexcept {
return ::atomic_compare_exchange_weak_explicit(&_value, &exp, x, order, order);
}
///
/// \details Atomically compares the value of `*this` with that of `exp`.
/// If they are equal, replaces the former with `x` (performs read-modify-write operation).
/// Otherwise, loads the actual value stored in *this into `exp` (performs load operation).
/// \param exp Reference to the value expected to be found in the atomic object.
/// \param x The value to store in the atomic object if it is as expected.
/// \param succ The memory synchronization ordering for the read-modify-write operation if the comparison succeeds.
/// \param fail The memory synchronization ordering for the load operation if the comparison fails.
/// \returns `true` if the underlying atomic value was successfully changed, `false` otherwise.
bool compare_exchange_strong(T& exp, T x, memory_order succ, memory_order fail) noexcept {
return ::atomic_compare_exchange_strong_explicit(&_value, &exp, x, succ, fail);
}
///
/// \details Atomically compares the value of `*this` with that of `exp`.
/// If they are equal, replaces the former with `x` (performs read-modify-write operation).
/// Otherwise, loads the actual value stored in *this into `exp` (performs load operation).
/// \param exp Reference to the value expected to be found in the atomic object.
/// \param x The value to store in the atomic object if it is as expected.
/// \param succ The memory synchronization ordering for the read-modify-write operation if the comparison succeeds.
/// \param fail The memory synchronization ordering for the load operation if the comparison fails.
/// \returns `true` if the underlying atomic value was successfully changed, `false` otherwise.
bool compare_exchange_strong(T& exp, T x, memory_order succ, memory_order fail) volatile noexcept {
return ::atomic_compare_exchange_strong_explicit(&_value, &exp, x, succ, fail);
}
///
/// \details Atomically compares the value of `*this` with that of `exp`.
/// If they are equal, replaces the former with `x` (performs read-modify-write operation).
/// Otherwise, loads the actual value stored in *this into `exp` (performs load operation).
/// \param exp Reference to the value expected to be found in the atomic object.
/// \param x The value to store in the atomic object if it is as expected.
/// \param order The memory synchronization ordering for both operations.
/// \returns `true` if the underlying atomic value was successfully changed, `false` otherwise.
bool compare_exchange_strong(T& exp, T x, memory_order order = memory_order_seq_cst) noexcept {
return ::atomic_compare_exchange_strong_explicit(&_value, &exp, x, order, order);
}
///
/// \brief Atomically compares the value of the atomic object with non-atomic argument and performs atomic exchange if equal or atomic load if not
///
/// \details Atomically compares the value of `*this` with that of `exp`.
/// If they are equal, replaces the former with `x` (performs read-modify-write operation).
/// Otherwise, loads the actual value stored in *this into `exp` (performs load operation).
/// \param exp Reference to the value expected to be found in the atomic object.
/// \param x The value to store in the atomic object if it is as expected.
/// \param order The memory synchronization ordering for both operations.
/// \returns `true` if the underlying atomic value was successfully changed, `false` otherwise.
bool compare_exchange_strong(T& exp, T x, memory_order order = memory_order_seq_cst) volatile noexcept {
return ::atomic_compare_exchange_strong_explicit(&_value, &exp, x, order, order);
}
// Operations ==========================================================================================================
T fetch_add(const T x, memory_order order = memory_order_seq_cst) noexcept {
return ::atomic_fetch_add_explicit(&_value, x, order);
}
T fetch_add(const T x, memory_order order = memory_order_seq_cst) volatile noexcept {
return ::atomic_fetch_add_explicit(&_value, x, order);
}
T operator+=(const T x) noexcept {
return this->fetch_add(x) + x;
}
T operator+=(const T x) volatile noexcept {
return this->fetch_add(x) + x;
}
T operator++() noexcept {
return this->fetch_add(1) + 1;
}
T operator++() volatile noexcept {
return this->fetch_add(1) + 1;
}
T operator++(int) noexcept {
return this->fetch_add(1);
}
T operator++(int) volatile noexcept {
return this->fetch_add(1);
}
T fetch_sub(const T x, memory_order order = memory_order_seq_cst) noexcept {
return ::atomic_fetch_sub_explicit(&_value, x, order);
}
T fetch_sub(const T x, memory_order order = memory_order_seq_cst) volatile noexcept {
return ::atomic_fetch_sub_explicit(&_value, x, order);
}
T operator-=(const T x) noexcept {
return this->fetch_sub(x) - x;
}
T operator-=(const T x) volatile noexcept {
return this->fetch_sub(x) - x;
}
T operator--() noexcept {
return this->fetch_sub(1) - 1;
}
T operator--() volatile noexcept {
return this->fetch_sub(1) - 1;
}
T operator--(int) noexcept {
return this->fetch_sub(1);
}
T operator--(int) volatile noexcept {
return this->fetch_sub(1);
}
// Bit Operations ======================================================================================================
T fetch_and(const T x, memory_order order = memory_order_seq_cst) noexcept {
return ::atomic_fetch_and_explicit(&_value, x, order);
}
T fetch_and(const T x, memory_order order = memory_order_seq_cst) volatile noexcept {
return ::atomic_fetch_and_explicit(&_value, x, order);
}
T operator&=(const T x) noexcept {
return this->fetch_and(x) & x;
}
T operator&=(const T x) volatile noexcept {
return this->fetch_and(x) & x;
}
T fetch_or(const T x, memory_order order = memory_order_seq_cst) noexcept {
return ::atomic_fetch_or_explicit(&_value, x, order);
}
T fetch_or(const T x, memory_order order = memory_order_seq_cst) volatile noexcept {
return ::atomic_fetch_or_explicit(&_value, x, order);
}
T operator|=(const T x) noexcept {
return this->fetch_or(x) & x;
}
T operator|=(const T x) volatile noexcept {
return this->fetch_or(x) & x;
}
T fetch_xor(const T x, memory_order order = memory_order_seq_cst) noexcept {
return ::atomic_fetch_xor_explicit(&_value, x, order);
}
T fetch_xor(const T x, memory_order order = memory_order_seq_cst) volatile noexcept {
return ::atomic_fetch_xor_explicit(&_value, x, order);
}
T operator^=(const T x) noexcept {
return this->fetch_xor(x) & x;
}
T operator^=(const T x) volatile noexcept {
return this->fetch_xor(x) & x;
}
private:
_Atomic T _value;
};
}
#endif // FENNEC_THREADING_ATOMIC_H