diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramRunner.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramRunner.java
index 5a81579b2..7b5338e1d 100644
--- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramRunner.java
+++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramRunner.java
@@ -52,7 +52,8 @@ public interface EntityProgramRunner {
/**
* Check whether the given program can be run to the given entity.
*
- * First checks {@link #canRunProgram(String)} and then {@link EntityProgram#canRunOn(Entity)}.
+ * First checks {@link #canRunProgram(String)} and then {@link EntityProgram#canRunOn(Entity)}. If the entity was
+ * running a program also checks if that program finished succesfully.
*
*
* @param program
@@ -86,6 +87,19 @@ public interface EntityProgramRunner {
*/
void run(String program, Entity entity);
+ /**
+ * Get the running program of a given entity.
+ *
+ * @param entity
+ * The entity to run the program on
+ * @return the information object for the running program
+ * @throws IllegalArgumentException
+ * if an argument is null
+ * @throws NoSuchElementException
+ * if no running program is found for the entity
+ */
+ RunningProgramInfo getRunningProgramInfo(Entity entity);
+
/**
* Force stop all running entity programs.
*
diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramState.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramState.java
index 3f6872618..34d95cf4b 100644
--- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramState.java
+++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/EntityProgramState.java
@@ -15,6 +15,8 @@
* @author Tim Neumann
*/
public enum EntityProgramState {
+ /** When the program has a program factory that creates new instances of this program. */
+ IS_FACTORY,
/** When the entity program is new and was not started yet. */
NEW,
/** When the entity program is currently running. */
diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/RunningProgramInfo.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/RunningProgramInfo.java
new file mode 100644
index 000000000..f873ad66b
--- /dev/null
+++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/entity/program/RunningProgramInfo.java
@@ -0,0 +1,32 @@
+/*
+ * This source file is part of the FIUS ICGE project.
+ * For more information see github.com/FIUS/ICGE2
+ *
+ * Copyright (c) 2019 the ICGE project authors.
+ *
+ * This software is available under the MIT license.
+ * SPDX-License-Identifier: MIT
+ */
+package de.unistuttgart.informatik.fius.icge.simulation.entity.program;
+
+import java.util.concurrent.CompletableFuture;
+
+
+/**
+ * Info object holding a running program.
+ *
+ * @author Fabian Bühler
+ */
+public interface RunningProgramInfo {
+
+ /**
+ * @return the state of this object; cannot be null
+ */
+ public EntityProgramState getState();
+
+ /**
+ * @return the future running the program
+ */
+ public CompletableFuture getFuture();
+
+}
diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRegistryEntry.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRegistryEntry.java
index 34cd6ac42..576629c1f 100644
--- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRegistryEntry.java
+++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRegistryEntry.java
@@ -22,7 +22,7 @@
*/
public class EntityProgramRegistryEntry {
private final String name;
- private final boolean single;
+ private final boolean isSingleInstance;
private final EntityProgram program;
private final Supplier programGenerator;
@@ -39,7 +39,7 @@ public class EntityProgramRegistryEntry {
public EntityProgramRegistryEntry(final String name, final EntityProgram program) {
if ((name == null) || (program == null)) throw new IllegalArgumentException("Argument cannot be null.");
this.name = name;
- this.single = true;
+ this.isSingleInstance = true;
this.program = program;
this.programGenerator = null;
}
@@ -57,7 +57,7 @@ public EntityProgramRegistryEntry(final String name, final EntityProgram program
public EntityProgramRegistryEntry(final String name, final Supplier programGenerator) {
if ((name == null) || (programGenerator == null)) throw new IllegalArgumentException("Argument cannot be null.");
this.name = name;
- this.single = false;
+ this.isSingleInstance = false;
this.program = null;
this.programGenerator = programGenerator;
}
@@ -69,6 +69,13 @@ public String getName() {
return this.name;
}
+ /**
+ * @return false iff the program has a factory to create new instances
+ */
+ public boolean isSingle() {
+ return this.isSingleInstance;
+ }
+
/**
* Get the program instance of this info.
*
@@ -78,7 +85,7 @@ public String getName() {
* @return the program instance
*/
public EntityProgram getProgram() {
- if (this.single) return this.program;
+ if (this.isSingleInstance) return this.program;
final EntityProgram prog = this.programGenerator.get();
if (prog == null) throw new IllegalStateException("Program Generator returned null.");
return prog;
@@ -86,7 +93,7 @@ public EntityProgram getProgram() {
@Override
public int hashCode() {
- if (this.single) return this.name.hashCode() + this.program.hashCode();
+ if (this.isSingleInstance) return this.name.hashCode() + this.program.hashCode();
return this.name.hashCode() + this.programGenerator.hashCode();
}
@@ -94,9 +101,9 @@ public int hashCode() {
public boolean equals(final Object obj) {
if (!(obj instanceof EntityProgramRegistryEntry)) return false;
final EntityProgramRegistryEntry other = (EntityProgramRegistryEntry) obj;
- if (this.single != other.single) return false;
+ if (this.isSingleInstance != other.isSingleInstance) return false;
if (!this.name.equals(other.name)) return false;
- if (this.single) return this.program.equals(other.program);
+ if (this.isSingleInstance) return this.program.equals(other.program);
return this.programGenerator.equals(other.programGenerator);
}
diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRunningInfo.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRunningInfo.java
index b2b8b3283..f36b18102 100644
--- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRunningInfo.java
+++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/EntityProgramRunningInfo.java
@@ -9,8 +9,11 @@
*/
package de.unistuttgart.informatik.fius.icge.simulation.internal.entity.program;
+import java.util.concurrent.CompletableFuture;
+
import de.unistuttgart.informatik.fius.icge.simulation.entity.program.EntityProgram;
import de.unistuttgart.informatik.fius.icge.simulation.entity.program.EntityProgramState;
+import de.unistuttgart.informatik.fius.icge.simulation.entity.program.RunningProgramInfo;
/**
@@ -18,10 +21,11 @@
*
* @author Tim Neumann
*/
-public class EntityProgramRunningInfo {
- private EntityProgramState state;
- private final EntityProgram program;
- private Thread thread;
+public class EntityProgramRunningInfo implements RunningProgramInfo {
+
+ private EntityProgramState state;
+ private final EntityProgram program;
+ private CompletableFuture future;
/**
* Initialize
@@ -38,9 +42,7 @@ public EntityProgramRunningInfo(final EntityProgram program) {
this.state = EntityProgramState.NEW;
}
- /**
- * @return the state of this object; cannot be null
- */
+ @Override
public EntityProgramState getState() {
return this.state;
}
@@ -67,23 +69,17 @@ public EntityProgram getProgram() {
return this.program;
}
- /**
- * Get the thread of this object
- *
- * @return the thread of this object; can be null
- */
- public Thread getThread() {
- return this.thread;
+ @Override
+ public CompletableFuture getFuture() {
+ return this.future;
}
/**
- * Set the thread of this object
- *
- * @param thread
- * the new thread; may be null
+ * @param future
+ * the future to set; may be null
*/
- public void setThread(final Thread thread) {
- this.thread = thread;
+ public void setFuture(CompletableFuture future) {
+ this.future = future;
}
}
diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRegistry.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRegistry.java
index bfd408376..65304d743 100644
--- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRegistry.java
+++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRegistry.java
@@ -67,4 +67,17 @@ public EntityProgram getEntityProgram(final String name) {
return this.programs.get(name).getProgram();
}
+ /**
+ * Get the full program entry.
+ *
+ * @param name
+ * the program name
+ * @return iff the program has a factory to create new instances
+ */
+ public boolean checkIfProgramHasFactory(final String name) {
+ if (name == null) throw new IllegalArgumentException("An argument is null.");
+ if (!this.programs.containsKey(name)) throw new NoSuchElementException();
+ return !this.programs.get(name).isSingle();
+ }
+
}
diff --git a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java
index 0c90cedf9..490fa6542 100644
--- a/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java
+++ b/ICGE-Simulation/src/main/java/de/unistuttgart/informatik/fius/icge/simulation/internal/entity/program/StandardEntityProgramRunner.java
@@ -11,11 +11,18 @@
import java.util.HashMap;
import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory;
+import java.util.concurrent.ForkJoinWorkerThread;
+import de.unistuttgart.informatik.fius.icge.log.Logger;
import de.unistuttgart.informatik.fius.icge.simulation.entity.Entity;
-import de.unistuttgart.informatik.fius.icge.simulation.entity.program.EntityProgramRegistry;
import de.unistuttgart.informatik.fius.icge.simulation.entity.program.EntityProgramRunner;
import de.unistuttgart.informatik.fius.icge.simulation.entity.program.EntityProgramState;
+import de.unistuttgart.informatik.fius.icge.simulation.entity.program.RunningProgramInfo;
import de.unistuttgart.informatik.fius.icge.simulation.exception.CannotRunProgramException;
import de.unistuttgart.informatik.fius.icge.simulation.exception.UncheckedInterruptedException;
@@ -27,9 +34,25 @@
*/
public class StandardEntityProgramRunner implements EntityProgramRunner {
- private final EntityProgramRegistry registry;
+ private final StandardEntityProgramRegistry registry;
- private final Map programs = new HashMap<>();
+ private final ExecutorService executor;
+
+ private final Map singlePrograms = new HashMap<>();
+ private final Map entityPrograms = new HashMap<>();
+
+ private ExecutorService createExecutor() {
+ final ForkJoinWorkerThreadFactory factory = new ForkJoinWorkerThreadFactory() {
+ @Override
+ public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
+ final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
+ worker.setName("EntityProgramRunnerThread-" + worker.getPoolIndex());
+ return worker;
+ }
+ };
+
+ return new ForkJoinPool(Runtime.getRuntime().availableProcessors(), factory, null, false);
+ }
/**
* Create a new StandardEntityProgramRunner.
@@ -37,21 +60,37 @@ public class StandardEntityProgramRunner implements EntityProgramRunner {
* @param registry
* The EntityProgramRegistry to use
*/
- public StandardEntityProgramRunner(final EntityProgramRegistry registry) {
+ public StandardEntityProgramRunner(final StandardEntityProgramRegistry registry) {
this.registry = registry;
+ this.executor = createExecutor();
+ }
+
+ private EntityProgramRunningInfo getSingleInstanceProgramInfo(final String programName) {
+ if (!this.singlePrograms.containsKey(programName)) {
+ if (!this.registry.checkIfProgramHasFactory(programName)) {
+ this.singlePrograms.put(programName, new EntityProgramRunningInfo(this.registry.getEntityProgram(programName)));
+ }
+ }
+ return this.singlePrograms.get(programName);
}
- private EntityProgramRunningInfo getInfo(final String program) {
- if (!this.programs.containsKey(program)) {
- this.programs.put(program, new EntityProgramRunningInfo(this.registry.getEntityProgram(program)));
+ private EntityProgramRunningInfo getProgramInfo(final String programName) {
+ final EntityProgramRunningInfo programInfo = this.getSingleInstanceProgramInfo(programName);
+ if (programInfo != null) return programInfo;
+ if (this.registry.checkIfProgramHasFactory(programName)) {
+ return new EntityProgramRunningInfo(this.registry.getEntityProgram(programName));
}
- return this.programs.get(program);
+ throw new NoSuchElementException("No Program registered with the name \"" + programName + "\"!");
}
@Override
public EntityProgramState getState(final String program) {
if (program == null) throw new IllegalArgumentException("Argument is null.");
- return this.getInfo(program).getState();
+ EntityProgramRunningInfo singleProgramInstance = this.getSingleInstanceProgramInfo(program);
+ if (singleProgramInstance != null) return singleProgramInstance.getState();
+ boolean hasFactory = this.registry.checkIfProgramHasFactory(program);
+ if (hasFactory) return EntityProgramState.IS_FACTORY;
+ throw new IllegalStateException("Program should either have a specific instance or be instantiated by a factory!");
}
private boolean canRunProgram(final EntityProgramRunningInfo info) {
@@ -61,10 +100,17 @@ private boolean canRunProgram(final EntityProgramRunningInfo info) {
@Override
public boolean canRunProgram(final String program) {
if (program == null) throw new IllegalArgumentException("Argument is null.");
- return this.canRunProgram(this.getInfo(program));
+ return this.canRunProgram(this.getProgramInfo(program));
+ }
+
+ public boolean entityCanRunProgram(final Entity entity) {
+ EntityProgramRunningInfo oldInfo = this.entityPrograms.get(entity);
+ // only allow new programs to run on the entity if the last finished without exception!
+ return (oldInfo == null) || oldInfo.getState().equals(EntityProgramState.FINISHED);
}
private boolean canRunProgramOn(final EntityProgramRunningInfo info, final Entity entity) {
+ if (!this.entityCanRunProgram(entity)) return false;
if (!this.canRunProgram(info)) return false;
return info.getProgram().canRunOn(entity);
}
@@ -72,36 +118,54 @@ private boolean canRunProgramOn(final EntityProgramRunningInfo info, final Entit
@Override
public boolean canRunProgramOn(final String program, final Entity entity) {
if ((program == null) || (entity == null)) throw new IllegalArgumentException("Argument is null.");
- return this.canRunProgramOn(this.getInfo(program), entity);
+ return this.canRunProgramOn(this.getProgramInfo(program), entity);
}
@Override
public void run(final String program, final Entity entity) {
if ((program == null) || (entity == null)) throw new IllegalArgumentException("Argument is null.");
- final var info = this.getInfo(program);
+ final var info = this.getProgramInfo(program);
if (!this.canRunProgramOn(info, entity)) throw new CannotRunProgramException();
- final String threadName = "EntityProgramRunner" + "_" + program + "_on_" + entity.toString();
- final Thread thread = new Thread(() -> {
+ CompletableFuture future = CompletableFuture.supplyAsync(() -> {
try {
info.getProgram().run(entity);
info.setState(EntityProgramState.FINISHED);
} catch (@SuppressWarnings("unused") final UncheckedInterruptedException e) {
info.setState(EntityProgramState.KILLED);
+ } catch (Exception e) {
+ Logger.simulation.println("----------------------------------------------");
+ Logger.simulation
+ .println("The following exception happened in program " + program + " running on entity " + entity.toString());
+ e.printStackTrace(Logger.simulation);
+ Logger.simulation.println("----------------------------------------------");
+ info.setState(EntityProgramState.KILLED);
}
- }, threadName);
+ return null;
+ }, this.executor);
- info.setThread(thread);
+ info.setFuture(future);
info.setState(EntityProgramState.RUNNING);
- thread.start();
+
+ // set the running program in the entityMap
+ this.entityPrograms.put(entity, info);
+ }
+
+ @Override
+ public RunningProgramInfo getRunningProgramInfo(Entity entity) {
+ if (entity == null) throw new IllegalArgumentException("Entity cannot be null!");
+ if (
+ !this.entityPrograms.containsKey(entity)
+ ) throw new NoSuchElementException("No running program for entity " + entity.toString() + "found!");
+ return this.entityPrograms.get(entity);
}
@Override
public void forceStop() {
- for (final EntityProgramRunningInfo info : this.programs.values()) {
- info.getThread().interrupt();
+ for (final EntityProgramRunningInfo info : this.entityPrograms.values()) {
+ info.getFuture().cancel(true);
}
}