What's new
  • Visit Rebornbuddy
  • Visit Panda Profiles
  • Visit LLamamMagic
  • Visit Resources
  • Visit Downloads
  • Visit Portal

Behaviour Trees and Object Scopt

spriestdev

New Member
Joined
Sep 19, 2011
Messages
236
Reaction score
11
Hey guys, can someone help me understand something about behaviour trees.

I have a pretty good understanding of the way that bt's work for the most part. One area that I'm having trouble with is how they interact with the lifetime of collections.

Let me do my best to explain my scenario.

I want to build a composite that is responsible for choosing a target. In this hypothetical composite lets say there are 10 different priority selectors for choosing a target based on different criteria. Most of these priority selectors at some point are making a call to the same function to return a list of nearby targets.

My understanding is that calling the same function multiple times per section while evaluating the bt composites is a performance hit. At what point should I be caching & expiring that list of nearby targets so that I'm still getting an accurate list of targets but not making tons of requests that aren't needed.

Any thoughts?
 
Heya

That's what I do for my RogueAssassin CC. I have not noticed any negative side effects to caching values that I'm reusing in the same walk. I reset my cache at the beginning of the walk. Feel free to visit my github. Check the cc topic in Rogue subforum for the link. I'd link you, but that's harder to accomplish on a phone.
 
Thanks man I took a quick look through it (at work) and I like the way you handled that. Thanks for the heads up.
 
I guess the next question would be. Does the object manager already handle this on its own, and is it necessary for me to worry about caching these collections.
 
I guess one thing to think about is how you are using any of the wait composites with this pattern.
 
From my experiments, I've concluded the everything you call from objectmanager is already in memory and there is no real reason for caching it apart from keeping a local, easy to reach copy, or to avoid excessive looping (ie, if you have to use linq to find your object, cache it).

However, that leads you with the issue that some of your values win be out of date when the combat by starts its iteration, such as energy for a rogue. In cases such as those, you should instead call a lua function directly to get your data and cache it. Again, see my github for an example. That way your data is up-to-date and accurate within milliseconds.

Incidentally, I arrived at this conclusion by testing methods to determine if the rogue is behind he target. After finding the methods and properties that were accurate in terms of backstab, I then ran a benchmark on each using the stopwatch object. None of the methods or properties called from objectmanager took a millisecond to complete. A lua call takes roughly 60ms, though that's likely to depend on your ping.

Sent from my SPH-D710
 
the object manager updates with every pulse. so if your having problems with it not finding things its because it hasnt had a chance to update yet, you can always force the objectmanager to update by doing (i think its) ObjectManager.Update(). sometimes this is needed depending on how you program.
 
I did see the update method in the reference but thought it would be a very bad idea to call it at the beginning of each combat bt iteration. Has anyone quantified the performance hit of calling the update method? I would think that you would only want to call it very sparingly, and that wouldn't be the case when the issue is keeping your energy value up-to-date.

Sent from my SPH-D710
 
There's no real reason to call ObjectManager.Update(). We offer the method mostly for those writing plugins that work in their own threads, etc. (And require object updates at different times. Such as when the bot is stopped.)

