Commits

Michael Ludwig committed 928c653

Improve controller data interface, add more event hooks for controllers, improve transient property handling, and improve default value support, as well as bug fixes.

Comments (0)

Files changed (21)

src/main/java/com/googlecode/entreri/AbstractController.java

+/*
+ * Entreri, an entity-component framework in Java
+ *
+ * Copyright (c) 2011, Michael Ludwig
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *     Redistributions of source code must retain the above copyright notice,
+ *         this list of conditions and the following disclaimer.
+ *     Redistributions in binary form must reproduce the above copyright notice,
+ *         this list of conditions and the following disclaimer in the
+ *         documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.googlecode.entreri;
+
+/**
+ * AbstractController implements Controller by performing no action on each of
+ * Controller's process or event hooks. Subclasses can override just the methods
+ * they are interested in implementing.
+ * 
+ * @author Michael Ludwig
+ */
+public abstract class AbstractController implements Controller {
+
+    @Override
+    public void preProcess(EntitySystem system, float dt) {
+        // do nothing in base class
+    }
+
+    @Override
+    public void process(EntitySystem system, float dt) {
+        // do nothing in base class
+    }
+
+    @Override
+    public void postProcess(EntitySystem system, float dt) {
+        // do nothing in base class
+    }
+
+    @Override
+    public void addedToSystem(EntitySystem system) {
+        // do nothing in base class
+    }
+
+    @Override
+    public void removedFromSystem(EntitySystem system) {
+        // do nothing in base class
+    }
+
+    @Override
+    public void onEntityAdd(Entity e) {
+        // do nothing in base class
+    }
+
+    @Override
+    public void onEntityRemove(Entity e) {
+        // do nothing in base class
+    }
+
+    @Override
+    public void onComponentAdd(Component c) {
+        // do nothing in base class
+    }
+
+    @Override
+    public void onComponentRemove(Component c) {
+        // do nothing in base class
+    }
+}

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

 
 import com.googlecode.entreri.property.IndexedDataStore;
 import com.googlecode.entreri.property.Property;
+import com.googlecode.entreri.property.PropertyFactory;
+import com.googlecode.entreri.property.Unmanaged;
 
 /**
  * <p>
      * component in the system.
      */
     int index;
-
+    
     final ComponentIndex<?> owner;
+    
     private final TypedId<? extends Component> typedId;
 
     /**
      * 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).
+     * (because it's just acting as a shell in that case), or being cloned from
+     * a template.
      * </p>
      * <p>
      * The var-args initParams are the initial object parameters, in the same
      * sure to call super with arguments matching its super-type's InitParams
      * annotation.
      * </p>
+     * <p>
+     * Note that in many cases the default value (usually 0 or null), assigned
+     * by the {@link PropertyFactory} that created the component's properties
+     * could be sufficient.
+     * </p>
+     * 
      * @param initParams The initial parameters for the Component
      */
     protected abstract void init(Object... initParams) throws Exception;
     public final int getIndex() {
         return index;
     }
-    
+
+    /**
+     * @return True if this Component is still attached to an Entity, or false
+     *         if it has been removed
+     */
+    public final boolean isLive() {
+        return index != 0;
+    }
+
+    /**
+     * <p>
+     * Component's hashCode() returns the entity id of the component's owning
+     * entity. This means that any Component instance of this type that has the same index in
+     * the same EntitySystem will correctly use the same hash code.
+     * </p>
+     * <p>
+     * This means you can use the components created by a system's fast
+     * iterators to query a hash-based collections. However, you should never
+     * store fast components into a hash-based collection because their index
+     * (and thus hash code) will change each iteration.
+     * </p>
+     * <p>
+     * Additionally, a component's index is updated when it is removed from an
+     * entity or system. This means it is critical to remove components from
+     * collections before they a removed from a system. This can be done in
+     * {@link Controller#onComponentRemove(Component)}.
+     * </p>
+     * 
+     * @throws IllegalStateException if the component has already been removed
+     */
     @Override
     public int hashCode() {
-        return index;
+        if (!isLive())
+            throw new IllegalStateException("Component is not alive anymore");
+        return owner.getEntitySystem().getEntityId(owner.getEntityIndex(index));
     }
