package com.indyforge.twod.engine.graphics.rendering.scenegraph;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
import com.indyforge.twod.engine.graphics.rendering.scenegraph.util.iteration.ChildIterator;
import com.indyforge.twod.engine.graphics.rendering.scenegraph.util.iteration.ParentIterator;
import com.indyforge.twod.engine.graphics.rendering.scenegraph.util.iteration.RecursiveChildIterator;
import com.indyforge.twod.engine.graphics.rendering.scenegraph.util.iteration.RootFilter;
import com.indyforge.twod.engine.graphics.rendering.scenegraph.util.iteration.SiblingIterator;
import com.indyforge.twod.engine.util.iteration.ArrayIterator;
import com.indyforge.twod.engine.util.iteration.Filter;
import com.indyforge.twod.engine.util.iteration.FilteredIterator;
import com.indyforge.twod.engine.util.iteration.IterationRoutines;
import com.indyforge.twod.engine.util.iteration.SerializableIterable;
import com.indyforge.twod.engine.util.task.Task;
import com.indyforge.twod.engine.util.task.TaskQueue;
/**
*
* An entity is basically a node of a tree. Each entity (node) can have multiple
* children which are sorted by their indeces. This ensures modifiable event
* ordering. The entity-tree-concept is the key feature for processing
* hierarchically ordered data.
*
* @author Christopher Probst
* @see EntityFilter
*/
public class Entity implements Comparable<Entity>, Iterable<Entity>,
Serializable {
/**
* Used for tags.
*/
public static final Object TAG_VALUE = Void.TYPE;
/**
*
* The default entity events.
*
* @author Christopher Probst
*
*/
public enum EntityEvent {
Attached, Detached, Update
}
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Fires the event for the given entity.
*
* @param entity
* The entity which will receive the event.
*
* @param source
* The entity which fired the event or null.
* @param event
* The event.
* @param params
* The parameters.
*/
public static void fireEvent(Entity entity, Entity source, Object event,
Object... params) {
if (entity != null
&& (source == null || source.events().containsKey(event))) {
// Just call the method
entity.onEvent(source, event, params);
}
}
/**
* Fires the given event for all entities.
*
* @param entityIterator
* The entity iterator.
* @param source
* The entity which fired the event or null.
* @param event
* The event.
* @param params
* The parameters.
*/
public static void fireEvent(Iterator<? extends Entity> entityIterator,
Entity source, Object event, Object... params) {
if (entityIterator != null
&& (source == null || source.events().containsKey(event))) {
// Post event to all entities
while (entityIterator.hasNext()) {
// Just call the method
entityIterator.next().onEvent(source, event, params);
}
}
}
/*
* The name of this entity.
*/
private String name = super.toString();
/*
* The uuid of this entity which is the registration key. Used to identify
* entities on multiple platforms.
*/
private UUID registrationKey = UUID.randomUUID();
/*
* The ordering index of this entity.
*/
private int index = 0,
/*
* The index in the parent cache. This value will be set by the parent if
* the cache changes.
*/
cacheIndex = 0;
/*
* The parent entity which ones this entity.
*/
private Entity parent = null;
/*
* The map which contains all children. The children are stored in sets
* which are mapped to their indeces.
*/
private final NavigableMap<Integer, Set<Entity>> children = new TreeMap<Integer, Set<Entity>>();
/*
* This map is used to map the uuids of all (sub-) children. The concept of
* a registry is very important to identify single entities in a probably
* complex hierarchy.
*/
private final Map<UUID, Entity> registry = new HashMap<UUID, Entity>(),
readOnlyRegistry = Collections.unmodifiableMap(registry);
/*
* Used to create event iterators.
*/
private final Map<Object, Iterable<? extends Entity>> events = new HashMap<Object, Iterable<? extends Entity>>();
/*
* The properties of this entity.
*/
private final Map<Object, Object> props = new HashMap<Object, Object>();
/*
* Each entity can execute taskQueue before processing the update event.
*/
private final TaskQueue taskQueue = new TaskQueue(new LinkedList<Task>());
/*
* Used to cache children iterations.
*/
private List<Entity> cachedChildren = null;
/**
* Removes the given set if it is empty.
*
* @param order
* The order of the given set.
* @param entities
* The entity set.
*/
private void removeSetIfEmpty(int order, Set<Entity> entities) {
if (entities != null && entities.isEmpty()) {
// Try to remove the set from the map
Set<Entity> removedEntities = children.remove(order);
// If it is not the same set readd it (should never happen...)
if (removedEntities != entities) {
// Put again into map
children.put(order, removedEntities);
// Throw an exception
throw new IllegalArgumentException("You tried to remove "
+ "a set which does not represent the set which "
+ "is mapped to the given order");
}
}
}
/**
* Returns the lazy entity set of the given order. If there is no set yet a
* new one will be created.
*
* @param order
* The order.
* @return a valid set.
*/
private Set<Entity> lazyEntities(int order) {
// Try to get the entities
Set<Entity> entities = children.get(order);
// Lazy creation
if (entities == null) {
/*
* Always use linked-hash-set for better iteration performance and
* correct ordering.
*/
children.put(order, entities = new LinkedHashSet<Entity>());
}
return entities;
}
/**
* Clears the cached children.
*/
private void clearCachedChildren() {
cachedChildren = null;
}
/**
* OVERRIDE FOR CUSTOM BEHAVIOUR.
*
* This method is called when an event is fired.
*
* @param source
* The entity which fired the event or null.
* @param event
* The event.
* @param params
* The parameters.
*/
protected void onEvent(Entity source, Object event, Object... params) {
/*
* Process the default events.
*/
if (event instanceof EntityEvent) {
switch ((EntityEvent) event) {
case Attached:
// Convert
Entity parentPtr = (Entity) params[0],
childPtr = (Entity) params[1];
// Invoke default event
onEntityAttached(parentPtr, childPtr);
// Evaluate the other events...
if (this == parentPtr) {
onChildAttached(parentPtr, childPtr);
} else if (this == childPtr) {
onAttached(parentPtr, childPtr);
} else if (parent == childPtr) {
onParentAttached(parentPtr, childPtr);
}
break;
case Detached:
// Convert
parentPtr = (Entity) params[0];
childPtr = (Entity) params[1];
// Invoke default event
onEntityDetached(parentPtr, childPtr);
// Evaluate the other events...
if (this == parentPtr) {
onChildDetached(parentPtr, childPtr);
} else if (this == childPtr) {
onDetached(parentPtr, childPtr);
} else if (parent == childPtr) {
onParentDetached(parentPtr, childPtr);
}
break;
case Update:
// The tpf
float tpf = (Float) params[0];
// Execute all tasks of this entity
taskQueue.update(tpf);
// Invoke callback method
onUpdate(tpf);
}
}
}
/**
* OVERRIDE FOR CUSTOM BEHAVIOUR.
*
* This method is called when an entity was attached to an entity.
*
* @param parent
* The parent which attached the given child.
* @param child
* The child which has been attached.
*/
protected void onEntityAttached(Entity parent, Entity child) {
}
/**
* OVERRIDE FOR CUSTOM BEHAVIOUR.
*
* This method is called when this entity was attached to an entity.
*
* @param parent
* The parent which attached the given child.
* @param child
* The child which has been attached.
*/
protected void onAttached(Entity parent, Entity child) {
}
/**
* OVERRIDE FOR CUSTOM BEHAVIOUR.
*
* This method is called when a child is attached to this entity.
*
* @param parent
* The parent which attached the given child.
* @param child
* The child which has been attached.
*/
protected void onChildAttached(Entity parent, Entity child) {
}
/**
* OVERRIDE FOR CUSTOM BEHAVIOUR.
*
* This method is called when the parent of this entity was attached to an
* entity.
*
* @param parent
* The parent which attached the given child.
* @param child
* The child which has been attached.
*/
protected void onParentAttached(Entity parent, Entity child) {
}
/**
* OVERRIDE FOR CUSTOM BEHAVIOUR.
*
* This method is called when an entity was detached from an entity.
*
* @param parent
* The parent which detached the given child.
* @param child
* The child which has been detached.
*/
protected void onEntityDetached(Entity parent, Entity child) {
}
/**
* OVERRIDE FOR CUSTOM BEHAVIOUR.
*
* This method is called when this entity was detached from an entity.
*
* @param parent
* The parent which detached the given child.
* @param child
* The child which has been detached.
*/
protected void onDetached(Entity parent, Entity child) {
}
/**
* OVERRIDE FOR CUSTOM BEHAVIOUR.
*
* This method is called when a child is detached from this entity.
*
* @param parent
* The parent which detached the given child.
* @param child
* The child which has been detached.
*/
protected void onChildDetached(Entity parent, Entity child) {
}
/**
* OVERRIDE FOR CUSTOM BEHAVIOUR.
*
* This method is called when the parent of this entity was detached from an
* entity.
*
* @param parent
* The parent which detached the given child.
* @param child
* The child which has been detached.
*/
protected void onParentDetached(Entity parent, Entity child) {
}
/**
* OVERRIDE FOR CUSTOM UPDATE BEHAVIOUR.
*
* This method gets called every frame to update the state of this entity.
*
* @param tpf
* The time per frame in seconds. Used to interpolate
* time-sensitive data.
*/
protected void onUpdate(float tpf) {
}
public Entity() {
// Register the default entity events
events().put(EntityEvent.Attached, iterableChildren(true, true));
events().put(EntityEvent.Detached, iterableChildren(true, true));
events().put(EntityEvent.Update, iterableChildren(true, true));
/*
* Very important! Put this entity into the own map!
*/
registry.put(registrationKey, this);
}
/**
* @return the task queue.
*/
public TaskQueue taskQueue() {
return taskQueue;
}
/**
* @return the uuid of this entity which is the registration key.
*/
public UUID registrationKey() {
return registrationKey;
}
/**
* Sets the registration key of this entity. The new key will be checked to
* be valid (not already in use).
*
* @param registrationKey
* The new registration key.
* @return this for chaining.
*/
public Entity registrationKey(UUID registrationKey) {
if (registrationKey == null) {
throw new NullPointerException("registrationKey");
}
// Find root entity
Entity root = root();
// Lookup
Entity ptr = root.registry.get(registrationKey);
// Check the entity
if (ptr != null) {
/*
* The registration key already exists but is owned by an other
* entity.
*/
if (ptr != this) {
throw new IllegalArgumentException("Registration key "
+ registrationKey + " is already in use by " + ptr);
}
} else {
// Remove old registration
root.registry.remove(this.registrationKey);
// Put into registry and save the key
root.registry.put(this.registrationKey = registrationKey, this);
}
return this;
}
/**
* @return the registry of the root entity.
*/
public Map<UUID, Entity> registry() {
return root().readOnlyRegistry;
}
/**
* @return the name.
*/
public String name() {
return name;
}
/**
* Sets the name of this entity.
*
* @param name
* The new name.
* @return this for chaining.
*/
public Entity name(String name) {
if (name == null) {
throw new NullPointerException("name");
}
this.name = name;
return this;
}
/**
* @return true if this entity has children, otherwise false.
*/
public boolean hasChildren() {
return !children.isEmpty();
}
/**
* @return true if this entity has a parent, otherwise false.
*/
public boolean hasParent() {
return parent != null;
}
/**
* @param cacheIndex
* The cache index.
* @return the child which is stored at the given cache index.
*/
public Entity childAt(int cacheIndex) {
return children().get(cacheIndex);
}
/**
* @return a snapshot of all children sorted by their indeces as
* unmodifiable list.
*/
public List<Entity> children() {
// Do we already have created the cache?
if (cachedChildren == null) {
// Create a new list
cachedChildren = new ArrayList<Entity>();
// Used to give the children cache indeces
int counter = 0;
// Copy all entities to the list
for (Set<Entity> entities : children.values()) {
for (Entity entity : entities) {
// The cache index
cachedChildren.add(entity);
// Set the cache index
entity.cacheIndex = counter++;
}
}
// Make unmodifiable
cachedChildren = Collections.unmodifiableList(cachedChildren);
}
return cachedChildren;
}
/**
* @return the parent or null.
*/
public Entity parent() {
return parent;
}
/*
* (non-Javadoc)
*
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<Entity> iterator() {
return new ChildIterator(this, false);
}
/**
* {@link SiblingIterator#SiblingIterator(Entity)}
*/
public Iterator<Entity> siblingIterator() {
return new SiblingIterator(this);
}
/**
* {@link SiblingIterator#SiblingIterator(Entity, boolean)}
*/
public Iterator<Entity> siblingIterator(boolean includeParent) {
return new SiblingIterator(this, includeParent);
}
/**
*
* {@link ChildIterator#ChildIterator(Entity)}
* <p>
* OR
* <p>
* {@link RecursiveChildIterator#RecursiveChildIterator(Entity)}
*/
public Iterator<Entity> childIterator(boolean recursive) {
return recursive ? new RecursiveChildIterator(this)
: new ChildIterator(this);
}
/**
*
* {@link ChildIterator#ChildIterator(Entity, boolean)}
* <p>
* OR
* <p>
* {@link RecursiveChildIterator#RecursiveChildIterator(Entity, boolean)}
*/
public Iterator<Entity> childIterator(boolean includeThis, boolean recursive) {
return recursive ? new RecursiveChildIterator(this, includeThis)
: new ChildIterator(this, includeThis);
}
/**
* Wrapps {@link Entity#childIterator(boolean, boolean)}.
*
* @return iterable children.
*/
public SerializableIterable<Entity> iterableChildren(
final boolean includeThis, final boolean recursive) {
return new SerializableIterable<Entity>() {
/**
*
*/
private static final long serialVersionUID = 1L;
/*
* (non-Javadoc)
*
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<Entity> iterator() {
return childIterator(includeThis, recursive);
}
};
}
/**
* {@link ParentIterator#ParentIterator(Entity)}
*/
public Iterator<Entity> parentIterator() {
return new ParentIterator(this);
}
/**
* {@link ParentIterator#ParentIterator(Entity, boolean)}
*/
public Iterator<Entity> parentIterator(boolean includeThis) {
return new ParentIterator(this, includeThis);
}
/**
* @return true if this entity has no parent, otherwise false.
*/
public boolean isRoot() {
return parent == null;
}
/**
* @return the ordering index of this entity.
*/
public int index() {
return index;
}
/**
* @return true if and only if the cache index is completely valid.
*/
public boolean isCacheIndexValid() {
return parent != null && parent.cachedChildren != null
&& cacheIndex < parent.cachedChildren.size() ? parent.cachedChildren
.get(cacheIndex) == this : false;
}
/**
* @return the cache index of this entity.
*/
public int cacheIndex() {
return cacheIndex;
}
/**
* Sets the ordering index of this entity. If this entity is already
* attached the parent will sort its children again.
*
* @param index
* The new index.
* @return this for chaining.
*/
public Entity index(int index) {
// A new index ??
if (index != this.index) {
// Save the old index
int oldIndex = this.index;
// Is there already a parent ?
if (parent != null) {
// Lookup
Set<Entity> entities = parent.children.get(oldIndex);
// If the set exist
if (entities != null) {
// Remove this entity
if (!entities.remove(this)) {
throw new IllegalStateException("This entity could "
+ "not be removed from the old parent set. "
+ "Please check your code.");
} else if (!parent.lazyEntities(index).add(this)) {
throw new IllegalStateException("This entity could "
+ "not be added to the new parent set. "
+ "Please check your code.");
} else {
// Clear the client cache!
parent.clearCachedChildren();
}
// Clean up
parent.removeSetIfEmpty(oldIndex, entities);
} else {
throw new IllegalStateException("The parent set of this "
+ "entity is null. Please check your code.");
}
}
// Save
this.index = index;
}
return this;
}
/**
* @return the root entity (an entity which has no parent) of this entity or
* this entity if this entity has no parent (which means that this
* entity is a root entity).
*/
public Entity root() {
return findParent(RootFilter.INSTANCE, true);
}
/**
* @see IterationRoutines#next(Iterator)
* @see Entity#findParents(EntityFilter, boolean)
*/
public Entity findParent(Filter<? super Entity> entityFilter,
boolean includeThis) {
return IterationRoutines.next(findParents(entityFilter, includeThis));
}
/**
* Returns a filtered parent iterator.
*
* @param entityFilter
* The entity filter.
* @param includeThis
* If true, this entity will be part of the iteration, too.
* @return an iterator.
*/
public Iterator<Entity> findParents(Filter<? super Entity> entityFilter,
boolean includeThis) {
return new FilteredIterator<Entity>(entityFilter,
parentIterator(includeThis));
}
/**
* @see IterationRoutines#next(Iterator)
* @see Entity#findChildren(EntityFilter, boolean, boolean)
*/
public Entity findChild(Filter<? super Entity> entityFilter,
boolean includeThis, boolean recursive) {
return IterationRoutines.next(findChildren(entityFilter, includeThis,
recursive));
}
/**
* Returns a filtered children iterator.
*
* @param entityFilter
* The entity filter.
* @param includeThis
* If true, this entity will be part of the iteration, too.
* @param recursive
* If true, the sub-children will be part of the iteration, too.
* @return an iterator.
*/
public Iterator<Entity> findChildren(Filter<? super Entity> entityFilter,
boolean includeThis, boolean recursive) {
return new FilteredIterator<Entity>(entityFilter, childIterator(
includeThis, recursive));
}
/**
* @see IterationRoutines#next(Iterator)
* @see Entity#findSiblings(EntityFilter, boolean)
*/
public Entity findSibling(Filter<? super Entity> entityFilter,
boolean includeParent) {
return IterationRoutines
.next(findSiblings(entityFilter, includeParent));
}
/**
* Returns a filtered sibling iterator.
*
* @param entityFilter
* The entity filter.
* @param includeParent
* If true, the parent of the siblings will be part of the
* iteration, too.
* @return an iterator.
*/
public Iterator<Entity> findSiblings(Filter<? super Entity> entityFilter,
boolean includeParent) {
return new FilteredIterator<Entity>(entityFilter,
siblingIterator(includeParent));
}
/**
* @return the event-to-iterable map. An entity must declare the event types
* here first to be able to fire them.
*/
public Map<Object, Iterable<? extends Entity>> events() {
return events;
}
/**
* @param event
* The event you want to fire.
* @return a new iterator for the given event or null.
*/
public Iterator<? extends Entity> eventIterator(Object event) {
Iterable<? extends Entity> iterableEntities = events.get(event);
return iterableEntities != null ? iterableEntities.iterator() : null;
}
/**
* Fires the given event for all entities using this entity as source.
*
* @param event
* The event.
* @param params
* The parameters.
*/
public void fireEvent(Object event, Object... params) {
fireEvent(null, event, params);
}
/**
* Fires the given event for all entities using this entity as source.
*
* @param entityFilter
* The entity event filter or null.
* @param event
* The event.
* @param params
* The parameters.
*/
public void fireEvent(EntityFilter entityFilter, Object event,
Object... params) {
fireEvent(entityFilter != null ? new FilteredIterator<Entity>(
entityFilter, eventIterator(event)) : eventIterator(event),
this, event, params);
}
/**
* Attaches all children in the array to this entity. If these entities have
* already parents they are detached automatically and will be attached
* afterwards.
*
* @param children
* The entities you want to attach as children.
* @return this for chaining.
*/
public Entity attach(Entity... children) {
return attach((Iterator<Entity>) new ArrayIterator<Entity>(children));
}
/**
* Attaches all children given by the iterable interface to this entity. If
* these entities have already parents they are detached automatically and
* will be attached afterwards.
*
* @param children
* The entities you want to attach as children.
* @return this for chaining.
*/
public Entity attach(Iterable<Entity> children) {
if (children == null) {
throw new NullPointerException("children");
}
attach(children.iterator());
return this;
}
/**
* Attaches all children given by the iterator to this entity. If these
* entities have already parents they are detached automatically and will be
* attached afterwards.
*
* @param children
* The entities you want to attach as children.
* @return this for chaining.
*/
public Entity attach(Iterator<Entity> children) {
if (children == null) {
throw new NullPointerException("children");
}
// Iterate over the given children
while (children.hasNext()) {
// Attach the next child
attach(children.next());
}
return this;
}
/**
* Attaches a child to this entity. If this entity has already a parent it
* is detached automatically and will be attached afterwards.
*
* @param child
* The entity you want to attach as child.
* @return this for chaining.
*/
public Entity attach(Entity child) {
if (child == null) {
throw new NullPointerException("child");
} else if (child == this) {
throw new IllegalArgumentException("You cannot attach an entity "
+ "to it self");
} else if (child.parent != null) {
// If this entity is already the parent...
if (child.parent == this) {
return this;
} else if (!child.detach()) {
throw new IllegalStateException("Unable to detach child");
}
}
// Lookup entity set
Set<Entity> entities = lazyEntities(child.index());
if (entities.add(child)) {
// Lookup root
Entity root = root();
/*
* Very important! When we attach a child we have to copy its
* registry to OUR root entity.
*/
root.registry.putAll(child.registry);
// Clear the registry of the child
child.registry.clear();
// Set parent
child.parent = this;
// Cache is invalid now
clearCachedChildren();
// Fire the attached event for this entity
fireEvent(EntityEvent.Attached, this, child);
// Fire the attached event for the child
child.fireEvent(EntityEvent.Attached, this, child);
return this;
} else {
throw new IllegalStateException("This entity could not add the "
+ "given child to its set. Please check your code.");
}
}
/**
* @param typeClass
* The type of the property.
* @return the property with the given type.
*/
@SuppressWarnings("unchecked")
public <T> T typeProp(Class<T> typeClass) {
if (typeClass == null) {
throw new NullPointerException("typeClass");
}
return (T) props.get(typeClass);
}
/**
* Puts the given property. The class of the property is used as key.
*
* @param value
* The value you want to put.
* @return this for chaining.
*/
public Entity addTypeProp(Object value) {
if (value == null) {
throw new NullPointerException("value");
}
props.put(value.getClass(), value);
return this;
}
/**
* Removes the given property type.
*
* @param typeClass
* The type of the property.
* @return this for chaining.
*/
public Entity removeTypeProp(Class<?> typeClass) {
if (typeClass == null) {
throw new NullPointerException("typeClass");
}
props.remove(typeClass);
return this;
}
/**
* Puts the given property. If the key already exists the old value will be
* replaced.
*
* @param key
* The prop key.
* @param value
* The prop value.
* @return this for chaining.
*/
public Entity addProp(Object key, Object value) {
props.put(key, value);
return this;
}
/**
* Removes the given property.
*
* @param key
* The prop key.
* @return this for chaining.
*/
public Entity removeProp(Object key) {
props.remove(key);
return this;
}
/**
* @param key
* The key of the value you want to lookup.
* @return the value of the given key or null.
*/
public Object prop(Object key) {
return props.get(key);
}
/**
* @param key
* The key of the value you want to lookup.
* @param propType
* The type of the property.
* @return the (converted) value of the given key or null.
*/
@SuppressWarnings("unchecked")
public <T> T prop(Object key, Class<T> propType) {
return (T) prop(key);
}
/**
* @return the properties of this entity.
*/
public Map<Object, Object> props() {
return props;
}
/**
* Puts the given tag. If the key (tag) already exists the old value will be
* replaced.
*
* @param tag
* The tag.
* @return this for chaining.
*/
public Entity tag(Object tag) {
return addProp(tag, TAG_VALUE);
}
/**
* Removes the given tag.
*
* @param tag
* The tag.
* @return this for chaining.
*/
public Entity untag(Object tag) {
return removeProp(tag);
}
/**
* Tells whether or not the given tag exists.
*
* @param tag
* The tag you want to check.
* @return true if the tag exists, otherwise false.
*/
public boolean tagged(Object tag) {
return props.get(tag) == TAG_VALUE;
}
/**
* Detaches all children from this entity.
*/
public boolean detachAll() {
return detach(iterator());
}
/**
* Detaches all children in the array from this entity.
*
* @param children
* The entities you want to detach from this entity.
* @return true if all entities were children of this entity and are
* detached successfully, otherwise false.
*/
public boolean detach(Entity... children) {
return detach((Iterator<Entity>) new ArrayIterator<Entity>(children));
}
/**
* Detaches all children given by the iterable interface from this entity.
*
* @param children
* The entities you want to detach from this entity.
* @return true if all entities were children of this entity and are
* detached successfully, otherwise false.
*/
public boolean detach(Iterable<Entity> children) {
if (children == null) {
throw new NullPointerException("children");
}
return detach(children.iterator());
}
/**
* Detaches all children given by the iterator from this entity.
*
* @param children
* The entities you want to detach from this entity.
* @return true if all entities were children of this entity and are
* detached successfully, otherwise false.
*/
public boolean detach(Iterator<Entity> children) {
if (children == null) {
throw new NullPointerException("children");
}
boolean success = true;
// Iterate over the given children
while (children.hasNext()) {
// Detach the next child
if (!detach(children.next())) {
success = false;
}
}
return success;
}
/**
* Detaches this entity from its parent.
*
* @return true if there was a parent and this entity is detached
* successfully, otherwise false.
*/
public boolean detach() {
return parent != null ? parent.detach(this) : false;
}
/**
* Detaches a entity from this entity.
*
* @param child
* The entity you want to detach from this entity.
* @return true if the entity was a child of this entity and is detached
* successfully, otherwise false.
*/
public boolean detach(Entity child) {
if (child == null) {
throw new NullPointerException("child");
} else if (child == this) {
throw new IllegalArgumentException("You cannot detach an entity "
+ "from it self");
} else if (child.parent != this) {
return false;
}
// Lookup
Set<Entity> entities = children.get(child.index());
// Check
if (entities == null) {
throw new IllegalStateException("The entity set of the given "
+ "child order does not exist. Please check your code.");
} else if (entities.remove(child)) {
// Lookup root
Entity root = root();
/*
* Very important! When we detach a child we have to remove all
* (sub-) children of this child from the root registry and copy
* them to the child registry.
*/
for (Entity subChild : child.iterableChildren(true, true)) {
root.registry.remove(subChild.registrationKey);
child.registry.put(subChild.registrationKey, subChild);
}
// Clean up
removeSetIfEmpty(child.index(), entities);
// Clear parent
child.parent = null;
// Cache is invalid now
clearCachedChildren();
// Fire the detached event for this entity
fireEvent(EntityEvent.Detached, this, child);
// Fire the detached event for the child
child.fireEvent(EntityEvent.Detached, this, child);
return true;
} else {
throw new IllegalStateException("This entity could not remove "
+ "the given child from its set. Please check your code.");
}
}
/**
* Updates this entity and all children.
*
* @param tpf
* The passed time to update time-sensitive data.
* @return this for chaining.
*/
public Entity update(float tpf) {
return update(null, tpf);
}
/**
* Updates this entity and all children using the given filter.
*
* @param EntityFilter
* The entity filter or null.
* @param tpf
* The passed time to update time-sensitive data.
* @return this for chaining.
*/
public Entity update(EntityFilter entityFilter, float tpf) {
// Fire event for all children
fireEvent(entityFilter, EntityEvent.Update, tpf);
return this;
}
/*
* (non-Javadoc)
*
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(Entity o) {
if (index > o.index) {
return 1;
} else if (index == o.index) {
return 0;
} else {
return -1;
}
}
@Override
public String toString() {
return "[Name = " + name + ", Index = " + index + ", Properties = "
+ props + "]";
}
}