Skip to content

Commit

Permalink
Merge pull request #511 from bdeane-intel/update-flow-builder-docs
Browse files Browse the repository at this point in the history
Update flow builder docs
  • Loading branch information
elbeno authored Mar 14, 2024
2 parents a4f277e + 03ec58a commit f2c67e0
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 27 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ jobs:

- name: Test
working-directory: ${{github.workspace}}/build
run: ctest -j $(nproc) -C ${{matrix.build_type}}
run: ctest --output-on-failure -j $(nproc) -C ${{matrix.build_type}}

quality_checks_pass:
runs-on: ${{ github.repository_owner == 'intel' && 'intel-' || '' }}ubuntu-22.04
Expand Down Expand Up @@ -218,7 +218,7 @@ jobs:
- name: Test
working-directory: ${{github.workspace}}/build
run: |
ctest -j $(nproc) -E EXPECT_FAIL -T memcheck
ctest --output-on-failure -j $(nproc) -E "EXPECT_FAIL|PYTHON" -T memcheck
LOGFILE=$(ls ./Testing/Temporary/LastDynamicAnalysis_*.log)
FAILSIZE=$(du -c ./Testing/Temporary/MemoryChecker.* | tail -1 | cut -f1)
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ add_versioned_package(
NAME
fmt
GIT_TAG
10.2.0
10.2.1
GITHUB_REPOSITORY
fmtlib/fmt
OPTIONS
Expand Down
56 changes: 56 additions & 0 deletions docs/flows.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,59 @@ namespace example_component {
SOME_ACTION && SOME_OTHER_ACTION));
}
----

=== Alternative flow builders

The default flow service uses a graph builder that outputs the flow steps as an
array of function pointers. Traversing the array and calling those functions
ensures the correct relative ordering of flow steps in the graph, and this is
what happens by default when we run the flow.

[source,cpp]
----
// the default flow builder and service
template <stdx::ct_string Name = "">
using builder = flow::graph<Name, flow::graph_builder<impl>>;
template <stdx::ct_string Name = "">
struct service : cib::builder_meta<builder<Name>, flow::FunctionPtr> {};
// declare a flow service
struct MorningRoutine : public service<"MorningRoutine"> {};
// add steps, etc, then at runtime, run the flow:
nexus.service<"MorningRoutine">();
----

Here `graph_builder` is the type that renders the flow description into the
array of function pointers, and `flow::FunctionPtr` is the type-erased interface
(here a function taking and returning `void`) that is called to run a flow.

But given a flow, other renderings are possible.

[source,cpp]
----
// a flow builder and service that produces a graphviz rendering
template <stdx::ct_string Name = "">
using viz_builder = flow::graph<Name, flow::graphviz_builder>;
template <stdx::ct_string Name = "">
struct viz_service : cib::builder_meta<builder<Name>, flow::VizFunctionPtr> {};
----

Here, `viz_service` will produce a graphviz rendering of a flow using the
`graphviz_builder`. `flow::VizFunctionPtr` is the type-erased interface once
more, and it is defined to take `void` and return a `std::string`. When we "run"
the flow, we get the graphviz rendering.

[source,cpp]
----
// instead of the default flow::service, use the viz_service
struct MorningRoutine : public viz_service<"MorningRoutine"> {};
// add steps, etc, as before
// this time, when we "run" the flow, we get a string representing the graphviz rendering
auto graphviz_str = nexus.service<"MorningRoutine">();
----

`graphviz_builder` is available as a debugging aid. But in general, having the
flow rendering separate from the flow definition enables any kind of rendering
with correponding runtime behaviour.
22 changes: 22 additions & 0 deletions include/flow/graphviz_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <string_view>

