Commits

Michael Ludwig committed 74f56d3

Remove EntitySetComponent, it is not worth it, and is a performance hit.

Comments (0)

Files changed (6)

ferox-scene/src/main/java/com/ferox/scene/AtmosphericFog.java

 
 /**
  * <p>
- * AtmosphericFog is a Component that can add a visual approximation to fog to a rendered
- * scene. This component models fog by using a density fall-off function and a
- * distance through which the fog will become opaque. This model is compatible
- * with the classic eye-space fog that is usable in a fixed-function OpenGL
- * rendering engine and can be extended by more Component types to provide more
- * advanced fogs, such as spatialized or shaped fogs.
+ * AtmosphericFog is a Component that can add a visual approximation to fog to a
+ * rendered scene. This component models fog by using a density fall-off
+ * function and a distance through which the fog will become opaque. This model
+ * is compatible with the classic eye-space fog that is usable in a
+ * fixed-function OpenGL rendering engine.
  * </p>
  * 
  * @author Michael Ludwig

ferox-scene/src/main/java/com/ferox/scene/EntitySetComponent.java

-package com.ferox.scene;
-
-import java.util.Arrays;
-import java.util.Set;
-
-import com.lhkbob.entreri.ComponentData;
-import com.lhkbob.entreri.Factory;
-import com.lhkbob.entreri.PropertyFactory;
-import com.lhkbob.entreri.property.ElementSize;
-import com.lhkbob.entreri.property.IntProperty;
-import com.lhkbob.entreri.property.ObjectProperty;
-
-/**
- * EntitySetComponent is an abstract component that provides protected methods
- * to store a set of entities by their ids. Internally it stores the set as a
- * sorted array so that contains queries can be done in O(log n) time with a
- * binary search. Updates and removes are more expensive, but this structure
- * allows small sets (size under 6) to be completely packed into a property and
- * avoids object allocation (unlike {@link Set} implementations).
- * 
- * @author Michael Ludwig
- * @param <T>
- */
-public abstract class EntitySetComponent<T extends EntitySetComponent<T>> extends ComponentData<T> {
-    private static final int CACHE_SIZE = 6;
-    private static final int CACHE_OFFSET = 2;
-    private static final int SCALE = CACHE_SIZE + CACHE_OFFSET;
-    
-    @ElementSize(SCALE)
-    private IntProperty firstCache; // 0 = 1st size, 1 = 2nd size, and 6 cached entity ids
-
-    // entity ids that don't fit in firstCache, the length of the array is 
-    // greater than or equal to the second cache size at offset 1
-    @Factory(CloningFactory.class)
-    private ObjectProperty<int[]> secondCache; 
-    
-    protected EntitySetComponent() { }
-    
-    /**
-     * @return The number of unique entities within this set
-     */
-    protected int sizeInternal() {
-        int[] ids = firstCache.getIndexedData();
-        int index = getIndex() * SCALE;
-        return ids[index] + ids[index + 1];
-    }
-    
-    /**
-     * Get the entity id of for the <tt>setIndex</tt>'th element in this set.
-     * The index should be between 0 and ({@link #sizeInternal()} - 1). This can
-     * be used to iterate over the entity ids within this set. The ids returned
-     * will be sorted in ascending order.
-     * 
-     * @param setIndex The index into the logical set
-     * @return The entity id stored at the given index
-     * @throws IndexOutOfBoundsException if index is not between 0 and size - 1
-     */
-    protected int getInternal(int setIndex) {
-        if (setIndex < 0)
-            throw new IndexOutOfBoundsException("Index cannot be less than 0, but was: " + setIndex);
-        
-        int[] ids = firstCache.getIndexedData();
-        int baseIndex = getIndex() * SCALE;
-        
-        int firstSize = ids[baseIndex];
-        if (setIndex < firstSize) {
-            // read value from the first index
-            return ids[baseIndex + CACHE_OFFSET + setIndex];
-        }
-        
-        int secondSize = ids[baseIndex + 1];
-        int secondSetIndex = setIndex - CACHE_SIZE;
-        if (secondSize > 0 && secondSetIndex < secondSize) {
-            // second set exists and the index is in range
-            return secondCache.get(getIndex(), 0)[secondSetIndex];
-        }
-        
-        // otherwise index is out of bounds
-        throw new IndexOutOfBoundsException("Index must be less than " + (firstSize + secondSize) + ", but was: " + setIndex);
-    }
-    
-    /**
-     * Return true if the entity with id <tt>entitId</tt> is in this set.
-     * 
-     * @param entityId The entity in question
-     * @return True if the entity was previously added to this set
-     */
-    protected boolean containsInternal(int entityId) {
-        final int[] ids = firstCache.getIndexedData();
-        final int index = getIndex() * SCALE;
-        
-        int size = ids[index];
-        if (contains(entityId, ids, index + CACHE_OFFSET, index + size + CACHE_OFFSET))
-            return true;
-        
-        size = ids[index + 1];
-        if (size > 0)
-            return contains(entityId, secondCache.get(getIndex(), 0), 0, size);
-        
-        return false;
-    }
-    
-    private boolean contains(int entityId, int[] array, int from, int to) {
-        return Arrays.binarySearch(array, from, to, entityId) >= 0;
-    }
-    
-    /*
-     * Returns -1 if added successfully.
-     * Returns MIN_VALUE if already in the array.
-     * Returns a positive value if that value was evicted, or if the input id 
-     *    was not added.
-     */
-    private int add(int entityId, int[] array, int cacheSize, int from, int to) {
-        int maxSize = to - from;
-        if (cacheSize == 0 || (cacheSize < maxSize - 1 && entityId > array[from + cacheSize - 1])) {
-            // append the entity to the end, since there is room, and it will
-            // remain in sorted order.
-            // - since the size was 0, or the entity was strictly greater than
-            //   the previously greatest item we know it hasn't been seen before
-            array[from + cacheSize] = entityId;
-            return -1;
-        }
-        
-        // search for the insert index into the array, to maintain sorted order
-        int insertIndex = Arrays.binarySearch(array, from, from + cacheSize, entityId);
-        if (insertIndex >= 0) {
-            // the entity is already in this array
-            return Integer.MIN_VALUE;
-        }
-        insertIndex = -insertIndex + 1; // convert to the actual index it should be
-        
-        if (insertIndex < to) {
-            // the entity belongs within this array
-            int evicted = -1;
-            int shift = from + cacheSize;
-            
-            if (cacheSize == maxSize) {
-                // in order to insert the new entity, the last entity gets evicted,
-                // so we decrement the shift to prevent bleeding into outside indices,
-                // and remember the last element to return
-                evicted = array[--shift];
-            }
-            
-            // we can safely shift and insert the entity
-            for (int i = shift; i > insertIndex; i--)
-                array[i] = array[i - 1];
-            array[insertIndex] = entityId;
-
-            return evicted;
-        } else {
-            // the array has no more room, so return the entity id to signal
-            // it must be added to another array
-            return entityId;
-        }
-    }
-    
-    /**
-     * Add the entity with id <tt>entityId</tt> to this set. If the entity is
-     * already within the set, then the set is not modified.
-     * 
-     * @param entityId
-     * @return True if the set was modified
-     */
-    protected boolean addInternal(int entityId) {
-        int[] ids = firstCache.getIndexedData();
-        int index = getIndex() * SCALE;
-        
-        int evicted = add(entityId, ids, ids[index], index + CACHE_OFFSET, index + CACHE_OFFSET + CACHE_SIZE);
-        if (evicted < 0) {
-            // entityId was successfully inserted into the first cache,
-            // or that it is already in the set
-            if (evicted == Integer.MIN_VALUE) {
-                // already present
-                return false;
-            } else {
-                // added, so increase the size
-                ids[index]++;
-                return true;
-            }
-        } else {
-            // secondInsert must be added to the second cache, this might
-            // be the new entity or an evicted entity
-            int[] ids2 = secondCache.get(getIndex(), 0);
-            int size = ids[index + 1];
-            
-            if (ids2 == null || size == ids2.length) {
-                // expand the 2nd cache
-                int[] newIds2 = new int[size + CACHE_SIZE];
-                for (int i = 0; i < size; i++)
-                    newIds2[i] = ids[i];
-                ids2 = newIds2;
-                secondCache.set(newIds2, getIndex(), 0);
-            }
-            
-            evicted = add(evicted, ids2, size, 0, ids2.length);
-            if (evicted < 0) {
-                if (evicted == Integer.MIN_VALUE) {
-                    // already in second set
-                    return false;
-                } else {
-                    // update size
-                    ids[index + 1]++;
-                    return true;
-                }
-            } else {
-                // should not happen with the second cache so it is sized to
-                // always have enough room above
-                throw new IllegalStateException("Set corrupted, should not happen");
-            }
-        }
-    }
-    
-    /**
-     * Remove the entity with id <tt>entitId</tt> from this set. This does
-     * nothing if the entity was not already in the set.
-     * 
-     * @param entityId The entity to remove
-     * @return True if the set was modified
-     */
-    protected boolean removeInternal(int entityId) {
-        int index = getIndex() * SCALE;
-        int[] ids = firstCache.getIndexedData();
-        
-        int firstSize = ids[index];
-        if (remove(entityId, ids, index + CACHE_OFFSET, index + CACHE_OFFSET + firstSize)) {
-            // entity was removed from the first cache, so the size must be updated
-            // or an element moved from the second to the first if available
-            if (ids[index + 1] > 0) {
-                // transfer from second to first cache
-                int[] second = secondCache.get(getIndex(), 0);
-                ids[index + CACHE_OFFSET + CACHE_SIZE - 1] = second[0];
-                // shift over remaining values in second cache
-                for (int i = ids[index + 1] - 1; i > 0; i--)
-                    second[i - 1] = second[i];
-                ids[index + 1]--; // decrease size of second cache
-            } else {
-                // decrease size of first cache
-                ids[index]--;
-            }
-            
-            return true;
-        } else {
-            // entity might be in the second cache
-            if (ids[index + 1] > 0) {
-                if (remove(entityId, secondCache.get(getIndex(), 0), 0, ids[index + 1])) {
-                    // entity was in the second cache so update the size
-                    ids[index + 1]--;
-                    return true;
-                }
-            }
-        }
-        
-        // not removed
-        return false;
-    }
-    
-    private boolean remove(int entityId, int[] array, int from, int to) {
-        int index = Arrays.binarySearch(array, from, to, entityId);
-        if (index >= 0) {
-            // found it in this array
-            if (index < to - 1) {
-                // shift over elements when not removing the last element
-                for (int i = index; i < to; i++)
-                    array[i] = array[i + 1];
-            }
-            return true;
-        } else {
-            // not in this array
-            return false;
-        }
-    }
-    
-    /**
-     * Clear all entities from this set and reset its size back to 0.
-     */
-    protected void clearInternal() {
-        int index = getIndex() * SCALE;
-
-        // reset cache counts to 0, and null the second cache
-        firstCache.getIndexedData()[index] = 0;
-        firstCache.getIndexedData()[index + 1] = 0;
-        secondCache.set(null, getIndex(), 0);
-    }
-
-    /**
-     * A PropertyFactory with copying semantics for the visibility set.
-     */
-    private static class CloningFactory implements PropertyFactory<ObjectProperty<int[]>> {
-        @Override
-        public ObjectProperty<int[]> create() {
-            return new ObjectProperty<int[]>(1);
-        }
-
-        @Override
-        public void setDefaultValue(ObjectProperty<int[]> property, int index) {
-            property.set(null, index, 0);
-        }
-
-        @Override
-        public void clone(ObjectProperty<int[]> src, int srcIndex, ObjectProperty<int[]> dst,
-                          int dstIndex) {
-            int[] original = src.get(srcIndex, 0);
-            if (original != null)
-                dst.set(Arrays.copyOf(original, original.length), dstIndex, 0);
-            else
-                dst.set(null, dstIndex, 0);
-        }
-    }
-}

