@tool @icon("../../icons/simple_parallel.svg") class_name SimpleParallelComposite extends Composite ## Simple Parallel nodes will attampt to execute all chidren at same time and ## can only have exactly two children. First child as primary node, second ## child as secondary node. ## This node will always report primary node's state, and continue tick while ## primary node return 'RUNNING'. The state of secondary node will be ignored ## and executed like a subtree. ## If primary node return 'SUCCESS' or 'FAILURE', this node will interrupt ## secondary node and return primary node's result. ## If this node is running under delay mode, it will wait seconday node ## finish its action after primary node terminates. #how many times should secondary node repeat, zero means loop forever @export var secondary_node_repeat_count: int = 0 #wether to wait secondary node finish its current action after primary node finished @export var delay_mode: bool = false var delayed_result := SUCCESS var main_task_finished: bool = false var secondary_node_running: bool = false var secondary_node_repeat_left: int = 0 func _get_configuration_warnings() -> PackedStringArray: var warnings: PackedStringArray = super._get_configuration_warnings() if get_child_count() != 2: warnings.append("SimpleParallel should have exactly two child nodes.") if not get_child(0) is ActionLeaf: warnings.append("SimpleParallel should have an action leaf node as first child node.") return warnings func tick(actor, blackboard: Blackboard): for c in get_children(): var node_index = c.get_index() if node_index == 0 and not main_task_finished: 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) delayed_result = response match response: SUCCESS, FAILURE: _cleanup_running_task(c, actor, blackboard) c.after_run(actor, blackboard) main_task_finished = true if not delay_mode: if secondary_node_running: get_child(1).interrupt(actor, blackboard) _reset() return delayed_result RUNNING: running_child = c if c is ActionLeaf: blackboard.set_value("running_action", c, str(actor.get_instance_id())) elif node_index == 1: if secondary_node_repeat_count == 0 or secondary_node_repeat_left > 0: if not secondary_node_running: c.before_run(actor, blackboard) var subtree_response = c.tick(actor, blackboard) if subtree_response != RUNNING: secondary_node_running = false c.after_run(actor, blackboard) if delay_mode and main_task_finished: _reset() return delayed_result elif secondary_node_repeat_left > 0: secondary_node_repeat_left -= 1 else: secondary_node_running = true return RUNNING func before_run(actor: Node, blackboard: Blackboard) -> void: secondary_node_repeat_left = secondary_node_repeat_count super(actor, blackboard) func interrupt(actor: Node, blackboard: Blackboard) -> void: if not main_task_finished: get_child(0).interrupt(actor, blackboard) if secondary_node_running: get_child(1).interrupt(actor, blackboard) _reset() super(actor, blackboard) func after_run(actor: Node, blackboard: Blackboard) -> void: _reset() super(actor, blackboard) func _reset() -> void: main_task_finished = false secondary_node_running = false ## 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(&"SimpleParallelComposite") return classes