Program Listing for File rw_spinlock.hpp

Return to documentation for file (/home/runner/work/Legion-Engine/Legion-Engine/legion/engine/core/async/rw_spinlock.hpp)

#pragma once
#include <atomic>
#include <tuple>
#include <unordered_map>
#include <array>
#include <thread>
#include <functional>
#include <core/types/primitives.hpp>
#include <core/platform/platform.hpp>
#include <core/containers/sparse_set.hpp>
#include <core/detail/internals.hpp>
#include <core/async/wait_priority.hpp>

namespace legion::core::async
{
    enum struct lock_state : int { idle = 0, read = 1, write = -1 };

    inline constexpr lock_state lock_state_idle = lock_state::idle;
    inline constexpr lock_state lock_state_read = lock_state::read;
    inline constexpr lock_state lock_state_write = lock_state::write;

    struct rw_spinlock final
    {
    private:
        static bool m_forceRelease;
        static std::atomic_uint m_lastId;

        static thread_local std::unordered_map<uint, int> m_localWriters;
        static thread_local std::unordered_map<uint, int> m_localReaders;
        static thread_local std::unordered_map<uint, lock_state> m_localState;

        uint m_id = m_lastId.fetch_add(1, std::memory_order_relaxed);
        // State of the lock. -1 means that a thread has write permission. 0 means that the lock is unlocked. 1+ means that there are N amount of readers.
        mutable  std::atomic_int m_lockState = { 0 };
        mutable std::thread::id m_writer;

        void read_lock(wait_priority priority = wait_priority::real_time) const;

        bool read_try_lock() const;

        void write_lock(wait_priority priority = wait_priority::real_time) const;

        bool write_try_lock() const;

        void read_unlock() const;

        void write_unlock() const;

    public:
        static void force_release(bool release = true);

        rw_spinlock() = default;

        rw_spinlock(rw_spinlock&& source) noexcept;
        rw_spinlock& operator=(rw_spinlock&& source) noexcept;

        rw_spinlock(const rw_spinlock&) = delete;
        rw_spinlock& operator=(const rw_spinlock&) = delete;

        void lock(lock_state permissionLevel = lock_state::write, wait_priority priority = wait_priority::real_time) const;

        bool try_lock(lock_state permissionLevel = lock_state::write) const;

        void unlock(lock_state permissionLevel = lock_state::write) const;

        void lock_shared() const;

        bool try_lock_shared() const;

        void unlock_shared() const;

        template<typename Guard, typename Func>
        auto critical_section(const Func& func) const -> decltype(auto)
        {
            Guard guard(*this);
            return std::invoke(func);
        }
    };

    class readonly_guard final
    {
    private:
        const rw_spinlock& m_lock;

    public:
        readonly_guard(const rw_spinlock& lock, wait_priority priority = wait_priority::real_time) : m_lock(lock)
        {
            m_lock.lock(lock_state::read, priority);
        }

        readonly_guard(const readonly_guard&) = delete;

        ~readonly_guard()
        {
            m_lock.unlock(lock_state::read);
        }

        readonly_guard& operator=(readonly_guard&&) = delete;
    };

    template<size_type S>
    class readonly_multiguard final
    {
    private:
        std::array<const rw_spinlock*, S> m_locks;

    public:
        template<typename lock_type1 = rw_spinlock, typename lock_type2 = rw_spinlock, typename... lock_typesN>
        readonly_multiguard(const lock_type1& lock1, const lock_type2& lock2, const lock_typesN&... locks) : m_locks{ {&lock1, &lock2, &locks...} }
        {
            int lastLocked = -1; // Index to the last locked lock.

            bool locked = true;
            do
            {
                for (int i = 0; i <= lastLocked; i++) // If we failed to lock all locks we need to unlock the ones we did lock.
                    m_locks[i]->unlock(lock_state::read);

                // Reset variables
                locked = true;
                lastLocked = -1;

                // Try to lock all locks.
                for (int i = 0; i < m_locks.size(); i++)
                {
                    if (m_locks[i]->try_lock(lock_state::read))
                    {
                        lastLocked = i;
                    }
                    else
                    {
                        locked = false;
                        break;
                    }
                }
            } while (!locked);
        }

        readonly_multiguard(const readonly_multiguard&) = delete;

        ~readonly_multiguard()
        {
            for (auto* lock : m_locks)
                lock->unlock(lock_state::read);
        }

