diff --git a/CHANGES b/CHANGES index 4c8a302c..9a57243a 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ [1.7.1] - Updated to libgdx 1.7.2 -- Now the behavior tree parser can report comments, which is useful for certain tools such as graphical editors. +- API Change and Addition: Behavior Tree API + * Added ability to guard any task. + * Added branch task DynamicGuardSelector. + * Now the text format supports subtree references which, besides improving reuse and readability, allows you to use guards as trees. + * Now the parser can report comments, which is useful for certain tools such as graphical editors. [1.7.0] - Updated to libgdx 1.7.1 diff --git a/gdx-ai/src/com/badlogic/gdx/ai/btree/BehaviorTree.java b/gdx-ai/src/com/badlogic/gdx/ai/btree/BehaviorTree.java index 5bbf8c52..2f6ac259 100644 --- a/gdx-ai/src/com/badlogic/gdx/ai/btree/BehaviorTree.java +++ b/gdx-ai/src/com/badlogic/gdx/ai/btree/BehaviorTree.java @@ -28,6 +28,7 @@ public class BehaviorTree extends Task { private Task rootTask; private E object; + GuardEvaluator guardEvalutor; /** Creates a {@code BehaviorTree} with no root task and no blackboard object. Both the root task and the blackboard object must * be set before running this behavior tree, see {@link #addChild(Task) addChild()} and {@link #setObject(Object) setObject()} @@ -55,6 +56,7 @@ public BehaviorTree (Task rootTask, E object) { this.rootTask = rootTask; this.object = object; this.tree = this; + this.guardEvalutor = new GuardEvaluator(this); } /** Returns the blackboard object of this behavior tree. */ @@ -111,11 +113,16 @@ public void childSuccess (Task runningTask) { /** This method should be called when game entity needs to make decisions: call this in game loop or after a fixed time slice if * the game is real-time, or on entity's turn if the game is turn-based */ public void step () { - if (rootTask.status != Status.RUNNING) { + if (rootTask.status == Status.RUNNING) { + rootTask.run(); + } else { rootTask.setControl(this); rootTask.start(); + if (rootTask.checkGuard(this)) + rootTask.run(); + else + rootTask.fail(); } - rootTask.run(); } @Override @@ -163,6 +170,50 @@ public void notifyChildAdded (Task task, int index) { } } + private static final class GuardEvaluator extends Task { + + public GuardEvaluator (BehaviorTree tree) { + this.tree = tree; + } + + @Override + protected int addChildToTask (Task child) { + return 0; + } + + @Override + public int getChildCount () { + return 0; + } + + @Override + public Task getChild (int i) { + return null; + } + + @Override + public void run () { + } + + @Override + public void childSuccess (Task task) { + } + + @Override + public void childFail (Task task) { + } + + @Override + public void childRunning (Task runningTask, Task reporter) { + } + + @Override + protected Task copyTo (Task task) { + return null; + } + + } + /** The listener interface for receiving task events. The class that is interested in processing a task event implements this * interface, and the object created with that class is registered with a behavior tree, using the * {@link BehaviorTree#addListener(Listener)} method. When a task event occurs, the corresponding method is invoked. diff --git a/gdx-ai/src/com/badlogic/gdx/ai/btree/Decorator.java b/gdx-ai/src/com/badlogic/gdx/ai/btree/Decorator.java index 0c4c81fd..11d5107e 100644 --- a/gdx-ai/src/com/badlogic/gdx/ai/btree/Decorator.java +++ b/gdx-ai/src/com/badlogic/gdx/ai/btree/Decorator.java @@ -62,11 +62,16 @@ public Task getChild (int i) { @Override public void run () { - if (child.status != Status.RUNNING) { + if (child.status == Status.RUNNING) { + child.run(); + } else { child.setControl(this); child.start(); + if (child.checkGuard(this)) + child.run(); + else + child.fail(); } - child.run(); } @Override diff --git a/gdx-ai/src/com/badlogic/gdx/ai/btree/LoopDecorator.java b/gdx-ai/src/com/badlogic/gdx/ai/btree/LoopDecorator.java index e1275f56..b9f0bd46 100644 --- a/gdx-ai/src/com/badlogic/gdx/ai/btree/LoopDecorator.java +++ b/gdx-ai/src/com/badlogic/gdx/ai/btree/LoopDecorator.java @@ -47,11 +47,16 @@ public boolean condition () { public void run () { loop = true; while (condition()) { - if (child.status != Status.RUNNING) { + if (child.status == Status.RUNNING) { + child.run(); + } else { child.setControl(this); child.start(); + if (child.checkGuard(this)) + child.run(); + else + child.fail(); } - child.run(); } } diff --git a/gdx-ai/src/com/badlogic/gdx/ai/btree/SingleRunningChildBranch.java b/gdx-ai/src/com/badlogic/gdx/ai/btree/SingleRunningChildBranch.java index 318bba46..000aa4d0 100644 --- a/gdx-ai/src/com/badlogic/gdx/ai/btree/SingleRunningChildBranch.java +++ b/gdx-ai/src/com/badlogic/gdx/ai/btree/SingleRunningChildBranch.java @@ -85,6 +85,8 @@ public void run () { } runningChild.setControl(this); runningChild.start(); + if (!runningChild.checkGuard(this)) + runningChild.fail(); run(); } else { // Should never happen; this case must be handled by subclasses in childXXX methods diff --git a/gdx-ai/src/com/badlogic/gdx/ai/btree/Task.java b/gdx-ai/src/com/badlogic/gdx/ai/btree/Task.java index 1c231b5f..f4669f0a 100644 --- a/gdx-ai/src/com/badlogic/gdx/ai/btree/Task.java +++ b/gdx-ai/src/com/badlogic/gdx/ai/btree/Task.java @@ -79,6 +79,8 @@ public enum Status { /** The behavior tree this task belongs to. */ protected BehaviorTree tree; + public Task guard; + /** This method will add a child to the list of this task's children * * @param child the child task which will be added @@ -125,6 +127,27 @@ public final void setControl (Task control) { this.tree = control.tree; } + public boolean checkGuard(Task control) { + if (guard != null) { + // Check the guard of the guard recursively + if (!guard.checkGuard(control)) return false; + + // Use the tree's guard evaluator task to check the guard of this task + guard.setControl(control.tree.guardEvalutor); + guard.start(); + guard.run(); + switch (guard.getStatus()) { + case SUCCEEDED: + return true; + case FAILED: + return false; + default: + throw new RuntimeException(); + } + } + return true; + } + /** This method will be called once before this task's first run. */ public void start () { } @@ -226,7 +249,9 @@ public Task cloneTask () { } } try { - return copyTo(ClassReflection.newInstance(this.getClass())); + Task clone = copyTo(ClassReflection.newInstance(this.getClass())); + clone.guard = guard == null ? null : guard.cloneTask(); + return clone; } catch (ReflectionException e) { throw new TaskCloneException(e); } diff --git a/gdx-ai/src/com/badlogic/gdx/ai/btree/branch/DynamicGuardSelector.java b/gdx-ai/src/com/badlogic/gdx/ai/btree/branch/DynamicGuardSelector.java new file mode 100644 index 00000000..1e7750f6 --- /dev/null +++ b/gdx-ai/src/com/badlogic/gdx/ai/btree/branch/DynamicGuardSelector.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright 2014 See AUTHORS file. + * + * 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 com.badlogic.gdx.ai.btree.branch; + +import com.badlogic.gdx.ai.btree.BranchTask; +import com.badlogic.gdx.ai.btree.Task; +import com.badlogic.gdx.utils.Array; + +/** A {@code DynamicGuardSelector} is a branch task that executes the first child whose guard is evaluated to {@code true}. At + * every AI cycle, the children's guards are re-evaluated, so if the guard of the running child is evaluated to {@code false}, it + * is cancelled, and the child with the highest priority starts running. The {@code DynamicGuardSelector} task finishes when no + * guard is evaluated to {@code true} (thus failing) or when its active child finishes (returning the active child's termination + * status). + * + * @param type of the blackboard object that tasks use to read or modify game state + * + * @author davebaol */ +public class DynamicGuardSelector extends BranchTask { + + /** The child in the running status or {@code null} if no child is running. */ + protected Task runningChild; + + /** Creates a {@code DynamicGuardSelector} branch with no children. */ + public DynamicGuardSelector () { + super(); + } + + /** Creates a {@code DynamicGuardSelector} branch with the given children. + * + * @param tasks the children of this task */ + public DynamicGuardSelector (Task... tasks) { + super(new Array>(tasks)); + } + + /** Creates a {@code DynamicGuardSelector} branch with the given children. + * + * @param tasks the children of this task */ + public DynamicGuardSelector (Array> tasks) { + super(tasks); + } + + @Override + public void childRunning (Task task, Task reporter) { + runningChild = task; + running(); // Return a running status when a child says it's running + } + + @Override + public void childSuccess (Task task) { + this.runningChild = null; + success(); + } + + @Override + public void childFail (Task task) { + this.runningChild = null; + fail(); + } + + @Override + public void run () { + // Check guards + Task childToRun = null; + for (int i = 0, n = children.size; i < n; i++) { + Task child = children.get(i); + if (child.checkGuard(this)) { + childToRun = child; + break; + } + } + + if (runningChild != null && runningChild != childToRun) { + runningChild.cancel(); + runningChild = null; + } + if (childToRun == null) { + fail(); + } else { + if (runningChild == null) { + runningChild = childToRun; + runningChild.setControl(this); + runningChild.start(); + } + runningChild.run(); + } + } + + @Override + public void reset () { + super.reset(); + this.runningChild = null; + } + + @Override + protected Task copyTo (Task task) { + DynamicGuardSelector branch = (DynamicGuardSelector)task; + branch.runningChild = null; + + return super.copyTo(task); + } + +} diff --git a/gdx-ai/src/com/badlogic/gdx/ai/btree/branch/Parallel.java b/gdx-ai/src/com/badlogic/gdx/ai/btree/branch/Parallel.java index 52ba1b68..8b71f19c 100644 --- a/gdx-ai/src/com/badlogic/gdx/ai/btree/branch/Parallel.java +++ b/gdx-ai/src/com/badlogic/gdx/ai/btree/branch/Parallel.java @@ -90,13 +90,17 @@ public void run () { lastResult = null; for (currentChildIndex = 0; currentChildIndex < children.size; currentChildIndex++) { Task child = children.get(currentChildIndex); - if (child.getStatus() != Status.RUNNING) { + if (child.getStatus() == Status.RUNNING) { + child.run(); + } else { child.setControl(this); child.start(); + if (child.checkGuard(this)) + child.run(); + else + child.fail(); } - child.run(); - if (lastResult != null) { cancelRunningChildren(noRunningTasks ? currentChildIndex + 1 : 0); if (lastResult) diff --git a/gdx-ai/src/com/badlogic/gdx/ai/btree/utils/BehaviorTreeParser.java b/gdx-ai/src/com/badlogic/gdx/ai/btree/utils/BehaviorTreeParser.java index a1cbaef6..4707f2d1 100644 --- a/gdx-ai/src/com/badlogic/gdx/ai/btree/utils/BehaviorTreeParser.java +++ b/gdx-ai/src/com/badlogic/gdx/ai/btree/utils/BehaviorTreeParser.java @@ -23,6 +23,7 @@ import com.badlogic.gdx.ai.btree.Task; import com.badlogic.gdx.ai.btree.annotation.TaskAttribute; import com.badlogic.gdx.ai.btree.annotation.TaskConstraint; +import com.badlogic.gdx.ai.btree.branch.DynamicGuardSelector; import com.badlogic.gdx.ai.btree.branch.Parallel; import com.badlogic.gdx.ai.btree.branch.RandomSelector; import com.badlogic.gdx.ai.btree.branch.RandomSequence; @@ -139,6 +140,13 @@ protected BehaviorTree createBehaviorTree (Task root, E object) { protected void printTree (Task task, int indent) { for (int i = 0; i < indent; i++) System.out.print(' '); + if (task.guard != null) { + System.out.println("Guard"); + indent = indent + 2; + printTree(task.guard, indent); + for (int i = 0; i < indent; i++) + System.out.print(' '); + } System.out.println(task.getClass().getSimpleName()); for (int i = 0; i < task.getChildCount(); i++) { printTree(task.getChild(i), indent + 2); @@ -152,6 +160,7 @@ public static class DefaultBehaviorTreeReader extends BehaviorTreeReader { Class[] classes = new Class[] {// @off - disable libgdx formatter AlwaysFail.class, AlwaysSucceed.class, + DynamicGuardSelector.class, Failure.class, Include.class, Invert.class, @@ -176,27 +185,117 @@ public static class DefaultBehaviorTreeReader extends BehaviorTreeReader { } } - private static final int TAG_NONE = -1; - private static final int TAG_IMPORT = 0; - private static final int TAG_ROOT = 1; + enum Statement { + Import("import") { + @Override + protected void enter (DefaultBehaviorTreeReader reader, String name, boolean isGuard) { + } - private static final String[] STATEMENTS = new String[] {"import", "root"}; + @Override + protected boolean attribute (DefaultBehaviorTreeReader reader, String name, Object value) { + if (!(value instanceof String)) reader.throwAttributeTypeException(this.name, name, "String"); + reader.addImport(name, (String)value); + return true; + } - protected BehaviorTreeParser btParser; + @Override + protected void exit (DefaultBehaviorTreeReader reader) { + return; + } + }, + Subtree("subtree") { + @Override + protected void enter (DefaultBehaviorTreeReader reader, String name, boolean isGuard) { + } - ObjectMap userImports = new ObjectMap(); + @Override + protected boolean attribute (DefaultBehaviorTreeReader reader, String name, Object value) { + if (!name.equals("name")) reader.throwAttributeNameException(this.name, name, "name"); + if (!(value instanceof String)) reader.throwAttributeTypeException(this.name, name, "String"); + if ("".equals(value)) throw new GdxRuntimeException(this.name + ": the name connot be empty"); + if (reader.subtreeName != null) + throw new GdxRuntimeException(this.name + ": the name has been already specified"); + reader.subtreeName = (String)value; + return true; + } + + @Override + protected void exit (DefaultBehaviorTreeReader reader) { + reader.switchToNewTree(reader.subtreeName); + reader.subtreeName = null; + } + }, + Root("root") { + @Override + protected void enter (DefaultBehaviorTreeReader reader, String name, boolean isGuard) { + reader.subtreeName = ""; // the root tree has empty name + } + + @Override + protected boolean attribute (DefaultBehaviorTreeReader reader, String name, Object value) { + reader.throwAttributeTypeException(this.name, name, null); + return true; + } + + @Override + protected void exit (DefaultBehaviorTreeReader reader) { + reader.switchToNewTree(reader.subtreeName); + reader.subtreeName = null; + } + }, + TreeTask(null) { + @Override + protected void enter (DefaultBehaviorTreeReader reader, String name, boolean isGuard) { + // Root tree is the default one + if (reader.currentTree == null) { + reader.switchToNewTree(""); + reader.subtreeName = null; + } + + reader.openTask(name, isGuard); + } + + @Override + protected boolean attribute (DefaultBehaviorTreeReader reader, String name, Object value) { + StackedTask stackedTask = reader.getCurrentTask(); + AttrInfo ai = stackedTask.metadata.attributes.get(name); + if (ai == null) return false; + boolean isNew = reader.encounteredAttributes.add(name); + if (!isNew) throw reader.stackedTaskException(stackedTask, "attribute '" + name + "' specified more than once"); + Field attributeField = reader.getField(stackedTask.task.getClass(), ai.fieldName); + reader.setField(attributeField, stackedTask.task, value); + return true; + } + + @Override + protected void exit (DefaultBehaviorTreeReader reader) { + if (!reader.isSubtreeRef) { + reader.checkRequiredAttributes(reader.getCurrentTask()); + reader.encounteredAttributes.clear(); + } + } + }; + + String name; + + Statement(String name) { + this.name = name; + } + + protected abstract void enter (DefaultBehaviorTreeReader reader, String name, boolean isGuard); + protected abstract boolean attribute (DefaultBehaviorTreeReader reader, String name, Object value); + protected abstract void exit (DefaultBehaviorTreeReader reader); + + } + + protected BehaviorTreeParser btParser; ObjectMap, Metadata> metadataCache = new ObjectMap, Metadata>(); Task root; - protected Array> stack = new Array>(); - ObjectSet encounteredAttributes = new ObjectSet(); - int tagType; - boolean isTask; - int currentDepth; - protected StackedTask prevTask; - int step; - int rootIndent; + String subtreeName; + Statement statement; + private int indent; public DefaultBehaviorTreeReader () { this(false); @@ -217,69 +316,67 @@ public void setParser (BehaviorTreeParser parser) { @Override public void parse (char[] data, int offset, int length) { debug = btParser.debugLevel > BehaviorTreeParser.DEBUG_NONE; - tagType = TAG_NONE; - isTask = false; - userImports.clear(); root = null; - prevTask = null; - currentDepth = -1; - step = 1; - stack.clear(); - encounteredAttributes.clear(); + clear(); super.parse(data, offset, length); // Pop all task from the stack and check their minimum number of children popAndCheckMinChildren(0); + Subtree rootTree = subtrees.get(""); + if (rootTree == null) throw new GdxRuntimeException("Missing root tree"); + root = rootTree.rootTask; if (root == null) throw new GdxRuntimeException("The tree must have at least the root task"); + + clear(); + } + + @Override + protected void startLine (int indent) { + if (btParser.debugLevel > BehaviorTreeParser.DEBUG_LOW) + System.out.println(lineNumber + ": <" + indent + ">"); + this.indent = indent; } + private Statement checkStatement (String name) { + if (name.equals(Statement.Import.name)) return Statement.Import; + if (name.equals(Statement.Subtree.name)) return Statement.Subtree; + if (name.equals(Statement.Root.name)) return Statement.Root; + return Statement.TreeTask; + } + @Override - protected void startStatement (int indent, String name) { + protected void startStatement (String name, boolean isSubtreeReference, boolean isGuard) { if (btParser.debugLevel > BehaviorTreeParser.DEBUG_LOW) - System.out.println(lineNumber + ": <" + indent + "> task name '" + name + "'"); - if (tagType == TAG_ROOT) - openTask(indent, name); - else { - boolean validStatement = openTag(name); - if (!validStatement) { - if (btParser.debugLevel > BehaviorTreeParser.DEBUG_LOW) { - System.out.println("validStatement: " + validStatement); - System.out.println("getImport(name): " + getImport(name)); - } - if (getImport(name) != null) { - // root statement is optional - tagType = TAG_ROOT; - openTask(indent, name); - return; - } - throw new GdxRuntimeException("Unknown tag '" + name + "'"); - } + System.out.println((isGuard? " guard" : " task") + " name '" + name + "'"); + + this.isSubtreeRef = isSubtreeReference; + + this.statement = isSubtreeReference ? Statement.TreeTask : checkStatement(name); + if (isGuard) { + if (statement != Statement.TreeTask) + throw new GdxRuntimeException(name + ": only tree's tasks can be guarded"); } + + System.out.println("Statement = " + statement.name()); + statement.enter(this, name, isGuard); } @Override protected void attribute (String name, Object value) { if (btParser.debugLevel > BehaviorTreeParser.DEBUG_LOW) System.out.println(lineNumber + ": attribute '" + name + " : " + value + "'"); - if (isTask) { - if (!attributeTask(name, value)) throw new GdxRuntimeException(prevTask.name + ": unknown attribute '" + name + "'"); - } else { - if (!attributeTag(name, value)) - throw new GdxRuntimeException(STATEMENTS[tagType] + ": unknown attribute '" + name + "'"); + + boolean validAttribute = statement.attribute(this, name, value); + if (!validAttribute) { + if (statement == Statement.TreeTask) { + throw stackedTaskException(getCurrentTask(), "unknown attribute '" + name + "'"); + } else { + throw new GdxRuntimeException(statement.name + ": unknown attribute '" + name + "'"); + } } } - private boolean attributeTask (String name, Object value) { - AttrInfo ai = prevTask.metadata.attributes.get(name); - if (ai == null) return false; - boolean isNew = encounteredAttributes.add(name); - if (!isNew) throw new GdxRuntimeException(prevTask.name + ": attribute '" + name + "' specified more than once"); - Field attributeField = getField(prevTask.task.getClass(), ai.fieldName); - setField(attributeField, prevTask.task, value); - return true; - } - private Field getField (Class clazz, String name) { try { return ClassReflection.getField(clazz, name); @@ -344,18 +441,15 @@ else if (type == char.class || type == Character.class) { } } } - if (ret == null) throwAttributeTypeException(prevTask.name, field.getName(), type.getSimpleName()); + if (ret == null) throwAttributeTypeException(getCurrentTask().name, field.getName(), type.getSimpleName()); return ret; } - private boolean attributeTag (String name, Object value) { - if (tagType == TAG_IMPORT) { - if (value instanceof String) - addImport(name, (String)value); - else - throwAttributeTypeException(STATEMENTS[tagType], name, "String"); - } - return true; + private void throwAttributeNameException (String statement, String name, String expectedName) { + String expected = " no attribute expected"; + if (expectedName != null) + expected = "expected '" + expectedName + "' instead"; + throw new GdxRuntimeException(statement + ": attribute '" + name + "' unknown; " + expected); } private void throwAttributeTypeException (String statement, String name, String expectedType) { @@ -363,75 +457,41 @@ private void throwAttributeTypeException (String statement, String name, String } @Override - protected void endStatement () { - if (isTask) { - isTask = (stack.size != 0); - if (isTask) { - checkRequiredAttributes(prevTask); - encounteredAttributes.clear(); - } - } else { -// if (tagType == TAG_IMPORT) { -// addImport(importTask, importAs); -// } -// // Reset the tag type to the parent -// if (tagType != TAG_NONE) { -// tagType = TAGS[tagType].parentIndex; -// } - } + protected void endLine () { } - private void addImport (String alias, String task) { - if (task == null) throw new GdxRuntimeException("import: missing task class name."); - if (alias == null) { - Class clazz = null; - try { - clazz = ClassReflection.forName(task); - } catch (ReflectionException e) { - throw new GdxRuntimeException("import: class not found '" + task + "'"); - } - alias = clazz.getSimpleName(); - } - String className = getImport(alias); - if (className != null) throw new GdxRuntimeException("import: alias '" + alias + "' previously defined already."); - userImports.put(alias, task); - } - - private String getImport (String as) { - String className = DEFAULT_IMPORTS.get(as); - return className != null ? className : userImports.get(as); - } - - private boolean openTag (String name) { - for (int i = 0; i < STATEMENTS.length; i++) { - String tag = STATEMENTS[i]; - if (name.equals(tag)) { - tagType = i; - return true; - } - } - return false; + @Override + protected void endStatement () { + System.out.println("end statement"); + statement.exit(this); } - private void openTask (int indent, String name) { - isTask = true; - String className = getImport(name); - if (className == null) className = name; + private void openTask (String name, boolean isGuard) { try { - @SuppressWarnings("unchecked") - Task task = (Task)ClassReflection.newInstance(ClassReflection.forName(className)); - - if (prevTask == null) { - root = task; - rootIndent = indent; + Task task; + if (isSubtreeRef) { + task = subtreeRootTaskInstance(name); + } + else { + String className = getImport(name); + if (className == null) className = name; + @SuppressWarnings("unchecked") + Task tmpTask = (Task)ClassReflection.newInstance(ClassReflection.forName(className)); + task = tmpTask; + } + + if (!currentTree.inited()) { + initCurrentTree(task, indent); indent = 0; - } else { - indent -= rootIndent; - if (prevTask.task == root) { + } else if (!isGuard) { + StackedTask stackedTask = getPrevTask(); + + indent -= currentTreeStartIndent; + if (stackedTask.task == currentTree.rootTask) { step = indent; } if (indent > currentDepth) { - stack.add(prevTask); // push + stack.add(stackedTask); // push } else if (indent <= currentDepth) { // Pop tasks from the stack based on indentation // and check their minimum number of children @@ -443,54 +503,24 @@ private void openTask (int indent, String name) { StackedTask stackedParent = stack.peek(); int maxChildren = stackedParent.metadata.maxChildren; if (stackedParent.task.getChildCount() >= maxChildren) - throw new GdxRuntimeException(stackedParent.name + ": max number of children exceeded (" + throw stackedTaskException(stackedParent, "max number of children exceeded (" + (stackedParent.task.getChildCount() + 1) + " > " + maxChildren + ")"); // Add child task to the parent stackedParent.task.addChild(task); } - prevTask = createStackedTask(name, task); - currentDepth = indent; + updateCurrentTask(createStackedTask(name, task), indent, isGuard); } catch (ReflectionException e) { throw new GdxRuntimeException("Cannot parse behavior tree!!!", e); } } - - private void popAndCheckMinChildren (int upToFloor) { - // Check the minimum number of children in prevTask - if (prevTask != null) checkMinChildren(prevTask); - - // Check the minimum number of children while popping up to the specified floor - while (stack.size > upToFloor) { - StackedTask stackedTask = stack.pop(); - checkMinChildren(stackedTask); - } - } - - private void checkMinChildren (StackedTask stackedTask) { - // Check the minimum number of children - int minChildren = stackedTask.metadata.minChildren; - if (stackedTask.task.getChildCount() < minChildren) - throw new GdxRuntimeException(stackedTask.name + ": not enough children (" + stackedTask.task.getChildCount() + " < " - + minChildren + ")"); - } - - private void checkRequiredAttributes (StackedTask stackedTask) { - // Check the minimum number of children - Entries entries = stackedTask.metadata.attributes.iterator(); - while (entries.hasNext()) { - Entry entry = entries.next(); - if (entry.value.required && !encounteredAttributes.contains(entry.key)) - throw new GdxRuntimeException(stackedTask.name + ": missing required attribute '" + entry.key + "'"); - } - } - + private StackedTask createStackedTask (String name, Task task) { Metadata metadata = findMetadata(task.getClass()); if (metadata == null) throw new GdxRuntimeException(name + ": @TaskConstraint annotation not found in '" + task.getClass().getSimpleName() + "' class hierarchy"); - return new StackedTask(name, task, metadata); + return new StackedTask(lineNumber, name, task, metadata); } private Metadata findMetadata (Class clazz) { @@ -516,11 +546,13 @@ private Metadata findMetadata (Class clazz) { } protected static class StackedTask { + public int lineNumber; public String name; public Task task; public Metadata metadata; - StackedTask (String name, Task task, Metadata metadata) { + StackedTask (int lineNumber, String name, Task task, Metadata metadata) { + this.lineNumber = lineNumber; this.name = name; this.task = task; this.metadata = metadata; @@ -559,5 +591,185 @@ private static class AttrInfo { this.required = required; } } + + protected static class Subtree { + String name; // root tree must have no name + Task rootTask; + int referenceCount; + + Subtree() { + this(null); + } + + Subtree(String name) { + this.name = name; + this.rootTask = null; + this.referenceCount = 0; + } + + public void init(Task rootTask) { + this.rootTask = rootTask; + } + + public boolean inited() { + return rootTask != null; + } + + public boolean isRootTree() { + return name == null || "".equals(name); + } + + public Task rootTaskInstance () { + if (referenceCount++ == 0) { + return rootTask; + } + return rootTask.cloneTask(); + } + } + + ObjectMap userImports = new ObjectMap(); + + ObjectMap> subtrees = new ObjectMap>(); + Subtree currentTree; + + int currentTreeStartIndent; + int currentDepth; + int step; + boolean isSubtreeRef; + protected StackedTask prevTask; + protected StackedTask guardChain; + protected Array> stack = new Array>(); + ObjectSet encounteredAttributes = new ObjectSet(); + boolean isGuard; + + StackedTask getLastStackedTask() { + return stack.peek(); + } + + StackedTask getPrevTask() { + return prevTask; + } + + StackedTask getCurrentTask() { + return isGuard? guardChain : prevTask; + } + + void updateCurrentTask(StackedTask stackedTask, int indent, boolean isGuard) { + this.isGuard = isGuard; + stackedTask.task.guard = guardChain == null ? null : guardChain.task; + if (isGuard) { + guardChain = stackedTask; + } + else { + prevTask = stackedTask; + guardChain = null; + currentDepth = indent; + } + } + + void clear() { + prevTask = null; + guardChain = null; + currentTree = null; + userImports.clear(); + subtrees.clear(); + stack.clear(); + encounteredAttributes.clear(); + } + + // + // Subtree + // + + void switchToNewTree(String name) { + System.out.println(">>>>>>>>>>>>>>>>>> switchToNewTree '" + name + "'"); + // TODO chiudere albero precedente + + // Pop all task from the stack and check their minimum number of children + popAndCheckMinChildren(0); + + this.currentTree = new Subtree(name); + Subtree oldTree = subtrees.put(name, currentTree); + if (oldTree != null) + throw new GdxRuntimeException("A subtree named '" + name + "' is already defined"); + } + + void initCurrentTree(Task rootTask, int startIndent) { + currentDepth = -1; + step = 1; + currentTreeStartIndent = startIndent; + this.currentTree.init(rootTask); + prevTask = null; + } + + Task subtreeRootTaskInstance(String name) { + Subtree tree = subtrees.get(name); + if (tree == null) + throw new GdxRuntimeException("Undefined subtree with name '" + name + "'"); + return tree.rootTaskInstance(); + } + + // + // Import + // + + void addImport (String alias, String task) { + if (task == null) throw new GdxRuntimeException("import: missing task class name."); + if (alias == null) { + Class clazz = null; + try { + clazz = ClassReflection.forName(task); + } catch (ReflectionException e) { + throw new GdxRuntimeException("import: class not found '" + task + "'"); + } + alias = clazz.getSimpleName(); + } + String className = getImport(alias); + if (className != null) throw new GdxRuntimeException("import: alias '" + alias + "' previously defined already."); + userImports.put(alias, task); + } + + String getImport (String as) { + String className = DEFAULT_IMPORTS.get(as); + return className != null ? className : userImports.get(as); + } + + // + // Integrity checks + // + + private void popAndCheckMinChildren (int upToFloor) { + // Check the minimum number of children in prevTask + if (prevTask != null) checkMinChildren(prevTask); + + // Check the minimum number of children while popping up to the specified floor + while (stack.size > upToFloor) { + StackedTask stackedTask = stack.pop(); + checkMinChildren(stackedTask); + } + } + + private void checkMinChildren (StackedTask stackedTask) { + // Check the minimum number of children + int minChildren = stackedTask.metadata.minChildren; + if (stackedTask.task.getChildCount() < minChildren) + throw stackedTaskException(stackedTask, "not enough children (" + stackedTask.task.getChildCount() + " < " + minChildren + + ")"); + } + + private void checkRequiredAttributes (StackedTask stackedTask) { + // Check the minimum number of children + Entries entries = stackedTask.metadata.attributes.iterator(); + while (entries.hasNext()) { + Entry entry = entries.next(); + if (entry.value.required && !encounteredAttributes.contains(entry.key)) + throw stackedTaskException(stackedTask, "missing required attribute '" + entry.key + "'"); + } + } + + private GdxRuntimeException stackedTaskException(StackedTask stackedTask, String message) { + return new GdxRuntimeException(stackedTask.name + " at line " + stackedTask.lineNumber + ": " + message); + } + } } diff --git a/gdx-ai/src/com/badlogic/gdx/ai/btree/utils/BehaviorTreeReader.java b/gdx-ai/src/com/badlogic/gdx/ai/btree/utils/BehaviorTreeReader.java index 8fc27cf7..d7cc6b36 100644 --- a/gdx-ai/src/com/badlogic/gdx/ai/btree/utils/BehaviorTreeReader.java +++ b/gdx-ai/src/com/badlogic/gdx/ai/btree/utils/BehaviorTreeReader.java @@ -43,12 +43,16 @@ public abstract class BehaviorTreeReader { protected int lineNumber; protected boolean reportsComments; - protected abstract void startStatement (int indent, String name); + protected abstract void startLine (int indent); + + protected abstract void startStatement (String name, boolean isSubtreeReference, boolean isGuard); protected abstract void attribute (String name, Object value); protected abstract void endStatement (); + protected abstract void endLine (); + protected void comment (String text) { } @@ -127,6 +131,9 @@ public void parse (char[] data, int offset, int length) { int s = 0; int indent = 0; + int taskIndex = -1; + boolean isGuard = false; + boolean isSubtreeRef = false; String statementName = null; boolean taskProcessed = false; boolean needsUnescape = false; @@ -138,12 +145,12 @@ public void parse (char[] data, int offset, int length) { try { -// line 140 "BehaviorTreeReader.java" +// line 147 "BehaviorTreeReader.java" { cs = btree_start; } -// line 144 "BehaviorTreeReader.java" +// line 151 "BehaviorTreeReader.java" { int _klen; int _trans = 0; @@ -213,7 +220,6 @@ else if ( data[p] > _btree_trans_keys[_mid+1] ) } } while (false); - _trans = _btree_indicies[_trans]; cs = _btree_trans_targs[_trans]; if ( _btree_trans_actions[_trans] != 0 ) { @@ -224,7 +230,7 @@ else if ( data[p] > _btree_trans_keys[_mid+1] ) switch ( _btree_actions[_acts++] ) { case 0: -// line 141 "BehaviorTreeReader.rl" +// line 148 "BehaviorTreeReader.rl" { String value = new String(data, s, p - s); s = p; @@ -267,7 +273,7 @@ else if ( data[p] > _btree_trans_keys[_mid+1] ) } break; case 1: -// line 181 "BehaviorTreeReader.rl" +// line 188 "BehaviorTreeReader.rl" { if (debug) GdxAI.getLogger().info(LOG_TAG, "unquotedChars"); s = p; @@ -279,6 +285,8 @@ else if ( data[p] > _btree_trans_keys[_mid+1] ) case '\\': needsUnescape = true; break; + case ')': + case '(': case ' ': case '\r': case '\n': @@ -293,7 +301,7 @@ else if ( data[p] > _btree_trans_keys[_mid+1] ) } break; case 2: -// line 204 "BehaviorTreeReader.rl" +// line 213 "BehaviorTreeReader.rl" { if (debug) GdxAI.getLogger().info(LOG_TAG, "quotedChars"); s = ++p; @@ -316,9 +324,12 @@ else if ( data[p] > _btree_trans_keys[_mid+1] ) } break; case 3: -// line 224 "BehaviorTreeReader.rl" +// line 233 "BehaviorTreeReader.rl" { indent = 0; + taskIndex = -1; + isGuard = false; + isSubtreeRef = false; statementName = null; taskProcessed = false; lineNumber++; @@ -326,28 +337,31 @@ else if ( data[p] > _btree_trans_keys[_mid+1] ) } break; case 4: -// line 231 "BehaviorTreeReader.rl" +// line 243 "BehaviorTreeReader.rl" { indent++; } break; case 5: -// line 234 "BehaviorTreeReader.rl" +// line 246 "BehaviorTreeReader.rl" { + if (taskIndex >= 0) { + endStatement(); // Close the last task of the line + } taskProcessed = true; if (statementName != null) - endStatement(); + endLine(); if (debug) GdxAI.getLogger().info(LOG_TAG, "endLine: indent: " + indent + " taskName: " + statementName + " data[" + p + "] = " + (p >= eof ? "EOF" : "\"" + data[p] + "\"")); } break; case 6: -// line 240 "BehaviorTreeReader.rl" +// line 255 "BehaviorTreeReader.rl" { s = p; } break; case 7: -// line 243 "BehaviorTreeReader.rl" +// line 258 "BehaviorTreeReader.rl" { if (reportsComments) { comment(new String(data, s, p - s)); @@ -357,19 +371,42 @@ else if ( data[p] > _btree_trans_keys[_mid+1] ) } break; case 8: -// line 250 "BehaviorTreeReader.rl" +// line 265 "BehaviorTreeReader.rl" { + if (taskIndex++ < 0) { + startLine(indent); // First task/guard of the line + } + else { + endStatement(); // Close previous task/guard in line + } statementName = new String(data, s, p - s); - startStatement(indent, statementName); + startStatement(statementName, isSubtreeRef, isGuard); // Start this task/guard + isGuard = false; } break; case 9: -// line 254 "BehaviorTreeReader.rl" +// line 276 "BehaviorTreeReader.rl" { attrName = new String(data, s, p - s); } break; -// line 370 "BehaviorTreeReader.java" + case 10: +// line 290 "BehaviorTreeReader.rl" + {isSubtreeRef = false;} + break; + case 11: +// line 291 "BehaviorTreeReader.rl" + {isSubtreeRef = true;} + break; + case 12: +// line 293 "BehaviorTreeReader.rl" + {isGuard = true;} + break; + case 13: +// line 293 "BehaviorTreeReader.rl" + {isGuard = false;} + break; +// line 407 "BehaviorTreeReader.java" } } } @@ -391,7 +428,7 @@ else if ( data[p] > _btree_trans_keys[_mid+1] ) while ( __nacts-- > 0 ) { switch ( _btree_actions[__acts++] ) { case 0: -// line 141 "BehaviorTreeReader.rl" +// line 148 "BehaviorTreeReader.rl" { String value = new String(data, s, p - s); s = p; @@ -434,22 +471,25 @@ else if ( data[p] > _btree_trans_keys[_mid+1] ) } break; case 5: -// line 234 "BehaviorTreeReader.rl" +// line 246 "BehaviorTreeReader.rl" { + if (taskIndex >= 0) { + endStatement(); // Close the last task of the line + } taskProcessed = true; if (statementName != null) - endStatement(); + endLine(); if (debug) GdxAI.getLogger().info(LOG_TAG, "endLine: indent: " + indent + " taskName: " + statementName + " data[" + p + "] = " + (p >= eof ? "EOF" : "\"" + data[p] + "\"")); } break; case 6: -// line 240 "BehaviorTreeReader.rl" +// line 255 "BehaviorTreeReader.rl" { s = p; } break; case 7: -// line 243 "BehaviorTreeReader.rl" +// line 258 "BehaviorTreeReader.rl" { if (reportsComments) { comment(new String(data, s, p - s)); @@ -459,13 +499,28 @@ else if ( data[p] > _btree_trans_keys[_mid+1] ) } break; case 8: -// line 250 "BehaviorTreeReader.rl" +// line 265 "BehaviorTreeReader.rl" { + if (taskIndex++ < 0) { + startLine(indent); // First task/guard of the line + } + else { + endStatement(); // Close previous task/guard in line + } statementName = new String(data, s, p - s); - startStatement(indent, statementName); + startStatement(statementName, isSubtreeRef, isGuard); // Start this task/guard + isGuard = false; } break; -// line 466 "BehaviorTreeReader.java" + case 10: +// line 290 "BehaviorTreeReader.rl" + {isSubtreeRef = false;} + break; + case 11: +// line 291 "BehaviorTreeReader.rl" + {isSubtreeRef = true;} + break; +// line 521 "BehaviorTreeReader.java" } } } @@ -475,7 +530,7 @@ else if ( data[p] > _btree_trans_keys[_mid+1] ) break; } } -// line 276 "BehaviorTreeReader.rl" +// line 300 "BehaviorTreeReader.rl" } catch (RuntimeException ex) { parseRuntimeEx = ex; @@ -490,15 +545,17 @@ else if ( data[p] > _btree_trans_keys[_mid+1] ) } -// line 489 "BehaviorTreeReader.java" +// line 544 "BehaviorTreeReader.java" private static byte[] init__btree_actions_0() { return new byte [] { 0, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, - 5, 1, 6, 1, 8, 1, 9, 2, 0, 5, 2, 5, - 3, 2, 7, 5, 2, 8, 5, 3, 0, 5, 3, 3, - 6, 7, 5, 3, 7, 5, 3, 3, 8, 5, 3, 4, - 6, 7, 5, 3 + 5, 1, 6, 1, 9, 1, 12, 1, 13, 2, 0, 5, + 2, 0, 13, 2, 5, 3, 2, 7, 5, 2, 10, 8, + 2, 11, 8, 3, 0, 5, 3, 3, 6, 7, 5, 3, + 7, 5, 3, 3, 10, 8, 5, 3, 10, 8, 13, 3, + 11, 8, 5, 3, 11, 8, 13, 4, 6, 7, 5, 3, + 4, 10, 8, 5, 3, 4, 11, 8, 5, 3 }; } @@ -508,9 +565,12 @@ private static byte[] init__btree_actions_0() private static short[] init__btree_key_offsets_0() { return new short [] { - 0, 0, 1, 13, 17, 24, 25, 29, 34, 46, 50, 57, - 58, 62, 67, 77, 87, 92, 94, 96, 110, 120, 125, 130, - 135, 140, 154, 164, 169, 174 + 0, 0, 1, 6, 16, 21, 33, 37, 47, 59, 63, 72, + 73, 77, 82, 86, 99, 108, 120, 124, 133, 137, 138, 142, + 146, 151, 155, 160, 170, 175, 187, 191, 201, 213, 217, 226, + 227, 231, 236, 240, 253, 262, 274, 278, 287, 291, 292, 296, + 300, 305, 309, 321, 333, 345, 347, 349, 362, 367, 372, 386, + 396, 401, 406, 411, 423, 436, 441, 446, 460, 470, 475, 480 }; } @@ -520,21 +580,47 @@ private static short[] init__btree_key_offsets_0() private static char[] init__btree_trans_keys_0() { return new char [] { - 10, 9, 13, 32, 58, 63, 95, 48, 57, 65, 90, 97, - 122, 9, 13, 32, 58, 9, 10, 13, 32, 34, 35, 58, + 10, 95, 65, 90, 97, 122, 9, 13, 32, 36, 41, 95, + 65, 90, 97, 122, 95, 65, 90, 97, 122, 9, 13, 32, + 41, 63, 95, 48, 57, 65, 90, 97, 122, 9, 13, 32, + 41, 9, 13, 32, 36, 40, 95, 65, 90, 97, 122, 9, + 13, 32, 58, 63, 95, 48, 57, 65, 90, 97, 122, 9, + 13, 32, 58, 9, 10, 13, 32, 34, 35, 58, 40, 41, 34, 9, 13, 32, 58, 95, 65, 90, 97, 122, 9, 13, + 32, 41, 9, 13, 32, 41, 46, 63, 95, 48, 57, 65, + 90, 97, 122, 9, 13, 32, 41, 95, 65, 90, 97, 122, + 9, 13, 32, 58, 63, 95, 48, 57, 65, 90, 97, 122, + 9, 13, 32, 58, 9, 10, 13, 32, 34, 35, 58, 40, + 41, 9, 13, 32, 41, 34, 9, 13, 32, 41, 9, 13, + 32, 58, 95, 65, 90, 97, 122, 9, 13, 32, 41, 95, + 65, 90, 97, 122, 9, 13, 32, 36, 41, 95, 65, 90, + 97, 122, 95, 65, 90, 97, 122, 9, 13, 32, 41, 63, + 95, 48, 57, 65, 90, 97, 122, 9, 13, 32, 41, 9, + 13, 32, 36, 40, 95, 65, 90, 97, 122, 9, 13, 32, + 58, 63, 95, 48, 57, 65, 90, 97, 122, 9, 13, 32, + 58, 9, 10, 13, 32, 34, 35, 58, 40, 41, 34, 9, + 13, 32, 58, 95, 65, 90, 97, 122, 9, 13, 32, 41, + 9, 13, 32, 41, 46, 63, 95, 48, 57, 65, 90, 97, + 122, 9, 13, 32, 41, 95, 65, 90, 97, 122, 9, 13, 32, 58, 63, 95, 48, 57, 65, 90, 97, 122, 9, 13, - 32, 58, 9, 10, 13, 32, 34, 35, 58, 34, 9, 13, - 32, 58, 95, 65, 90, 97, 122, 9, 10, 13, 32, 35, - 95, 65, 90, 97, 122, 9, 10, 13, 32, 35, 95, 65, - 90, 97, 122, 9, 10, 13, 32, 35, 10, 13, 10, 13, + 32, 58, 9, 10, 13, 32, 34, 35, 58, 40, 41, 9, + 13, 32, 41, 34, 9, 13, 32, 41, 9, 13, 32, 58, + 95, 65, 90, 97, 122, 9, 13, 32, 41, 9, 10, 13, + 32, 35, 36, 40, 95, 65, 90, 97, 122, 9, 10, 13, + 32, 35, 36, 40, 95, 65, 90, 97, 122, 9, 10, 13, + 32, 35, 36, 40, 95, 65, 90, 97, 122, 10, 13, 10, + 13, 9, 10, 13, 32, 35, 63, 95, 48, 57, 65, 90, + 97, 122, 9, 10, 13, 32, 35, 9, 10, 13, 32, 35, 9, 10, 13, 32, 35, 46, 63, 95, 48, 57, 65, 90, 97, 122, 9, 10, 13, 32, 35, 95, 65, 90, 97, 122, 9, 10, 13, 32, 35, 9, 10, 13, 32, 35, 9, 10, - 13, 32, 35, 9, 10, 13, 32, 35, 9, 10, 13, 32, - 35, 46, 63, 95, 48, 57, 65, 90, 97, 122, 9, 10, - 13, 32, 35, 95, 65, 90, 97, 122, 9, 10, 13, 32, - 35, 9, 10, 13, 32, 35, 9, 10, 13, 32, 35, 0 + 13, 32, 35, 9, 10, 13, 32, 35, 36, 40, 95, 65, + 90, 97, 122, 9, 10, 13, 32, 35, 63, 95, 48, 57, + 65, 90, 97, 122, 9, 10, 13, 32, 35, 9, 10, 13, + 32, 35, 9, 10, 13, 32, 35, 46, 63, 95, 48, 57, + 65, 90, 97, 122, 9, 10, 13, 32, 35, 95, 65, 90, + 97, 122, 9, 10, 13, 32, 35, 9, 10, 13, 32, 35, + 9, 10, 13, 32, 35, 0 }; } @@ -544,9 +630,12 @@ private static char[] init__btree_trans_keys_0() private static byte[] init__btree_single_lengths_0() { return new byte [] { - 0, 1, 6, 4, 7, 1, 4, 1, 6, 4, 7, 1, - 4, 1, 6, 6, 5, 2, 2, 8, 6, 5, 5, 5, - 5, 8, 6, 5, 5, 5 + 0, 1, 1, 6, 1, 6, 4, 6, 6, 4, 7, 1, + 4, 1, 4, 7, 5, 6, 4, 7, 4, 1, 4, 4, + 1, 4, 1, 6, 1, 6, 4, 6, 6, 4, 7, 1, + 4, 1, 4, 7, 5, 6, 4, 7, 4, 1, 4, 4, + 1, 4, 8, 8, 8, 2, 2, 7, 5, 5, 8, 6, + 5, 5, 5, 8, 7, 5, 5, 8, 6, 5, 5, 5 }; } @@ -556,9 +645,12 @@ private static byte[] init__btree_single_lengths_0() private static byte[] init__btree_range_lengths_0() { return new byte [] { - 0, 0, 3, 0, 0, 0, 0, 2, 3, 0, 0, 0, - 0, 2, 2, 2, 0, 0, 0, 3, 2, 0, 0, 0, - 0, 3, 2, 0, 0, 0 + 0, 0, 2, 2, 2, 3, 0, 2, 3, 0, 1, 0, + 0, 2, 0, 3, 2, 3, 0, 1, 0, 0, 0, 0, + 2, 0, 2, 2, 2, 3, 0, 2, 3, 0, 1, 0, + 0, 2, 0, 3, 2, 3, 0, 1, 0, 0, 0, 0, + 2, 0, 2, 2, 2, 0, 0, 3, 0, 0, 3, 2, + 0, 0, 0, 2, 3, 0, 0, 3, 2, 0, 0, 0 }; } @@ -568,48 +660,61 @@ private static byte[] init__btree_range_lengths_0() private static short[] init__btree_index_offsets_0() { return new short [] { - 0, 0, 2, 12, 17, 25, 27, 32, 36, 46, 51, 59, - 61, 66, 70, 79, 88, 94, 97, 100, 112, 121, 127, 133, - 139, 145, 157, 166, 172, 178 + 0, 0, 2, 6, 15, 19, 29, 34, 43, 53, 58, 67, + 69, 74, 78, 83, 94, 102, 112, 117, 126, 131, 133, 138, + 143, 147, 152, 156, 165, 169, 179, 184, 193, 203, 208, 217, + 219, 224, 228, 233, 244, 252, 262, 267, 276, 281, 283, 288, + 293, 297, 302, 313, 324, 335, 338, 341, 352, 358, 364, 376, + 385, 391, 397, 403, 414, 425, 431, 437, 449, 458, 464, 470 }; } private static final short _btree_index_offsets[] = init__btree_index_offsets_0(); -private static byte[] init__btree_indicies_0() -{ - return new byte [] { - 0, 1, 2, 2, 2, 4, 5, 3, 3, 3, 3, 1, - 6, 6, 6, 7, 1, 7, 1, 7, 7, 9, 1, 1, - 8, 10, 1, 2, 2, 2, 4, 1, 11, 11, 11, 1, - 12, 12, 12, 14, 15, 13, 13, 13, 13, 1, 16, 16, - 16, 17, 1, 17, 1, 17, 17, 19, 1, 1, 18, 20, - 1, 12, 12, 12, 14, 1, 21, 21, 21, 1, 22, 23, - 24, 22, 25, 26, 26, 26, 1, 27, 23, 28, 27, 25, - 29, 29, 29, 1, 28, 23, 28, 28, 25, 1, 31, 32, - 30, 34, 35, 33, 36, 37, 36, 36, 38, 39, 40, 11, - 11, 11, 11, 1, 41, 23, 41, 41, 25, 42, 42, 42, - 1, 43, 44, 43, 43, 45, 1, 41, 23, 41, 41, 25, - 1, 36, 37, 36, 36, 38, 1, 46, 23, 24, 46, 25, - 1, 47, 37, 48, 47, 38, 49, 50, 21, 21, 21, 21, - 1, 51, 23, 52, 51, 25, 53, 53, 53, 1, 54, 44, - 55, 54, 45, 1, 51, 23, 52, 51, 25, 1, 47, 37, - 48, 47, 38, 1, 0 - }; -} - -private static final byte _btree_indicies[] = init__btree_indicies_0(); - - private static byte[] init__btree_trans_targs_0() { return new byte [] { - 15, 0, 3, 2, 4, 6, 3, 4, 21, 5, 22, 19, - 9, 8, 10, 12, 9, 10, 27, 11, 28, 25, 14, 15, - 24, 17, 25, 15, 16, 19, 18, 15, 1, 18, 15, 1, - 20, 15, 17, 7, 23, 20, 2, 20, 15, 17, 24, 26, - 26, 13, 29, 26, 26, 8, 26, 26 + 51, 0, 55, 55, 55, 0, 3, 3, 3, 4, 7, 15, + 15, 15, 0, 5, 5, 5, 0, 6, 6, 6, 7, 14, + 5, 5, 5, 5, 0, 6, 6, 6, 7, 0, 7, 7, + 7, 2, 3, 58, 58, 58, 0, 9, 9, 9, 10, 12, + 8, 8, 8, 8, 0, 9, 9, 9, 10, 0, 10, 0, + 10, 10, 11, 0, 0, 0, 60, 61, 0, 9, 9, 9, + 10, 0, 58, 58, 58, 0, 6, 6, 6, 7, 0, 16, + 16, 16, 7, 24, 25, 15, 15, 15, 15, 0, 16, 16, + 16, 7, 17, 17, 17, 0, 18, 18, 18, 19, 23, 17, + 17, 17, 17, 0, 18, 18, 18, 19, 0, 19, 0, 19, + 19, 21, 0, 0, 0, 20, 16, 16, 16, 7, 0, 22, + 0, 16, 16, 16, 7, 0, 18, 18, 18, 19, 0, 15, + 15, 15, 0, 16, 16, 16, 7, 0, 64, 64, 64, 0, + 27, 27, 27, 28, 31, 39, 39, 39, 0, 29, 29, 29, + 0, 30, 30, 30, 31, 38, 29, 29, 29, 29, 0, 30, + 30, 30, 31, 0, 31, 31, 31, 26, 27, 67, 67, 67, + 0, 33, 33, 33, 34, 36, 32, 32, 32, 32, 0, 33, + 33, 33, 34, 0, 34, 0, 34, 34, 35, 0, 0, 0, + 69, 70, 0, 33, 33, 33, 34, 0, 67, 67, 67, 0, + 30, 30, 30, 31, 0, 40, 40, 40, 31, 48, 49, 39, + 39, 39, 39, 0, 40, 40, 40, 31, 41, 41, 41, 0, + 42, 42, 42, 43, 47, 41, 41, 41, 41, 0, 42, 42, + 42, 43, 0, 43, 0, 43, 43, 45, 0, 0, 0, 44, + 40, 40, 40, 31, 0, 46, 0, 40, 40, 40, 31, 0, + 42, 42, 42, 43, 0, 39, 39, 39, 0, 40, 40, 40, + 31, 0, 50, 51, 63, 50, 53, 26, 27, 67, 67, 67, + 0, 51, 51, 52, 51, 53, 2, 3, 58, 58, 58, 0, + 52, 51, 52, 52, 53, 2, 3, 58, 58, 58, 0, 51, + 1, 54, 51, 1, 54, 56, 51, 56, 56, 53, 57, 55, + 55, 55, 55, 0, 56, 51, 56, 56, 53, 0, 56, 51, + 56, 56, 53, 0, 59, 51, 59, 59, 53, 13, 62, 58, + 58, 58, 58, 0, 59, 51, 59, 59, 53, 8, 8, 8, + 0, 59, 51, 59, 59, 53, 0, 59, 51, 59, 59, 53, + 0, 59, 51, 59, 59, 53, 0, 63, 51, 63, 63, 53, + 26, 27, 67, 67, 67, 0, 65, 51, 65, 65, 53, 66, + 64, 64, 64, 64, 0, 65, 51, 65, 65, 53, 0, 65, + 51, 65, 65, 53, 0, 68, 51, 68, 68, 53, 37, 71, + 67, 67, 67, 67, 0, 68, 51, 68, 68, 53, 32, 32, + 32, 0, 68, 51, 68, 68, 53, 0, 68, 51, 68, 68, + 53, 0, 68, 51, 68, 68, 53, 0, 0 }; } @@ -619,11 +724,46 @@ private static byte[] init__btree_trans_targs_0() private static byte[] init__btree_trans_actions_0() { return new byte [] { - 7, 0, 17, 0, 17, 0, 0, 0, 3, 5, 1, 0, - 17, 0, 17, 0, 0, 0, 3, 5, 1, 0, 9, 22, - 11, 0, 13, 9, 0, 13, 13, 47, 35, 0, 39, 25, - 15, 43, 15, 0, 0, 0, 13, 1, 31, 1, 0, 15, - 28, 0, 0, 0, 11, 13, 1, 19 + 7, 0, 13, 13, 13, 0, 0, 0, 0, 0, 19, 13, + 13, 13, 0, 13, 13, 13, 0, 36, 36, 36, 63, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, + 0, 0, 17, 13, 13, 13, 0, 15, 15, 15, 15, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 5, 0, 0, 0, 3, 1, 0, 15, 15, 15, + 15, 0, 0, 0, 0, 0, 36, 36, 36, 63, 0, 33, + 33, 33, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 19, 13, 13, 13, 0, 15, 15, 15, 15, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 5, 0, 0, 0, 3, 1, 1, 1, 24, 0, 1, + 0, 0, 0, 0, 19, 0, 15, 15, 15, 15, 0, 0, + 0, 0, 0, 33, 33, 33, 55, 0, 13, 13, 13, 0, + 0, 0, 0, 0, 19, 13, 13, 13, 0, 13, 13, 13, + 0, 36, 36, 36, 63, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 19, 0, 0, 0, 0, 0, 17, 13, 13, 13, + 0, 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, + 3, 1, 0, 15, 15, 15, 15, 0, 0, 0, 0, 0, + 36, 36, 36, 63, 0, 33, 33, 33, 55, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 19, 13, 13, 13, 0, + 15, 15, 15, 15, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 3, + 1, 1, 1, 24, 0, 1, 0, 0, 0, 0, 19, 0, + 15, 15, 15, 15, 0, 0, 0, 0, 0, 33, 33, 33, + 55, 0, 9, 27, 11, 9, 0, 0, 17, 13, 13, 13, + 0, 9, 27, 0, 9, 0, 0, 17, 13, 13, 13, 0, + 0, 27, 0, 0, 0, 0, 17, 13, 13, 13, 0, 67, + 43, 13, 47, 30, 0, 36, 77, 36, 36, 36, 0, 0, + 0, 0, 0, 0, 0, 27, 0, 0, 0, 0, 36, 77, + 36, 36, 36, 0, 33, 72, 33, 33, 33, 0, 0, 0, + 0, 0, 0, 0, 0, 27, 0, 0, 0, 13, 13, 13, + 0, 1, 39, 1, 1, 1, 0, 0, 27, 0, 0, 0, + 0, 33, 72, 33, 33, 33, 0, 0, 27, 11, 0, 0, + 0, 17, 13, 13, 13, 0, 36, 77, 59, 36, 36, 0, + 0, 0, 0, 0, 0, 0, 27, 11, 0, 0, 0, 36, + 77, 59, 36, 36, 0, 33, 72, 51, 33, 33, 0, 0, + 0, 0, 0, 0, 0, 0, 27, 11, 0, 0, 13, 13, + 13, 0, 1, 39, 21, 1, 1, 0, 0, 27, 11, 0, + 0, 0, 33, 72, 51, 33, 33, 0, 0 }; } @@ -634,22 +774,25 @@ private static byte[] init__btree_eof_actions_0() { return new byte [] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 11, 11, 11, 35, 25, 28, 11, 19, 11, 28, - 11, 28, 11, 19, 11, 28 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 11, 11, 11, 43, 30, 59, 11, 59, 51, 11, + 21, 11, 51, 11, 59, 11, 59, 51, 11, 21, 11, 51 }; } private static final byte _btree_eof_actions[] = init__btree_eof_actions_0(); -static final int btree_start = 14; -static final int btree_first_final = 14; +static final int btree_start = 50; +static final int btree_first_final = 50; static final int btree_error = 0; -static final int btree_en_main = 14; +static final int btree_en_main = 50; -// line 290 "BehaviorTreeReader.rl" +// line 314 "BehaviorTreeReader.rl" private static boolean containsFloatingPointCharacters (String value) { for (int i = 0, n = value.length(); i < n; i++) { diff --git a/gdx-ai/src/com/badlogic/gdx/ai/btree/utils/BehaviorTreeReader.rl b/gdx-ai/src/com/badlogic/gdx/ai/btree/utils/BehaviorTreeReader.rl index b5b2d17e..f5369e6a 100644 --- a/gdx-ai/src/com/badlogic/gdx/ai/btree/utils/BehaviorTreeReader.rl +++ b/gdx-ai/src/com/badlogic/gdx/ai/btree/utils/BehaviorTreeReader.rl @@ -41,12 +41,16 @@ public abstract class BehaviorTreeReader { protected int lineNumber; protected boolean reportsComments; - protected abstract void startStatement (int indent, String name); + protected abstract void startLine (int indent); + + protected abstract void startStatement (String name, boolean isSubtreeReference, boolean isGuard); protected abstract void attribute (String name, Object value); protected abstract void endStatement (); + protected abstract void endLine (); + protected void comment (String text) { } @@ -125,6 +129,9 @@ public abstract class BehaviorTreeReader { int s = 0; int indent = 0; + int taskIndex = -1; + boolean isGuard = false; + boolean isSubtreeRef = false; String statementName = null; boolean taskProcessed = false; boolean needsUnescape = false; @@ -189,6 +196,8 @@ public abstract class BehaviorTreeReader { case '\\': needsUnescape = true; break; + case ')': + case '(': case ' ': case '\r': case '\n': @@ -223,6 +232,9 @@ public abstract class BehaviorTreeReader { } action newLine { indent = 0; + taskIndex = -1; + isGuard = false; + isSubtreeRef = false; statementName = null; taskProcessed = false; lineNumber++; @@ -232,9 +244,12 @@ public abstract class BehaviorTreeReader { indent++; } action endLine { + if (taskIndex >= 0) { + endStatement(); // Close the last task of the line + } taskProcessed = true; if (statementName != null) - endStatement(); + endLine(); if (debug) GdxAI.getLogger().info(LOG_TAG, "endLine: indent: " + indent + " taskName: " + statementName + " data[" + p + "] = " + (p >= eof ? "EOF" : "\"" + data[p] + "\"")); } action savePos { @@ -248,8 +263,15 @@ public abstract class BehaviorTreeReader { } } action taskName { + if (taskIndex++ < 0) { + startLine(indent); // First task/guard of the line + } + else { + endStatement(); // Close previous task/guard in line + } statementName = new String(data, s, p - s); - startStatement(indent, statementName); + startStatement(statementName, isSubtreeRef, isGuard); // Start this task/guard + isGuard = false; } action attrName { attrName = new String(data, s, p - s); @@ -262,11 +284,13 @@ public abstract class BehaviorTreeReader { comment = '#' /[^\r\n]*/ >savePos %comment; indent = [ \t] @indent; attrName = idBegin '?'? %attrName; - attrValue = '"' @quotedChars %attrValue '"' | ^[#:"\r\n\t ] >unquotedChars %attrValue; + attrValue = '"' @quotedChars %attrValue '"' | ^[#:"()\r\n\t ] >unquotedChars %attrValue; attribute = attrName ws* ':' ws* attrValue; attributes = (ws+ attribute)+; - taskName = idBegin ('.' id)* '?'? %taskName; - task = taskName attributes?; + taskName = idBegin ('.' id)* '?'? %{isSubtreeRef = false;} %taskName; + subtreeRef = '$' idBegin '?'? %{isSubtreeRef = true;} %taskName; + unguardedTask = taskName attributes? | subtreeRef; # task with attributes or subtree reference + task = (ws* '(' @{isGuard = true;} ws* unguardedTask? ws* ')' @{isGuard = false;} )* ws* unguardedTask; line = indent* task? ws* comment? %endLine; main := line (nl line)** nl?; diff --git a/tests/src/com/badlogic/gdx/ai/tests/btree/tests/ParseTreeTest.java b/tests/src/com/badlogic/gdx/ai/tests/btree/tests/ParseTreeTest.java index 4669f1df..7e3cb13c 100644 --- a/tests/src/com/badlogic/gdx/ai/tests/btree/tests/ParseTreeTest.java +++ b/tests/src/com/badlogic/gdx/ai/tests/btree/tests/ParseTreeTest.java @@ -46,7 +46,7 @@ public Actor createActor (Skin skin) { Reader reader = null; try { reader = Gdx.files.internal("data/dog.tree").reader(); - BehaviorTreeParser parser = new BehaviorTreeParser(BehaviorTreeParser.DEBUG_NONE); + BehaviorTreeParser parser = new BehaviorTreeParser(BehaviorTreeParser.DEBUG_HIGH); BehaviorTree tree = parser.parse(reader, new Dog("Buddy")); treeViewer = createTreeViewer(tree.getObject().name, tree, true, skin);