package jalse.entities;
import static jalse.actions.Actions.requireNotStopped;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger;
import jalse.actions.ActionEngine;
import jalse.actions.Actions;
import jalse.actions.ForkJoinActionEngine;
/**
* A {@link EntityFactory} implementation that creates/kills {@link DefaultEntity}. Default entity
* factory can have a total entity limit set. When this factory kills an entity it will kill the
* entity tree under it (can only kill entities his factory has created).<br>
* <br>
* This factory assumes all source containers (and when importing target containers) are genuine.
* <br>
* <br>
* If no {@link ActionEngine} is supplied {@link ForkJoinActionEngine#commonPoolEngine()} will be
* used.
*
* @author Elliot Ford
*
*/
public class DefaultEntityFactory implements EntityFactory {
private static final Logger logger = Logger.getLogger(DefaultEntityFactory.class.getName());
private final int entityLimit;
private final Set<UUID> entityIDs;
private ActionEngine engine;
private final Lock read;
private final Lock write;
/**
* Creates a default entity factory with no entity limit.
*/
public DefaultEntityFactory() {
this(Integer.MAX_VALUE);
}
/**
* Creates a default entity factory with the supplied entity limit.
*
* @param entityLimit
* Maximum entity limit.
*/
public DefaultEntityFactory(final int entityLimit) {
if (entityLimit <= 0) {
throw new IllegalArgumentException();
}
this.entityLimit = entityLimit;
entityIDs = new HashSet<>();
engine = ForkJoinActionEngine.commonPoolEngine(); // Defaults use common engine
final ReadWriteLock rwLock = new ReentrantReadWriteLock();
read = rwLock.readLock();
write = rwLock.writeLock();
}
@Override
public void exportEntity(final Entity e) {
final UUID eID = e.getID();
write.lock();
try {
if (!entityIDs.remove(eID)) {
throw new IllegalArgumentException(String.format("Does not know of entity %s", eID));
}
final ActionEngine emptyEngine = Actions.emptyActionEngine();
final DefaultEntity de = (DefaultEntity) e;
de.cancelAllScheduledForActor();
de.setEngine(emptyEngine);
de.setContainer(null); // Remove parent reference.
Entities.walkEntities(e).map(DefaultEntity.class::cast).forEach(ce -> {
entityIDs.remove(ce.getID());
ce.cancelAllScheduledForActor();
ce.setEngine(emptyEngine);
});
logger.fine(String.format("Entity %s exported", eID));
} finally {
write.unlock();
}
}
/**
* Gets the associated engine.
*
* @return Action engine.
*/
public ActionEngine getEngine() {
read.lock();
try {
return engine;
} finally {
read.unlock();
}
}
/**
* Gets the current total entity count.
*
* @return Entity count.
*/
public int getEntityCount() {
read.lock();
try {
return entityIDs.size();
} finally {
read.unlock();
}
}
/**
* Gets the total entity limit.
*
* @return Entity limit.
*/
public int getEntityLimit() {
return entityLimit;
}
/**
* This is a hook for extending this factory to allow this factory to maintain subclasses of
* {@link DefaultEntity}. The code for this is equivalent to:
* {@code new DefaultEntity(id, this, target)}.
*
* @param id
* ID of the entity.
* @param target
* Parent container.
* @return Newly created default entity.
*/
protected DefaultEntity newDefaultEntity(final UUID id, final EntityContainer target) {
return new DefaultEntity(id, this, target);
}
@Override
public DefaultEntity newEntity(final UUID id, final EntityContainer target) {
Objects.requireNonNull(id);
Objects.requireNonNull(target);
write.lock();
try {
if (entityIDs.size() >= entityLimit) {
throw new IllegalStateException(String.format("Entity limit of %d has been reached", entityLimit));
}
// Unique only
if (!entityIDs.add(id)) {
throw new IllegalArgumentException(String.format("Entity %s is already associated", id));
}
final DefaultEntity e = newDefaultEntity(id, target);
e.setEngine(engine);
e.markAsAlive();
logger.fine(String.format("Entity %s created", id));
return e;
} finally {
write.unlock();
}
}
@Override
public void setEngine(final ActionEngine engine) {
Objects.requireNonNull(engine);
write.lock();
try {
logger.fine(String.format("Switching engine type %s to %s", this.engine.getClass(), engine.getClass()));
this.engine = requireNotStopped(engine);
} finally {
write.unlock();
}
}
@Override
public String toString() {
return "DefaultEntityFactory [entityLimit=" + entityLimit + ", entityCount=" + getEntityCount() + "]";
}
@Override
public boolean tryImportEntity(final Entity e, final EntityContainer target) {
final UUID eID = e.getID();
write.lock();
try {
if (!entityIDs.add(eID)) {
return false;
}
final DefaultEntity de = (DefaultEntity) e;
de.setEngine(engine);
de.setContainer(target);
Entities.walkEntities(de).map(DefaultEntity.class::cast).forEach(ve -> {
entityIDs.add(eID);
ve.setEngine(engine);
});
logger.fine(String.format("Entity %s imported", eID));
return true;
} finally {
write.unlock();
}
}
@Override
public boolean tryKillEntity(final Entity e) {
final UUID eID = e.getID();
write.lock();
try {
final DefaultEntity de = (DefaultEntity) e;
if (!entityIDs.remove(eID) || !de.isAlive()) { // Kill only those in need
return false;
}
de.markAsDead();
de.cancelAllScheduledForActor();
de.setEngine(null);
de.killEntities(); // Kill tree
logger.fine(String.format("Entity %s killed", eID));
return true;
} finally {
write.unlock();
}
}
@Override
public boolean tryTakeFromTree(final Entity e, final EntityContainer target) {
final UUID eID = e.getID();
write.lock();
try {
if (!entityIDs.contains(eID)) {
return false;
}
((DefaultEntity) e).setContainer(target);
logger.fine(String.format("Entity %s taken from tree", eID));
return true;
} finally {
write.unlock();
}
}
@Override
public boolean withinSameTree(final EntityContainer source, final EntityContainer target) {
Objects.requireNonNull(source);
Objects.requireNonNull(target);
read.lock();
try {
return Entities.withinSameTree(source, target);
} finally {
read.unlock();
}
}
}