package jalse; import static jalse.actions.Actions.requireNotStopped; import static jalse.tags.Tags.setCreated; import static jalse.tags.Tags.setRootDepth; import static jalse.tags.Tags.setRootMember; import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import java.util.stream.Stream; import jalse.actions.Action; import jalse.actions.ActionBindings; import jalse.actions.ActionContext; import jalse.actions.ActionEngine; import jalse.actions.DefaultActionScheduler; import jalse.actions.ForkJoinActionEngine; import jalse.actions.ManualActionEngine; import jalse.actions.SchedulableActionContext; import jalse.actions.ThreadPoolActionEngine; import jalse.attributes.AttributeContainer; import jalse.entities.DefaultEntityContainer; import jalse.entities.DefaultEntityFactory; import jalse.entities.Entities; import jalse.entities.Entity; import jalse.entities.EntityContainer; import jalse.entities.EntityFactory; import jalse.entities.EntityListener; import jalse.misc.AbstractIdentifiable; import jalse.tags.Created; import jalse.tags.Tag; import jalse.tags.TagTypeSet; import jalse.tags.TreeDepth; import jalse.tags.TreeMember; /** * A {@link JALSE} implementation that using default implementations.<br> * <br> * DefaultJALSE uses {@link DefaultEntityFactory} (with no {@link Entity} limit) and * {@link ForkJoinActionEngine#commonPoolEngine()} by default if no {@link EntityFactory} or * {@link ActionEngine} are provided.<br> * * @author Elliot Ford * * @see #addTags() * */ public class DefaultJALSE extends AbstractIdentifiable implements JALSE { /** * A {@link DefaultJALSE} instance builder that uses the defined {@link ActionEngine} * implementation with {@link DefaultEntityFactory}.<br> * <br> * By default this builder uses the common pool ({@link ForkJoinActionEngine#commonPoolEngine()} * ), has a random ID and has no entity limit. * * @author Elliot Ford * * * @see ForkJoinActionEngine * @see ThreadPoolActionEngine * @see ManualActionEngine * */ public static final class Builder { private enum EngineType { COMMON, FORKJOIN, MANUAL, THREADPOOL } private static final int MINIMUM_PARALLALISM = 1; private static final Supplier<UUID> RANDOM_ID_SUPPLIER = UUID::randomUUID; private EngineType engineType; private Supplier<UUID> idSupplier; private int parallelism; private int totalEntityLimit; /** * Creates a new Builder instance. */ public Builder() { idSupplier = RANDOM_ID_SUPPLIER; parallelism = MINIMUM_PARALLALISM; totalEntityLimit = Integer.MAX_VALUE; engineType = EngineType.COMMON; } /** * Builds an instance of JALSE with the supplied parameters. * * @return Newly created JALSE instance. */ public DefaultJALSE build() throws IllegalStateException { ActionEngine engine = null; switch (engineType) { case COMMON: engine = ForkJoinActionEngine.commonPoolEngine(); break; case MANUAL: engine = new ManualActionEngine(); break; case THREADPOOL: engine = new ThreadPoolActionEngine(parallelism); break; case FORKJOIN: engine = new ForkJoinActionEngine(parallelism); break; default: /* * Engine must not be null. */ throw new IllegalStateException(); } return new DefaultJALSE(idSupplier.get(), engine, new DefaultEntityFactory(totalEntityLimit)); } /** * Users the common engine based on the common pool. * * @return This builder. * * @see ForkJoinPool#commonPool() * @see ForkJoinActionEngine#commonPoolEngine() */ public Builder setCommonPoolEngine() { engineType = EngineType.COMMON; return this; } /** * Sets fork join engine to be used. * * @return This builder. * * @see ForkJoinActionEngine */ public Builder setForkJoinEngine() { engineType = EngineType.FORKJOIN; return this; } /** * Sets the unique ID for JALSE instance. * * @param id * ID of JALSE. * @return This builder. */ public Builder setID(final UUID id) { idSupplier = () -> id; return this; } /** * Sets the engine to be a manual tick engine. * * @return This builder. */ public Builder setManualEngine() { engineType = EngineType.MANUAL; return this; } /** * Sets there to be no entity limit. * * @return This builder. * * @see Integer#MAX_VALUE */ public Builder setNoEntityLimit() { totalEntityLimit = Integer.MAX_VALUE; return this; } /** * Sets the parallelism to be utilised by the engine. * * @param parallelism * Thread parallelism. * @return This builder. * @throws IllegalArgumentException */ public Builder setParallelism(final int parallelism) { if (parallelism < MINIMUM_PARALLALISM) { throw new IllegalArgumentException( String.format("Parallelism must be %d or above", MINIMUM_PARALLALISM)); } this.parallelism = parallelism; return this; } /** * Sets the parallelism to the available processors. * * @return This builder. * * @see Runtime#availableProcessors() */ public Builder setParallelismToProcessors() { parallelism = Runtime.getRuntime().availableProcessors(); return this; } /** * Sets the ID to a random one. * * @return This builder. * * @see UUID#randomUUID() */ public Builder setRandomID() { idSupplier = RANDOM_ID_SUPPLIER; return this; } /** * Sets to use a single thread. * * @return This builder. */ public Builder setSingleThread() { parallelism = MINIMUM_PARALLALISM; return this; } /** * Sets thread pool engine to be used. * * @return This builder. * * @see ThreadPoolActionEngine */ public Builder setThreadPoolEngine() { engineType = EngineType.THREADPOOL; return this; } /** * Sets the total entity limit parameter. * * @param totalEntityLimit * Maximum entity limited. * @return This builder. * @throws IllegalArgumentException */ public Builder setTotalEntityLimit(final int totalEntityLimit) { if (totalEntityLimit <= 0) { throw new IllegalArgumentException("Total entity limit must be positive"); } this.totalEntityLimit = totalEntityLimit; return this; } } /** * Action engine to be supplied to entities. */ protected final ActionEngine engine; /** * Backing entity container for top level entities. */ protected final DefaultEntityContainer entities; /** * Entity factory for creating/killing entities. */ protected final EntityFactory factory; /** * Self action scheduler. */ protected final DefaultActionScheduler<JALSE> scheduler; /** * Current state information. */ protected final TagTypeSet tags; /** * Creates a new instance of DefaultJALSE with the supplied engine and factory. * * @param id * The ID used to identify between JALSE instances. * * @param engine * Action engine to associate to factory and schedule actions. * @param factory * Entity factory to create/kill child entities. * * @see #addTags() * */ public DefaultJALSE(final UUID id, final ActionEngine engine, final EntityFactory factory) { super(id); this.engine = requireNotStopped(engine); this.factory = Objects.requireNonNull(factory); factory.setEngine(engine); scheduler = new DefaultActionScheduler<>(this); scheduler.setEngine(engine); entities = new DefaultEntityContainer(factory, this); tags = new TagTypeSet(); addTags(); } @Override public boolean addEntityListener(final EntityListener listener) { return entities.addEntityListener(listener); } /** * Sets the default tags. * * @see Created * @see TreeMember * @see TreeDepth */ protected void addTags() { setCreated(tags); setRootMember(tags); setRootDepth(tags); } @Override public void cancelAllScheduledForActor() { scheduler.cancelAllScheduledForActor(); } @Override public ActionBindings getBindings() { return engine.getBindings(); } @Override public Entity getEntity(final UUID id) { return entities.getEntity(id); } @Override public int getEntityCount() { return entities.getEntityCount(); } @Override public Set<UUID> getEntityIDs() { return entities.getEntityIDs(); } @Override public Set<? extends EntityListener> getEntityListeners() { return entities.getEntityListeners(); } @Override public Set<UUID> getIDsInTree() { return Entities.getEntityIDsRecursively(entities); } @Override public <T extends Tag> Set<T> getTagsOfType(final Class<T> type) { return tags.getOfType(type); } @Override public int getTreeCount() { return Entities.getEntityCountRecursively(entities); } @Override public boolean isPaused() { return engine.isPaused(); } @Override public boolean isStopped() { return engine.isStopped(); } @Override public void killEntities() { entities.killEntities(); } @Override public boolean killEntity(final UUID id) { return entities.killEntity(id); } @Override public <T> SchedulableActionContext<T> newContext(final Action<T> action) { return engine.newContext(action); } @Override public SchedulableActionContext<JALSE> newContextForActor(final Action<JALSE> action) { return scheduler.newContextForActor(action); } @Override public Entity newEntity(final UUID id, final AttributeContainer sourceContainer) { return entities.newEntity(id, sourceContainer); } @Override public <T extends Entity> T newEntity(final UUID id, final Class<T> type, final AttributeContainer sourceContainer) { return entities.newEntity(id, type, sourceContainer); } @Override public void pause() { engine.pause(); } @Override public boolean receiveEntity(final Entity e) { return entities.receiveEntity(e); } @Override public boolean removeEntityListener(final EntityListener listener) { return entities.removeEntityListener(listener); } @Override public void removeEntityListeners() { entities.removeEntityListeners(); } @Override public void resume() { engine.resume(); } @Override public ActionContext<JALSE> scheduleForActor(final Action<JALSE> action, final long initialDelay, final long period, final TimeUnit unit) { return scheduler.scheduleForActor(action, initialDelay, period, unit); } @Override public void stop() { engine.stop(); } @Override public Stream<Entity> streamEntities() { return entities.streamEntities(); } @Override public Stream<Entity> streamEntityTree() { return Entities.walkEntities(entities); } @Override public Stream<Tag> streamTags() { return tags.stream(); } @Override public boolean transferEntity(final UUID id, final EntityContainer destination) { return entities.transferEntity(id, destination); } }