1. Michael Ludwig
  2. entreri

Commits

Michael Ludwig  committed 2ebea94

Initial commit - moved completed work from ferox-gl/ferox-entity project to the entreri project, and added Maven support.

  • Participants
  • Parent commits f0a5664
  • Branches default

Comments (0)

Files changed (42)

File pom.xml

View file
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.entreri</groupId>
+    <artifactId>entreri</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>Entreri Entity-Component Framework</name>
+
+    <build>
+        <finalName>entreri</finalName>
+
+        <sourceDirectory>src/main/java</sourceDirectory>
+        <testSourceDirectory>src/test/java/</testSourceDirectory>
+
+        <plugins>
+            <plugin>
+            	<groupId>org.apache.maven.plugins</groupId>
+            	<artifactId>maven-compiler-plugin</artifactId>
+            	<version>2.3.2</version>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.8.2</version>
+            <type>jar</type>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>

File src/main/java/com/entreri/Component.java

View file
+package com.entreri;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.entreri.property.IndexedDataStore;
+import com.entreri.property.Property;
+
+/**
+ * <p>
+ * Component represents a set of self-consistent state that are added to an
+ * {@link Entity}. Components are intended to be data storage objects, so their
+ * definition should not contain methods for processing or updating (that is the
+ * responsibility of a {@link Controller}).
+ * </p>
+ * <p>
+ * The behavior or purpose of a Component should be well defined, including its
+ * behavior with respect to other Components attached to the same Entity. It may
+ * be that to function correctly or--more likely--usefully, related Components
+ * will have to be used as well. An example of this might be a transform
+ * component and a shape component for rendering.
+ * </p>
+ * <p>
+ * Each Component class gets a {@link TypedId}, which can be looked up with
+ * {@link #getTypedId(Class)}, passing in the desired class type. Because the
+ * entity-component design pattern does not follow common object-oriented
+ * principles, certain rules are followed when handling Component types in a
+ * class hierarchy:
+ * <ol>
+ * <li>Any abstract type extending Component cannot get a TypedId</li>
+ * <li>All concrete classes extending Component get separate TypedIds, even if
+ * they extend from the same intermediate classes beneath Component.</li>
+ * <li>All intermediate classes in a Component type's hierarchy must be abstract
+ * or runtime exceptions will be thrown.</li>
+ * </ol>
+ * As an example, an abstract component could be Light, with concrete subclasses
+ * SpotLight and DirectionLight. SpotLight and DirectionLight would be separate
+ * component types as determined by TypedId. Light would not have any TypedId
+ * and only serves to consolidate property definition among related component
+ * types.
+ * </p>
+ * <p>
+ * Implementations of Components must follow certain rules with respect to their
+ * declared fields. For performance reasons, an EntitySystem packs all
+ * components of the same type into the same region of memory using the
+ * {@link Property} and {@link IndexedDataStore} API. To ensure that Components
+ * behave correctly, a type can only declare private or protected Property
+ * fields. These fields should be considered "final" from the Components point
+ * of view and will be assigned by the EntitySystem. The can be declared final
+ * but any assigned value will be overwritten.
+ * </p>
+ * <p>
+ * They can declare any methods they wish to expose the data these properties
+ * represent. It is strongly recommended to not expose the Property objects
+ * themselves. See {@link #getTypedId(Class)} for the complete contract.
+ * </p>
+ * <p>
+ * Component instances are tied to an index into the IndexedDataStores used by
+ * their properties. The index can be fetched by calling {@link #getIndex()}. An
+ * instance of Component may have its index changed, effectively changing it to
+ * a different "instance". This is most common when using the fast iterators.
+ * Because of this, reference equality may not work, instead you should rely on
+ * {@link #equals(Object)}.
+ * </p>
+ * 
+ * @author Michael Ludwig
+ */
+public abstract class Component {
+    // Use a ConcurrentHashMap to perform reads. It is still synchronized completely to do
+    // an insert to make sure a type doesn't try to use two different id values.
+    private static final ConcurrentHashMap<Class<? extends Component>, TypedId<? extends Component>> typeMap 
+        = new ConcurrentHashMap<Class<? extends Component>, TypedId<? extends Component>>();
+    private static final ConcurrentHashMap<TypedId<? extends Component>, ComponentBuilder<?>> builderMap
+        = new ConcurrentHashMap<TypedId<? extends Component>, ComponentBuilder<?>>();
+    
+    private static int idSeq = 0;
+    
+    /**
+     * <var>index</var> is a sliding component index into the indexed data store
+     * for each property of the component. It can be mutated by the EntitySystem
+     * to effectively change the Component instance's values to another
+     * component in the system.
+     */
+    int index;
+
+    final ComponentIndex<?> owner;
+    private final TypedId<? extends Component> typedId;
+
+    /**
+     * <p>
+     * Create a new Component instance that has its property data managed by the
+     * given EntitySystem. Multiple Component instances may represent the same
+     * "component" if their index's are the same.
+     * </p>
+     * <p>
+     * Subclasses must call this constructor with the arguments as passed-in and
+     * must not change them. Abstract subclasses can add additional arguments,
+     * but concrete subclasses must have the same constructor signatures except
+     * that it is private.
+     * </p>
+     * 
+     * @param system The owning EntitySystem of the Component
+     * @param index The initial index of this Component
+     * @throws NullPointerException if system is null
+     * @throws IllegalArgumentException if index is less than 0
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    protected Component(EntitySystem system, int index) {
+        if (system == null)
+            throw new NullPointerException("EntitySystem cannot be null");
+        if (index < 0)
+            throw new IllegalArgumentException("Index must be at least 0: " + index);
+        TypedId raw = getTypedId(getClass());
+
+        this.owner = system.getIndex(raw);
+        this.index = index;
+        typedId = raw;
+    }
+
+    /**
+     * Called when the EntitySystem creates a new Component and has properly
+     * configured its declared properties. This is only called when the
+     * Component is being added to an Entity. This is not called when a new
+     * component instance is created for the purposes of a fast iterator
+     * (because it's just acting as a shell in that case).
+     */
+    protected void init() {
+        // do nothing by default
+    }
+    
+    /**
+     * <p>
+     * Return the unique TypedId associated with this Component's class type.
+     * All Components of the same class will return this id, too.
+     * </p>
+     * <p>
+     * It is recommended that implementations override this method to use the
+     * proper return type. Component does not perform this cast to avoid a
+     * parameterizing Component. Do not change the actual returned instance,
+     * though.
+     * </p>
+     * 
+     * @return The TypedId of this Component
+     */
+    public TypedId<? extends Component> getTypedId() {
+        return typedId;
+    }
+
+    /**
+     * Get the Entity that owns this Component. The Entity will still be part of
+     * an EntitySystem, and the component can be iterated over via
+     * {@link EntitySystem#iterator(TypedId)}. If a Component is removed from an
+     * Entity (or the Entity is removed from the system), this will return null.
+     * 
+     * @return The owning Entity
+     * @throws IndexOutOfBoundsException if the Component has been removed from
+     *             an Entity, or if its owning Entity has been removed from its
+     *             EntitySystem.
+     */
+    public final Entity getEntity() {
+        int entityIndex = owner.getEntityIndex(index);
+        return owner.getEntitySystem().getEntityByIndex(entityIndex);
+    }
+
+    /**
+     * Return the index of this Component within the IndexedDataStores that back
+     * the defined properties of a Component. A Component instance may have its
+     * index change if it is being used to slide over the component data (e.g.
+     * in a fast iterator).
+     * 
+     * @return The index of the component used to access its IndexedDataStores.
+     */
+    public final int getIndex() {
+        return index;
+    }
+    
+    @Override
+    public int hashCode() {
+        return index;
+    }
+    
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Component))
+            return false;
+        Component c = (Component) o;
+        if (c.owner == owner && c.typedId == typedId) {
+            // We can't use index because a canonical component might have it change,
+            // instead use its owning entity
+            int tei = owner.getEntitySystem().getEntityId(owner.getEntityIndex(index));
+            int cei = c.owner.getEntitySystem().getEntityId(c.owner.getEntityIndex(c.index));
+            return tei == cei;
+        } else {
+            // type and owner don't match
+            return false;
+        }
+    }
+
+    /**
+     * <p>
+     * Return the unique TypedId instance for the given <tt>type</tt>. If a
+     * TypedId hasn't yet been created a new one is instantiated with the next
+     * numeric id in the internal id sequence. The new TypedId is stored for
+     * later, so that subsequent calls to {@link #getTypedId(Class)} with
+     * <tt>type</tt> will return the same instance.
+     * {@link Component#Component()} implicitly calls this method when a
+     * Component is created.
+     * </p>
+     * <p>
+     * This method also performs runtime checks to ensure the validity of the
+     * Component type definition. The following rules must be upheld or an
+     * {@link IllegalComponentDefinitionException} is thrown.
+     * <ul>
+     * <li>If the class extends from a class other than Component, that class
+     * must be a subclass of Component and be declared abstract. Additional
+     * rules might affect these parent classes.</li>
+     * <li>A concrete Component type must have only one constructor; it must be
+     * private and with arguments: EntitySystem, int. Abstract Component types
+     * do not have this restriction.</li>
+     * <li>Any non-static fields defined in a Component (abstract or concrete)
+     * must implement Property and be declared private or protected.</li>
+     * </ul>
+     * Additionally, abstract Component types cannot have a TypedId assigned to
+     * them.
+     * </p>
+     * 
+     * @param <T> The Component class type
+     * @param type The Class whose TypedId is fetched, which must be a subclass
+     *            of Component
+     * @return A unique TypedId associated with the given type
+     * @throws NullPointerException if type is null
+     * @throws IllegalArgumentException if type is not actually a subclass of
+     *             Component, or if it is abstract
+     * @throws IllegalComponentDefinitionException if the type does not follow
+     *             the definition rules described above
+     * @throws SecurityException if the reflection needed to create and analyze
+     *             the Component fails
+     */
+    @SuppressWarnings("unchecked")
+    public static <T extends Component> TypedId<T> getTypedId(Class<T> type) {
+        if (type == null)
+            throw new NullPointerException("Type cannot be null");
+        
+        // Do a look up first without locking to avoid the synchronized lock and expensive
+        // error checking.  If we found one, we know it passed validation the first time, otherwise
+        // we'll validate it before creating a new TypedId.
+        TypedId<T> id = (TypedId<T>) typeMap.get(type);
+        if (id != null)
+            return id; // Found an existing id
+        
+        // Create a ComponentBuilder for the type - theoretically we could double-up
+        // on ComponentBuilder creation if the same type is requested, but that has no
+        // adverse consequences. The first builder will get stored for later
+        ComponentBuilder<T> builder = new ComponentBuilder<T>(type);
+        
+        synchronized(typeMap) {
+            // Must create a new id, we lock completely to prevent concurrent getTypedId() on the
+            // same type using two different ids.  One would get overridden and its returned TypedId
+            // would be invalid.
+            // - Double check, though, before creating a new id
+            id = (TypedId<T>) typeMap.get(type);
+            if (id != null)
+                return id; // Someone else put in the type after we checked but before we locked
+            
+            id = new TypedId<T>(type, idSeq++);
+            typeMap.put(type, id);
+            builderMap.put(id, builder);
+            return id;
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    static <T extends Component> ComponentBuilder<T> getBuilder(TypedId<T> id) {
+        // If they have the TypedId instance, then we already have created the builder
+        return (ComponentBuilder<T>) builderMap.get(id);
+    }
+}

File src/main/java/com/entreri/ComponentBuilder.java

View file
+package com.entreri;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.entreri.property.Factory;
+import com.entreri.property.Parameter;
+import com.entreri.property.Parameters;
+import com.entreri.property.Property;
+import com.entreri.property.PropertyFactory;
+
+/**
+ * <p>
+ * ComponentBuilder is a factory for creating new instances of Components of a
+ * specific type. It is capable of using reflection to instantiate Property
+ * instances for the Components declared property fields, based on the
+ * {@link Factory}, {@link Parameters}, and {@link Parameter} annotations.
+ * </p>
+ * <p>
+ * ComponentBuilder is thread safe, unlike the majority of the library, because
+ * a single ComponentBuilder is used for each type, across all systems.
+ * </p>
+ * 
+ * @author Michael Ludwig
+ * @param <T> The built component type
+ */
+final class ComponentBuilder<T extends Component> {
+    private final List<PropertyFactory<?>> propertyFactories;
+    private final Constructor<T> constructor;
+    private final List<Field> fields;
+
+    /**
+     * Create a new ComponentBuilder for the given type of Component. This is a
+     * slower constructor with lots of reflection so builders should be cached.
+     * This constructor also validates the component definition.
+     * 
+     * @param type The component type created by this builder
+     * @throws IllegalArgumentException if the class is not really a component
+     *             or if it is abstract
+     * @throws IllegalComponentDefinitionException if the class hierarchy of the
+     *             component type is invalid by breaking any of the constructor
+     *             or field rules for defining a component
+     */
+    @SuppressWarnings("unchecked")
+    public ComponentBuilder(Class<T> type) {
+        // Now we actually have to build up a new TypedId - which is sort of slow
+        if (!Component.class.isAssignableFrom(type))
+            throw new IllegalArgumentException("Type must be a subclass of Component: " + type);
+        
+        // Make sure we don't create TypedIds for abstract Component types 
+        // (we don't want to try to allocate these)
+        if (Modifier.isAbstract(type.getModifiers()))
+            throw new IllegalArgumentException("Component class type cannot be abstract: " + type);
+        
+        // Accumulate all property fields and validate type hierarchy
+        fields = new ArrayList<Field>(getFields(type));
+        
+        Class<? super T> parent = type.getSuperclass();
+        while(!Component.class.equals(parent)) {
+            if (!Modifier.isAbstract(parent.getModifiers()))
+                throw new IllegalComponentDefinitionException(type, "Parent class " + parent + " is not abstract");
+            
+            // this cast is safe since we're in the while loop
+            fields.addAll(getFields((Class<? extends Component>) parent));
+            parent = parent.getSuperclass();
+        }
+        
+        constructor = getConstructor(type);
+        propertyFactories = getPropertyFactories(fields);
+    }
+
+    /**
+     * Create a new list of property instances that can be shared by all
+     * instances of the Component type built by this builder for a single
+     * EntitySystem. The list is ordered such that it can be passed into
+     * {@link #newInstance(EntitySystem, int, List)}.
+     * 
+     * @return A new list of properties used to set the property fields in the
+     *         component
+     */
+    public List<Property> createProperties() {
+        List<Property> props = new ArrayList<Property>(propertyFactories.size());
+        for (int i = 0; i < propertyFactories.size(); i++)
+            props.add(propertyFactories.get(i).create());
+        return props;
+    }
+
+    /**
+     * <p>
+     * Create a new instance of the Component type created by this builder, for
+     * the given system. The component will use the given index initially, but
+     * its {@link Component#init()} method is NOT called. The list of properties
+     * is used to assign values to the declared property fields of the type.
+     * </p>
+     * <p>
+     * It is assumed that the list was previously returned from a call to
+     * {@link #createProperties()}.
+     * </p>
+     * 
+     * @param system The owning EntitySystem
+     * @param index The index of the new component in the system
+     * @param properties The list of properties used to assign field values for
+     *            the new component
+     * @return A new component of type T
+     * @throws RuntimeException if the properties weren't compatible with the
+     *             list returned by createProperties()
+     */
+    public T newInstance(EntitySystem system, int index, List<Property> properties) {
+        try {
+            T t = constructor.newInstance(system, index);
+            for (int i = 0; i < fields.size(); i++) {
+                fields.get(i).set(t, properties.get(i));
+            }
+            
+            return t;
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to create new Component instance", e);
+        }
+    }
+    
+    private static <T extends Component> List<PropertyFactory<?>> getPropertyFactories(List<Field> fields) {
+        List<PropertyFactory<?>> factories = new ArrayList<PropertyFactory<?>>();
+        for (int i = 0; i < fields.size(); i++) {
+            factories.add(createFactory(fields.get(i)));
+        }
+        return factories;
+    }
+    
+    private static Object parseValue(Class<?> paramType, String paramValue, Class<? extends Component> forCType) {
+        try {
+            if (String.class.equals(paramType)) {
+                return paramValue;
+            } else if (Class.class.equals(paramType)) {
+                return Class.forName(paramValue);
+            } else if (int.class.equals(paramType) || Integer.class.equals(paramType)) {
+                return Integer.parseInt(paramValue);
+            } else if (float.class.equals(paramType) || Float.class.equals(paramType)) {
+                return Float.parseFloat(paramValue);
+            } else if (double.class.equals(paramType) || Double.class.equals(paramType)) {
+                return Double.parseDouble(paramValue);
+            } else if (long.class.equals(paramType) || Long.class.equals(paramType)) {
+                return Long.parseLong(paramValue);
+            } else if (short.class.equals(paramType) || Short.class.equals(paramType)) {
+                return Short.parseShort(paramValue);
+            } else if (byte.class.equals(paramType) || Byte.class.equals(paramType)) {
+                return Byte.parseByte(paramValue);
+            } else if (char.class.equals(paramType) || Character.class.equals(paramType)) {
+                return paramValue.charAt(0);
+            }
+        } catch(Exception e) {
+            throw new IllegalComponentDefinitionException(forCType, "Cannot convert parameter value, " + paramValue + ", to type: " + paramType);
+        }
+        
+        throw new IllegalComponentDefinitionException(forCType, "Unsupported parameter value type: " + paramType + ", it must be a String, Class, or (boxed) primitive");
+    }
+    
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private static PropertyFactory<?> createFactory(Field field) {
+        Class<? extends Property> type = (Class<? extends Property>) field.getType();
+        Annotation[] annots = field.getAnnotations();
+        Class<? extends Component> forCType = (Class<? extends Component>) field.getDeclaringClass();
+        
+        Parameter[] params = new Parameter[0];
+        for (int i = 0; i < annots.length; i++) {
+            if (annots[i] instanceof Parameters) {
+                // take params array from the @Parameters annotation
+                params = ((Parameters) annots[i]).value();
+                break;
+            } else if (annots[i] instanceof Parameter) {
+                // take @Parameter annotation as single-arg constructor
+                params = new Parameter[] { (Parameter) annots[i] };
+                break;
+            } else if (annots[i] instanceof Factory) {
+                // use the declared PropertyFactory from the @Factory annotation
+                Factory fa = (Factory) annots[i];
+                
+                // verify that the PropertyFactory actually creates the right type
+                Method create;
+                try {
+                    create = fa.value().getMethod("create");
+                } catch (Exception e) {
+                    // should not happen
+                    throw new RuntimeException("Unable to inspect PropertyFactory create() method", e);
+                }
+                
+                if (!type.isAssignableFrom(create.getReturnType()))
+                    throw new IllegalComponentDefinitionException(forCType, "@Factory for " + fa.value() + " creates incorrect Property type for property type: " + type);
+                
+                PropertyFactory<?> factory;
+                try {
+                    factory = fa.value().newInstance();
+                } catch (Exception e) {
+                    throw new IllegalComponentDefinitionException(forCType, "Cannot create PropertyFactory from @Factory annotation: " + fa.value());
+                }
+                
+                return factory;
+            } // else unknown annotation so ignore it
+        }
+        
+        // At this point we need to be able to instantiate the Property with reflection
+        // so make sure it's not an abstract type (below we'll make sure we have a ctor)
+        if (Modifier.isAbstract(type.getModifiers()))
+            throw new IllegalComponentDefinitionException(forCType, "Property cannot be instantiated because its type is abstract: " + field);
+        
+        Class<?>[] ctorTypes = new Class<?>[params.length];
+        Object[] paramValues = new Object[params.length];
+        for (int i = 0; i < params.length; i++) {
+            ctorTypes[i] = params[i].type();
+            paramValues[i] = parseValue(ctorTypes[i], params[i].value(), forCType);
+        }
+        
+        try {
+            Constructor<?> ctor = type.getDeclaredConstructor(ctorTypes);
+            ctor.setAccessible(true); // just in case
+            return new ReflectionPropertyFactory(ctor, paramValues);
+        } catch(NoSuchMethodException e) {
+            // parameterized constructor does not exist
+            throw new IllegalComponentDefinitionException(forCType, "Property does not have a constructor matching: " + Arrays.toString(ctorTypes) + " for property " + field);
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    private static <T extends Component> Constructor<T> getConstructor(Class<T> type) {
+        // This assumes that type is the concrete type, so it will fail if there
+        // are multiple constructors or it's not private with the correct arguments
+        Constructor<?>[] ctors = type.getDeclaredConstructors();
+        if (ctors.length != 1)
+            throw new IllegalComponentDefinitionException(type, "Component type must only define a single constructor");
+        
+        Constructor<T> ctor = (Constructor<T>) ctors[0];
+        if (!Modifier.isPrivate(ctor.getModifiers()) && !Modifier.isProtected(ctor.getModifiers()))
+            throw new IllegalComponentDefinitionException(type, "Component constructor must be private or protected");
+        
+        Class<?>[] args = ctor.getParameterTypes();
+        if (args.length != 2 || !EntitySystem.class.equals(args[0]) || !int.class.equals(args[1]))
+            throw new IllegalComponentDefinitionException(type, "Component constructor does not have proper signature of (ComponentIndex<T>, int, ...)");
+        
+        // Found it, now make it accessible (which might throw a SecurityException)
+        ctor.setAccessible(true);
+        return ctor;
+    }
+    
+    private static List<Field> getFields(Class<? extends Component> type) {
+        Field[] declared = type.getDeclaredFields();
+        AccessibleObject[] access = new AccessibleObject[declared.length];
+
+        for (int i = 0; i < declared.length; i++) {
+            int modifiers = declared[i].getModifiers();
+            if (Modifier.isStatic(modifiers))
+                continue; // ignore static fields
+            
+            if (!Property.class.isAssignableFrom(declared[i].getType()))
+                throw new IllegalComponentDefinitionException(type, "Component has non-Property field: " + declared[i]);
+            if (!Modifier.isPrivate(modifiers) && !Modifier.isProtected(modifiers))
+                throw new IllegalComponentDefinitionException(type, "Field must be private or protected: " + declared[i]);
+            
+            access[i] = declared[i];
+        }
+        
+        // Make sure all fields are accessible so we can assign them
+        Field.setAccessible(access, true);
+        return Arrays.asList(declared);
+    }
+    
+    private static class ReflectionPropertyFactory<P extends Property> implements PropertyFactory<P> {
+        private final Constructor<P> ctor;
+        private final Object[] values;
+        
+        public ReflectionPropertyFactory(Constructor<P> ctor, Object[] values) {
+            this.ctor = ctor;
+            this.values = values;
+        }
+        
+        @Override
+        public P create() {
+            try {
+                return ctor.newInstance(values);
+            } catch (Exception e) {
+                throw new RuntimeException("Unexpected exception when creating Property, with constructor " + ctor);
+            }
+        }
+    }
+}

File src/main/java/com/entreri/ComponentIndex.java

View file
+package com.entreri;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import com.entreri.property.CompactAwareProperty;
+import com.entreri.property.IndexedDataStore;
+import com.entreri.property.Property;
+import com.entreri.property.PropertyFactory;
+
+/**
+ * ComponentIndex manages storing all the components of a specific type for an
+ * EntitySystem. It also controls the IndexedDataStore's for the type's set of
+ * properties. It is package-private because its details are low-level and
+ * complex.
+ * 
+ * @author Michael Ludwig
+ * @param <T> The type of component stored by the index
+ */
+final class ComponentIndex<T extends Component> {
+    // These three arrays have a special value of 0 or null stored in the 0th
+    // index, which allows us to lookup components or entities when they
+    // normally aren't attached.
+    private int[] entityIndexToComponentIndex;
+    private int[] componentIndexToEntityIndex;
+    private Component[] components;
+    
+    private int componentInsert;
+
+    private final List<PropertyStore> declaredProperties;
+    private final List<PropertyStore> decoratedProperties;
+    
+    private final ComponentBuilder<T> builder;
+    private final List<Property> builderProperties; // Properties from declaredProperties, cached for newInstance()
+    
+    private final EntitySystem system;
+    
+    private final Comparator<Component> entityIndexComparator;
+
+    /**
+     * Create a ComponentIndex for the given system, that will store Components
+     * of the given type.
+     * 
+     * @param system The owning system
+     * @param type The type of component
+     * @throws NullPointerException if system or type are null
+     */
+    public ComponentIndex(EntitySystem system, TypedId<T> type) {
+        if (system == null || type == null)
+            throw new NullPointerException("Arguments cannot be null");
+        
+        this.system = system;
+        
+        builder = Component.getBuilder(type);
+        
+        builderProperties = builder.createProperties();
+        
+        declaredProperties = new ArrayList<PropertyStore>();
+        decoratedProperties = new ArrayList<PropertyStore>(); // empty for now
+        for (Property p: builderProperties)
+            declaredProperties.add(new PropertyStore(p));
+        
+        entityIndexToComponentIndex = new int[1]; // holds default 0 value in 0th index
+        componentIndexToEntityIndex = new int[1]; // holds default 0 value in 0th index
+        components = new Component[1]; // holds default null value in 0th index
+        
+        componentInsert = 1;
+        
+        entityIndexComparator = new Comparator<Component>() {
+            @Override
+            public int compare(Component o1, Component o2) {
+                if (o1 != null && o2 != null)
+                    return componentIndexToEntityIndex[o1.index] - componentIndexToEntityIndex[o2.index];
+                else if (o1 != null)
+                    return -1; // push null o2 to end of array
+                else if (o2 != null)
+                    return 1; // push null o1 to end of array
+                else
+                    return 0; // both null so they are "equal"
+            }
+        };
+        
+        // Make sure properties' stores hold enough space
+        resizePropertyStores(declaredProperties, 1);
+    }
+
+    /**
+     * @return An estimate on the number of components in the index, cannot be
+     *         less than the true size
+     */
+    public int getSizeEstimate() {
+        return componentInsert + 1;
+    }
+    
+    /**
+     * @return The owning EntitySystem
+     */
+    public EntitySystem getEntitySystem() {
+        return system;
+    }
+
+    /**
+     * Given the index of a Component (e.g. {@link Component#getIndex()}, return
+     * the index of an entity within the owning system. The returned entity
+     * index can be safely passed to {@link EntitySystem#getEntityByIndex(int)}.
+     * 
+     * @param componentIndex The component index whose owning entity is fetched
+     * @return The index of the entity that has the given component index, or 0
+     *         if the component is not attached
+     */
+    public int getEntityIndex(int componentIndex) {
+        return componentIndexToEntityIndex[componentIndex];
+    }
+
+    /**
+     * Given the index of an entity (e.g. {@link Entity#index}), return the
+     * index of the attached component of this ComponentIndex's type. The
+     * returned component index can be used in {@link #getComponent(int)} and
+     * related methods.
+     * 
+     * @param entityIndex The entity index to look up
+     * @return The index of the attached component, or 0 if the entity does not
+     *         have a component of this type attached
+     */
+    public int getComponentIndex(int entityIndex) {
+        return entityIndexToComponentIndex[entityIndex];
+    }
+
+    /**
+     * Ensure that this ComponentIndex has enough internal space to hold its
+     * entity to component mapping for the given number of entities.
+     * 
+     * @param numEntities The new number of entities
+     */
+    public void expandEntityIndex(int numEntities) {
+        if (entityIndexToComponentIndex.length < numEntities) {
+            entityIndexToComponentIndex = Arrays.copyOf(entityIndexToComponentIndex, (int) (numEntities * 1.5f) + 1);
+        }
+    }
+    
+    /*
+     * As expandEntityIndex() but expands all related component data and arrays
+     * to hold the number of components. This doesn't need to be public so its hidden.
+     */
+    private void expandComponentIndex(int numComponents) {
+        if (numComponents < components.length)
+            return;
+
+        int size = (int) (numComponents * 1.5f) + 1;
+        
+        // Expand the indexed data stores for the properties
+        resizePropertyStores(declaredProperties, size);
+        resizePropertyStores(decoratedProperties, size);
+        
+        // Expand the canonical component array
+        components = Arrays.copyOf(components, size);
+        
+        // Expand the component index
+        componentIndexToEntityIndex = Arrays.copyOf(componentIndexToEntityIndex, size);
+    }
+
+    /*
+     * Convenience to create a new data store for each property with the given
+     * size, copy the old data over, and assign it back to the property.
+     */
+    private void resizePropertyStores(List<PropertyStore> properties, int size) {
+        int ct = properties.size();
+        for (int i = 0; i < ct; i++) {
+            IndexedDataStore oldStore = properties.get(i).property.getDataStore();
+            IndexedDataStore newStore = oldStore.create(size);
+            oldStore.copy(0, Math.min(oldStore.size(), size), newStore, 0);
+            properties.get(i).property.setDataStore(newStore);
+        }
+    }
+    
+    /**
+     * @param entityIndex The entity index whose component is fetched
+     * @return The canonical component instance attached to the given entity
+     *         index, or null if no component is attached yet
+     */
+    @SuppressWarnings("unchecked")
+    public T getComponent(int entityIndex) {
+        return (T) components[entityIndexToComponentIndex[entityIndex]];
+    }
+
+    /**
+     * Create a new component of this index's type and attach to it the entity
+     * at the given entity index. If <tt>fromTemplate</tt> is non-null, the
+     * property values from the template should be copied to the values of new
+     * component.
+     * 
+     * @param entityIndex The entity index which the component is attached to
+     * @param fromTemplate A template to assign values to the new component, may
+     *            be null
+     * @return A new component of type T
+     */
+    @SuppressWarnings("unchecked")
+    public T addComponent(int entityIndex, T fromTemplate) {
+        int componentIndex = entityIndexToComponentIndex[entityIndex];
+        if (componentIndex == 0) {
+            // no existing component, so we make one, possibly expanding the backing array
+            componentIndex = componentInsert++;
+            if (componentIndex >= components.length)
+                expandComponentIndex(componentIndex + 1);
+            
+            T instance = newInstance(componentIndex);
+            components[componentIndex] = instance;
+            componentIndexToEntityIndex[componentIndex] = entityIndex;
+            entityIndexToComponentIndex[entityIndex] = componentIndex;
+            
+            instance.init();
+        }
+        
+        if (fromTemplate != null) {
+            // Copy values from fromTemplate's properties to the new instances
+            List<PropertyStore> templateProps = fromTemplate.owner.declaredProperties;
+            for (int i = 0; i < templateProps.size(); i++) {
+                templateProps.get(i).property.getDataStore().copy(fromTemplate.index, 1,
+                                                                  declaredProperties.get(i).property.getDataStore(), 
+                                                                  componentIndex);
+            }
+        }
+        
+        // Copy default value for decorated properties
+        for (int i = 0; i < decoratedProperties.size(); i++) {
+            PropertyStore p = decoratedProperties.get(i);
+            p.defaultData.copy(0, 1, p.property.getDataStore(), componentIndex);
+        }
+        
+        return (T) components[componentIndex];
+    }
+
+    /**
+     * Detach or remove any component of this index's type from the entity with
+     * the given index. True is returned if a component was removed, or false
+     * otherwise.
+     * 
+     * @param entityIndex The entity's index whose component is removed
+     * @return True if a component was removed
+     */
+    public boolean removeComponent(int entityIndex) {
+        int componentIndex = entityIndexToComponentIndex[entityIndex];
+
+        // This code works even if componentIndex is 0
+        Component oldComponent = components[componentIndex];
+
+        components[componentIndex] = null;
+        entityIndexToComponentIndex[entityIndex] = 0; // entity does not have component
+        componentIndexToEntityIndex[componentIndex] = 0; // component does not have entity
+        
+        // Make all removed component instances point to the 0th index
+        if (oldComponent != null)
+            oldComponent.index = 0;
+        
+        return oldComponent != null;
+    }
+
+    /*
+     * Update all component data in the list of properties. If possible the data
+     * store in swap is reused.
+     */
+    private void update(List<PropertyStore> properties, Component[] newToOldMap) {
+        for (int i = 0; i < properties.size(); i++) {
+            PropertyStore p = properties.get(i);
+            IndexedDataStore origStore = p.property.getDataStore();
+            
+            p.property.setDataStore(update(origStore, p.swap, newToOldMap));
+            p.swap = origStore;
+        }
+    }
+
+    /*
+     * Update all component data in src to be in dst by shuffling it to match
+     * newToOldMap.
+     */
+    private IndexedDataStore update(IndexedDataStore src, IndexedDataStore dst, 
+                                    Component[] newToOldMap) {
+        int dstSize = newToOldMap.length;
+        
+        if (dst == null || dst.size() < dstSize)
+            dst = src.create(dstSize);
+        
+        int i;
+        int lastIndex = -1;
+        int copyIndexNew = -1;
+        int copyIndexOld = -1;
+        for (i = 1; i < componentInsert; i++) {
+            if (newToOldMap[i] == null) {
+                // we've hit the end of existing components, so break
+                break;
+            }
+            
+            if (newToOldMap[i].getIndex() != lastIndex + 1) {
+                // we are not in a contiguous section
+                if (copyIndexOld >= 0) {
+                    // we have to copy over the last section
+                    src.copy(copyIndexOld, (i - copyIndexNew), dst, copyIndexNew);
+                }
+                
+                // set the copy indices
+                copyIndexNew = i;
+                copyIndexOld = newToOldMap[i].getIndex();
+            }
+            lastIndex = newToOldMap[i].getIndex();
+        }
+        
+        if (copyIndexOld >= 0) {
+            // final copy
+            src.copy(copyIndexOld, (i - copyIndexNew), dst, copyIndexNew);
+        }
+
+        return dst;
+    }
+    
+    private void notifyCompactAwareProperties(List<PropertyStore> props) {
+        PropertyStore p;
+        for (int i = 0; i < props.size(); i++) {
+            p = props.get(i);
+            if (p.property instanceof CompactAwareProperty)
+                ((CompactAwareProperty) p.property).onCompactComplete();
+        }
+    }
+
+    /**
+     * <p>
+     * Compact the data of this ComponentIndex to account for removals and
+     * additions to the index. This will ensure that all active components are
+     * packed into the underlying arrays, and that they will be accessed in the
+     * same order as iterating over the entities directly.
+     * </p>
+     * <p>
+     * The map from old to new entity index must be used to properly update the
+     * component index's data so that the system is kept in sync.
+     * </p>
+     * 
+     * @param entityOldToNewMap A map from old entity index to new index
+     * @param numEntities The number of entities that are in the system
+     */
+    public void compact(int[] entityOldToNewMap, int numEntities) {
+        // First sort the canonical components array
+        Arrays.sort(components, 1, componentInsert, entityIndexComparator);
+        
+        // Update all of the property stores to match up with the components new positions
+        update(declaredProperties, components);
+        update(decoratedProperties, components);
+        
+        // Repair the componentToEntityIndex and the component.index values
+        componentInsert = 1;
+        int[] newComponentIndex = new int[components.length];
+        for (int i = 1; i < components.length; i++) {
+            if (components[i] != null) {
+                newComponentIndex[i] = entityOldToNewMap[componentIndexToEntityIndex[components[i].index]];
+                components[i].index = i;
+                componentInsert = i + 1;
+            }
+        }
+        componentIndexToEntityIndex = newComponentIndex;
+        
+        // Possibly compact the component data
+        if (componentInsert < .6f * components.length) {
+            int newSize = (int) (1.2f * componentInsert) + 1;
+            components = Arrays.copyOf(components, newSize);
+            componentIndexToEntityIndex = Arrays.copyOf(componentIndexToEntityIndex, newSize);
+            resizePropertyStores(declaredProperties, newSize);
+            resizePropertyStores(decoratedProperties, newSize);
+        }
+        
+        // Repair entityIndexToComponentIndex - and possible shrink the index
+        // based on the number of packed entities
+        if (numEntities < .6f * entityIndexToComponentIndex.length)
+            entityIndexToComponentIndex = new int[(int) (1.2f * numEntities) + 1];
+        else
+            Arrays.fill(entityIndexToComponentIndex, 0);
+        
+        for (int i = 1; i < componentInsert; i++)
+            entityIndexToComponentIndex[componentIndexToEntityIndex[i]] = i;
+        
+        notifyCompactAwareProperties(declaredProperties);
+        notifyCompactAwareProperties(decoratedProperties);
+    }
+
+    /**
+     * @return An iterator over the canonical components in the index. The
+     *         iterator's remove() detaches the component from the entity
+     */
+    public Iterator<T> iterator() {
+        return new ComponentIterator();
+    }
+
+    /**
+     * @return An iterator over the components of the index, but a single
+     *         component instance is reused. remove() detaches the current
+     *         component from the entity
+     */
+    public Iterator<T> fastIterator() {
+        return new FastComponentIterator();
+    }
+
+    /**
+     * Create a new instance of T that will take its data from the given index.
+     * The init() method of the Component is not called.
+     * 
+     * @param index The component index to wrap
+     * @return The new instance wrapping the data at the given index
+     */
+    public T newInstance(int index) {
+        return builder.newInstance(system, index, builderProperties);
+    }
+
+    /**
+     * Decorate the type information of this ComponentIndex to add a property
+     * created by the given factory. The returned property will have default
+     * data assigned for each current Component in the index, and will have the
+     * default value assigned for each new Component. Decorators can then access
+     * the returned property to manipulate the decorated component data.
+     * 
+     * @param <P> The type of property created
+     * @param factory The factory that will create a unique Property instance
+     *            associated with the decorated property and this index
+     * @return The property decorated onto the type of the index
+     */
+    public <P extends Property> P decorate(PropertyFactory<P> factory) {
+        P prop = factory.create();
+        
+        int size = (declaredProperties.isEmpty() ? componentInsert + 1 
+                                                 : declaredProperties.get(0).property.getDataStore().size());
+        
+        // Copy original values from factory property over to all component slots
+        IndexedDataStore oldStore = prop.getDataStore();
+        IndexedDataStore newStore = oldStore.create(size);
+        for (int i = 1; i < size; i++) {
+            // This assumes that the property stores its data in the 0th index
+            oldStore.copy(0, 1, newStore, i);
+        }
+        prop.setDataStore(newStore);
+        
+        PropertyStore pstore = new PropertyStore(prop);
+        pstore.defaultData = oldStore;
+        
+        decoratedProperties.add(pstore);
+        return prop;
+    }
+
+    /**
+     * Remove the given property from the set of decorated properties on this
+     * index's type. If the property is invalid or not a decorated property for
+     * the index, this does nothing.
+     * 
+     * @param p The property to remove
+     */
+    public void undecorate(Property p) {
+        Iterator<PropertyStore> it = decoratedProperties.iterator();
+        while(it.hasNext()) {
+            if (it.next().property == p) {
+                it.remove();
+                break;
+            }
+        }
+    }
+    
+    /*
+     * An iterator implementation over the canonical components of the index.
+     */
+    private class ComponentIterator implements Iterator<T> {
+        private int index;
+        private boolean advanced;
+        
+        public ComponentIterator() {
+            index = 0;
+            advanced = false;
+        }
+        
+        @Override
+        public boolean hasNext() {
+            if (!advanced)
+                advance();
+            return index < componentInsert;
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public T next() {
+            if (!hasNext())
+                throw new NoSuchElementException();
+            advanced = false;
+            return (T) components[index];
+        }
+
+        @Override
+        public void remove() {
+            if (advanced || index == 0)
+                throw new IllegalStateException("Must call next() before remove()");
+            if (components[index] == null)
+                throw new IllegalStateException("Component already removed");
+            removeComponent(componentIndexToEntityIndex[index]);
+        }
+        
+        private void advance() {
+            index++; // always advance at least 1
+            while(index < components.length && components[index] == null) {
+                index++;
+            }
+            advanced = true;
+        }
+    }
+
+    /*
+     * An iterator over the components of the system that reuses a single
+     * instance for performance.
+     */
+    private class FastComponentIterator implements Iterator<T> {
+        private final T instance;
+        
+        private int index;
+        private boolean advanced;
+        
+        public FastComponentIterator() {
+            instance = newInstance(0);
+            index = 0;
+            advanced = false;
+        }
+        
+        @Override
+        public boolean hasNext() {
+            if (!advanced)
+                advance();
+            return index < componentInsert;
+        }
+
+        @Override
+        public T next() {
+            if (!hasNext())
+                throw new NoSuchElementException();
+            advanced = false;
+            instance.index = index;
+            return instance;
+        }
+
+        @Override
+        public void remove() {
+            if (advanced || index == 0)
+                throw new IllegalStateException("Must call next() before remove()");
+            
+            int entityIndex = componentIndexToEntityIndex[index];
+            if (entityIndex == 0)
+                throw new IllegalStateException("Component already removed");
+            
+            removeComponent(entityIndex);
+        }
+        
+        private void advance() {
+            // Check componentIndexToEntityIndex so we don't pull in an instance 
+            // and we can just iterate along the int[] array. A 0 value implies that
+            // the component does not have an attached entity, and has been removed
+            
+            index++; // always advance
+            while(index < componentIndexToEntityIndex.length && 
+                  componentIndexToEntityIndex[index] == 0) {
+                index++;
+            }
+            advanced = true;
+        }
+    }
+    
+    private static class PropertyStore {
+        final Property property;
+        IndexedDataStore swap; // may be null
+        IndexedDataStore defaultData; // if not null, has a single component
+        
+        public PropertyStore(Property p) {
+            property = p;
+        }
+    }
+}

File src/main/java/com/entreri/Controller.java

View file
+package com.entreri;
+
+/**
+ * Controllers are functional processors of the entities and components within
+ * an EntitySystem. Different Controller implementations have different
+ * purposes, such as updating transforms, computing physics or AI, or rendering
+ * a scene. Generally controller implementations should be as small and as
+ * independent as possible to allow for reuse and easy composability.
+ * 
+ * @author Michael Ludwig
+ */
+public interface Controller {
+    /**
+     * Invoke operations on the given EntitySystem that must occur before the
+     * main processing of the frame. All controllers in a system will have their
+     * preProcess() method called before the first process() method is called.
+     * 
+     * @param system The entity system to process
+     * @param dt The elapsed time since the last processing
+     */
+    public void preProcess(EntitySystem system, float dt);
+
+    /**
+     * Invoke controller specific operations to process the EntitySystem. All
+     * controllers will have already had their preProcess() method invoked and
+     * none will have postProcess() invoked until process() has completed for
+     * all controllers in the system.
+     * 
+     * @param system The entity system to process
+     * @param dt The elapsed time since the last processing
+     */
+    public void process(EntitySystem system, float dt);
+
+    /**
+     * Invoked at the end of a processing phase after all controllers in a
+     * system have completed their process() methods.
+     * 
+     * @param system The entity system to process
+     * @param dt The elapsed time since the last processing
+     */
+    public void postProcess(EntitySystem system, float dt);
+}

File src/main/java/com/entreri/ControllerManager.java

View file
+package com.entreri;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * <p>
+ * ControllerManager is a utility that manages the list of Controllers that can
+ * process an EntitySystem. It also exposes two different ways of storing
+ * controller data: the first is by annotation key for sharing between
+ * controller types, the second is by a private object key for storing data
+ * dependent on a system but internal to a controller implementation.
+ * </p>
+ * <p>
+ * Additionally, the ControllerManager is used to invoke the phase processing
+ * methods on each Controller on its configured EntitySystem.
+ * </p>
+ * 
+ * @author Michael Ludwig
+ */
+public class ControllerManager {
+    /**
+     * The Phase enum represents the different phases of
+     * processing that an EntitySystem can go through during
+     * what is often considered a "frame".
+     */
+    public static enum Phase {
+        /**
+         * The PREPROCESS phase is invoked before all other phases. All
+         * controllers in a manager will have their
+         * {@link Controller#preProcess(EntitySystem, float)} method called
+         * before moving to the next phase.
+         */
+        PREPROCESS,
+
+        /**
+         * The PROCESS phase is invoked between PREPROCESS and POSTPROCESS. All
+         * controllers in the manager will have their
+         * {@link Controller#process(EntitySystem, float)} method called before
+         * moving to the next phase.
+         */
+        PROCESS,
+
+        /**
+         * The POSTPROCESS phase is invoked after PREPROCESS and POSTPROCESS.
+         * All controllers in their manager will have their
+         * {@link Controller#postProcess(EntitySystem, float)} method called
+         * before the frame is completed.
+         */
+        POSTPROCESS,
+
+        /**
+         * ALL is a special PHASE that represents all three true phases invoked
+         * in their proper order.
+         */
+        ALL
+    }
+
+    private float fixedDelta;
+    private final List<Controller> controllers;
+    
+    // This is a concurrent map so that parallel controllers can access it efficiently
+    // - the rest of the class is assumed to be single-threaded
+    private final ConcurrentHashMap<Class<? extends Annotation>, Object> controllerData;
+    private final ConcurrentHashMap<Object, Object> privateData;
+
+    private final EntitySystem system;
+
+    /**
+     * Create a new ControllerManager that will store controllers and controller
+     * data for processing the given EntitySystem.
+     * 
+     * @param system The EntitySystem that is managed by this controller manager
+     * @throws NullPointerException if system is null
+     */
+    public ControllerManager(EntitySystem system) {
+        if (system == null)
+            throw new NullPointerException("EntitySystem cannot be null");
+        
+        this.system = system;
+        controllerData = new ConcurrentHashMap<Class<? extends Annotation>, Object>();
+        privateData = new ConcurrentHashMap<Object, Object>();
+        controllers = new ArrayList<Controller>();
+        
+        fixedDelta = 1 / 60f; // 60fps
+    }
+
+    /**
+     * Set a new fixed time frame delta. This can take any value and represents
+     * the number of seconds between each frame, so negative values may produce
+     * undefined results with some controllers. This value is only used if
+     * {@link #process()} is invoked, the other varieties take a delta value
+     * that overrides this one.
+     * 
+     * @param dt The new time frame delta
+     */
+    public void setFixedDelta(float dt) {
+        fixedDelta = dt;
+    }
+
+    /**
+     * @return The current fixed time frame delta that is used if
+     *         {@link #process()} is invoked
+     */
+    public float getFixedDelta() {
+        return fixedDelta;
+    }
+    
+    /**
+     * Return the controller data that has been mapped to the given annotation
+     * <tt>key</tt>. This will return if there has been no assigned data. This
+     * can be used to store arbitrary data that must be shared between related
+     * controllers.
+     * 
+     * @param key The annotation key
+     * @return The object previously mapped to the annotation with
+     *         {@link #setControllerData(Class, Object)}
+     * @throws NullPointerException if key is null
+     */
+    public Object getControllerData(Class<? extends Annotation> key) {
+        if (key == null)
+            throw new NullPointerException("Key cannot be null");
+        return controllerData.get(key);
+    }
+
+    /**
+     * Map <tt>value</tt> to the given annotation <tt>key</tt> so that future
+     * calls to {@link #getControllerData(Class)} with the same key will return
+     * the new value. If the value is null, any previous mapping is removed.
+     * 
+     * @param key The annotation key
+     * @param value The new value to store
+     * @throws NullPointerException if key is null
+     */
+    public void setControllerData(Class<? extends Annotation> key, Object value) {
+        if (key == null)
+            throw new NullPointerException("Key cannot be null");
+        if (value == null)
+            controllerData.remove(key);
+        else
+            controllerData.put(key, value);
+    }
+
+    /**
+     * Retrieve a privately stored instance from this manager's cache. It is
+     * similar to {@link #getControllerData(Class)} except the key is intended
+     * to be something known only to the owner so the data is effectively hidden
+     * from other controllers (unlike the annotation key which facilitates
+     * cross-controller communication).
+     * 
+     * @param key The key to lookup
+     * @return The value associated with the key, or null
+     * @throws NullPointerException if key is null
+     */
+    public Object getPrivateData(Object key) {
+        if (key == null)
+            throw new NullPointerException("Key cannot be null");
+        return privateData.get(key);
+    }
+
+    /**
+     * Store the given value into this manager's cache so that it can be
+     * retrieved by the given key. It is intended that the key is not accessible
+     * to other controllers so that this represents private data. If sharing is
+     * desired, create an annotation description and use
+     * {@link #setControllerData(Class, Object)}.
+     * 
+     * @param key The key to store the value to
+     * @param value The value being stored, or null to remove the old value
+     * @throws NullPointerException if key is null
+     */
+    public void setPrivateData(Object key, Object value) {
+        if (key == null)
+            throw new NullPointerException("Key cannot be null");
+        if (value == null)
+            privateData.remove(key);
+        else
+            privateData.put(key, value);
+    }
+
+    /**
+     * <p>
+     * Add a Controller to this manager so that subsequent calls to
+     * {@link #process()} and its varieties will invoke the process hooks on the
+     * controller. The new controller is invoked after all already added
+     * controllers.
+     * </p>
+     * <p>
+     * If this controller was already added to the manager, it will be moved to
+     * the end of the list.
+     * </p>
+     * 
+     * @param controller The controller to add
+     * @throws NullPointerException if controller is null
+     */
+    public void addController(Controller controller) {
+        if (controller == null)
+            throw new NullPointerException("Controller cannot be null");
+        
+        // remove it first - which does nothing if not in the list
+        controllers.remove(controller);
+        // now add it to the end
+        controllers.add(controller);
+    }
+
+    /**
+     * Remove a controller from the manager so that it is no longer invoked when
+     * {@link #process()} and its related functions are called. If the
+     * controller has not been added to the manager, this does nothing.
+     * 
+     * @param controller The controller to remove
+     * @throws NullPointerException if controller is null
+     */
+    public void removeController(Controller controller) {
+        if (controller == null)
+            throw new NullPointerException("Controller cannot be null");
+        controllers.remove(controller);
+    }
+    
+    /**
+     * Run all phases of the manager using the current fixed frame time delta.
+     */
+    public void process() {
+        process(fixedDelta);
+    }
+
+    /**
+     * Run all phases of the manager using the specified frame time delta,
+     * <tt>dt</tt>.
+     * 
+     * @param dt The time delta for the frame, or the amount of time since the
+     *            start of the last frame and this one
+     */
+    public void process(float dt) {
+        process(Phase.ALL, dt);
+    }
+
+    /**
+     * Run the processing of a particular phase for this manager, using the
+     * specified time delta. If the phase is {@link Phase#ALL}, all phases will
+     * be run in their proper order.
+     * 
+     * @param phase The specific phase to run, or ALL to specify all phases
+     * @param dt The time delta for the frame, or the amount of time since the
+     *            start of the last frame and this one
+     * @throws NullPointerException if phase is null
+     */
+    public void process(Phase phase, float dt) {
+        if (phase == null)
+            throw new NullPointerException("Phase cannot be null");
+        
+        switch(phase) {
+        case PREPROCESS:
+            doPreProcess(dt); break;
+        case PROCESS:
+            doProcess(dt); break;
+        case POSTPROCESS:
+            doPostProcess(dt); break;
+        case ALL:
+            // Perform all stages in one go
+            doPreProcess(dt);
+            doProcess(dt);
+            doPostProcess(dt);
+            break;
+        }
+    }
+    
+    private void doPreProcess(float dt) {
+        for (int i = 0; i < controllers.size(); i++)
+            controllers.get(i).preProcess(system, dt);
+    }
+    
+    private void doProcess(float dt) {
+        for (int i = 0; i < controllers.size(); i++)
+            controllers.get(i).process(system, dt);
+    }
+    
+    private void doPostProcess(float dt) {
+        for (int i = 0; i < controllers.size(); i++)
+            controllers.get(i).postProcess(system, dt);
+    }
+}

File src/main/java/com/entreri/Entity.java

View file
+package com.entreri;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * <p>
+ * An Entity represents a collection of Components within an EntitySystem.
+ * Entities are created by calling {@link EntitySystem#addEntity()} or the
+ * similar function that takes another Entity as a template.
+ * </p>
+ * <p>
+ * Like {@link Component}, a given instance of Entity might change its true
+ * identity by having its index into the system changed. An Entity's identity is
+ * determined by its id, which can be found with {@link #getId()}.
+ * </p>
+ * 
+ * @author Michael Ludwig
+ */
+public final class Entity implements Iterable<Component> {
+    private final EntitySystem system;
+    int index;
+
+    /**
+     * Create an Entity that will be owned by the given system and is placed at
+     * the given index.
+     * 
+     * @param system The owning system
+     * @param index The index into the system
+     */
+    Entity(EntitySystem system, int index) {
+        if (system == null)
+            throw new NullPointerException("System cannot be null");
+        if (index < 0)
+            throw new IllegalArgumentException("Index must be at least 0, not: " + index);
+        
+        this.system = system;
+        this.index = index;
+    }
+    
+    /**
+     * @return The owning EntitySystem of this entity
+     */
+    public EntitySystem getEntitySystem() {
+        return system;
+    }
+
+    /**
+     * @return The unique id of this Entity, or 0 if the entity has been removed
+     *         from its system
+     */
+    public int getId() {
+        return system.getEntityId(index);
+    }
+
+    /**
+     * @return True if this Entity is still in its EntitySystem, or false if it
+     *         has been removed
+     */
+    public boolean isLive() {
+        return getId() != 0;
+    }
+
+    /**
+     * Get the Component instance of the given type that's attached to this
+     * Entity. A null value is returned if the component type has not been
+     * attached to the entity. The same Component instance is returned for a
+     * given type until that component has been removed, meaning that the
+     * component can be safely stored in collections.
+     * 
+     * @param <T> The parameterized type of Component being fetched
+     * @param id The TypedId representing the given type
+     * @return The current Component of type T attached to this container
+     * @throws NullPointerException if id is null
+     */
+    public <T extends Component> T get(TypedId<T> componentId) {
+        ComponentIndex<T> ci = system.getIndex(componentId);
+        return ci.getComponent(index);
+    }
+
+    /**
+     * Attach a Component of type T to this Entity. If the Entity already has
+     * component of type T attached, that component is returned unmodified.
+     * Otherwise, a new instance is created with its default values and added to
+     * the system. The returned instance will be the canonical component for the
+     * given type (until its removed) and can be safely stored in collections.
+     * 
+     * @param <T> The parameterized type of component being added
+     * @param componentId The TypedId of the component type
+     * @return A new component of type T, or an existing T if already attached
+     * @throws NullPointerException if componentId is null
+     */
+    public <T extends Component> T add(TypedId<T> componentId) {
+        ComponentIndex<T> ci = system.getIndex(componentId);
+        return ci.addComponent(index, null);
+    }
+
+    /**
+     * Remove any attached Component of the given type, T, from this Entity.
+     * True is returned if a component was removed, and false otherwise. If a
+     * component is removed, the component should no longer be used and it will
+     * return null from {@link Component#getEntity()}.
+     * 
+     * @param <T> The parameterized type of component to remove
+     * @param componentId The TypedId of the component type
+     * @return True if a component was removed
+     * @throws NullPointerException if componentId is null
+     */
+    public <T extends Component> boolean remove(TypedId<T> componentId) {
+        ComponentIndex<T> ci = system.getIndex(componentId);
+        return ci.removeComponent(index);
+    }
+
+    /**
+     * Return an iterator over the components currently attached to the Entity.
+     * The iterator supports the remove operation and will detach the component
+     * from the entity. The returned components are the canonical component
+     * instances for the entity and can be safely held in collections.
+     * 
+     * @return An iterator over the entity's components
+     */
+    @Override
+    public Iterator<Component> iterator() {
+        return new ComponentIterator(system, index);
+    }
+    
+    @Override
+    public int hashCode() {
+        return getId();
+    }
+    
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Entity))
+            return false;
+        Entity e = (Entity) o;
+        return e.system == system && e.getId() == getId();
+    }
+    
+    /*
+     * Iterator implementation that iterates over the components
+     * attached to an entity, based on entity index rather than reference
+     */
+    private static class ComponentIterator implements Iterator<Component> {
+        private final int entityIndex;
+        private final Iterator<ComponentIndex<?>> indices;
+        
+        private ComponentIndex<?> currentIndex;
+        private ComponentIndex<?> nextIndex;
+        
+        public ComponentIterator(EntitySystem system, int entityIndex) {
+            this.entityIndex = entityIndex;
+            indices = system.iterateComponentIndices();
+        }
+        
+        @Override
+        public boolean hasNext() {
+            if (nextIndex == null)
+                advance();
+            return nextIndex != null;
+        }
+
+        @Override
+        public Component next() {
+            if (!hasNext())
+                throw new NoSuchElementException();
+            
+            currentIndex = nextIndex;
+            nextIndex = null;
+            return currentIndex.getComponent(entityIndex);
+        }
+
+        @Override
+        public void remove() {
+            if (currentIndex == null)
+                throw new IllegalStateException("Must call next first");
+            
+            if (currentIndex.removeComponent(entityIndex))
+                currentIndex = null; // so next call to remove() fails
+            else
+                throw new IllegalStateException("Already removed");
+        }
+        
+        private void advance() {
+            while(indices.hasNext()) {
+                nextIndex = indices.next();
+                if (nextIndex.getComponentIndex(entityIndex) != 0)
+                    break;
+                else
+                    nextIndex = null; // must set to null if this was last element
+            }
+        }
+    }
+}

File src/main/java/com/entreri/EntitySystem.java

View file
+package com.entreri;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import com.entreri.property.FloatProperty;
+import com.entreri.property.ObjectProperty;
+import com.entreri.property.Property;
+import com.entreri.property.PropertyFactory;
+
+/**
+ * <p>
+ * EntitySystem is the main container for the entities within a logical system
+ * such as a game or physics world. It contains all entities needed for
+ * processing the scene or data. Entities are created with {@link #addEntity()}
+ * or {@link #addEntity(Entity)}. They can be removed (and effectively
+ * destroyed) with {@link #removeEntity(Entity)} or from the remove() methods of
+ * the various iterators over the system data.
+ * </p>
+ * <p>
+ * After an Entity is created, Components can be added to it to store
+ * domain-specific data and control its behaviors. This depends on the Component
+ * implementations and Controllers used to process your data.
+ * </p>
+ * <p>
+ * The {@link ControllerManager} of an EntitySystem can be used to register
+ * Controllers that will process the entities within an entity system in an
+ * organized fashion. Generally, the processing of all controllers through their
+ * different phases constitutes a complete "frame".
+ * </p>
+ * <p>
+ * When Entities are created by an EntitySystem, the created instance is
+ * assigned an ID which represents its true identity. Certain iterators in the
+ * system may create a single Entity object which slides over the underlying
+ * entity data for performance purposes. With each iteration, its ID changes
+ * even though the reference does not. This is same way that {@link Component
+ * Components} are stored and treated by the EntitySystem.
+ * </p>
+ * 
+ * @author Michael Ludwig
+ */
+public final class EntitySystem {
+    private ComponentIndex<?>[] componentIndices;
+    private int[] entityIds; // binary search provides index
+    
+    private Entity[] entities;
+    
+    private int entityInsert;
+    private int entityIdSeq;
+    
+    private boolean requireReIndex;
+    
+    private final ControllerManager manager;
+
+    /**
+     * Create a new EntitySystem that has no entities added.
+     */
+    public EntitySystem() {
+        manager = new ControllerManager(this);
+        
+        entities = new Entity[1];
+        entityIds = new int[1];
+        
+        componentIndices = new ComponentIndex[0];
+        
+        entityIdSeq = 1; // start at 1, id 0 is reserved for index = 0 
+        entityInsert = 1;
+    }
+
+    /**
+     * Return the ControllerManager for this EntitySystem that can be used to
+     * organize processing of the system using {@link Controller}
+     * implementations.
+     * 
+     * @return The ControllerManager for this system
+     */
+    public ControllerManager getControllerManager() {
+        return manager;
+    }
+    
+    /**
+     * Return an iterator over all of the entities within the system. The
+     * returned iterator's remove() method will remove the entity from the
+     * system. The returned entities are the "canonical" entities and can be
+     * safely used stored outside of the iterator.
+     * 
+     * @return An iterator over the entities of the system
+     */
+    public Iterator<Entity> iterator() {
+        return new EntityIterator();
+    }
+
+    /**
+     * <p>
+     * Return a "fast" iterator over all the entities within the system. To
+     * avoid potential cache misses, a single Entity object is created and
+     * slides over the entity data stored within the system. If entities do not
+     * need to be held onto after iteration, this is faster than
+     * {@link #iterator()}.
+     * </p>
+     * <p>
+     * The returned iterator's remove() method will remove the entity from the
+     * system (where entity is determined by the entity's id and not Entity
+     * instance). The returned iterator will return the same Entity object with
+     * every call to next(), but its index into the system will be updated every
+     * iteration.
+     * </p>
+     * 
+     * @return A fast iterator over the entities of the system
+     */
+    public Iterator<Entity> fastIterator() {
+        return new FastEntityIterator();
+    }
+
+    /**
+     * <p>
+     * Return an iterator over the components of type T that are in this system.
+     * The returned iterator supports the remove() operation, and will remove
+     * the component from its owning entity. The entity attached to the
+     * component can be found with {@link Component#getEntity()}.
+     * </p>
+     * <p>
+     * The iterator returns the canonical Component instance for each component
+     * of the type in the system. This is the same instance that was returned by
+     * {@link Entity#add(TypedId)} and is safe to access and store after
+     * iteration has completed.
+     * </p>
+     * 
+     * @param <T> The component type that is iterated over
+     * @param id The TypedId of the iterated component
+     * @return An iterator over all Components of type T in the system
+     * @throws NullPointerException if id is null
+     */
+    public <T extends Component> Iterator<T> iterator(TypedId<T> id) {
+        return getIndex(id).iterator();
+    }
+
+    /**
+     * As {@link #iterator(TypedId)} but the iterator will reuse a single
+     * instance of Component. Every call to next() will update the Component's
+     * index within the system. Using a fast iterator helps cache performance,
+     * but cannot be used if the component must be stored for later processing.
+     * 
+     * @param <T> The component type that is iterated over
+     * @param id The TypedId of the iterated component
+     * @return A fast iterator over all Components of type T in the system
+     * @throws NullPointerException if id is null
+     */
+    public <T extends Component> Iterator<T> fastIterator(TypedId<T> id) {
+        return getIndex(id).fastIterator();
+    }
+
+    /**
+     * <p>
+     * Return an iterator over all entities within the system that have the
+     * given component types attached to them. Entities returned by the iterator
+     * will have all requested components; if even one desired component is not
+     * present on an entity, it is not included.
+     * </p>
+     * <p>
+     * For performance reasons, the "entity" is exposed as an
+     * IndexedComponentMap. The components can be queried using the index of
+     * their type within <tt>ids</tt>. Alternatively, it can be queried by
+     * TypedId.
+     * </p>
+     * <p>
+     * The returned iterator will always return the same IndexedComponentMap,
+     * although each call to next() will update the Components returned by the
+     * map. The accessed components are canonical components in the same way
+     * that {@link #iterator(TypedId)}'s components are. The remove() operation
+     * is not supported.
+     * </p>
+     * 
+     * @param ids The ordered set of ids that constrain the returned entities
+     * @return An iterator that efficiently exposes all entities and their
+     *         components which satisfy the given type constraint
+     * @throws NullPointerException if ids is null or contains null elements
+     * @throws IllegalArgumentException if ids is empty
+     */
+    public Iterator<IndexedComponentMap> iterator(TypedId<?>... ids) { 
+        return bulkIterator(false, ids);
+    }