Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions .github/workflows/auto-clang-format.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
name: auto-clang-format
on: [pull_request]
on:
pull_request:
types: [opened, synchronize, reopened]

jobs:
build:
Expand All @@ -13,11 +15,4 @@ jobs:
exclude: './third_party ./external'
extensions: 'h,cpp,hpp'
clangFormatVersion: 19
inplace: True
- uses: EndBug/add-and-commit@v9
with:
author_name: Clang Robot
author_email: robot@example.com
message: ':art: Committing clang-format changes'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
inplace: False
17 changes: 17 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-yaml
- id: end-of-file-fixer
exclude: \.uxf$
- id: trailing-whitespace
- id: mixed-line-ending
args: [--fix=auto]


- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v17.0.4
hooks:
- id: clang-format
types_or: [c++, c]
4 changes: 2 additions & 2 deletions include/chains/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ target_sources( chains INTERFACE
BASE_DIRS ../
FILES

#chains.hpp
chains.hpp
config.hpp
#on.hpp
#segment.hpp
segment.hpp
#split.hpp
#start.hpp
#sync_wait.hpp
Expand Down
206 changes: 18 additions & 188 deletions include/chains/chains.hpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
Copyright 2026 Adobe
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
Copyright 2026 Adobe
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/

#ifndef CHAINS_CHAINS_HPP
Expand All @@ -24,8 +24,7 @@ set on the receiver.

*/

namespace chains {
inline namespace CHAINS_VERSION_NAMESPACE() {
namespace chains::inline CHAINS_VERSION_NAMESPACE() {

/*
segment is invoked with a receiver -
Expand All @@ -34,11 +33,11 @@ segment is invoked with a receiver -
template <class Receiver>
struct receiver_ref {
Receiver* _receiver;
void operator()(auto&&... args) {
auto operator()(auto&&... args) -> void {
_receiver->operator()(std::forward<decltype(args)>(args)...);
}
void set_exception(std::exception_ptr p) { _receiver->set_exception(p); }
bool canceled() const { return _receiver->canceled(); }
auto set_exception(std::exception_ptr p) -> void { _receiver->set_exception(p); }
[[nodiscard]] auto canceled() const -> bool { return _receiver->canceled(); }
};

namespace detail {
Expand Down Expand Up @@ -70,17 +69,17 @@ class chain {
static consteval auto result_type_helper(Tail&& tail,
segment<Injects, Applicator, Fs...>&& head) {
return detail::fold_over(
[]<typename Fold, typename First, typename... Rest>([[maybe_unused]] Fold fold,
First&& first, Rest&&... rest) {
[]<typename Fold, typename First, typename... Rest>(
[[maybe_unused]] Fold fold, First&& first, Rest&&... rest) -> auto {
if constexpr (sizeof...(rest) == 0) {
return [_segment = std::forward<First>(first)]<typename... Args>(
Args&&... args) mutable {
Args&&... args) mutable -> auto {
return std::move(_segment).result_type_helper(std::forward<Args>(args)...);
};
} else {
return [_segment = std::forward<First>(first).append(
fold(fold, std::forward<Rest>(rest)...))]<typename... Args>(
Args&&... args) mutable {
Args&&... args) mutable -> auto {
return std::move(_segment).result_type_helper(std::forward<Args>(args)...);
};
}
Expand All @@ -93,18 +92,18 @@ class chain {
return detail::fold_over(
[_receiver =
std::forward<R>(receiver)]<typename Fold, typename First, typename... Rest>(
[[maybe_unused]] Fold fold, First&& first, Rest&&... rest) mutable {
[[maybe_unused]] Fold fold, First&& first, Rest&&... rest) mutable -> auto {
if constexpr (sizeof...(rest) == 0) {
return [_receiver, _segment = std::forward<First>(first).append(
[_receiver]<typename V>(V&& val) {
_receiver->operator()(std::forward<V>(val));
})]<typename... Args>(Args&&... args) mutable {
})]<typename... Args>(Args&&... args) mutable -> auto {
return std::move(_segment).invoke(_receiver, std::forward<Args>(args)...);
};
} else {
return [_receiver, _segment = std::forward<First>(first).append(fold(
fold, std::forward<Rest>(rest)...))]<typename... Args>(
Args&&... args) mutable {
Args&&... args) mutable -> auto {
return std::move(_segment).invoke(_receiver, std::forward<Args>(args)...);
};
}
Expand Down Expand Up @@ -142,8 +141,8 @@ class chain {

chain(const chain&) = default;
chain(chain&&) noexcept = default;
chain& operator=(const chain&) = default;
chain& operator=(chain&&) noexcept = default;
auto operator=(const chain&) -> chain& = default;
auto operator=(chain&&) noexcept -> chain& = default;

// append function to the last sequence
template <class F>
Expand All @@ -163,17 +162,6 @@ class chain {
std::forward<Args>(args)...);
}

#if 0
template <class... Args>
[[deprecated]] auto operator()(Args&&... args) && {
using result_t = result_type<Args...>;
auto [receiver, future] =
stlab::package<result_t(result_t)>(stlab::immediate_executor, std::identity{});
invoke(std::move(receiver), std::forward<Args>(args)...);
return std::move(future);
}
#endif

template <class F>
friend auto operator|(chain&& c, F&& f) {
return std::move(c).append(std::forward<F>(f));
Expand All @@ -194,164 +182,6 @@ auto operator|(segment<Injects, Applicator, Fs...>&& head, F&& f) {
return chain{std::tuple<>{}, std::move(head).append(std::forward<F>(f))};
}

} // namespace CHAINS_VERSION_NAMESPACE()

//--------------------------------------------------------------------------------------------------

#include <stlab/concurrency/future.hpp>
#include <variant>

namespace chains::inline v1 {


} // namespace chains::inline v1

//--------------------------------------------------------------------------------------------------

#include <iostream>
#include <stlab/concurrency/default_executor.hpp>
#include <stlab/test/model.hpp>
#include <thread>

using namespace std;
using namespace chains;
using namespace stlab;

// Cancellation example


TEST_CASE("Cancellation injection", "[initial_draft]") {
{
cancellation_source src;

// Build a chain where the first function expects the token as first argument.
auto c = with_cancellation(src) | [](cancellation_token token, int x) {
if (token.canceled()) return 0;
return x * 2;
} | [](int y) { return y + 10; }; // token only needed by first step

auto f = start(std::move(c), 5);
REQUIRE(f.get_ready() == 20); // (5*2)+10

// Demonstrate cancel before start
src.cancel();
auto c2 = with_cancellation(src) | [](cancellation_token token, int x) {
if (token.canceled()) return 0;
return x * 3;
};
auto f2 = start(std::move(c2), 7);
REQUIRE(f2.get_ready() == 0);
}

//{
// cancellation_source src;

// // Build a chain where each function expects the token as first argument.
// // First function uses the token, returns an int.
// auto c = with_cancellation(src) | [](cancellation_token token, int x) {
// if (token.canceled()) return 0;
// return x * 2;
// } | [](int y) { return y + 10; }; // token only needed by first step

// auto f = start(std::move(c), 5);
// REQUIRE(f.get_ready() == 20); // (5*2)+10
//}
}

// --- Example test demonstrating split ---------------------------------------------------------
TEST_CASE("Split fan-out", "[initial_draft]") {
auto base = on(immediate_executor) | [](int a) { return a; } | [](int x) { return x + 5; };
auto splitter = split(std::move(base));
auto left = splitter.fan([](int v) { return v * 2; }) | [](int x) { return x + 1; };
auto right = splitter.fan([](int v) { return std::string("v=") + std::to_string(v); });

auto f_right = start(std::move(right), 10);
auto f_left = start(std::move(left), 5);
REQUIRE(f_right.get_ready() == std::string("v=15"));
REQUIRE(f_left.get_ready() == 31);
}

TEST_CASE("Split fan-out bound", "[initial_draft]") {
auto base = on(immediate_executor) | [](int a) { return a; } | [](int x) { return x + 5; };
} // namespace chains::inline CHAINS_VERSION_NAMESPACE()

// Bind upstream start argument 10 once:
auto splitter = split_bind(std::move(base), 10);

// Branches now start with no args; upstream result (15) is injected.
auto left = splitter.fan([](int v) { return v * 2; }) | [](int x) { return x + 1; };
auto right = splitter.fan([](int v) { return std::string("v=") + std::to_string(v); });

auto f_right = start(std::move(right)); // no argument
auto f_left = start(std::move(left)); // no argument

REQUIRE(f_right.get_ready() == std::string("v=15"));
REQUIRE(f_left.get_ready() == 31);
}

TEST_CASE("Initial draft", "[initial_draft]") {
GIVEN("a sequence of callables with different arguments") {
auto oneInt2Int = [](int a) { return a * 2; };
auto twoInt2Int = [](int a, int b) { return a + b; };
auto void2Int = []() { return 42; };

auto a0 = on(stlab::immediate_executor) | oneInt2Int | void2Int | twoInt2Int;

auto f = start(std::move(a0), 2);
REQUIRE(f.is_ready());
auto val = f.get_ready();
REQUIRE(46 == val);
}

GIVEN("a sequence of callables that just work with move only value") {
auto oneInt2Int = [](move_only a) { return move_only(a.member() * 2); };
auto twoInt2Int = [](move_only a, move_only b) {
return move_only(a.member() + b.member());
};
auto void2Int = []() { return move_only(42); };

auto a0 = on(stlab::immediate_executor) | oneInt2Int | void2Int | twoInt2Int;

auto f = start(std::move(a0), move_only(2));
REQUIRE(f.is_ready());
auto val = std::move(f).get_ready();
REQUIRE(46 == val.member());
}

GIVEN("a sequence of callables in a chain of chains synchronous") {
auto a0 = on(immediate_executor) | [](int x) { return x * 2; } | on(immediate_executor) |
[](int x) { return to_string(x); } | on(immediate_executor) |
[](const string& s) { return s + "!"; };

auto f = start(std::move(a0), 42);
auto val = f.get_ready();
REQUIRE(val == string("84!"));
}

GIVEN("a sequence of callables in a chain of chains asynchronous") {
auto a0 = on(default_executor) | [](int x) { return x * 2; } | on(immediate_executor) |
[](int x) { return to_string(x); } | on(default_executor) |
[](const string& s) { return s + "!"; };

auto val = sync_wait(std::move(a0), 42);
REQUIRE(val == string("84!"));
}
}

TEST_CASE("Cancellation of then()", "[initial_draft]") {
annotate_counters cnt;
GIVEN("that a ") {
auto fut =
async(default_executor, [] {
std::this_thread::sleep_for(std::chrono::seconds{3});
std::cout << "Future did run" << std::endl;
return std::string("42");
}).then([_counter = annotate{cnt}](const auto& s) { std::cout << s << std::endl; });

auto result_f = start(then(fut));
}
std::this_thread::sleep_for(std::chrono::seconds{5});
std::cout << cnt << std::endl;
}
} // namespace chains

#endif
#endif
Loading
Loading