├── LICENSE ├── example ├── Example01_Simple.cpp └── Example02_Simple.cpp ├── README.md └── BrainTree.h /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arvidsson/BrainTree/HEAD/LICENSE -------------------------------------------------------------------------------- /example/Example01_Simple.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "BrainTree.h" 3 | 4 | namespace bt = BrainTree; 5 | 6 | class SayHello : public bt::Node 7 | { 8 | Status update() override 9 | { 10 | std::cout << "Hello, World!" << std::endl; 11 | return Node::Status::Success; 12 | } 13 | }; 14 | 15 | int main() 16 | { 17 | bt::BehaviorTree tree; 18 | 19 | auto repeater = std::make_shared(5); 20 | repeater->setChild(std::make_shared()); 21 | tree.setRoot(repeater); 22 | 23 | // simulate 5 frames 24 | for (int i = 0; i < 5; i++) 25 | tree.update(); 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BrainTree 2 | 3 | A C++ [behavior tree](http://gamasutra.com/blogs/ChrisSimpson/20140717/221339/Behavior_trees_for_AI_How_they_work.php) single header library. 4 | 5 | ## Features 6 | 7 | - behavior tree implementation 8 | - predefined composites 9 | - predefined decorators 10 | - (optional) rudimentary blackboard 11 | - (optional) behavior tree builders 12 | 13 | ## Install 14 | 15 | Include `BrainTree.h` in your project. 16 | 17 | ## Example 18 | 19 | ```c++ 20 | // this example should print out "Hello, World!", four times 21 | #include 22 | #include "BrainTree.h" 23 | 24 | class Action : public BrainTree::Node 25 | { 26 | public: 27 | Status update() override 28 | { 29 | std::cout << "Hello, World!" << std::endl; 30 | return Node::Status::Success; 31 | } 32 | }; 33 | 34 | void CreatingBehaviorTreeManually() 35 | { 36 | BrainTree::BehaviorTree tree; 37 | auto sequence = std::make_shared(); 38 | auto sayHello = std::make_shared(); 39 | auto sayHelloAgain = std::make_shared(); 40 | sequence->addChild(sayHello); 41 | sequence->addChild(sayHelloAgain); 42 | tree.setRoot(sequence); 43 | tree.update(); 44 | } 45 | 46 | void CreatingBehaviorTreeUsingBuilders() 47 | { 48 | auto tree = BrainTree::Builder() 49 | .composite() 50 | .leaf() 51 | .leaf() 52 | .end() 53 | .build(); 54 | tree->update(); 55 | } 56 | 57 | int main() 58 | { 59 | CreatingBehaviorTreeManually(); 60 | CreatingBehaviorTreeUsingBuilders(); 61 | return 0; 62 | } 63 | ``` 64 | 65 | ## Composites 66 | 67 | | Composite | Behaviour | Success | Running | Failure | 68 | | ---------------- | ------------------------------------------------------------------------------- | ----------------------- | --------------------- | -------------------- | 69 | | Selector | Ticks each child node in order, starting from the beginning each tick | If a child succeeds | If a child is running | If all children fail | 70 | | Sequence | Ticks each child node in order, starting from the beginning each tick | If all children succeed | If a child is running | If a child fails | 71 | | StatefulSelector | Ticks each child node in order, starting from the child ticked in previous tick | If a child succeeds | If a child is running | If all children fail | 72 | | StatefulSequence | Ticks each child node in order, starting from the child ticked in previous tick | If all children succeed | If a child is running | If a child fails | 73 | 74 | ## Decorators 75 | 76 | | Decorator | Behaviour | 77 | | ------------ | --------------------------------------------------------- | 78 | | Succeeder | Always returns success | 79 | | Failer | Always returns failure | 80 | | Inverter | Inverts the result of the child node | 81 | | Repeater | Repeats until child node succeeds (infinitely or limited) | 82 | | UntilSuccess | Repeats until child node succeeds | 83 | | UntilFailure | Repeats until child node fails | 84 | 85 | ## Builder 86 | 87 | The Builder class simplifies the process of creating a behavior tree. You use three methods to build your tree: 88 | 89 | - `leaf()` 90 | - `composite()` 91 | - `decorator()` 92 | 93 | Both `composite()` and `decorator()` require a corresponding call to `end()`, this marks where you are done adding children to a composite or a child to a decorator. At the very end you call `build()` which will then give you the finished behavior tree. 94 | 95 | ``` 96 | auto tree = Builder() 97 | .decorator() 98 | .composite() 99 | .leaf("Foo") 100 | .leaf("Bar") 101 | .end() 102 | .end() 103 | .build(); 104 | ``` 105 | 106 | ## License 107 | 108 | MIT (c) Pär Arvidsson 2015-2018 109 | -------------------------------------------------------------------------------- /example/Example02_Simple.cpp: -------------------------------------------------------------------------------- 1 | // this example demonstrates the beviorial difference between 2 | // Sequence, MemSequence, Selector, StatefulSelector 3 | // 4 | // g++ -std=c++11 Example02_Simple.cpp && ./a.out 5 | #include 6 | using namespace std; 7 | #include "../BrainTree.h" 8 | 9 | #define STR(var) #var 10 | 11 | enum BoardItem { 12 | MSG, 13 | }; 14 | 15 | class SuccessAction : public BrainTree::Node { 16 | public: 17 | SuccessAction(int i) { 18 | id = i; 19 | } 20 | Status update() override { 21 | cout << " success" << id << " action" << endl; 22 | sprintf(buffer, " message from success%d", id); 23 | assert(blackboard != nullptr && "The Blackboard is empty!"); 24 | blackboard->setString(STR(MSG), buffer); 25 | return Status::Success; 26 | } 27 | private: 28 | char buffer[128]; 29 | int id; 30 | }; 31 | 32 | class FailAction : public BrainTree::Node { 33 | public: 34 | Status update() override { 35 | cout << " fail action" << endl; 36 | assert(blackboard != nullptr && "The Blackboard is empty!"); 37 | cout << blackboard->getString(STR(MSG)) << endl; 38 | return Status::Failure; 39 | } 40 | }; 41 | 42 | void CreatingBehaviorTreeUsingBuilders() { 43 | cout << "\n*** Creating BehaviorTree Using Builders" << endl; 44 | cout << "\n* Sequence: success1 -> fail -> success2, three times" << endl; 45 | auto tree = BrainTree::Builder() 46 | .composite() 47 | .leaf(1) 48 | .leaf() 49 | .leaf(2) 50 | .end() 51 | .build(); 52 | cout << " first update()" << endl; 53 | tree->update(); 54 | cout << " second update()" << endl; 55 | tree->update(); 56 | cout << " third update()" << endl; 57 | tree->update(); 58 | 59 | cout << "\n* MemSequence: success1 -> fail -> success2, three times" << endl; 60 | tree = BrainTree::Builder() 61 | .composite() 62 | .leaf(1) 63 | .leaf() 64 | .leaf(2) 65 | .end() 66 | .build(); 67 | cout << " first update()" << endl; 68 | tree->update(); 69 | cout << " second update()" << endl; 70 | tree->update(); 71 | cout << " third update()" << endl; 72 | tree->update(); 73 | 74 | cout << "\n* Selector: fail -> success1 -> success2, three times" << endl; 75 | tree = BrainTree::Builder() 76 | .composite() 77 | .leaf() 78 | .leaf(1) 79 | .leaf(2) 80 | .end() 81 | .build(); 82 | cout << " first update()" << endl; 83 | tree->update(); 84 | cout << " second update()" << endl; 85 | tree->update(); 86 | cout << " third update()" << endl; 87 | tree->update(); 88 | 89 | cout << "\n* StatefulSelector: fail -> success1 -> success2, three times" << endl; 90 | tree = BrainTree::Builder() 91 | .composite() 92 | .leaf() 93 | .leaf(1) 94 | .leaf(2) 95 | .end() 96 | .build(); 97 | cout << " first update()" << endl; 98 | tree->update(); 99 | cout << " second update()" << endl; 100 | tree->update(); 101 | cout << " third update()" << endl; 102 | tree->update(); 103 | 104 | cout << "\n* Sequence: Succeeder w/ fail child" << endl; 105 | cout << "-> Inverted Failer w/ success1 child" << endl; 106 | cout << "-> success2" << endl; 107 | tree = BrainTree::Builder() 108 | .composite() 109 | .decorator() 110 | .leaf() 111 | .end() 112 | .decorator() 113 | .decorator() 114 | .leaf(1) 115 | .end() 116 | .end() 117 | .leaf(2) 118 | .end() 119 | .build(); 120 | tree->update(); 121 | } 122 | 123 | int main() { 124 | CreatingBehaviorTreeUsingBuilders(); 125 | return 0; 126 | } -------------------------------------------------------------------------------- /BrainTree.h: -------------------------------------------------------------------------------- 1 | // BrainTree - A C++ behavior tree single header library. 2 | // Copyright 2015-2018 Par Arvidsson. All rights reserved. 3 | // Licensed under the MIT license (https://github.com/arvidsson/BrainTree/blob/master/LICENSE). 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace BrainTree 14 | { 15 | 16 | class Blackboard 17 | { 18 | public: 19 | void setBool(std::string key, bool value) { bools[key] = value; } 20 | bool getBool(std::string key) 21 | { 22 | if (bools.find(key) == bools.end()) { 23 | bools[key] = false; 24 | } 25 | return bools[key]; 26 | } 27 | bool hasBool(std::string key) const { return bools.find(key) != bools.end(); } 28 | 29 | void setInt(std::string key, int value) { ints[key] = value; } 30 | int getInt(std::string key) 31 | { 32 | if (ints.find(key) == ints.end()) { 33 | ints[key] = 0; 34 | } 35 | return ints[key]; 36 | } 37 | bool hasInt(std::string key) const { return ints.find(key) != ints.end(); } 38 | 39 | void setFloat(std::string key, float value) { floats[key] = value; } 40 | float getFloat(std::string key) 41 | { 42 | if (floats.find(key) == floats.end()) { 43 | floats[key] = 0.0f; 44 | } 45 | return floats[key]; 46 | } 47 | bool hasFloat(std::string key) const { return floats.find(key) != floats.end(); } 48 | 49 | void setDouble(std::string key, double value) { doubles[key] = value; } 50 | double getDouble(std::string key) 51 | { 52 | if (doubles.find(key) == doubles.end()) { 53 | doubles[key] = 0.0f; 54 | } 55 | return doubles[key]; 56 | } 57 | bool hasDouble(std::string key) const { return doubles.find(key) != doubles.end(); } 58 | 59 | void setString(std::string key, std::string value) { strings[key] = value; } 60 | std::string getString(std::string key) 61 | { 62 | if (strings.find(key) == strings.end()) { 63 | strings[key] = ""; 64 | } 65 | return strings[key]; 66 | } 67 | bool hasString(std::string key) const { return strings.find(key) != strings.end(); } 68 | 69 | using Ptr = std::shared_ptr; 70 | 71 | protected: 72 | std::unordered_map bools; 73 | std::unordered_map ints; 74 | std::unordered_map floats; 75 | std::unordered_map doubles; 76 | std::unordered_map strings; 77 | }; 78 | 79 | class Node 80 | { 81 | public: 82 | enum class Status 83 | { 84 | Invalid, 85 | Success, 86 | Failure, 87 | Running, 88 | }; 89 | 90 | virtual ~Node() {} 91 | void setBlackboard(Blackboard::Ptr board) { 92 | blackboard = board; 93 | } 94 | Blackboard::Ptr getBlackboard() const { return blackboard; } 95 | 96 | virtual Status update() = 0; 97 | virtual void initialize() {} 98 | virtual void terminate(Status s) {} 99 | 100 | Status tick() 101 | { 102 | if (status != Status::Running) { 103 | initialize(); 104 | } 105 | 106 | status = update(); 107 | 108 | if (status != Status::Running) { 109 | terminate(status); 110 | } 111 | 112 | return status; 113 | } 114 | 115 | bool isSuccess() const { return status == Status::Success; } 116 | bool isFailure() const { return status == Status::Failure; } 117 | bool isRunning() const { return status == Status::Running; } 118 | bool isTerminated() const { return isSuccess() || isFailure(); } 119 | 120 | void reset() { status = Status::Invalid; } 121 | 122 | using Ptr = std::shared_ptr; 123 | 124 | protected: 125 | Status status = Status::Invalid; 126 | Blackboard::Ptr blackboard = nullptr; 127 | }; 128 | 129 | class Composite : public Node 130 | { 131 | public: 132 | virtual ~Composite() {} 133 | 134 | void addChild(Node::Ptr child) { children.push_back(child); it=children.begin(); } 135 | bool hasChildren() const { return !children.empty(); } 136 | 137 | protected: 138 | std::vector children; 139 | std::vector::iterator it; 140 | }; 141 | 142 | class Decorator : public Node 143 | { 144 | public: 145 | virtual ~Decorator() {} 146 | 147 | void setChild(Node::Ptr node) { child = node; } 148 | bool hasChild() const { return child != nullptr; } 149 | 150 | protected: 151 | Node::Ptr child = nullptr; 152 | }; 153 | 154 | class Leaf : public Node 155 | { 156 | public: 157 | Leaf() {} 158 | virtual ~Leaf() {} 159 | Leaf(Blackboard::Ptr blackboard) : blackboard(blackboard) {} 160 | 161 | virtual Status update() = 0; 162 | 163 | protected: 164 | Blackboard::Ptr blackboard; 165 | }; 166 | 167 | class BehaviorTree : public Node 168 | { 169 | public: 170 | BehaviorTree() { 171 | blackboard = std::make_shared(); 172 | } 173 | BehaviorTree(const Node::Ptr &rootNode) : BehaviorTree() { root = rootNode; } 174 | 175 | Status update() { return root->tick(); } 176 | 177 | void setRoot(const Node::Ptr &node) { root = node; } 178 | 179 | private: 180 | Node::Ptr root = nullptr; 181 | }; 182 | 183 | template 184 | class DecoratorBuilder; 185 | 186 | template 187 | class CompositeBuilder 188 | { 189 | public: 190 | CompositeBuilder(Parent* parent, Composite* node) : parent(parent), node(node) {} 191 | 192 | template 193 | CompositeBuilder leaf(Args... args) 194 | { 195 | auto child = std::make_shared((args)...); 196 | child->setBlackboard(node->getBlackboard()); 197 | node->addChild(child); 198 | return *this; 199 | } 200 | 201 | template 202 | CompositeBuilder> composite(Args... args) 203 | { 204 | auto child = std::make_shared((args)...); 205 | child->setBlackboard(node->getBlackboard()); 206 | node->addChild(child); 207 | return CompositeBuilder>(this, (CompositeType*)child.get()); 208 | } 209 | 210 | template 211 | DecoratorBuilder> decorator(Args... args) 212 | { 213 | auto child = std::make_shared((args)...); 214 | child->setBlackboard(node->getBlackboard()); 215 | node->addChild(child); 216 | return DecoratorBuilder>(this, (DecoratorType*)child.get()); 217 | } 218 | 219 | Parent& end() 220 | { 221 | return *parent; 222 | } 223 | 224 | private: 225 | Parent * parent; 226 | Composite* node; 227 | }; 228 | 229 | template 230 | class DecoratorBuilder 231 | { 232 | public: 233 | DecoratorBuilder(Parent* parent, Decorator* node) : parent(parent), node(node) {} 234 | 235 | template 236 | DecoratorBuilder leaf(Args... args) 237 | { 238 | auto child = std::make_shared((args)...); 239 | child->setBlackboard(node->getBlackboard()); 240 | node->setChild(child); 241 | return *this; 242 | } 243 | 244 | template 245 | CompositeBuilder> composite(Args... args) 246 | { 247 | auto child = std::make_shared((args)...); 248 | child->setBlackboard(node->getBlackboard()); 249 | node->setChild(child); 250 | return CompositeBuilder>(this, (CompositeType*)child.get()); 251 | } 252 | 253 | template 254 | DecoratorBuilder> decorator(Args... args) 255 | { 256 | auto child = std::make_shared((args)...); 257 | child->setBlackboard(node->getBlackboard()); 258 | node->setChild(child); 259 | return DecoratorBuilder>(this, (DecoratorType*)child.get()); 260 | } 261 | 262 | Parent& end() 263 | { 264 | return *parent; 265 | } 266 | 267 | private: 268 | Parent * parent; 269 | Decorator* node; 270 | }; 271 | 272 | class Builder 273 | { 274 | public: 275 | Builder() { 276 | tree = std::make_shared(); 277 | } 278 | template 279 | Builder leaf(Args... args) 280 | { 281 | root = std::make_shared((args)...); 282 | root->setBlackboard(tree->getBlackboard()); 283 | return *this; 284 | } 285 | 286 | template 287 | CompositeBuilder composite(Args... args) 288 | { 289 | root = std::make_shared((args)...); 290 | root->setBlackboard(tree->getBlackboard()); 291 | return CompositeBuilder(this, (CompositeType*)root.get()); 292 | } 293 | 294 | template 295 | DecoratorBuilder decorator(Args... args) 296 | { 297 | root = std::make_shared((args)...); 298 | root->setBlackboard(tree->getBlackboard()); 299 | return DecoratorBuilder(this, (DecoratorType*)root.get()); 300 | } 301 | 302 | Node::Ptr build() 303 | { 304 | assert(root != nullptr && "The Behavior Tree is empty!"); 305 | tree->setRoot(root); 306 | return tree; 307 | } 308 | 309 | private: 310 | Node::Ptr root; 311 | std::shared_ptr tree; 312 | }; 313 | 314 | // The Selector composite ticks each child node in order. 315 | // If a child succeeds or runs, the selector returns the same status. 316 | // In the next tick, it will try to run each child in order again. 317 | // If all children fails, only then does the selector fail. 318 | class Selector : public Composite 319 | { 320 | public: 321 | void initialize() override 322 | { 323 | it = children.begin(); 324 | } 325 | 326 | Status update() override 327 | { 328 | assert(hasChildren() && "Composite has no children"); 329 | 330 | while (it != children.end()) { 331 | auto status = (*it)->tick(); 332 | 333 | if (status != Status::Failure) { 334 | return status; 335 | } 336 | 337 | it++; 338 | } 339 | 340 | return Status::Failure; 341 | } 342 | }; 343 | 344 | // The Sequence composite ticks each child node in order. 345 | // If a child fails or runs, the sequence returns the same status. 346 | // In the next tick, it will try to run each child in order again. 347 | // If all children succeeds, only then does the sequence succeed. 348 | class Sequence : public Composite 349 | { 350 | public: 351 | void initialize() override 352 | { 353 | it = children.begin(); 354 | } 355 | 356 | Status update() override 357 | { 358 | assert(hasChildren() && "Composite has no children"); 359 | 360 | while (it != children.end()) { 361 | auto status = (*it)->tick(); 362 | 363 | if (status != Status::Success) { 364 | return status; 365 | } 366 | 367 | it++; 368 | } 369 | 370 | return Status::Success; 371 | } 372 | }; 373 | 374 | // The StatefulSelector composite ticks each child node in order, and remembers what child it prevously tried to tick. 375 | // If a child succeeds or runs, the stateful selector returns the same status. 376 | // In the next tick, it will try to run the next child or start from the beginning again. 377 | // If all children fails, only then does the stateful selector fail. 378 | class StatefulSelector : public Composite 379 | { 380 | public: 381 | Status update() override 382 | { 383 | assert(hasChildren() && "Composite has no children"); 384 | 385 | while (it != children.end()) { 386 | auto status = (*it)->tick(); 387 | 388 | if (status != Status::Failure) { 389 | return status; 390 | } 391 | 392 | it++; 393 | } 394 | 395 | it = children.begin(); 396 | return Status::Failure; 397 | } 398 | }; 399 | 400 | // The StatefulSequence composite ticks each child node in order, and remembers what child it prevously tried to tick. 401 | // If a child fails or runs, the stateful sequence returns the same status. 402 | // In the next tick, it will try to run the next child or start from the beginning again. 403 | // If all children succeeds, only then does the stateful sequence succeed. 404 | class StatefulSequence : public Composite 405 | { 406 | public: 407 | Status update() override 408 | { 409 | assert(hasChildren() && "Composite has no children"); 410 | 411 | while (it != children.end()) { 412 | auto status = (*it)->tick(); 413 | 414 | if (status != Status::Success) { 415 | return status; 416 | } 417 | 418 | it++; 419 | } 420 | 421 | it = children.begin(); 422 | return Status::Success; 423 | } 424 | }; 425 | 426 | class ParallelSequence : public Composite 427 | { 428 | public: 429 | ParallelSequence(bool successOnAll = true, bool failOnAll = true) : useSuccessFailPolicy(true), successOnAll(successOnAll), failOnAll(failOnAll) {} 430 | ParallelSequence(int minSuccess, int minFail) : minSuccess(minSuccess), minFail(minFail) {} 431 | 432 | Status update() override 433 | { 434 | assert(hasChildren() && "Composite has no children"); 435 | 436 | int minimumSuccess = minSuccess; 437 | int minimumFail = minFail; 438 | 439 | if (useSuccessFailPolicy) { 440 | if (successOnAll) { 441 | minimumSuccess = children.size(); 442 | } 443 | else { 444 | minimumSuccess = 1; 445 | } 446 | 447 | if (failOnAll) { 448 | minimumFail = children.size(); 449 | } 450 | else { 451 | minimumFail = 1; 452 | } 453 | } 454 | 455 | int total_success = 0; 456 | int total_fail = 0; 457 | 458 | for (auto &child : children) { 459 | auto status = child->tick(); 460 | if (status == Status::Success) { 461 | total_success++; 462 | } 463 | if (status == Status::Failure) { 464 | total_fail++; 465 | } 466 | } 467 | 468 | if (total_success >= minimumSuccess) { 469 | return Status::Success; 470 | } 471 | if (total_fail >= minimumFail) { 472 | return Status::Failure; 473 | } 474 | 475 | return Status::Running; 476 | } 477 | 478 | private: 479 | bool useSuccessFailPolicy = false; 480 | bool successOnAll = true; 481 | bool failOnAll = true; 482 | int minSuccess = 0; 483 | int minFail = 0; 484 | }; 485 | 486 | // The Succeeder decorator returns success, regardless of what happens to the child. 487 | class Succeeder : public Decorator 488 | { 489 | public: 490 | Status update() override 491 | { 492 | child->tick(); 493 | return Status::Success; 494 | } 495 | }; 496 | 497 | // The Failer decorator returns failure, regardless of what happens to the child. 498 | class Failer : public Decorator 499 | { 500 | public: 501 | Status update() override 502 | { 503 | child->tick(); 504 | return Status::Failure; 505 | } 506 | }; 507 | 508 | // The Inverter decorator inverts the child node's status, i.e. failure becomes success and success becomes failure. 509 | // If the child runs, the Inverter returns the status that it is running too. 510 | class Inverter : public Decorator 511 | { 512 | public: 513 | Status update() override 514 | { 515 | auto s = child->tick(); 516 | 517 | if (s == Status::Success) { 518 | return Status::Failure; 519 | } 520 | else if (s == Status::Failure) { 521 | return Status::Success; 522 | } 523 | 524 | return s; 525 | } 526 | }; 527 | 528 | // The Repeater decorator repeats infinitely or to a limit until the child returns success. 529 | class Repeater : public Decorator 530 | { 531 | public: 532 | Repeater(int limit = 0) : limit(limit) {} 533 | 534 | void initialize() override 535 | { 536 | counter = 0; 537 | } 538 | 539 | Status update() override 540 | { 541 | child->tick(); 542 | 543 | if (limit > 0 && ++counter == limit) { 544 | return Status::Success; 545 | } 546 | 547 | return Status::Running; 548 | } 549 | 550 | protected: 551 | int limit; 552 | int counter = 0; 553 | }; 554 | 555 | // The UntilSuccess decorator repeats until the child returns success and then returns success. 556 | class UntilSuccess : public Decorator 557 | { 558 | public: 559 | Status update() override 560 | { 561 | while (1) { 562 | auto status = child->tick(); 563 | 564 | if (status == Status::Success) { 565 | return Status::Success; 566 | } 567 | } 568 | } 569 | }; 570 | 571 | // The UntilFailure decorator repeats until the child returns fail and then returns success. 572 | class UntilFailure : public Decorator 573 | { 574 | public: 575 | Status update() override 576 | { 577 | while (1) { 578 | auto status = child->tick(); 579 | 580 | if (status == Status::Failure) { 581 | return Status::Success; 582 | } 583 | } 584 | } 585 | }; 586 | 587 | } // namespace BrainTree 588 | --------------------------------------------------------------------------------