Commits

Michael Ludwig committed 8074a20

Implement transient properties and fields.

Comments (0)

Files changed (7)

-<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">
+<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.googlecode.entreri</groupId>
     <artifactId>entreri</artifactId>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>2.3.2</version>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</target>
+                    <showWarnings>true</showWarnings>
+                </configuration>
             </plugin>
 
             <plugin>

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

      * 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>
+     * must implement Property and be declared private or protected, or be
+     * transient.</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.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import com.googlecode.entreri.property.Factory;
 import com.googlecode.entreri.property.Parameter;
     }
 
     /**
-     * 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)}.
+     * 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
+     * {@link #newInstance(EntitySystem, int, Map)}.
      * 
      * @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());
+    public Map<Field, Property> createProperties() {
+        Map<Field, Property> props = new HashMap<Field, Property>(propertyFactories.size());
         for (int i = 0; i < propertyFactories.size(); i++)
-            props.add(propertyFactories.get(i).create());
-        return props;
+            props.put(fields.get(i), propertyFactories.get(i).create());
+        return Collections.unmodifiableMap(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
+     * its {@link Component#init()} method is NOT called. The map 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
+     * It is assumed that the map 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
+     * @param properties The map 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) {
+    public T newInstance(EntitySystem system, int index, Map<Field, 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));
+                fields.get(i).set(t, properties.get(fields.get(i)));
             }
             
             return t;
     
     private static List<Field> getFields(Class<? extends Component> type) {
         Field[] declared = type.getDeclaredFields();
-        AccessibleObject[] access = new AccessibleObject[declared.length];
+        List<Field> nonTransientFields = new ArrayList<Field>(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 (!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]);
+            }
+            
             if (!Modifier.isPrivate(modifiers) && !Modifier.isProtected(modifiers))
                 throw new IllegalComponentDefinitionException(type, "Field must be private or protected: " + declared[i]);
             
-            access[i] = declared[i];
+            nonTransientFields.add(declared[i]);
         }
         
         // Make sure all fields are accessible so we can assign them
+        AccessibleObject[] access = new AccessibleObject[nonTransientFields.size()];
+        for (int i = 0; i < access.length; i++)
+            access[i] = nonTransientFields.get(i);
         Field.setAccessible(access, true);
-        return Arrays.asList(declared);
+        return nonTransientFields;
     }
     
     private static class ReflectionPropertyFactory<P extends Property> implements PropertyFactory<P> {

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.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.NoSuchElementException;
 
 import com.googlecode.entreri.property.CompactAwareProperty;
     private final List<PropertyStore> decoratedProperties;
     
     private final ComponentBuilder<T> builder;
-    private final List<Property> builderProperties; // Properties from declaredProperties, cached for newInstance()
+    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 EntitySystem system;
         
         declaredProperties = new ArrayList<PropertyStore>();
         decoratedProperties = new ArrayList<PropertyStore>(); // empty for now
-        for (Property p: builderProperties)
-            declaredProperties.add(new PropertyStore(p));
+        for (Entry<Field, Property> e: builderProperties.entrySet()) {
+            boolean isTransient = Modifier.isTransient(e.getKey().getModifiers());
+            declaredProperties.add(new PropertyStore(e.getValue(), isTransient));
+        }
         
         entityIndexToComponentIndex = new int[1]; // holds default 0 value in 0th index
         componentIndexToEntityIndex = new int[1]; // holds default 0 value in 0th index
         // 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(), 
-                                                              instance.getIndex());
+            if (!templateProps.get(i).transientProperty) {
+                templateProps.get(i).property.getDataStore().copy(fromTemplate.index, 1,
+                                                                  declaredProperties.get(i).property.getDataStore(), 
+                                                                  instance.getIndex());
+            }
         }
         
         return instance;
         componentIndexToEntityIndex[componentIndex] = entityIndex;
         entityIndexToComponentIndex[entityIndex] = componentIndex;
 
-        // Copy default value for decorated properties
+        // Copy default value for declared and decorated properties
+        for (int i = 0; i < declaredProperties.size(); i++) {
+            PropertyStore p = declaredProperties.get(i);
+            p.defaultData.copy(0, 1, p.property.getDataStore(), componentIndex);
+        }
+        
         for (int i = 0; i < decoratedProperties.size(); i++) {
             PropertyStore p = decoratedProperties.get(i);
             p.defaultData.copy(0, 1, p.property.getDataStore(), componentIndex);
         int size = (declaredProperties.isEmpty() ? componentInsert + 1 
                                                  : declaredProperties.get(0).property.getDataStore().size());
         
+        PropertyStore pstore = new PropertyStore(prop, true);
+
         // Copy original values from factory property over to all component slots
-        IndexedDataStore oldStore = prop.getDataStore();
-        IndexedDataStore newStore = oldStore.create(size);
+        IndexedDataStore newStore = prop.getDataStore().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);
+            pstore.defaultData.copy(0, 1, newStore, i);
         }
         prop.setDataStore(newStore);
         
-        PropertyStore pstore = new PropertyStore(prop);
-        pstore.defaultData = oldStore;
-        
         decoratedProperties.add(pstore);
         return prop;
     }
     
     private static class PropertyStore {
         final Property property;
+        final boolean transientProperty;
+        final IndexedDataStore defaultData; // if not null, has a single component
+
         IndexedDataStore swap; // may be null
-        IndexedDataStore defaultData; // if not null, has a single component
         
-        public PropertyStore(Property p) {
+        
+        public PropertyStore(Property p, boolean isTransient) {
             property = p;
+            transientProperty = isTransient;
+            
+            defaultData = property.getDataStore().create(1);
+            property.getDataStore().copy(0, 1, defaultData, 0);
         }
     }
 }

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

      * ways to access their data; as an example see
      * {@link FloatProperty#getIndexedData()}.
      * </p>
+     * <p>
+     * The returned data store must always have at least 1 element in it.
+     * </p>
      * 
      * @return The current IndexedDataStore used by the property
      */

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

  */
 package com.googlecode.entreri;
 
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Set;
 
 import org.junit.Assert;
 import org.junit.Test;
 
