├── make_toolchain ├── .gitignore ├── toolchain_bash ├── .devcontainer ├── Dockerfile └── devcontainer.json ├── TODO ├── test ├── Makefile ├── test_continuation_chain.cpp ├── test_operations.cpp ├── test_compile_duration.cpp ├── test_comparison.cpp └── test_future.cpp ├── .vscode ├── tasks.json └── settings.json ├── LICENSE ├── include └── minicoros │ ├── testing.h │ ├── operations.h │ ├── continuation_chain.h │ ├── detail │ └── operation_helpers.h │ ├── types.h │ └── future.h ├── README.md └── tools ├── testing.h └── testing.cpp /make_toolchain: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | docker build -t toolchain . -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.o 2 | a.out 3 | **/.DS_Store 4 | include/continuable 5 | include/function2 6 | -------------------------------------------------------------------------------- /toolchain_bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker run -it --privileged -v "`pwd`":/opt/src toolchain bash 4 | 5 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \ 3 | build-essential clang libc++-dev gdb time 4 | 5 | #WORKDIR /opt/src/ 6 | 7 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minicoros toolchain", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | }, 6 | "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"], 7 | 8 | "settings": {}, 9 | 10 | "extensions": [ 11 | "ms-vscode.cpptools" 12 | ], 13 | } -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * tests for both std and eastl 2 | * remove the return_type solution in types.h 3 | * support for auto parameter type deduction for .then handlers 4 | * benchmark vs continuables (both synthetic tests and real-world) 5 | * measure how much partial application would cost (+ unpacking tuples) 6 | * measure how much the void support costs 7 | * static assert that verifies that callbacks return mc::result (and other cases) 8 | * guards against multiple invocation of promise 9 | * future instead of future> for compositions. Note: not high prio; future> should only be used as intermediate steps; not referenced directly 10 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | CXX = clang++ 2 | CXXFLAGS = -std=c++17 -fno-exceptions -I../include/ -I../tools/ -O3 -Werror -Wall -Wextra -Wpedantic 3 | 4 | obj_files = ../tools/testing.o test_continuation_chain.o test_future.o test_operations.o 5 | compile_duration_files = test_compile_duration.o 6 | comparison_files = test_comparison.o 7 | 8 | %.o: %.cc ../include/coro.h 9 | $(CXX) -c $(CXXFLAGS) $< -o $@ 10 | 11 | test: $(obj_files) 12 | $(CXX) $(obj_files) 13 | 14 | test_compile_duration: $(compile_duration_files) 15 | $(CXX) $(compile_duration_files) 16 | 17 | comparison: $(comparison_files) 18 | $(CXX) $(comparison_files) 19 | 20 | clean: 21 | rm *.o 22 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build & Run Test", 6 | "type": "shell", 7 | "command": "cd test && make clean ; time make test && ./a.out", 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | }, 12 | "problemMatcher": { 13 | "base": "$gcc", 14 | "fileLocation": ["relative", "${workspaceRoot}/test"] 15 | } 16 | }, 17 | { 18 | "label": "Build & Run Build Benchmark", 19 | "type": "shell", 20 | "command": "cd test && make clean ; time make test_compile_duration && ./a.out", 21 | "group": { 22 | "kind": "build", 23 | "isDefault": true 24 | }, 25 | "problemMatcher": { 26 | "base": "$gcc", 27 | "fileLocation": ["relative", "${workspaceRoot}/test"] 28 | } 29 | }, 30 | { 31 | "label": "Build & Run Build Comparison", 32 | "type": "shell", 33 | "command": "cd test && make clean ; time make comparison && ./a.out", 34 | "group": { 35 | "kind": "build", 36 | "isDefault": true 37 | }, 38 | "problemMatcher": { 39 | "base": "$gcc", 40 | "fileLocation": ["relative", "${workspaceRoot}/test"] 41 | } 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. Neither the name of Electronic Arts, Inc. ("EA") nor the names of 13 | its contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY ELECTRONIC ARTS AND ITS CONTRIBUTORS "AS IS" AND ANY 17 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS OR ITS CONTRIBUTORS BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /include/minicoros/testing.h: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | 3 | #ifndef MINICOROS_TESTING_H_ 4 | #define MINICOROS_TESTING_H_ 5 | 6 | #ifdef MINICOROS_USE_EASTL 7 | #include 8 | 9 | #ifndef MINICOROS_STD 10 | #define MINICOROS_STD eastl 11 | #endif 12 | #else 13 | #include 14 | 15 | #ifndef MINICOROS_STD 16 | #define MINICOROS_STD std 17 | #endif 18 | #endif 19 | 20 | namespace mc { 21 | 22 | template 23 | void assert_successful_result_eq(mc::future&& coro, T&& value) { 24 | auto called = MINICOROS_STD::make_shared(); 25 | MINICOROS_STD::move(coro).chain().evaluate_into([expected_value = MINICOROS_STD::move(value), called](mc::concrete_result&& value) { 26 | *called = true; 27 | ASSERT_TRUE(value.success()); 28 | ASSERT_EQ_NOPRINT(*value.get_value(), expected_value); 29 | }); 30 | 31 | ASSERT_TRUE(*called); 32 | } 33 | 34 | inline void assert_successful_result(mc::future&& coro) { 35 | auto called = MINICOROS_STD::make_shared(); 36 | MINICOROS_STD::move(coro).chain().evaluate_into([called](mc::concrete_result&& value) { 37 | *called = true; 38 | ASSERT_TRUE(value.success()); 39 | }); 40 | 41 | ASSERT_TRUE(*called); 42 | } 43 | 44 | template 45 | void assert_fail_eq(mc::future&& coro, MINICOROS_ERROR_TYPE&& expected_error) { 46 | auto called = MINICOROS_STD::make_shared(); 47 | MINICOROS_STD::move(coro).chain().evaluate_into([expected_error = MINICOROS_STD::move(expected_error), called](mc::concrete_result&& value) { 48 | *called = true; 49 | ASSERT_FALSE(value.success()); 50 | ASSERT_EQ_NOPRINT(value.get_failure()->error, expected_error); 51 | }); 52 | 53 | ASSERT_TRUE(*called); 54 | } 55 | 56 | } // mc 57 | 58 | #endif // MINICOROS_TESTING_H_ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.insertSpaces": true, 4 | "editor.detectIndentation": true, 5 | 6 | "files.exclude": { 7 | "**/*.o": true, 8 | "**/a.out": true 9 | }, 10 | "files.associations": { 11 | "sstream": "cpp", 12 | "functional": "cpp", 13 | "array": "cpp", 14 | "atomic": "cpp", 15 | "bit": "cpp", 16 | "*.tcc": "cpp", 17 | "cctype": "cpp", 18 | "clocale": "cpp", 19 | "cmath": "cpp", 20 | "cstdarg": "cpp", 21 | "cstddef": "cpp", 22 | "cstdint": "cpp", 23 | "cstdio": "cpp", 24 | "cstdlib": "cpp", 25 | "cwchar": "cpp", 26 | "cwctype": "cpp", 27 | "deque": "cpp", 28 | "map": "cpp", 29 | "unordered_map": "cpp", 30 | "unordered_set": "cpp", 31 | "vector": "cpp", 32 | "exception": "cpp", 33 | "algorithm": "cpp", 34 | "iterator": "cpp", 35 | "memory": "cpp", 36 | "memory_resource": "cpp", 37 | "numeric": "cpp", 38 | "optional": "cpp", 39 | "random": "cpp", 40 | "string": "cpp", 41 | "string_view": "cpp", 42 | "system_error": "cpp", 43 | "tuple": "cpp", 44 | "type_traits": "cpp", 45 | "utility": "cpp", 46 | "fstream": "cpp", 47 | "initializer_list": "cpp", 48 | "iosfwd": "cpp", 49 | "iostream": "cpp", 50 | "istream": "cpp", 51 | "limits": "cpp", 52 | "new": "cpp", 53 | "ostream": "cpp", 54 | "stdexcept": "cpp", 55 | "streambuf": "cpp", 56 | "typeinfo": "cpp", 57 | "strstream": "cpp", 58 | "bitset": "cpp", 59 | "charconv": "cpp", 60 | "chrono": "cpp", 61 | "cinttypes": "cpp", 62 | "codecvt": "cpp", 63 | "condition_variable": "cpp", 64 | "csignal": "cpp", 65 | "cstring": "cpp", 66 | "ctime": "cpp", 67 | "forward_list": "cpp", 68 | "list": "cpp", 69 | "set": "cpp", 70 | "ratio": "cpp", 71 | "regex": "cpp", 72 | "future": "cpp", 73 | "iomanip": "cpp", 74 | "mutex": "cpp", 75 | "scoped_allocator": "cpp", 76 | "shared_mutex": "cpp", 77 | "thread": "cpp", 78 | "typeindex": "cpp", 79 | "variant": "cpp" 80 | } 81 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `Minicoros` is a C++17 header-only library that implements future chains (similar to coroutines). Heavily inspired by Denis Blank's (Naios) Continuable library but with the following differences: 2 | * __Faster compilation time__ through simpler code: 3 | * Minicoros executes two calls to `operator new` for each `.then` handler, as opposed to the Continuable library that opts for zero-cost abstractions. Allocations can possibly be mitigated by using pooled allocators and `std::function` implementations that have larger SBO, 4 | but no such support exists in Minicoros 5 | * Less flexibility in values accepted to/from callbacks 6 | * __More opinionated__, which should make it easier to use 7 | * No threading support, no exceptions, uses `std::function` 8 | 9 | Why use Minicoros over Continuables? Minicoros is much friendlier to the compiler; preliminary measurements point to code using Minicoros compiling in 1/2 to 1/4 of the time Continuable uses and that Minicoros scales _much_ better for longer chains. Compiler memory usage follows a similar pattern. (TODO: measure) 10 | 11 | Unfortunately there's no support for C++20 coroutines yet, but that will be added. 12 | 13 | ## Examples 14 | ```cpp 15 | mc::future sum1(int o1, int o2) { 16 | // Return a future that synchronously/immediately returns a successful value 17 | return mc::make_successful_future(o1 + o2); 18 | } 19 | 20 | mc::future sum2(int o1, int o2) { 21 | // A future is backed by a promise -- the promise resolves the future 22 | return mc::future([o1, o2] (mc::promise promise) { 23 | // This lambda will get invoked lazily at a later point 24 | promise(o1 + o2); 25 | }); 26 | } 27 | 28 | void main() { 29 | sum1(4, 10) 30 | .then([](int sum) -> mc::result { // Specify the return type 31 | if (sum != 14) { 32 | // Preferred way of returning an error. With `failure`, you don't have to re-type the return type 33 | return mc::failure(EBADSUM); 34 | } 35 | 36 | // ... or by returning a future that fails: 37 | if (sum != 14) 38 | return mc::make_failed_future(EBADSUM); 39 | 40 | return sum * 2; 41 | }) 42 | .fail([](int error) { 43 | return failure(error); // Rethrow 44 | }) 45 | .fail([](int error) -> mc::result { // Explicitly recover from the error 46 | return {}; 47 | }) 48 | .then([] () -> mc::result { 49 | return sum1(1, 3) && sum2(1, 3); 50 | }) 51 | .then([] (int s1, int s2) { 52 | // Actually, it's alright not specifying mc::result on .thens that return void 53 | ASSERT_EQ(s1, s2); 54 | }) 55 | .ignore_result(); // To protect against forgetting to handle errors, a chain either needs to be returned 56 | // from the current function, or finalized with `done` 57 | } 58 | ``` 59 | 60 | ## Contributing 61 | Before you can contribute, EA must have a Contributor License Agreement (CLA) on file that has been signed by each contributor. 62 | You can sign here: [Go to CLA](https://electronicarts.na1.echosign.com/public/esignWidget?wid=CBFCIBAA3AAABLblqZhByHRvZqmltGtliuExmuV-WNzlaJGPhbSRg2ufuPsM3P0QmILZjLpkGslg24-UJtek*) 63 | 64 | -------------------------------------------------------------------------------- /tools/testing.h: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | /// Minimal testing framework similar to gtest 3 | 4 | #ifndef MINICOROS_TOOLS_TESTING_H_ 5 | #define MINICOROS_TOOLS_TESTING_H_ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace testing { 15 | 16 | #define MINICOROS_STR(s) #s 17 | #define MINICOROS_XSTR(s) MINICOROS_STR(s) 18 | 19 | #define ASSERT_EQ(val1, val2) if (!((val1) == (val2))) {std::cerr << __FILE__ ":" MINICOROS_XSTR(__LINE__) " FAIL: `" << MINICOROS_XSTR(val1) << "` was " << val1 << " but expected " << val2 << std::endl; std::abort(); } 20 | #define ASSERT_EQ_NOPRINT(val1, val2) if (!((val1) == (val2))) {std::cerr << __FILE__ ":" MINICOROS_XSTR(__LINE__) " comparison FAIL" << std::endl; std::abort(); } 21 | #define ASSERT_TRUE(val1) if (!((val1))) {std::cerr << __FILE__ ":" MINICOROS_XSTR(__LINE__) " FAIL: `" << MINICOROS_XSTR(val1) << "` was " << val1 << " but expected to be truthy" << std::endl; std::abort(); } 22 | #define ASSERT_FALSE(val1) if ((val1)) {std::cerr << __FILE__ ":" MINICOROS_XSTR(__LINE__) " FAIL: `" << MINICOROS_XSTR(val1) << "` was " << val1 << " but expected to be falsy" << std::endl; std::abort(); } 23 | #define TEST_FAIL(text) {std::cerr << __FILE__ ":" MINICOROS_XSTR(__LINE__) " FAIL: " << (text) << std::endl; std::abort(); } 24 | 25 | #define TEST_FUNCTION_NAME(suite_name, test_name) test_##suite_name##_##test_name 26 | 27 | #define TEST(suite_name, test_name) \ 28 | void TEST_FUNCTION_NAME(suite_name, test_name)(void); \ 29 | static test_registration suite_name##_##test_name##_registration(MINICOROS_STR(suite_name), MINICOROS_STR(test_name), TEST_FUNCTION_NAME(suite_name, test_name)); \ 30 | void TEST_FUNCTION_NAME(suite_name, test_name)(void) 31 | 32 | class test_suite { 33 | public: 34 | test_suite(const char* name) : name_(name) {} 35 | 36 | void add_test(const char* name, void(*fun)()); 37 | void run_tests(); 38 | 39 | private: 40 | const char* name_; 41 | std::vector> tests_; 42 | }; 43 | 44 | class test_system { 45 | public: 46 | test_suite& get_suite(const char* name); 47 | void run_suites(); 48 | 49 | static test_system& instance(); 50 | 51 | private: 52 | std::map suites_; 53 | }; 54 | 55 | class test_registration { 56 | public: 57 | test_registration(const char* suite_name, const char* test_name, void(*fun)()) 58 | { 59 | test_system::instance().get_suite(suite_name).add_test(test_name, fun); 60 | } 61 | }; 62 | 63 | class alloc_counter; 64 | 65 | class alloc_system { 66 | public: 67 | void add_counter(alloc_counter* counter); 68 | void remove_counter(alloc_counter* counter); 69 | 70 | void add_allocation(void* ptr, size_t size); 71 | void remove_allocation(void* ptr); 72 | 73 | static alloc_system& instance(); 74 | 75 | private: 76 | std::unordered_set active_counters_; 77 | }; 78 | 79 | class alloc_counter { 80 | public: 81 | alloc_counter(); 82 | ~alloc_counter(); 83 | 84 | void add_allocation(void* ptr, size_t size); 85 | void remove_allocation(void* ptr); 86 | 87 | std::vector active_allocations() const; 88 | int total_allocation_count() const; 89 | 90 | private: 91 | int total_allocation_count_ = 0; 92 | std::unordered_map active_allocations_; 93 | }; 94 | 95 | } // testing 96 | 97 | #endif // !MINICOROS_TOOLS_TESTING_H_ 98 | -------------------------------------------------------------------------------- /tools/testing.cpp: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | 3 | #include "testing.h" 4 | 5 | static bool alloc_reporting_enabled = true; 6 | 7 | int main() { 8 | testing::test_system::instance().run_suites(); 9 | } 10 | 11 | void* operator new(size_t size) { 12 | void* ptr = malloc(size); 13 | 14 | if (alloc_reporting_enabled) 15 | testing::alloc_system::instance().add_allocation(ptr, size); 16 | 17 | return ptr; 18 | } 19 | 20 | void operator delete(void* ptr) { 21 | if (alloc_reporting_enabled) 22 | testing::alloc_system::instance().remove_allocation(ptr); 23 | 24 | free(ptr); 25 | } 26 | 27 | namespace testing { 28 | 29 | test_system& test_system::instance() { 30 | static test_system system; 31 | return system; 32 | } 33 | 34 | void test_system::run_suites() { 35 | for (auto& [_, suite] : suites_) 36 | suite.run_tests(); 37 | } 38 | 39 | test_suite& test_system::get_suite(const char* name) { 40 | return suites_.emplace(std::string{name}, test_suite{name}).first->second; 41 | } 42 | 43 | void test_suite::add_test(const char* name, void(*fun)()) { 44 | tests_.emplace_back(name, fun); 45 | } 46 | 47 | void test_suite::run_tests() { 48 | std::cout << "[-------------] " << tests_.size() << " tests from " << name_ << std::endl; 49 | 50 | for (auto& [name, fun] : tests_) { 51 | std::cout << "[ RUN ] " << name_ << "." << name << std::endl; 52 | fun(); 53 | std::cout << "[ OK ] " << name_ << "." << name << std::endl; 54 | } 55 | 56 | std::cout << "[-------------] " << tests_.size() << " tests from " << name_ << std::endl; 57 | } 58 | 59 | alloc_system& alloc_system::instance() { 60 | static alloc_system system; 61 | return system; 62 | } 63 | 64 | void alloc_system::add_counter(alloc_counter* counter) { 65 | alloc_reporting_enabled = false; 66 | active_counters_.insert(counter); 67 | alloc_reporting_enabled = true; 68 | } 69 | 70 | void alloc_system::remove_counter(alloc_counter* counter) { 71 | alloc_reporting_enabled = false; 72 | active_counters_.erase(counter); 73 | alloc_reporting_enabled = true; 74 | } 75 | 76 | alloc_counter::alloc_counter() { 77 | alloc_system::instance().add_counter(this); 78 | } 79 | 80 | alloc_counter::~alloc_counter() { 81 | alloc_system::instance().remove_counter(this); 82 | } 83 | 84 | void alloc_system::add_allocation(void* ptr, size_t size) { 85 | alloc_reporting_enabled = false; 86 | for (alloc_counter* counter : active_counters_) { 87 | counter->add_allocation(ptr, size); 88 | } 89 | alloc_reporting_enabled = true; 90 | } 91 | 92 | void alloc_system::remove_allocation(void* ptr) { 93 | alloc_reporting_enabled = false; 94 | for (alloc_counter* counter : active_counters_) { 95 | counter->remove_allocation(ptr); 96 | } 97 | alloc_reporting_enabled = true; 98 | } 99 | 100 | void alloc_counter::add_allocation(void* ptr, size_t size) { 101 | active_allocations_.emplace(ptr, size); 102 | ++total_allocation_count_; 103 | } 104 | 105 | void alloc_counter::remove_allocation(void* ptr) { 106 | active_allocations_.erase(ptr); 107 | } 108 | 109 | std::vector alloc_counter::active_allocations() const { 110 | std::vector result; 111 | result.reserve(active_allocations_.size()); 112 | 113 | for (auto& [_, size] : active_allocations_) 114 | result.push_back(size); 115 | return result; 116 | } 117 | 118 | int alloc_counter::total_allocation_count() const { 119 | return total_allocation_count_; 120 | } 121 | 122 | } // testing 123 | -------------------------------------------------------------------------------- /include/minicoros/operations.h: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | 3 | #ifndef MINICOROS_OPERATIONS_H_ 4 | #define MINICOROS_OPERATIONS_H_ 5 | 6 | #ifdef MINICOROS_CUSTOM_INCLUDE 7 | #include MINICOROS_CUSTOM_INCLUDE 8 | #endif 9 | 10 | #include 11 | #include 12 | 13 | #ifdef MINICOROS_USE_EASTL 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifndef MINICOROS_STD 20 | #define MINICOROS_STD eastl 21 | #endif 22 | #else 23 | #include 24 | #include 25 | #include 26 | 27 | #ifndef MINICOROS_STD 28 | #define MINICOROS_STD std 29 | #endif 30 | #endif 31 | 32 | namespace mc { 33 | 34 | namespace detail { 35 | 36 | /// Unwrap the chains from their future overcoats. Futures aren't copy-constructible, but the chains are. Remove 37 | /// this when we have move-only std::function. 38 | template 39 | MINICOROS_STD::vector>> unwrap_chains(MINICOROS_STD::vector>&& futures) { 40 | MINICOROS_STD::vector>> chains; 41 | chains.reserve(futures.size()); 42 | 43 | for (future& fut : futures) 44 | chains.push_back(MINICOROS_STD::move(fut).chain()); 45 | 46 | return chains; 47 | } 48 | 49 | } // detail 50 | 51 | template 52 | auto when_all(MINICOROS_STD::vector>&& futures) { 53 | using ResultType = typename detail::vector_result::value_type; 54 | auto chains = detail::unwrap_chains(MINICOROS_STD::move(futures)); 55 | 56 | return future([chains = MINICOROS_STD::move(chains)](promise&& p) mutable { 57 | if (chains.empty()) { 58 | p(concrete_result{}); 59 | return; 60 | } 61 | 62 | auto result_builder = MINICOROS_STD::make_shared>(MINICOROS_STD::move(p)); 63 | result_builder->resize(static_cast(chains.size())); 64 | 65 | for (size_t i = 0; i < chains.size(); ++i) { 66 | MINICOROS_STD::move(chains[static_cast(i)]).evaluate_into([i, result_builder] (concrete_result&& result) { 67 | result_builder->assign(static_cast(i), MINICOROS_STD::move(result)); 68 | }); 69 | } 70 | }); 71 | } 72 | 73 | /// Returns the first result from any of the futures. If the first result is a failure, 74 | /// `when_any` will return that failure. 75 | template 76 | auto when_any(MINICOROS_STD::vector>&& futures) { 77 | auto chains = detail::unwrap_chains(MINICOROS_STD::move(futures)); 78 | 79 | return future([chains = MINICOROS_STD::move(chains)](promise&& p) mutable { 80 | if (chains.empty()) { 81 | p(concrete_result{}); 82 | return; 83 | } 84 | 85 | auto result_builder = MINICOROS_STD::make_shared>(MINICOROS_STD::move(p)); 86 | 87 | for (size_t i = 0; i < chains.size(); ++i) { 88 | MINICOROS_STD::move(chains[static_cast(i)]).evaluate_into([result_builder] (concrete_result&& result) { 89 | result_builder->assign(MINICOROS_STD::move(result)); 90 | }); 91 | } 92 | }); 93 | } 94 | 95 | /// Evaluates the given futures in sequential order and returns all the results. 96 | template 97 | auto when_seq(MINICOROS_STD::vector>&& futures) { 98 | using ResultType = typename detail::vector_result::value_type; 99 | auto chains = detail::unwrap_chains(MINICOROS_STD::move(futures)); 100 | 101 | return future([chains = MINICOROS_STD::move(chains)](promise&& p) mutable { 102 | if (chains.empty()) { 103 | p(concrete_result{}); 104 | return; 105 | } 106 | 107 | MINICOROS_STD::make_shared>(MINICOROS_STD::move(p), MINICOROS_STD::move(chains))->evaluate(); 108 | }); 109 | } 110 | 111 | } // mc 112 | 113 | #endif // MINICOROS_OPERATIONS_H_ 114 | -------------------------------------------------------------------------------- /test/test_continuation_chain.cpp: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | 3 | #include "testing.h" 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace testing; 9 | 10 | TEST(continuation_chain, chain_of_1_element_evalutes_directly_into_the_sink) { 11 | auto result = std::make_shared(); 12 | 13 | mc::continuation_chain([](mc::continuation promise) { 14 | promise(12345); 15 | }) 16 | .evaluate_into([result](int value) { 17 | *result = std::move(value); 18 | }); 19 | 20 | ASSERT_EQ(*result, 12345); 21 | } 22 | 23 | TEST(continuation_chain, chain_of_1_element_evalutes_into_sink_when_promise_is_set) { 24 | auto result = std::make_shared(0); 25 | mc::continuation saved_promise; 26 | 27 | mc::continuation_chain([&saved_promise](mc::continuation promise) { 28 | saved_promise = std::move(promise); 29 | }) 30 | .evaluate_into([result](int value) { 31 | *result = std::move(value); 32 | }); 33 | 34 | // Nothing should've happened since the promise wasn't triggered 35 | ASSERT_EQ(*result, 0); 36 | 37 | saved_promise(4433); 38 | 39 | ASSERT_EQ(*result, 4433); 40 | } 41 | 42 | TEST(continuation_chain, not_evaluated_when_destructed) { 43 | auto count = std::make_shared(0); 44 | 45 | { 46 | auto c = mc::continuation_chain([count](mc::continuation promise) { 47 | ++*count; 48 | promise(12345); 49 | }) 50 | .transform([count](int value, mc::continuation promise) { 51 | ASSERT_EQ(value, 12345); 52 | ++*count; 53 | promise("hello"); 54 | }) 55 | .transform([count](std::string value, mc::continuation promise) { 56 | ASSERT_EQ(value, "hello"); 57 | ++*count; 58 | promise("moof"); 59 | }); 60 | 61 | ASSERT_EQ(*count, 0); 62 | } 63 | 64 | ASSERT_EQ(*count, 0); 65 | } 66 | 67 | TEST(continuation_chain, can_be_moved_into_scope) { 68 | auto count = std::make_shared(0); 69 | auto c = mc::continuation_chain([count](mc::continuation promise) { 70 | ++*count; 71 | promise(12345); 72 | }); 73 | 74 | { 75 | std::move(c) 76 | .transform([count](int value, mc::continuation promise) { 77 | ASSERT_EQ(value, 12345); 78 | ++*count; 79 | promise("hello"); 80 | }) 81 | .evaluate_into([](auto){}); 82 | 83 | ASSERT_EQ(*count, 2); 84 | } 85 | } 86 | 87 | TEST(continuation_chain, evaluation_can_be_disrupted) { 88 | auto count = std::make_shared(0); 89 | mc::continuation saved_promise; 90 | 91 | mc::continuation_chain([count](mc::continuation promise) { 92 | ++*count; 93 | promise(12345); 94 | }) 95 | .transform([count, &saved_promise](int value, mc::continuation promise) { 96 | ASSERT_EQ(value, 12345); 97 | ++*count; 98 | saved_promise = std::move(promise); 99 | }) 100 | .transform([count](std::string value, mc::continuation promise) { 101 | ASSERT_EQ(value, "hello"); 102 | ++*count; 103 | promise("moof"); 104 | }) 105 | .evaluate_into([](auto){}); 106 | 107 | // Chain is stuck since the promise we saved hasn't been triggered 108 | ASSERT_EQ(*count, 2); 109 | 110 | saved_promise("hello"); 111 | ASSERT_EQ(*count, 3); 112 | } 113 | 114 | TEST(continuation_chain, evaluated) { 115 | mc::continuation_chain chain1([] (mc::continuation promise) {promise(2); }); 116 | ASSERT_FALSE(chain1.evaluated()); 117 | 118 | mc::continuation_chain chain2 = std::move(chain1).transform([] (int, mc::continuation promise) { 119 | promise(123); 120 | }); 121 | 122 | ASSERT_TRUE(chain1.evaluated()); 123 | ASSERT_FALSE(chain2.evaluated()); 124 | 125 | mc::continuation_chain chain3(std::move(chain2)); 126 | ASSERT_FALSE(chain3.evaluated()); 127 | ASSERT_TRUE(chain2.evaluated()); 128 | 129 | std::move(chain3).evaluate_into([] (auto) {}); 130 | ASSERT_TRUE(chain3.evaluated()); 131 | } 132 | -------------------------------------------------------------------------------- /include/minicoros/continuation_chain.h: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | 3 | #ifndef MINICOROS_CONTINUATION_CHAIN_H_ 4 | #define MINICOROS_CONTINUATION_CHAIN_H_ 5 | 6 | #ifdef MINICOROS_CUSTOM_INCLUDE 7 | #include MINICOROS_CUSTOM_INCLUDE 8 | #endif 9 | 10 | #ifdef MINICOROS_USE_EASTL 11 | #include 12 | #include 13 | #include 14 | 15 | #ifndef MINICOROS_STD 16 | #define MINICOROS_STD eastl 17 | #endif 18 | 19 | #ifndef MINICOROS_FUNCTION_TYPE 20 | #define MINICOROS_FUNCTION_TYPE eastl::function 21 | #endif 22 | #else 23 | #include 24 | #include 25 | #include 26 | 27 | #ifndef MINICOROS_STD 28 | #define MINICOROS_STD std 29 | #endif 30 | 31 | #ifndef MINICOROS_FUNCTION_TYPE 32 | #define MINICOROS_FUNCTION_TYPE std::function 33 | #endif 34 | #endif 35 | 36 | namespace mc { 37 | 38 | template 39 | using continuation = MINICOROS_FUNCTION_TYPE; 40 | 41 | template 42 | using functor = MINICOROS_FUNCTION_TYPE&&)>; 43 | 44 | /// The continuation chain monad, implements a lazy/async (based on promises) evaluation model and 45 | /// is the core component that this library is built around. 46 | /// Works by creating a chain of "activators" (promise of promises) that gets evaluated bottom-up. 47 | /// 48 | /// ```cpp 49 | /// continuation_chain([count](continuation&& c) { 50 | /// c(12345); 51 | /// }) 52 | /// .transform([count](int&& value, continuation&& c) { 53 | /// c("hello"); 54 | /// }) 55 | /// .evaluate_into([count](std::string&& value) { 56 | /// // ... 57 | /// }); 58 | /// ``` 59 | template 60 | class continuation_chain 61 | { 62 | public: 63 | continuation_chain(continuation>&& fun); 64 | continuation_chain(continuation_chain&& other); 65 | 66 | // NOTE: this copy-ctor is needed because std::function requires the lambda to be copy-constructible. 67 | // We don't really copy any functions containing continuation_chains, so when we have support for 68 | // move-only std::function, remove this copy-ctor. 69 | continuation_chain(const continuation_chain&) { 70 | // TODO pbackman: configurable assert 71 | assert("copying continuation chains isn't supported" && 0); 72 | } 73 | 74 | /// Appends a functor to the chain, leading to a new chain tail 75 | template */> 76 | continuation_chain transform(TransformType&& transformation) &&; 77 | 78 | void evaluate_into(continuation&& sink) &&; 79 | 80 | bool evaluated() const { 81 | return !activator_; 82 | } 83 | 84 | void reset() { 85 | activator_ = {}; 86 | } 87 | 88 | private: 89 | continuation> activator_; 90 | }; 91 | 92 | template 93 | continuation_chain::continuation_chain(continuation>&& fun) : activator_(MINICOROS_STD::move(fun)) {} 94 | 95 | template 96 | continuation_chain::continuation_chain(continuation_chain&& other) { activator_.swap(other.activator_); } 97 | 98 | template 99 | template 100 | continuation_chain continuation_chain::transform(TransformType&& transformation) && { 101 | return continuation_chain{ 102 | [ 103 | transformation = MINICOROS_STD::forward(transformation), 104 | parent_activator = MINICOROS_STD::move(activator_) 105 | ] 106 | (continuation&& next_continuation) mutable { 107 | parent_activator( 108 | [ 109 | next_continuation = MINICOROS_STD::move(next_continuation), 110 | transformation = MINICOROS_STD::forward(transformation) 111 | ] 112 | (T&& input) mutable { 113 | // This gets invoked through the continuation; it's the part of the evaluation flow that actually calls the code and binds it with a continuation 114 | // that evaluates the next functor of the chain. 115 | transformation(MINICOROS_STD::move(input), MINICOROS_STD::move(next_continuation)); 116 | } 117 | ); 118 | } 119 | }; 120 | } 121 | 122 | template 123 | void continuation_chain::evaluate_into(continuation&& sink) && { 124 | assert(activator_ && "trying to evaluate using a non-set activator"); 125 | activator_(MINICOROS_STD::move(sink)); 126 | activator_ = {}; 127 | } 128 | 129 | } // mc 130 | 131 | #endif // MINICOROS_CONTINUATION_CHAIN_H_ 132 | -------------------------------------------------------------------------------- /test/test_operations.cpp: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | 3 | #include "testing.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace testing; 11 | using namespace mc; 12 | 13 | TEST(operations_when_all, vector_of_successful_futures_returns_successfully) { 14 | std::vector> v; 15 | v.push_back(make_successful_future(123)); 16 | v.push_back(make_successful_future(444)); 17 | assert_successful_result_eq(when_all(std::move(v)), {123, 444}); 18 | } 19 | 20 | TEST(operations_when_all, resolving_promises_in_reverse_order_remaps_values) { 21 | std::vector> v; 22 | promise p1, p2; 23 | auto called = std::make_shared(); 24 | 25 | v.push_back(future([&](promise p) {p1 = std::move(p); })); 26 | v.push_back(future([&](promise p) {p2 = std::move(p); })); 27 | 28 | when_all(std::move(v)) 29 | .then([called](std::vector result) { 30 | bool eq = result == std::vector{123, 444}; 31 | ASSERT_TRUE(eq); 32 | *called = true; 33 | }) 34 | .ignore_result(); 35 | 36 | ASSERT_FALSE(*called); 37 | 38 | p2(444); 39 | 40 | ASSERT_FALSE(*called); 41 | 42 | p1(123); 43 | 44 | ASSERT_TRUE(*called); 45 | } 46 | 47 | TEST(operations_when_all, empty_vector_returns_immediately) { 48 | std::vector> v; 49 | assert_successful_result_eq(when_all(std::move(v)), {}); 50 | } 51 | 52 | TEST(operations_when_all, failure_is_propagated) { 53 | std::vector> v; 54 | v.push_back(make_successful_future(4)); 55 | v.push_back(make_failed_future(444)); 56 | v.push_back(make_failed_future(456)); 57 | v.push_back(make_successful_future(5)); 58 | assert_fail_eq(when_all(std::move(v)), 444); 59 | } 60 | 61 | TEST(operations_when_all, takes_void) { 62 | std::vector> v; 63 | v.push_back(make_successful_future()); 64 | assert_successful_result(when_all(std::move(v))); 65 | } 66 | 67 | TEST(operations_when_any, resolves_to_first_value) { 68 | promise p1, p2; 69 | bool called = false; 70 | 71 | std::vector> c; 72 | c.push_back(future([&](promise p) {p1 = std::move(p); })); 73 | c.push_back(future([&](promise p) {p2 = std::move(p); })); 74 | 75 | future first = when_any(std::move(c)); 76 | 77 | std::move(first) 78 | .then([&](int result) { 79 | ASSERT_EQ(result, 444); 80 | called = true; 81 | }) 82 | .ignore_result(); 83 | 84 | ASSERT_FALSE(called); 85 | 86 | p1(444); 87 | 88 | ASSERT_TRUE(called); 89 | 90 | p2(123); // Check that it doesn't crash 91 | } 92 | 93 | TEST(operations_when_any, resolves_to_first_result_even_when_it_is_a_failure) { 94 | promise p1, p2; 95 | bool called = false; 96 | 97 | std::vector> c; 98 | c.push_back(future([&](promise p) {p1 = std::move(p); })); 99 | c.push_back(future([&](promise p) {p2 = std::move(p); })); 100 | 101 | future first = when_any(std::move(c)); 102 | 103 | std::move(first) 104 | .fail([&](int error_code) { 105 | ASSERT_EQ(error_code, 445); 106 | called = true; 107 | return failure(std::move(error_code)); 108 | }) 109 | .ignore_result(); 110 | 111 | ASSERT_FALSE(called); 112 | 113 | p1(failure{445}); 114 | 115 | ASSERT_TRUE(called); 116 | 117 | p2(123); // Check that it doesn't crash 118 | } 119 | 120 | TEST(operations_when_any, supports_void) { 121 | std::vector> futures; 122 | futures.push_back(make_successful_future()); 123 | futures.push_back(make_failed_future(123)); 124 | 125 | future fut = when_any(std::move(futures)); 126 | 127 | assert_successful_result(std::move(fut)); 128 | } 129 | 130 | TEST(operations_when_seq, vector_of_successful_futures_returns_successfully) { 131 | std::vector> v; 132 | v.push_back(make_successful_future(123)); 133 | v.push_back(make_successful_future(444)); 134 | assert_successful_result_eq(when_seq(std::move(v)), {123, 444}); 135 | } 136 | 137 | TEST(operations_when_seq, futures_are_evaluated_in_order) { 138 | std::vector> v; 139 | promise p1, p2; 140 | bool called; 141 | 142 | v.push_back(future([&](promise p) {p1 = std::move(p); })); 143 | v.push_back(future([&](promise p) {p2 = std::move(p); })); 144 | 145 | when_seq(std::move(v)) 146 | .then([&](std::vector result) { 147 | bool eq = result == std::vector{444, 123}; 148 | ASSERT_TRUE(eq); 149 | called = true; 150 | }) 151 | .ignore_result(); 152 | 153 | ASSERT_FALSE(called); 154 | ASSERT_TRUE(bool{p1}); 155 | ASSERT_FALSE(bool{p2}); 156 | p1(444); 157 | 158 | ASSERT_FALSE(called); 159 | ASSERT_TRUE(bool{p2}); 160 | p2(123); 161 | 162 | ASSERT_TRUE(called); 163 | } 164 | 165 | TEST(operations_when_seq, empty_vector_returns_immediately) { 166 | std::vector> v; 167 | assert_successful_result_eq(when_seq(std::move(v)), {}); 168 | } 169 | 170 | TEST(operations_when_seq, failure_is_propagated) { 171 | std::vector> v; 172 | v.push_back(make_successful_future(4)); 173 | v.push_back(make_failed_future(444)); 174 | v.push_back(make_failed_future(456)); 175 | v.push_back(make_successful_future(5)); 176 | assert_fail_eq(when_seq(std::move(v)), 444); 177 | } 178 | 179 | TEST(operations_when_seq, takes_void) { 180 | std::vector> v; 181 | v.push_back(make_successful_future()); 182 | assert_successful_result(when_seq(std::move(v))); 183 | } 184 | -------------------------------------------------------------------------------- /test/test_compile_duration.cpp: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | 3 | #include 4 | #include 5 | 6 | using namespace mc; 7 | 8 | future doStuff() 9 | { 10 | return future([](promise p) 11 | { 12 | std::move(p)(123); 13 | }); 14 | } 15 | 16 | int main() 17 | { 18 | future([](promise p) 19 | { 20 | p(123); 21 | }) 22 | .then([](int) -> mc::result 23 | { 24 | return doStuff(); 25 | }) 26 | .then([](int) -> mc::result 27 | { 28 | return doStuff(); 29 | }) 30 | .then([](int) -> mc::result 31 | { 32 | return doStuff(); 33 | }) 34 | .then([](int) -> mc::result 35 | { 36 | return doStuff(); 37 | }) 38 | .then([](int) -> mc::result 39 | { 40 | return doStuff(); 41 | }) 42 | .then([](int) -> mc::result 43 | { 44 | return doStuff(); 45 | }) 46 | .then([](int) -> mc::result 47 | { 48 | return doStuff(); 49 | }) 50 | .then([](int) -> mc::result 51 | { 52 | return doStuff(); 53 | }) 54 | .then([](int) -> mc::result 55 | { 56 | return doStuff(); 57 | }) 58 | .then([](int) -> mc::result 59 | { 60 | return doStuff(); 61 | }) 62 | .then([](int) -> mc::result 63 | { 64 | return doStuff(); 65 | }) 66 | .then([](int) -> mc::result 67 | { 68 | return doStuff(); 69 | }) 70 | .then([](int&&) -> mc::result 71 | { 72 | return doStuff(); 73 | }) 74 | .then([](int&&) -> mc::result 75 | { 76 | return doStuff(); 77 | }) 78 | .then([](int&&) -> mc::result 79 | { 80 | return doStuff(); 81 | }) 82 | .then([](int&&) -> mc::result 83 | { 84 | return doStuff(); 85 | }) 86 | .then([](int&&) -> mc::result 87 | { 88 | return doStuff(); 89 | }) 90 | .then([](int&&) -> mc::result 91 | { 92 | return doStuff(); 93 | }) 94 | .then([](int&&) -> mc::result 95 | { 96 | return doStuff(); 97 | }) 98 | .then([](int&&) -> mc::result 99 | { 100 | return doStuff(); 101 | }) 102 | .then([](int&&) -> mc::result 103 | { 104 | return doStuff(); 105 | }) 106 | .then([](int&&) -> mc::result 107 | { 108 | return doStuff(); 109 | }) 110 | .then([](int&&) -> mc::result 111 | { 112 | return doStuff(); 113 | }) 114 | .then([](int&&) -> mc::result 115 | { 116 | return doStuff(); 117 | }) 118 | .then([](int&&) -> mc::result 119 | { 120 | return doStuff(); 121 | }) 122 | .then([](int&&) -> mc::result 123 | { 124 | return doStuff(); 125 | }) 126 | .then([](int&&) -> mc::result 127 | { 128 | return doStuff(); 129 | }) 130 | .then([](int&&) -> mc::result 131 | { 132 | return doStuff(); 133 | }) 134 | .then([](int&&) -> mc::result 135 | { 136 | return doStuff(); 137 | }) 138 | .then([](int&&) -> mc::result 139 | { 140 | return doStuff(); 141 | }) 142 | .then([](int&&) -> mc::result 143 | { 144 | return doStuff(); 145 | }) 146 | .then([](int&&) -> mc::result 147 | { 148 | return doStuff(); 149 | }) 150 | .then([](int&&) -> mc::result 151 | { 152 | return doStuff(); 153 | }) 154 | .then([](int&&) -> mc::result 155 | { 156 | return doStuff(); 157 | }) 158 | .then([](int&&) -> mc::result 159 | { 160 | return doStuff(); 161 | }) 162 | .then([](int&&) -> mc::result 163 | { 164 | return doStuff(); 165 | }) 166 | .then([](int&&) -> mc::result 167 | { 168 | return doStuff(); 169 | }) 170 | .then([](int&&) -> mc::result 171 | { 172 | return doStuff(); 173 | }) 174 | .then([](int&&) -> mc::result 175 | { 176 | return doStuff(); 177 | }) 178 | .then([](int&&) -> mc::result 179 | { 180 | return 123; 181 | }) 182 | .then([](int&&) -> mc::result 183 | { 184 | return 123; 185 | }) 186 | .then([](int&&) -> mc::result 187 | { 188 | return 123; 189 | }) 190 | .then([](int&&) -> mc::result 191 | { 192 | return 123; 193 | }) 194 | .then([](int&&) -> mc::result 195 | { 196 | return failure(123); 197 | }) 198 | .then([](std::string&&) -> mc::result 199 | { 200 | return "hello"; 201 | }) 202 | .fail([](int) -> mc::result 203 | { 204 | return "moofie"; 205 | }) 206 | .then([](std::string&&) -> mc::result 207 | { 208 | return failure(123); 209 | }).then([](std::string&&) -> mc::result 210 | { 211 | return "hello"; 212 | }) 213 | .fail([](int) -> mc::result 214 | { 215 | return "moofie"; 216 | }) 217 | .then([](std::string&&) -> mc::result 218 | { 219 | return failure(123); 220 | }).then([](std::string&&) -> mc::result 221 | { 222 | return "hello"; 223 | }) 224 | .fail([](int) -> mc::result 225 | { 226 | return "moofie"; 227 | }) 228 | .then([](std::string&&) -> mc::result 229 | { 230 | return failure(123); 231 | }).then([](std::string&&) -> mc::result 232 | { 233 | return "hello"; 234 | }) 235 | .fail([](int) -> mc::result 236 | { 237 | return "moofie"; 238 | }) 239 | .then([](std::string&&) -> mc::result 240 | { 241 | return failure(123); 242 | }).then([](std::string&&) -> mc::result 243 | { 244 | return "hello"; 245 | }) 246 | .fail([](int) -> mc::result 247 | { 248 | return "moofie"; 249 | }) 250 | .then([](std::string&&) -> mc::result 251 | { 252 | return failure(123); 253 | }).then([](std::string&&) -> mc::result 254 | { 255 | return "hello"; 256 | }) 257 | .fail([](int) -> mc::result 258 | { 259 | return "moofie"; 260 | }) 261 | .then([](std::string&&) -> mc::result 262 | { 263 | return failure(123); 264 | }).then([](std::string&&) -> mc::result 265 | { 266 | return "hello"; 267 | }) 268 | .fail([](int) -> mc::result 269 | { 270 | return "moofie"; 271 | }) 272 | .then([](std::string&&) -> mc::result 273 | { 274 | return failure(123); 275 | }) 276 | .ignore_result(); 277 | 278 | } -------------------------------------------------------------------------------- /include/minicoros/detail/operation_helpers.h: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | 3 | #ifndef MINICOROS_DETAIL_OPERATION_HELPERS_H_ 4 | #define MINICOROS_DETAIL_OPERATION_HELPERS_H_ 5 | 6 | #ifdef MINICOROS_CUSTOM_INCLUDE 7 | #include MINICOROS_CUSTOM_INCLUDE 8 | #endif 9 | 10 | #include 11 | #include 12 | 13 | #ifdef MINICOROS_USE_EASTL 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #ifndef MINICOROS_STD 21 | #define MINICOROS_STD eastl 22 | #endif 23 | #else 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #ifndef MINICOROS_STD 31 | #define MINICOROS_STD std 32 | #endif 33 | #endif 34 | 35 | namespace mc::detail { 36 | 37 | template 38 | MINICOROS_STD::tuple> convert_to_tuple(T&& value) { 39 | return MINICOROS_STD::tuple(MINICOROS_STD::move(value)); 40 | } 41 | 42 | template 43 | MINICOROS_STD::tuple...> convert_to_tuple(MINICOROS_STD::tuple&& tup) { 44 | return MINICOROS_STD::move(tup); 45 | } 46 | 47 | template 48 | auto make_flat_tuple(A&& a, B&& b) { 49 | auto tup1 = convert_to_tuple(MINICOROS_STD::move(a)); 50 | auto tup2 = convert_to_tuple(MINICOROS_STD::move(b)); 51 | return MINICOROS_STD::tuple_cat(MINICOROS_STD::move(tup1), MINICOROS_STD::move(tup2)); 52 | } 53 | 54 | template 55 | class vector_result { 56 | public: 57 | using value_type = MINICOROS_STD::vector; 58 | 59 | vector_result(promise&& p) : promise_(MINICOROS_STD::move(p)) {} 60 | 61 | void resize(int new_size) { 62 | values_.resize(new_size); // T has to have a default constructor. TODO: make it work without a default ctor 63 | } 64 | 65 | void assign(int index, concrete_result&& result) { 66 | if (auto fail = result.get_failure()) { 67 | resolve(MINICOROS_STD::move(*fail)); 68 | return; 69 | } 70 | 71 | values_[index] = MINICOROS_STD::move(*result.get_value()); 72 | 73 | if (++num_finished_futures_ == values_.size()) 74 | resolve(MINICOROS_STD::move(values_)); 75 | } 76 | 77 | private: 78 | void resolve(concrete_result&& value) { 79 | if (!promise_) 80 | return; 81 | 82 | auto promise = MINICOROS_STD::move(promise_); 83 | promise_ = {}; 84 | promise(MINICOROS_STD::move(value)); 85 | } 86 | 87 | MINICOROS_STD::vector values_; 88 | size_t num_finished_futures_ = 0; 89 | promise promise_; 90 | }; 91 | 92 | template<> 93 | class vector_result { 94 | public: 95 | using value_type = void; 96 | 97 | vector_result(promise&& p) : promise_(MINICOROS_STD::move(p)) {} 98 | 99 | void resize(size_t new_size) { 100 | num_expected_futures_ = new_size; 101 | } 102 | 103 | void assign(size_t index, concrete_result&& result) { 104 | (void)index; 105 | 106 | if (auto fail = result.get_failure()) { 107 | resolve(MINICOROS_STD::move(*fail)); 108 | return; 109 | } 110 | 111 | if (++num_finished_futures_ == num_expected_futures_) 112 | resolve({}); 113 | } 114 | 115 | static concrete_result empty_value() { 116 | return {}; 117 | } 118 | 119 | private: 120 | void resolve(concrete_result&& value) { 121 | if (!promise_) 122 | return; 123 | 124 | auto promise = MINICOROS_STD::move(promise_); 125 | promise_ = {}; 126 | promise(MINICOROS_STD::move(value)); 127 | } 128 | 129 | size_t num_finished_futures_ = 0; 130 | size_t num_expected_futures_ = 0; 131 | promise promise_; 132 | }; 133 | 134 | template 135 | class tuple_result { 136 | public: 137 | using value_type = decltype(make_flat_tuple(MINICOROS_STD::declval(), MINICOROS_STD::declval())); 138 | 139 | tuple_result(promise&& p) : promise_(MINICOROS_STD::move(p)) {} 140 | 141 | void assign_lhs(concrete_result&& result) { 142 | if (auto fail = result.get_failure()) { 143 | resolve(MINICOROS_STD::move(*fail)); 144 | return; 145 | } 146 | 147 | lhs_.emplace(MINICOROS_STD::move(*result.get_value())); 148 | check_and_resolve(); 149 | } 150 | 151 | void assign_rhs(concrete_result&& result) { 152 | if (auto fail = result.get_failure()) { 153 | resolve(MINICOROS_STD::move(*fail)); 154 | return; 155 | } 156 | 157 | rhs_.emplace(MINICOROS_STD::move(*result.get_value())); 158 | check_and_resolve(); 159 | } 160 | 161 | private: 162 | void check_and_resolve() { 163 | if (lhs_ && rhs_) 164 | resolve(make_flat_tuple(MINICOROS_STD::move(*lhs_), MINICOROS_STD::move(*rhs_))); 165 | } 166 | 167 | void resolve(concrete_result&& value) { 168 | if (!promise_) 169 | return; 170 | 171 | auto promise = MINICOROS_STD::move(promise_); 172 | promise_ = {}; 173 | promise(MINICOROS_STD::move(value)); 174 | } 175 | 176 | MINICOROS_STD::optional lhs_; 177 | MINICOROS_STD::optional rhs_; 178 | promise promise_; 179 | }; 180 | 181 | template 182 | class tuple_result { 183 | public: 184 | using value_type = LHS; 185 | 186 | tuple_result(promise&& p) : promise_(MINICOROS_STD::move(p)) {} 187 | 188 | void assign_lhs(concrete_result&& result) { 189 | if (auto fail = result.get_failure()) { 190 | resolve(MINICOROS_STD::move(*fail)); 191 | return; 192 | } 193 | 194 | lhs_.emplace(MINICOROS_STD::move(*result.get_value())); 195 | check_and_resolve(); 196 | } 197 | 198 | void assign_rhs(concrete_result&& result) { 199 | if (auto fail = result.get_failure()) { 200 | resolve(MINICOROS_STD::move(*fail)); 201 | return; 202 | } 203 | 204 | received_rhs_ = true; 205 | check_and_resolve(); 206 | } 207 | 208 | private: 209 | void check_and_resolve() { 210 | if (lhs_ && received_rhs_) 211 | resolve(MINICOROS_STD::move(*lhs_)); 212 | } 213 | 214 | void resolve(concrete_result&& value) { 215 | if (!promise_) 216 | return; 217 | 218 | auto promise = MINICOROS_STD::move(promise_); 219 | promise_ = {}; 220 | promise(MINICOROS_STD::move(value)); 221 | } 222 | 223 | MINICOROS_STD::optional lhs_; 224 | bool received_rhs_ = false; 225 | promise promise_; 226 | }; 227 | 228 | template 229 | class tuple_result { 230 | public: 231 | using value_type = RHS; 232 | 233 | tuple_result(promise&& p) : promise_(MINICOROS_STD::move(p)) {} 234 | 235 | void assign_lhs(concrete_result&& result) { 236 | if (auto fail = result.get_failure()) { 237 | resolve(MINICOROS_STD::move(*fail)); 238 | return; 239 | } 240 | 241 | received_lhs_ = true; 242 | check_and_resolve(); 243 | } 244 | 245 | void assign_rhs(concrete_result&& result) { 246 | if (auto fail = result.get_failure()) { 247 | resolve(MINICOROS_STD::move(*fail)); 248 | return; 249 | } 250 | 251 | rhs_.emplace(MINICOROS_STD::move(*result.get_value())); 252 | check_and_resolve(); 253 | } 254 | 255 | private: 256 | void check_and_resolve() { 257 | if (received_lhs_ && rhs_) 258 | resolve(MINICOROS_STD::move(*rhs_)); 259 | } 260 | 261 | void resolve(concrete_result&& value) { 262 | if (!promise_) 263 | return; 264 | 265 | auto promise = MINICOROS_STD::move(promise_); 266 | promise_ = {}; 267 | promise(MINICOROS_STD::move(value)); 268 | } 269 | 270 | bool received_lhs_ = false; 271 | MINICOROS_STD::optional rhs_; 272 | promise promise_; 273 | }; 274 | 275 | template<> 276 | class tuple_result { 277 | public: 278 | using value_type = void; 279 | 280 | tuple_result(promise&& p) : promise_(MINICOROS_STD::move(p)) {} 281 | 282 | void assign_lhs(concrete_result&& result) { 283 | if (auto fail = result.get_failure()) { 284 | resolve(MINICOROS_STD::move(*fail)); 285 | return; 286 | } 287 | 288 | received_lhs_ = true; 289 | check_and_resolve(); 290 | } 291 | 292 | void assign_rhs(concrete_result&& result) { 293 | if (auto fail = result.get_failure()) { 294 | resolve(MINICOROS_STD::move(*fail)); 295 | return; 296 | } 297 | 298 | received_rhs_ = true; 299 | check_and_resolve(); 300 | } 301 | 302 | private: 303 | void check_and_resolve() { 304 | if (received_lhs_ && received_rhs_) 305 | resolve({}); 306 | } 307 | 308 | void resolve(concrete_result&& value) { 309 | if (!promise_) 310 | return; 311 | 312 | auto promise = MINICOROS_STD::move(promise_); 313 | promise_ = {}; 314 | promise(MINICOROS_STD::move(value)); 315 | } 316 | 317 | bool received_lhs_ = false; 318 | bool received_rhs_ = false; 319 | promise promise_; 320 | }; 321 | 322 | template 323 | class any_result { 324 | public: 325 | using value_type = T; 326 | 327 | any_result(promise&& promise) : promise_(MINICOROS_STD::move(promise)) {} 328 | 329 | /// First invocation resolves the promise 330 | void assign(concrete_result&& result) { 331 | if (!promise_) 332 | return; 333 | 334 | auto promise = MINICOROS_STD::move(promise_); 335 | promise_ = {}; 336 | promise(MINICOROS_STD::move(result)); 337 | } 338 | 339 | private: 340 | promise promise_; 341 | }; 342 | 343 | template 344 | class seq_submitter : public MINICOROS_STD::enable_shared_from_this> { 345 | using ResultingType = typename vector_result::value_type; 346 | using ChainType = continuation_chain>; 347 | 348 | public: 349 | seq_submitter(promise&& p, MINICOROS_STD::vector&& chains) : storage_(MINICOROS_STD::move(p)), chains_(MINICOROS_STD::move(chains)) {} 350 | 351 | void evaluate() { 352 | storage_.resize(chains_.size()); 353 | evaluate_next_chain(); 354 | } 355 | 356 | private: 357 | using MINICOROS_STD::enable_shared_from_this>::shared_from_this; 358 | 359 | void evaluate_next_chain() { 360 | if (next_chain_idx_ >= chains_.size()) { 361 | // Not much to do -- should only happen for the last chain 362 | return; 363 | } 364 | 365 | const size_t chain_idx = next_chain_idx_++; 366 | auto& chain = chains_[chain_idx]; 367 | 368 | MINICOROS_STD::move(chain).evaluate_into([shared_this = shared_from_this()] (concrete_result&& result) { 369 | shared_this->storage_.assign(shared_this->next_chain_idx_ - 1, MINICOROS_STD::move(result)); 370 | shared_this->evaluate_next_chain(); 371 | }); 372 | } 373 | 374 | vector_result storage_; 375 | MINICOROS_STD::vector chains_; 376 | size_t next_chain_idx_ = 0u; 377 | }; 378 | 379 | } // mc::detail 380 | 381 | #endif // MINICOROS_DETAIL_OPERATION_HELPERS_H_ 382 | -------------------------------------------------------------------------------- /include/minicoros/types.h: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | 3 | #ifndef MINICOROS_TYPES_H_ 4 | #define MINICOROS_TYPES_H_ 5 | 6 | #ifdef MINICOROS_CUSTOM_INCLUDE 7 | #include MINICOROS_CUSTOM_INCLUDE 8 | #endif 9 | 10 | #ifdef MINICOROS_USE_EASTL 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #ifndef MINICOROS_STD 19 | #define MINICOROS_STD eastl 20 | #endif 21 | #else 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #ifndef MINICOROS_STD 30 | #define MINICOROS_STD std 31 | #endif 32 | #endif 33 | 34 | #ifndef MINICOROS_ERROR_TYPE 35 | #define MINICOROS_ERROR_TYPE int 36 | #endif 37 | 38 | namespace mc { 39 | namespace detail { 40 | 41 | class untyped_declval 42 | { 43 | public: 44 | template 45 | operator T() const { 46 | return MINICOROS_STD::declval(); 47 | } 48 | }; 49 | 50 | // TODO: we shouldn't try to pry a return type from the lambda. We should instead invert the flow. 51 | template 52 | auto return_type(LambdaType&& lambda) -> decltype(lambda(untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{})); 53 | 54 | template 55 | auto return_type(LambdaType&& lambda) -> decltype(lambda(untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{})); 56 | 57 | template 58 | auto return_type(LambdaType&& lambda) -> decltype(lambda(untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{})); 59 | 60 | template 61 | auto return_type(LambdaType&& lambda) -> decltype(lambda(untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{})); 62 | 63 | template 64 | auto return_type(LambdaType&& lambda) -> decltype(lambda(untyped_declval{}, untyped_declval{}, untyped_declval{}, untyped_declval{})); 65 | 66 | template 67 | auto return_type(LambdaType&& lambda) -> decltype(lambda(untyped_declval{}, untyped_declval{}, untyped_declval{})); 68 | 69 | template 70 | auto return_type(LambdaType&& lambda) -> decltype(lambda(untyped_declval{}, untyped_declval{})); 71 | 72 | template 73 | auto return_type(LambdaType&& lambda) -> decltype(lambda(untyped_declval{})); 74 | 75 | template 76 | auto return_type(LambdaType&& lambda) -> decltype(lambda()); 77 | 78 | template 79 | struct lambda_helper_impl; 80 | 81 | template 82 | struct lambda_helper_impl { 83 | using return_type = Ret; 84 | static constexpr int num_arguments = sizeof...(Args); 85 | }; 86 | 87 | template 88 | struct lambda_helper_impl { 89 | using return_type = Ret; 90 | static constexpr int num_arguments = sizeof...(Args); 91 | }; 92 | 93 | template 94 | struct lambda_helper : lambda_helper_impl {}; 95 | 96 | template 97 | struct lambda_helper : lambda_helper_impl {}; 98 | 99 | /// Helper for counting the number of arguments a callable (most often a lambda in our case) takes 100 | template 101 | struct num_arguments_helper {}; 102 | 103 | template 104 | struct num_arguments_helper { 105 | static constexpr int num_arguments = sizeof...(ArgTypes); 106 | }; 107 | 108 | template 109 | struct num_arguments_helper { 110 | static constexpr int num_arguments = sizeof...(ArgTypes); 111 | }; 112 | 113 | template 114 | auto truncate_tuple_impl(MINICOROS_STD::index_sequence, MINICOROS_STD::tuple t) { 115 | return MINICOROS_STD::make_tuple(MINICOROS_STD::get(t)...); 116 | } 117 | 118 | /// Returns the first MaxCount elements of the given tuple 119 | template 120 | auto truncate_tuple(MINICOROS_STD::tuple&& t) { 121 | static_assert(MaxCount <= sizeof...(Ts), "cannot take that many elements from the given tuple"); 122 | return truncate_tuple_impl(MINICOROS_STD::make_index_sequence(), t); 123 | } 124 | 125 | /// Similar to std::apply but tries to apply as many arguments as the receiver takes. It also 126 | /// supports a single non-tuple value. 127 | /// Partial application was measured at one point to cost about ~4% more in test_compile_duration. 128 | /// This specialization is for calling an n-ary lambda using an m-ary tuple where m >= n 129 | template 130 | auto partial_call(LambdaType&& lambda, MINICOROS_STD::tuple&& t) { 131 | auto truncated_tuple = truncate_tuple::num_arguments>(MINICOROS_STD::move(t)); 132 | return MINICOROS_STD::apply(MINICOROS_STD::forward(lambda), MINICOROS_STD::move(truncated_tuple)); 133 | } 134 | 135 | /// Specialization for calling a 1-ary lambda using a single value. 136 | template 137 | auto partial_call(LambdaType&& lambda, T&& t) -> decltype(lambda(MINICOROS_STD::move(t))) { 138 | return MINICOROS_STD::invoke(MINICOROS_STD::forward(lambda), MINICOROS_STD::move(t)); 139 | } 140 | 141 | /// Specialization for calling a 0-ary lambda using a single value. 142 | template 143 | auto partial_call(LambdaType&& lambda, T&&) -> decltype(lambda()) { 144 | return lambda(); 145 | } 146 | 147 | template 148 | void partial_call_no_return(LambdaType&& lambda, MINICOROS_STD::tuple&& t) { 149 | auto truncated_tuple = truncate_tuple::num_arguments>(MINICOROS_STD::move(t)); 150 | MINICOROS_STD::apply(MINICOROS_STD::forward(lambda), MINICOROS_STD::move(truncated_tuple)); 151 | } 152 | 153 | /// Specialization for calling a 1-ary lambda using a single value. 154 | template 155 | auto partial_call_no_return(LambdaType&& lambda, T&& t) -> decltype((void)lambda(MINICOROS_STD::move(t))) { 156 | MINICOROS_STD::invoke(MINICOROS_STD::forward(lambda), MINICOROS_STD::move(t)); 157 | } 158 | 159 | /// Specialization for calling a 0-ary lambda using a single value. 160 | template 161 | auto partial_call_no_return(LambdaType&& lambda, T&& t) -> decltype((void)lambda()) { 162 | (void)t; 163 | lambda(); 164 | } 165 | 166 | } 167 | 168 | /// Tag for failures. 169 | struct failure { 170 | explicit failure(MINICOROS_ERROR_TYPE&& error) : error(MINICOROS_STD::move(error)) {} 171 | MINICOROS_ERROR_TYPE error; 172 | }; 173 | 174 | /// Holds the actual resulting value of a callback (or the failure). 175 | template 176 | class concrete_result { 177 | public: 178 | using type = MINICOROS_STD::decay_t; 179 | 180 | concrete_result() : value_(type{}) {} 181 | concrete_result(T&& value) : value_(type{MINICOROS_STD::move(value)}) {} 182 | concrete_result(const concrete_result& other) : value_(other.value_) {} 183 | concrete_result(concrete_result&& other) : value_(MINICOROS_STD::move(other.value_)) {} 184 | concrete_result(failure&& f) : value_(MINICOROS_STD::move(f)) {} 185 | 186 | /// Invokes the callback with this result and resolves the promise using the return value 187 | /// from the callback. 188 | template 189 | auto resolve_promise_with_callback(CallbackType&& callback, PromiseType&& promise) -> MINICOROS_STD::enable_if_t::return_type>::value> { 190 | // General case for handling callbacks that return mc::result 191 | detail::partial_call(MINICOROS_STD::forward(callback), MINICOROS_STD::move(*MINICOROS_STD::get_if(&value_))) 192 | .resolve_promise(MINICOROS_STD::move(promise)); 193 | } 194 | 195 | template 196 | auto resolve_promise_with_callback(CallbackType&& callback, PromiseType&& promise) -> MINICOROS_STD::enable_if_t::return_type>::value> { 197 | // Callbacks are allowed to return void, and for those we need some special handling 198 | detail::partial_call_no_return(MINICOROS_STD::forward(callback), MINICOROS_STD::move(*MINICOROS_STD::get_if(&value_))); 199 | MINICOROS_STD::move(promise)({}); // Only going to be used for future since we infer the type from the lambda 200 | } 201 | 202 | bool success() const { 203 | return !MINICOROS_STD::get_if(&value_); 204 | } 205 | 206 | type* get_value() { 207 | return MINICOROS_STD::get_if(&value_); 208 | } 209 | 210 | failure* get_failure() { 211 | return MINICOROS_STD::get_if(&value_); 212 | } 213 | 214 | private: 215 | MINICOROS_STD::variant value_; 216 | }; 217 | 218 | /// Specialization for `void` since we don't want to pass any argument to the callback. We also allow the callback to return 219 | /// `void` directly without wrapping it using `result`. 220 | template<> 221 | class concrete_result { 222 | public: 223 | using type = void; 224 | 225 | concrete_result() = default; 226 | concrete_result(failure&& f) : failure_(MINICOROS_STD::move(f)) {} 227 | 228 | template 229 | auto resolve_promise_with_callback(CallbackType&& callback, PromiseType&& promise) -> MINICOROS_STD::enable_if_t::value> { 230 | // General case for handling callbacks that return mc::result 231 | callback() 232 | .resolve_promise(MINICOROS_STD::move(promise)); 233 | } 234 | 235 | template 236 | auto resolve_promise_with_callback(CallbackType&& callback, PromiseType&& promise) -> MINICOROS_STD::enable_if_t::value> { 237 | // Callbacks are allowed to return void, and for those we need some special handling 238 | callback(); 239 | MINICOROS_STD::move(promise)({}); 240 | } 241 | 242 | bool success() const { 243 | return !failure_; 244 | } 245 | 246 | MINICOROS_STD::optional&& get_failure() { 247 | return MINICOROS_STD::move(failure_); 248 | } 249 | 250 | private: 251 | MINICOROS_STD::optional failure_; 252 | }; 253 | 254 | template 255 | struct is_concrete_result : public MINICOROS_STD::false_type {}; 256 | 257 | template 258 | struct is_concrete_result> : public MINICOROS_STD::true_type {}; 259 | 260 | template 261 | constexpr bool is_concrete_result_v = is_concrete_result::value; 262 | 263 | template 264 | using promise = continuation>; 265 | 266 | } // mc 267 | 268 | #endif // MINICOROS_TYPES_H_ 269 | -------------------------------------------------------------------------------- /test/test_comparison.cpp: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | 3 | #define USE_MINICOROS 4 | //#define USE_CONTINUABLES 5 | 6 | #define MIXED_TEST_1 7 | 8 | #ifdef USE_CONTINUABLES 9 | #define CONTINUABLE_WITH_NO_EXCEPTIONS 10 | #define CONTINUABLE_WITH_CUSTOM_ERROR_TYPE int 11 | #include 12 | 13 | #define SUCCESS cti::make_ready_continuable(123) 14 | #define SUCCESS_EXPLICIT cti::make_continuable([](cti::promise&& p) {p.set_value(123); }) 15 | #define THEN(val) .then((val)) 16 | #define FORWARD_INT .then([](int&& value){return value; }) 17 | #define FORWARD_INT_RETURN(val) .then([](int&& value){return (val); }) 18 | #define FAIL .fail([](int&& failure) {return cti::rethrow(std::move(failure)); }) 19 | #define WHEN_BOTH(a, b) ((a) && (b)) 20 | 21 | cti::continuable custom_success() { 22 | return SUCCESS_EXPLICIT; 23 | } 24 | #elif defined(USE_MINICOROS) 25 | #include 26 | 27 | #define SUCCESS mc::make_successful_future(123) 28 | #define SUCCESS_EXPLICIT mc::future([](mc::promise&& p) {p(123); }) 29 | #define THEN(val) .then((val)) 30 | #define FORWARD_INT .then([](int&& value) -> mc::result {return value; }) 31 | #define FORWARD_INT_RETURN(val) .then([](int&& value) -> mc::result {return (val); }) 32 | #define FAIL .fail([](int&& error_code) {return mc::failure(std::move(error_code)); }) 33 | #define WHEN_BOTH(a, b) ((a) && (b)) 34 | 35 | mc::future custom_success() { 36 | return SUCCESS; 37 | } 38 | #endif 39 | 40 | #ifdef TEST1 41 | int main() { 42 | SUCCESS 43 | FORWARD_INT 44 | FORWARD_INT 45 | FORWARD_INT 46 | FORWARD_INT 47 | FORWARD_INT 48 | FORWARD_INT 49 | FORWARD_INT 50 | FORWARD_INT 51 | FORWARD_INT 52 | FORWARD_INT 53 | FORWARD_INT 54 | FORWARD_INT 55 | FORWARD_INT 56 | FORWARD_INT 57 | FORWARD_INT 58 | FORWARD_INT 59 | FORWARD_INT 60 | FORWARD_INT 61 | FORWARD_INT 62 | FORWARD_INT 63 | FORWARD_INT 64 | FORWARD_INT 65 | FORWARD_INT 66 | FORWARD_INT 67 | FORWARD_INT 68 | FORWARD_INT 69 | FORWARD_INT 70 | FORWARD_INT 71 | FORWARD_INT 72 | FORWARD_INT 73 | FORWARD_INT 74 | FORWARD_INT 75 | FORWARD_INT 76 | FORWARD_INT 77 | FORWARD_INT 78 | FORWARD_INT 79 | ; 80 | } 81 | #endif 82 | 83 | #ifdef TEST1_1 84 | int main() { 85 | SUCCESS 86 | FORWARD_INT 87 | FORWARD_INT 88 | FORWARD_INT 89 | FORWARD_INT 90 | ; 91 | 92 | SUCCESS 93 | FORWARD_INT 94 | FORWARD_INT 95 | FORWARD_INT 96 | FORWARD_INT 97 | ; 98 | 99 | SUCCESS 100 | FORWARD_INT 101 | FORWARD_INT 102 | FORWARD_INT 103 | FORWARD_INT 104 | ; 105 | 106 | SUCCESS 107 | FORWARD_INT 108 | FORWARD_INT 109 | FORWARD_INT 110 | FORWARD_INT 111 | ; 112 | 113 | SUCCESS 114 | FORWARD_INT 115 | FORWARD_INT 116 | FORWARD_INT 117 | FORWARD_INT 118 | ; 119 | 120 | SUCCESS 121 | FORWARD_INT 122 | FORWARD_INT 123 | FORWARD_INT 124 | FORWARD_INT 125 | ; 126 | 127 | SUCCESS 128 | FORWARD_INT 129 | FORWARD_INT 130 | FORWARD_INT 131 | FORWARD_INT 132 | ; 133 | 134 | SUCCESS 135 | FORWARD_INT 136 | FORWARD_INT 137 | FORWARD_INT 138 | FORWARD_INT 139 | ; 140 | 141 | SUCCESS 142 | FORWARD_INT 143 | FORWARD_INT 144 | FORWARD_INT 145 | FORWARD_INT 146 | ; 147 | } 148 | #endif 149 | 150 | #ifdef TEST2 151 | int main() { 152 | SUCCESS 153 | FORWARD_INT_RETURN(SUCCESS) 154 | FORWARD_INT_RETURN(SUCCESS) 155 | FORWARD_INT_RETURN(SUCCESS) 156 | FORWARD_INT_RETURN(SUCCESS) 157 | FORWARD_INT_RETURN(SUCCESS) 158 | FORWARD_INT_RETURN(SUCCESS) 159 | FORWARD_INT_RETURN(SUCCESS) 160 | FORWARD_INT_RETURN(SUCCESS) 161 | FORWARD_INT_RETURN(SUCCESS) 162 | FORWARD_INT_RETURN(SUCCESS) 163 | FORWARD_INT_RETURN(SUCCESS) 164 | FORWARD_INT_RETURN(SUCCESS) 165 | FORWARD_INT_RETURN(SUCCESS) 166 | FORWARD_INT_RETURN(SUCCESS) 167 | FORWARD_INT_RETURN(SUCCESS) 168 | FORWARD_INT_RETURN(SUCCESS) 169 | FORWARD_INT_RETURN(SUCCESS) 170 | FORWARD_INT_RETURN(SUCCESS) 171 | FORWARD_INT_RETURN(SUCCESS) 172 | FORWARD_INT_RETURN(SUCCESS) 173 | ; 174 | } 175 | #endif 176 | 177 | #ifdef TEST3 178 | int main() { 179 | SUCCESS 180 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 181 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 182 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 183 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 184 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 185 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 186 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 187 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 188 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 189 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 190 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 191 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 192 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 193 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 194 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 195 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 196 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 197 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 198 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 199 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 200 | ; 201 | } 202 | #endif 203 | 204 | #ifdef TEST3_1 205 | int main() { 206 | SUCCESS 207 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 208 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 209 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 210 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 211 | ; 212 | 213 | SUCCESS 214 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 215 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 216 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 217 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 218 | ; 219 | 220 | SUCCESS 221 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 222 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 223 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 224 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 225 | ; 226 | 227 | SUCCESS 228 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 229 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 230 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 231 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 232 | ; 233 | 234 | SUCCESS 235 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 236 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 237 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 238 | FORWARD_INT_RETURN(SUCCESS_EXPLICIT) 239 | ; 240 | } 241 | #endif 242 | 243 | #ifdef TEST4 244 | int main() { 245 | SUCCESS 246 | THEN(SUCCESS) 247 | THEN(SUCCESS) 248 | THEN(SUCCESS) 249 | THEN(SUCCESS) 250 | THEN(SUCCESS) 251 | THEN(SUCCESS) 252 | THEN(SUCCESS) 253 | THEN(SUCCESS) 254 | THEN(SUCCESS) 255 | THEN(SUCCESS) 256 | THEN(SUCCESS) 257 | THEN(SUCCESS) 258 | THEN(SUCCESS) 259 | THEN(SUCCESS) 260 | THEN(SUCCESS) 261 | THEN(SUCCESS) 262 | THEN(SUCCESS) 263 | THEN(SUCCESS) 264 | THEN(SUCCESS) 265 | THEN(SUCCESS) 266 | THEN(SUCCESS) 267 | THEN(SUCCESS) 268 | THEN(SUCCESS) 269 | THEN(SUCCESS) 270 | THEN(SUCCESS) 271 | THEN(SUCCESS) 272 | THEN(SUCCESS) 273 | THEN(SUCCESS) 274 | ; 275 | } 276 | #endif 277 | 278 | #ifdef TEST4_1 279 | int main() { 280 | SUCCESS 281 | THEN(SUCCESS) 282 | THEN(SUCCESS) 283 | THEN(SUCCESS) 284 | THEN(SUCCESS) 285 | ; 286 | 287 | SUCCESS 288 | THEN(SUCCESS) 289 | THEN(SUCCESS) 290 | THEN(SUCCESS) 291 | THEN(SUCCESS) 292 | ; 293 | 294 | SUCCESS 295 | THEN(SUCCESS) 296 | THEN(SUCCESS) 297 | THEN(SUCCESS) 298 | THEN(SUCCESS) 299 | ; 300 | 301 | SUCCESS 302 | THEN(SUCCESS) 303 | THEN(SUCCESS) 304 | THEN(SUCCESS) 305 | THEN(SUCCESS) 306 | ; 307 | 308 | SUCCESS 309 | THEN(SUCCESS) 310 | THEN(SUCCESS) 311 | THEN(SUCCESS) 312 | THEN(SUCCESS) 313 | ; 314 | 315 | SUCCESS 316 | THEN(SUCCESS) 317 | THEN(SUCCESS) 318 | THEN(SUCCESS) 319 | THEN(SUCCESS) 320 | ; 321 | 322 | SUCCESS 323 | THEN(SUCCESS) 324 | THEN(SUCCESS) 325 | THEN(SUCCESS) 326 | THEN(SUCCESS) 327 | ; 328 | } 329 | #endif 330 | 331 | #ifdef TEST4_2 332 | int main() { 333 | SUCCESS 334 | THEN(custom_success()) 335 | THEN(custom_success()) 336 | THEN(custom_success()) 337 | THEN(custom_success()) 338 | ; 339 | 340 | SUCCESS 341 | THEN(custom_success()) 342 | THEN(custom_success()) 343 | THEN(custom_success()) 344 | THEN(custom_success()) 345 | ; 346 | 347 | SUCCESS 348 | THEN(custom_success()) 349 | THEN(custom_success()) 350 | THEN(custom_success()) 351 | THEN(custom_success()) 352 | ; 353 | 354 | SUCCESS 355 | THEN(custom_success()) 356 | THEN(custom_success()) 357 | THEN(custom_success()) 358 | THEN(custom_success()) 359 | ; 360 | 361 | SUCCESS 362 | THEN(custom_success()) 363 | THEN(custom_success()) 364 | THEN(custom_success()) 365 | THEN(custom_success()) 366 | ; 367 | 368 | SUCCESS 369 | THEN(custom_success()) 370 | THEN(custom_success()) 371 | THEN(custom_success()) 372 | THEN(custom_success()) 373 | ; 374 | 375 | SUCCESS 376 | THEN(custom_success()) 377 | THEN(custom_success()) 378 | THEN(custom_success()) 379 | THEN(custom_success()) 380 | ; 381 | } 382 | #endif 383 | 384 | #ifdef TEST5 385 | int main() { 386 | SUCCESS 387 | FORWARD_INT 388 | FAIL 389 | FORWARD_INT 390 | FAIL 391 | FORWARD_INT 392 | FAIL 393 | FORWARD_INT 394 | FAIL 395 | FORWARD_INT 396 | FAIL 397 | FORWARD_INT 398 | FAIL 399 | FORWARD_INT 400 | FAIL 401 | FORWARD_INT 402 | FAIL 403 | FORWARD_INT 404 | FAIL 405 | FORWARD_INT 406 | FAIL 407 | FORWARD_INT 408 | FAIL 409 | FORWARD_INT 410 | FAIL 411 | FORWARD_INT 412 | FAIL 413 | FORWARD_INT 414 | FAIL 415 | ; 416 | } 417 | #endif 418 | 419 | #ifdef MIXED_TEST 420 | int main() { 421 | SUCCESS 422 | FAIL 423 | FORWARD_INT 424 | FORWARD_INT 425 | FORWARD_INT 426 | FAIL 427 | FORWARD_INT 428 | FORWARD_INT 429 | FORWARD_INT 430 | FORWARD_INT 431 | FORWARD_INT 432 | FORWARD_INT 433 | FAIL 434 | FORWARD_INT 435 | FORWARD_INT 436 | FORWARD_INT 437 | FORWARD_INT 438 | THEN(SUCCESS) 439 | THEN(SUCCESS) 440 | THEN(SUCCESS) 441 | THEN(SUCCESS) 442 | FORWARD_INT 443 | FORWARD_INT_RETURN(SUCCESS) 444 | FORWARD_INT_RETURN(SUCCESS) 445 | FORWARD_INT_RETURN(SUCCESS) 446 | FAIL 447 | FORWARD_INT_RETURN(SUCCESS) 448 | FORWARD_INT_RETURN(SUCCESS) 449 | THEN(SUCCESS) 450 | THEN(SUCCESS) 451 | THEN(SUCCESS) 452 | FORWARD_INT_RETURN(SUCCESS) 453 | FORWARD_INT_RETURN(SUCCESS) 454 | FORWARD_INT_RETURN(SUCCESS) 455 | FORWARD_INT_RETURN(SUCCESS) 456 | FAIL 457 | ; 458 | } 459 | #endif 460 | 461 | #ifdef TEST6 462 | int main() { 463 | WHEN_BOTH( 464 | WHEN_BOTH(SUCCESS, SUCCESS), 465 | WHEN_BOTH( 466 | WHEN_BOTH(SUCCESS, SUCCESS), 467 | SUCCESS 468 | ) 469 | ) 470 | ; 471 | 472 | WHEN_BOTH( 473 | WHEN_BOTH(SUCCESS, SUCCESS), 474 | WHEN_BOTH( 475 | WHEN_BOTH(SUCCESS, SUCCESS), 476 | SUCCESS 477 | ) 478 | ) 479 | ; 480 | 481 | WHEN_BOTH( 482 | WHEN_BOTH(SUCCESS, SUCCESS), 483 | WHEN_BOTH( 484 | WHEN_BOTH(SUCCESS, SUCCESS), 485 | SUCCESS 486 | ) 487 | ) 488 | ; 489 | 490 | WHEN_BOTH( 491 | WHEN_BOTH(SUCCESS, SUCCESS), 492 | WHEN_BOTH( 493 | WHEN_BOTH(SUCCESS, SUCCESS), 494 | SUCCESS 495 | ) 496 | ) 497 | ; 498 | 499 | WHEN_BOTH( 500 | WHEN_BOTH(SUCCESS, SUCCESS), 501 | WHEN_BOTH( 502 | WHEN_BOTH(SUCCESS, SUCCESS), 503 | SUCCESS 504 | ) 505 | ) 506 | ; 507 | 508 | WHEN_BOTH( 509 | WHEN_BOTH(SUCCESS, SUCCESS), 510 | WHEN_BOTH( 511 | WHEN_BOTH(SUCCESS, SUCCESS), 512 | SUCCESS 513 | ) 514 | ) 515 | ; 516 | 517 | WHEN_BOTH( 518 | WHEN_BOTH(SUCCESS, SUCCESS), 519 | WHEN_BOTH( 520 | WHEN_BOTH(SUCCESS, SUCCESS), 521 | SUCCESS 522 | ) 523 | ) 524 | ; 525 | 526 | WHEN_BOTH( 527 | WHEN_BOTH(SUCCESS, SUCCESS), 528 | WHEN_BOTH( 529 | WHEN_BOTH(SUCCESS, SUCCESS), 530 | SUCCESS 531 | ) 532 | ) 533 | ; 534 | } 535 | #endif 536 | 537 | #ifdef MIXED_TEST_1 538 | int main() { 539 | WHEN_BOTH( 540 | SUCCESS 541 | FORWARD_INT 542 | THEN(SUCCESS) 543 | FORWARD_INT_RETURN(SUCCESS), 544 | WHEN_BOTH( 545 | WHEN_BOTH(SUCCESS, SUCCESS), 546 | SUCCESS 547 | ) 548 | ) 549 | FAIL 550 | ; 551 | 552 | WHEN_BOTH( 553 | SUCCESS 554 | FORWARD_INT 555 | THEN(SUCCESS) 556 | FORWARD_INT_RETURN(SUCCESS), 557 | WHEN_BOTH( 558 | WHEN_BOTH(SUCCESS, SUCCESS), 559 | SUCCESS 560 | ) 561 | ) 562 | FAIL 563 | ; 564 | 565 | WHEN_BOTH( 566 | SUCCESS 567 | FORWARD_INT 568 | THEN(SUCCESS) 569 | FORWARD_INT_RETURN(SUCCESS), 570 | WHEN_BOTH( 571 | WHEN_BOTH(SUCCESS, SUCCESS), 572 | SUCCESS 573 | ) 574 | ) 575 | FAIL 576 | ; 577 | } 578 | #endif -------------------------------------------------------------------------------- /include/minicoros/future.h: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | 3 | #ifndef MINICOROS_FUTURE_H_ 4 | #define MINICOROS_FUTURE_H_ 5 | 6 | #ifdef MINICOROS_CUSTOM_INCLUDE 7 | #include MINICOROS_CUSTOM_INCLUDE 8 | #endif 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef MINICOROS_USE_EASTL 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #ifndef MINICOROS_STD 22 | #define MINICOROS_STD eastl 23 | #endif 24 | #else 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #ifndef MINICOROS_STD 31 | #define MINICOROS_STD std 32 | #endif 33 | #endif 34 | 35 | namespace mc { 36 | 37 | template 38 | class result; 39 | 40 | namespace detail { 41 | 42 | template 43 | struct is_result : public MINICOROS_STD::false_type {}; 44 | 45 | template 46 | struct is_result> : public MINICOROS_STD::true_type {}; 47 | 48 | template 49 | constexpr bool is_result_v = is_result::value; 50 | 51 | template 52 | struct resulting_successful_type { 53 | static constexpr bool CallbackHasCorrectReturnType = is_result_v || MINICOROS_STD::is_void_v; 54 | static_assert(CallbackHasCorrectReturnType, "Invalid return type in .then handler. Please explicitly set the return type to `mc::result<...>`."); 55 | }; 56 | 57 | template 58 | struct resulting_successful_type> { 59 | using type = ResultType; 60 | }; 61 | 62 | template 63 | struct resulting_successful_type> { 64 | using type = MINICOROS_STD::tuple; 65 | }; 66 | 67 | /// To simplify common usage, returning `void` directly from a then handler is OK. 68 | /// This decision causes an exception to the general rule that handlers should always return `mc::result`. 69 | template<> 70 | struct resulting_successful_type { 71 | using type = void; 72 | }; 73 | 74 | template 75 | auto resulting_type_from_successful_callback(LambdaType&& lambda) -> typename resulting_successful_type(lambda)))>::type; 76 | 77 | 78 | /// Struct used to map the return type of fail handlers to a naked type that can be used in a future. 79 | /// Ie; 80 | /// mc::result -> T 81 | /// mc::failure -> FallbackType 82 | /// ... -> error 83 | template 84 | struct resulting_failure_type { 85 | static constexpr bool CallbackHasCorrectReturnType = is_result_v || MINICOROS_STD::is_same_v; 86 | static_assert(CallbackHasCorrectReturnType, "Invalid return type in .fail handler. Please `return mc::failure(...)` to rethrow the error or explicitly set the return type to `mc::result<...>` to recover with a new successful value."); 87 | }; 88 | 89 | template 90 | struct resulting_failure_type, FallbackType> { 91 | using type = ResultType; 92 | }; 93 | 94 | template 95 | struct resulting_failure_type, FallbackType> { 96 | using type = MINICOROS_STD::tuple; 97 | }; 98 | 99 | /// Specialization for `failure` so that fail handlers can be created without knowing 100 | /// the return type of the future. Useful for reusable failure handlers. 101 | template 102 | struct resulting_failure_type { 103 | using type = FallbackType; 104 | }; 105 | 106 | template 107 | auto resulting_type_from_failure_callback(CallbackType&& callback) -> typename resulting_failure_type())), FallbackType>::type; 108 | 109 | } // detail 110 | 111 | /// Represents a lazily evaluated process which can be composed of multiple sub-processes ("callbacks") and that 112 | /// eventually results in a value of type `T`. 113 | /// Each callback can decide to return immediately/synchronously or later/asynchronously. 114 | /// Callbacks are arranged in a chain/pipeline and interact using arguments and return values -- callback 1 115 | /// returns value X which is given to callback 2 as an argument. 116 | /// Callbacks can also return failures which are propagated through the chain in a similar fashion as the return values. 117 | /// 118 | /// ```cpp 119 | /// future([] (promise p) { 120 | /// p(6581); 121 | /// }) 122 | /// .then([](int value) -> result { 123 | /// return "text"; 124 | /// }) 125 | /// .then([](std::string value) -> result { 126 | /// return failure(ENOENT); 127 | /// }) 128 | /// .fail([](int error_code) { 129 | /// log("error!"); 130 | /// return failure(std::move(error_code)); 131 | /// }); 132 | /// ``` 133 | /// 134 | /// The `future` class is a monad that wraps a `continuation_chain`. Basically it's syntactic sugar 135 | /// on top of the continuation chain to make it easier to use. It also has support for exception-like 136 | /// error handling. 137 | template 138 | class [[nodiscard]] future { 139 | public: 140 | static_assert(MINICOROS_STD::is_void_v || MINICOROS_STD::is_copy_constructible_v, "Type must be copy-constructible"); // TODO: unfortunately we need copy-constructors. Get rid of that (might need move-only std::function) 141 | static_assert(MINICOROS_STD::is_void_v || MINICOROS_STD::is_move_constructible_v, "Type must be move-constructible"); 142 | 143 | future(continuation>&& callback) : chain_(MINICOROS_STD::move(callback)) {} 144 | future(continuation_chain>&& chain) : chain_(MINICOROS_STD::move(chain)) {} 145 | 146 | future(const future&) = delete; 147 | future& operator =(const future&) = delete; 148 | 149 | future(future&& other) : chain_(MINICOROS_STD::move(other.chain_)) {} 150 | future& operator =(future&& other) {chain_ = MINICOROS_STD::move(other.chain_); return *this; } 151 | 152 | ~future() { 153 | if (!chain_.evaluated()) 154 | MINICOROS_STD::move(*this).ignore_result(); 155 | } 156 | 157 | using type = T; 158 | 159 | /// Creates a new future by transforming this future through the given callback. 160 | /// The callback will be invoked with the resulting value of this future iff it's successful, otherwise 161 | /// execution will propagate through to the next callback. 162 | /// The callback must either return `mc::result` (which transforms this future to a `future`), or 163 | /// `void`, ie, no return statement. 164 | /// The callback must accept as an argument the resulting value from this future. 165 | /// 166 | /// ```cpp 167 | /// future(...) 168 | /// .then([](int&& resulting_value) -> mc::result { 169 | /// return "hello"; 170 | /// }) 171 | /// .then([](std::string&& resulting_value) { 172 | /// ... 173 | /// }) 174 | /// .then([] { 175 | /// ... 176 | /// }); 177 | /// ``` 178 | template 179 | auto then(CallbackType&& callback) && { 180 | using ReturnType = decltype(detail::resulting_type_from_successful_callback(MINICOROS_STD::forward(callback))); 181 | 182 | // Transform the continuation chain... 183 | auto new_chain = MINICOROS_STD::move(chain_).template transform>([callback = MINICOROS_STD::forward(callback)](concrete_result&& result, promise&& promise) mutable { 184 | if (result.success()) { 185 | result.resolve_promise_with_callback(MINICOROS_STD::forward(callback), MINICOROS_STD::move(promise)); 186 | } 187 | else { 188 | promise(MINICOROS_STD::move(*result.get_failure())); 189 | } 190 | }); 191 | 192 | // ... and return it wrapped in a future 193 | return future{MINICOROS_STD::move(new_chain)}; 194 | } 195 | 196 | /// Creates a new future by transforming this future through the given callback. 197 | /// The callback is executed iff this future has resulted in a failure, otherwise execution will be 198 | /// propagated to the next callback. 199 | /// A fail callback can return either `mc::result` to transform the value or raise a new error, or 200 | /// `failure` to propagate the existing or a transformed error. 201 | /// 202 | /// ```cpp 203 | /// future(...) 204 | /// .then([] (std::string&&) -> mc::result { 205 | /// return failure(ENOENT); 206 | /// }) 207 | /// .fail([] (int&& error_code) -> mc::result { 208 | /// return failure(EBUSY); // Remap the error 209 | /// }) 210 | /// .fail([] (int&& error_code) { 211 | /// log() << "received error: " << error_code; 212 | /// return failure(error_code); 213 | /// }) 214 | /// .fail([] (int&& error_code) -> mc::result { 215 | /// return "success"; // Recover from the error 216 | /// }); 217 | /// ``` 218 | template 219 | auto fail(CallbackType&& callback) && { 220 | using ReturnType = decltype(detail::resulting_type_from_failure_callback(MINICOROS_STD::forward(callback))); 221 | using CallbackReturnType = decltype(callback(MINICOROS_STD::declval())); 222 | using ResultType = MINICOROS_STD::conditional_t, CallbackReturnType, mc::result>; 223 | 224 | // Transform the continuation chain... 225 | auto new_chain = MINICOROS_STD::move(chain_).template transform>([callback = MINICOROS_STD::forward(callback)] (concrete_result&& result, promise&& promise) mutable { 226 | if (result.success()) { 227 | promise(MINICOROS_STD::move(result)); 228 | } 229 | else { 230 | // We received a failure, so invoke the callback 231 | ResultType res{callback(MINICOROS_STD::move(result.get_failure()->error))}; 232 | res.resolve_promise(MINICOROS_STD::move(promise)); 233 | } 234 | }); 235 | 236 | // ... and return it wrapped in a future 237 | return future{MINICOROS_STD::move(new_chain)}; 238 | } 239 | 240 | /// Called regardless of success or failure. A `concrete_result` will be passed to 241 | /// the callback, and the callback is expected to return a `concrete_result`. 242 | template 243 | auto map(CallbackType&& callback) && { 244 | using ReturnType = decltype(callback(MINICOROS_STD::declval>())); 245 | static_assert(is_concrete_result_v, "Callback must return concrete_result<...>"); 246 | 247 | using WrappedType = typename ReturnType::type; 248 | 249 | auto new_chain = MINICOROS_STD::move(chain_).template transform([callback = MINICOROS_STD::forward(callback)] (concrete_result&& result, promise&& promise) mutable { 250 | promise(callback(MINICOROS_STD::move(result))); 251 | }); 252 | 253 | return future{MINICOROS_STD::move(new_chain)}; 254 | } 255 | 256 | template 257 | auto finally(CallbackType&& callback) && { 258 | return MINICOROS_STD::move(*this).map(MINICOROS_STD::forward(callback)); 259 | } 260 | 261 | template 262 | void done(CallbackType&& callback) && { 263 | MINICOROS_STD::move(chain_).evaluate_into(MINICOROS_STD::forward(callback)); 264 | } 265 | 266 | /// Explicitly terminate this chain; we've handled everything we need. 267 | void ignore_result() && { 268 | MINICOROS_STD::move(chain_).evaluate_into([] (auto) {}); 269 | } 270 | 271 | /// Transforms this future by executing the downstream callbacks through the given "executor". 272 | /// An executor is something that has an `operator ()(WorkType&&)` where WorkType is a move-only object 273 | /// that has an `operator ()()`. 274 | /// Typically used for enqueuing evaluation on a work queue. 275 | template 276 | future enqueue(ExecutorType&& executor) && { 277 | // Take the executor by copy 278 | return MINICOROS_STD::move(chain_).template transform>([executor](concrete_result&& value, promise&& promise) mutable { 279 | executor([value = MINICOROS_STD::move(value), promise = MINICOROS_STD::move(promise)] () mutable { 280 | MINICOROS_STD::move(promise)(MINICOROS_STD::move(value)); 281 | }); 282 | }); 283 | } 284 | 285 | template 286 | auto operator &&(future&& rhs) && { 287 | using ResultingTupleType = typename detail::tuple_result::value_type; 288 | 289 | return future([lhs_chain = MINICOROS_STD::move(*this).chain(), rhs_chain = MINICOROS_STD::move(rhs).chain()](promise&& p) mutable { 290 | auto result_builder = MINICOROS_STD::make_shared>(MINICOROS_STD::move(p)); 291 | 292 | MINICOROS_STD::move(lhs_chain).evaluate_into([result_builder] (concrete_result&& result) { 293 | result_builder->assign_lhs(MINICOROS_STD::move(result)); 294 | }); 295 | 296 | MINICOROS_STD::move(rhs_chain).evaluate_into([result_builder] (concrete_result&& result) { 297 | result_builder->assign_rhs(MINICOROS_STD::move(result)); 298 | }); 299 | }); 300 | } 301 | 302 | /// Returns the first result from any of the futures. If the first result is a failure, 303 | /// `||` will return that failure. 304 | future operator ||(future&& rhs) && { 305 | // Unwrap the chains from their future overcoats. Futures aren't copy-constructible, but the chains are. Remove 306 | // this when we have move-only std::function. 307 | return future([lhs_chain = MINICOROS_STD::move(*this).chain(), rhs_chain = MINICOROS_STD::move(rhs).chain()](promise&& p) mutable { 308 | auto result_builder = MINICOROS_STD::make_shared>(MINICOROS_STD::move(p)); 309 | 310 | MINICOROS_STD::move(lhs_chain).evaluate_into([result_builder] (concrete_result&& result) { 311 | result_builder->assign(MINICOROS_STD::move(result)); 312 | }); 313 | 314 | MINICOROS_STD::move(rhs_chain).evaluate_into([result_builder] (concrete_result&& result) { 315 | result_builder->assign(MINICOROS_STD::move(result)); 316 | }); 317 | }); 318 | } 319 | 320 | continuation_chain>&& chain() && { 321 | return MINICOROS_STD::move(chain_); 322 | } 323 | 324 | /// Stops the chain from getting evaluated on future destruction. 325 | void freeze() { 326 | chain_.reset(); 327 | } 328 | 329 | private: 330 | continuation_chain> chain_; 331 | }; 332 | 333 | template 334 | future make_successful_future(T&& value) { 335 | return future([value = MINICOROS_STD::forward(value)](promise&& p) mutable {p(MINICOROS_STD::forward(value)); }); 336 | } 337 | 338 | template 339 | future make_successful_future(const T& value) { 340 | return future([value](promise&& p) {p(T{MINICOROS_STD::move(value)}); }); 341 | } 342 | 343 | template 344 | future make_successful_future(mc::future&& value) { 345 | return MINICOROS_STD::move(value); 346 | } 347 | 348 | template 349 | future make_successful_future() { 350 | return future([](promise&& p) {p({}); }); 351 | } 352 | 353 | template 354 | future make_failed_future(MINICOROS_ERROR_TYPE&& error) { 355 | return future([error = MINICOROS_STD::move(error)](promise&& p) mutable {p(failure{MINICOROS_STD::move(error)}); }); 356 | } 357 | 358 | template 359 | class persistent_promise 360 | { 361 | public: 362 | void resolve(concrete_result value) { 363 | if (stored_promise_) { 364 | MINICOROS_STD::move(stored_promise_)(MINICOROS_STD::move(value)); 365 | stored_promise_ = nullptr; 366 | } 367 | else { 368 | stored_value_.emplace(MINICOROS_STD::move(value)); 369 | } 370 | } 371 | 372 | void imbue(promise p) { 373 | if (stored_value_) { 374 | p(MINICOROS_STD::move(*stored_value_)); 375 | stored_value_.reset(); 376 | } 377 | else { 378 | stored_promise_ = MINICOROS_STD::move(p); 379 | } 380 | } 381 | 382 | private: 383 | MINICOROS_STD::optional> stored_value_; 384 | promise stored_promise_; 385 | }; 386 | 387 | /// An easier way for creating a future so that you get a promise at creation and don't have to wait for the lambda 388 | /// in the constructor to get called. This function hides the lazy evaluation of minicoros, and in that way, avoids some 389 | /// common mistakes. The promise can be resolved at any time. 390 | /// 391 | /// Example: 392 | /// ``` 393 | /// auto [future, promise] = make_future(); 394 | /// promise(123); 395 | /// return move(future); 396 | /// ``` 397 | template 398 | MINICOROS_STD::pair, promise> make_future() 399 | { 400 | MINICOROS_STD::shared_ptr> pp = MINICOROS_STD::make_shared>(); 401 | 402 | auto fut = future([pp](promise p) { 403 | pp->imbue(MINICOROS_STD::move(p)); 404 | }); 405 | 406 | promise pp_resolver = [pp](concrete_result result) { 407 | pp->resolve(MINICOROS_STD::move(result)); 408 | }; 409 | 410 | return {MINICOROS_STD::move(fut), MINICOROS_STD::move(pp_resolver)}; 411 | } 412 | 413 | /// Deals with the various types a callback can return: 414 | /// 415 | /// ```cpp 416 | /// .then([](int&& value) -> mc::result { 417 | /// // Return a value: 418 | /// return 3.141f; 419 | /// 420 | /// // Return a failure: 421 | /// return failure(1234); 422 | /// 423 | /// // Return a future: 424 | /// return mc::make_successful_future(1.23f); 425 | /// }); 426 | /// ``` 427 | template 428 | class result { 429 | public: 430 | using type = MINICOROS_STD::conditional_t>::type, MINICOROS_STD::tuple>; 431 | 432 | private: 433 | using StoredType = MINICOROS_STD::decay_t; 434 | 435 | public: 436 | result(future&& coro) : value_(MINICOROS_STD::move(coro)) {} 437 | 438 | template 439 | result(OtherType&& value) : value_(StoredType(MINICOROS_STD::move(value))) {} 440 | 441 | result(failure&& f) : value_(MINICOROS_STD::move(f)) {} 442 | 443 | void resolve_promise(promise&& promise) { 444 | if (StoredType* value = MINICOROS_STD::get_if(&value_)) 445 | MINICOROS_STD::move(promise)(MINICOROS_STD::move(*value)); 446 | else if (future* coro = MINICOROS_STD::get_if>(&value_)) 447 | MINICOROS_STD::move(*coro).chain().evaluate_into(MINICOROS_STD::move(promise)); 448 | else if (failure* f = MINICOROS_STD::get_if(&value_)) 449 | MINICOROS_STD::move(promise)(MINICOROS_STD::move(*f)); 450 | else 451 | assert("invalid result state" && 0); 452 | } 453 | 454 | private: 455 | MINICOROS_STD::variant, failure> value_; 456 | }; 457 | 458 | /// Specalization for callbacks that return `result`. 459 | template<> 460 | class result { 461 | struct success_t {}; 462 | 463 | public: 464 | using type = void; 465 | 466 | result() : value_(success_t{}) {} 467 | result(future&& coro) : value_(MINICOROS_STD::move(coro)) {} 468 | result(failure&& f) : value_(MINICOROS_STD::move(f)) {} 469 | 470 | void resolve_promise(promise&& promise) { 471 | if (MINICOROS_STD::get_if(&value_)) 472 | MINICOROS_STD::move(promise)({}); 473 | else if (future* coro = MINICOROS_STD::get_if>(&value_)) 474 | MINICOROS_STD::move(*coro).chain().evaluate_into(MINICOROS_STD::move(promise)); 475 | else if (failure* f = MINICOROS_STD::get_if(&value_)) 476 | MINICOROS_STD::move(promise)(MINICOROS_STD::move(*f)); 477 | else 478 | assert("invalid result state" && 0); 479 | } 480 | 481 | private: 482 | MINICOROS_STD::variant, failure> value_; 483 | }; 484 | 485 | } // mc 486 | 487 | #endif // MINICOROS_FUTURE_H_ 488 | -------------------------------------------------------------------------------- /test/test_future.cpp: -------------------------------------------------------------------------------- 1 | /// Copyright (C) 2022 Electronic Arts Inc. All rights reserved. 2 | 3 | #include "testing.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace testing; 11 | 12 | class work_queue { 13 | public: 14 | void enqueue_work(std::function item) { 15 | work_items_.push_back(std::move(item)); 16 | } 17 | 18 | void execute() { 19 | std::vector> items = std::move(work_items_); 20 | 21 | for (auto& item : items) 22 | item(); 23 | } 24 | 25 | private: 26 | std::vector> work_items_; 27 | }; 28 | 29 | TEST(future, chaining_works) { 30 | auto count = std::make_shared(0); 31 | 32 | { 33 | mc::future([](mc::promise p) { 34 | p(123); 35 | }) 36 | .then([count](int value) -> mc::result { 37 | ++*count; 38 | ASSERT_EQ(value, 123); 39 | return "hullo"; 40 | }) 41 | .then([count](std::string value) -> mc::result { 42 | ++*count; 43 | ASSERT_EQ(value, "hullo"); 44 | return 8086; 45 | }) 46 | .done([](auto) {}); 47 | } 48 | 49 | ASSERT_EQ(*count, 2); 50 | } 51 | 52 | TEST(future, can_return_nested_future) { 53 | auto count = std::make_shared(0); 54 | 55 | { 56 | mc::future([](mc::promise p) { 57 | p(123); 58 | }) 59 | .then([count](int) -> mc::result { 60 | return mc::future([count](mc::promise p) { 61 | ++*count; 62 | p(std::string{"mo"}); 63 | }) 64 | .then([](std::string value) -> mc::result { 65 | return value + "of"; 66 | }); 67 | }) 68 | .then([count](std::string value) -> mc::result { 69 | ++*count; 70 | ASSERT_EQ(value, "moof"); 71 | return 8086; 72 | }) 73 | .done([](auto) {}); 74 | } 75 | 76 | ASSERT_EQ(*count, 2); 77 | } 78 | 79 | TEST(future, failures_jump_to_fail_handler) { 80 | using namespace mc; 81 | auto num_fail_invocations = std::make_shared(); 82 | 83 | future([](promise p) { 84 | p(123); 85 | }) 86 | .then([](int) -> mc::result { 87 | return failure(123); 88 | }) 89 | .then([](std::string) -> mc::result { 90 | TEST_FAIL("Reached a .then handler we shouldn't"); 91 | return "moof"; 92 | }) 93 | .fail([num_fail_invocations](int error_code) -> mc::result { 94 | ASSERT_EQ(error_code, 123); 95 | ++*num_fail_invocations; 96 | return failure(1234); 97 | }) 98 | .fail([num_fail_invocations](int error_code) -> mc::result { 99 | ASSERT_EQ(error_code, 1234); 100 | ++*num_fail_invocations; 101 | return failure(444); 102 | }) 103 | .done([](auto) {}); 104 | 105 | ASSERT_EQ(*num_fail_invocations, 2); 106 | } 107 | 108 | TEST(future, failures_can_be_recovered) { 109 | using namespace mc; 110 | auto num_invocations = std::make_shared(); 111 | 112 | future([](promise p) { 113 | p(failure(1235)); 114 | }) 115 | .fail([num_invocations](int error_code) -> mc::result { 116 | ASSERT_EQ(error_code, 1235); 117 | ++*num_invocations; 118 | return "hullo"; 119 | }) 120 | .fail([](int error_code) -> mc::result { 121 | (void)error_code; 122 | TEST_FAIL("Reached a .fail handler we shouldn't"); 123 | return failure(444); 124 | }) 125 | .then([num_invocations](std::string value) -> mc::result { 126 | ASSERT_EQ(value, "hullo"); 127 | ++*num_invocations; 128 | return "moof"; 129 | }) 130 | .done([](auto) {}); 131 | 132 | ASSERT_EQ(*num_invocations, 2); 133 | } 134 | 135 | TEST(future, enqueue_executes_through_executor) { 136 | using namespace mc; 137 | 138 | auto executor = std::make_shared(); 139 | auto put_on_executor = [executor] (std::function work) {executor->enqueue_work(std::move(work)); }; 140 | auto num_invocations = std::make_shared(); 141 | 142 | future([](promise p) { 143 | p(123); 144 | }) 145 | .then([num_invocations](int) -> mc::result { 146 | ++*num_invocations; 147 | return 444; 148 | }) 149 | .enqueue(put_on_executor) 150 | .then([num_invocations](int) -> mc::result { 151 | ++*num_invocations; 152 | return failure(123); 153 | }) 154 | .enqueue(put_on_executor) 155 | .fail([num_invocations](int) -> mc::result { 156 | ++*num_invocations; 157 | return failure(123); 158 | }) 159 | .done([](auto) {}); 160 | 161 | ASSERT_EQ(*num_invocations, 1); 162 | executor->execute(); 163 | ASSERT_EQ(*num_invocations, 2); 164 | executor->execute(); 165 | ASSERT_EQ(*num_invocations, 3); 166 | } 167 | 168 | TEST(future, success_type_is_deduced) { 169 | using namespace mc; 170 | 171 | future([](promise p) { 172 | p(std::string{"hullo"}); 173 | }) 174 | .then([](std::string) -> result { 175 | return "hey"; 176 | }) 177 | .then([](std::string) -> result { 178 | return make_successful_future(1234); 179 | }) 180 | .then([](int) -> result { 181 | if (1 == 1) 182 | return make_successful_future(4444); 183 | else 184 | return failure(12345); 185 | 186 | return 123; 187 | }) 188 | .then([](int) -> result { 189 | return "huhu"; 190 | }) 191 | .then([](std::string) -> result { 192 | return 444; 193 | }) 194 | .done([](auto) {}); 195 | } 196 | 197 | 198 | TEST(future, then_propagates_future_failures) { 199 | using namespace mc; 200 | auto num_invocations = std::make_shared(); 201 | 202 | make_successful_future() 203 | .then([]()-> mc::result { 204 | return make_failed_future(123456); 205 | }) 206 | .then([num_invocations] {*num_invocations += 1; }) 207 | .fail([num_invocations](int error_code) { 208 | ASSERT_EQ(error_code, 123456); 209 | *num_invocations += 2; 210 | return failure(123); 211 | }) 212 | .done([](auto) {}); 213 | 214 | ASSERT_EQ(*num_invocations, 2); 215 | } 216 | 217 | TEST(future, failure_is_not_propagated_to_future) { 218 | using namespace mc; 219 | auto num_invocations = std::make_shared(); 220 | 221 | make_failed_future(12345) 222 | .then([num_invocations] {*num_invocations += 1; }) 223 | .done([](auto) {}); 224 | 225 | ASSERT_EQ(*num_invocations, 0); 226 | } 227 | 228 | TEST(future, futures_can_return_void) { 229 | using namespace mc; 230 | auto num_invocations = std::make_shared(); 231 | 232 | { 233 | future c = make_successful_future() 234 | .then([num_invocations] { 235 | ++*num_invocations; 236 | }) 237 | .then([num_invocations]() -> mc::result { 238 | ++*num_invocations; 239 | return make_successful_future(); 240 | }); 241 | } 242 | 243 | ASSERT_EQ(*num_invocations, 2); 244 | } 245 | 246 | TEST(future, futures_returning_void_can_be_transformed_to_and_from) { 247 | using namespace mc; 248 | auto num_invocations = std::make_shared(); 249 | 250 | { 251 | future c = make_successful_future() 252 | .then([num_invocations]() -> result { 253 | ++*num_invocations; 254 | return 123; 255 | }) 256 | .then([num_invocations](int value) -> result { 257 | ASSERT_EQ(value, 123); 258 | ++*num_invocations; 259 | return {}; 260 | }) 261 | .then([num_invocations]() -> result { 262 | ++*num_invocations; 263 | return 124; 264 | }) 265 | .then([num_invocations](int value) { 266 | ASSERT_EQ(value, 124); 267 | ++*num_invocations; 268 | }); 269 | } 270 | 271 | ASSERT_EQ(*num_invocations, 4); 272 | } 273 | 274 | TEST(future, fail_handler_can_take_untyped_passthrough_callback) { 275 | auto num_invocations = std::make_shared(); 276 | 277 | // Verifies that `fail` can take callbacks that transform the error without knowing about 278 | // the successful future return type 279 | mc::make_failed_future(12345) 280 | .fail([](int error) { 281 | return mc::failure(error + 1); 282 | }) 283 | .fail([num_invocations](int error_code) { 284 | ASSERT_EQ(error_code, 12346); 285 | ++*num_invocations; 286 | return mc::failure(std::move(error_code)); 287 | }) 288 | .done([](auto) {}); 289 | 290 | ASSERT_EQ(*num_invocations, 1); 291 | } 292 | 293 | TEST(future, fail_handler_can_recover_with_result_void) { 294 | auto num_invocations = std::make_shared(); 295 | 296 | mc::make_failed_future(12345) 297 | .then([num_invocations](std::string) -> mc::result { 298 | ++*num_invocations; 299 | return {}; 300 | }) 301 | .fail([num_invocations](int) -> mc::result { 302 | *num_invocations += 2; 303 | return {}; 304 | }) 305 | .fail([num_invocations](int error) { 306 | *num_invocations += 4; 307 | return mc::failure(std::move(error)); 308 | }) 309 | .then([num_invocations] { 310 | *num_invocations += 8; 311 | }) 312 | .done([](auto) {}); 313 | 314 | ASSERT_EQ(*num_invocations, 2 + 8); 315 | } 316 | 317 | TEST(future, two_allocations_per_evaluated_then) { 318 | using namespace mc; 319 | alloc_counter allocs; 320 | 321 | { 322 | make_successful_future(8086) 323 | .then([] (int) -> mc::result {return 123;}) 324 | .then([] (int) {}) 325 | .then([] {}) 326 | .done([](auto) {}); 327 | } 328 | 329 | ASSERT_EQ(allocs.total_allocation_count(), 6); 330 | } 331 | 332 | TEST(future, one_allocation_per_unevaluated_then) { 333 | using namespace mc; 334 | alloc_counter allocs; 335 | 336 | { 337 | auto c = make_successful_future(8086) 338 | .then([] (int) -> mc::result {return 123;}) 339 | .then([] (int) {}) 340 | .then([] {}); 341 | ASSERT_EQ(allocs.total_allocation_count(), 3); 342 | } 343 | } 344 | 345 | TEST(future, two_allocations_per_evaluated_fail) { 346 | using namespace mc; 347 | alloc_counter allocs; 348 | 349 | { 350 | make_failed_future(8086) 351 | .fail([] (int) {return failure(123);}) 352 | .fail([] (int) {return failure(444);}) 353 | .done([](auto) {}); 354 | } 355 | 356 | ASSERT_EQ(allocs.total_allocation_count(), 4); 357 | } 358 | 359 | TEST(future, one_allocation_per_unevaluated_fail) { 360 | using namespace mc; 361 | alloc_counter allocs; 362 | 363 | { 364 | auto c = make_failed_future(8086) 365 | .fail([] (int) {return failure(123);}) 366 | .fail([] (int) {return failure(444);}); 367 | ASSERT_EQ(allocs.total_allocation_count(), 2); 368 | } 369 | } 370 | 371 | TEST(future, andand_with_two_successful_futures_returns_tuple_successfully) { 372 | using namespace mc; 373 | 374 | future> coro = make_successful_future(123) && make_successful_future("hello"); 375 | assert_successful_result_eq(std::move(coro), {123, std::string{"hello"}}); 376 | } 377 | 378 | TEST(future, andand_with_value_and_value_get_concatenated) { 379 | using namespace mc; 380 | 381 | future> coro = make_successful_future(123) && make_successful_future(true); 382 | assert_successful_result_eq(std::move(coro), {123, true}); 383 | } 384 | 385 | TEST(future, andand_with_value_and_value_can_raise_error) { 386 | using namespace mc; 387 | 388 | future> fut = make_successful_future(123) && make_successful_future(true); 389 | assert_successful_result_eq(std::move(fut), {123, true}); 390 | 391 | future> fut2 = make_failed_future(123) && make_successful_future(true); 392 | assert_fail_eq(std::move(fut2), 123); 393 | 394 | future> fut3 = make_successful_future(123) && make_failed_future(444); 395 | assert_fail_eq(std::move(fut3), 444); 396 | } 397 | 398 | TEST(future, andand_with_tuple_and_value_get_concatenated) { 399 | using namespace mc; 400 | 401 | auto operand = make_successful_future(123) && make_successful_future("hello"); 402 | future> fut = std::move(operand) && make_successful_future(true); 403 | assert_successful_result_eq(std::move(fut), {123, std::string{"hello"}, true}); 404 | } 405 | 406 | TEST(future, andand_with_value_and_tuple_get_concatenated) { 407 | using namespace mc; 408 | 409 | auto operand = make_successful_future(123) && make_successful_future("hello"); 410 | future> fut = make_successful_future(true) && std::move(operand); 411 | assert_successful_result_eq(std::move(fut), {true, 123, std::string{"hello"}}); 412 | } 413 | 414 | TEST(future, andand_with_tuple_and_tuple_get_concatenated) { 415 | using namespace mc; 416 | 417 | auto operand1 = make_successful_future(true) && make_successful_future(false); 418 | auto operand2 = make_successful_future(123) && make_successful_future("hello"); 419 | 420 | future> fut = std::move(operand1) && std::move(operand2); 421 | assert_successful_result_eq(std::move(fut), {true, false, 123, std::string{"hello"}}); 422 | } 423 | 424 | TEST(future, andand_supports_void) { 425 | using namespace mc; 426 | 427 | { 428 | future fut = make_successful_future(true) && make_successful_future(); 429 | assert_successful_result_eq(std::move(fut), {true}); 430 | 431 | future fut2 = make_failed_future(333) && make_successful_future(); 432 | assert_fail_eq(std::move(fut2), 333); 433 | 434 | future fut3 = make_successful_future(true) && make_failed_future(222); 435 | assert_fail_eq(std::move(fut3), 222); 436 | } 437 | 438 | { 439 | future coro = make_successful_future() && make_successful_future(true); 440 | assert_successful_result_eq(std::move(coro), {true}); 441 | 442 | future coro2 = make_failed_future(555) && make_successful_future(true); 443 | assert_fail_eq(std::move(coro2), 555); 444 | 445 | future coro3 = make_successful_future() && make_failed_future(555); 446 | assert_fail_eq(std::move(coro3), 555); 447 | } 448 | 449 | { 450 | future coro = make_successful_future() && make_successful_future(); 451 | assert_successful_result(std::move(coro)); 452 | 453 | future coro2 = make_failed_future(444) && make_successful_future(); 454 | assert_fail_eq(std::move(coro2), 444); 455 | 456 | future coro3 = make_successful_future() && make_failed_future(444); 457 | assert_fail_eq(std::move(coro3), 444); 458 | } 459 | } 460 | 461 | class type_without_copy_assignment 462 | { 463 | public: 464 | ~type_without_copy_assignment() = default; 465 | type_without_copy_assignment(const type_without_copy_assignment&) = default; 466 | type_without_copy_assignment(type_without_copy_assignment&&) = default; 467 | type_without_copy_assignment& operator=(const type_without_copy_assignment&) = delete; 468 | type_without_copy_assignment& operator=(type_without_copy_assignment&&) = delete; 469 | }; 470 | 471 | TEST(future, andand_supports_type_without_copy_assignment) { 472 | using namespace mc; 473 | 474 | ( 475 | make_successful_future() 476 | && make_successful_future(type_without_copy_assignment{}) 477 | ).ignore_result(); 478 | 479 | ( 480 | make_successful_future(type_without_copy_assignment{}) 481 | && make_successful_future() 482 | ).ignore_result(); 483 | 484 | ( 485 | make_successful_future(type_without_copy_assignment{}) 486 | && make_successful_future(type_without_copy_assignment{}) 487 | ).ignore_result(); 488 | } 489 | 490 | TEST(future, oror_resolves_to_first) { 491 | using namespace mc; 492 | 493 | future coro = make_successful_future(1234) || make_failed_future(444); 494 | assert_successful_result_eq(std::move(coro), 1234); 495 | } 496 | 497 | TEST(future, oror_resolves_to_first_even_if_it_is_a_failure) { 498 | using namespace mc; 499 | 500 | future coro = make_failed_future(555) || make_successful_future(123); 501 | assert_fail_eq(std::move(coro), 555); 502 | } 503 | 504 | TEST(future, oror_supports_void) { 505 | using namespace mc; 506 | 507 | future coro = make_successful_future() || make_failed_future(444); 508 | assert_successful_result(std::move(coro)); 509 | } 510 | 511 | TEST(future, oror_handles_delayed_results) { 512 | using namespace mc; 513 | 514 | promise p1, p2; 515 | bool called = false; 516 | 517 | auto coro1 = future([&](promise p) {p1 = std::move(p); }); 518 | auto coro2 = future([&](promise p) {p2 = std::move(p); }); 519 | 520 | (std::move(coro1) || std::move(coro2)) 521 | .fail([&](int error_code) { 522 | ASSERT_EQ(error_code, 445); 523 | called = true; 524 | return failure(std::move(error_code)); 525 | }) 526 | .done([](auto) {}); 527 | 528 | ASSERT_FALSE(called); 529 | 530 | p1(failure{445}); 531 | 532 | ASSERT_TRUE(called); 533 | 534 | p2(123); // Check that it doesn't crash 535 | } 536 | 537 | 538 | mc::future make_future(mc::promise& p) { 539 | return mc::future([&](mc::promise new_promise) {p = std::move(new_promise); }); 540 | } 541 | 542 | TEST(future, oror_composed_evaluates_all_promises) { 543 | mc::promise p1, p2, p3; 544 | 545 | (make_future(p1) || make_future(p2) || make_future(p3)) 546 | .done([](auto) {}); 547 | 548 | ASSERT_TRUE(bool{p1}); 549 | ASSERT_TRUE(bool{p2}); 550 | ASSERT_TRUE(bool{p3}); 551 | } 552 | 553 | TEST(future, oror_composed_resolve_on_first_call) { 554 | mc::promise p1, p2, p3; 555 | 556 | { 557 | bool called = false; 558 | (make_future(p1) || make_future(p2) || make_future(p3)).done([&] (auto) {called = true; }); 559 | ASSERT_FALSE(called); 560 | 561 | p1({}); 562 | ASSERT_TRUE(called); 563 | } 564 | 565 | { 566 | bool called = false; 567 | (make_future(p1) || make_future(p2) || make_future(p3)).done([&] (auto) {called = true; }); 568 | ASSERT_FALSE(called); 569 | 570 | p2({}); 571 | ASSERT_TRUE(called); 572 | } 573 | 574 | { 575 | bool called = false; 576 | (make_future(p1) || make_future(p2) || make_future(p3)).done([&] (auto) {called = true; }); 577 | ASSERT_FALSE(called); 578 | 579 | p3({}); 580 | ASSERT_TRUE(called); 581 | } 582 | } 583 | 584 | TEST(future, oror_supports_type_without_copy_assignment) { 585 | using namespace mc; 586 | 587 | ( 588 | make_successful_future(type_without_copy_assignment{}) 589 | || make_successful_future(type_without_copy_assignment{}) 590 | ).ignore_result(); 591 | } 592 | 593 | TEST(future, partial_application_can_take_subsets) { 594 | auto call_count = std::make_shared(); 595 | 596 | (mc::make_successful_future(123) && mc::make_successful_future(true) && mc::make_successful_future()) 597 | .then([call_count](int v1, bool v2) { 598 | ++*call_count; 599 | ASSERT_EQ(v1, 123); 600 | ASSERT_EQ(v2, true); 601 | }) 602 | .ignore_result(); 603 | 604 | (mc::make_successful_future(123) && mc::make_successful_future(true) && mc::make_successful_future()) 605 | .then([call_count](int v1) { 606 | ++*call_count; 607 | ASSERT_EQ(v1, 123); 608 | }) 609 | .ignore_result(); 610 | 611 | (mc::make_successful_future(123) && mc::make_successful_future(true) && mc::make_successful_future()) 612 | .then([call_count](int v1) mutable { 613 | ++*call_count; 614 | ASSERT_EQ(v1, 123); 615 | }) 616 | .ignore_result(); 617 | 618 | 619 | (mc::make_successful_future(123) && mc::make_successful_future(true) && mc::make_successful_future()) 620 | .then([call_count] { 621 | ++*call_count; 622 | }) 623 | .ignore_result(); 624 | 625 | ASSERT_EQ(*call_count, 4); 626 | } 627 | 628 | TEST(future, can_return_composed_futures) { 629 | auto call_count = std::make_shared(); 630 | 631 | mc::make_successful_future() 632 | .then([] () -> mc::result { 633 | return mc::make_successful_future(123) && mc::make_successful_future(444); 634 | }) 635 | .then([call_count] (int i1, int i2) { 636 | ASSERT_EQ(i1, 123); 637 | ASSERT_EQ(i2, 444); 638 | ++*call_count; 639 | }) 640 | .ignore_result(); 641 | 642 | ASSERT_EQ(*call_count, 1); 643 | } 644 | 645 | TEST(future, can_take_mutable_lambdas) { 646 | mc::make_successful_future(123) 647 | .then([](int) mutable { 648 | 649 | }) 650 | .fail([] (int) mutable { 651 | return mc::failure(123); 652 | }) 653 | .ignore_result(); 654 | 655 | mc::make_successful_future("hello") 656 | .then([](std::string) mutable { 657 | 658 | }) 659 | .ignore_result(); 660 | } 661 | 662 | TEST(future, fails_can_take_auto_parameter) { 663 | mc::make_successful_future(123) 664 | .fail([] (auto error) { 665 | return mc::failure(std::move(error)); 666 | }) 667 | .ignore_result(); 668 | } 669 | 670 | TEST(future, fails_can_return_void) { 671 | mc::make_successful_future() 672 | .fail([] (auto) -> mc::result { 673 | return {}; 674 | }) 675 | .ignore_result(); 676 | } 677 | 678 | TEST(future, make_successful_future_takes_untyped_value) { 679 | mc::make_successful_future>({1, 4}) 680 | .then([] (std::vector values) { 681 | ASSERT_EQ(values.size(), 2); 682 | ASSERT_EQ(values[0], 1); 683 | ASSERT_EQ(values[1], 4); 684 | }) 685 | .ignore_result(); 686 | 687 | mc::make_successful_future("hello") 688 | .then([] (std::string value) { 689 | ASSERT_EQ(value.size(), 5); 690 | }) 691 | .ignore_result(); 692 | } 693 | 694 | TEST(future, make_successful_future_takes_copy) { 695 | std::string value = "hello"; 696 | mc::make_successful_future(value) 697 | .then([] (std::string value) { 698 | ASSERT_EQ(value.size(), 5); 699 | }) 700 | .ignore_result(); 701 | } 702 | 703 | TEST(future, make_successful_future_takes_move) { 704 | std::string value = "hello"; 705 | mc::make_successful_future(std::move(value)) 706 | .then([] (std::string value) { 707 | ASSERT_EQ(value.size(), 5); 708 | }) 709 | .ignore_result(); 710 | } 711 | 712 | TEST(future, finally) { 713 | auto call_count = std::make_shared(); 714 | 715 | { 716 | mc::future c = mc::make_successful_future("hello") 717 | .finally([call_count] (mc::concrete_result res) { 718 | ASSERT_TRUE(res.success()); 719 | ASSERT_EQ(*res.get_value(), "hello"); 720 | ++*call_count; 721 | return res; 722 | }) 723 | .finally([call_count] (auto res) { 724 | ASSERT_TRUE(res.success()); 725 | ASSERT_EQ(*res.get_value(), "hello"); 726 | ++*call_count; 727 | return res; 728 | }); 729 | } 730 | 731 | ASSERT_EQ(*call_count, 2); 732 | } 733 | 734 | TEST(future, accepts_type_without_default_constructor) { 735 | class type_without_default_ctor { 736 | public: 737 | type_without_default_ctor() = delete; 738 | type_without_default_ctor(const type_without_default_ctor&) = default; 739 | type_without_default_ctor(int) {} 740 | }; 741 | 742 | mc::make_successful_future(1234) 743 | .then([] (type_without_default_ctor) { 744 | 745 | }) 746 | .done([] (mc::concrete_result) {}); 747 | } 748 | 749 | TEST(future, captured_promise_does_not_evaluate_rest_of_chain) { 750 | // The continuation_chain destructor used to call `evaluate_into` which would evaluate the chain on 751 | // destruction. That's unexpected and we don't want that. 752 | 753 | auto called = std::make_shared(false); 754 | std::unique_ptr> captured_promise; 755 | 756 | { 757 | // A lazy future that saves its promise into `captured_promise` (which is outside current scope) 758 | auto fut = mc::future([&captured_promise] (auto promise) { 759 | captured_promise = std::make_unique>(std::move(promise)); 760 | }); 761 | 762 | std::move(fut) 763 | .then([called] {*called = true; }) 764 | .ignore_result(); // Evaluate the chain so that the promise is put in `captured_promise` 765 | 766 | ASSERT_FALSE(*called); 767 | } 768 | 769 | ASSERT_FALSE(*called); 770 | 771 | captured_promise = nullptr; 772 | 773 | ASSERT_FALSE(*called); 774 | } 775 | 776 | TEST(future, freeze_makes_future_not_evaluate) { 777 | bool called = false; 778 | 779 | { 780 | mc::future fut([&called] (auto) { 781 | called = true; 782 | }); 783 | 784 | fut.freeze(); 785 | } 786 | 787 | ASSERT_FALSE(called); 788 | } 789 | 790 | mc::future foo1() { 791 | return mc::make_successful_future(1) 792 | .then([] (int val) -> mc::result {return val + 1; }); 793 | } 794 | 795 | mc::future foo2() { 796 | return foo1() 797 | .then([] (int val) -> mc::result {return val + 1; }); 798 | } 799 | 800 | TEST(future, functions_compose) { 801 | int result = 0; 802 | 803 | foo2() 804 | .then([&] (int val) {result = val; }) 805 | .ignore_result(); 806 | 807 | ASSERT_EQ(result, 3); 808 | } 809 | --------------------------------------------------------------------------------