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

Add ParallelNode composite #58

Closed
6 tasks
monkeez opened this issue Nov 22, 2022 · 16 comments
Closed
6 tasks

Add ParallelNode composite #58

monkeez opened this issue Nov 22, 2022 · 16 comments
Assignees
Labels
🍒 cherrypick:beehave_v3 🍯 feature A new addition to this addon. 🐝 new node A new node type.
Milestone

Comments

@monkeez
Copy link

monkeez commented Nov 22, 2022

I was trying out beehave and I've been enjoying it for creating AI. But it got me thinking about if it'd be possible to reuse AI actions and conditions for a player character controller too. This is where I ran into issues as dealing with player inputs in a behaviour tree gets messy pretty fast (very deep nested node trees).

While looking around I noticed a description here of a parallel composite node (which can run in selector or sequence mode). This would make it possible to make a behviour tree more flat which would work better when you're dealing with lots of different player inputs.

I think I know how to implement this in a simple way, but it wouldn't be truly parallel. It would run each child tree collecting their statuses and after all children have been processed it the Parallel node could return success or failure based on the collected results.

Todo

  • introduce SequenceParallelComposite
  • introduce SelectorParallelComposite
  • write tests for SequenceParallelComposite
  • write tests for SelectorParallelComposite
  • update documentation
  • 💥 BREAKING: replace get_running_action() with get_running_actions() array, as with parallel nodes multiple nodes can be running at the same time.
@bitbrain
Copy link
Owner

@monkeez thank you for your suggestion. Could you perhaps update the description with more details on:

  • an example of what the problem currently is (screenshot?)
  • an example of how the parallel sequence/parallel selector can solve this issue / how will that node be used

@bitbrain bitbrain added 🍯 feature A new addition to this addon. 🐝 new node A new node type. labels Nov 22, 2022
@Ohan17
Copy link
Contributor

Ohan17 commented Feb 25, 2023

@bitbrain I stumbled upon a situation that I think a parallel node would solve my problem.
image
Here's an example, I have an AI that roam and idle respectively so I use a SequenceStarComposite, I also whant my AI to discharge every x seconds while roaming/idling. Basically I need 2 actions running along side each other that is Roam/Discharge.
A Parallel Sequence/Selector should continue to process its children even if one of its child is in RUNNING state.

@GeroVeni
Copy link
Contributor

@Ohan17 I didn't put a lot of thought in it, but on first sight it seems that since Roam and Discharge are independent actions maybe they could be different behavior trees all together. And if they need to share data you can assign a common blackboard to both of them. The only issue with that is that it kind of works if your independent nodes are children of the first node I guess. If they are further down the tree, maybe you can have conditions that check the status and running action of the other tree maybe, without having to replicate the whole tree up to that point.

@Ohan17
Copy link
Contributor

Ohan17 commented Feb 25, 2023

@GeroVeni This while could work, I consider this as a workaround since this is not very flexible. The best solution would be introduce new nodes like I mentioned which will be closer to existing composite nodes or completely parallel using threads like @monkeez proposed.

@feelingsonice
Copy link

Any updates on this? Very interested.

@bitbrain
Copy link
Owner

bitbrain commented Mar 19, 2023

My understanding after reading this is that there cannot be a "parallel" version of sequence and selector, but it is rather its own type of composite node with a policy. We can therefore introduce some sort of enum and call it 'ExecutionPolicy' that can either be SEQUENCE or SELECTOR. In case you want to introduce two nodes, feel free to do so. It is up to the implementer to decide.

In addition, the implementer needs to consider the table on that page and replicate its behavior by using Godot threads. Also, we require extensive unit tests (it is documented here how to do that). Also do not forget to write documentation!

@bitbrain bitbrain changed the title Adding a Parallel Sequence and Parallel Selector composite node Add ParallelNode composite Mar 19, 2023
@bitbrain bitbrain self-assigned this Jun 14, 2023
@bitbrain
Copy link
Owner

bitbrain commented Jun 14, 2023

I am working on a pull request for this! EDIT unfortunately, I got distracted here so no PR right now.

@bitbrain bitbrain pinned this issue Jun 14, 2023
@bitbrain bitbrain added the 💥 breaks compatibility Changes the API contract so it may break compatibility with older versions. label Jun 18, 2023
@lostptr
Copy link
Contributor

lostptr commented Aug 13, 2023

I would just like to add my two cents to this discussion...

How is using two AI trees for two different behaviors less flexible than creating a whole new node for just this case?

If the two behaviors need to share data you can use the same blackboard, as it has already been said.

By breaking the single responsibility principle, we actually make it less flexible.

I will give you an example that is an extreme edge case but is just to prove my point: imagine you want to run 2 random actions in parallel from a collection of 3 possible actions. This is possible to do it with 3 trees for each behavior, sharing the same blackboard. Or we would need to make an entirely new RandomParallelCompositeNode along with tests to make sure it works and added complexity (ie.: theres already an issue for weighted random selectors , imagine we would now need to make weighted random parallel actions as well)

