Skip to content

File SPSCQueue.h

File List > Amplitude > Core > SPSCQueue.h

Go to the documentation of this file

// Copyright (c) 2026-present Sparky Studios. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef _AM_CORE_SPSCQUEUE_H
#define _AM_CORE_SPSCQUEUE_H

#include <atomic>

#include <SparkyStudios/Audio/Amplitude/Core/Common.h>

namespace SparkyStudios::Audio::Amplitude
{
    template<typename T, AmSize Capacity = 256>
    class SPSCQueue
    {
        static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be a power of 2");
        static_assert(Capacity > 0, "Capacity must be greater than 0");

    public:
        SPSCQueue()
            : _head(0)
            , _tail(0)
        {}

        ~SPSCQueue() = default;

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

        bool TryEnqueue(const T& item)
        {
            const AmSize tail = _tail.load(std::memory_order_relaxed);
            const AmSize nextTail = (tail + 1) & kMask;

            if (nextTail == _head.load(std::memory_order_acquire))
                return false;

            _buffer[tail] = item;
            _tail.store(nextTail, std::memory_order_release);
            return true;
        }

        bool TryEnqueue(T&& item)
        {
            const AmSize tail = _tail.load(std::memory_order_relaxed);
            const AmSize nextTail = (tail + 1) & kMask;

            if (nextTail == _head.load(std::memory_order_acquire))
                return false;

            _buffer[tail] = std::move(item);
            _tail.store(nextTail, std::memory_order_release);
            return true;
        }

        bool TryDequeue(T& item)
        {
            const AmSize head = _head.load(std::memory_order_relaxed);

            if (head == _tail.load(std::memory_order_acquire))
                return false;

            item = std::move(_buffer[head]);
            _head.store((head + 1) & kMask, std::memory_order_release);
            return true;
        }

        [[nodiscard]] bool IsEmpty() const
        {
            return _head.load(std::memory_order_relaxed) == _tail.load(std::memory_order_relaxed);
        }

        [[nodiscard]] static constexpr AmSize GetCapacity()
        {
            return Capacity - 1;
        }

    private:
        static constexpr AmSize kCacheLineSize = 64;
        static constexpr AmSize kMask = Capacity - 1;

        alignas(kCacheLineSize) std::atomic<AmSize> _head;
        alignas(kCacheLineSize) std::atomic<AmSize> _tail;
        alignas(kCacheLineSize) T _buffer[Capacity];
    };

} // namespace SparkyStudios::Audio::Amplitude

#endif // _AM_CORE_SPSCQUEUE_H