Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LoopDecorator ignores RUNNING result #74

Open
markpol opened this issue Mar 7, 2023 · 4 comments
Open

LoopDecorator ignores RUNNING result #74

markpol opened this issue Mar 7, 2023 · 4 comments

Comments

@markpol
Copy link

markpol commented Mar 7, 2023

Hello,

I'm using the LoopDecorator as a way to dynamically add nodes to my trees. I queue up some actions by adding them to an array in the blackboard and then use a looped sequence to shift an action off of the queue and process it in a task. This continues until the action queue is empty at which point a FAILURE result is returned, breaking out of the Loop and continuing with the rest of the tree.

This pattern has been working fine so far but now i have some cases where I need to interrupt the Loop and then have it continue where it left off on a later step. Unfortunately, if i return a RUNNING status in one of the loop iterations it just continues to the next one.

Does it make sense to extend the LoopDecorator with a custom one that allows it to be stopped and resumed or is there perhaps a better way to handle the scenario I described above?

Thanks for a great library by the way! I'm currently using the typescript version (3.0.0-beta.1).

@Calamari
Copy link
Owner

Calamari commented Mar 7, 2023

Hello @markpol.

Glad you like it. And also good to here that someone is trying out the typescript version. I think I should put it down as official release soon, since I haven't heared any typescript related problems yet. And it is pretty old by now :-D

But to your question/use case: That is an interesting one. It definitely sounds that you are misusing the sequence node for something it ain’t meant to do since it does not have a sequence of nodes to care for – since it is only dynamically done in the blackboards –, right?.
As far as I understand your problem, you need a new kind of node, that runs through the list in the blackboard, and can therefore remember where it left off.

Does your sequence have any real children?

@markpol
Copy link
Author

markpol commented Mar 7, 2023

Hi @Calamari,

Thanks for the quick reply. Here is some simplified code that perhaps better describes what I'm trying to do:

import BehaviorTree, { FAILURE, Introspector, RUNNING, Sequence, SUCCESS, Task } from "behaviortree"
import { LoopDecorator } from "behaviortree/lib/decorators"

const actionQueue = [() => 1, () => 2, () => 3, () => 4, () => 5]

const testActionsTree = new BehaviorTree({
  tree: new Sequence({
    nodes: [
      new Task({
        name: "populateQueue",
        run: (b) => {
          b.queuedActions = [...actionQueue]
          console.log("Queued items:" + b.queuedActions.length)
          return SUCCESS
        },
      }),
      new LoopDecorator({
        node: new Sequence({
          nodes: [
            new Task({
              name: "shiftItem",
              run: (b) => {
                b.currentAction = b.queuedActions.shift()
                if (b.currentAction) {
                  console.log("Picked item:", actionQueue.length - b.queuedActions.length)
                  return SUCCESS
                }
                return FAILURE
              },
            }),
            new Task({
              name: "processItem",
              run: (b) => {
                const actionResult = b.currentAction()
                console.log("Executed item:", actionResult)
                return actionResult === 3 ? RUNNING : SUCCESS
              },
            }),
          ],
        }),
      }),
    ],
  }),
  blackboard: {
    queuedActions: [],
    currentAction: undefined,
  },
})
const introspector = new Introspector()
testActionsTree.step({ introspector })
console.log("lastresult: ", JSON.stringify(introspector.lastResult, null, 2))
testActionsTree.step({ introspector })

and the output:

npx ts-node --project tsconfig.test.json test-tree.ts                                                                                                                                                                             ─╯
Queued items: 5
Picked item: 1
Executed item: 1
Picked item: 2
Executed item: 2
Picked item: 3
Executed item: 3
Picked item: 4
Executed item: 4
Picked item: 5
Executed item: 5
lastresult:  {
  "result": false,
  "children": [
    {
      "name": "populateQueue",
      "result": true
    },
    {
      "result": false,
      "children": [
        {
          "result": true,
          "children": [
            {
              "name": "shiftItem",
              "result": true
            },
            {
              "name": "processItem",
              "result": true
            }
          ]
        },
        {
          "result": true,
          "children": [
            {
              "name": "shiftItem",
              "result": true
            },
            {
              "name": "processItem",
              "result": true
            }
          ]
        },
        {
          "children": [
            {
              "name": "shiftItem",
              "result": true
            },
            {
              "name": "processItem"
            }
          ]
        },
        {
          "result": true,
          "children": [
            {
              "name": "shiftItem",
              "result": true
            },
            {
              "name": "processItem",
              "result": true
            }
          ]
        },
        {
          "result": true,
          "children": [
            {
              "name": "shiftItem",
              "result": true
            },
            {
              "name": "processItem",
              "result": true
            }
          ]
        },
        {
          "result": false,
          "children": [
            {
              "name": "shiftItem",
              "result": false
            }
          ]
        }
      ]
    }
  ]
}
Queued items: 5
Picked item: 1
Executed item: 1
Picked item: 2
Executed item: 2
Picked item: 3
Executed item: 3
Picked item: 4
Executed item: 4
Picked item: 5
Executed item: 5

