Behaviour Trees

It’s been raining all day and night here.
I decided to spend some time playing with lezak’s BehaviorTree codebase.
After doing that for some time, I decided to find a decent editor I could use to create my trees, rather than using hardcode or handcrafting xml/json.

I came across “owl-bt” today. https://www.npmjs.com/package/owl-bt
I’ve fallen in love with its simple approach and engine-agnostic design.
Super easy to add new custom node types and even declare custom data types…
Saves to json. Hotloads your changes. Runs in your web browser.

I can see myself putting together code to load that json into lezak’s codebase, which is also highly flexible, and seems to be well thought out, other than one small issue… it’s a classical implementation of BT, and so there is no thought to data oriented design or re-use of existing subtrees…

Behaviour tree nodes that contain dynamic data are a no-no… (nodes that use constant or statically shared data are ok)…
If we do that, and by that, I mean store any dynamic values in our nodes, then we can’t easily reference entire subtrees at runtime, which means that every actor that needs a BT has to have a whole unique copy of said tree instantiated at runtime.

A perfect example is a repeater node, that holds a counter. We need to store that data outside the node, and pass it in our calling context, then we are “sweet”.

Just my two cents worth.

1 Like

Interesting find. I was searching for s.t. like it without the hassle of putting together an editor… Thx

1 Like

– referencebot

1 Like

Have started writing code to load the json saved by owl-bt
I thought I’d mention a couple of things I’ve noticed so far:

  1. Any node can have a Name - but the editor does not display them or let you set them.
  2. Node properties are not serialized if they bear the value we specified to be default (similar to Urho attributes).
  3. Manual editing of the owl-bt.json file usually (but not always) is hotloaded by the editor.
  4. Manual editing of mytree.json file is never hotloaded (refresh your browser to reload the tree)

I am still very happy with this editor because it is so easy to adapt to your custom bt node types.

Since I have access to class names, I’ve decided to let the parser attempt to construct node instances by name via class object factory method. Since I use scripting little if at all, I’ve been searching for a while for a good reason to register object factories, in a context where I could take advantage of name-based instantiation. It’s a shame we can’t register factory functions with arguments.

What is the proper way to cast a shared pointer in Urho? If I am using factory instantiation, then I get a SharedPtr, which is not really what my object type is.

@johnnycable, yeah, “something like it” was what I needed too - something that was not dedicated to an existing codebase or engine, and was easily configurable for my nefarious purposes.
I certainly wanted to avoid writing a full fledged editor, though there is some sourcecode floating around that I could have used to do so - it’s just not a good use of my time, for my project, to create custom editor solutions, when I can just rely on established stuff like xml and json, and deal with that at my end.

What I see in Urho (and what I often do) is cast the raw pointer. SharedPtr::Get() et al.

[WARNING: WORKAROUND HACK CODE AHEAD! Also - this code is not complete…]

What I was looking for was Detach()…

Urho’s object factory implementation returns a shared pointer, but deep in my recursive json parser it was not necessary or even desirable to have my object pointers wrapped at all, let alone by the insidious shared pointer (hey - I DO use them, but I want to decide when and where something gets wrapped in one of those…)

