{"id":14991996,"url":"https://github.com/msakuta/rusty-behavior-tree-lite","last_synced_at":"2025-09-25T14:30:42.335Z","repository":{"id":64913475,"uuid":"476460143","full_name":"msakuta/rusty-behavior-tree-lite","owner":"msakuta","description":"Lightweight behavior tree implementation in Rust","archived":false,"fork":false,"pushed_at":"2023-11-18T13:42:14.000Z","size":225,"stargazers_count":8,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-09-25T16:09:07.987Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/msakuta.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-03-31T20:03:12.000Z","updated_at":"2024-07-09T10:13:58.000Z","dependencies_parsed_at":"2023-02-18T15:31:12.967Z","dependency_job_id":null,"html_url":"https://github.com/msakuta/rusty-behavior-tree-lite","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msakuta%2Frusty-behavior-tree-lite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msakuta%2Frusty-behavior-tree-lite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msakuta%2Frusty-behavior-tree-lite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msakuta%2Frusty-behavior-tree-lite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/msakuta","download_url":"https://codeload.github.com/msakuta/rusty-behavior-tree-lite/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234200156,"owners_count":18795139,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-09-24T15:00:38.358Z","updated_at":"2025-09-25T14:30:36.947Z","avatar_url":"https://github.com/msakuta.png","language":"Rust","funding_links":[],"categories":["Data Structure and Algorithm"],"sub_categories":[],"readme":"\n![logo](vscode-ext/images/icon.png)\n\n# behavior-tree-lite (Rust crate)\n\nAn experimental Rust crate for minimal behavior tree implementation.\n\n![image](vscode-ext/images/screenshot00.png)\n\n## Overview\n\nThis is an implementation of behavior tree in Rust, inspired by [BehaviorTreeCPP](https://github.com/BehaviorTree/BehaviorTree.CPP.git).\n\nA behavior tree is an extension to finite state machines that makes describing transitional behavior easier.\nSee [BehaviorTreeCPP's documentation](https://www.behaviortree.dev/) for the thorough introduction to the idea.\n\nSee the historical notes at the bottom of this README.md for more full history.\n\n## How it looks like\n\nFirst, you define the state with a data structure.\n\n```rust\nstruct Arm {\n    name: String,\n}\n\nstruct Body {\n    left_arm: Arm,\n    right_arm: Arm,\n}\n\nlet body = Body {\n    left_arm: Arm {\n        name: \"leftArm\".to_string(),\n    },\n    right_arm: Arm {\n        name: \"rightArm\".to_string(),\n    },\n};\n```\n\nThen register the data to the context.\n\n```rust\nlet mut ctx = Context::default();\nctx.set(\"body\", body);\n```\n\nThen, you define a behavior tree.\nNote that `add_child` method takes a mapping of blackboard variables as the second argument.\n\n```rust\nlet mut root = BehaviorNodeContainer::new_node(SequenceNode::default());\nroot.add_child(BehaviorNodeContainer::new_node(PrintBodyNode));\n\nlet mut print_arms = BehaviorNodeContainer::new_node(SequenceNode::default());\nprint_arms.add_child(BehaviorNodeContainer::new(Box::new(PrintArmNode), hash_map!(\"arm\" =\u003e \"left_arm\")));\nprint_arms.add_child(BehaviorNodeContainer::new(Box::new(PrintArmNode), hash_map!(\"arm\" =\u003e \"right_arm\")));\n\nroot.add_child(print_arms);\n```\n\nand call `tick()`.\n\n```rust\nlet result = root.tick(\u0026mut |_| None, \u0026mut ctx);\n```\n\nThe first argument to the `tick` has weird value `\u0026mut |_| None`.\nIt is a callback for the behavior nodes to communicate with the environment.\nYou could supply a closure to handle messages from the behavior nodes.\nThe closure, aliased as `BehaviorCallback`, takes a `\u0026dyn std::any::Any` and returns a `Box\u003cdyn std::any::Any\u003e`,\nwhich allows the user to pass or return any type, but in exchange, the user needs to\ncheck the type with `downcast_ref` in order to use it like below.\n\n```rust\ntree.tick(\n    \u0026mut |v: \u0026dyn std::any::Any| {\n        res.push(*v.downcast_ref::\u003cbool\u003e().unwrap());\n        None\n    },\n    \u0026mut Context::default(),\n)\n```\n\nThis design was adopted because there is no other good ways to communicate between behavior nodes and the envrionment _whose lifetime is not 'static_.\n\nIt is easy to communicate with global static variables, but users often want\nto use behavior tree with limited lifetimes, like enemies' AI in a game.\nBecause you can't name a lifetime until you actually use the behavior tree,\nyou can't define a type that can send/receive data with arbitrary type having\nlifetime shorter than 'static.\n`std::any::Any` can't circumvent the limitation, because it is also bounded by 'static lifetime,\nso as soon as you put your custom payload into it, you can't put any references other than `\u0026'static`.\n\nWith a closure, we don't have to name the lifetime and it will clearly outlive the duration of the closure body, so we can pass references around.\n\nOf course, you can also use blackboard variables, but they have the same limitation of lifetimes; you can't pass a reference through blackboard.\nA callback is much more direct (and doesn't require indirection of port names)\nway to communicate with the environment.\n\n## How to define your own node\n\nThe core of the library is the `BehaviorNode` trait.\nYou can implement the trait to your own type to make a behavior node of your own.\n\nIt is very similar to BehaviorTreeCPP.\nFor example a node to print the name of the arm can be defined like below.\n\n```rust\nstruct PrintArmNode;\n\nimpl BehaviorNode for PrintArmNode {\n    fn tick(\u0026mut self, _arg: BehaviorCallback, ctx: \u0026mut Context) -\u003e BehaviorResult {\n        println!(\"Arm {:?}\", ctx);\n\n        if let Some(arm) = ctx.get::\u003cArm\u003e(\"arm\") {\n            println!(\"Got {}\", arm.name);\n        }\n        BehaviorResult::Success\n    }\n}\n```\n\nIn order to pass the variables, you need to set the variables to the blackboard.\nThis is done by `Context::set` method.\n\n```rust\nstruct PrintBodyNode;\n\nimpl BehaviorNode for PrintBodyNode {\n    fn tick(\u0026mut self, _arg: BehaviorCallback, ctx: \u0026mut Context) -\u003e BehaviorResult {\n        if let Some(body) = ctx.get::\u003cBody\u003e(\"body\") {\n            let left_arm = body.left_arm.clone();\n            let right_arm = body.right_arm.clone();\n            println!(\"Got Body: {:?}\", body);\n            ctx.set(\"left_arm\", left_arm);\n            ctx.set(\"right_arm\", right_arm);\n            BehaviorResult::Success\n        } else {\n            BehaviorResult::Fail\n        }\n    }\n}\n```\n\n### Optimizing port access by caching symbols\n\nIf you use ports a lot, you can try to minimize the cost of comparing and finding the port names as strings by using symbols.\nSymbols are pointers that are guaranteed to compare equal if they point to the same string.\nSo you can simply compare the address to check the equality of them.\n\nYou can use `Lazy\u003cSymbol\u003e` to use cache-on-first-use pattern on the symbol like below.\n`Lazy` is re-exported type from `once_cell`.\n\n```rust\nuse ::behavior_tree_lite::{\n    BehaviorNode, BehaviorResult, BehaviorCallback, Symbol, Lazy, Context\n};\n\nstruct PrintBodyNode;\n\nimpl BehaviorNode for PrintBodyNode {\n    fn tick(\u0026mut self, _: BehaviorCallback, ctx: \u0026mut Context) -\u003e BehaviorResult {\n        static BODY_SYM: Lazy\u003cSymbol\u003e = Lazy::new(|| \"body\".into());\n        static LEFT_ARM_SYM: Lazy\u003cSymbol\u003e = Lazy::new(|| \"left_arm\".into());\n        static RIGHT_ARM_SYM: Lazy\u003cSymbol\u003e = Lazy::new(|| \"right_arm\".into());\n\n        if let Some(body) = ctx.get::\u003cBody\u003e(*BODY_SYM) {\n            // ...\n            BehaviorResult::Success\n        } else {\n            BehaviorResult::Fail\n        }\n    }\n}\n```\n\n### Provided ports\n\nYou can declare what ports you would use in a node by defining `provided_ports` method.\nThis is optional, and only enforced if you specify `check_ports` argument in the `load` function explained later.\nHowever, declaring provided_ports will help statically checking the code and source file consistency, so is generally encouraged.\n\n```rust\nuse ::behavior_tree_lite::{\n    BehaviorNode, BehaviorCallback, BehaviorResult, Context, Symbol, Lazy, PortSpec\n};\n\nstruct PrintBodyNode;\n\nimpl BehaviorNode for PrintBodyNode {\n    fn provided_ports(\u0026self) -\u003e Vec\u003cPortSpec\u003e {\n        vec![PortSpec::new_in(\"body\"), PortSpec::new_out(\"left_arm\"), PortSpec::new_out(\"right_arm\")]\n    }\n\n    fn tick(\u0026mut self, _: BehaviorCallback, ctx: \u0026mut Context) -\u003e BehaviorResult {\n        // ...\n    }\n}\n```\n\nSee [example code](examples/main.rs) for the full code.\n\n### Loading the tree structure from a yaml file (deprecated)\n\nDeprecated in favor of \u003ca href=\"#The custom config file format\"\u003ethe custom config file format\u003c/a\u003e.\nIt doesn't have much advantage over our custom format, except that it can be parsed by any yaml parser library (not limited to Rust).\nHowever, parsing is only a small part of the whole process of loading dynamically configured behavior tree.\nThere are validation of port mapping and specifying input/output,\nwhich is not any easier with yaml.\nOur custom format has much more flexibility in load-time validation and\nerror handling.\n\nYou can define the tree structure in a yaml file and configure it on runtime.\nYaml file is very good for writing human readable/writable configuration file.\nIt also looks similar to the actual tree structure.\n\n```yaml\nbehavior_tree:\n  type: Sequence\n  children:\n  - type: PrintBodyNode\n  - type: Sequence\n    children:\n    - type: PrintArmNode\n      ports:\n        arm: left_arm\n    - type: PrintArmNode\n      ports:\n        arm: right_arm\n```\n\nIn order to load a tree from a yaml file, you need to register the node types\nto the registry.\n\n```rust\nlet mut registry = Registry::default();\nregistry.register(\"PrintArmNode\", boxify(|| PrintArmNode));\nregistry.register(\"PrintBodyNode\", boxify(|| PrintBodyNode));\n```\n\nSome node types are registered by default, e.g. `SequenceNode` and `FallbackNode`.\n\n## The custom config file format\n\nWe have specific file format for describing behavior tree structure of our own.\nThe usual file extension for this file format is `.btc` (behavior tree config).\nSee the [VSCode extension](https://marketplace.visualstudio.com/items?itemName=msakuta.rusty-behavior-tree-lite) for syntax highlighting.\n\nWith this format, the same tree shown as YAML earlier can be written even more concisely like below.\n\n```\ntree main = Sequence {\n  PrintBodyNode\n  Sequence {\n    PrintArmNode (arm \u003c- left_arm)\n    PrintArmNode (arm \u003c- right_arm)\n  }\n}\n```\n\nIt can be converted to an AST with `parse_file` function.\nThe AST (Abstract Syntax Tree) is a intermediate format of behavior tree,\nfrom which you can instantiate actual behavior trees as many times as you want.\nNote that the AST borrows lifetime of the argument string, so you cannot free the\nsource string before the AST.\n\n```rust\nlet (_, tree_source) = parse_file(source_string)?;\n```\n\nand subsequently be instantiated to a tree.\nThe second argument `registry` is the same as yaml parser.\nThe third argument `check_ports` will switch port direction checking during loading.\nIf your `BehaviorNode::provided_ports` and the source file's direction arrow (`\u003c-`, `-\u003e` or `\u003c-\u003e`) disagree, it will become an error.\n\n```rust\nlet tree = load(\u0026tree_source, \u0026registry, check_ports)?;\n```\n\n\n### Line comments\n\nYou can put a line comment starting with a hash (`#`).\n\n```\n# This is a comment at the top level.\n\ntree main = Sequence { # This is a comment after opening brace.\n           # This is a comment in a whole line.\n    var a  # This is a comment after a variable declaration.\n    Yes    # This is a comment after a node.\n}          # This is a comment after a closing brace.\n```\n\n\n### Node definition\n\nA node can be specified like below.\nIt starts with a node name defined in Rust code.\nAfter that, it can take an optional list of input/output ports in parentheses.\n\n```\nPrintString (input \u003c- \"Hello, world!\")\n```\n\nThe direction of the arrow in the ports specify input, output or inout port types.\nThe left hand side of the arrow is the port name defined in the node.\nThe right hand side is the blackboard variable name or a literal.\n\n```\na \u003c- b      input port\na -\u003e b      output port\na \u003c-\u003e b     inout port\n```\n\nYou can specify a literal string, surrounded by double quotes, to an input port,\nbut specifying a literal to output or inout node is an error.\nThe type of the literal is always a string, so if you want a number,\nyou may want to use `Context::get_parse()` method, which will automatically try\nto parse from a string, if the type was not desired one.\n\n```\na \u003c- \"Hi!\"\na -\u003e \"Error!\"\na \u003c-\u003e \"Error too!\"\n```\n\nIt is an error to try to read from an output port or write to an input port,\nbut inout port can do both.\n\n\n### Child nodes\n\nA node can have a list of child nodes in braces.\n\n```\nSequence {\n    PrintString (input \u003c- \"First\")\n    PrintString (input \u003c- \"Second\")\n}\n```\n\nOr even both ports and children.\n\n```\nRepeat (n \u003c- \"100\") {\n    PrintString (input \u003c- \"Spam\")\n}\n```\n\n### Subtrees\n\nIt is very easy to define subtrees and organize your huge tree into modular structure.\n\n```\ntree main = Sequence {\n    CanICallSubTree\n    SubTree\n}\n\ntree SubTree = Sequence {\n    PrintString (input \u003c- \"Hi there!\")\n}\n```\n\nA subtree has its own namespace of blackboard variables.\nIt will keep the blackboard from being a huge table of global variables.\n\nIf you need blackboard variables to communicate between the parent tree\nand the subtree, you can put parentheses and a comma-separated list\nafter subtree name to specify the port definition of a subtree.\n\nA port \"parameter\" can be prefixed by either `in`, `out` or `inout`.\nIt will indicate the direction of data flow.\n\nThe syntax is intentionally made similar to a function definition, because\nit really is.\n\n```\ntree main = Sequence {\n    SubTree (input \u003c- \"42\", output -\u003e subtreeResult)\n    PrintString (input \u003c- subtreeResult)\n}\n\ntree SubTree(in input, out output) = Sequence {\n    Calculate (input \u003c- input, result -\u003e output)\n}\n```\n\n\n### Conditional syntax\n\nLike a programming language, the format supports conditional syntax.\n\n```\ntree main = Sequence {\n    if (ConditionNode) {\n        Yes\n    }\n}\n```\n\nIf the `ConditionNode` returns `Success`, the inner braces are ticked\nas a Sequence, otherwise it skips the nodes.\n\nIt is not an `if` statement per se, because behavior tree does not have\nthe concept of a statement.\nIt is internally just a behavior node, having a special syntax for the\nease of editing and understanding.\n\nThe code above desugars into this:\n\n```\ntree main = Sequence {\n    if {\n        ConditionNode\n        Sequence {\n            Yes\n        }\n    }\n}\n```\n\nAs you may expect, you can put `else` clause.\n\n```\ntree main = Sequence {\n    if (ConditionNode) {\n        Yes\n    } else {\n        No\n    }\n}\n```\n\n`if` is a built-in node type, which can take 2 or 3 child nodes.\nThe first child is the condition, the second is the `then` clause, and\nthe optional third child is the `else` clause.\n`then` and `else` clause are implicitly wrapped in a `Sequence`.\n\nThe syntax also supports negation operator (`!`) in front of the condition node.\nThis code below is\n\n```\ntree main = Sequence {\n    if (!ConditionNode) {\n        Yes\n    }\n}\n```\n\nequivalent to this one:\n\n```\ntree main = Sequence {\n    if (ConditionNode) {\n    } else {\n        Yes\n    }\n}\n```\n\nYou can put logical operators (`\u0026\u0026` and `||`) like conditional expressions in programming languages.\n`\u0026\u0026` is just a shorthand for a Sequence node and `||` is a Fallback node.\n\n```raw\ntree main = Sequence {\n    if (!a || b \u0026\u0026 c) {}\n}\n```\n\nIn fact, a child node is implicitly a logical expression, so you can write like this:\n\n```raw\ntree main = Sequence {\n    !a || b \u0026\u0026 c\n}\n```\n\nParentheses can be used to group operators.\n\n```raw\ntree main = Sequence {\n    (!a || b) \u0026\u0026 c\n}\n```\n\n`if` node without else clause is semantically the same as a Sequence node like below,\nbut Sequence or Fallback nodes cannot represent `else` clause easily.\n\n```\ntree main = Sequence {\n    Sequence {\n        ConditionNode\n        Sequence {\n            Yes\n        }\n    }\n}\n```\n\n\n### Blackboard variable declarations\n\nYou can optionally declare and initialize a blackboard variable.\nIt can be used as a node name, and its value is evaluated as a boolean.\nSo, you can put the variable into a `if` condition.\n\n```\ntree main = Sequence {\n    var flag = true\n    if (flag) {\n        Yes\n    }\n}\n```\n\nCurrently, only `true` or `false` is allowed as the initializer (the right hand side of `=`).\n\nThe variable declaration with initialization will desugar into a `SetBool` node.\nA reference to a variable name will desugar into a `IsTrue` node.\n\n```\ntree main = Sequence {\n    SetBool (value \u003c- \"true\", output -\u003e flag)\n    if (IsTrue (input \u003c- flag)) {\n        Yes\n    }\n}\n```\n\nHowever, it is necessary to declare the variable name in order to use it as a\nvariable reference.\nFor example, the code below will be a `load` error for `MissingNode`, even though\nthe variable is set with `SetBool`.\n\n```\ntree main = Sequence {\n    SetBool (value \u003c- \"true\", output -\u003e flag)\n    if (flag) {\n        Yes\n    }\n}\n```\n\nThis design is a step towards statically checked source code.\n\n\n### Variable assignment\n\nA variable can be assigned value with this syntax:\n\n```\nvalue = true\n```\n\nCurrently, only `true` or `false` can be used as the initializer.\n\nIt is a syntax sugar like below, but without variable declaration.\n\n```\nSetBool (value \u003c- \"true\", output -\u003e value)\n```\n\n\n### Syntax specification\n\nHere is a pseudo-EBNF notation of the syntax.\n\nNote that this specification is by no means accurate EBNF.\nThe syntax is defined by recursive descent parser with parser combinator,\nwhich removes ambiguity, but this EBNF may have ambiguity.\n\n```\ntree = \"tree\" tree-name [ \"(\" tree-port-list \")\" ] \"=\" node\n\ntree-port-list = port-def | tree-port-list \",\" port-def\n\nport-def = ( \"in\" | \"out\" | \"inout\" ) tree-port-name\n\ntree-port-name = identifier\n\nnode = if-syntax | conditional | var-def-syntax | var-assign\n\nif-syntax = \"if\" \"(\" conditional \")\"\n\nconditional-factor = \"!\" conditional-factor | node-syntax\n\nconditional-and =  conditional-factor | conditional \"\u0026\u0026\" conditional-factor\n\nconditional =  conditional-and | conditional \"||\" conditional-and\n\nnode-syntax = node-name [ \"(\" port-list \")\" ] [ \"{\" node* \"}\" ]\n\nport-list = port [ \",\" port-list ]\n\nport = node-port-name (\"\u003c-\" | \"-\u003e\" | \"\u003c-\u003e\") blackboard-port-name\n\nnode-port-name = identifier\n\nblackboard-port-name = identifier\n\nvar-def-syntax = \"var\" identifier \"=\" initializer\n\nvar-assign = identifier \"=\" initializer\n\ninitializer = \"true\" | \"false\"\n```\n\n## TODO\n\n* [x] Easier way to define constructors (macros?)\n* [ ] Full set of control nodes\n  * [x] Reactive nodes\n  * [ ] Star nodes\n  * [x] Decorator nodes\n* [x] Performance friendly blackboard keys\n* [x] DSL for defining behavior tree structure\n  * [x] Programming language-like flow control syntax\n* [ ] Static type checking for behavior tree definition file\n\n\n\n# Historical notes\n\nThis is a sister project of [tiny-behavior-tree](https://github.com/msakuta/rusty_tiny_behavior_tree) which in turn inspired by [BehaviorTreeCPP](https://github.com/BehaviorTree/BehaviorTree.CPP.git).\n\nWhile tiny-behavior-tree aims for more innovative design and experimental features, this crate aims for more traditional behavior tree implementation.\nThe goal is to make a crate lightweight enough to use in WebAssembly.\n\n## The difference from tiny-behavior-tree\n\nThe main premise of tiny-behavior-tree is that it passes data with function arguments.\nThis is very good for making fast and small binary, but it suffers from mixed node types in a tree.\n\nIt requires ugly boilerplate code or macros to convert types between different node argument types, and there is the concept of \"PeelNode\" which would be unnecessary in traditional behavior tree design.\n\nOn top of that, uniform types make it much easier to implement configuration file parser that can change the behavior tree at runtime.\n\n\n## Performance consideration\n\nOne of the issues with behavior tree in general regarding performance is that the nodes communicate with blackboard variables, which is essentially a key-value store.\nIt is not particularly bad, but if you read/write a lot of variables in the blackboard (which easily happens with a large behavior tree), you would pay the cost of constructing a string and looking up HashMap every time.\n\nOne of the tiny-behavior-tree's goals is to address this issue by passing variables with function call arguments.\nWhy would you pay the cost of looking up HashMap if you already know the address of the variable?\n\nAlso, the blackboard is not very scalable, since it is essentially a huge table of global variables.\nAlthough there is sub-blackboards in subtrees, it is difficult to keep track of similar to scripting language's stack frame without proper debugging tools.\n\nI might experiment with non-string keys to make it more efficient, but the nature of the variables need to be handled dynamically in uniformly typeds nodes.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsakuta%2Frusty-behavior-tree-lite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmsakuta%2Frusty-behavior-tree-lite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsakuta%2Frusty-behavior-tree-lite/lists"}