The only thing the Update() func does, is refreshes/cleans out entries in the cached objects list. (Basically; it ensures references for WoWObject's are still valid, and adds any new ones, removes invalid ones.)


If you find yourself calling the same "iteration" func many times in a decorator (which you should be avoiding, as you want decorators to be as fast as possible) you need to look into using the context property for composites. An example of such usage is as following;


Code:
return new PrioritySelector(
                ctx => TankManager.Instance.FirstUnit ?? StyxWoW.Me.CurrentTarget,
                new Decorator(
                    ret => StyxWoW.Me.Shapeshift != ShapeshiftForm.Bear,
                    Spell.BuffSelf("Bear Form")),
Spell.Cast("Pulverize", ret => ((WoWUnit)ret).HasAura("Lacerate", 3) && !StyxWoW.Me.HasAura("Pulverize")),


At the moment, only PrioritySelector offers an "easy context change" without creating a derived class.


In short; when you change the context of a parent composite, that context flows down the tree to every child. However, changing the context in a child composite, does NOT change the context of a parent node. This will allow you to run your enumerate once, then check the value of that enumeration in all children.
 
Thanks Apoc. I've seen you use the ctx in your singular composites and had a feeling they had something to do with. I guess the main question I have is how the child composites actually reference the context property.

In altarboy, this is currently how targeting is being handled. It's working for now, but I the implementation doesn't feel very optimized.

PHP:
 public static Composite Target()
        {
           return new PrioritySelector(                        
            
            new Decorator(ret => AltarboySettings.Instance.EnableTargeting,
                new Sequence(  
                    //cache possible targets
                    new Action(delegate {
                       TargetManager.AcquireTargets();
                    }),
                    new PrioritySelector(
                       TargetManager.SelectFocusTarget(),
                       TargetManager.SelectExecuteTarget(),
                       TargetManager.SelectFriendlyMindSearTarget(),
                       TargetManager.SelectMultiDotTarget(),
                       TargetManager.SelectLeadersTarget(),
                       TargetManager.SelectClosestTarget()
                    ),
                    //reset target cache
                      new Action(delegate {
                       TargetManager.ResetTargets();
                    })
                 )
             ),

             new ActionAlwaysSucceed()

           );
        }

Each of the child composites in selector are using use a cached version of possible targets (enemies within 40 yards) Previous versions of this were looking up enemies in each composite. Any suggestions on how I could rewrite this using the context property? Sorry having a hard time grasping the context property.

Appreciate the help.
 
Well, your current version does not appear to be context-friendly, since you're handling all of your targeting via static functions in a static class. I'm going to break down Apoc's example to the best of my ability (right now I can't test it to "validate" this, so he might have to come in and correct me).

Before I go on, though, let me specify that when I say 'iteration', I'm talking about a distinct walk of the BT. ie, when the PrioritySelector finds a successful child, its iteration is over. When the PrioritySelector is called/walked again, it is a new iteration. I do not use iteration in context with calling the same function multiple times during the same walk (which you shouldn't do, unless the parameters are different).

Code:
ctx => TankManager.Instance.FirstUnit ?? StyxWoW.Me.CurrentTarget,

This is the first argument to the PrioritySelector. Notice that it's not a boolean. The context is set to an instance of the WoWUnit class, either the TankManager instance's FirstUnit, or, if null, the current target.

Code:
ret => ((WoWUnit)ret).HasAura("Lacerate", 3)

This is the use of his context (called ret here). ret is the inherited context of the parent PrioritySelector (although, as you can see from the necessary cast, the child does not appear to know the exact type of the parent context).

Anyway, the point is that the context needs an instantiated object / collection to pass to its children. However, you appear to hold the collection within your TargetManager static class as a static member.

If you wanted to use a context, I would do something like the following. NOTE that this is just an EXAMPLE of how you could write it. However, this code is pretty "scrappy" IMO. I wouldn't recommend using it like this.

Code:
new Decorator(ret => AltarboySettings.Instance.EnableTargeting,
	new PrioritySelector(ctx => TargetManager.AquireTargets(), 
	// Returns an instance of TargetManager w/ the list of your enemies in 40 yds
		new Decorator(ret => ((TargetManager)ret).FocusTarget != null
                             && TargetManager.SetTarget((TargetManager)ret).FocusTarget),
		//  Succeeds if the instantiated TargetManager.FocusTarget is not null and TargetManager.SetTarget succeeded in setting
		//  ret.FocusTarget as the target.  FocusTarget property might include its own logic that only returns a WoWUnit if
		//	a focus exists and it's a valid target for your purposes.
		...

What I would recommend instead, in your case, is to have a method that returns the first valid unit from your PrioritySelector (instead of having it as a Selector / BehaviorTree) as a WoWUnit, and using THAT as your context instead. It's worth noting that you don't necessarily have to be targeting something in order to cast a spell at it. You might be able to save yourself some time by not forcing the toon to change targets until its primary target dies, or else has to change for some other reason.

BUT take all that with a grain of salt. I've yet to start studying Apoc's CCs, which I would actually recommend / will start using as an official point of reference. While I feel that my current cc is functioning successfully, I can in no way claim any real knowledge or even comfortable understanding of the HB API. I think it can use a lot more documentation (although, of course, I understand completely why a concurrent one does not exist, as documentation of an API takes a LOT of time to write and flesh out properly).

As an afterthought, perhaps we CC/plugin coders should take it upon ourselves to help update the wiki with the parts of the API that we know or at least of which we feel we have a thorough understanding. I don't know what the rules are for updating the wiki, but I think an organized, concentrated effort should be made to have an up-to-date reference to these kinds of questions. Forums are nice, but they can be hard to sort through.
 
Well, your current version does not appear to be context-friendly, since you're handling all of your targeting via static functions in a static class. I'm going to break down Apoc's example to the best of my ability (right now I can't test it to "validate" this, so he might have to come in and correct me).

Before I go on, though, let me specify that when I say 'iteration', I'm talking about a distinct walk of the BT. ie, when the PrioritySelector finds a successful child, its iteration is over. When the PrioritySelector is called/walked again, it is a new iteration. I do not use iteration in context with calling the same function multiple times during the same walk (which you shouldn't do, unless the parameters are different).

