Skip to content

Commit bc764fe

Browse files
committed
Transition and guard with context
1 parent d2c8682 commit bc764fe

12 files changed

Lines changed: 266 additions & 119 deletions

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ add_executable(export_dot_example examples/export_dot.cpp)
1818
target_include_directories(export_dot_example PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
1919
add_executable(export_dot_nested_example examples/export_dot_nested.cpp)
2020
target_include_directories(export_dot_nested_example PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
21+
add_executable(export_dot_context_example examples/export_dot_context.cpp)
22+
target_include_directories(export_dot_context_example PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
23+
24+
add_executable(context_example examples/context_example.cpp)
25+
target_include_directories(context_example PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
2126

2227
# GoogleTest setup
2328
include(FetchContent)

examples/basic.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ int main()
1010
auto machine = Builder()
1111
.initial("Idle")
1212
.state("Idle", [](State &s)
13-
{ s.on("Start", "Running", nullptr, []()
13+
{ s.on("Start", "Running", nullptr, [](const std::any &)
1414
{ std::cout << "Transition: Idle -> Running" << std::endl; }); })
1515
.state("Running", [](State &s)
1616
{
17-
s.on("Pause", "Paused", nullptr, []() {
17+
s.on("Pause", "Paused", nullptr, [](const std::any &) {
1818
std::cout << "Transition: Running -> Paused" << std::endl;
1919
});
20-
s.on("Stop", "Idle", nullptr, []() {
20+
s.on("Stop", "Idle", nullptr, [](const std::any &) {
2121
std::cout << "Transition: Running -> Idle" << std::endl;
2222
}); })
2323
.state("Paused", [](State &s)
24-
{ s.on("Resume", "Running", nullptr, []()
24+
{ s.on("Resume", "Running", nullptr, [](const std::any &)
2525
{ std::cout << "Transition: Paused -> Running" << std::endl; }); })
2626
.build();
2727

examples/context_example.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include "CXXStateTree/Builder.hpp"
2+
#include <iostream>
3+
#include <string>
4+
#include <any>
5+
6+
using namespace CXXStateTree;
7+
8+
// A custom guard that checks if the context contains an authorized user
9+
class UserAuthorizedGuard : public IGuard
10+
{
11+
public:
12+
bool evaluate(const std::any &context) const override
13+
{
14+
if (context.type() == typeid(std::string))
15+
{
16+
std::string user = std::any_cast<std::string>(context);
17+
return user == "admin";
18+
}
19+
return false;
20+
}
21+
};
22+
23+
int main()
24+
{
25+
UserAuthorizedGuard auth_guard;
26+
27+
auto sm = Builder()
28+
.initial("Idle")
29+
.state("Idle", [&](State &s)
30+
{ s.on("login", "Dashboard", &auth_guard, [](const std::any &ctx)
31+
{ std::cout << "Login accepted for user: " << std::any_cast<std::string>(ctx) << "\n"; }); })
32+
.state("Dashboard", [&](State &s)
33+
{ s.on("logout", "Idle", nullptr, [](const std::any &ctx)
34+
{ std::cout << "User logged out.\n"; }); })
35+
.build();
36+
37+
std::cout << "Sending login with unauthorized user:\n";
38+
sm.send("login", std::string("guest")); // Guard will reject
39+
40+
std::cout << "Sending login with admin:\n";
41+
sm.send("login", std::string("admin")); // Guard will accept
42+
43+
std::cout << "Sending logout:\n";
44+
sm.send("logout");
45+
}

examples/export_dot_context.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#include "CXXStateTree/Builder.hpp"
2+
#include "CXXStateTree/StateTree.hpp"
3+
#include <iostream>
4+
#include <fstream>
5+
6+
using namespace CXXStateTree;
7+
// A custom guard that checks if the context contains an authorized user
8+
class UserAuthorizedGuard : public IGuard
9+
{
10+
public:
11+
bool evaluate(const std::any &context) const override
12+
{
13+
if (context.type() == typeid(std::string))
14+
{
15+
std::string user = std::any_cast<std::string>(context);
16+
return user == "admin";
17+
}
18+
return false;
19+
}
20+
};
21+
int main()
22+
{
23+
UserAuthorizedGuard auth_guard;
24+
auto tree = Builder()
25+
.initial("Idle")
26+
.state("Idle", [&](State &s)
27+
{ s.on("login", "Dashboard", &auth_guard, [](const std::any &ctx)
28+
{ std::cout << "Login accepted for user: " << std::any_cast<std::string>(ctx) << "\n"; }); })
29+
.state("Dashboard", [&](State &s)
30+
{ s.on("logout", "Idle", nullptr, [](const std::any &ctx)
31+
{ std::cout << "User logged out.\n"; }); })
32+
.build();
33+
34+
// Export to DOT format and print
35+
std::string dot = tree.export_dot();
36+
std::cout << dot;
37+
38+
// Optionally write to file
39+
std::ofstream("example_state_tree.dot") << dot;
40+
41+
return 0;
42+
}

examples/export_dot_nested.cpp

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,39 @@ using namespace CXXStateTree;
77

88
int main()
99
{
10-
auto tree = Builder()
11-
.initial("Main")
12-
.state("Main", [](State &s)
13-
{ s.initial_substate("Idle")
14-
.substate("Idle", [](State &s)
15-
{ s.on("Start", "Running", nullptr, []()
16-
{ std::cout << "Transition: Idle -> Running" << std::endl; }); })
17-
18-
.substate("Running", [](State &s)
19-
{ s.on("Stop", "Idle", nullptr, []()
20-
{ std::cout << "Transition: Running -> idle" << std::endl; }); })
21-
22-
.on("Switch", "Alternate", nullptr, []()
23-
{ std::cout << "Transition: Main -> Alternate" << std::endl; }); })
24-
.state("Alternate", [](State &s)
25-
{ s.initial_substate("Idle")
26-
.substate("Idle", [](State &s)
27-
{ s.on("Start", "Running", nullptr, []()
28-
{ std::cout << "Transition: Idle -> Running" << std::endl; }); })
29-
30-
.substate("Running", [](State &s)
31-
{ s.on("Stop", "Idle", nullptr, []()
32-
{ std::cout << "Transition: Running -> idle" << std::endl; }); })
33-
.on("Switch", "Main", nullptr, []()
34-
{ std::cout << "Transition: Alternate -> Main" << std::endl; }); })
35-
.build();
36-
37-
// Export to DOT format and print
38-
std::string dot = tree.export_dot();
39-
std::cout << dot;
40-
41-
// Optionally write to file
42-
std::ofstream("example_state_tree_nested.dot") << dot;
43-
44-
return 0;
10+
auto tree = Builder()
11+
.initial("Main")
12+
.state("Main", [](State &s)
13+
{ s.initial_substate("Idle")
14+
.substate("Idle", [](State &s)
15+
{ s.on("Start", "Running", nullptr, [](const std::any &)
16+
{ std::cout << "Transition: Idle -> Running" << std::endl; }); })
17+
18+
.substate("Running", [](State &s)
19+
{ s.on("Stop", "Idle", nullptr, [](const std::any &)
20+
{ std::cout << "Transition: Running -> idle" << std::endl; }); })
21+
22+
.on("Switch", "Alternate", nullptr, [](const std::any &)
23+
{ std::cout << "Transition: Main -> Alternate" << std::endl; }); })
24+
.state("Alternate", [](State &s)
25+
{ s.initial_substate("Idle")
26+
.substate("Idle", [](State &s)
27+
{ s.on("Start", "Running", nullptr, [](const std::any &)
28+
{ std::cout << "Transition: Idle -> Running" << std::endl; }); })
29+
30+
.substate("Running", [](State &s)
31+
{ s.on("Stop", "Idle", nullptr, [](const std::any &)
32+
{ std::cout << "Transition: Running -> idle" << std::endl; }); })
33+
.on("Switch", "Main", nullptr, [](const std::any &)
34+
{ std::cout << "Transition: Alternate -> Main" << std::endl; }); })
35+
.build();
36+
37+
// Export to DOT format and print
38+
std::string dot = tree.export_dot();
39+
std::cout << dot;
40+
41+
// Optionally write to file
42+
std::ofstream("example_state_tree_nested.dot") << dot;
43+
44+
return 0;
4545
}

examples/nested.cpp

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,44 @@ using namespace CXXStateTree;
66

77
int main()
88
{
9-
auto machine = Builder()
10-
.initial("Main")
11-
.state("Main", [](State &s)
12-
{ s.initial_substate("Idle")
13-
.substate("Idle", [](State &s)
14-
{ s.on("Start", "Running", nullptr, []()
15-
{ std::cout << "Transition: Idle -> Running" << std::endl; }); })
9+
auto machine = Builder()
10+
.initial("Main")
11+
.state("Main", [](State &s)
12+
{ s.initial_substate("Idle")
13+
.substate("Idle", [](State &s)
14+
{ s.on("Start", "Running", nullptr, [](const std::any &)
15+
{ std::cout << "Transition: Idle -> Running" << std::endl; }); })
1616

17-
.substate("Running", [](State &s)
18-
{ s.on("Stop", "Idle", nullptr, []()
19-
{ std::cout << "Transition: Running -> idle" << std::endl; }); })
17+
.substate("Running", [](State &s)
18+
{ s.on("Stop", "Idle", nullptr, [](const std::any &)
19+
{ std::cout << "Transition: Running -> idle" << std::endl; }); })
2020

21-
.on("Switch", "Alternate", nullptr, []()
22-
{ std::cout << "Transition: Main -> Alternate" << std::endl; }); })
23-
.state("Alternate", [](State &s)
24-
{ s.initial_substate("Idle")
25-
.substate("Idle", [](State &s)
26-
{ s.on("Start", "Running", nullptr, []()
27-
{ std::cout << "Transition: Idle -> Running" << std::endl; }); })
21+
.on("Switch", "Alternate", nullptr, [](const std::any &)
22+
{ std::cout << "Transition: Main -> Alternate" << std::endl; }); })
23+
.state("Alternate", [](State &s)
24+
{ s.initial_substate("Idle")
25+
.substate("Idle", [](State &s)
26+
{ s.on("Start", "Running", nullptr, [](const std::any &)
27+
{ std::cout << "Transition: Idle -> Running" << std::endl; }); })
2828

29-
.substate("Running", [](State &s)
30-
{ s.on("Stop", "Idle", nullptr, []()
31-
{ std::cout << "Transition: Running -> idle" << std::endl; }); })
32-
.on("Switch", "Main", nullptr, []()
33-
{ std::cout << "Transition: Alternate -> Main" << std::endl; }); })
34-
.build();
29+
.substate("Running", [](State &s)
30+
{ s.on("Stop", "Idle", nullptr, [](const std::any &)
31+
{ std::cout << "Transition: Running -> idle" << std::endl; }); })
32+
.on("Switch", "Main", nullptr, [](const std::any &)
33+
{ std::cout << "Transition: Alternate -> Main" << std::endl; }); })
34+
.build();
3535

36-
std::cout << "Initial state: " << machine.current_state().fullName() << std::endl;
37-
machine.send("Start");
38-
std::cout << "Current state: " << machine.current_state().fullName() << std::endl;
39-
machine.send("Stop");
40-
std::cout << "Current state: " << machine.current_state().fullName() << std::endl;
41-
machine.send("Switch");
42-
std::cout << "Current state: " << machine.current_state().fullName() << std::endl;
43-
machine.send("Start");
44-
std::cout << "Current state: " << machine.current_state().fullName() << std::endl;
45-
machine.send("Stop");
46-
std::cout << "Current state: " << machine.current_state().fullName() << std::endl;
36+
std::cout << "Initial state: " << machine.current_state().fullName() << std::endl;
37+
machine.send("Start");
38+
std::cout << "Current state: " << machine.current_state().fullName() << std::endl;
39+
machine.send("Stop");
40+
std::cout << "Current state: " << machine.current_state().fullName() << std::endl;
41+
machine.send("Switch");
42+
std::cout << "Current state: " << machine.current_state().fullName() << std::endl;
43+
machine.send("Start");
44+
std::cout << "Current state: " << machine.current_state().fullName() << std::endl;
45+
machine.send("Stop");
46+
std::cout << "Current state: " << machine.current_state().fullName() << std::endl;
4747

48-
return 0;
48+
return 0;
4949
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <optional>
5+
#include <functional>
6+
#include <any>
7+
8+
namespace CXXStateTree
9+
{
10+
11+
template <typename T>
12+
const T &get_context(const std::any &ctx)
13+
{
14+
if (!ctx.has_value())
15+
throw std::bad_any_cast();
16+
17+
return std::any_cast<const T &>(ctx);
18+
}
19+
20+
template <typename T>
21+
T get_context_or(const std::any &ctx, const T &default_value)
22+
{
23+
if (ctx.type() == typeid(T))
24+
{
25+
return std::any_cast<T>(ctx);
26+
}
27+
return default_value;
28+
}
29+
30+
} // namespace CXXStateTree

include/CXXStateTree/IGuard.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <optional>
5+
#include <functional>
6+
#include <any>
7+
8+
namespace CXXStateTree
9+
{
10+
11+
class IGuard
12+
{
13+
public:
14+
virtual ~IGuard() = default;
15+
virtual bool evaluate(const std::any &context) const = 0;
16+
};
17+
18+
} // namespace CXXStateTree

include/CXXStateTree/State.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace CXXStateTree
1212
explicit State(std::string name, State *parent = nullptr) : name_(std::move(name)), parent_(parent) {}
1313

1414
State &on(const std::string &event, const std::string &target,
15-
Guard guard = nullptr, Action action = nullptr)
15+
IGuard *guard = nullptr, Action action = nullptr)
1616
{
1717
transitions_[event] = {std::move(target), guard, std::move(action)};
1818
return *this;
@@ -77,11 +77,12 @@ namespace CXXStateTree
7777
return nullptr;
7878
}
7979

80-
void collect_transitions(std::vector<std::tuple<std::string, std::string, std::string>> &all_transitions, const std::string &full_name, const std::string &base_name) const
80+
void collect_transitions(std::vector<std::tuple<std::string, std::string, std::string, bool>> &all_transitions, const std::string &full_name, const std::string &base_name) const
8181
{
8282
for (const auto &[event, t] : transitions_)
8383
{
84-
all_transitions.emplace_back(full_name, base_name != "" ? base_name + "." + t.target : t.target, event);
84+
bool has_guard = t.guard != nullptr;
85+
all_transitions.emplace_back(full_name, base_name != "" ? base_name + "." + t.target : t.target, event, has_guard);
8586
}
8687
for (const auto &sub : substates_)
8788
{

0 commit comments

Comments
 (0)