Commits

Michael Ludwig committed 9b44b3f

Implement object ownership

  • Participants
  • Parent commits fa669dc

Comments (0)

Files changed (10)

src/main/java/com/lhkbob/entreri/Attribute.java

 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
+// FIXME document
 @Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.ANNOTATION_TYPE)

src/main/java/com/lhkbob/entreri/Attributes.java

 import java.util.HashMap;
 import java.util.Map;
 
+// FIXME document
 public class Attributes {
     private final Map<Class<? extends Annotation>, Annotation> attrs;
 

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

  * @author Michael Ludwig
  * @param <T> The ComponentData type defining the data of this component
  */
-public final class Component<T extends ComponentData<T>> {
+public final class Component<T extends ComponentData<T>> implements Ownable, Owner {
     private final ComponentRepository<T> owner;
 
+    final OwnerSupport delegate;
+
     int index;
 
     /**
     Component(ComponentRepository<T> owner, int index) {
         this.owner = owner;
         this.index = index;
+        delegate = new OwnerSupport(this);
     }
 
     /**
     ComponentRepository<T> getRepository() {
         return owner;
     }
+
+    @Override
+    public void notifyOwnershipGranted(Ownable obj) {
+        delegate.notifyOwnershipGranted(obj);
+    }
+
+    @Override
+    public void notifyOwnershipRevoked(Ownable obj) {
+        delegate.notifyOwnershipRevoked(obj);
+    }
+
+    @Override
+    public void setOwner(Owner owner) {
+        delegate.setOwner(owner);
+    }
+
+    @Override
+    public Owner getOwner() {
+        return delegate.getOwner();
+    }
 }

src/main/java/com/lhkbob/entreri/ComponentRepository.java

      */
     private Component<T> allocateComponent(int entityIndex) {
         if (entityIndexToComponentRepository[entityIndex] != 0) {
-            removeComponent(entityIndex);
+            if (!removeComponent(entityIndex)) {
+                // could not remove it because it was owned, so we abort
+                // the addition
+                return null;
+            }
         }
 
         int componentIndex = componentInsert++;
         // This code works even if componentIndex is 0
         Component<T> oldComponent = components[componentIndex];
         if (oldComponent != null) {
-            oldComponent.index = 0;
+            if (oldComponent.getOwner() != null) {
+                // halt removal
+                return false;
+            } else {
+                oldComponent.delegate.disownAndRemoveChildren();
+                oldComponent.index = 0;
+            }
         }
 
         components[componentIndex] = null;

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

  * 
  * @author Michael Ludwig
  */
-public final class Entity implements Iterable<Component<?>>, Comparable<Entity> {
+public final class Entity implements Iterable<Component<?>>, Comparable<Entity>, Ownable, Owner {
     private final EntitySystem system;
     private final int id;
 
+    final OwnerSupport delegate;
+
     int index;
 
     /**
         this.system = system;
         this.index = index;
         this.id = id;
+
+        delegate = new OwnerSupport(this);
     }
 
     /**
 
     /**
      * <p>
-     * Add a new Component with a data type T to this Entity. If the Entity
-     * already has component of type T attached, that component is removed and a
-     * new one is created. Otherwise, a new instance is created with its default
-     * values and added to the system.
+     * Add a new Component with a data type T to this Entity. If there already
+     * exists a component of type T and it is not owned, it is removed first,
+     * and a new one is instantiated. If there is an existing component that is
+     * owned, it is not removed and the addition fails. To designate this, null
+     * is returned.
      * </p>
      * 
      * @param <T> The parameterized type of component being added
      * @param componentType The component type
-     * @return A new component of type T
+     * @return A new component of type T, or null if there was an existing
+     *         component of type T and it is owned
      * @throws NullPointerException if componentId is null
      */
     public <T extends ComponentData<T>> Component<T> add(Class<T> componentType) {
      * Add a new Component with a data of type T to this Entity, but the new
      * component's state will be cloned from the given Component instance. The
      * <tt>toClone</tt> instance must still be live. If there already exists a
-     * component of type T in this entity, it is removed first, and a new one is
-     * instantiated.
+     * component of type T and it is not owned, it is removed first, and a new
+     * one is instantiated. If there is an existing component that is owned, it
+     * is not removed and the addition fails. To designate this, null is
+     * returned.
      * </p>
      * <p>
      * The new component is initialized by cloning the property values from
      * 
      * @param <T> The parameterized type of component to add
      * @param toClone The existing T to clone when attaching to this component
-     * @return A new component of type T
+     * @return A new component of type T, or null if there was an existing
+     *         component of type T and it is owned
      * @throws NullPointerException if toClone is null
      * @throws IllegalArgumentException if toClone is not from the same system
      *             as this entity
     }
 
     /**
+     * <p>
      * Remove any attached Component with the data type, T, from this Entity.
      * True is returned if a component was removed, and false otherwise. If a
      * component is removed, the component should no longer be used and it will
      * return false from {@link Component#isLive()}. This will remove the
      * component even if the component has been disabled.
+     * <p>
+     * However, if the component is has a non-null owner, it is not removed and
+     * false is returned. When a component is removed, all entities and
+     * components that it owns are also removed.
      * 
      * @param <T> The parameterized type of component to remove
      * @param componentType The component type
-     * @return True if a component was removed
+     * @return True if a component was removed, false if there was no component
+     *         or if the component has an owner
      * @throws NullPointerException if componentId is null
      */
     public <T extends ComponentData<T>> boolean remove(Class<T> componentType) {
     public int compareTo(Entity o) {
         return id - o.id;
     }
+
+    @Override
+    public void notifyOwnershipGranted(Ownable obj) {
+        delegate.notifyOwnershipGranted(obj);
+    }
+
+    @Override
+    public void notifyOwnershipRevoked(Ownable obj) {
+        delegate.notifyOwnershipRevoked(obj);
+    }
+
+    @Override
+    public void setOwner(Owner owner) {
+        delegate.setOwner(owner);
+    }
+
+    @Override
+    public Owner getOwner() {
+        return delegate.getOwner();
+    }
 }

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

     }
 
     /**
+     * <p>
      * Remove the given entity from this system. The entity and its attached
      * components are removed from the system. This will cause the entity and
      * its components to no longer be alive. Any ComponentData's referencing the
      * entity's components will become invalid until assigned to a new
      * component.
+     * <p>
+     * If the entity has a non-null owner, the removal does not occur and false
+     * is returned. When an entity is removed, all entities and components that
+     * it owns are also removed.
      * 
      * @param e The entity to remove
+     * @return True if the entity was removed, false if the entity has an owner
+     *         preventing its removal
      * @throws NullPointerException if e is null
-     * @throws IllegalArgumentException if the entity is not owned by this
+     * @throws IllegalArgumentException if the entity was not created by this
      *             system, or already removed
      */
-    public void removeEntity(Entity e) {
+    public boolean removeEntity(Entity e) {
         if (e == null) {
             throw new NullPointerException("Cannot remove a null entity");
         }
             throw new IllegalArgumentException("Entity has already been removed");
         }
 
-        // Remove all components from the entity
+        if (e.getOwner() != null) {
+            // already owned, so abort
+            return false;
+        }
+
+        // Handle ownership removals
+        e.delegate.disownAndRemoveChildren();
+
+        // Remove all components from the entity (that weren't removed
+        // by ownership rules)
         for (int i = 0; i < componentRepositories.length; i++) {
             if (componentRepositories[i] != null) {
                 componentRepositories[i].removeComponent(e.index);
         // clear out the entity
         entities[e.index] = null;
         e.index = 0;
+
+        return true;
     }
 
     /**

src/main/java/com/lhkbob/entreri/Ownable.java

+package com.lhkbob.entreri;
+
+public interface Ownable {
+    public void setOwner(Owner owner);
+
+    public Owner getOwner();
+}

src/main/java/com/lhkbob/entreri/Owner.java

+package com.lhkbob.entreri;
+
+
+public interface Owner {
+    public void notifyOwnershipGranted(Ownable obj);
+
+    public void notifyOwnershipRevoked(Ownable obj);
+}

src/main/java/com/lhkbob/entreri/OwnerSupport.java

+package com.lhkbob.entreri;
+
+import java.util.HashSet;
+import java.util.Set;
+
+class OwnerSupport {
+    private final Ownable target;
+    private final Set<Ownable> ownedObjects;
+    private Owner currentOwner;
+
+    public OwnerSupport(Ownable target) {
+        this.target = target;
+        ownedObjects = new HashSet<Ownable>();
+        currentOwner = null;
+    }
+
+    public void notifyOwnershipGranted(Ownable obj) {
+        ownedObjects.add(obj);
+    }
+
+    public void notifyOwnershipRevoked(Ownable obj) {
+        ownedObjects.remove(obj);
+    }
+
+    public void setOwner(Owner owner) {
+        if (currentOwner != null) {
+            currentOwner.notifyOwnershipRevoked(target);
+        }
+        currentOwner = owner;
+        if (owner != null) {
+            owner.notifyOwnershipGranted(target);
+        }
+    }
+
+    public Owner getOwner() {
+        return currentOwner;
+    }
+
+    public void disownAndRemoveChildren() {
+        // Mark all owned objects as not owned
+        // if they are an entity or component, recurse and remove them as well
+        Set<Ownable> cloned = new HashSet<Ownable>(ownedObjects);
+        for (Ownable owned : cloned) {
+            owned.setOwner(null);
+            if (owned instanceof Entity) {
+                Entity ownedEntity = (Entity) owned;
+                ownedEntity.getEntitySystem().removeEntity(ownedEntity);
+            } else if (owned instanceof Component) {
+                Component<?> ownedComp = (Component<?>) owned;
+                ownedComp.getEntity().remove(ownedComp.getType());
+            }
+        }
+    }
+}

src/main/java/com/lhkbob/entreri/Requires.java

+package com.lhkbob.entreri;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Documented
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Requires {
+    Class<? extends ComponentData<?>>[] value();
+}