namespace flow {
using VizFunctionPtr = auto (*)() -> std::string;

struct graphviz_builder {
template <typename Graph>
[[nodiscard]] static auto build(Graph const &input) {
Expand Down Expand Up @@ -49,5 +51,25 @@ struct graphviz_builder {
output += "}";
return output;
}

template <typename Initialized> class built_flow {
static auto built() {
auto const v = Initialized::value;
return build(v);
}

static auto run() -> std::string { return built(); }

public:
// NOLINTNEXTLINE(google-explicit-constructor)
constexpr operator VizFunctionPtr() const { return run; }
auto operator()() const -> std::string { return run(); }
constexpr static bool active = true;
};

template <typename Initialized>
[[nodiscard]] constexpr static auto render() -> built_flow<Initialized> {
return {};
}
};
} // namespace flow
26 changes: 26 additions & 0 deletions test/flow/flow.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include <cib/cib.hpp>
#include <cib/func_decl.hpp>
#include <flow/flow.hpp>
#include <flow/graphviz_builder.hpp>
#include <log/fmt/logger.hpp>

#include <catch2/catch_test_macros.hpp>
Expand Down Expand Up @@ -147,3 +148,28 @@ TEST_CASE("named flow logs milestones", "[flow]") {
nexus.service<NamedTestFlow>();
CHECK(log_buffer.find("flow.milestone(ms)") != std::string::npos);
}

namespace {
template <stdx::ct_string Name = "">
using alt_builder = flow::graph<Name, flow::graphviz_builder>;
template <stdx::ct_string Name = "">
struct alt_flow_service
: cib::builder_meta<alt_builder<Name>, flow::VizFunctionPtr> {};

struct VizFlow : public alt_flow_service<"debug"> {};
struct VizConfig {
constexpr static auto config =
cib::config(cib::exports<VizFlow>, cib::extend<VizFlow>(a));
};
} // namespace

TEST_CASE("vizualize flow", "[flow]") {
cib::nexus<VizConfig> nexus{};
auto viz = nexus.service<VizFlow>();
auto expected = std::string{
R"__debug__(digraph debug {
start -> a
a -> end
})__debug__"};
CHECK(viz == expected);
}
12 changes: 6 additions & 6 deletions test/flow/graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,32 @@ constexpr auto b = flow::action<"b">([] {});
constexpr auto c = flow::action<"c">([] {});
} // namespace

