Skip to content
This repository has been archived by the owner on Jan 18, 2024. It is now read-only.

Commit

Permalink
Add embed::resumable<T>
Browse files Browse the repository at this point in the history
Resolves #205
  • Loading branch information
kammce committed Nov 6, 2022
1 parent d1cb3fa commit 5f0ce3b
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 7 deletions.
103 changes: 103 additions & 0 deletions include/libhal/resumable.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#pragma once

#include <array>
#include <cstddef>
#include <experimental/coroutine>
#include <iterator>
#include <memory>
#include <optional>
#include <span>
#include <type_traits>

namespace hal {

/**
* @brief An allocator aware coroutine for creating resumable functions
*
* When the coroutine finishes, meaning a `co_return` was called, the resumable
* will continue to return the last yield value it had, thus calling a finished
* resumable has well defined behavior.
*
* @tparam T - return type of the resumable function, cannot be void
*/
template<typename T>
struct resumable
{
struct promise_type;
using handle_t = std::experimental::coroutine_handle<promise_type>;

struct promise_type
{
resumable get_return_object()
{
return resumable(handle_t::from_promise(*this));
}

std::experimental::suspend_always initial_suspend()
{
return {};
}

std::experimental::suspend_always final_suspend() noexcept
{
return {};
}

void unhandled_exception()
{
}

template<std::convertible_to<T> From>
std::experimental::suspend_always yield_value(From&& from)
{
value = std::forward<From>(from);
return {};
}

void return_void()
{
}

template<class Allocator, typename... Args>
void* operator new(size_t p_size,
std::allocator_arg_t,
Allocator* alloc,
[[maybe_unused]] Args&... args)
{
return alloc->allocate(p_size);
}

void operator delete(void*, size_t)
{
}

T value;
};

resumable(handle_t p_handle)
: m_coroutine_handle(p_handle)
{
}

~resumable()
{
m_coroutine_handle.destroy();
}

explicit operator bool()
{
return !m_coroutine_handle.done();
}

T operator()()
{
if (!m_coroutine_handle.done()) {
m_coroutine_handle();
}
return std::move(m_coroutine_handle.promise().value);
}

private:
handle_t m_coroutine_handle;
};
} // namespace hal
105 changes: 105 additions & 0 deletions include/libhal/static_allocator.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#pragma once

#include <array>
#include <cstddef>
#include <cstdint>
#include <span>

namespace hal {

/**
* @brief An Allocator that uses a pool of memory
*
* This allocator is monotonic and does not deallocate
*
* @tparam T - allocator type
*/
template<class T>
class allocator
{
public:
using value_type = T;

/**
* @brief Construct a new allocator object
*
* @param p_memory - pool of memory to use for the allocator. This memory must
* outlive this object.
*/
allocator(std::span p_memory)
: m_memory(p_memory)
{
}

template<class U>
constexpr allocator(const allocator<U, Size>&) noexcept
{
}

/**
* @brief Allocate a number of elements from the memory pool
*
* @param p_number_of_elements - element count
* @return T* - address of the allocated memory
*/
[[nodiscard]] T* allocate(std::size_t p_number_of_elements)
{
if (p_number_of_elements > m_memory.size()) {
return nullptr;
}

T* result = m_memory.data();
m_memory = m_memory.subspan(p_number_of_elements);
return result;
}

/**
* @brief Deallocator that does nothing
*
* @param p_address
* @param p_elements
*/
void deallocate([[maybe_unused]] T* p_address,
[[maybe_unused]] std::size_t p_elements) noexcept
{
}

template<class T, size_t TSize, class U, size_t USize>
friend bool operator==(const allocator<T, TSize>&, const allocator<U, USize>&)
{
return true;
}

template<class T, size_t TSize, class U, size_t USize>
friend bool operator!=(const allocator<T, TSize>&, const allocator<U, USize>&)
{
return false;
}

private:
std::span<T> m_memory;
};

/**
* @brief Owning static memory allocator
*
* @tparam T - allocator type
* @tparam Size - number of elements
*/
template<class T, size_t Size>
class static_allocator : public hal::allocator
{
public:
/**
* @brief Construct a new static allocator object
*
*/
static_allocator()
: allocator(m_buffer)
{
}

private:
std::array<T, Size> m_buffer;
};
} // namespace hal
15 changes: 8 additions & 7 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
message(FATAL_ERROR "GCC version must be at least 11!")
endif()
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
# require at least clang 13
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13)
message(FATAL_ERROR "Clang version must be at least 13!")
# require at least clang 14
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 14)
message(FATAL_ERROR "Clang version must be at least 14!")
endif()
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
# require at least clang 13
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13)
message(FATAL_ERROR "Clang version must be at least 13!")
# require at least clang 14
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 14)
message(FATAL_ERROR "Clang version must be at least 14!")
endif()
else()
message(WARNING "You are using an unsupported compiler! Compilation has only been tested with Clang and GCC, detected ${CMAKE_CXX_COMPILER_ID}.")
Expand Down Expand Up @@ -74,13 +74,14 @@ add_executable(${PROJECT_NAME}
main.test.cpp
move_interceptor.test.cpp
overflow_counter.test.cpp
resumable.test.cpp
math.test.cpp
error.test.cpp
map.test.cpp
units.test.cpp)

target_include_directories(${PROJECT_NAME} PUBLIC . ../include)
target_compile_options(${PROJECT_NAME} PRIVATE -Werror -Wall -Wextra
target_compile_options(${PROJECT_NAME} PRIVATE -fcoroutines-ts -Werror -Wall -Wextra
-Wno-unused-function -Wconversion -Wno-psabi)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20)
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_EXTENSIONS OFF)
Expand Down
43 changes: 43 additions & 0 deletions tests/resumable.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include <libhal/resumable.hpp>

#include <boost/ut.hpp>

#include <array>

namespace hal {
boost::ut::suite resumable_test = []() {
using namespace boost::ut;

"hal::resumable<T>()"_test = []() {
"hal::resumable<int>()"_test = []() {
// Setup
auto resumable_lambda = []() -> hal::resumable<int> {
co_yield 1;
co_yield 2;
co_return;
};

// Exercise
auto resumer = resumable_lambda();
auto resumer_state1 = bool{ resumer };
auto value1 = resumer();
auto value2 = resumer();
auto resumer_state2 = bool{ resumer };
auto value3 = resumer();
auto resumer_state3 = bool{ resumer };
auto value4 = resumer();
auto resumer_state4 = bool{ resumer };

// Verify
expect(that % resumer_state1);
expect(that % 1 == value1);
expect(that % 2 == value2);
expect(that % resumer_state2);
expect(that % 2 == value3);
expect(that % 2 == value4);
expect(that % !resumer_state3);
expect(that % !resumer_state4);
};
};
};
}

0 comments on commit 5f0ce3b

Please sign in to comment.