/* * Copyright 2014 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.logic.behavior.tree; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Deque; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import org.terasology.entitySystem.prefab.Prefab; import org.terasology.logic.common.DisplayNameComponent; import org.terasology.module.sandbox.API; import org.terasology.registry.InjectionHelper; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Queues; import com.google.common.collect.Sets; /** * An interpreter evaluates a behavior tree. This is done by creating tasks for an actor for the nodes of the BT. * If a task returns RUNNING, the task is placed to the active list and asked next tick again. * Finished nodes may create new tasks, which are placed to the active list. * <br><br> * */ @API public class Interpreter { private static final Task TERMINAL = new Task(null) { @Override public Status update(float dt) { return null; } @Override public void handle(Status result) { } }; private Debugger debugger; private Actor actor; private Deque<Task> tasks = Queues.newLinkedBlockingDeque(); private Node root; private Set<Node> startedNodes = Sets.newHashSet(); private Map<Task, List<Task>> startedTasks = Maps.newHashMap(); public Interpreter(Actor actor) { this.actor = actor; tasks.addLast(TERMINAL); } public Actor actor() { return actor; } public void reset() { tasks.clear(); startedTasks.clear(); start(root); tasks.addLast(TERMINAL); } public Task start(Node start) { root = start; Task task = start(start, null); if (debugger != null) { debugger.started(); } return task; } public Task start(Node node, Task parent) { if (node == null) { return null; } Task task = node.createTask(); return start(task, parent); } private Task start(final Task task, Task parent) { task.setActor(actor); task.setInterpreter(this); task.setParent(parent); if (parent != null) { List<Task> subTasks = startedTasks.get(parent); if (subTasks == null) { subTasks = Lists.newArrayList(); startedTasks.put(parent, subTasks); } subTasks.add(task); } tasks.addFirst(task); return AccessController.doPrivileged((PrivilegedAction<Task>) () -> { InjectionHelper.inject(task); return task; }); } public void stop(Task task, Status result) { task.setStatus(result); Task parent = task.getParent(); if (parent != null) { parent.handle(result); } stopStartedTasks(task); if (debugger != null) { debugger.nodeFinished(task.getNode(), result); } } private void stopStartedTasks(Task parent) { Queue<Task> open = Queues.newArrayDeque(); open.offer(parent); while (!open.isEmpty()) { Task current = open.poll(); if (current.getStatus() == Status.RUNNING) { current.onTerminate(Status.FAILURE); } tasks.remove(current); List<Task> subTasks = startedTasks.remove(current); if (subTasks != null) { open.addAll(subTasks); } } } /** * Executes one tick on the interpreter (one step) * @param deltaSeconds Seconds since last update * @return the number of started nodes */ public int tick(float deltaSeconds) { if (debugger == null || debugger.beforeTick()) { startedNodes.clear(); while (step(deltaSeconds)) { continue; } if (debugger != null) { debugger.afterTick(); } } return startedNodes.size(); } /** * @param deltaSeconds Seconds since last update * @return false if no nodes were updated */ public boolean step(float deltaSeconds) { Task current = tasks.pollFirst(); if (current == TERMINAL) { tasks.addLast(TERMINAL); return false; } if (startedNodes.contains(current.getNode())) { tasks.addLast(current); return true; } startedNodes.add(current.getNode()); current.tick(deltaSeconds); if (current.getStatus() != Status.RUNNING) { if (debugger != null) { debugger.nodeFinished(current.getNode(), current.getStatus()); } if (current.getParent() != null) { stop(current, current.getStatus()); } } else { tasks.addLast(current); if (debugger != null) { debugger.nodeUpdated(current.getNode(), current.getStatus()); } } return true; } public void setDebugger(Debugger debugger) { this.debugger = debugger; } @Override public String toString() { //try to find the best name for the entity. //use display name first if (actor.hasComponent(DisplayNameComponent.class)) { return actor.getComponent(DisplayNameComponent.class).name; } //minimal name: id of the entity String entityId = "entityId: " + actor.getEntity().getId(); //if possible, attach the prefab name for better readability Prefab parentPrefab = actor.getEntity().getParentPrefab(); if (parentPrefab != null) { return "prefab: " + parentPrefab.getName() + " " + entityId; } else { return entityId; } } public interface Debugger { void nodeFinished(Node node, Status status); void nodeUpdated(Node node, Status status); void started(); boolean beforeTick(); void afterTick(); } }