View Javadoc

1   /*
2    * Entreri, an entity-component framework in Java
3    *
4    * Copyright (c) 2013, Michael Ludwig
5    * All rights reserved.
6    *
7    * Redistribution and use in source and binary forms, with or without modification,
8    * are permitted provided that the following conditions are met:
9    *
10   *     Redistributions of source code must retain the above copyright notice,
11   *         this list of conditions and the following disclaimer.
12   *     Redistributions in binary form must reproduce the above copyright notice,
13   *         this list of conditions and the following disclaimer in the
14   *         documentation and/or other materials provided with the distribution.
15   *
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
20   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   */
27  package com.lhkbob.entreri.task;
28  
29  import com.lhkbob.entreri.Component;
30  import com.lhkbob.entreri.ComponentIterator;
31  import com.lhkbob.entreri.EntitySystem;
32  
33  import java.lang.annotation.*;
34  import java.lang.reflect.Method;
35  
36  /**
37   * <p/>
38   * SimpleTask extends Task adds logic to simplify the creation of tasks that perform the
39   * same operations on each entity that matches a specific component configuration.
40   * Subclasses of SimpleTask should define a single method named 'processEntity' that takes
41   * as its only parameters, any number of Component instances of specific types. An example
42   * might be:
43   * <p/>
44   * <pre>
45   * public class ExampleTask extends SimpleTask {
46   *     // c1 and c2 are required types
47   *     // c3 is an optional component type
48   *     protected void processEntities(TypeA c1, TypeB c2, @Optional TypeC c3) {
49   *         // perform operations on c1 and c2
50   *         if (c3 != null) {
51   *             // perform additional operations on c3
52   *         }
53   *     }
54   *
55   *     public Task process(EntitySystem system, Job job) {
56   *         // this will invoke processEntities() for each entity in the system
57   *         // that has a TypeA and TypeB component. If the entity also has
58   *         // a TypeC component, it is passed in too, otherwise it's null
59   *         processEntities(system);
60   *         return null;
61   *     }
62   * }
63   * </pre>
64   * <p/>
65   * In the task's {@link #process(EntitySystem, Job)} method, it can then invoke {@link
66   * #processEntities(EntitySystem)} to perform the automated iteration over matching
67   * entities within the system. SimpleTask will call the identified 'processEntity' method
68   * for each matched entity.
69   *
70   * @author Michael Ludwig
71   */
72  public abstract class SimpleTask implements Task {
73      @Target(ElementType.PARAMETER)
74      @Retention(RetentionPolicy.RUNTIME)
75      public static @interface Optional {
76      }
77  
78      private final Method processMethod;
79      private final boolean[] optional;
80  
81      // filled with instances after first call to processEntities
82      private final Component[] componentDatas;
83  
84      // "final" after the first call to processEntities() or until the system changes
85      private ComponentIterator iterator;
86      private EntitySystem lastSystem;
87  
88      public SimpleTask() {
89          Method processMethod = null;
90  
91          Class<?> cls = getClass();
92          while (!SimpleTask.class.equals(cls)) {
93              for (Method m : cls.getDeclaredMethods()) {
94                  if (m.getName().equals("processEntity")) {
95                      if (m.getParameterTypes().length > 0 &&
96                          m.getReturnType().equals(boolean.class)) {
97                          boolean paramsValid = true;
98                          for (Class<?> p : m.getParameterTypes()) {
99                              if (!Component.class.isAssignableFrom(p)) {
100                                 paramsValid = false;
101                                 break;
102                             }
103                         }
104 
105                         if (paramsValid) {
106                             if (processMethod == null) {
107                                 processMethod = m;
108                             } else {
109                                 throw new IllegalStateException(
110                                         "More than one processEntity() method defined");
111                             }
112                         }
113                     }
114                 }
115             }
116             cls = cls.getSuperclass();
117         }
118 
119         if (processMethod == null) {
120             throw new IllegalStateException(
121                     "SimpleTask subclasses must define a processEntity() method");
122         }
123 
124         processMethod.setAccessible(true);
125 
126         this.processMethod = processMethod;
127         optional = new boolean[processMethod.getParameterTypes().length];
128         componentDatas = new Component[optional.length];
129 
130         for (int i = 0; i < optional.length; i++) {
131             for (Annotation a : processMethod.getParameterAnnotations()[i]) {
132                 if (a instanceof Optional) {
133                     optional[i] = true;
134                     break;
135                 }
136             }
137         }
138     }
139 
140     /**
141      * The default implementation of process() just invokes {@link
142      * #processEntities(com.lhkbob.entreri.EntitySystem)} immediately and returns no
143      * future task.
144      *
145      * @param system The EntitySystem being processed, which will always be the same for a
146      *               given Task instance
147      * @param job    The Job this task belongs to
148      *
149      * @return Will return null unless overridden
150      */
151     @Override
152     public Task process(EntitySystem system, Job job) {
153         processEntities(system);
154         return null;
155     }
156 
157     /**
158      * Process all entities that fit the component profile mandated by the defined
159      * 'processEntity()' method in the subclass.
160      *
161      * @param system The system to process
162      */
163     @SuppressWarnings({ "unchecked", "rawtypes" })
164     protected void processEntities(EntitySystem system) {
165         if (iterator == null || lastSystem != system) {
166             iterator = system.fastIterator();
167             for (int i = 0; i < optional.length; i++) {
168                 if (optional[i]) {
169                     componentDatas[i] = iterator
170                             .addOptional((Class) processMethod.getParameterTypes()[i]);
171                 } else {
172                     componentDatas[i] = iterator
173                             .addRequired((Class) processMethod.getParameterTypes()[i]);
174                 }
175             }
176 
177             lastSystem = system;
178         }
179 
180         try {
181             Object[] invokeArgs = new Object[optional.length];
182             iterator.reset();
183             while (iterator.next()) {
184                 for (int i = 0; i < optional.length; i++) {
185                     invokeArgs[i] = (optional[i] && !componentDatas[i].isAlive() ? null
186                                                                                  : componentDatas[i]);
187                 }
188 
189                 boolean iterate = ((Boolean) processMethod.invoke(this, invokeArgs));
190                 if (!iterate) {
191                     break;
192                 }
193             }
194         } catch (Exception e) {
195             throw new RuntimeException("Exception while invoking processEntity()", e);
196         }
197     }
198 }