فهرست منبع

Adding `Sendable`, `DataMutex`, and `SrwLock`

* `Sendable` -> A concept used by DataMutex to help make sending data
  across thread boundaries safer
* `DataMutex` -> A wrapper around a `Mutex` and `Data` that removes
  accidental read/writes to data without the associate lock. Combined
  with `Sendable` it also makes ref/pointer smuggling out of locked types harder.
* `SrwLock` -> A wrapper around `SRWLOCK` that enables std::lock_guard, std::unique_lock, and std::shared_lock use
Kenny Mecham 2 هفته پیش
والد
کامیت
489d45ea6d
4فایلهای تغییر یافته به همراه146 افزوده شده و 1 حذف شده
  1. 1 1
      .clang-tidy
  2. 81 0
      Sunrise/src/core/threading/data_mutex.h
  3. 17 0
      Sunrise/src/core/threading/sendable.h
  4. 47 0
      Sunrise/src/core/threading/srw_lock.h

+ 1 - 1
.clang-tidy

@@ -74,5 +74,5 @@ ExtraArgsBefore:
   - -Wdocumentation
 FormatStyle: file
 CheckOptions:
-  portability-restrict-system-includes.Includes: '-*,Windows.h,WinSock2.h,WS2tcpip.h,MSWSock.h,WinDNS.h,TlHelp32.h,Shellapi.h,bcrypt.h,d3d11.h,detours.h,dxgi.h,wincodec.h,imgui.h,imgui_impl_dx11.h,imgui_impl_win32.h,intrin.h,algorithm,array,atomic,bit,bitset,cctype,charconv,chrono,climits,cmath,cstdarg,cstddef,cstdint,cstdio,cstdlib,cstring,cwchar,limits,memory,new,optional,span,string_view,type_traits,utility,variant,vector'
+  portability-restrict-system-includes.Includes: '-*,Windows.h,WinSock2.h,WS2tcpip.h,MSWSock.h,WinDNS.h,TlHelp32.h,Shellapi.h,bcrypt.h,d3d11.h,detours.h,dxgi.h,wincodec.h,imgui.h,imgui_impl_dx11.h,imgui_impl_win32.h,intrin.h,algorithm,array,atomic,bit,bitset,cctype,charconv,chrono,climits,cmath,concepts,cstdarg,cstddef,cstdint,cstdio,cstdlib,cstring,cwchar,limits,memory,mutex,new,optional,shared_mutex,span,string_view,type_traits,utility,variant,vector'
 ...

+ 81 - 0
Sunrise/src/core/threading/data_mutex.h

