Danny Robson
71d9a71b20
aligned_storage_t is deprecated and will generate warnings. Use a very similar construct within the stack itself instead.
167 lines
4.9 KiB
C++
167 lines
4.9 KiB
C++
/*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* Copyright 2019 Danny Robson <danny@nerdcruft.net>
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "../thread/primitive.hpp"
|
|
#include "../thread/spinlock.hpp"
|
|
#include "../view.hpp"
|
|
|
|
#include <atomic>
|
|
#include <vector>
|
|
#include <mutex>
|
|
|
|
#include <cstddef>
|
|
|
|
|
|
namespace cruft::parallel {
|
|
/// A trivial thread safe stack with a fixed capacity.
|
|
///
|
|
/// This implementation uses a spinlock, so don't try to push large
|
|
/// objects under high contention. In the future it may be replaced with a
|
|
/// different locking mechanism.
|
|
template <typename ValueT>
|
|
class stack {
|
|
public:
|
|
stack (stack const &) = delete;
|
|
stack& operator=(stack const &) = delete;
|
|
|
|
stack (stack &&rhs):
|
|
stack (0)
|
|
{
|
|
swap (rhs);
|
|
}
|
|
|
|
stack& operator= (stack &&rhs)
|
|
{
|
|
swap (rhs);
|
|
return *this;
|
|
}
|
|
|
|
stack clone (void) const
|
|
{
|
|
stack res (capacity ());
|
|
|
|
std::lock_guard lk (m_lock);
|
|
res.m_cursor = m_cursor.load ();
|
|
res.m_store = m_store;
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/// Thread safe swapping with the supplied object
|
|
void swap (stack &rhs)
|
|
{
|
|
std::lock_guard lk0 (m_lock);
|
|
std::lock_guard lk1 (rhs.m_lock);
|
|
|
|
auto tmp = m_cursor.load ();
|
|
m_cursor = rhs.m_cursor.load ();
|
|
rhs.m_cursor = tmp;
|
|
|
|
std::swap (m_store, rhs.m_store);
|
|
std::swap (m_lock, rhs.m_lock);
|
|
}
|
|
|
|
|
|
stack (std::size_t capacity)
|
|
: m_cursor (0)
|
|
, m_store (capacity)
|
|
{
|
|
// NOTE: a capacity of zero must be supported as it forms part of
|
|
// how we structure the move operations
|
|
}
|
|
|
|
/// Move the value from the top of the stack into an output pointer.
|
|
///
|
|
/// The internal object will be destroyed after the output has been set.
|
|
///
|
|
/// The value `true` will be returned if the value was successfully
|
|
/// copied. If no object is available for popping, or locking failed,
|
|
/// the value `false` will be returned.
|
|
///
|
|
/// NOTE: There are no exception guarantees at this time.
|
|
bool pop [[nodiscard]] (ValueT *out)
|
|
{
|
|
std::lock_guard lk (m_lock);
|
|
if (m_cursor == 0)
|
|
return false;
|
|
|
|
auto ptr = reinterpret_cast<ValueT*> (&m_store[--m_cursor]);
|
|
*out = std::move (*ptr);
|
|
ptr->~ValueT ();
|
|
return true;
|
|
}
|
|
|
|
|
|
/// Constructs a ValueT on the top of the stack.
|
|
///
|
|
/// The value `true` will be returned if the object was successfully
|
|
/// stored. In the event of a lack of capacity, locking failure, or
|
|
/// other error the value `false` will be returned and the user may
|
|
/// retry the operation.
|
|
///
|
|
/// NOTE: There are no exception guarantees at this time.
|
|
template <typename InputT>
|
|
bool push [[nodiscard]] (InputT &&arg)
|
|
{
|
|
std::lock_guard lk (m_lock);
|
|
if (m_cursor >= m_store.size ())
|
|
return false;
|
|
|
|
auto ptr = reinterpret_cast<ValueT *> (&m_store[m_cursor++]);
|
|
new (ptr) ValueT (std::forward<InputT> (arg));
|
|
return true;
|
|
}
|
|
|
|
std::size_t capacity (void) const { return m_store.size (); }
|
|
std::size_t size (void) const { return m_cursor; }
|
|
bool empty (void) const { return m_cursor == 0; }
|
|
|
|
|
|
void clear (void)
|
|
{
|
|
std::lock_guard lk (m_lock);
|
|
for (auto &raw: store (contract::I_HAVE_LOCKED_THIS_STRUCTURE))
|
|
raw.~ValueT ();
|
|
m_cursor = 0;
|
|
}
|
|
|
|
|
|
// DO NOT make this enum easier to use. It's supposed to be annoying
|
|
// so that people don't use it.
|
|
enum class contract { I_HAVE_LOCKED_THIS_STRUCTURE };
|
|
|
|
/// Returns the currently available data.
|
|
///
|
|
/// Data is laid out bottom-to-top of the stack.
|
|
///
|
|
/// DO NOT call this if there are any other potential clients
|
|
/// accessing the structure as it will not be protected by locks.
|
|
cruft::view<ValueT*> store (contract)
|
|
{
|
|
auto begin = reinterpret_cast<ValueT*> (m_store.data ());
|
|
auto end = begin + m_cursor.load ();
|
|
|
|
return cruft::view<ValueT*> { begin, end };
|
|
}
|
|
|
|
private:
|
|
using index_type = std::size_t;
|
|
|
|
struct raw_type {
|
|
alignas(ValueT) std::byte data[sizeof(ValueT)];
|
|
};
|
|
|
|
mutable cruft::thread::spinlock m_lock;
|
|
std::atomic<index_type> m_cursor;
|
|
std::vector<raw_type> m_store;
|
|
};
|
|
}
|