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); } }