TEST_CASE("node size (empty graph)", "[flow]") {
TEST_CASE("node size (empty graph)", "[graph]") {
constexpr auto g = flow::graph<>{};
static_assert(node_size(g) == 0);
}

TEST_CASE("node size (single action)", "[flow]") {
TEST_CASE("node size (single action)", "[graph]") {
constexpr auto g = flow::graph<>{}.add(a >> b);
static_assert(node_size(g) == 2);
}

TEST_CASE("node size (overlapping actions)", "[flow]") {
TEST_CASE("node size (overlapping actions)", "[graph]") {
constexpr auto g = flow::graph<>{}.add(a >> b).add(b >> c);
static_assert(node_size(g) == 3);
}

TEST_CASE("edge size (empty flow)", "[flow]") {
TEST_CASE("edge size (empty flow)", "[graph]") {
constexpr auto g = flow::graph<>{};
static_assert(edge_size(g) == 1);
}

TEST_CASE("edge size (single action)", "[flow]") {
TEST_CASE("edge size (single action)", "[graph]") {
constexpr auto g = flow::graph<>{}.add(a >> b);
static_assert(edge_size(g) == 1);
}

TEST_CASE("edge size (overlapping actions)", "[flow]") {
TEST_CASE("edge size (overlapping actions)", "[graph]") {
constexpr auto g = flow::graph<>{}.add(a >> b).add(b >> c);
static_assert(edge_size(g) == 2);
}
37 changes: 19 additions & 18 deletions test/flow/graph_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ constexpr auto d = flow::action<"d">([] { actual += "d"; });
using builder = flow::graph_builder<flow::impl>;
} // namespace

TEST_CASE("build and run empty flow", "[flow]") {
TEST_CASE("build and run empty flow", "[graph_builder]") {
auto g = flow::graph<>{};
auto const flow = builder::build(g);
REQUIRE(flow.has_value());
flow.value()();
}

TEST_CASE("add single action", "[flow]") {
TEST_CASE("add single action", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add(a);
auto const flow = builder::build(g);
Expand All @@ -35,7 +35,7 @@ TEST_CASE("add single action", "[flow]") {
CHECK(actual == "a");
}

TEST_CASE("two milestone linear before dependency", "[flow]") {
TEST_CASE("two milestone linear before dependency", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add(a >> milestone0);
auto const flow = builder::build(g);
Expand All @@ -44,7 +44,7 @@ TEST_CASE("two milestone linear before dependency", "[flow]") {
CHECK(actual == "a");
}

TEST_CASE("actions get executed once", "[flow]") {
TEST_CASE("actions get executed once", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}
.add(a >> milestone0)
Expand All @@ -55,7 +55,7 @@ TEST_CASE("actions get executed once", "[flow]") {
CHECK(actual == "a");
}

TEST_CASE("two milestone linear after dependency", "[flow]") {
TEST_CASE("two milestone linear after dependency", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}
.add(a >> milestone0)
Expand All @@ -66,31 +66,32 @@ TEST_CASE("two milestone linear after dependency", "[flow]") {
CHECK(actual == "ab");
}

TEST_CASE("three milestone linear before and after dependency", "[flow]") {
TEST_CASE("three milestone linear before and after dependency",
"[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add(a >> b >> c);
auto const flow = builder::build(g);
flow.value()();
CHECK(actual == "abc");
}

TEST_CASE("just two actions in order", "[flow]") {
TEST_CASE("just two actions in order", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add(a >> b);
auto const flow = builder::build(g);
flow.value()();
CHECK(actual == "ab");
}

TEST_CASE("insert action between two actions", "[flow]") {
TEST_CASE("insert action between two actions", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add(a >> c).add(a >> b >> c);
auto const flow = builder::build(g);
flow.value()();
CHECK(actual == "abc");
}

TEST_CASE("add single parallel 2", "[flow]") {
TEST_CASE("add single parallel 2", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add(a && b);
auto const flow = builder::build(g);
Expand All @@ -101,7 +102,7 @@ TEST_CASE("add single parallel 2", "[flow]") {
CHECK(actual.size() == 2);
}

TEST_CASE("add single parallel 3", "[flow]") {
TEST_CASE("add single parallel 3", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add(a && b && c);
auto const flow = builder::build(g);
Expand All @@ -113,7 +114,7 @@ TEST_CASE("add single parallel 3", "[flow]") {
CHECK(actual.size() == 3);
}

TEST_CASE("add single parallel 3 with later dependency 1", "[flow]") {
TEST_CASE("add single parallel 3 with later dependency 1", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add(a && b && c).add(c >> a);
auto const flow = builder::build(g);
Expand All @@ -126,7 +127,7 @@ TEST_CASE("add single parallel 3 with later dependency 1", "[flow]") {
CHECK(actual.size() == 3);
}

TEST_CASE("add single parallel 3 with later dependency 2", "[flow]") {
TEST_CASE("add single parallel 3 with later dependency 2", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add(a && b && c).add(a >> c);
auto const flow = builder::build(g);
Expand All @@ -139,7 +140,7 @@ TEST_CASE("add single parallel 3 with later dependency 2", "[flow]") {
CHECK(actual.size() == 3);
}

TEST_CASE("add parallel rhs", "[flow]") {
TEST_CASE("add parallel rhs", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add(a >> (b && c));
auto const flow = builder::build(g);
Expand All @@ -153,7 +154,7 @@ TEST_CASE("add parallel rhs", "[flow]") {
CHECK(actual.size() == 3);
}

TEST_CASE("add parallel lhs", "[flow]") {
TEST_CASE("add parallel lhs", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add((a && b) >> c);
auto const flow = builder::build(g);
Expand All @@ -167,7 +168,7 @@ TEST_CASE("add parallel lhs", "[flow]") {
CHECK(actual.size() == 3);
}

TEST_CASE("add parallel in the middle", "[flow]") {
TEST_CASE("add parallel in the middle", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add(a >> (b && c) >> d);
auto const flow = builder::build(g);
Expand All @@ -187,7 +188,7 @@ TEST_CASE("add parallel in the middle", "[flow]") {
CHECK(actual.size() == 4);
}

TEST_CASE("add dependency lhs", "[flow]") {
TEST_CASE("add dependency lhs", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add((a >> b) && c);
auto const flow = builder::build(g);
Expand All @@ -202,7 +203,7 @@ TEST_CASE("add dependency lhs", "[flow]") {
CHECK(actual.size() == 3);
}

TEST_CASE("add dependency rhs", "[flow]") {
TEST_CASE("add dependency rhs", "[graph_builder]") {
actual.clear();
auto g = flow::graph<>{}.add(a && (b >> c));
auto const flow = builder::build(g);
Expand All @@ -217,7 +218,7 @@ TEST_CASE("add dependency rhs", "[flow]") {
CHECK(actual.size() == 3);
}

TEST_CASE("alternate builder", "[flow]") {
TEST_CASE("alternate builder", "[graph_builder]") {
using alt_builder = flow::graphviz_builder;
auto g = flow::graph<"debug">{}.add(a && (b >> c));
auto const flow = alt_builder::build(g);
Expand Down

0 comments on commit f2c67e0

Please sign in to comment.