Skip to content

Commit c6cd622

Browse files
committed
♻️ separate plan and planner, plan is run one step at a time
1 parent 50b237d commit c6cd622

File tree

2 files changed

+109
-100
lines changed

2 files changed

+109
-100
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,12 @@ auto goal = blackboard_type{
299299
.wood = 3
300300
};
301301
302-
auto p = plan<blackboard_type>(actions, initial, goal);
303-
p.run(initial); // will mutate the blackboard
302+
auto p = planner<blackboard_type>(actions, initial, goal);
303+
304+
auto blackboard = initial;
305+
while (p) {
306+
p.run_next(initial); // will mutate the blackboard
307+
}
304308
```
305309

306310
For more informations, consult the

include/aitoolkit/goap.hpp

Lines changed: 103 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,18 @@ auto goal = blackboard_type{
150150
.stone = 1
151151
};
152152
153-
auto p = plan<blackboard_type>(actions, initial, goal);
154-
p.run(initial); // will mutate the blackboard
153+
auto p = planner<blackboard_type>(actions, initial, goal);
154+
155+
auto blackboard = initial;
156+
while (p) {
157+
p.run_next(blackboard); // will mutate the blackboard
158+
}
155159
```
156160
*/
157161

158162
#include <unordered_set>
163+
#include <functional>
164+
#include <coroutine>
159165
#include <memory>
160166
#include <vector>
161167
#include <queue>
@@ -216,96 +222,11 @@ namespace aitoolkit::goap {
216222
* @ingroup goap
217223
* @class plan
218224
* @brief A plan is a sequence of actions that will lead to a goal state.
219-
*
220-
* The plan is created by providing a list of actions, an initial blackboard
221-
* state, and a goal blackboard state. The plan will then find a sequence of
222-
* actions that will lead to the goal state.
223-
*
224-
* The plan can then be run on a blackboard to mutate it.
225225
*/
226226
template <blackboard_trait T>
227227
class plan {
228228
public:
229-
/**
230-
* @brief Create a plan.
231-
*
232-
* @param actions The list of actions that can be performed.
233-
* @param initital_blackboard The initial state of the blackboard.
234-
* @param goal_blackboard The goal state of the blackboard.
235-
* @param max_iterations The maximum number of iterations to perform
236-
* before giving up. If 0, the plan will run until it finds a solution.
237-
*/
238-
plan(
239-
std::vector<action_ptr<T>> actions,
240-
T initital_blackboard,
241-
T goal_blackboard,
242-
size_t max_iterations = 0
243-
) {
244-
struct node_type {
245-
T blackboard;
246-
float cost;
247-
248-
action_ptr<T> action_taken;
249-
std::shared_ptr<node_type> parent;
250-
};
251-
252-
using node_ptr = std::shared_ptr<node_type>;
253-
254-
struct node_compare {
255-
bool operator()(const node_ptr& a, const node_ptr& b) const {
256-
return a->cost > b->cost;
257-
}
258-
};
259-
260-
std::priority_queue<node_ptr, std::vector<node_ptr>, node_compare> open_set;
261-
std::unordered_set<T> closed_set;
262-
263-
open_set.push(std::make_shared<node_type>(node_type{
264-
.blackboard = initital_blackboard,
265-
.cost = 0.0f,
266-
.action_taken = nullptr,
267-
.parent = nullptr
268-
}));
269-
270-
for (
271-
size_t iteration = 0;
272-
!open_set.empty() && (max_iterations == 0 || iteration < max_iterations);
273-
++iteration
274-
) {
275-
auto current_node = open_set.top();
276-
open_set.pop();
277-
278-
if (current_node->blackboard == goal_blackboard) {
279-
while (current_node->parent != nullptr) {
280-
m_actions.push(current_node->action_taken);
281-
current_node = current_node->parent;
282-
}
283-
284-
break;
285-
}
286-
287-
if (!closed_set.contains(current_node->blackboard)) {
288-
closed_set.insert(current_node->blackboard);
289-
290-
for (auto& action : actions) {
291-
if (action->check_preconditions(current_node->blackboard)) {
292-
auto next_blackboard = current_node->blackboard;
293-
action->apply_effects(next_blackboard);
294-
auto next_cost = current_node->cost + action->cost(current_node->blackboard);
295-
296-
if (!closed_set.contains(next_blackboard)) {
297-
open_set.push(std::make_shared<node_type>(node_type{
298-
.blackboard = next_blackboard,
299-
.cost = next_cost,
300-
.action_taken = action,
301-
.parent = current_node
302-
}));
303-
}
304-
}
305-
}
306-
}
307-
}
308-
}
229+
plan(std::queue<action_ptr<T>> actions) : m_actions(actions) {}
309230

310231
/**
311232
* @brief Get the number of actions in the plan.
@@ -322,20 +243,104 @@ namespace aitoolkit::goap {
322243
}
323244

324245
/**
325-
* @brief Run the plan on a blackboard.
246+
* @brief Execute the next planned action.
326247
*/
327-
void run(T& blackboard) {
328-
auto actions = m_actions;
329-
330-
while (!actions.empty()) {
331-
auto action = actions.front();
332-
actions.pop();
333-
248+
void run_next(T& blackboard) {
249+
if (!m_actions.empty()) {
250+
auto action = m_actions.front();
251+
m_actions.pop();
334252
action->apply_effects(blackboard);
335253
}
336254
}
255+
};
256+
257+
/**
258+
* @ingroup goap
259+
* @brief Create a plan.
260+
*
261+
* The plan is created by providing a list of actions, an initial blackboard
262+
* state, and a goal blackboard state. The plan will then find a sequence of
263+
* actions that will lead to the goal state.
264+
*
265+
* @param actions The list of actions that can be performed.
266+
* @param initital_blackboard The initial state of the blackboard.
267+
* @param goal_blackboard The goal state of the blackboard.
268+
* @param max_iterations The maximum number of iterations to perform
269+
* before giving up. If 0, the plan will run until it finds a solution.
270+
* @return A plan that can be executed.
271+
*/
272+
template <blackboard_trait T>
273+
plan<T> planner(
274+
std::vector<action_ptr<T>> actions,
275+
T initital_blackboard,
276+
T goal_blackboard,
277+
size_t max_iterations = 0
278+
) {
279+
struct node_type {
280+
T blackboard;
281+
float cost;
282+
283+
action_ptr<T> action_taken;
284+
std::shared_ptr<node_type> parent;
285+
};
286+
287+
using node_ptr = std::shared_ptr<node_type>;
288+
289+
struct node_compare {
290+
bool operator()(const node_ptr& a, const node_ptr& b) const {
291+
return a->cost > b->cost;
292+
}
293+
};
294+
295+
std::priority_queue<node_ptr, std::vector<node_ptr>, node_compare> open_set;
296+
std::unordered_set<T> closed_set;
297+
298+
open_set.push(std::make_shared<node_type>(node_type{
299+
.blackboard = initital_blackboard,
300+
.cost = 0.0f,
301+
.action_taken = nullptr,
302+
.parent = nullptr
303+
}));
304+
305+
for (
306+
size_t iteration = 0;
307+
!open_set.empty() && (max_iterations == 0 || iteration < max_iterations);
308+
++iteration
309+
) {
310+
auto current_node = open_set.top();
311+
open_set.pop();
312+
313+
if (current_node->blackboard == goal_blackboard) {
314+
auto actions = std::queue<action_ptr<T>>{};
315+
316+
while (current_node->parent != nullptr) {
317+
actions.push(current_node->action_taken);
318+
current_node = current_node->parent;
319+
}
320+
321+
return plan<T>(actions);
322+
}
337323

338-
private:
339-
std::queue<action_ptr<T>> m_actions;
324+
if (!closed_set.contains(current_node->blackboard)) {
325+
closed_set.insert(current_node->blackboard);
326+
327+
for (auto& action : actions) {
328+
if (action->check_preconditions(current_node->blackboard)) {
329+
auto next_blackboard = current_node->blackboard;
330+
action->apply_effects(next_blackboard);
331+
auto next_cost = current_node->cost + action->cost(current_node->blackboard);
332+
333+
if (!closed_set.contains(next_blackboard)) {
334+
open_set.push(std::make_shared<node_type>(node_type{
335+
.blackboard = next_blackboard,
336+
.cost = next_cost,
337+
.action_taken = action,
338+
.parent = current_node
339+
}));
340+
}
341+
}
342+
}
343+
}
344+
}
340345
};
341346
}

0 commit comments

Comments
 (0)