2023-08-26 10:33:12 +00:00
|
|
|
/**
|
|
|
|
* @file CircularQueue.h
|
|
|
|
* @author apio (cloudapio.eu)
|
|
|
|
* @brief Lock-free FIFO data structures.
|
|
|
|
*
|
|
|
|
* @copyright Copyright (c) 2022-2023, the Luna authors.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2022-12-26 18:59:18 +00:00
|
|
|
#pragma once
|
|
|
|
#include <luna/Atomic.h>
|
2023-07-28 15:28:18 +00:00
|
|
|
#include <luna/Heap.h>
|
|
|
|
#include <luna/Result.h>
|
2022-12-26 18:59:18 +00:00
|
|
|
#include <luna/Types.h>
|
|
|
|
|
2023-08-26 10:33:12 +00:00
|
|
|
/**
|
|
|
|
* @brief An atomic lock-free circular FIFO queue.
|
|
|
|
*
|
|
|
|
* @tparam T The type of data to store in this queue.
|
|
|
|
* @tparam Size The amount of elements to make space for.
|
|
|
|
*/
|
2022-12-26 18:59:18 +00:00
|
|
|
template <typename T, usize Size> class CircularQueue
|
|
|
|
{
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
Capacity = Size + 1
|
|
|
|
};
|
|
|
|
|
|
|
|
public:
|
|
|
|
CircularQueue()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-08-26 10:33:12 +00:00
|
|
|
/**
|
|
|
|
* @brief Return whether the queue is empty.
|
|
|
|
*
|
|
|
|
* @return true The queue is empty.
|
|
|
|
* @return false The queue is not empty.
|
|
|
|
*/
|
2023-08-02 09:55:08 +00:00
|
|
|
bool is_empty()
|
|
|
|
{
|
|
|
|
return m_tail.load() == m_head.load();
|
|
|
|
}
|
|
|
|
|
2023-08-26 10:33:12 +00:00
|
|
|
/**
|
|
|
|
* @brief Push a value onto the queue.
|
|
|
|
*
|
|
|
|
* @param value The value to push.
|
2023-08-26 18:49:57 +00:00
|
|
|
* @return true The operation succeeded.
|
2023-08-26 10:33:12 +00:00
|
|
|
* @return false The queue was full or someone else was trying to push a value at the same time.
|
|
|
|
*/
|
2022-12-26 18:59:18 +00:00
|
|
|
bool try_push(const T& value)
|
|
|
|
{
|
|
|
|
usize current_tail = m_tail.load(MemoryOrder::Relaxed);
|
|
|
|
const usize new_tail = (current_tail + 1) % Capacity;
|
|
|
|
if (new_tail == m_head.load(MemoryOrder::Acquire))
|
|
|
|
{
|
|
|
|
// Queue is full
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
m_data[current_tail] = value;
|
|
|
|
if (!m_tail.compare_exchange_strong(current_tail, new_tail, MemoryOrder::Release, MemoryOrder::Relaxed))
|
|
|
|
{
|
|
|
|
// Someone else updated the tail
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-08-26 10:33:12 +00:00
|
|
|
/**
|
|
|
|
* @brief Pop a value from the queue.
|
|
|
|
*
|
|
|
|
* @param value The variable to store the value into.
|
2023-08-26 18:49:57 +00:00
|
|
|
* @return true The operation succeeded.
|
2023-08-26 10:33:12 +00:00
|
|
|
* @return false The queue was empty or someone else was trying to pop a value at the same time.
|
|
|
|
*/
|
2022-12-26 18:59:18 +00:00
|
|
|
bool try_pop(T& value)
|
|
|
|
{
|
|
|
|
usize current_head = m_head.load(MemoryOrder::Relaxed);
|
|
|
|
if (current_head == m_tail.load(MemoryOrder::Acquire))
|
|
|
|
{
|
|
|
|
// Queue is empty
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
value = m_data[current_head];
|
|
|
|
const usize new_head = (current_head + 1) % Capacity;
|
|
|
|
if (!m_head.compare_exchange_strong(current_head, new_head, MemoryOrder::Release, MemoryOrder::Relaxed))
|
|
|
|
{
|
|
|
|
// Someone else updated the head
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
T m_data[Capacity];
|
|
|
|
Atomic<usize> m_head = 0;
|
|
|
|
Atomic<usize> m_tail = 0;
|
|
|
|
};
|
2023-07-28 15:28:18 +00:00
|
|
|
|
2023-08-26 10:33:12 +00:00
|
|
|
/**
|
|
|
|
* @brief An atomic lock-free circular FIFO queue.
|
|
|
|
*
|
|
|
|
* In this variant the size is set at runtime.
|
|
|
|
*
|
|
|
|
* @tparam T The type of data to store in this queue.
|
|
|
|
*/
|
2023-07-28 15:28:18 +00:00
|
|
|
template <typename T> class DynamicCircularQueue
|
|
|
|
{
|
|
|
|
|
|
|
|
public:
|
|
|
|
DynamicCircularQueue()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
~DynamicCircularQueue()
|
|
|
|
{
|
|
|
|
if (m_data) free_impl(m_data);
|
|
|
|
}
|
|
|
|
|
2023-08-26 10:33:12 +00:00
|
|
|
/**
|
|
|
|
* @brief Return whether the queue is empty.
|
|
|
|
*
|
|
|
|
* @return true The queue is empty.
|
|
|
|
* @return false The queue is not empty.
|
|
|
|
*/
|
2023-08-02 09:55:08 +00:00
|
|
|
bool is_empty()
|
|
|
|
{
|
|
|
|
return m_tail.load() == m_head.load();
|
|
|
|
}
|
|
|
|
|
2023-08-26 10:33:12 +00:00
|
|
|
/**
|
|
|
|
* @brief Set the size of the queue and allocate memory for it.
|
|
|
|
*
|
|
|
|
* This should not be used to grow the queue, as all existing data is lost. In most cases, this function will only
|
|
|
|
* be called once to set the initial size of the queue and that's it.
|
|
|
|
*
|
|
|
|
* @param size The amount of elements to make space for.
|
2023-08-26 18:49:57 +00:00
|
|
|
* @return Result<void> Whether the operation succeeded.
|
2023-08-26 10:33:12 +00:00
|
|
|
*/
|
2023-07-28 15:28:18 +00:00
|
|
|
Result<void> set_size(usize size)
|
|
|
|
{
|
2023-08-26 10:33:12 +00:00
|
|
|
if (m_data) free_impl(m_data);
|
2023-07-28 15:28:18 +00:00
|
|
|
m_data = (T*)TRY(calloc_impl(size + 1, sizeof(T), false));
|
|
|
|
m_capacity = size + 1;
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2023-08-26 10:33:12 +00:00
|
|
|
/**
|
|
|
|
* @brief Push a value onto the queue.
|
|
|
|
*
|
|
|
|
* @param value The value to push.
|
2023-08-26 18:49:57 +00:00
|
|
|
* @return true The operation succeeded.
|
2023-08-26 10:33:12 +00:00
|
|
|
* @return false The queue was full or someone else was trying to push a value at the same time.
|
|
|
|
*/
|
2023-07-28 15:28:18 +00:00
|
|
|
bool try_push(const T& value)
|
|
|
|
{
|
|
|
|
check(m_capacity);
|
|
|
|
usize current_tail = m_tail.load(MemoryOrder::Relaxed);
|
|
|
|
const usize new_tail = (current_tail + 1) % m_capacity;
|
|
|
|
if (new_tail == m_head.load(MemoryOrder::Acquire))
|
|
|
|
{
|
|
|
|
// Queue is full
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
m_data[current_tail] = value;
|
|
|
|
if (!m_tail.compare_exchange_strong(current_tail, new_tail, MemoryOrder::Release, MemoryOrder::Relaxed))
|
|
|
|
{
|
|
|
|
// Someone else updated the tail
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-08-26 10:33:12 +00:00
|
|
|
/**
|
|
|
|
* @brief Pop a value from the queue.
|
|
|
|
*
|
|
|
|
* @param value The variable to store the value into.
|
2023-08-26 18:49:57 +00:00
|
|
|
* @return true The operation succeeded.
|
2023-08-26 10:33:12 +00:00
|
|
|
* @return false The queue was empty or someone else was trying to pop a value at the same time.
|
|
|
|
*/
|
2023-07-28 15:28:18 +00:00
|
|
|
bool try_pop(T& value)
|
|
|
|
{
|
|
|
|
check(m_capacity);
|
|
|
|
usize current_head = m_head.load(MemoryOrder::Relaxed);
|
|
|
|
if (current_head == m_tail.load(MemoryOrder::Acquire))
|
|
|
|
{
|
|
|
|
// Queue is empty
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
value = m_data[current_head];
|
|
|
|
const usize new_head = (current_head + 1) % m_capacity;
|
|
|
|
if (!m_head.compare_exchange_strong(current_head, new_head, MemoryOrder::Release, MemoryOrder::Relaxed))
|
|
|
|
{
|
|
|
|
// Someone else updated the head
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
T* m_data = nullptr;
|
|
|
|
usize m_capacity = 0;
|
|
|
|
Atomic<usize> m_head = 0;
|
|
|
|
Atomic<usize> m_tail = 0;
|
|
|
|
};
|