-    
+
+    /**
+     * <p>
+     * Component's equals() returns true if the object is another component of
+     * the same type, with the same owning Entity. This means that any component
+     * of this type that has the same index in the same entity system will
+     * correctly be treated as equal.
+     * </p>
+     * <p>
+     * This means you can use the components created by a system's fast
+     * iterators to query equals-based collections. However, you should never
+     * store fast components into a equals-based collections because their index
+     * (and thus definition of equality) will change with each iteration.
+     * </p>
+     * <p>
+     * Additionally, a component's index is updated when it is removed from an
+     * entity or system. This means it is critical to remove components from
+     * collections before they are removed from the system. This can be done in
+     * {@link Controller#onComponentRemove(Component)}.
+     * </p>
+     * 
+     * @param o The object to test equality with
+     * @return True if the two instances represent the same conceptual component
+     * @throws IllegalStateException if the component has already been removed
+     */
     @Override
     public boolean equals(Object o) {
+        if (!isLive())
+            throw new IllegalStateException("Component is not alive anymore");
+        
         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;
+        if (c.owner == owner) {
+            // We use the hash code, since it is either the owner's id (unchanging)
+            // or the cached id from just before it was removed
+            return hashCode() == c.hashCode();
         } else {
             // type and owner don't match
             return false;
      * 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, or be
-     * transient.</li>
+     * annotated with {@link Unmanaged} (in which case the field is ignored.</li>
      * </ul>
      * Additionally, abstract Component types cannot have a TypedId assigned to
      * them.
      * </p>
-     * <p>
-     * A note on transient fields when defining a component. Transient fields
-     * that are not properties are completely unmanaged. They are intended for
-     * caching at the level of Component instance, and not storing managed
-     * component data (i.e. the instance created for a fast iterator transient
-     * fields are different from the canonical instance). Transient fields that
-     * are Properties are still managed, and are intended for caching at the
-     * level of conceptual component. Their values are not copied when a
-     * Component is used as a template, but are otherwise like regular
-     * properties.
-     * </p>
      * 
      * @param <T> The Component class type
      * @param type The Class whose TypedId is fetched, which must be a subclass

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

 import java.util.List;
 import java.util.Map;
 
+import com.googlecode.entreri.property.AbstractPropertyFactory;
 import com.googlecode.entreri.property.Factory;
 import com.googlecode.entreri.property.Parameter;
 import com.googlecode.entreri.property.Parameters;
 import com.googlecode.entreri.property.Property;
 import com.googlecode.entreri.property.PropertyFactory;
+import com.googlecode.entreri.property.Unmanaged;
 
 /**
  * <p>
     }
 
     /**
-     * Create a new map from field to property instances that can be shared by
-     * all instances of the Component type built by this builder for a single
-     * EntitySystem. It can be passed into
+     * Get the map from Fields of the component type to PropertyFactory
+     * implementations that will create valid property objects for each field.
+     * The created properties can then form a map valid with
      * {@link #newInstance(EntitySystem, int, Map)}.
      * 
-     * @return A new list of properties used to set the property fields in the
-     *         component
+     * @return A map from field to property factory for the type associated with
+     *         this builder
      */
-    public Map<Field, Property> createProperties() {
-        Map<Field, Property> props = new HashMap<Field, Property>(propertyFactories.size());
+    public Map<Field, PropertyFactory<?>> getPropertyFactories() {
+        Map<Field, PropertyFactory<?>> props = new HashMap<Field, PropertyFactory<?>>();
         for (int i = 0; i < propertyFactories.size(); i++)
-            props.put(fields.get(i), propertyFactories.get(i).create());
+            props.put(fields.get(i), propertyFactories.get(i));
         return Collections.unmodifiableMap(props);
     }
 
      * is used to assign values to the declared property fields of the type.
      * </p>
      * <p>
-     * It is assumed that the map was previously returned from a call to
-     * {@link #createProperties()}.
+     * It is assumed that the map was created by the factories returned from
+     * {@link #getPropertyFactories()}. Additionally, it is assumed that
+     * {@link PropertyFactory#setValue(Property, int)} is invoked by the caller
+     * as appropriate (no initialization is performed by the builder).
      * </p>
      * 
      * @param system The owning EntitySystem
             if (Modifier.isStatic(modifiers))
                 continue; // ignore static fields
             
+            if (declared[i].isAnnotationPresent(Unmanaged.class))
+                continue; // ignore the field
+            
             if (!Property.class.isAssignableFrom(declared[i].getType())) {
-                if (Modifier.isTransient(modifiers))
-                    continue; // ignore this field
-                else
-                    throw new IllegalComponentDefinitionException(type, "Component has non-Property field that is not transient: " + declared[i]);
+                throw new IllegalComponentDefinitionException(type, "Component has non-Property field that is not transient: " + declared[i]);
             }
             
             if (!Modifier.isPrivate(modifiers) && !Modifier.isProtected(modifiers))
         return nonTransientFields;
     }
     
-    private static class ReflectionPropertyFactory<P extends Property> implements PropertyFactory<P> {
+    private static class ReflectionPropertyFactory<P extends Property> extends AbstractPropertyFactory<P> {
         private final Constructor<P> ctor;
         private final Object[] values;
         

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

 package com.googlecode.entreri;
 
 import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
     
     private int componentInsert;
 
-    private final List<PropertyStore> declaredProperties;
-    private final List<PropertyStore> decoratedProperties;
+    private final List<PropertyStore<?>> declaredProperties;
+    private final List<PropertyStore<?>> decoratedProperties;
     
     private final ComponentBuilder<T> builder;
     private final Map<Field, Property> builderProperties; // Properties from declaredProperties, cached for newInstance()
-    private final Class<?>[] initParams; // all primitives will be boxed at this point
+    private final Class<?>[] initParamTypes; // all primitives will be boxed at this point
     
     private final EntitySystem system;
     
      * @param type The type of component
      * @throws NullPointerException if system or type are null
      */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
     public ComponentIndex(EntitySystem system, TypedId<T> type) {
         if (system == null || type == null)
             throw new NullPointerException("Arguments cannot be null");
         
         this.system = system;
-        initParams = getInitParams(type.getType());
+        initParamTypes = getInitParams(type.getType());
         
         builder = Component.getBuilder(type);
         
-        builderProperties = builder.createProperties();
+        Map<Field, PropertyFactory<?>> builderPropertyFactories = builder.getPropertyFactories();
+        builderProperties = new HashMap<Field, Property>();
         
-        declaredProperties = new ArrayList<PropertyStore>();
-        decoratedProperties = new ArrayList<PropertyStore>(); // empty for now
-        for (Entry<Field, Property> e: builderProperties.entrySet()) {
-            boolean isTransient = Modifier.isTransient(e.getKey().getModifiers());
-            declaredProperties.add(new PropertyStore(e.getValue(), isTransient));
+        declaredProperties = new ArrayList<PropertyStore<?>>();
+        decoratedProperties = new ArrayList<PropertyStore<?>>(); // empty for now
+        for (Entry<Field, PropertyFactory<?>> e: builderPropertyFactories.entrySet()) {
+            PropertyStore store = new PropertyStore(e.getValue());
+            declaredProperties.add(store);
+            builderProperties.put(e.getKey(), store.property);
         }
         
         entityIndexToComponentIndex = new int[1]; // holds default 0 value in 0th index
      * 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) {
+    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();
      * @return A new component of type T
      * @throws NullPointerException if fromTemplate is null
      */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     public T addComponent(int entityIndex, T fromTemplate) {
         T instance = addComponent(entityIndex);
 
         // Copy values from fromTemplate's properties to the new instances
-        List<PropertyStore> templateProps = fromTemplate.owner.declaredProperties;
+        List<PropertyStore<?>> templateProps = fromTemplate.owner.declaredProperties;
         for (int i = 0; i < templateProps.size(); i++) {
-            if (!templateProps.get(i).transientProperty) {
-                templateProps.get(i).property.getDataStore().copy(fromTemplate.index, 1,
-                                                                  declaredProperties.get(i).property.getDataStore(), 
-                                                                  instance.getIndex());
-            }
+            PropertyStore src = templateProps.get(i);
+            PropertyStore dst = declaredProperties.get(i);
+            
+            src.clone(fromTemplate.index, dst.property, instance.index);
         }
         
+        // fire add-event listener after cloning is completed
+        system.getControllerManager().fireComponentAdd(instance);
         return instance;
     }
 
             initParams = new Object[0];
         
         boolean valid = true;
-        if (initParams.length == this.initParams.length) {
+        if (initParams.length == this.initParamTypes.length) {
             for (int i = 0; i < initParams.length; i++) {
-                if (!this.initParams[i].isInstance(initParams[i])) {
+                if (!this.initParamTypes[i].isInstance(initParams[i])) {
                     valid = false;
                     break;
                 }
         }
 
         if (!valid)
-            throw new IllegalArgumentException("Must provide init params in the order: " + Arrays.toString(this.initParams));
+            throw new IllegalArgumentException("Must provide init params in the order: " + Arrays.toString(this.initParamTypes));
 
         // We know the arguments types match, so continue
         T instance = addComponent(entityIndex);
             throw new IllegalArgumentException("Init parameters failed validation", e);
         }
         
+        // fire add-event listener after initialization is completed
+        system.getControllerManager().fireComponentAdd(instance);
         return instance;
     }
     
     /*
      * Allocate and store a new component, but don't initialize it yet.
      */
-    @SuppressWarnings("unchecked")
     private T addComponent(int entityIndex) {
         if (entityIndexToComponentIndex[entityIndex] != 0)
             removeComponent(entityIndex);
         componentIndexToEntityIndex[componentIndex] = entityIndex;
         entityIndexToComponentIndex[entityIndex] = componentIndex;
 
-        // Copy default value for declared and decorated properties
+        // Set default value for declared and decorated properties,
+        // this is needed because we might be overwriting a previously removed
+        // component, or the factory might be doing something tricky
         for (int i = 0; i < declaredProperties.size(); i++) {
-            PropertyStore p = declaredProperties.get(i);
-            p.defaultData.copy(0, 1, p.property.getDataStore(), componentIndex);
+            declaredProperties.get(i).setValue(componentIndex);
         }
         
         for (int i = 0; i < decoratedProperties.size(); i++) {
-            PropertyStore p = decoratedProperties.get(i);
-            p.defaultData.copy(0, 1, p.property.getDataStore(), componentIndex);
+            decoratedProperties.get(i).setValue(componentIndex);
         }
         
-        return (T) components[componentIndex];
+        return instance;
     }
 
     /**
 
         // This code works even if componentIndex is 0
         Component oldComponent = components[componentIndex];
+        if (oldComponent != null) {
+            // perform component clean up before data is invalidated
+            system.getControllerManager().fireComponentRemove(oldComponent);
+            oldComponent.index = 0;
+        }
 
         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) {
+    private void update(List<PropertyStore<?>> properties, Component[] newToOldMap) {
         for (int i = 0; i < properties.size(); i++) {
-            PropertyStore p = properties.get(i);
+            PropertyStore<?> p = properties.get(i);
             IndexedDataStore origStore = p.property.getDataStore();
             
             p.property.setDataStore(update(origStore, p.swap, newToOldMap));
         return dst;
     }
     
-    private void notifyCompactAwareProperties(List<PropertyStore> props) {
-        PropertyStore p;
+    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)
      * @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 
+        int size = (declaredProperties.isEmpty() ? componentInsert
                                                  : declaredProperties.get(0).property.getDataStore().size());
         
-        PropertyStore pstore = new PropertyStore(prop, true);
+        PropertyStore<P> pstore = new PropertyStore<P>(factory);
 
-        // Copy original values from factory property over to all component slots
-        IndexedDataStore newStore = prop.getDataStore().create(size);
+        // Set values from factory to all component slots
+        IndexedDataStore newStore = pstore.property.getDataStore().create(size);
+        pstore.property.setDataStore(newStore);
         for (int i = 1; i < size; i++) {
-            // This assumes that the property stores its data in the 0th index
-            pstore.defaultData.copy(0, 1, newStore, i);
+            pstore.setValue(i);
         }
-        prop.setDataStore(newStore);
         
         decoratedProperties.add(pstore);
-        return prop;
+        return pstore.property;
     }
 
     /**
      * @param p The property to remove
      */
     public void undecorate(Property p) {
-        Iterator<PropertyStore> it = decoratedProperties.iterator();
+        Iterator<PropertyStore<?>> it = decoratedProperties.iterator();
         while(it.hasNext()) {
             if (it.next().property == p) {
                 it.remove();
         }
     }
     
-    private static class PropertyStore {
-        final Property property;
-        final boolean transientProperty;
-        final IndexedDataStore defaultData; // if not null, has a single component
-
+    private static class PropertyStore<P extends Property> {
+        final P property;
+        final PropertyFactory<P> creator;
         IndexedDataStore swap; // may be null
         
         
-        public PropertyStore(Property p, boolean isTransient) {
-            property = p;
-            transientProperty = isTransient;
-            
-            defaultData = property.getDataStore().create(1);
-            property.getDataStore().copy(0, 1, defaultData, 0);
+        public PropertyStore(PropertyFactory<P> creator) {
+            this.creator = creator;
+            property = creator.create();
+        }
+        
+        private void clone(int srcIndex, P dst, int dstIndex) {
+            creator.clone(property, srcIndex, dst, dstIndex);
+        }
+        
+        private void setValue(int index) {
+            creator.setValue(property, index);
         }
     }
 }

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

 package com.googlecode.entreri;
 
 /**
+ * <p>
  * 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.
+ * </p>
+ * <p>
+ * Controllers also have event listener method hooks so that they can be
+ * notified when they are added or removed from an EntitySystem, or when an
+ * Entity or component in a system they're attached to is added or removed.
+ * </p>
  * 
  * @author Michael Ludwig
  */
      * @param dt The elapsed time since the last processing
      */
     public void postProcess(EntitySystem system, float dt);
+    
+    public void addedToSystem(EntitySystem system);
+    
+    public void removedFromSystem(EntitySystem system);
+    
+    public void onEntityAdd(Entity e);
+    
+    public void onEntityRemove(Entity e);
+    
+    public void onComponentAdd(Component c);
+    
+    public void onComponentRemove(Component c);
 }

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

  */
 package com.googlecode.entreri;
 
-import java.lang.annotation.Annotation;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
  */
 public class ControllerManager {
     /**
+     * Key represents a typed key into controller data managed by a
+     * ControllerManager. Equality is defined by reference, so it is generally
+     * best to store keys for reuse in private or public fields (possibly static
+     * if the key is shared).
+     * 
+     * @param <T> The type of data associated with the key
+     */
+    public static class Key<T> { }
+    
+    /**
      * The Phase enum represents the different phases of
      * processing that an EntitySystem can go through during
      * what is often considered a "frame".
     
     // 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 ConcurrentHashMap<Key<?>, Object> controllerData;
 
     private final EntitySystem system;
 
             throw new NullPointerException("EntitySystem cannot be null");
         
         this.system = system;
-        controllerData = new ConcurrentHashMap<Class<? extends Annotation>, Object>();
-        privateData = new ConcurrentHashMap<Object, Object>();
+        controllerData = new ConcurrentHashMap<Key<?>, Object>();
         controllers = new ArrayList<Controller>();
         
         fixedDelta = 1 / 60f; // 60fps
     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.
+     * Return the controller data that has been mapped to the given <tt>key</tt>
+     * . This will return null if there has been no assigned data. The getData()
+     * and setData() methods should be used by controllers to share data between
+     * themselves, and to store system-dependent state so that their
+     * implementations are properly indepdent of the system.
      * 
      * @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) {
+    @SuppressWarnings("unchecked")
+    public <T> T getData(Key<T> key) {
         if (key == null)
             throw new NullPointerException("Key cannot be null");
-        return controllerData.get(key);
+        return (T) 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
+     * Store <tt>value</tt> to the given <tt>key</tt> so that future
+     * calls to {@link #getData(Key)} 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 key The key
      * @param value The new value to store
      * @throws NullPointerException if key is null
      */
-    public void setControllerData(Class<? extends Annotation> key, Object value) {
+    public <T> void setData(Key<T> key, T value) {
         if (key == null)
             throw new NullPointerException("Key cannot be null");
         if (value == null)
     }
 
     /**
-     * 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
             throw new NullPointerException("Controller cannot be null");
         
         // remove it first - which does nothing if not in the list
-        controllers.remove(controller);
+        boolean removed = controllers.remove(controller);
         // now add it to the end
         controllers.add(controller);
+        
+        if (!removed)
+            controller.addedToSystem(system);
     }
 
     /**
     public void removeController(Controller controller) {
         if (controller == null)
             throw new NullPointerException("Controller cannot be null");
-        controllers.remove(controller);
+        boolean removed = controllers.remove(controller);
+        if (removed)
+            controller.removedFromSystem(system);
     }
     
     /**
         
         switch(phase) {
         case PREPROCESS:
-            doPreProcess(dt); break;
+            firePreProcess(dt); break;
         case PROCESS:
-            doProcess(dt); break;
+            fireProcess(dt); break;
         case POSTPROCESS:
-            doPostProcess(dt); break;
+            firePostProcess(dt); break;
         case ALL:
-            // Perform all stages in one go
-            doPreProcess(dt);
-            doProcess(dt);
-            doPostProcess(dt);
+            // Perform all phases in one go
+            firePreProcess(dt);
+            fireProcess(dt);
+            firePostProcess(dt);
             break;
         }
     }
+
+    /**
+     * Invoke onEntityAdd() for all controllers.
+     * 
+     * @param e The entity being added
+     */
+    void fireEntityAdd(Entity e) {
+        for (int i = 0; i < controllers.size(); i++)
+            controllers.get(i).onEntityAdd(e);
+    }
     
-    private void doPreProcess(float dt) {
+    /**
+     * Invoke onEntityRemove() for all controllers.
+     * 
+     * @param e The entity being removed
+     */
+    void fireEntityRemove(Entity e) {
+        for (int i = 0; i < controllers.size(); i++)
+            controllers.get(i).onEntityRemove(e);
+    }
+    
+    /**
+     * Invoke onComponentAdd() for all controllers.
+     * 
+     * @param c The component being added
+     */
+    void fireComponentAdd(Component c) {
+        for (int i = 0; i < controllers.size(); i++)
+            controllers.get(i).onComponentAdd(c);
+    }
+    
+    /**
+     * Invoke onComponentRemove() for all controllers.
+     * 
+     * @param c The component being removed
+     */
+    void fireComponentRemove(Component c) {
+        for (int i = 0; i < controllers.size(); i++)
+            controllers.get(i).onComponentRemove(c);
+    }
+    
+    private void firePreProcess(float dt) {
         for (int i = 0; i < controllers.size(); i++)
             controllers.get(i).preProcess(system, dt);
     }
     
-    private void doProcess(float dt) {
+    private void fireProcess(float dt) {
         for (int i = 0; i < controllers.size(); i++)
             controllers.get(i).process(system, dt);
     }
     
-    private void doPostProcess(float dt) {
+    private void firePostProcess(float dt) {
         for (int i = 0; i < controllers.size(); i++)
             controllers.get(i).postProcess(system, dt);
     }

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

 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
+import com.googlecode.entreri.property.Property;
+import com.googlecode.entreri.property.PropertyFactory;
+
 /**
  * <p>
  * An Entity represents a collection of Components within an EntitySystem.
      *         has been removed
      */
     public boolean isLive() {
-        return getId() != 0;
+        // index == 0 is equivalent to getId() == 0, given the way the system
+        // manages index's, but this is faster
+        return index != 0;
     }
 
     /**
     }
 
     /**
+     * <p>
      * Add a Component of type T to this Entity, but clone its state from the
      * existing component of type T. The existing component must still be
      * attached to an Entity other than this entity, but it could be from a
      * different EntitySystem. If there already exists a component of type T
      * added to this entity, it is removed first, and a new one is instantiated.
+     * </p>
+     * <p>
+     * The new component is initialized by cloning the property values from
+     * <tt>toClone</tt> into the values of the new instance. This is performed
+     * by invoking {@link PropertyFactory#clone(Property, int, Property, int)}
+     * with the factory that created each property. All default property
+     * factories perform a copy by value (or copy by reference for object
+     * types).
+     * </p>
      * 
      * @param <T> The parameterized type of component to add
      * @param toClone The existing T to clone when attaching to this component
     public Iterator<Component> iterator() {
         return new ComponentIterator(system, index);
     }
-    
+
+    /**
+     * <p>
+     * Entity's hashCode() returns its entity id.
+     * </p>
+     * <p>
+     * This means you can use the entities created by a system's fast iterators
+     * to query a hash-based collections. However, you should never store fast
+     * entities into a hash-based collection because their id (and thus hash
+     * code) will change each iteration.
+     * </p>
+     * <p>
+     * Additionally, an entity's id is updated when it is removed from an system.
+     * This means it is critical to remove entities from collections before they
+     * a removed from a system. This can be done in
+     * {@link Controller#onEntityRemove(Entity)}.
+     * </p>
+     * 
+     * @throws IllegalStateException if the entity has already been removed
+     */
     @Override
     public int hashCode() {
+        if (!isLive())
+            throw new IllegalStateException("Entity is no longer alive");
         return getId();
     }
-    
+
+    /**
+     * <p>
+     * Entity's equals() returns true if the object is another entity in the
+     * same EntitySystem, with the same id.
+     * </p>
+     * <p>
+     * This means you can use the entities created by a system's fast iterators
+     * to query equals-based collections. However, you should never store fast
+     * entities into a equals-based collections because their id (and thus
+     * definition of equality) will change with each iteration.
+     * </p>
+     * <p>
+     * Additionally, an entity's index is updated when it is removed from an
+     * entity or system. This means it is critical to remove entities from
+     * collections before they are removed from the system. This can be done in
+     * {@link Controller#onEntityRemove(Entity)}.
+     * </p>
+     * 
+     * @param o The object to test equality with
+     * @return True if the two instances represent the same conceptual entity
+     * @throws IllegalStateException if the entity has already been removed
+     */
     @Override
     public boolean equals(Object o) {
+        if (!isLive())
+            throw new IllegalStateException("Entity is no longer alive");
+        
         if (!(o instanceof Entity))
             return false;
         Entity e = (Entity) o;

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

         entities[entityIndex] = newEntity;
         entityIds[entityIndex] = entityIdSeq++;
         
+        // invoke add-event listeners now before we invoke any listeners
+        // due to templating so events have proper sequencing
+        manager.fireEntityAdd(newEntity);
+        
         if (template != null) {
             for (Component c: template) {
                 addFromTemplate(entityIndex, c.getTypedId(), c);
         
         // clear out id and canonical entity
         Entity old = entities[index];
-        entityIds[index] = 0;
-        entities[index] = null;
-        
         if (old != null) {
-            // update its index
+            // fire remove-event listener
+            manager.fireEntityRemove(old);
+            
+            entityIds[index] = 0;
+            entities[index] = null;
             old.index = 0;
+            
             return true;
-        } else
+        } else {
             return false;
+        }
     }
     
     @SuppressWarnings({ "unchecked", "rawtypes" })

src/main/java/com/googlecode/entreri/property/AbstractPropertyFactory.java

+/*
+ * Entreri, an entity-component framework in Java
+ *
+ * Copyright (c) 2011, Michael Ludwig
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *     Redistributions of source code must retain the above copyright notice,
+ *         this list of conditions and the following disclaimer.
+ *     Redistributions in binary form must reproduce the above copyright notice,
+ *         this list of conditions and the following disclaimer in the
+ *         documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.googlecode.entreri.property;
+
+/**
+ * AbstractPropertyFactory is an abstract PropertyFactory implementation that
+ * implements {@link #setValue(Property, int)} in terms of
+ * {@link IndexedDataStore#setDefault(int)} and implements
+ * {@link #clone(Property, int, Property, int)} in terms of
+ * {@link IndexedDataStore#copy(int, int, IndexedDataStore, int)}. In many cases
+ * this is sufficient for most PropertyFactories.
+ * 
+ * @author Michael Ludwig
+ * @param <P>
+ */
+public abstract class AbstractPropertyFactory<P extends Property> implements PropertyFactory<P> {
+    @Override
+    public void setValue(P property, int index) {
+        property.getDataStore().setDefault(index);
+    }
+
+    @Override
+    public void clone(P src, int srcIndex, P dst, int dstIndex) {
+        src.getDataStore().copy(srcIndex, 1, dst.getDataStore(), dstIndex);
+    }
+}

src/main/java/com/googlecode/entreri/property/FloatProperty.java

      * @return A PropertyFactory for FloatProperty
      */
     public static PropertyFactory<FloatProperty> factory(final int elementSize) {
-        return new PropertyFactory<FloatProperty>() {
+        return new AbstractPropertyFactory<FloatProperty>() {
             @Override
             public FloatProperty create() {
                 return new FloatProperty(elementSize);
         protected int getArrayLength(float[] array) {
             return array.length;
         }
+
+        @Override
+        public void setDefault(int offset) {
+            for (int i = offset * elementSize; i < (offset + 1) * elementSize; i++)
+                array[i] = 0f;
+        }
     }
 }

src/main/java/com/googlecode/entreri/property/IndexedDataStore.java

      *             out-of-bounds exceptions
      */
     public void copy(int srcOffset, int len, IndexedDataStore dest, int destOffset);
+
+    /**
+     * Update the values of the data store at the given component offset to
+     * reflect the default value. The default value is dependent on the
+     * IndexedDataStore (and in that sense, the Property creating the data
+     * store), but will usually be null or 0.
+     * 
+     * @param offset The component offset into the data store
+     * @throws IndexOutOfBoundsException if the offset is invalid
+     */
+    public void setDefault(int offset);
 }

src/main/java/com/googlecode/entreri/property/IntProperty.java

      * @return A PropertyFactory for IntProperty
      */
     public static PropertyFactory<IntProperty> factory(final int elementSize) {
-        return new PropertyFactory<IntProperty>() {
+        return new AbstractPropertyFactory<IntProperty>() {
             @Override
             public IntProperty create() {
                 return new IntProperty(elementSize);
         protected int getArrayLength(int[] array) {
             return array.length;
         }
+        
+        @Override
+        public void setDefault(int offset) {
+            for (int i = offset * elementSize; i < (offset + 1) * elementSize; i++)
+                array[i] = 0;
+        }
     }
 }

src/main/java/com/googlecode/entreri/property/ObjectProperty.java

      * @return A PropertyFactory for ObjectProperty
      */
     public static <T> PropertyFactory<ObjectProperty<T>> factory(final int elementSize) {
-        return new PropertyFactory<ObjectProperty<T>>() {
+        return new AbstractPropertyFactory<ObjectProperty<T>>() {
             @Override
             public ObjectProperty<T> create() {
                 return new ObjectProperty<T>(elementSize);
         protected int getArrayLength(Object[] array) {
             return array.length;
         }
+        
+        @Override
+        public void setDefault(int offset) {
+            for (int i = offset * elementSize; i < (offset + 1) * elementSize; i++)
+                array[i] = null;
+        }
     }
 }

src/main/java/com/googlecode/entreri/property/PropertyFactory.java

  */
 package com.googlecode.entreri.property;
 
+import com.googlecode.entreri.Entity;
+
 /**
  * <p>
  * A PropertyFactory is a simple factory that can be used to create Property
      * @return A new Property of type T
      */
     public T create();
+
+    /**
+     * Set the default value that the component at the specified <tt>index</tt>
+     * will see before it's init() method is invoked. In some cases, this could
+     * be used in-place of initializing in init() method.
+     * 
+     * @param property The property whose value will be updated
+     * @param index The component index to be updated
+     */
+    public void setValue(T property, int index);
+
+    /**
+     * Copy the value from <tt>src</tt> at component index, <tt>srcIndex</tt> to
+     * <tt>dst</tt> at <tt>dstIndex</tt>. This is used when a component is
+     * created and cloned from a template with
+     * {@link Entity#add(com.googlecode.entreri.Component)}. For many cases a
+     * plain copy-by-value or copy-by-reference is sufficient, but some
+     * component types might require more complicated cloning rules.
+     * 
+     * @param src The source property that is being cloned
+     * @param srcIndex The index into src of the component being cloned
+     * @param dst The destination property created from the template
+     * @param dstIndex The index into dst of the component being created
+     */
+    public void clone(T src, int srcIndex, T dst, int dstIndex);
 }

src/main/java/com/googlecode/entreri/property/Unmanaged.java

+/*
+ * Entreri, an entity-component framework in Java
+ *
+ * Copyright (c) 2011, Michael Ludwig
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *     Redistributions of source code must retain the above copyright notice,
+ *         this list of conditions and the following disclaimer.
+ *     Redistributions in binary form must reproduce the above copyright notice,
+ *         this list of conditions and the following disclaimer in the
+ *         documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.googlecode.entreri.property;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.googlecode.entreri.IllegalComponentDefinitionException;
+
+/**
+ * Unmanaged is an annotation that can be applied to fields in a Component
+ * definition to make the field completely ignored by the EntitySystem creating
+ * or managing the component. This can be used to store per-instance cached data
+ * in the component without triggering
+ * {@link IllegalComponentDefinitionException exceptions}. However, it makes
+ * little sense to declare a Property field as unmanaged because then its data
+ * store will not be kept in sync with the component's other indexed data.
+ * 
+ * @author Michael Ludwig
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Unmanaged { }

src/test/java/com/googlecode/entreri/ComponentTest.java

 import com.googlecode.entreri.component.ObjectComponent;
 import com.googlecode.entreri.component.PublicConstructorComponent;
 import com.googlecode.entreri.component.PublicPropertyComponent;
-import com.googlecode.entreri.component.TransientFieldComponent;
+import com.googlecode.entreri.component.UnmanagedFieldComponent;
 import com.googlecode.entreri.property.FloatProperty;
 import com.googlecode.entreri.property.FloatPropertyFactory;
 import com.googlecode.entreri.property.MultiParameterProperty;
 import com.googlecode.entreri.property.NoParameterProperty;
 import com.googlecode.entreri.property.Property;
+import com.googlecode.entreri.property.PropertyFactory;
 
 public class ComponentTest {
     @Test
         doGetTypedIdTest(IntComponent.class);
         doGetTypedIdTest(ObjectComponent.class);
         doGetTypedIdTest(MultiPropertyComponent.class);
-        doGetTypedIdTest(TransientFieldComponent.class);
+        doGetTypedIdTest(UnmanagedFieldComponent.class);
     }
     
     private void doGetTypedIdTest(Class<? extends Component> type) {
     }
     
     @Test
+    public void testFactorySetValue() {
+        EntitySystem system = new EntitySystem();
+        MultiPropertyComponent c = system.addEntity().add(Component.getTypedId(MultiPropertyComponent.class));
+        Assert.assertEquals(FloatPropertyFactory.DEFAULT, c.getFactoryFloat(), .0001f);
+    }
+    
+    @Test
     public void testPropertyLookup() {
         ComponentBuilder<MultiPropertyComponent> builder = Component.getBuilder(Component.getTypedId(MultiPropertyComponent.class));
-        Collection<Property> props = builder.createProperties().values();
+        Collection<Property> props = new HashSet<Property>();
+        for (PropertyFactory<?> factory: builder.getPropertyFactories().values()) {
+            props.add(factory.create());
+        }
         
         Set<Class<? extends Property>> propTypeSet = new HashSet<Class<? extends Property>>();
         for (Property p: props) {
     }
     
     @Test
-    public void testTransientField() {
-        TypedId<TransientFieldComponent> id = Component.getTypedId(TransientFieldComponent.class);
+    public void testUnmanagedField() {
+        TypedId<UnmanagedFieldComponent> id = Component.getTypedId(UnmanagedFieldComponent.class);
         
         EntitySystem system = new EntitySystem();
         for (int i = 0; i < 4; i++) {
         }
         
         int i = 0;
-        Iterator<TransientFieldComponent> it = system.iterator(id);
+        Iterator<UnmanagedFieldComponent> it = system.iterator(id);
         while(it.hasNext()) {
             float f = it.next().getFloat();
             Assert.assertEquals(i, f, .0001f);

src/test/java/com/googlecode/entreri/ControllerManagerTest.java

 import org.junit.Assert;
 import org.junit.Test;
 
-import com.googlecode.entreri.Controller;
-import com.googlecode.entreri.EntitySystem;
+import com.googlecode.entreri.ControllerManager.Key;
 import com.googlecode.entreri.ControllerManager.Phase;
+import com.googlecode.entreri.component.IntComponent;
 
 public class ControllerManagerTest {
     @Test
     
     @Test
     public void testControllerData() {
+        Key<Object> key = new Key<Object>();
         EntitySystem system = new EntitySystem();
         Object data = new Object();
-        system.getControllerManager().setControllerData(Key.class, data);
+        system.getControllerManager().setData(key, data);
         
-        Assert.assertEquals(data, system.getControllerManager().getControllerData(Key.class));
+        Assert.assertEquals(data, system.getControllerManager().getData(key));
         
-        system.getControllerManager().setControllerData(Key.class, null);
-        Assert.assertNull(system.getControllerManager().getControllerData(Key.class));
+        system.getControllerManager().setData(key, null);
+        Assert.assertNull(system.getControllerManager().getData(key));
     }
     
     @Test
-    public void testPrivateData() {
+    public void testControllerAddRemoveListener() {
         EntitySystem system = new EntitySystem();
-        Object data = new Object();
-        Object key = new Object();
+        ControllerImpl ctrl = new ControllerImpl();
         
-        system.getControllerManager().setPrivateData(key, data);
-        
-        Assert.assertEquals(data, system.getControllerManager().getPrivateData(key));
-        
-        system.getControllerManager().setPrivateData(key, null);
-        Assert.assertNull(system.getControllerManager().getPrivateData(key));
+        system.getControllerManager().addController(ctrl);
+        Assert.assertTrue(ctrl.added);
+        system.getControllerManager().removeController(ctrl);
+        Assert.assertTrue(ctrl.removed);
     }
     
-    private static @interface Key {
+    @Test
+    public void testEntityAddRemoveListener() {
+        EntitySystem system = new EntitySystem();
+        ControllerImpl ctrl = new ControllerImpl();
+        system.getControllerManager().addController(ctrl);
         
+        Entity e = system.addEntity();
+        Assert.assertEquals(e, ctrl.lastAddedEntity);
+        system.removeEntity(e);
+        Assert.assertTrue(e == ctrl.lastRemovedEntity);
+    }
+    
+    @Test
+    public void testComponentAddRemoveListener() {
+        EntitySystem system = new EntitySystem();
+        ControllerImpl ctrl = new ControllerImpl();
+        system.getControllerManager().addController(ctrl);
+        
+        Entity e = system.addEntity();
+        IntComponent i = e.add(Component.getTypedId(IntComponent.class));
+        
+        Assert.assertEquals(i, ctrl.lastAddedComponent);
+        e.remove(Component.getTypedId(IntComponent.class));
+        Assert.assertTrue(i == ctrl.lastRemovedComponent);
     }
     
     private static class ControllerImpl implements Controller {
         
         private float dt;
         
+        private boolean added;
+        private boolean removed;
+        
+        private Entity lastAddedEntity;
+        private Entity lastRemovedEntity;
+        
+        private Component lastAddedComponent;
+        private Component lastRemovedComponent;
+        
         @Override
         public void preProcess(EntitySystem system, float dt) {
             preprocessed = true;
             postprocessed = true;
             this.dt = dt;
         }
+
+        @Override
+        public void addedToSystem(EntitySystem system) {
+            added = true;
+        }
+
+        @Override
+        public void removedFromSystem(EntitySystem system) {
+            removed = true;
+        }
+
+        @Override
+        public void onEntityAdd(Entity e) {
+            lastAddedEntity = e;
+        }
+
+        @Override
+        public void onEntityRemove(Entity e) {
+            lastRemovedEntity = e;
+        }
+
+        @Override
+        public void onComponentAdd(Component c) {
+            lastAddedComponent = c;
+        }
+
+        @Override
+        public void onComponentRemove(Component c) {
+            lastRemovedComponent = c;
+        }
     }
 }

src/test/java/com/googlecode/entreri/component/MultiPropertyComponent.java

         return noparams;
     }
     
+    public void setFactoryFloat(float f) {
+        fromFactory.set(f, getIndex(), 0);
+    }
+    
+    public float getFactoryFloat() {
+        return fromFactory.get(getIndex(), 0);
+    }
+    
     @Override
     protected void init(Object... initParams) throws Exception {
     }

src/test/java/com/googlecode/entreri/component/TransientFieldComponent.java

-/*
- * Entreri, an entity-component framework in Java
- *
- * Copyright (c) 2011, Michael Ludwig
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- *     Redistributions of source code must retain the above copyright notice,
- *         this list of conditions and the following disclaimer.
- *     Redistributions in binary form must reproduce the above copyright notice,
- *         this list of conditions and the following disclaimer in the
- *         documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.googlecode.entreri.component;
-
-import com.googlecode.entreri.Component;
-import com.googlecode.entreri.EntitySystem;
-import com.googlecode.entreri.property.ObjectProperty;
-
-public class TransientFieldComponent extends Component {
-    private transient ObjectProperty<Object> transientProperty;
-    private transient float field;
-    
-    protected TransientFieldComponent(EntitySystem system, int index) {
-        super(system, index);
-    }
-
-    @Override
-    protected void init(Object... initParams) throws Exception {
-        
-    }
-
-    public void setObject(Object v) {
-        transientProperty.set(v, getIndex(), 0);
-    }
-    
-    public Object getObject() {
-        return transientProperty.get(getIndex(), 0);
-    }
-    
-    public float getFloat() {
-        return field;
-    }
-    
-    public void setFloat(float f) {
-        field = f;
-    }
-}

src/test/java/com/googlecode/entreri/component/UnmanagedFieldComponent.java

+/*
+ * Entreri, an entity-component framework in Java
+ *
+ * Copyright (c) 2011, Michael Ludwig
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *     Redistributions of source code must retain the above copyright notice,
+ *         this list of conditions and the following disclaimer.
+ *     Redistributions in binary form must reproduce the above copyright notice,
+ *         this list of conditions and the following disclaimer in the
+ *         documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.googlecode.entreri.component;
+
+import com.googlecode.entreri.Component;
+import com.googlecode.entreri.EntitySystem;
+import com.googlecode.entreri.property.ObjectProperty;
+import com.googlecode.entreri.property.Unmanaged;
+
+public class UnmanagedFieldComponent extends Component {
+    private transient ObjectProperty<Object> transientProperty;
+    
+    @Unmanaged
+    private transient float field;
+    
+    protected UnmanagedFieldComponent(EntitySystem system, int index) {
+        super(system, index);
+    }
+
+    @Override
+    protected void init(Object... initParams) throws Exception {
+        
+    }
+
+    public void setObject(Object v) {
+        transientProperty.set(v, getIndex(), 0);
+    }
+    
+    public Object getObject() {
+        return transientProperty.get(getIndex(), 0);
+    }
+    
+    public float getFloat() {
+        return field;
+    }
+    
+    public void setFloat(float f) {
+        field = f;
+    }
+}

src/test/java/com/googlecode/entreri/property/FloatPropertyFactory.java

  */
 package com.googlecode.entreri.property;
 
-import com.googlecode.entreri.property.FloatProperty;
-import com.googlecode.entreri.property.PropertyFactory;
 
-public class FloatPropertyFactory implements PropertyFactory<FloatProperty> {
+public class FloatPropertyFactory extends AbstractPropertyFactory<FloatProperty> {
+    public static final float DEFAULT = 5f;
+    
     @Override
     public FloatProperty create() {
         return new FloatProperty(1);
     }
+    
+    @Override
+    public void setValue(FloatProperty p, int index) {
+        p.set(DEFAULT, index, 0);
+    }
 }