-import com.googlecode.entreri.Component;
-import com.googlecode.entreri.ComponentBuilder;
-import com.googlecode.entreri.Entity;
-import com.googlecode.entreri.EntitySystem;
-import com.googlecode.entreri.IllegalComponentDefinitionException;
 import com.googlecode.entreri.component.BadConstructorComponent;
 import com.googlecode.entreri.component.BadParametersComponent;
 import com.googlecode.entreri.component.ExtraFieldComponent;
 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.property.FloatProperty;
 import com.googlecode.entreri.property.FloatPropertyFactory;
 import com.googlecode.entreri.property.MultiParameterProperty;
         doGetTypedIdTest(IntComponent.class);
         doGetTypedIdTest(ObjectComponent.class);
         doGetTypedIdTest(MultiPropertyComponent.class);
+        doGetTypedIdTest(TransientFieldComponent.class);
     }
     
     private void doGetTypedIdTest(Class<? extends Component> type) {
     @Test
     public void testPropertyLookup() {
         ComponentBuilder<MultiPropertyComponent> builder = Component.getBuilder(Component.getTypedId(MultiPropertyComponent.class));
-        List<Property> props = builder.createProperties();
+        Collection<Property> props = builder.createProperties().values();
         
         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);
+        
+        EntitySystem system = new EntitySystem();
+        for (int i = 0; i < 4; i++) {
+            system.addEntity().add(id).setFloat(i);
+        }
+        
+        int i = 0;
+        Iterator<TransientFieldComponent> it = system.iterator(id);
+        while(it.hasNext()) {
+            float f = it.next().getFloat();
+            Assert.assertEquals(i, f, .0001f);
+            i++;
+        }
+        
+        it = system.fastIterator(id);
+        while(it.hasNext()) {
+            float f = it.next().getFloat();
+            Assert.assertEquals(0, f, .0001f);
+        }
+    }
+    
+    @Test
     public void testDecorateProperty() {
         EntitySystem system = new EntitySystem();
         Entity e = system.addEntity();

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

+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;
+    }
+}