Botcraft 26.1.2
Loading...
Searching...
No Matches
TemplatedBehaviourClient.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <atomic>
4
10#if USE_GUI
12#endif
13
14namespace Botcraft
15{
16 /// @brief The base class you should inherit if you need to
17 /// implement some custom Handle functions AND need to add
18 /// custom fields to your derived class. If you just need
19 /// some custom Handle, inheriting SimpleBehaviourClient is
20 /// sufficient.
21 /// @tparam TDerived Any class inheriting this class (see SimpleBehaviourClient)
22 template<typename TDerived>
24 {
25 private:
26 /// @brief Custom internal type used when the tree needs
27 /// to be changed. It does not inherit std::exception to
28 /// prevent tree components from catching it
30 {
31 };
32 /// @brief Custom internal type used when the tree needs
33 /// to be stopped. It does not inherit std::exception to
34 /// prevent tree components from catching it
36 {
37 };
38
39 public:
44
46 {
47 // Make sure should_be_closed is set to false
48 // otherwise the behaviour_thread will not exit
49 // properly
50 {
51 std::lock_guard<std::mutex> behaviour_guard(behaviour_mutex);
52 should_be_closed = true;
53 }
54
55 behaviour_cond_var.notify_all();
56 if (behaviour_thread.joinable())
57 {
58 behaviour_thread.join();
59 }
60 }
61
62 /// @brief Save the given tree to replace the current one as soon as possible.
63 /// @param tree_ The new tree
64 /// @param blackboard_ Initial values to put into the blackboard when swapping tree
65 void SetBehaviourTree(const std::shared_ptr<BehaviourTree<TDerived> >& tree_, const std::map<std::string, std::any>& blackboard_ = {})
66 {
67 std::lock_guard<std::mutex> behaviour_guard(behaviour_mutex);
68 swap_tree = true;
69 new_tree = tree_;
70 new_blackboard = blackboard_;
71 }
72
73 /// @brief Can be called to pause the execution of the internal
74 /// tree function. Call it in long function so the behaviour
75 /// can be interrupted.
76 virtual void Yield() override
77 {
78 std::unique_lock<std::mutex> lock(behaviour_mutex);
79 behaviour_cond_var.notify_all();
80 behaviour_cond_var.wait(lock);
82 {
83 throw Interrupted();
84 }
85 else if (swap_tree)
86 {
87 throw SwapTree();
88 }
89 }
90
91 /// @brief Start the behaviour thread loop.
93 {
94 tree_loop_ready = false;
96
97 // Wait for the thread to start and ready to run
98 while (!tree_loop_ready)
99 {
100 Utilities::SleepFor(std::chrono::milliseconds(10));
101 }
102 }
103
104 /// @brief Blocking call, will return only when the client is
105 /// disconnected from the server.
107 {
108 if (!behaviour_thread.joinable())
109 {
111 }
112
113 // Main behaviour loop
114 while (!should_be_closed)
115 {
116 std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
117 std::chrono::steady_clock::time_point end = start + std::chrono::milliseconds(10);
118
120
122 }
123 }
124
125 /// @brief Perform one step of the behaviour tree.
126 /// Don't forget to call StartBehaviour before.
128 {
130 {
131 return;
132 }
133
134 std::unique_lock<std::mutex> lock(behaviour_mutex);
135 // Resume tree ticking
136 behaviour_cond_var.notify_all();
137 // Wait for the next call to Yield()
138 behaviour_cond_var.wait(lock);
139 }
140
141 /// @brief Set a tree to execute the given action once and block until done.
142 /// This will change the current tree. BehaviourStep should **NOT** be called
143 /// by another thread simultaneously. It means you should **NOT** call
144 /// RunBehaviourUntilClosed when using this sync version.
145 /// @param timeout_ms Max running time of the function in ms, ignored if 0
146 /// @param ...args Parameters passed to create tree leaf
147 /// @return Return value of the action (success/failure)
148 template<typename... Args>
149 Status SyncAction(const int timeout_ms, Args&&... args)
150 {
151 // Make sure the behaviour thread is running
152 if (!behaviour_thread.joinable())
153 {
155 }
156
157 // Make sure there is no active tree
158 if (tree != nullptr)
159 {
160 SetBehaviourTree(nullptr);
162
163 // Wait for the tree to be set as active one
164 if (!Utilities::WaitForCondition([&]() {
165 return tree == nullptr;
166 }, 500)
167 )
168 {
169 LOG_WARNING("Timeout waiting for tree swapping in SyncAction");
170 // It's not realy a failure as we couldn't swap the tree but we need to return something
171 return Status::Failure;
172 }
173 }
174
175 Status return_value = Status::Failure;
176
177 // Set the tree
179 .sequence()
180 .selector()
181 .sequence()
182 .leaf(std::forward<Args>(args)...)
183 // We know return_value will be a valid ref at least as long as the tree exists
184 .leaf([&return_value](TDerived&) { return_value = Status::Success; return Status::Success; })
185 .end()
186 .leaf([&return_value](TDerived&) { return_value = Status::Failure; return Status::Success; })
187 .end()
188 .leaf([](TDerived& c) { c.SetBehaviourTree(nullptr); return Status::Success; })
189 .end());
190
191 // Perform one step to get out of the Yield lock and swap tree
193
194 // Wait for the tree to be set as active one
195 if (!Utilities::WaitForCondition([&]() {
196 return tree != nullptr;
197 }, 500)
198 )
199 {
200 LOG_WARNING("Timeout waiting for tree swapping in SyncAction");
201 // It's not realy a failure as we couldn't swap the tree but we need to return something
202 return Status::Failure;
203 }
204
205 // Run until tree is ticked once and reset to nullptr
206 const std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
207 while (tree != nullptr && !should_be_closed)
208 {
209 std::chrono::steady_clock::time_point iter_start = std::chrono::steady_clock::now();
210 std::chrono::steady_clock::time_point iter_end = iter_start + std::chrono::milliseconds(10);
211
212 // Timeout, reset tree and get out
213 if (timeout_ms > 0 && std::chrono::duration_cast<std::chrono::milliseconds>(iter_start - start).count() > timeout_ms)
214 {
215 LOG_WARNING("Timeout when doing SyncAction");
216 SetBehaviourTree(nullptr);
219 {
220 return tree == nullptr;
221 }, 500);
222 break;
223 }
224
226
227 Utilities::SleepUntil(iter_end);
228 }
229
230 return return_value;
231 }
232
233
234 /// @brief Set a tree to execute the given action once and block until done.
235 /// This will change the current tree. BehaviourStep should **NOT** be called
236 /// by another thread simultaneously. It means you should **NOT** call
237 /// RunBehaviourUntilClosed when using this sync version. This version has no
238 /// timeout and will run until the tree ends.
239 /// @param ...args Parameters passed to create tree leaf
240 template<typename... Args>
241 void SyncAction(Args&&... args)
242 {
243 return SyncAction(0, std::forward<Args>(args)...);
244 }
245
246 void OnTreeChanged(const BaseNode* root)
247 {
248 const std::string& tree_name = root != nullptr ? root->GetName() : "nullptr";
249 LOG_INFO("Behaviour tree changed" << (tree_name.empty() ? " (anonymous tree)" : (" to " + tree_name)));
250#if USE_GUI
251 if (rendering_manager != nullptr)
252 {
253 rendering_manager->SetCurrentBehaviourTree(root);
254 }
255#endif
256 }
257
258#if USE_GUI
260 {
261 if (rendering_manager != nullptr)
262 {
263 rendering_manager->ResetBehaviourState();
264 }
265 }
266
268 {
269 if (rendering_manager != nullptr)
270 {
271 rendering_manager->BehaviourStartTick();
272 while (rendering_manager->IsBehaviourGUIPaused())
273 {
274 Yield();
275 }
276 }
277 }
278
279 void OnNodeEndTick(const Status s)
280 {
281 if (rendering_manager != nullptr)
282 {
283 rendering_manager->BehaviourEndTick(s == Status::Success);
284 }
285 }
286
287 void OnNodeTickChild(const size_t i)
288 {
289 if (rendering_manager != nullptr)
290 {
291 rendering_manager->BehaviourTickChild(i);
292 }
293 }
294#endif
295
296 private:
297 void TreeLoop()
298 {
299 Logger::GetInstance().RegisterThread("Behaviour - " + GetNetworkManager()->GetMyName());
300 tree_loop_ready = true;
301 while (true)
302 {
303 try
304 {
305 if (tree)
306 {
307#if USE_GUI
309#endif
310 tree->Tick(static_cast<TDerived&>(*this));
311 }
312 Yield();
313 }
314 // We need to update the tree with the new one
315 catch (const SwapTree&)
316 {
317 {
318 std::lock_guard<std::mutex> behaviour_guard(behaviour_mutex);
319 tree = new_tree;
320 new_tree = nullptr;
321 swap_tree = false;
322 OnTreeChanged(tree.get());
324 }
325 continue;
326 }
327 // We need to stop the behaviour thread
328 catch (const Interrupted&)
329 {
330 return;
331 }
332 catch (const std::exception& e)
333 {
334 LOG_ERROR("Exception caught during tree ticking:\n" << e.what() << "\nStopping behaviour.");
335 return;
336 }
337 catch (...)
338 {
339 LOG_ERROR("Unknown exception caught during tree ticking. Stopping behaviour.");
340 return;
341 }
342 }
343 }
344
345 private:
346 std::shared_ptr<BehaviourTree<TDerived> > tree;
347 std::shared_ptr<BehaviourTree<TDerived> > new_tree;
348 std::map<std::string, std::any> new_blackboard;
350
351 std::thread behaviour_thread;
352 std::condition_variable behaviour_cond_var;
353 std::mutex behaviour_mutex;
354
355 std::atomic<bool> tree_loop_ready;
356 };
357} // namespace Botcraft
#define LOG_ERROR(osstream)
Definition Logger.hpp:45
#define LOG_WARNING(osstream)
Definition Logger.hpp:44
#define LOG_INFO(osstream)
Definition Logger.hpp:43
const std::string & GetName() const
Definition BaseNode.cpp:24
A ManagersClient extended with a blackboard that can store any kind of data and a virtual Yield funct...
void Reset(const std::map< std::string, std::any > &values={})
Clear all the entries in the blackboard and load new ones.
std::shared_ptr< NetworkManager > GetNetworkManager() const
std::shared_ptr< NetworkManager > network_manager
void RegisterThread(const std::string &name)
Register the current thread in the map.
Definition Logger.cpp:104
static Logger & GetInstance()
Definition Logger.cpp:36
std::shared_ptr< Renderer::RenderingManager > rendering_manager
Custom internal type used when the tree needs to be stopped.
Custom internal type used when the tree needs to be changed.
The base class you should inherit if you need to implement some custom Handle functions AND need to a...
void StartBehaviour()
Start the behaviour thread loop.
Status SyncAction(const int timeout_ms, Args &&... args)
Set a tree to execute the given action once and block until done.
void RunBehaviourUntilClosed()
Blocking call, will return only when the client is disconnected from the server.
virtual void Yield() override
Can be called to pause the execution of the internal tree function.
std::map< std::string, std::any > new_blackboard
void SetBehaviourTree(const std::shared_ptr< BehaviourTree< TDerived > > &tree_, const std::map< std::string, std::any > &blackboard_={})
Save the given tree to replace the current one as soon as possible.
void BehaviourStep()
Perform one step of the behaviour tree.
std::shared_ptr< BehaviourTree< TDerived > > new_tree
std::shared_ptr< BehaviourTree< TDerived > > tree
void SyncAction(Args &&... args)
Set a tree to execute the given action once and block until done.
void SleepUntil(const std::chrono::steady_clock::time_point &end)
bool WaitForCondition(const std::function< bool()> &condition, const long long int timeout_ms=0, const long long int check_interval_ms=10)
void SleepFor(const std::chrono::duration< _Rep, _Period > &time)