Skip to content

Commit 6a6fe88

Browse files
committed
Added Nested State ( missing tests )
1 parent df814ab commit 6a6fe88

9 files changed

Lines changed: 268 additions & 77 deletions

File tree

.vscode/settings.json

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,27 @@
4747
"streambuf": "cpp",
4848
"text_encoding": "cpp",
4949
"typeinfo": "cpp",
50-
"variant": "cpp"
50+
"variant": "cpp",
51+
"any": "cpp",
52+
"chrono": "cpp",
53+
"condition_variable": "cpp",
54+
"csignal": "cpp",
55+
"cstring": "cpp",
56+
"forward_list": "cpp",
57+
"list": "cpp",
58+
"map": "cpp",
59+
"set": "cpp",
60+
"unordered_set": "cpp",
61+
"ratio": "cpp",
62+
"fstream": "cpp",
63+
"iomanip": "cpp",
64+
"iostream": "cpp",
65+
"istream": "cpp",
66+
"mutex": "cpp",
67+
"semaphore": "cpp",
68+
"sstream": "cpp",
69+
"stop_token": "cpp",
70+
"thread": "cpp",
71+
"cinttypes": "cpp"
5172
}
5273
}

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ target_include_directories(CXXStateTree INTERFACE include)
1010
add_executable(basic examples/basic.cpp)
1111
target_link_libraries(basic PRIVATE CXXStateTree)
1212

13+
14+
add_executable(nested examples/nested.cpp)
15+
target_link_libraries(nested PRIVATE CXXStateTree)
16+
1317
# GoogleTest setup
1418
include(FetchContent)
1519
FetchContent_Declare(

examples/basic.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ int main()
2525
{ std::cout << "Transition: Paused -> Running" << std::endl; }); })
2626
.build();
2727

28-
std::cout << "Initial state: " << machine.current_state() << std::endl;
28+
std::cout << "Initial state: " << machine.current_state().name() << std::endl;
2929
machine.send("Start");
30-
std::cout << "Current state: " << machine.current_state() << std::endl;
30+
std::cout << "Current state: " << machine.current_state().name() << std::endl;
3131
machine.send("Pause");
32-
std::cout << "Current state: " << machine.current_state() << std::endl;
32+
std::cout << "Current state: " << machine.current_state().name() << std::endl;
3333
machine.send("Resume");
34-
std::cout << "Current state: " << machine.current_state() << std::endl;
34+
std::cout << "Current state: " << machine.current_state().name() << std::endl;
3535
machine.send("Stop");
36-
std::cout << "Current state: " << machine.current_state() << std::endl;
36+
std::cout << "Current state: " << machine.current_state().name() << std::endl;
3737

3838
return 0;
3939
}

examples/nested.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// File: examples/basic.cpp
2+
#include <iostream>
3+
#include "CXXStateTree/Builder.hpp"
4+
5+
using namespace CXXStateTree;
6+
7+
int main()
8+
{
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; }); })
16+
17+
.substate("Running", [](State &s)
18+
{ s.on("Stop", "Idle", nullptr, []()
19+
{ std::cout << "Transition: Running -> idle" << std::endl; }); })
20+
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; }); })
28+
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();
35+
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;
47+
48+
return 0;
49+
}

include/CXXStateTree/Builder.hpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <list>
34
#include "StateTree.hpp"
45

56
namespace CXXStateTree
@@ -16,23 +17,25 @@ namespace CXXStateTree
1617

1718
Builder &state(const std::string &name, std::function<void(State &)> config)
1819
{
19-
State s(name);
20-
config(s);
21-
states_.insert({name, std::move(s)});
20+
states_.emplace_back(name);
21+
config(states_.back());
2222
return *this;
2323
}
2424

2525
StateTree build()
2626
{
27-
if (initial_state_.empty())
27+
StateTree tree;
28+
tree.states_ = std::move(states_);
29+
tree.current_ = tree.find_state(initial_state_);
30+
if (tree.current_ && tree.current_->initial_substate())
2831
{
29-
throw std::runtime_error("Initial state not set");
32+
tree.current_ = tree.current_->find_substate(*tree.current_->initial_substate());
3033
}
31-
return StateTree(states_, initial_state_);
34+
return tree;
3235
}
3336

3437
private:
35-
std::unordered_map<std::string, State> states_;
38+
std::list<State> states_;
3639
std::string initial_state_;
3740
};
3841
} // namespace CXXStateTree

include/CXXStateTree/State.hpp

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <list>
34
#include "Transition.hpp"
45