@@ -0,0 +1,81 @@
+#pragma once
+
+#include <concepts>
+#include <mutex>
+#include <shared_mutex>
+#include <utility>
+
+#include "sendable.h"
+#include "srw_lock.h"
+
+namespace sunrise::core::threading {
+
+/** A combination of Mutex + Data. This allows Data types to be written as if they're single
+ * threaded as you'll only have access when the mutex is locked. */
+template <typename Data, typename Mutex = SrwLock> class DataMutex {
+public:
+    explicit DataMutex() noexcept
+        requires std::default_initializable<Data>
+    = default;
+
+    template <typename... Args>
+        requires std::constructible_from<Data, Args...>
+    explicit DataMutex(std::in_place_t, Args&&... args) : data_(std::forward<Args>(args)...) {}
+
+    /** Locks the mutex and calls the given Func */
+    template <std::invocable<Data&> Func, Sendable Return = std::invoke_result_t<Func, Data&>>
+    [[nodiscard]] Return lock(Func&& func) noexcept {
+        const std::lock_guard lock(mutex_);
+        return std::invoke(std::forward<Func>(func), data_);
+    }
+
+    /** Tries to loc the mutex, only calls the given Func if successful */
+    template <std::invocable<Data&> Func> void try_lock(Func&& func) noexcept {
+        std::unique_lock lock(mutex_, std::try_to_lock);
+
+        if (lock.owns_lock()) {
+            std::invoke(std::forward<Func>(func), data_);
+        }
+    }
+
+private:
+    mutable Mutex mutex_;
+    Data data_;
+};
+
+/** Similar to the above but also allows for multple readers. Readers are passed a const Data&,
+ * making accidental writes impossible */
+template <typename Data, typename SharedMutex = SrwLock> class SharedDataMutex {
+public:
+    explicit SharedDataMutex() noexcept
+        requires std::default_initializable<Data>
+    = default;
+
+    template <typename... Args>
+        requires std::constructible_from<Data, Args...>
+    explicit SharedDataMutex(std::in_place_t, Args&&... args)
+        : data_(std::forward<Args>(args)...) {}
+
+    /** Locks the mutex for reading and calls the given Func. Multiple readers can be active at
+     * once */
+    template <std::invocable<const Data&> Func,
+              Sendable Return = std::invoke_result_t<Func, const Data&>>
+    [[nodiscard]] Return lock_read(Func&& func) const noexcept {
+        const std::shared_lock lock(mutex_);
+        return std::invoke(std::forward<Func>(func), data_);
+    }
+
+    /** Locks the mutex for writing and calls the given Func. This is an exclusive lock and
+     * guarantees there are no other readers or writers */
+    template <std::invocable<Data&> Func, Sendable Return = std::invoke_result_t<Func, Data&>>
+    [[nodiscard]] Return lock_write(Func&& func) noexcept {
+        const std::lock_guard lock(mutex_);
+        return std::invoke(std::forward<Func>(func), data_);
+    }
+
+private:
+    mutable SharedMutex mutex_{};
+    Data data_{};
+};
+
+} // namespace sunrise::core::threading

+ 17 - 0
Sunrise/src/core/threading/sendable.h

@@ -0,0 +1,17 @@
+#pragma once
+
+#include <concepts>
+
+namespace sunrise::core::threading {
+
+/** An specializable struct that indicates a type can be sent across thread boundaries */
+template <typename T> struct IsSendable : std::false_type {};
+
+/** Indicates a specific type can be sent across thread boundaries. Integral, loating point, and
+ * void types are always allowed since they're easily copyable. Custom types can be marked as
+ * `Sendable` by specializing `IsSendable` above */
+template <typename T>
+concept Sendable =
+    std::integral<T> || std::floating_point<T> || std::is_void_v<T> || IsSendable<T>::value;
+
+} // namespace sunrise::core::threading

+ 47 - 0
Sunrise/src/core/threading/srw_lock.h

@@ -0,0 +1,47 @@
+#pragma once
+
+#include <WinSock2.h>
+
+namespace sunrise::core::threading {
+
+/** Wrapper to enable std::lock_guard and std::shared_lock for SRWLOCK */
+class SrwLock final {
+public:
+    constexpr explicit SrwLock() noexcept = default;
+
+    SrwLock(const SrwLock&) = delete;
+    SrwLock(SrwLock&&) = delete;
+    SrwLock& operator=(const SrwLock&) = delete;
+    SrwLock& operator=(SrwLock&&) = delete;
+
+    // stl Lockable
+    constexpr void lock() noexcept {
+        AcquireSRWLockExclusive(&lock_);
+    }
+
+    [[nodiscard]] constexpr bool try_lock() noexcept {
+        return TryAcquireSRWLockExclusive(&lock_);
+    }
+
+    constexpr void unlock() noexcept {
+        ReleaseSRWLockExclusive(&lock_);
+    }
+
+    // stl SharedLockable
+    constexpr void lock_shared() noexcept {
+        AcquireSRWLockShared(&lock_);
+    }
+
+    [[nodiscard]] constexpr bool try_lock_shared() noexcept {
+        return TryAcquireSRWLockShared(&lock_);
+    }
+
+    constexpr void unlock_shared() noexcept {
+        ReleaseSRWLockShared(&lock_);
+    }
+
+private:
+    SRWLOCK lock_{SRWLOCK_INIT};
+};
+
+} // namespace sunrise::core::threading