Parallel Task flips execution order on the second frame

abc123

Member
I ran into a strange problem. My behavior tree works as expected on the 1st frame, but then it starts to run tasks from right to left, which is a complete disaster :rolleyes: Some of my tasks depend on the calculations made in previous tasks.. And I strictly need Send Output to be processed the last on every tick.

The right order is marked in green.
111.PNG

Yes, I use parallels. So what.. Shouldn't parallels also run their children from left to right?

Justin, please explain what's happening. I attached the scene, see the logs.
 

Attachments

Last edited:

Justin

Administrator
Staff member
The parallel branch works by adding a new element to the list of branches to run. On subsequent runs this branch is executed from the last element to the first element. This is done so you can have later branches interrupt earlier branches.

In the past I have thought about changing this so it executes in the left to right order but have decided against it to keep the tree execution consistent across updates.
 

abc123

Member
The parallel branch works by adding a new element to the list of branches to run. On subsequent runs this branch is executed from the last element to the first element. This is done so you can have later branches interrupt earlier branches.

In the past I have thought about changing this so it executes in the left to right order but have decided against it to keep the tree execution consistent across updates.
This is a pity. But what should I do now, how to arrange the tree in strict order.. Any tips on writing custom parallel class to always run from left to right?

May be you could introduce a checkbox in preferences, kind of "inverse parallels"? :rolleyes:
 
Last edited:

Justin

Administrator
Staff member
I think that you should be able to create a new parallel task that moves the currentChildIndex from last to first instead of first to last. OnChildStarted would decrement the currentChildIndex, and OnConditionalAbort/OnEnd would reset it back to the last child index. You'll also want to initialize it to the last child index within Awake.
 

abc123

Member
I think that you should be able to create a new parallel task that moves the currentChildIndex from last to first instead of first to last. OnChildStarted would decrement the currentChildIndex, and OnConditionalAbort/OnEnd would reset it back to the last child index. You'll also want to initialize it to the last child index within Awake.
Ok, thanks for the advice, I will try this.

By the way, I wish fields like currentChildIndex were protected. So we could derive from the class and change what we need, instead of copying the whole code to another class.

111.PNG
 

abc123

Member
Well, it works, but... man do I hate the idea to hack every parallel class like that. And of course, now it's wrong order on the first frame. :(

I will post a feature request, I hope we can find a solution in a short time.
 

abc123

Member
I think that you should be able to create a new parallel task that moves the currentChildIndex from last to first instead of first to last. OnChildStarted would decrement the currentChildIndex, and OnConditionalAbort/OnEnd would reset it back to the last child index. You'll also want to initialize it to the last child index within Awake.
Justin, I'm having trouble with something. Now I'm trying to make parallel always run from left to right. Otherwise the tree gets filled with wrong data on the first frame. But it doesn't work.. Whatever I do, I get opposite order on the first frame and after. Please take a look at the code, or check out the sample scene. What am I missing?

C#:
namespace BehaviorDesigner.Runtime.Tasks
{
    [TaskIcon("Assets/SampleScene/ParallelFixed.png")]
    public class ParallelFixed : Composite
    {
        // The index of the child that is currently running or is about to run.
        private int currentChildIndex;
        // The task status of every child task.
        private TaskStatus[] executionStatus;

        private bool reverse;

        public override void OnAwake()
        {
            // Create a new task status array that will hold the execution status of all of the children tasks.
            executionStatus = new TaskStatus[children.Count];

            reverse = false;
            currentChildIndex = 0;
        }

        public override void OnChildStarted(int childIndex)
        {
            // One of the children has started to run. Increment the child index and set the current task status of that child to running.
            currentChildIndex += reverse ? -1 : 1;
            executionStatus[childIndex] = TaskStatus.Running;
        }

        public override bool CanRunParallelChildren()
        {
            // This task can run parallel children.
            return true;
        }

        public override int CurrentChildIndex()
        {
            return currentChildIndex;
        }

        public override bool CanExecute()
        {
            // We can continue executing if we have more children that haven't been started yet.
            return reverse ? (currentChildIndex > -1) : (currentChildIndex < children.Count);
        }

        public override void OnChildExecuted(int childIndex, TaskStatus childStatus)
        {
            // One of the children has finished running. Set the task status.
            executionStatus[childIndex] = childStatus;
        }

        public override TaskStatus OverrideStatus(TaskStatus status)
        {
            // Assume all of the children have finished executing. Loop through the execution status of every child and check to see if any tasks are currently running
            // or failed. If a task is still running then all of the children are not done executing and the parallel task should continue to return a task status of running.
            // If a task failed then return failure. The Behavior Manager will stop all of the children tasks. If no child task is running or has failed then the parallel
            // task succeeded and it will return success.
            bool childrenComplete = true;
            for (int i = 0; i < executionStatus.Length; ++i)
            {
                if (executionStatus[i] == TaskStatus.Running)
                {
                    childrenComplete = false;
                }
                else if (executionStatus[i] == TaskStatus.Failure)
                {
                    return TaskStatus.Failure;
                }
            }

            // Enable reverse after first run
            reverse = true;
            currentChildIndex = children.Count - 1;

            return childrenComplete ? TaskStatus.Success : TaskStatus.Running;
        }

        public override void OnConditionalAbort(int childIndex)
        {
            // Start from the beginning on an abort
            reverse = true;
            currentChildIndex = children.Count - 1;
            for (int i = 0; i < executionStatus.Length; ++i)
            {
                executionStatus[i] = TaskStatus.Inactive;
            }
        }

        public override void OnEnd()
        {
            // Reset the execution status and the child index back to their starting values.
            for (int i = 0; i < executionStatus.Length; ++i)
            {
                executionStatus[i] = TaskStatus.Inactive;
            }
            currentChildIndex = children.Count - 1;
            reverse = true;
        }
    }
}
 

Attachments

Last edited:

Justin

Administrator
Staff member
When you have reverse enabled on the first frame the runtime is building the stack like:

- Stack 0: Root
- Stack 1: Send Output
- Stack 2: Strafe
- Stack 3: Advanced
- Stack 4: Look At

When it then executes it executes from last to first, so it'll execute everything from stack 4 to 0. This is why the execution flips on the next tick. In order to have it be consistent it'll take some modifications within the Behavior Manager component. I have this on my list for the next update. In the meantime what data is wrong on the first frame?
 

abc123

Member
When you have reverse enabled on the first frame the runtime is building the stack like:

- Stack 0: Root
- Stack 1: Send Output
- Stack 2: Strafe
- Stack 3: Advanced
- Stack 4: Look At

When it then executes it executes from last to first, so it'll execute everything from stack 4 to 0. This is why the execution flips on the next tick. In order to have it be consistent it'll take some modifications within the Behavior Manager component.
What I was trying to achieve, is to have reverse disabled on awake, and enable it after first execution cycle or in case of interruption. But I failed to find a way to do this. Seems like OverrideStatus() is called before execution of child tasks, enabling reverse on the first iteration.


I have this on my list for the next update. In the meantime what data is wrong on the first frame?
My tree isn't directly controlling AI agent. Instead, it calculates output data and sends it to another component on every frame. And the whole tree is designed to work in strict order, imagine branch like this running backwards:

222.PNG
Only a small example, in fact there are many variables and data which gets mixed. This may affect the calculations on the second frame. But for now it's not a very big deal. I'll close this thread and wait for the next update then.
 
Last edited:
Top