From 3d76078c5ccc4703c5a0255e682bd3ead112ee1e Mon Sep 17 00:00:00 2001 From: bergmann Date: Fri, 15 Nov 2019 00:05:09 +0100 Subject: [PATCH] * Initial commit (implemented future and result) --- .gitignore | 1 + .gitmodules | 3 + .vscode/launch.json | 27 +++++ .vscode/settings.json | 53 ++++++++ CMakeLists.txt | 63 ++++++++++ README.md | 5 + cmake/asyncpp-config.cmake | 9 ++ cmake/asyncpp-const.cmake | 28 +++++ cmake/asyncpp-options.cmake | 11 ++ cmake/asyncpp-var.cmake | 32 +++++ cmake/modules | 1 + include/asyncpp.h | 7 ++ include/asyncpp/future.h | 51 ++++++++ include/asyncpp/future.inl | 93 +++++++++++++++ include/asyncpp/future.pre.h | 38 ++++++ include/asyncpp/future/and_then.h | 37 ++++++ include/asyncpp/future/and_then.inl | 64 ++++++++++ include/asyncpp/future/map.h | 31 +++++ include/asyncpp/future/map.inl | 55 +++++++++ include/asyncpp/result.h | 118 ++++++++++++++++++ include/asyncpp/result.inl | 179 ++++++++++++++++++++++++++++ src/CMakeLists.txt | 30 +++++ test/CMakeLists.txt | 59 +++++++++ test/asyncpp/future_tests.cpp | 111 +++++++++++++++++ test/asyncpp/result_tests.cpp | 49 ++++++++ 25 files changed, 1155 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 cmake/asyncpp-config.cmake create mode 100644 cmake/asyncpp-const.cmake create mode 100644 cmake/asyncpp-options.cmake create mode 100644 cmake/asyncpp-var.cmake create mode 160000 cmake/modules create mode 100644 include/asyncpp.h create mode 100644 include/asyncpp/future.h create mode 100644 include/asyncpp/future.inl create mode 100644 include/asyncpp/future.pre.h create mode 100644 include/asyncpp/future/and_then.h create mode 100644 include/asyncpp/future/and_then.inl create mode 100644 include/asyncpp/future/map.h create mode 100644 include/asyncpp/future/map.inl create mode 100644 include/asyncpp/result.h create mode 100644 include/asyncpp/result.inl create mode 100644 src/CMakeLists.txt create mode 100644 test/CMakeLists.txt create mode 100644 test/asyncpp/future_tests.cpp create mode 100644 test/asyncpp/result_tests.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f65519e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/** diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..37e8830 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "cmake/modules"] + path = cmake/modules + url = b3rgmann@git.bergmann89.de:cpp/CMakeModules.git diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c94db89 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,27 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "future tests", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/test/test-asyncpp-future-tests", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f828218 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,53 @@ +{ + "files.associations": { + "optional": "cpp", + "memory": "cpp", + "type_traits": "cpp", + "variant": "cpp", + "array": "cpp", + "atomic": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "cstdarg": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "fstream": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "new": "cpp", + "ostream": "cpp", + "numeric": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "system_error": "cpp", + "cinttypes": "cpp", + "tuple": "cpp", + "utility": "cpp", + "typeinfo": "cpp", + "cstddef": "cpp", + "string_view": "cpp", + "algorithm": "cpp", + "iterator": "cpp", + "map": "cpp", + "memory_resource": "cpp", + "random": "cpp", + "set": "cpp", + "string": "cpp" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6b848ea --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,63 @@ +# Initialize CMake ################################################################################ + +CMake_Minimum_Required ( VERSION 3.12.0 FATAL_ERROR ) + +# Set CMAKE_BUILD_TYPE +If ( NOT CMAKE_BUILD_TYPE ) + Set ( CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build!" FORCE ) +EndIf ( NOT CMAKE_BUILD_TYPE ) +Set_Property ( CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel ) + +# Set CMAKE_MODULE_PATH +If ( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" ) + Set ( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/" ) +EndIf ( ) +If ( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/cmake" ) + Set ( CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/cmake" ) +EndIf ( ) + +# Project ######################################################################################### + +Include ( GNUInstallDirs ) +Include ( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/asyncpp-options.cmake ) +Include ( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/asyncpp-const.cmake ) +Include ( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/asyncpp-var.cmake ) +Project ( ${ASYNCPP_PROJECT_NAME} + DESCRIPTION "${ASYNCPP_PROJECT_DESCRIPTION}" + VERSION "${ASYNCPP_VERSION}" ) +Include ( CTest ) + +# Subdirectories +Add_SubDirectory ( ${CMAKE_CURRENT_SOURCE_DIR}/src ) +Add_SubDirectory ( ${CMAKE_CURRENT_SOURCE_DIR}/test ) + +# Install +If ( NOT ASYNCPP_HAS_EXPORT + OR NOT ASYNCPP_INSTALL_PACKAGE ) + Return ( ) +EndIf ( ) + +Include ( CMakePackageConfigHelpers ) +Write_Basic_Package_Version_File ( "${CMAKE_CURRENT_BINARY_DIR}/cmake/asyncpp-config-version.cmake" + VERSION ${ASYNCPP_VERSION} + COMPATIBILITY AnyNewerVersion ) +Configure_File ( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/asyncpp-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/asyncpp-config.cmake" + @ONLY ) + +Set ( ConfigPackageLocation "${ASYNCPP_INSTALL_DIR_SHARE}/cmake" ) +Install ( EXPORT + asyncpp + NAMESPACE + asyncpp:: + DESTINATION + ${ConfigPackageLocation} ) +Install ( FILES + "${CMAKE_CURRENT_BINARY_DIR}/cmake/asyncpp-config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/cmake/asyncpp-config-version.cmake" + DESTINATION + ${ConfigPackageLocation} + COMPONENT + Devel ) diff --git a/README.md b/README.md new file mode 100644 index 0000000..95ae3a0 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# asyncpp + +This is a C++ library that implements handling of asynchronous tasks. + +It is based on the idea of the [rust tokio crate](https://github.com/tokio-rs/tokio). diff --git a/cmake/asyncpp-config.cmake b/cmake/asyncpp-config.cmake new file mode 100644 index 0000000..d825fa7 --- /dev/null +++ b/cmake/asyncpp-config.cmake @@ -0,0 +1,9 @@ +# asyncpp-config.cmake - package configuration file + +# Include ( CMakeFindDependencyMacro ) +# Find_Dependency ( ) + +Include ( FindPackageHandleStandardArgs ) +Set ( ${CMAKE_FIND_PACKAGE_NAME}_CONFIG ${CMAKE_CURRENT_LIST_FILE} ) +Find_Package_Handle_Standard_Args ( asyncpp CONFIG_MODE ) +Include ( "${CMAKE_CURRENT_LIST_DIR}/asyncpp.cmake") diff --git a/cmake/asyncpp-const.cmake b/cmake/asyncpp-const.cmake new file mode 100644 index 0000000..c7b78aa --- /dev/null +++ b/cmake/asyncpp-const.cmake @@ -0,0 +1,28 @@ +# This file contains constant variables that are fixed to this project + +# Version +Set ( ASYNCPP_VERSION_MAJOR 1 ) +Set ( ASYNCPP_VERSION_MINOR 0 ) +Set ( ASYNCPP_VERSION_PATCH 0 ) +Set ( ASYNCPP_VERSION_BUILD 0 ) +Set ( ASYNCPP_VERSION_HASH "" ) +Set ( ASYNCPP_VERSION_BEHIND 0 ) +Set ( ASYNCPP_VERSION_DIRTY 0 ) + +# Names +Set ( ASYNCPP_PROJECT_NAME "asyncpp" ) +Set ( ASYNCPP_PROJECT_DESCRIPTION "A simple interface library" ) + +# Include generated variables for further usage +Include ( ${CMAKE_CURRENT_LIST_DIR}/asyncpp-var.cmake ) + +# Install directories +Set ( ASYNCPP_INSTALL_DIR_INCLUDE "${CMAKE_INSTALL_INCLUDEDIR}/${ASYNCPP_NAME}" ) +Set ( ASYNCPP_INSTALL_DIR_LIB "${CMAKE_INSTALL_LIBDIR}" ) +Set ( ASYNCPP_INSTALL_DIR_SHARE "${CMAKE_INSTALL_DATAROOTDIR}/${ASYNCPP_NAME}" ) + +# C Standard +Set ( CMAKE_C_STANDARD 11 ) +Set ( CMAKE_CXX_STANDARD 17 ) +Set ( CMAKE_C_STANDARD_REQUIRED ON ) +Set ( CMAKE_CXX_STANDARD_REQUIRED ON ) diff --git a/cmake/asyncpp-options.cmake b/cmake/asyncpp-options.cmake new file mode 100644 index 0000000..0eb8548 --- /dev/null +++ b/cmake/asyncpp-options.cmake @@ -0,0 +1,11 @@ +# This file contains options that can be passed to the cmake command + +Option ( ASYNCPP_INSTALL_HEADER + "Install headers of asyncpp." + ON ) +Option ( ASYNCPP_INSTALL_PACKAGE + "Install the cmake package of asyncpp." + ON ) +Option ( ASYNCPP_USE_GIT_VERSION + "Read the git tags to get the version of asyncpp" + ON ) diff --git a/cmake/asyncpp-var.cmake b/cmake/asyncpp-var.cmake new file mode 100644 index 0000000..77e3a5e --- /dev/null +++ b/cmake/asyncpp-var.cmake @@ -0,0 +1,32 @@ +# This file contains generated variables that are needed for the project + +# Git Version +If ( ASYNCPP_USE_GIT_VERSION ) + Include ( git_helper OPTIONAL RESULT_VARIABLE HAS_GIT_HELPER ) + If ( HAS_GIT_HELPER ) + GitGetVersion ( ${CMAKE_CURRENT_LIST_DIR}/.. + ASYNCPP_VERSION_MAJOR + ASYNCPP_VERSION_MINOR + ASYNCPP_VERSION_PATCH + ASYNCPP_VERSION_BUILD + ASYNCPP_VERSION_HASH + ASYNCPP_VERSION_BEHIND + ASYNCPP_VERSION_DIRTY ) + EndIf ( ) +EndIf ( ) + +# Strings +Set ( ASYNCPP_VERSION_SHORT + "${ASYNCPP_VERSION_MAJOR}.${ASYNCPP_VERSION_MINOR}" ) +Set ( ASYNCPP_VERSION + "${ASYNCPP_VERSION_SHORT}.${ASYNCPP_VERSION_PATCH}.${ASYNCPP_VERSION_BUILD}" ) +Set ( ASYNCPP_VERSION_COMPLETE + "${ASYNCPP_VERSION}" ) +Set ( ASYNCPP_NAME + "${ASYNCPP_PROJECT_NAME}-${ASYNCPP_VERSION_SHORT}" ) +Set ( ASYNCPP_OUTPUTNAME + "${ASYNCPP_PROJECT_NAME}" ) +If ( ASYNCPP_VERSION_BEHIND ) + Set ( ASYNCPP_VERSION_COMPLETE + "${ASYNCPP_VERSION_COMPLETE}+${ASYNCPP_VERSION_BEHIND}" ) +EndIf ( ) diff --git a/cmake/modules b/cmake/modules new file mode 160000 index 0000000..94b9877 --- /dev/null +++ b/cmake/modules @@ -0,0 +1 @@ +Subproject commit 94b9877d65e46c9d8169ebc46f163d02e4d9dcf3 diff --git a/include/asyncpp.h b/include/asyncpp.h new file mode 100644 index 0000000..f266853 --- /dev/null +++ b/include/asyncpp.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +#include +#include diff --git a/include/asyncpp/future.h b/include/asyncpp/future.h new file mode 100644 index 0000000..f9b646a --- /dev/null +++ b/include/asyncpp/future.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "result.h" +#include "future.pre.h" +#include "future/map.h" +#include "future/and_then.h" + +namespace asyncpp +{ + + template< + typename T_object, + typename T_impl> + struct future + { + using object_type = T_object; + using clean_object_type = std::decay_t; + using impl_type = impl::future; + using value_type = typename impl_type::value_type; + using result_type = result; + + object_type ref; + + /** + * @brief Value constructor. + */ + template + inline future(X_object&& p_ref); + + /** + * @brief Function that will be called repeatedly to check if the future is ready. + */ + inline result_type poll(); + + /** + * @brief Transform the result of this future. + */ + template + inline auto map(X_lambda&& p_lambda); + + /** + * @brief Execute the given lambda after the future is finished and + * wait for the future returned by the lambda. + */ + template + inline auto and_then(X_lambda&& p_lambda); + }; + +} diff --git a/include/asyncpp/future.inl b/include/asyncpp/future.inl new file mode 100644 index 0000000..c1e4027 --- /dev/null +++ b/include/asyncpp/future.inl @@ -0,0 +1,93 @@ +#pragma once + +#include "future.h" + +#include "future/map.inl" +#include "future/and_then.inl" + +namespace asyncpp +{ + + namespace impl + { + + template + template + auto future_base + ::map(X_future&& self, X_lambda&& p_lambda) + { + using future_type = X_future; + using lambda_type = X_lambda; + using map_type = future_impl::map_impl; + + return as_future(map_type( + std::forward(self), + std::forward(p_lambda))); + } + + template + template + auto future_base + ::and_then(X_future&& self, X_lambda&& p_lambda) + { + using future_type = X_future; + using lambda_type = X_lambda; + using and_then_type = future_impl::and_then_impl; + + return as_future(and_then_type( + std::forward(self), + std::forward(p_lambda))); + } + + } + + /* future */ + + template< + typename T_value, + typename T_impl> + template< + typename X_object> + future + ::future(X_object&& p_ref) + : ref(std::forward(p_ref)) + { } + + template< + typename T_value, + typename T_impl> + typename future::result_type + future + ::poll() + { return impl_type::poll(*this); } + + template< + typename T_value, + typename T_impl> + template< + typename X_lambda> + auto future + ::map(X_lambda&& p_lambda) + { return impl_type::map(std::move(*this), std::forward(p_lambda)); } + + template< + typename T_value, + typename T_impl> + template< + typename X_lambda> + auto future + ::and_then(X_lambda&& p_lambda) + { return impl_type::and_then(std::move(*this), std::forward(p_lambda)); } + + /* misc */ + + template + constexpr decltype(auto) as_future(X_value&& value) + { + using value_type = X_value; + using future_type = future; + + return future_type(std::forward(value)); + } + +} diff --git a/include/asyncpp/future.pre.h b/include/asyncpp/future.pre.h new file mode 100644 index 0000000..df87319 --- /dev/null +++ b/include/asyncpp/future.pre.h @@ -0,0 +1,38 @@ +#pragma once + +namespace asyncpp +{ + + namespace impl + { + + template + struct future_base + { + template + static inline auto poll(T_future& self) = delete; + + template + static inline auto map(T_future&& self, T_lambda&& p_lambda); + + template + static inline auto and_then(T_future&& self, T_lambda&& p_lambda); + }; + + template + struct future; + + } + + template< + typename T_object, + typename T_impl = impl::future>> + struct future; + + /** + * @brief Construct a future from the given value. + */ + template + constexpr decltype(auto) as_future(X_value&& value); + +} diff --git a/include/asyncpp/future/and_then.h b/include/asyncpp/future/and_then.h new file mode 100644 index 0000000..c1180c3 --- /dev/null +++ b/include/asyncpp/future/and_then.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +namespace asyncpp { +namespace future_impl { + + template< + typename T_future, + typename T_lambda> + struct and_then_impl + { + public: + using lambda_type = T_lambda; + using outer_future_type = T_future; + using outer_value_type = typename outer_future_type::value_type; + using inner_future_type = decltype(as_future(std::declval()(std::declval()))); + using inner_future_type_ptr = std::unique_ptr; + + public: + lambda_type lambda; + outer_future_type outer; + inner_future_type_ptr inner; + + public: + /** + * @brief Constructor. + */ + template< + typename X_future, + typename X_lambda> + inline and_then_impl( + X_future&& p_outer, + X_lambda&& p_lambda); + }; + +} } diff --git a/include/asyncpp/future/and_then.inl b/include/asyncpp/future/and_then.inl new file mode 100644 index 0000000..3203c53 --- /dev/null +++ b/include/asyncpp/future/and_then.inl @@ -0,0 +1,64 @@ +#pragma once + +#include "map.h" + +namespace asyncpp +{ + + namespace impl + { + + template< + typename T_future, + typename T_lambda> + struct future, void> + : public future_base, void>> + { + using and_then_type = future_impl::and_then_impl; + using inner_future_type = typename and_then_type::inner_future_type; + using value_type = typename inner_future_type::value_type; + using result_type = typename inner_future_type::result_type; + + template + static inline auto poll(X_future& self) + { + while (true) + { + if (self.ref.inner) + { + return self.ref.inner->poll(); + } + else + { + auto r = self.ref.outer.poll(); + if (!r) + return result_type::not_ready(); + + self.ref.inner = std::make_unique(as_future(self.ref.lambda(*r))); + } + } + } + }; + + } + +} + +namespace asyncpp { +namespace future_impl { + + template< + typename T_future, + typename T_lambda> + template< + typename X_future, + typename X_lambda> + and_then_impl + ::and_then_impl( + X_future&& p_outer, + X_lambda&& p_lambda) + : outer (std::forward(p_outer)) + , lambda(std::forward(p_lambda)) + { } + +} } diff --git a/include/asyncpp/future/map.h b/include/asyncpp/future/map.h new file mode 100644 index 0000000..e220a87 --- /dev/null +++ b/include/asyncpp/future/map.h @@ -0,0 +1,31 @@ +#pragma once + +namespace asyncpp { +namespace future_impl { + + template< + typename T_future, + typename T_lambda> + struct map_impl + { + public: + using future_type = T_future; + using lambda_type = T_lambda; + + public: + future_type future; + lambda_type lambda; + + public: + /** + * @brief Constructor. + */ + template< + typename X_future, + typename X_lambda> + inline map_impl( + X_future&& p_future, + X_lambda&& p_lambda); + }; + +} } diff --git a/include/asyncpp/future/map.inl b/include/asyncpp/future/map.inl new file mode 100644 index 0000000..469687a --- /dev/null +++ b/include/asyncpp/future/map.inl @@ -0,0 +1,55 @@ +#pragma once + +#include "map.h" + +namespace asyncpp +{ + + namespace impl + { + + template< + typename T_future, + typename T_lambda> + struct future, void> + : public future_base, void>> + { + using future_type = T_future; + using inner_value_type = typename future_type::value_type; + using lambda_type = T_lambda; + using value_type = decltype(std::declval()(std::declval())); + + template + static inline auto poll(X_future& self) + { + using result_type = result; + + auto r = self.ref.future.poll(); + return r + ? result_type::ready(self.ref.lambda(*r)) + : result_type::not_ready(); + } + }; + + } + +} + +namespace asyncpp { +namespace future_impl { + + template< + typename T_future, + typename T_lambda> + template< + typename X_future, + typename X_lambda> + map_impl + ::map_impl( + X_future&& p_future, + X_lambda&& p_lambda) + : future(std::forward(p_future)) + , lambda(std::forward(p_lambda)) + { } + +} } diff --git a/include/asyncpp/result.h b/include/asyncpp/result.h new file mode 100644 index 0000000..41427d7 --- /dev/null +++ b/include/asyncpp/result.h @@ -0,0 +1,118 @@ +#pragma once + +#include + +namespace asyncpp +{ + + namespace impl + { + + struct result_not_ready + { }; + + struct result_done + { }; + + template + struct result_ready + { + using value_type = T_value; + + value_type value; + + template + inline result_ready(T_args&&... p_args); + }; + + } + + enum class result_status + { + unknown = 0, + not_ready, + ready, + done, + }; + + template + struct result + { + public: + using value_type = T_value; + using not_ready_type = impl::result_not_ready; + using ready_type = impl::result_ready; + using done_type = impl::result_done; + using storage_type = std::variant; + using clean_value_type = std::remove_reference_t; + using pointer_type = clean_value_type*; + using reference_type = clean_value_type&; + using const_pointer_type = clean_value_type const *; + using const_reference_type = clean_value_type const &; + + private: + storage_type _storage; //!< Stores the actual result. + + private: + /** + * @brief Constructor. + */ + inline result(storage_type&& p_storage); + + public: + /** + * @brief returns a result that is not ready. + */ + static inline auto& not_ready(); + + /** + * @brief returns a result that is not ready. + */ + template + static inline auto ready(X_args&&... p_args); + + /** + * @brief returns a result that is not ready. + */ + static inline auto& done(); + + public: + /** + * @brief Get the status of the result. + */ + inline result_status status() const; + + /** + * @brief Check if the result is not ready (is pending). + */ + inline bool is_not_ready() const; + + /** + * @brief Check if the result is ready (has a value). + */ + inline bool is_ready() const; + + /** + * @brief Check if the result is done (stream is finished). + */ + inline bool is_done() const; + + /** + * @brief Get the value of the result. + */ + inline reference_type value(); + + /** + * @brief Get the value of the result. + */ + inline const_reference_type value() const; + + public: + inline operator bool() const; + inline pointer_type operator-> (); + inline reference_type operator* (); + inline const_pointer_type operator-> () const; + inline const_reference_type operator* () const; + }; + +} diff --git a/include/asyncpp/result.inl b/include/asyncpp/result.inl new file mode 100644 index 0000000..116011a --- /dev/null +++ b/include/asyncpp/result.inl @@ -0,0 +1,179 @@ +#pragma once + +#include "result.h" + +namespace asyncpp +{ + + namespace impl + { + + /* result_ready */ + + template + template + result_ready + ::result_ready(T_args&&... p_args) + : value(std::forward(p_args)...) + { } + + } + + /* result */ + + template + result + ::result(storage_type&& p_storage) + : _storage(std::move(p_storage)) + { } + + template + auto& result + ::not_ready() + { + static const result ret(storage_type(not_ready_type { })); + return ret; + } + + template + template + auto result + ::ready(X_args&&... p_args) + { return result(storage_type(ready_type(std::forward(p_args)...))); } + + template + auto& result + ::done() + { + static const result ret(storage_type(done_type { })); + return ret; + } + + template + result_status result + ::status() const + { + if (is_not_ready()) + return result_status::not_ready; + else if (is_ready()) + return result_status::ready; + else if (is_done()) + return result_status::done; + else + return result_status::unknown; + } + + template + bool result + ::is_not_ready() const + { return std::holds_alternative(_storage); } + + template + bool result + ::is_ready() const + { return std::holds_alternative(_storage); } + + template + bool result + ::is_done() const + { return std::holds_alternative(_storage); } + + template + typename result::reference_type + result + ::value() + { return std::get(_storage).value; } + + template + typename result::const_reference_type + result + ::value() const + { return std::get(_storage).value; } + + template + result + ::operator bool() const + { return is_ready(); } + + template + typename result::pointer_type + result + ::operator-> () + { return &value(); } + + template + typename result::reference_type + result + ::operator* () + { return value(); } + + template + typename result::const_pointer_type + result + ::operator-> () const + { return &value(); } + + template + typename result::const_reference_type + result + ::operator* () const + { return value(); } + + template<> + struct result + { + public: + using value_type = void; + + private: + result_status _status; + + private: + inline result(result_status p_status) + : _status(p_status) + { } + + public: + static inline auto& not_ready() + { + static const result ret { result_status::not_ready }; + return ret; + } + + template + static inline auto& ready(X_args&&... p_args) + { + static const result ret { result_status::ready }; + return ret; + } + + static inline auto& done() + { + static const result ret { result_status::done }; + return ret; + } + + public: + inline result_status status() const + { return _status; } + + inline bool is_not_ready() const + { return _status == result_status::not_ready; } + + inline bool is_ready() const + { return _status == result_status::ready; } + + inline bool is_done() const + { return _status == result_status::done; } + + inline void value() + { throw std::runtime_error("'void' result does not store any value!"); } + + inline operator bool() const + { return _status == result_status::ready; } + + inline void operator* () + { value(); } + }; + +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..6c607fc --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,30 @@ +# Initialize ###################################################################################### + +Include ( cotire OPTIONAL RESULT_VARIABLE HAS_COTIRE ) +Include ( pedantic OPTIONAL RESULT_VARIABLE HAS_PEDANTIC ) +Include ( strip_symbols OPTIONAL RESULT_VARIABLE HAS_STRIP_SYMBOLS ) + +# Interface Library ############################################################################### + +Set ( ASYNCPP_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../include ) +Add_Library ( asyncpp INTERFACE ) +Target_Include_Directories ( asyncpp + INTERFACE + $ + $ ) + +# Install ######################################################################################### + +Set ( ASYNCPP_HAS_EXPORT False PARENT_SCOPE ) + +# Header +If ( ASYNCPP_INSTALL_HEADER ) + Set ( ASYNCPP_HAS_EXPORT True PARENT_SCOPE ) + Install ( FILES ${ASYNCPP_INCLUDE_DIR}/asyncpp.h + DESTINATION ${ASYNCPP_INSTALL_DIR_INCLUDE} ) + Install ( DIRECTORY ${ASYNCPP_INCLUDE_DIR}/asyncpp + DESTINATION ${ASYNCPP_INSTALL_DIR_INCLUDE} ) + Install ( TARGETS asyncpp + EXPORT asyncpp + DESTINATION ${ASYNCPP_INSTALL_DIR_INCLUDE} ) +EndIf ( ) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..c5e1c64 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,59 @@ +# Initialize ###################################################################################### + +Include ( cotire OPTIONAL RESULT_VARIABLE HAS_COTIRE ) +Include ( pedantic OPTIONAL RESULT_VARIABLE HAS_PEDANTIC ) +Include ( cmake_tests OPTIONAL RESULT_VARIABLE HAS_CMAKE_TESTS ) + +Find_Package ( Sanitizers QUIET ) + +# Test ############################################################################################ + +Find_Package ( GTest ) +If ( NOT "${GTest_FOUND}" ) + Return ( ) +EndIf ( ) + +File ( GLOB_RECURSE ASYNCPP_TEST_HEADER_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/*.h ) +File ( GLOB_RECURSE ASYNCPP_TEST_INLINE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/*.inl ) +File ( GLOB_RECURSE ASYNCPP_TEST_SOURCE_FILES + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ) + +ForEach ( FILE IN LISTS ASYNCPP_TEST_SOURCE_FILES ) + # add test + Get_Filename_Component ( TEST_DIR ${FILE} DIRECTORY ) + Get_Filename_Component ( TEST_NAME ${FILE} NAME_WE ) + Set ( TEST_NAME "${TEST_DIR}/${TEST_NAME}" ) + String ( REPLACE "\\" "-" TEST_NAME "${TEST_NAME}" ) + String ( REPLACE "/" "-" TEST_NAME "${TEST_NAME}" ) + String ( REPLACE "_" "-" TEST_NAME "${TEST_NAME}" ) + Set ( TEST_NAME "test-${TEST_NAME}" ) + Add_Executable ( ${TEST_NAME} + EXCLUDE_FROM_ALL + ${ASYNCPP_TEST_HEADER_FILES} + ${ASYNCPP_TEST_INLINE_FILES} + ${FILE} ) + Target_Link_Libraries ( ${TEST_NAME} + PUBLIC + asyncpp + GTest::Main ) + + # Sanitizers + If ( Sanitizers_FOUND ) + Add_Sanitizers ( ${TEST_NAME} ) + EndIf ( ) + + # pedantic + If ( HAS_PEDANTIC ) + Pedantic_Apply_Flags_Target ( ${TEST_NAME} ALL ) + EndIf ( ) + + # test + If ( HAS_CMAKE_TESTS ) + Add_CMake_Test ( NAME ${TEST_NAME} TARGET ${TEST_NAME} GROUP asyncpp ) + Else ( ) + Add_Test ( NAME ${TEST_NAME} COMMAND ${TEST_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) + EndIf ( ) +EndForEach ( ) diff --git a/test/asyncpp/future_tests.cpp b/test/asyncpp/future_tests.cpp new file mode 100644 index 0000000..2033aae --- /dev/null +++ b/test/asyncpp/future_tests.cpp @@ -0,0 +1,111 @@ +#include + +#include + +using namespace ::testing; +using namespace ::asyncpp; + +struct delay +{ + int const delay { 5 }; + int count { 0 }; +}; + +namespace asyncpp +{ + + namespace impl + { + + template<> + struct future + : public future_base> + { + using value_type = int&; + + template + static inline auto poll(T_future& self) + { + using result_type = result; + + if (self.ref.count >= self.ref.delay) + return result_type::ready(self.ref.count); + + ++self.ref.count; + return result_type::not_ready(); + } + }; + + } + +} + +TEST(future_tests, poll) +{ + delay d { 5, 0 }; + auto f = as_future(d); + + auto r0 = f.poll(); + ASSERT_EQ(result_status::not_ready, r0.status()); + + auto r1 = f.poll(); + ASSERT_EQ(result_status::not_ready, r1.status()); + + auto r2 = f.poll(); + ASSERT_EQ(result_status::not_ready, r2.status()); + + auto r3 = f.poll(); + ASSERT_EQ(result_status::not_ready, r3.status()); + + auto r4 = f.poll(); + ASSERT_EQ(result_status::not_ready, r4.status()); + + auto r = f.poll(); + ASSERT_TRUE(r); + ASSERT_EQ (result_status::ready, r.status()); + ASSERT_EQ (5, *r); + ASSERT_EQ (5, d.count); + ASSERT_EQ (&*r, &d.count); +} + +TEST(future_tests, map) +{ + delay d { 1, 1 }; + auto f = as_future(d) + .map([](int& i){ + return 10 + i; + }); + + auto r = f.poll(); + ASSERT_TRUE(r); + ASSERT_EQ (11, *r); +} + +TEST(future_tests, and_then) +{ + delay d { 2, 0 }; + auto f = as_future(d) + .and_then([](int& i){ + return delay { 5, i }; + }); + + auto r0 = f.poll(); + ASSERT_FALSE(r0); + + auto r1 = f.poll(); + ASSERT_FALSE(r1); + + auto r2 = f.poll(); + ASSERT_FALSE(r2); + + auto r3 = f.poll(); + ASSERT_FALSE(r3); + + auto r4 = f.poll(); + ASSERT_FALSE(r4); + + auto r = f.poll(); + ASSERT_TRUE(r); + ASSERT_EQ (5, *r); + ASSERT_EQ (2, d.count); +} diff --git a/test/asyncpp/result_tests.cpp b/test/asyncpp/result_tests.cpp new file mode 100644 index 0000000..4d844ce --- /dev/null +++ b/test/asyncpp/result_tests.cpp @@ -0,0 +1,49 @@ +#include + +#include + +using namespace ::testing; +using namespace ::asyncpp; + +TEST(result_tests, not_ready) +{ + using result_type = result; + + auto r = result_type::not_ready(); + + EXPECT_EQ (result_status::not_ready, r.status()); + EXPECT_TRUE (r.is_not_ready()); + EXPECT_FALSE (r.is_ready()); + EXPECT_FALSE (r.is_done()); + EXPECT_FALSE (r); + EXPECT_ANY_THROW(*r); +} + +TEST(result_tests, ready) +{ + using result_type = result; + + int i; + auto r = result_type::ready(i); + + EXPECT_EQ (result_status::ready, r.status()); + EXPECT_FALSE (r.is_not_ready()); + EXPECT_TRUE (r.is_ready()); + EXPECT_FALSE (r.is_done()); + EXPECT_TRUE (r); + EXPECT_EQ (&*r, &i); +} + +TEST(result_tests, done) +{ + using result_type = result; + + auto r = result_type::done(); + + EXPECT_EQ (result_status::done, r.status()); + EXPECT_FALSE (r.is_not_ready()); + EXPECT_FALSE (r.is_ready()); + EXPECT_TRUE (r.is_done()); + EXPECT_FALSE (r); + EXPECT_ANY_THROW(*r); +}