mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-27 08:05:41 +00:00
230 lines
5.3 KiB
C++
230 lines
5.3 KiB
C++
#pragma once
|
|
#include "assert.h"
|
|
#include "types.h"
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <type_traits>
|
|
|
|
#ifdef _MSC_VER
|
|
#include <malloc.h> // _aligned_malloc
|
|
#else
|
|
#include <stdlib.h> // posix_memalign
|
|
#endif
|
|
|
|
template<typename T, u32 CAPACITY>
|
|
class FIFOQueue
|
|
{
|
|
public:
|
|
const T* GetDataPointer() const { return m_ptr; }
|
|
T* GetDataPointer() { return m_ptr; }
|
|
const T* GetReadPointer() const { return &m_ptr[m_head]; }
|
|
T* GetReadPointer() { return &m_ptr[m_head]; }
|
|
constexpr u32 GetCapacity() const { return CAPACITY; }
|
|
T* GetWritePointer() { return &m_ptr[m_tail]; }
|
|
u32 GetSize() const { return m_size; }
|
|
u32 GetSpace() const { return CAPACITY - m_size; }
|
|
u32 GetContiguousSpace() const { return (m_tail >= m_head) ? (CAPACITY - m_tail) : (m_head - m_tail); }
|
|
u32 GetContiguousSize() const { return std::min<u32>(CAPACITY - m_head, m_size); }
|
|
bool IsEmpty() const { return m_size == 0; }
|
|
bool IsFull() const { return m_size == CAPACITY; }
|
|
|
|
void Clear()
|
|
{
|
|
m_head = 0;
|
|
m_tail = 0;
|
|
m_size = 0;
|
|
}
|
|
|
|
template<class... Args>
|
|
T& Emplace(Args&&... args)
|
|
{
|
|
T& ref = PushAndGetReference();
|
|
new (&ref) T(std::forward<Args...>(args...));
|
|
return ref;
|
|
}
|
|
|
|
template<class Y = T, std::enable_if_t<std::is_pod_v<Y>, int> = 0>
|
|
T& Push(const T& value)
|
|
{
|
|
T& ref = PushAndGetReference();
|
|
std::memcpy(&ref, &value, sizeof(T));
|
|
return ref;
|
|
}
|
|
|
|
template<class Y = T, std::enable_if_t<!std::is_pod_v<Y>, int> = 0>
|
|
T& Push(const T& value)
|
|
{
|
|
T& ref = PushAndGetReference();
|
|
new (&ref) T(value);
|
|
return ref;
|
|
}
|
|
|
|
// faster version of push_back_range for POD types which can be memcpy()ed
|
|
template<class Y = T, std::enable_if_t<std::is_pod_v<Y>, int> = 0>
|
|
void PushRange(const T* data, u32 size)
|
|
{
|
|
DebugAssert((m_size + size) <= CAPACITY);
|
|
const u32 space_before_end = CAPACITY - m_tail;
|
|
const u32 size_before_end = (size > space_before_end) ? space_before_end : size;
|
|
const u32 size_after_end = size - size_before_end;
|
|
|
|
std::memcpy(&m_ptr[m_tail], data, sizeof(T) * size_before_end);
|
|
m_tail = (m_tail + size_before_end) % CAPACITY;
|
|
|
|
if (size_after_end > 0)
|
|
{
|
|
std::memcpy(&m_ptr[m_tail], data + size_before_end, sizeof(T) * size_after_end);
|
|
m_tail = (m_tail + size_after_end) % CAPACITY;
|
|
}
|
|
|
|
m_size += size;
|
|
}
|
|
|
|
template<class Y = T, std::enable_if_t<!std::is_pod_v<Y>, int> = 0>
|
|
void PushRange(const T* data, u32 size)
|
|
{
|
|
DebugAssert((m_size + size) <= CAPACITY);
|
|
while (size > 0)
|
|
{
|
|
T& ref = PushAndGetReference();
|
|
new (&ref) T(*data);
|
|
data++;
|
|
size--;
|
|
}
|
|
}
|
|
|
|
const T& Peek() const { return m_ptr[m_head]; }
|
|
const T& Peek(u32 offset) { return m_ptr[(m_head + offset) % CAPACITY]; }
|
|
|
|
void Remove(u32 count)
|
|
{
|
|
DebugAssert(m_size >= count);
|
|
for (u32 i = 0; i < count; i++)
|
|
{
|
|
m_ptr[m_head].~T();
|
|
m_head = (m_head + 1) % CAPACITY;
|
|
m_size--;
|
|
}
|
|
}
|
|
|
|
void RemoveOne()
|
|
{
|
|
DebugAssert(m_size > 0);
|
|
m_ptr[m_head].~T();
|
|
m_head = (m_head + 1) % CAPACITY;
|
|
m_size--;
|
|
}
|
|
|
|
// removes and returns moved value
|
|
T Pop()
|
|
{
|
|
DebugAssert(m_size > 0);
|
|
T val = std::move(m_ptr[m_head]);
|
|
m_ptr[m_head].~T();
|
|
m_head = (m_head + 1) % CAPACITY;
|
|
m_size--;
|
|
return val;
|
|
}
|
|
|
|
void PopRange(T* out_data, u32 count)
|
|
{
|
|
DebugAssert(m_size >= count);
|
|
|
|
for (u32 i = 0; i < count; i++)
|
|
{
|
|
out_data[i] = std::move(m_ptr[m_head]);
|
|
m_ptr[m_head].~T();
|
|
m_head = (m_head + 1) % CAPACITY;
|
|
m_size--;
|
|
}
|
|
}
|
|
|
|
template<u32 QUEUE_CAPACITY>
|
|
void PushFromQueue(FIFOQueue<T, QUEUE_CAPACITY>* other_queue)
|
|
{
|
|
while (!other_queue->IsEmpty() && !IsFull())
|
|
{
|
|
T& dest = PushAndGetReference();
|
|
dest = std::move(other_queue->Pop());
|
|
}
|
|
}
|
|
|
|
void AdvanceTail(u32 count)
|
|
{
|
|
DebugAssert((m_size + count) <= CAPACITY);
|
|
DebugAssert((m_tail + count) <= CAPACITY);
|
|
m_tail = (m_tail + count) % CAPACITY;
|
|
m_size += count;
|
|
}
|
|
|
|
protected:
|
|
FIFOQueue() = default;
|
|
|
|
T& PushAndGetReference()
|
|
{
|
|
DebugAssert(m_size < CAPACITY);
|
|
T& ref = m_ptr[m_tail];
|
|
m_tail = (m_tail + 1) % CAPACITY;
|
|
m_size++;
|
|
return ref;
|
|
}
|
|
|
|
T* m_ptr = nullptr;
|
|
u32 m_head = 0;
|
|
u32 m_tail = 0;
|
|
u32 m_size = 0;
|
|
};
|
|
|
|
template<typename T, u32 CAPACITY>
|
|
class InlineFIFOQueue : public FIFOQueue<T, CAPACITY>
|
|
{
|
|
public:
|
|
InlineFIFOQueue() : FIFOQueue<T, CAPACITY>() { this->m_ptr = m_inline_data; }
|
|
|
|
private:
|
|
T m_inline_data[CAPACITY] = {};
|
|
};
|
|
|
|
template<typename T, u32 CAPACITY, u32 ALIGNMENT = 0>
|
|
class HeapFIFOQueue : public FIFOQueue<T, CAPACITY>
|
|
{
|
|
public:
|
|
HeapFIFOQueue() : FIFOQueue<T, CAPACITY>()
|
|
{
|
|
if constexpr (ALIGNMENT > 0)
|
|
{
|
|
#ifdef _MSC_VER
|
|
this->m_ptr = static_cast<T*>(_aligned_malloc(sizeof(T) * CAPACITY, ALIGNMENT));
|
|
#else
|
|
if (posix_memalign(reinterpret_cast<void**>(&this->m_ptr), ALIGNMENT, sizeof(T) * CAPACITY) != 0)
|
|
this->m_ptr = nullptr;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
this->m_ptr = static_cast<T*>(std::malloc(sizeof(T) * CAPACITY));
|
|
}
|
|
|
|
if (!this->m_ptr)
|
|
Panic("Heap allocation failed");
|
|
|
|
std::memset(this->m_ptr, 0, sizeof(T) * CAPACITY);
|
|
}
|
|
|
|
~HeapFIFOQueue()
|
|
{
|
|
if constexpr (ALIGNMENT > 0)
|
|
{
|
|
#ifdef _MSC_VER
|
|
_aligned_free(this->m_ptr);
|
|
#else
|
|
free(this->m_ptr);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
free(this->m_ptr);
|
|
}
|
|
}
|
|
};
|