56
namespace CXXStateTree
@@ -8,26 +9,66 @@ namespace CXXStateTree
89
class State
910
{
1011
public:
11-
explicit State(std::string name) : name_(std::move(name)) {}
12+
explicit State(std::string name, State *parent = nullptr) : name_(std::move(name)), parent_(parent) {}
1213

13-
void on(const std::string &event, const std::string &target,
14-
Guard guard = nullptr, Action action = nullptr)
14+
State &on(const std::string &event, const std::string &target,
15+
Guard guard = nullptr, Action action = nullptr)
1516
{
16-
transitions_[event] = Transition{target, guard, action};
17+
transitions_[event] = {std::move(target), guard, std::move(action)};
18+
return *this;
1719
}
1820

19-
std::optional<Transition> get_transition(const std::string &event) const
21+
State &initial_substate(const std::string &name)
2022
{
21-
auto it = transitions_.find(event);
22-
if (it != transitions_.end())
23-
return it->second;
24-
return std::nullopt;
23+
initial_substate_ = name;
24+
return *this;
25+
}
26+
27+
State &substate(const std::string &name, std::function<void(State &)> config)
28+
{
29+
substates_.emplace_back(name, this);
30+
State &new_substate = substates_.back();
31+
config(new_substate);
32+
return *this;
2533
}
2634

2735
const std::string &name() const { return name_; }
36+
std::string fullName() const
37+
{
38+
if (parent_ != nullptr)
39+
{
40+
std::string result = parent_->fullName() + "." + name_;
41+
return result;
42+
}
43+
else
44+
{
45+
return name_;
46+
}
47+
}
48+
const std::list<State> &substates() const { return substates_; }
49+
const std::unordered_map<std::string, Transition> &transitions() const { return transitions_; }
50+
const std::optional<std::string> &initial_substate() const { return initial_substate_; }
51+
const State *parent() const { return parent_; }
52+
53+
const State *find_substate(const std::string &name) const
54+
{
55+
for (const auto &s : substates_)
56+
{
57+
if (s.name() == name)
58+
return &s;
59+
}
60+
if (parent_ != nullptr)
61+
{
62+
return parent_->find_substate(name);
63+
}
64+
return nullptr;
65+
}
2866

2967
private:
3068
std::string name_;
69+
State *parent_ = nullptr;
70+
std::list<State> substates_;
3171
std::unordered_map<std::string, Transition> transitions_;
72+
std::optional<std::string> initial_substate_;
3273
};
3374
} // namespace CXXStateTree

include/CXXStateTree/StateTree.hpp

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,102 @@ namespace CXXStateTree
2424

2525
void send(const std::string &event)
2626
{
27-
const auto &current = states_.at(current_state_);
28-
auto trans = current.get_transition(event);
29-
if (trans && (!trans->guard.has_value() || (trans->guard.has_value() && !trans->guard.value()) || (*trans->guard)()))
27+
if (!current_)
28+
return;
29+
const auto &transitions = current_->transitions();
30+
auto it = transitions.find(event);
31+
if (it != transitions.end())
3032
{
31-
if (trans->action.has_value() && trans->action.value() != nullptr)
33+
const auto &trans = it->second;
34+
if (trans.guard && !trans.guard())
35+
return;
36+
if (trans.action)
37+
trans.action();
38+
current_ = find_state(trans.target);
39+
if (current_ && current_->initial_substate())
3240
{
33-
(*trans->action)();
41+
current_ = current_->find_substate(*current_->initial_substate());
3442
}
35-
current_state_ = trans->target;
43+
}
44+
// try to send the event to parent states
45+
else if (current_->parent())
46+
{
47+
sendToParent(event, current_->parent());
48+
}
49+
else
50+
{
51+
throw std::runtime_error("Event '" + event + "' not handled in state '" + current_->name() + "'");
52+
}
53+
}
54+
55+
void sendToParent(const std::string &event, const State *parent)
56+
{
57+
if (!parent)
58+
return;
59+
const auto &transitions = parent->transitions();
60+
auto it = transitions.find(event);
61+
if (it != transitions.end())
62+
{
63+
const auto &trans = it->second;
64+
if (trans.guard && !trans.guard())
65+
return;
66+
if (trans.action)
67+
trans.action();
68+
current_ = find_state(trans.target);
69+
if (current_ && current_->initial_substate())
70+
{
71+
current_ = current_->find_substate(*current_->initial_substate());
72+
}
73+
}
74+
else if (parent->parent())
75+
{
76+
sendToParent(event, parent->parent());
77+
}
78+
else
79+
{
80+
throw std::runtime_error("Event '" + event + "' not handled in parent state '" + parent->name() + "'");
3681
}
3782
}
3883

39-
const std::string &current_state() const
84+
const State &current_state() const
4085
{
41-
return current_state_;
86+
return *current_;
4287
}
4388

4489
private:
45-
StateTree(std::unordered_map<std::string, State> states, std::string initial)
46-
: states_(std::move(states)), current_state_(std::move(initial)) {}
90+
std::list<State> states_;
91+
const State *current_ = nullptr;
4792

48-
std::unordered_map<std::string, State> states_;
49-
std::string current_state_;
93+
const State *find_state(const std::string &name) const
94+
{
95+
96+
if (!current_ || current_->parent() == nullptr)
97+
{
98+
for (const auto &s : states_)
99+
{
100+
if (s.name() == name)
101+
return &s;
102+
}
103+
return nullptr;
104+
}
105+
else
106+
{
107+
108+
auto foundState = current_->parent()->find_substate(name);
109+
if (foundState)
110+
{
111+
return foundState;
112+
}
113+
114+
for (const auto &s : states_)
115+
{
116+
if (s.name() == name)
117+
return &s;
118+
}
119+
}
120+
121+
return nullptr;
122+
}
50123
};
51124

52125
} // namespace CXXStateTree

include/CXXStateTree/Transition.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace CXXStateTree
1313
struct Transition
1414
{
1515
std::string target;
16-
std::optional<Guard> guard;
17-
std::optional<Action> action;
16+
Guard guard = nullptr;
17+
Action action = nullptr;
1818
};
1919
} // namespace CXXStateTree

0 commit comments

Comments
 (0)