        readonly_multiguard& operator=(readonly_multiguard&&) = delete;
    };

#if !defined(DOXY_EXCLUDE)
    template<typename... types>
    readonly_multiguard(types...)->readonly_multiguard<sizeof...(types)>;
#endif

    class readwrite_guard final
    {
    private:
        const rw_spinlock& m_lock;

    public:
        readwrite_guard(const rw_spinlock& lock, wait_priority priority = wait_priority::real_time) : m_lock(lock)
        {
            m_lock.lock(lock_state::write, priority);
        }

        readwrite_guard(const readwrite_guard&) = delete;

        ~readwrite_guard()
        {
            m_lock.unlock(lock_state::write);
        }

        readwrite_guard& operator=(readwrite_guard&&) = delete;
    };


    template<size_type S>
    class readwrite_multiguard final
    {
    private:
        std::array<const rw_spinlock*, S> m_locks;

    public:
        template<typename lock_type1, typename lock_type2, typename... lock_typesN>
        readwrite_multiguard(const lock_type1& lock1, const lock_type2& lock2, const lock_typesN&... locks) : m_locks{ {&lock1, &lock2, &locks...} }
        {
            int lastLocked = -1; // Index to the last locked lock.

            bool locked = true;
            do
            {
                for (int i = 0; i <= lastLocked; i++) // If we failed to lock all locks we need to unlock the ones we did lock.
                    m_locks[i]->unlock(lock_state::write);

                // Reset variables
                locked = true;
                lastLocked = -1;

                // Try to lock all locks.
                for (int i = 0; i < m_locks.size(); i++)
                {
                    if (m_locks[i]->try_lock(lock_state::write))
                    {
                        lastLocked = i;
                    }
                    else
                    {
                        locked = false;
                        break;
                    }
                }
            } while (!locked);
        }

        readwrite_multiguard(const readwrite_multiguard&) = delete;

        ~readwrite_multiguard()
        {
            for (auto* lock : m_locks)
                lock->unlock(lock_state::write);
        }

        readwrite_multiguard& operator=(readwrite_multiguard&&) = delete;
    };

#if !defined(DOXY_EXCLUDE)
    template<typename... types>
    readwrite_multiguard(types...)->readwrite_multiguard<sizeof...(types)>;
#endif

    template<size_type S>
    class mixed_multiguard final
    {
    private:
        std::array<const rw_spinlock*, S / 2> m_locks;
        std::array<lock_state, S / 2> m_states;

        // Recursive function for filling the arrays with the neccessary data from the template arguments.
        template<size_type I, typename... types>
        void fill(const rw_spinlock& lock, lock_state state, types&&... args)
        {
            if constexpr (I > 2)
            {
                fill<I - 2>(args...);
            }

            m_locks[(I / 2) - 1] = &lock;
            m_states[(I / 2) - 1] = state;
        }

    public:
        template<typename... types>
        explicit mixed_multiguard(types&&... arguments)
        {
            static_assert(sizeof...(types) % 2 == 0, "Argument order should be (lock, lock-state, lock, lock-state). Argument count should thus be even.");

            fill<sizeof...(types)>(arguments...);

            int lastLocked = -1; // Index to the last locked lock.

            bool locked = true;
            do
            {
                for (int i = 0; i <= lastLocked; i++) // If we failed to lock all locks we need to unlock the ones we did lock.
                    m_locks[i]->unlock(m_states[i]);

                // Reset variables
                locked = true;
                lastLocked = -1;

                // Try to lock all locks.
                for (int i = 0; i < m_locks.size(); i++)
                {
                    if (m_locks[i]->try_lock(m_states[i]))
                    {
                        lastLocked = i;
                    }
                    else
                    {
                        locked = false;
                        break;
                    }
                }
            } while (!locked);
        }

        mixed_multiguard(const mixed_multiguard&) = delete;
        mixed_multiguard(mixed_multiguard&&) = delete;

        ~mixed_multiguard()
        {
            for (int i = 0; i < m_locks.size(); i++)
                m_locks[i]->unlock(m_states[i]);
        }

        mixed_multiguard& operator=(mixed_multiguard&&) = delete;
        mixed_multiguard& operator=(const mixed_multiguard&) = delete;
    };

#if !defined(DOXY_EXCLUDE)
    // CTAD so you don't need to input the size of the guard parameters.
    template<typename... types>
    mixed_multiguard(types...)->mixed_multiguard<sizeof...(types)>;
#endif
}