ferox-scene/src/main/java/com/ferox/scene/Influences.java

 package com.ferox.scene;
 
+import java.util.HashSet;
+import java.util.Set;
+
+import com.lhkbob.entreri.ComponentData;
 import com.lhkbob.entreri.Entity;
+import com.lhkbob.entreri.Factory;
+import com.lhkbob.entreri.PropertyFactory;
 import com.lhkbob.entreri.TypeId;
+import com.lhkbob.entreri.property.ObjectProperty;
 
-public final class Influences extends EntitySetComponent<Influences> {
+public final class Influences extends ComponentData<Influences> {
     /**
      * TypeId for Influences Component type
      */
     public static final TypeId<Influences> ID = TypeId.get(Influences.class);
     
+    @Factory(SetFactory.class)
+    private ObjectProperty<Set<Entity>> entities;
+    
     private Influences() { }
     
     public Influences setInfluenced(Entity e, boolean canInfluence) {
-        return setInfluenced(e.getId(), canInfluence);
-    }
-    
-    public Influences setInfluenced(int entityId, boolean canInfluence) {
+        if (e == null)
+            throw new NullPointerException("Entity cannot be null");
+        
+        // SetFactory ensures that this is not null
+        Set<Entity> set = entities.get(getIndex(), 0);
         if (canInfluence)
-            addInternal(entityId);
+            set.add(e);
         else
-            removeInternal(entityId);
+            set.remove(e);
         return this;
     }
     
     public boolean canInfluence(Entity e) {
-        return canInfluence(e.getId());
+        if (e == null)
+            throw new NullPointerException("Entity cannot be null");
+        
+        // SetFactory ensures that this is not null
+        Set<Entity> set = entities.get(getIndex(), 0);
+        return set.contains(e);
     }
     
-    public boolean canInfluence(int entityId) {
-        return containsInternal(entityId);
+    private static class SetFactory implements PropertyFactory<ObjectProperty<Set<Entity>>> {
+        @Override
+        public ObjectProperty<Set<Entity>> create() {
+            return new ObjectProperty<Set<Entity>>(1);
+        }
+
+        @Override
+        public void setDefaultValue(ObjectProperty<Set<Entity>> property, int index) {
+            property.set(new HashSet<Entity>(), index, 0);
+        }
+
+        @Override
+        public void clone(ObjectProperty<Set<Entity>> src, int srcIndex,
+                          ObjectProperty<Set<Entity>> dst, int dstIndex) {
+            Set<Entity> toClone = src.get(srcIndex, 0);
+            dst.set(new HashSet<Entity>(toClone), dstIndex, 0);
+        }
     }
 }

ferox-scene/src/main/java/com/ferox/scene/Material.java

 
 import com.ferox.resource.BufferData.DataType;
 import com.ferox.resource.VertexAttribute;
-import com.lhkbob.entreri.Controller;
-import com.lhkbob.entreri.Entity;
+import com.lhkbob.entreri.ComponentData;
 import com.lhkbob.entreri.property.ObjectProperty;
 
 /**
  * @author Michael Ludwig
  * @param <T> The concrete type of Material
  */
-public abstract class Material<T extends Material<T>> extends EntitySetComponent<T> {
+public abstract class Material<T extends Material<T>> extends ComponentData<T> {
     private ObjectProperty<VertexAttribute> normals;
 
     protected Material() { }
     public final VertexAttribute getNormals() {
         return normals.get(getIndex(), 0);
     }
-    
-    /**
-     * Return true if this Entity has been flagged as lit by the given light
-     * Entity. Generally, it is assumed that <tt>e</tt> is a {@link Light}
-     * or other "light" component. Implementations of {@link Controller} are
-     * responsible for using this as appropriate
-     * 
-     * @param e The Entity to check light influence
-     * @return Whether or not this component's entity is lit by e
-     * @throws NullPointerException if e is null
-     */
-    public boolean isLit(Entity e) {
-        return containsInternal(e.getId());
-    }
-
-    /**
-     * As {@link #isLit(Entity)} but only requires the id of an Entity.
-     * 
-     * @param entityId The entity id
-     * @return True if the entity represented by <tt>entityId</tt> influences
-     *         this entity
-     */
-    public boolean isLit(int entityId) {
-        return containsInternal(entityId);
-    }
-
-    /**
-     * Set whether or not this Entity is considered lit by the light Entity,
-     * <tt>e</tt>. The method is provided so that Controllers can implement
-     * their own light influence algorithms.
-     * 
-     * @param e The Entity whose light influence is assigned
-     * @param lit Whether or not the Entity is lit or influence by the light, e
-     * @return This component, for chaining purposes
-     * @throws NullPointerException if e is null
-     */
-    public T setLit(Entity e, boolean lit) {
-        return setLit(e.getId(), lit);
-    }
-
-    /**
-     * As {@link #setLit(Entity, boolean)} but only requires the id of an
-     * Entity.
-     * 
-     * @param entityId The entity id that is lighting or not lighting this
-     *            entity
-     * @param lit True if the entity is lit by entityId
-     * @return This component, for chaining purposes
-     */
-    @SuppressWarnings("unchecked")
-    public T setLit(int entityId, boolean lit) {
-        if (lit)
-            addInternal(entityId);
-        else
-            removeInternal(entityId);
-        return (T) this;
-    }
-
-    /**
-     * Reset the lit flags so that the Entity is no longer lit by any lights.
-     * Subsequent calls to {@link #isLit(Entity)} will return false until an
-     * Entity has been flagged as lit via {@link #setLit(Entity, boolean)}.
-     * 
-     * @return This component, for chaining purposes
-     */
-    @SuppressWarnings("unchecked")
-    public T resetLightInfluences() {
-        clearInternal();
-        return (T) this;
-    }
 }

ferox-scene/src/main/java/com/ferox/scene/Renderable.java

 
 import com.ferox.math.Const;
 import com.ferox.math.bounds.AxisAlignedBox;
-import com.ferox.math.bounds.Frustum;
 import com.ferox.math.entreri.AxisAlignedBoxProperty;
 import com.ferox.renderer.Renderer.DrawStyle;
 import com.ferox.renderer.Renderer.PolygonType;
 import com.ferox.resource.VertexAttribute;
 import com.ferox.resource.VertexBufferObject;
 import com.ferox.util.geom.Geometry;
-import com.lhkbob.entreri.Controller;
-import com.lhkbob.entreri.Entity;
+import com.lhkbob.entreri.ComponentData;
 import com.lhkbob.entreri.TypeId;
 import com.lhkbob.entreri.Unmanaged;
 import com.lhkbob.entreri.property.ElementSize;
  * 
  * @author Michael Ludwig
  */
-public final class Renderable extends EntitySetComponent<Renderable> {
+public final class Renderable extends ComponentData<Renderable> {
     /**
      * The shared TypedId representing Renderable.
      */
     }
 
     /**
-     * Return true if this Entity has been flagged as visible to the given
-     * Entity. Generally, it is assumed that <tt>e</tt> provides a Frustum
-     * somehow (e.g. {@link Camera}. Implementations of {@link Controller} are
-     * responsible for using this as appropriate
-     * 
-     * @param e The Entity to check visibility
-     * @return Whether or not this component's entity is visible to e
-     * @throws NullPointerException if f is null
-     */
-    public boolean isVisible(Entity e) {
-        return containsInternal(e.getId());
-    }
-
-    /**
-     * As {@link #isVisible(Entity)} but only requires the id of an entity.
-     * 
-     * @param entityId The entity id to check visibility
-     * @return Whether or not this component's entity is visible to entityId
-     */
-    public boolean isVisible(int entityId) {
-        return containsInternal(entityId);
-    }
-
-    /**
-     * Set whether or not this Entity is considered visible to the Entity,
-     * <tt>e</tt>. The method is provided so that Controllers can implement
-     * their own visibility algorithms, instead of relying solely on
-     * {@link ReadOnlyAxisAlignedBox#intersects(Frustum, com.ferox.math.bounds.PlaneState)}
-     * . It is generally assumed that the input Entity somehow provides a
-     * {@link Frustum}.
-     * 
-     * @param e The Entity whose visibility is assigned
-     * @param pv Whether or not the Entity is visible to e
-     * @return This component, for chaining purposes
-     * @throws NullPointerException if f is null
-     */
-    public Renderable setVisible(Entity e, boolean pv) {
-        return setVisible(e.getId(), pv);
-    }
-
-    /**
-     * As {@link #setVisible(Entity, boolean)} but only requires the id of an
-     * entity.
-     * 
-     * @param entityId The entity id to check visibility
-     * @param pv Whether or not this component's entity is visible to entityId
-     * @return This component, for chaining purposes
-     */
-    public Renderable setVisible(int entityId, boolean pv) {
-        if (pv)
-            addInternal(entityId);
-        else
-            removeInternal(entityId);
-        return this;
-    }
-
-    /**
-     * Reset the visibility flags so that the Entity is no longer visible to any
-     * Frustums. Subsequent calls to {@link #isVisible(Entity)} will return
-     * false until a Entity has been flagged as visible via
-     * {@link #setVisible(Entity, boolean)}.
-     * 
-     * @return This component, for chaining purposes
-     */
-    public Renderable resetVisibility() {
-        clearInternal();
-        return this;
-    }
-
-    /**
      * Return the local bounds of this Renderable. The returned AxisAlignedBox
      * instance is reused by this Renderable instance so it should be cloned
      * before changing which Component is referenced.

ferox-scene/src/main/java/com/ferox/scene/controller/VisibilityController.java

 package com.ferox.scene.controller;
 
-import java.util.Iterator;
-
 import com.ferox.math.Const;
 import com.ferox.math.bounds.AxisAlignedBox;
 import com.ferox.math.bounds.QueryCallback;
 import com.ferox.scene.Renderable;
 import com.ferox.util.Bag;
 import com.lhkbob.entreri.Entity;
+import com.lhkbob.entreri.EntitySystem;
 import com.lhkbob.entreri.Result;
 import com.lhkbob.entreri.SimpleController;
 
     public void process(double dt) {
         if (index != null) {
             for (FrustumResult f: frustums) {
-                VisibilityCallback query = new VisibilityCallback(f.getSource().getEntity());
+                VisibilityCallback query = new VisibilityCallback(getEntitySystem());
                 index.query(f.getFrustum(), query);
                 
                 // sort the PVS by entity id before reporting it so that
     @Override
     public void preProcess(double dt) {
         frustums = new Bag<FrustumResult>();
-        
-        // reset visibility
-        Iterator<Renderable> it = getEntitySystem().iterator(Renderable.ID);
-        while(it.hasNext()) {
-            it.next().resetVisibility();
-        }
     }
     
     @Override
     }
     
     private static class VisibilityCallback implements QueryCallback<Entity> {
-        private final Entity camera;
         private final Renderable renderable;
         
         private final Bag<Entity> pvs;
          * @param camera The Entity that will be flagged as visible
          * @throws NullPointerException if camera is null
          */
-        public VisibilityCallback(Entity camera) {
-            if (camera == null)
-                throw new NullPointerException("Entity cannot be null");
-            this.camera = camera;
-            renderable = camera.getEntitySystem().createDataInstance(Renderable.ID);
+        public VisibilityCallback(EntitySystem system) {
+            renderable = system.createDataInstance(Renderable.ID);
             pvs = new Bag<Entity>();
         }
         
         @Override
         public void process(Entity r, @Const AxisAlignedBox bounds) {
+            // using ComponentData to query existence is faster
+            // than pulling in the actual Component
             if (r.get(renderable)) {
-                renderable.setVisible(camera, true);
                 pvs.add(r);
             }
         }