Sure, maybe creating a new ParallelComposite feels easier than creating a whole new tree but I think that in the provided case is just a matter of choosing the right tool for the job. The discharge part is so simple it doesnt even need a beehave tree. I could be just a node with a script and a child Timer. (From what I could gather from the comments, maybe theres more to it)

Sorry for the wall of text 😅

@Ohan17
Copy link
Contributor

Ohan17 commented Aug 13, 2023

The thing with splitting to another behavior tree in this usecase is it not being a composite node where it can be used in sub branches. My example may not be best to describe the issue but the general usage is still run this only when that is running. This is what I came up with using multiple trees approach, I would need a condition to enter Task2 and remember to SetParallelFalse in any child of any Task to interrupt the whole thing. Editing parallel tasks are even more troublesome, thus making it not very flexible.
image

@xaqbr
Copy link

xaqbr commented Oct 17, 2023

Here's a workaround I've managed for now, inspired by https://gdscript.com/solutions/godot-behaviour-tree/

class_name Parallel
extends Composite


enum Policy { SEQUENCE, SELECTOR }


@export var policy: Policy = Policy.SEQUENCE


var finish_count := 0


func tick(actor: Node, blackboard: Blackboard) -> int:
	for c in get_children():
		if c != running_child:
			c.before_run(actor, blackboard)
		
		var response = c.tick(actor, blackboard)
		if can_send_message(blackboard):
			BeehaveDebuggerMessages.process_tick(c.get_instance_id(), response)

		if c is ConditionLeaf:
			blackboard.set_value("last_condition", c, str(actor.get_instance_id()))
			blackboard.set_value("last_condition_status", response, str(actor.get_instance_id()))

		match response:
			SUCCESS:
				_cleanup_running_task(c, actor, blackboard)
				c.after_run(actor, blackboard)
				finish_count += 1
				if policy == Policy.SEQUENCE:
					if finish_count >= get_child_count():
						finish_count = 0
						return SUCCESS
				else:
					return SUCCESS
			FAILURE:
				_cleanup_running_task(c, actor, blackboard)
				c.after_run(actor, blackboard)
				finish_count += 1
				if policy == Policy.SELECTOR:
					if finish_count >= get_child_count():
						finish_count = 0
						return FAILURE
				else:
					return FAILURE
			RUNNING:
				running_child = c
				if c is ActionLeaf:
					blackboard.set_value("running_action", c, str(actor.get_instance_id()))

	return RUNNING


func after_run(actor: Node, blackboard: Blackboard) -> void:
	finish_count = 0
	super(actor, blackboard)


func interrupt(actor: Node, blackboard: Blackboard) -> void:
	finish_count = 0
	super(actor, blackboard)


## Changes `running_action` and `running_child` after the node finishes executing.
func _cleanup_running_task(finished_action: Node, actor: Node, blackboard: Blackboard):
	var blackboard_name = str(actor.get_instance_id())
	if finished_action == running_child:
		running_child = null
		if finished_action == blackboard.get_value("running_action", null, blackboard_name):
			blackboard.set_value("running_action", null, blackboard_name)


func get_class_name() -> Array[StringName]:
	var classes := super()
	classes.push_back(&"ParallelComposite")
	return classes

Doesn't support multithreading obviously but I just need basic parallelism here. If I have time later I will try to make a PR.

Sequence policy fails when any child fails, and succeeds only when all succeed.
Selector policy succeeds when any child succeeds, and fails only when all fail.

@eterlan
Copy link

eterlan commented Oct 22, 2023

May I ask how do you do two running task at the same time without parallel node? Just think about this a few days and cannot figure it out..

@bitbrain
Copy link
Owner

@eterlan the idea is that they are not truly running in parallel (multi-threaded) but effectively every frame multiple taks in a RUNNING state are executed.

@bitbrain bitbrain added this to the 3.0.0 milestone Nov 13, 2023
@Vukbo
Copy link

Vukbo commented Dec 2, 2023

May I ask what state this feature is currently in?
I stumbled in a situation where I would need to run two actions at the same time.

@bitbrain
Copy link
Owner

bitbrain commented Dec 3, 2023

@bozoVuksan architecturally, beehave 2.x has been designed to only ever have one running node at any time. This feature would require the need to support multiple running nodes. Our current assumption is that this breaks compatibility (beehave node methods need changing) so currently it is aimed for beehave 3.x

In case anyone finds a way to make this work for 2.x without breaking the compatibility, let us know.

@bitbrain bitbrain unpinned this issue Dec 4, 2023
@bitbrain
Copy link
Owner

Done thanks to #332

@bitbrain bitbrain added 🍒 cherrypick:beehave_v3 and removed 💥 breaks compatibility Changes the API contract so it may break compatibility with older versions. labels Apr 23, 2024
@brianrodri
Copy link

I think it's still valuable to keep this issue open since SimpleParallel only address N=2, but the request here is for any N.

Additionally, nesting SimpleParallel is explicitly discouraged so it'd still be nice to see this kept on the roadmap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🍒 cherrypick:beehave_v3 🍯 feature A new addition to this addon. 🐝 new node A new node type.
Projects
None yet
Development

No branches or pull requests

10 participants