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 }