diff --git a/docs/flows.adoc b/docs/flows.adoc index c2aac339..0c1ae0e9 100644 --- a/docs/flows.adoc +++ b/docs/flows.adoc @@ -44,11 +44,11 @@ Actions are added to the flow inside a component's `cib::config`. struct morning { constexpr auto config = cib::config( cib::extend( - WAKE_UP >> - selfcare::SHOWER >> - selfcare::GET_DRESSED >> - food::MAKE_COFFEE >> - food::DRINK_COFFEE)); + *WAKE_UP >> + *selfcare::SHOWER >> + *selfcare::GET_DRESSED >> + *food::MAKE_COFFEE >> + *food::DRINK_COFFEE)); }; ---- @@ -58,6 +58,17 @@ a shower. The flow library will order actions in a flow to respect these dependencies. The actions will be executed in an order that respects all given dependencies. +The `*` operator is used to explicitly add an action to the +flow. Without the `*` operator an action is just a reference. +A compile-time error will be triggered if an action is referenced without ever +being explicitly added to the flow. If an action is added under a compile-time +or runtime conditional, and the conditional is false, then it is as if the +action was never added at all. + +The behavior of the `*` operator ensures that merely referencing an +action to create an ordering dependency doesn't unintentionally add the action +to the flow. + If we only use the `morning` component in our project, the `MorningRoutine` flow graph would look like the following: @@ -93,11 +104,11 @@ struct childcare { constexpr auto config = cib::config( cib::extend( food::MAKE_COFFEE >> // this step exists in the MorningRoutine flow - PACK_SCHOOL_LUNCHES >> // new + *PACK_SCHOOL_LUNCHES >> // new food::DRINK_COFFEE >> // existing - food::MAKE_BREAKFAST >> // new - food::EAT_BREAKFAST >> // new - SEND_KIDS_TO_SCHOOL)); // new + *food::MAKE_BREAKFAST >> // new + *food::EAT_BREAKFAST >> // new + *SEND_KIDS_TO_SCHOOL)); // new }; ---- @@ -140,7 +151,7 @@ struct exercise { constexpr auto config = cib::config( cib::extend( morning::WAKE_UP >> - RIDE_STATIONARY_BIKE >> + *RIDE_STATIONARY_BIKE >> selfcare::SHOWER)); }; ---- @@ -321,7 +332,47 @@ namespace example_component { constexpr auto config = cib::config( cib::extend( // no order requirement between these actions - SOME_ACTION && SOME_OTHER_ACTION)); + *SOME_ACTION && *SOME_OTHER_ACTION)); +} +---- + +==== `operator*` + +Explicitly add an action to the flow. Actions used in flow extensions without +the `*` will be treated as references only and will not be added to the +flow at that location. It is a compilation error if an action is not added +with a `*` in exactly one location in the overall config. + +Actions can be added and ordered all at once: + +[source,cpp] +---- +namespace example_component { + constexpr auto config = cib::config( + cib::extend( + // Add both actions and create an ordering between them. + *SOME_ACTION >> *SOME_OTHER_ACTION)); +} +---- + +Actions can also be added and ordered seperately: + +[source,cpp] +---- +namespace other_component { + constexpr auto INIT_SOMETHING = ... + + constexpr auto config = cib::config( + cib::extend(*INIT_SOMETHING)); +} + +namespace example_component { + constexpr auto DO_A_THING = ... + + constexpr auto config = cib::config( + cib::extend( + other_component::INIT_SOMETHING >> + *DO_A_THING)); } ---- diff --git a/examples/flow_daily_routine/main.cpp b/examples/flow_daily_routine/main.cpp index 978d250d..7b94d43c 100644 --- a/examples/flow_daily_routine/main.cpp +++ b/examples/flow_daily_routine/main.cpp @@ -95,14 +95,10 @@ struct self_care_component_t { // Extend flow services constexpr static auto config = cib::config( - cib::extend(self_care_component_t::WAKE_UP >> - self_care_component_t::EXERCISE >> - self_care_component_t::TAKE_BATH), + cib::extend(*WAKE_UP >> *EXERCISE >> *TAKE_BATH), - cib::extend(self_care_component_t::EXERCISE >> - self_care_component_t::TAKE_BATH >> - self_care_component_t::RELAX >> - self_care_component_t::GO_TO_BED)); + cib::extend(*EXERCISE >> *TAKE_BATH >> *RELAX >> + *GO_TO_BED)); }; struct food_component_t { @@ -119,10 +115,10 @@ struct food_component_t { constexpr static auto config = cib::config( cib::extend(self_care_component_t::TAKE_BATH >> - food_component_t::BREAKFAST), + *BREAKFAST), cib::extend(self_care_component_t::RELAX >> - food_component_t::DINNER >> + *DINNER >> self_care_component_t::GO_TO_BED)); }; @@ -141,16 +137,13 @@ struct dress_up_component_t { constexpr static auto config = cib::config( cib::extend( - self_care_component_t::WAKE_UP >> - dress_up_component_t::GET_READY_FOR_EXERCISE >> + self_care_component_t::WAKE_UP >> *GET_READY_FOR_EXERCISE >> self_care_component_t::EXERCISE >> - self_care_component_t::TAKE_BATH >> - dress_up_component_t::GET_READY_TO_WORK >> + self_care_component_t::TAKE_BATH >> *GET_READY_TO_WORK >> food_component_t::BREAKFAST), - cib::extend( - dress_up_component_t::GET_READY_FOR_EXERCISE >> - self_care_component_t::EXERCISE)); + cib::extend(*GET_READY_FOR_EXERCISE >> + self_care_component_t::EXERCISE)); }; struct commute_component_t { @@ -167,11 +160,10 @@ struct commute_component_t { constexpr static auto config = cib::config( cib::extend(food_component_t::BREAKFAST >> - commute_component_t::GO_TO_OFFICE), + *GO_TO_OFFICE), cib::extend( - commute_component_t::RETURN_HOME >> - dress_up_component_t::GET_READY_FOR_EXERCISE)); + *RETURN_HOME >> dress_up_component_t::GET_READY_FOR_EXERCISE)); }; struct daily_routine_component_t { @@ -206,7 +198,7 @@ struct daily_routine_component_t { // we need to extend the MainLoop as cib::top implements // MainLoop service - cib::extend(DAILY_ROUTINES)); + cib::extend(*DAILY_ROUTINES)); }; struct person_routine_proj { diff --git a/include/cib/config.hpp b/include/cib/config.hpp index d08bb896..cd8fd7e9 100644 --- a/include/cib/config.hpp +++ b/include/cib/config.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -22,6 +23,7 @@ namespace cib { * @see cib::extend * @see cib::exports * @see cib::conditional + * @see cib::runtime_conditional */ template [[nodiscard]] CONSTEVAL auto config(Configs const &...configs) { @@ -72,4 +74,10 @@ template Configs const &...configs) { return detail::conditional{configs...}; } + +template +constexpr auto runtime_condition = [](P) { + static_assert(std::is_default_constructible_v

); + return detail::runtime_condition{}; +}; } // namespace cib diff --git a/include/cib/detail/runtime_conditional.hpp b/include/cib/detail/runtime_conditional.hpp new file mode 100644 index 00000000..29ba2f56 --- /dev/null +++ b/include/cib/detail/runtime_conditional.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace cib::detail { +namespace poison { +template +constexpr auto make_runtime_conditional(Ts &&...) = delete; +} + +template +struct runtime_conditional : config_item { + detail::config body; + + CONSTEVAL explicit runtime_conditional(Configs const &...configs) + : body{configs...} {} + + [[nodiscard]] constexpr auto extends_tuple() const { + return stdx::transform( + [](E e) { + auto args_tuple = stdx::transform( + [](Arg) { + using poison::make_runtime_conditional; + return make_runtime_conditional(Cond{}, Arg{}); + }, + e.args_tuple); + + return stdx::apply( + [](Args...) { + return extend{ + Args{}...}; + }, + args_tuple); + }, + body.extends_tuple()); + } + + [[nodiscard]] constexpr auto exports_tuple() const { + return body.exports_tuple(); + } +}; + +template // FIXME: concept for Ps +struct runtime_condition { + constexpr static auto predicates = stdx::make_tuple(Ps{}...); + + constexpr static auto ct_name = Name; + + template + [[nodiscard]] CONSTEVAL auto operator()(Configs const &...configs) const { + return detail::runtime_conditional, + Configs...>{configs...}; + } + + explicit operator bool() const { return (Ps{}() and ...); } +}; + +template +[[nodiscard]] constexpr auto +operator and(runtime_condition const &lhs, + runtime_condition const &rhs) { + if constexpr ((sizeof...(LhsPs) + sizeof...(RhsPs)) == 0) { + return runtime_condition<"always">{}; + + } else if constexpr (sizeof...(LhsPs) == 0) { + return rhs; + + } else if constexpr (sizeof...(RhsPs) == 0) { + return lhs; + + } else { + constexpr auto name = + stdx::ct_format<"{} and {}">(CX_VALUE(LhsName), CX_VALUE(RhsName)); + + return runtime_condition{}; + } +} + +using always_condition_t = runtime_condition<"always">; +constexpr auto always_condition = always_condition_t{}; +} // namespace cib::detail diff --git a/include/flow/builder.hpp b/include/flow/builder.hpp index c2048f7a..b9113640 100644 --- a/include/flow/builder.hpp +++ b/include/flow/builder.hpp @@ -9,7 +9,7 @@ namespace flow { template -using builder = graph>; +using builder = graph>; template struct service : cib::builder_meta, FunctionPtr> {}; diff --git a/include/flow/detail/par.hpp b/include/flow/detail/par.hpp index 287ac7e2..17912667 100644 --- a/include/flow/detail/par.hpp +++ b/include/flow/detail/par.hpp @@ -1,15 +1,22 @@ #pragma once #include +#include #include namespace flow::dsl { -template struct par { +template +struct par { Lhs lhs; Rhs rhs; - using is_node = void; + using is_subgraph = void; + + constexpr auto operator*() const { + return par{Lhs{}, Rhs{}}; + } private: friend constexpr auto tag_invoke(get_initials_t, par const &p) { @@ -21,7 +28,21 @@ template struct par { } friend constexpr auto tag_invoke(get_nodes_t, par const &p) { - return stdx::tuple_cat(get_nodes(p.lhs), get_nodes(p.rhs)); + if constexpr (Identity == subgraph_identity::VALUE) { + auto all_nodes = stdx::to_unsorted_set( + stdx::tuple_cat(get_all_mentioned_nodes(p.lhs), + get_all_mentioned_nodes(p.rhs))); + + return stdx::transform([](auto const &n) { return *n; }, all_nodes); + + } else { + return stdx::tuple_cat(get_nodes(p.lhs), get_nodes(p.rhs)); + } + } + + friend constexpr auto tag_invoke(get_all_mentioned_nodes_t, par const &p) { + return stdx::tuple_cat(get_all_mentioned_nodes(p.lhs), + get_all_mentioned_nodes(p.rhs)); } friend constexpr auto tag_invoke(get_edges_t, par const &p) { @@ -29,10 +50,23 @@ template struct par { } }; -template par(Lhs, Rhs) -> par; +template par(Lhs, Rhs) -> par; } // namespace flow::dsl -template +template [[nodiscard]] constexpr auto operator&&(Lhs const &lhs, Rhs const &rhs) { return flow::dsl::par{lhs, rhs}; } + +template +constexpr auto make_runtime_conditional(Cond, + flow::dsl::par) { + auto lhs = make_runtime_conditional(Cond{}, Lhs{}); + auto rhs = make_runtime_conditional(Cond{}, Rhs{}); + + using lhs_t = decltype(lhs); + using rhs_t = decltype(rhs); + + return flow::dsl::par{lhs, rhs}; +} diff --git a/include/flow/detail/seq.hpp b/include/flow/detail/seq.hpp index 0b481aa8..6494d280 100644 --- a/include/flow/detail/seq.hpp +++ b/include/flow/detail/seq.hpp @@ -1,15 +1,24 @@ #pragma once +#include #include +#include #include namespace flow::dsl { -template struct seq { +template +struct seq { Lhs lhs; Rhs rhs; - using is_node = void; + using is_subgraph = void; + + constexpr auto operator*() const { + return seq{Lhs{}, Rhs{}}; + } private: friend constexpr auto tag_invoke(get_initials_t, seq const &s) { @@ -21,7 +30,21 @@ template struct seq { } friend constexpr auto tag_invoke(get_nodes_t, seq const &s) { - return stdx::tuple_cat(get_nodes(s.lhs), get_nodes(s.rhs)); + if constexpr (Identity == subgraph_identity::VALUE) { + auto all_nodes = stdx::to_unsorted_set( + stdx::tuple_cat(get_all_mentioned_nodes(s.lhs), + get_all_mentioned_nodes(s.rhs))); + + return stdx::transform([](auto const &n) { return *n; }, all_nodes); + + } else { + return stdx::tuple_cat(get_nodes(s.lhs), get_nodes(s.rhs)); + } + } + + friend constexpr auto tag_invoke(get_all_mentioned_nodes_t, seq const &s) { + return stdx::tuple_cat(get_all_mentioned_nodes(s.lhs), + get_all_mentioned_nodes(s.rhs)); } friend constexpr auto tag_invoke(get_edges_t, seq const &s) { @@ -33,16 +56,31 @@ template struct seq { transform( [](P const &) { return edge, - stdx::tuple_element_t<1, P>>{}; + stdx::tuple_element_t<1, P>, Cond>{}; }, cartesian_product_copy(fs, is))); } }; -template seq(Lhs, Rhs) -> seq; +template seq(Lhs, Rhs) -> seq; + } // namespace flow::dsl -template +template [[nodiscard]] constexpr auto operator>>(Lhs const &lhs, Rhs const &rhs) { return flow::dsl::seq{lhs, rhs}; } + +template +constexpr auto +make_runtime_conditional(Cond, flow::dsl::seq) { + auto lhs = make_runtime_conditional(Cond{}, Lhs{}); + auto rhs = make_runtime_conditional(Cond{}, Rhs{}); + + using lhs_t = decltype(lhs); + using rhs_t = decltype(rhs); + using cond_t = decltype(EdgeCond{} and Cond{}); + + return flow::dsl::seq{lhs, rhs}; +} diff --git a/include/flow/detail/walk.hpp b/include/flow/detail/walk.hpp index 2348b4b1..596158b8 100644 --- a/include/flow/detail/walk.hpp +++ b/include/flow/detail/walk.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -8,15 +10,17 @@ namespace flow::dsl { template -concept node = requires { typename stdx::remove_cvref_t::is_node; }; +concept subgraph = requires { typename stdx::remove_cvref_t::is_subgraph; }; -template struct edge { +template struct edge { using source_t = Source; using dest_t = Dest; + using cond_t = Cond; }; constexpr inline class get_initials_t { - template friend constexpr auto tag_invoke(get_initials_t, N &&n) { + template + friend constexpr auto tag_invoke(get_initials_t, N &&n) { return stdx::make_tuple(std::forward(n)); } @@ -31,7 +35,8 @@ constexpr inline class get_initials_t { } get_initials{}; constexpr inline class get_finals_t { - template friend constexpr auto tag_invoke(get_finals_t, N &&n) { + template + friend constexpr auto tag_invoke(get_finals_t, N &&n) { return stdx::make_tuple(std::forward(n)); } @@ -46,8 +51,13 @@ constexpr inline class get_finals_t { } get_finals{}; constexpr inline class get_nodes_t { - template friend constexpr auto tag_invoke(get_nodes_t, N &&n) { - return stdx::make_tuple(std::forward(n)); + template friend constexpr auto tag_invoke(get_nodes_t, N &&n) { + if constexpr (std::remove_cvref_t::identity == + subgraph_identity::REFERENCE) { + return stdx::tuple{}; + } else { + return stdx::make_tuple(std::forward(n)); + } } public: @@ -60,8 +70,24 @@ constexpr inline class get_nodes_t { } } get_nodes{}; +constexpr inline class get_all_mentioned_nodes_t { + template + friend constexpr auto tag_invoke(get_all_mentioned_nodes_t, N &&n) { + return stdx::make_tuple(std::forward(n)); + } + + public: + template + constexpr auto operator()(Ts &&...ts) const + noexcept(noexcept(tag_invoke(std::declval(), + std::forward(ts)...))) + -> decltype(tag_invoke(*this, std::forward(ts)...)) { + return tag_invoke(*this, std::forward(ts)...); + } +} get_all_mentioned_nodes{}; + constexpr inline class get_edges_t { - friend constexpr auto tag_invoke(get_edges_t, node auto const &) { + friend constexpr auto tag_invoke(get_edges_t, subgraph auto const &) { return stdx::tuple{}; } diff --git a/include/flow/graph_builder.hpp b/include/flow/graph_builder.hpp index 7bde6491..22fadadf 100644 --- a/include/flow/graph_builder.hpp +++ b/include/flow/graph_builder.hpp @@ -4,12 +4,16 @@ #include #include +#include #include #include #include #include #include #include +#include + +#include #include #include @@ -20,14 +24,21 @@ #include namespace flow { +namespace detail { +template using is_duplicated = std::bool_constant<(T::size() > 1)>; +} +template using name_for = typename T::name_t; + [[nodiscard]] constexpr auto edge_size(auto const &nodes, auto const &edges) -> std::size_t { auto const edge_capacities = transform( [&](N const &) { return edges.fold_left( std::size_t{}, [](auto acc, E const &) { - if constexpr (std::is_same_v or - std::is_same_v) { + if constexpr (std::is_same_v, + name_for> or + std::is_same_v, + name_for>) { return ++acc; } else { return acc; @@ -41,10 +52,31 @@ namespace flow { }); } -template