Code:
ctx => TankManager.Instance.FirstUnit ?? StyxWoW.Me.CurrentTarget,

This is the first argument to the PrioritySelector. Notice that it's not a boolean. The context is set to an instance of the WoWUnit class, either the TankManager instance's FirstUnit, or, if null, the current target.

Code:
ret => ((WoWUnit)ret).HasAura("Lacerate", 3)

This is the use of his context (called ret here). ret is the inherited context of the parent PrioritySelector (although, as you can see from the necessary cast, the child does not appear to know the exact type of the parent context).

Anyway, the point is that the context needs an instantiated object / collection to pass to its children. However, you appear to hold the collection within your TargetManager static class as a static member.

If you wanted to use a context, I would do something like the following. NOTE that this is just an EXAMPLE of how you could write it. However, this code is pretty "scrappy" IMO. I wouldn't recommend using it like this.

Code:
new Decorator(ret => AltarboySettings.Instance.EnableTargeting,
	new PrioritySelector(ctx => TargetManager.AquireTargets(), 
	// Returns an instance of TargetManager w/ the list of your enemies in 40 yds
		new Decorator(ret => ((TargetManager)ret).FocusTarget != null
                             && TargetManager.SetTarget((TargetManager)ret).FocusTarget),
		//  Succeeds if the instantiated TargetManager.FocusTarget is not null and TargetManager.SetTarget succeeded in setting
		//  ret.FocusTarget as the target.  FocusTarget property might include its own logic that only returns a WoWUnit if
		//	a focus exists and it's a valid target for your purposes.
		...

What I would recommend instead, in your case, is to have a method that returns the first valid unit from your PrioritySelector (instead of having it as a Selector / BehaviorTree) as a WoWUnit, and using THAT as your context instead. It's worth noting that you don't necessarily have to be targeting something in order to cast a spell at it. You might be able to save yourself some time by not forcing the toon to change targets until its primary target dies, or else has to change for some other reason.

BUT take all that with a grain of salt. I've yet to start studying Apoc's CCs, which I would actually recommend / will start using as an official point of reference. While I feel that my current cc is functioning successfully, I can in no way claim any real knowledge or even comfortable understanding of the HB API. I think it can use a lot more documentation (although, of course, I understand completely why a concurrent one does not exist, as documentation of an API takes a LOT of time to write and flesh out properly).

As an afterthought, perhaps we CC/plugin coders should take it upon ourselves to help update the wiki with the parts of the API that we know or at least of which we feel we have a thorough understanding. I don't know what the rules are for updating the wiki, but I think an organized, concentrated effort should be made to have an up-to-date reference to these kinds of questions. Forums are nice, but they can be hard to sort through.

We're pretty lax on the wiki rules. Honorbuddy & Gatherbuddy Wiki - Buddy Wiki Scroll down to the bottom to see our policy on wiki edits/content.

And yes, you're correct about the usage of the context.

However, I'd like to point out (for clarity) why "ret => ((WoWUnit)ret)" works.

Firstly; if you don't know what a "lambda expression" is, I highly suggest reading up on it. I'll give a quick rundown on it for the case of TreeSharp (HBs Behavior Tree implementation).

A delegate is a "function pointer" of sorts, which defines a function signature.
Code:
public delegate object ContextGrabberDelegate(object context);

You can use a delegate in many ways. Assigning a value to a variable for the method sig.

Code:
public ContextGrabberDelegate MyContextGrabber = MyContextGrabberFunc;
public object MyContextGrabberFunc(object context) { /* do something */ }

Creating an "anonymous delegate" call
Code:
MyContextGrabber = delegate(object context) { /* do something */ }

Or via lambda expressions

Code:
MyContextGrabber = context => context /* do something more useful than return context */;

We just shortcut this with the TreeSharp API to make it easier to use. You'll often find people using "ret" as the context argument (which admittedly is my own fault, as I started that trend before context usage was more widespread).

Basically; the delegate will allow you to define a function, without fully defining the function. (Hence why Action's allow you to do things like new Action(ret=> SpellManager.Cast("Some spell")) )
 
Back
Top