I did need to perform an upcast from Urho3D::Object to my baseclass prior to calling Detach in order to ensure I was returned the correct object type.
Also, I think I’ll rename my classes to avoid the following ugly name-mangling… owl-bt calls its classes “Sequence”, “Selector” etc., while lezak’s classes are called “SequenceNode”, “SelectorNode”, and so on - and its the C++ class name that matters when instantiating class objects by name via factory.

    BehaviorTreeNode* BehaviorTree::ParseNodeFromJSON(const Urho3D::JSONValue& jvalue){
        String nodetype =jvalue.Get("type").GetString();
        String nodename =jvalue.Get("name").GetString();

        /// Instantiate the node by typename
        SharedPtr<BehaviorTreeNode> newNode(context_->CreateObject(nodetype+"Node")->Cast<BehaviorTreeNode>());

        /// If that failed, we probably forgot to register a node class with Urho!
        if(newNode==nullptr){
            URHO3D_LOGERROR("JSON PARSER - UNHANDLED NODE TYPE: "+nodetype);
            return nullptr;
        }

        /// Process node properties (if any)
        Urho3D::JSONArray props=jvalue.Get("properties").GetArray();
        for(auto it=props.Begin(); it!=props.End(); it++)
            ParseNodePropertyFromJSON(*it);

        /// Process node decorators (if any)
        Urho3D::JSONArray decorators=jvalue.Get("decorators").GetArray();
        for(auto it=decorators.Begin(); it!=decorators.End(); it++)
            ParseNodeDecoratorFromJSON(*it);

        /// If the node we just created is a Composite type?
        CompositeNode* n=newNode->Cast<CompositeNode>();
        if(n){
            /// Call initializer method on composite type
            n->OnFactoryConstruct(this, false, nodename);

            /// Process child nodes (only Composite Nodes should have children!)
            Urho3D::JSONArray children=jvalue.Get("childNodes").GetArray();
            for(auto it=children.Begin(); it!=children.End(); it++)
                n->AddChild(ParseNodeFromJSON(*it));
        }
        else
        {
            /// TODO:
            /// Node is some kind of Leaf node...
            /// Set a breakpoint here!
            int x=0;
        }

        /// HACK:
        /// Node Factory Function gave us a SharedPtr, but we did not really want one.
        /// We know there's no other "owners" of the shared pointer...
        /// Let's detach the raw pointer from the shared pointer :)
        return newNode.Detach();

    }

My code now returns raw pointers to the caller, who is in turn responsible for storing them in smart pointer objects. This is somewhat better than trying to pass / return shared pointers across call boundaries, and all the needless construction, copying and destruction that involves.
It would be nice if the Context class / Factory implementation provided a constructor that returned a raw pointer… is there something I missed?

1 Like

The reason that I have chosen to use factory instantiation is just this: once written, the same parser/loader code will still work, even if we register new node class types to Urho, with no further changes needed in the loader (99 percent of the time).

I tried to get my JSON parser to register new class attributes - this turned out to be a fizzer for several reasons, but I found an amicable workaround in the UIElement class - it implements a serializable attribute (variantmap type) called “Variables” (vars_ membername). I could easily add such an attribute to my base BT node class, so ANY node can potentially own an arbitrary list of properties / named and typed variables which would serialize easily.
I realized quickly that I could just add “node properties” as typed variants in a serialized map - this way, my classes did not need to express any details pertaining to properties.
Now dealing with some small issues involving an incomplete typemapping between JSON and Urho.

There are a bunch of “gotchas” when working with lezak’s behaviortree code.
I’ll try to put together some kind of documentation, as the owl-bt editor has very few limitations, while lezak’s code has a number of limitations where it comes to tree topology and execution.

One example is that the (owl-bt) editor will let you add multiple Decorators to any node in the tree, while the current codebase only allows one decorator per node.

It took me most of a day to completely implement and test the following node types:

Composites: Selector, Sequence, Parallel
Actions: LogAction, WaitStepsAction
Decorators: Invert, Loop, Success, Failure, IsBlackboardValueSet, IsBlackboardValueEqual

I found testing difficult, mainly because my understanding of how a BT works is fairly different to this (stack-based) re-entrant implementation. I am trying to cope, docs on the way (“it’s a man page”)

The first “gotcha” is that lezak’s BT nodes only support ONE Decorator. If you make more in the editor, they will not be loaded by my code (room to address this).

The second “gotcha” is that Decorators are only executed by nodes whose child reported that they completed (ie, not running).

The third, is that there are corner cases where Decorators won’t run at all.
I will try to elaborate on this as my understanding increases.

My next step is to implement something missing from lezak’s codebase: Service nodes… these basically execute a script… my first real foray into a reason to use script at all, coming right up…

Since decorators only “run late”, I may also introduce the notion of “guard node” - where one decorator node may prevent the execution of the node to which it is attached.

This is not immediately a good fit with the owl-bt editor, but I can work with it, given the flexibility in the editor, and possibly petition the author to extend their work.