So ideally, i would want the tree to stop at action 3 where RUNNING is returned in the processItem task. Then be able to continue afterwards with actions 4 and 5 by calling step() on the tree again. I hope this makes more sense.

I might be able to get the desired result by introducing more state in the blackboard but wasn't sure if there's a better approach.

@markpol
Copy link
Author

markpol commented Mar 7, 2023

I solved this by extending the LoopDecorator to return early on RUNNING results in addition to FAILURE as well as by only having it wrap a single Task rather than the Sequence that I used above.

import BehaviorTree, {
  FAILURE,
  Introspector,
  RunCallback,
  RUNNING,
  Sequence,
  StatusWithState,
  SUCCESS,
  Task,
} from "behaviortree"
import { LoopDecorator } from "behaviortree/lib/decorators"
import { RunResult } from "behaviortree/lib/types"

class InterruptableLoopDecorator extends LoopDecorator {
  nodeType = "InterruptableLoopDecorator"

  decorate(run: RunCallback) {
    let i = 0
    let result: RunResult = FAILURE
    while (i++ < this.config.loop) {
      result = run()
      if (result === FAILURE) return FAILURE
      if (result === RUNNING) return RUNNING
    }
    return result
  }
}

const actionQueue = [() => 1, () => 2, () => 3, () => 4, () => 5]

const testActionsTree = new BehaviorTree({
  tree: new Sequence({
    nodes: [
      new Task({
        name: "populateQueue",
        run: (b) => {
          b.queuedActions = [...actionQueue]
          console.log("Queued items:" + b.queuedActions.length)
          return SUCCESS
        },
      }),
      new InterruptableLoopDecorator({
        node: new Task({
          name: "executeItem",
          run: (b) => {
            b.currentAction = b.queuedActions.shift()
            if (b.currentAction) {
              const actionResult = b.currentAction()
              console.log("Executed item:", actionResult)
              if (actionResult === 3) {
                return RUNNING
              }
              return SUCCESS
            }
            return FAILURE
          },
        }),
      }),
    ],
  }),
  blackboard: {
    queuedActions: [],
    currentAction: undefined,
  },
})

const introspector = new Introspector()
testActionsTree.step({ introspector })
console.log("lastresult 1: ", JSON.stringify(introspector.lastResult, null, 2))
if ((testActionsTree.lastResult as StatusWithState)?.total === RUNNING) {
  testActionsTree.step({ introspector })
  console.log("lastresult 2: ", JSON.stringify(introspector.lastResult, null, 2))
}

which now outputs:

npx ts-node --project tsconfig.test.json test-tree.ts                                                                                                                                                               
Queued items:5
Executed item: 1
Executed item: 2
Executed item: 3
lastresult 1:  {
  "children": [
    {
      "name": "populateQueue",
      "result": true
    },
    {
      "children": [
        {
          "name": "executeItem",
          "result": true
        },
        {
          "name": "executeItem",
          "result": true
        },
        {
          "name": "executeItem"
        }
      ]
    }
  ]
}
Executed item: 4
Executed item: 5
lastresult 2:  {
  "result": false,
  "children": [
    {
      "result": false,
      "children": [
        {
          "name": "executeItem",
          "result": true
        },
        {
          "name": "executeItem",
          "result": true
        },
        {
          "name": "executeItem",
          "result": false
        }
      ]
    }
  ]
}

Thanks again!

@Calamari
Copy link
Owner

Calamari commented Mar 9, 2023

Nice, thanks for sharing. It is an interesting use case. Maybe useful for people to know about.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants