diff --git a/AMAKFX/src/fr/irit/smac/amak/Agent.java b/AMAKFX/src/fr/irit/smac/amak/Agent.java index 9685e065123e3ae277e7482e37e39789ce88c9f1..5166056c042ff55d44110d5d52733cc3cc798a0f 100644 --- a/AMAKFX/src/fr/irit/smac/amak/Agent.java +++ b/AMAKFX/src/fr/irit/smac/amak/Agent.java @@ -1,496 +1,518 @@ -package fr.irit.smac.amak; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import fr.irit.smac.amak.Amas.ExecutionPolicy; -import fr.irit.smac.amak.tools.Log; - -/** - * This class must be overridden by all agents - * - * @author Alexandre Perles - * - * @param <A> - * The kind of Amas the agent refers to - * @param <E> - * The kind of Environment the agent AND the Amas refer to - */ -public abstract class Agent<A extends Amas<E>, E extends Environment> implements Runnable { - /** - * Neighborhood of the agent (must refer to the same couple amas, environment - */ - protected final List<Agent<A, E>> neighborhood; - /** - * Criticalities of the neighbors (and it self) as perceived at the beginning of - * the agent's cycle - */ - protected final Map<Agent<A, E>, Double> criticalities = new HashMap<>(); - /** - * Last calculated criticality of the agent - */ - private double criticality; - /** - * Amas the agent belongs to - */ - protected final A amas; - /** - * Unique index to give unique id to each agent - */ - private static int uniqueIndex; - - /** - * The id of the agent - */ - private final int id; - /** - * The order of execution of the agent as computed by - * {@link Agent#_computeExecutionOrder()} - */ - private int executionOrder; - /** - * The parameters that can be user in the initialization process - * {@link Agent#onInitialization()} - */ - protected Object[] params; - - /** - * These phases are used to synchronize agents on phase - * - * @see fr.irit.smac.amak.Amas.ExecutionPolicy - * @author perles - * - */ - public enum Phase { - /** - * Agent is perceiving - */ - PERCEPTION, - /** - * Agent is deciding and acting - */ - DECISION_AND_ACTION, - /** - * Agent haven't started to perceive, decide or act - */ - INITIALIZING, - /** - * Agent is ready to decide - */ - PERCEPTION_DONE, - /** - * Agent is ready to perceive or die - */ - DECISION_AND_ACTION_DONE - } - - /** - * The current phase of the agent {@link Phase} - */ - protected Phase currentPhase = Phase.INITIALIZING; - - private boolean synchronous = true; - - /** - * The constructor automatically add the agent to the corresponding amas and - * initialize the agent - * - * @param amas - * Amas the agent belongs to - * @param params - * The params to initialize the agent - */ - public Agent(A amas, Object... params) { - this.id = uniqueIndex++; - this.params = params; - this.amas = amas; - neighborhood = new ArrayList<>(); - neighborhood.add(this); - onInitialization(); - if (!Configuration.commandLineMode) - onRenderingInitialization(); - - - if (amas != null) { - this.amas._addAgent(this); - } - } - - /** - * Add neighbors to the agent - * - * @param agents - * The list of agent that should be considered as neighbor - */ - @SafeVarargs - public final void addNeighbor(Agent<A, E>... agents) { - for (Agent<A, E> agent : agents) { - if (agent != null) { - neighborhood.add(agent); - criticalities.put(agent, Double.NEGATIVE_INFINITY); - } - } - } - - /** - * This method must be overridden by the agents. This method shouldn't make any - * calls to internal representation an agent has on its environment because - * these information maybe outdated. - * - * @return the criticality at a given moment - */ - protected double computeCriticality() { - return Double.NEGATIVE_INFINITY; - } - - protected void setAsynchronous() { - if (currentPhase != Phase.INITIALIZING) - Log.defaultLog.fatal("AMAK", "Asynchronous mode must be set during the initialization"); - this.synchronous = false; - } - /** - * This method must be overriden if you need to specify an execution order layer - * - * @return the execution order layer - */ - protected int computeExecutionOrderLayer() { - return 0; - } - - /** - * This method is called at the beginning of an agent's cycle - */ - protected void onAgentCycleBegin() { - - } - - /** - * This method is called at the end of an agent's cycle - */ - protected void onAgentCycleEnd() { - - } - - /** - * This method corresponds to the perception phase of the agents and must be - * overridden - */ - protected void onPerceive() { - - } - - /** - * This method corresponds to the decision phase of the agents and must be - * overridden - */ - protected void onDecide() { - - } - - /** - * This method corresponds to the action phase of the agents and must be - * overridden - */ - protected void onAct() { - - } - - /** - * In this method the agent should expose some variables with its neighbor - */ - protected void onExpose() { - - } - - /** - * This method should be used to update the representation of the agent for - * example in a VUI - */ - public void onUpdateRender() { - - } - - /** - * This method is now deprecated and should be replaced by onUpdateRender - * - * @deprecated Must be replaced by {@link #onUpdateRender()} - */ - @Deprecated - protected final void onDraw() { - - } - - /** - * Called when all initial agents have been created and are ready to be started - */ - protected void onReady() { - - } - - /** - * Called by the framework when all initial agents have been created and are - * almost ready to be started - */ - protected final void _onBeforeReady() { - criticality = computeCriticality(); - executionOrder = _computeExecutionOrder(); - } - - /** - * Called before all agents are created - */ - protected void onInitialization() { - - } - - /** - * Replaced by onInitialization - * - * @deprecated Must be replaced by {@link #onInitialization()} - */ - @Deprecated - protected final void onInitialize() { - - } - - /** - * Called to initialize the rendering of the agent - */ - protected void onRenderingInitialization() { - - } - - /** - * @deprecated This method is useless because the state of the agent is not - * supposed to evolve before or after its cycle. Use - * OnAgentCycleBegin/End instead. - * - * This method is final because it must not be implemented. - * Implement it will have no effect. - */ - @Deprecated - protected final void onSystemCycleBegin() { - - } - - /** - * @deprecated This method is useless because the state of the agent is not - * supposed to evolve before or after its cycle. Use - * OnAgentCycleBegin/End instead. - * - * This method is final because it must not be implemented. - * Implement it will have no effect. - */ - @Deprecated - protected final void onSystemCycleEnd() { - - } - - /** - * This method is called automatically and corresponds to a full cycle of an - * agent - */ - @Override - public void run() { - ExecutionPolicy executionPolicy = amas.getExecutionPolicy(); - if (executionPolicy == ExecutionPolicy.TWO_PHASES) { - - currentPhase = nextPhase(); - switch (currentPhase) { - case PERCEPTION: - phase1(); - amas.informThatAgentPerceptionIsFinished(); - break; - case DECISION_AND_ACTION: - phase2(); - amas.informThatAgentDecisionAndActionAreFinished(); - break; - default: - Log.defaultLog.fatal("AMAK", "An agent is being run in an invalid phase (%s)", currentPhase); - } - } else if (executionPolicy == ExecutionPolicy.ONE_PHASE) { - onePhaseCycle(); - amas.informThatAgentPerceptionIsFinished(); - amas.informThatAgentDecisionAndActionAreFinished(); - } - } - public void onePhaseCycle() { - currentPhase = Phase.PERCEPTION; - phase1(); - currentPhase = Phase.DECISION_AND_ACTION; - phase2(); - } - /** - * This method represents the perception phase of the agent - */ - protected final void phase1() { - onAgentCycleBegin(); - perceive(); - currentPhase = Phase.PERCEPTION_DONE; - } - - /** - * This method represents the decisionAndAction phase of the agent - */ - protected final void phase2() { - decideAndAct(); - executionOrder = _computeExecutionOrder(); - onExpose(); - if (!Configuration.commandLineMode) - onUpdateRender(); - onAgentCycleEnd(); - currentPhase = Phase.DECISION_AND_ACTION_DONE; - } - - /** - * Determine which phase comes after another - * - * @return the next phase - */ - private Phase nextPhase() { - switch (currentPhase) { - case PERCEPTION_DONE: - return Phase.DECISION_AND_ACTION; - case INITIALIZING: - case DECISION_AND_ACTION_DONE: - default: - return Phase.PERCEPTION; - } - } - - /** - * Compute the execution order from the layer and a random value. This method - * shouldn't be overridden. - * - * @return A number used by amak to determine which agent executes first - */ - protected int _computeExecutionOrder() { - return computeExecutionOrderLayer() * 10000 + amas.getEnvironment().getRandom().nextInt(10000); - } - - /** - * Perceive, decide and act - */ - void perceive() { - for (Agent<A, E> agent : neighborhood) { - criticalities.put(agent, agent.criticality); - } - onPerceive(); - // Criticality of agent should be updated after perception AND after action - criticality = computeCriticality(); - criticalities.put(this, criticality); - } - - /** - * A combination of decision and action as called by the framework - */ - private final void decideAndAct() { - onDecideAndAct(); - - criticality = computeCriticality(); - } - - /** - * Decide and act These two phases can often be grouped - */ - protected void onDecideAndAct() { - onDecide(); - onAct(); - } - - /** - * Convenient method giving the most critical neighbor at a given moment - * - * @param includingMe - * Should the agent also consider its own criticality - * @return the most critical agent - */ - protected final Agent<A, E> getMostCriticalNeighbor(boolean includingMe) { - List<Agent<A, E>> criticalest = new ArrayList<>(); - double maxCriticality = Double.NEGATIVE_INFINITY; - - if (includingMe) { - criticalest.add(this); - maxCriticality = criticalities.getOrDefault(criticalest, Double.NEGATIVE_INFINITY); - } - for (Entry<Agent<A, E>, Double> e : criticalities.entrySet()) { - if (e.getValue() > maxCriticality) { - criticalest.clear(); - maxCriticality = e.getValue(); - criticalest.add(e.getKey()); - } else if (e.getValue() == maxCriticality) { - criticalest.add(e.getKey()); - } - } - if (criticalest.isEmpty()) - return null; - return criticalest.get(getEnvironment().getRandom().nextInt(criticalest.size())); - } - - /** - * Get the latest computed execution order - * - * @return the execution order - */ - public int getExecutionOrder() { - return executionOrder; - } - - /** - * Getter for the AMAS - * - * @return the amas - */ - public A getAmas() { - return amas; - } - - /** - * Remove the agent from the system - */ - public void destroy() { - getAmas()._removeAgent(this); - } - - /** - * Agent toString - */ - @Override - public String toString() { - return String.format("Agent #%d", id); - } - - /** - * Getter for the current phase of the agent - * - * @return the current phase - */ - public Phase getCurrentPhase() { - return currentPhase; - } - - /** - * Return the id of the agent - * - * @return the id of the agent - */ - public int getId() { - return id; - } - - /** - * Getter for the environment - * - * @return the environment - */ - public E getEnvironment() { - return getAmas().getEnvironment(); - } - - public boolean isSynchronous() { - return synchronous ; - } -} +package fr.irit.smac.amak; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import fr.irit.smac.amak.Amas.ExecutionPolicy; +import fr.irit.smac.amak.tools.Log; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; + +/** + * This class must be overridden by all agents + * + * @author Alexandre Perles + * + * @param <A> + * The kind of Amas the agent refers to + * @param <E> + * The kind of Environment the agent AND the Amas refer to + */ +public abstract class Agent<A extends Amas<E>, E extends Environment> implements Runnable { + + public AmasMultiUIWindow amasMultiUIWindow; + + /** + * Neighborhood of the agent (must refer to the same couple amas, environment + */ + protected final List<Agent<A, E>> neighborhood; + /** + * Criticalities of the neighbors (and it self) as perceived at the beginning of + * the agent's cycle + */ + protected final Map<Agent<A, E>, Double> criticalities = new HashMap<>(); + /** + * Last calculated criticality of the agent + */ + private double criticality; + /** + * Amas the agent belongs to + */ + protected final A amas; + /** + * Unique index to give unique id to each agent + */ + private static int uniqueIndex; + + /** + * The id of the agent + */ + private final int id; + /** + * The order of execution of the agent as computed by + * {@link Agent#_computeExecutionOrder()} + */ + private int executionOrder; + /** + * The parameters that can be user in the initialization process + * {@link Agent#onInitialization()} + */ + protected Object[] params; + + /** + * These phases are used to synchronize agents on phase + * + * @see fr.irit.smac.amak.Amas.ExecutionPolicy + * @author perles + * + */ + public enum Phase { + /** + * Agent is perceiving + */ + PERCEPTION, + /** + * Agent is deciding and acting + */ + DECISION_AND_ACTION, + /** + * Agent haven't started to perceive, decide or act + */ + INITIALIZING, + /** + * Agent is ready to decide + */ + PERCEPTION_DONE, + /** + * Agent is ready to perceive or die + */ + DECISION_AND_ACTION_DONE + } + + /** + * The current phase of the agent {@link Phase} + */ + protected Phase currentPhase = Phase.INITIALIZING; + + private boolean synchronous = true; + + /** + * The constructor automatically add the agent to the corresponding amas and + * initialize the agent + * + * @param amas + * Amas the agent belongs to + * @param params + * The params to initialize the agent + */ + public Agent(A amas, Object... params) { + this.id = uniqueIndex++; + this.params = params; + this.amas = amas; + neighborhood = new ArrayList<>(); + neighborhood.add(this); + onInitialization(); + if (!Configuration.commandLineMode) + onRenderingInitialization(); + + + if (amas != null) { + this.amas._addAgent(this); + } + } + + public Agent(AmasMultiUIWindow window, A amas, Object... params) { + amasMultiUIWindow = window; + + this.id = uniqueIndex++; + this.params = params; + this.amas = amas; + neighborhood = new ArrayList<>(); + neighborhood.add(this); + onInitialization(); + if (!Configuration.commandLineMode) + onRenderingInitialization(); + + + if (amas != null) { + this.amas._addAgent(this); + } + } + + /** + * Add neighbors to the agent + * + * @param agents + * The list of agent that should be considered as neighbor + */ + @SafeVarargs + public final void addNeighbor(Agent<A, E>... agents) { + for (Agent<A, E> agent : agents) { + if (agent != null) { + neighborhood.add(agent); + criticalities.put(agent, Double.NEGATIVE_INFINITY); + } + } + } + + /** + * This method must be overridden by the agents. This method shouldn't make any + * calls to internal representation an agent has on its environment because + * these information maybe outdated. + * + * @return the criticality at a given moment + */ + protected double computeCriticality() { + return Double.NEGATIVE_INFINITY; + } + + protected void setAsynchronous() { + if (currentPhase != Phase.INITIALIZING) + Log.defaultLog.fatal("AMAK", "Asynchronous mode must be set during the initialization"); + this.synchronous = false; + } + /** + * This method must be overriden if you need to specify an execution order layer + * + * @return the execution order layer + */ + protected int computeExecutionOrderLayer() { + return 0; + } + + /** + * This method is called at the beginning of an agent's cycle + */ + protected void onAgentCycleBegin() { + + } + + /** + * This method is called at the end of an agent's cycle + */ + protected void onAgentCycleEnd() { + + } + + /** + * This method corresponds to the perception phase of the agents and must be + * overridden + */ + protected void onPerceive() { + + } + + /** + * This method corresponds to the decision phase of the agents and must be + * overridden + */ + protected void onDecide() { + + } + + /** + * This method corresponds to the action phase of the agents and must be + * overridden + */ + protected void onAct() { + + } + + /** + * In this method the agent should expose some variables with its neighbor + */ + protected void onExpose() { + + } + + /** + * This method should be used to update the representation of the agent for + * example in a VUI + */ + public void onUpdateRender() { + + } + + /** + * This method is now deprecated and should be replaced by onUpdateRender + * + * @deprecated Must be replaced by {@link #onUpdateRender()} + */ + @Deprecated + protected final void onDraw() { + + } + + /** + * Called when all initial agents have been created and are ready to be started + */ + protected void onReady() { + + } + + /** + * Called by the framework when all initial agents have been created and are + * almost ready to be started + */ + protected final void _onBeforeReady() { + criticality = computeCriticality(); + executionOrder = _computeExecutionOrder(); + } + + /** + * Called before all agents are created + */ + protected void onInitialization() { + + } + + /** + * Replaced by onInitialization + * + * @deprecated Must be replaced by {@link #onInitialization()} + */ + @Deprecated + protected final void onInitialize() { + + } + + /** + * Called to initialize the rendering of the agent + */ + protected void onRenderingInitialization() { + + } + + /** + * @deprecated This method is useless because the state of the agent is not + * supposed to evolve before or after its cycle. Use + * OnAgentCycleBegin/End instead. + * + * This method is final because it must not be implemented. + * Implement it will have no effect. + */ + @Deprecated + protected final void onSystemCycleBegin() { + + } + + /** + * @deprecated This method is useless because the state of the agent is not + * supposed to evolve before or after its cycle. Use + * OnAgentCycleBegin/End instead. + * + * This method is final because it must not be implemented. + * Implement it will have no effect. + */ + @Deprecated + protected final void onSystemCycleEnd() { + + } + + /** + * This method is called automatically and corresponds to a full cycle of an + * agent + */ + @Override + public void run() { + ExecutionPolicy executionPolicy = amas.getExecutionPolicy(); + if (executionPolicy == ExecutionPolicy.TWO_PHASES) { + + currentPhase = nextPhase(); + switch (currentPhase) { + case PERCEPTION: + phase1(); + amas.informThatAgentPerceptionIsFinished(); + break; + case DECISION_AND_ACTION: + phase2(); + amas.informThatAgentDecisionAndActionAreFinished(); + break; + default: + Log.defaultLog.fatal("AMAK", "An agent is being run in an invalid phase (%s)", currentPhase); + } + } else if (executionPolicy == ExecutionPolicy.ONE_PHASE) { + onePhaseCycle(); + amas.informThatAgentPerceptionIsFinished(); + amas.informThatAgentDecisionAndActionAreFinished(); + } + } + public void onePhaseCycle() { + currentPhase = Phase.PERCEPTION; + phase1(); + currentPhase = Phase.DECISION_AND_ACTION; + phase2(); + } + /** + * This method represents the perception phase of the agent + */ + protected final void phase1() { + onAgentCycleBegin(); + perceive(); + currentPhase = Phase.PERCEPTION_DONE; + } + + /** + * This method represents the decisionAndAction phase of the agent + */ + protected final void phase2() { + decideAndAct(); + executionOrder = _computeExecutionOrder(); + onExpose(); + if (!Configuration.commandLineMode) + onUpdateRender(); + onAgentCycleEnd(); + currentPhase = Phase.DECISION_AND_ACTION_DONE; + } + + /** + * Determine which phase comes after another + * + * @return the next phase + */ + private Phase nextPhase() { + switch (currentPhase) { + case PERCEPTION_DONE: + return Phase.DECISION_AND_ACTION; + case INITIALIZING: + case DECISION_AND_ACTION_DONE: + default: + return Phase.PERCEPTION; + } + } + + /** + * Compute the execution order from the layer and a random value. This method + * shouldn't be overridden. + * + * @return A number used by amak to determine which agent executes first + */ + protected int _computeExecutionOrder() { + return computeExecutionOrderLayer() * 10000 + amas.getEnvironment().getRandom().nextInt(10000); + } + + /** + * Perceive, decide and act + */ + void perceive() { + for (Agent<A, E> agent : neighborhood) { + criticalities.put(agent, agent.criticality); + } + onPerceive(); + // Criticality of agent should be updated after perception AND after action + criticality = computeCriticality(); + criticalities.put(this, criticality); + } + + /** + * A combination of decision and action as called by the framework + */ + private final void decideAndAct() { + onDecideAndAct(); + + criticality = computeCriticality(); + } + + /** + * Decide and act These two phases can often be grouped + */ + protected void onDecideAndAct() { + onDecide(); + onAct(); + } + + /** + * Convenient method giving the most critical neighbor at a given moment + * + * @param includingMe + * Should the agent also consider its own criticality + * @return the most critical agent + */ + protected final Agent<A, E> getMostCriticalNeighbor(boolean includingMe) { + List<Agent<A, E>> criticalest = new ArrayList<>(); + double maxCriticality = Double.NEGATIVE_INFINITY; + + if (includingMe) { + criticalest.add(this); + maxCriticality = criticalities.getOrDefault(criticalest, Double.NEGATIVE_INFINITY); + } + for (Entry<Agent<A, E>, Double> e : criticalities.entrySet()) { + if (e.getValue() > maxCriticality) { + criticalest.clear(); + maxCriticality = e.getValue(); + criticalest.add(e.getKey()); + } else if (e.getValue() == maxCriticality) { + criticalest.add(e.getKey()); + } + } + if (criticalest.isEmpty()) + return null; + return criticalest.get(getEnvironment().getRandom().nextInt(criticalest.size())); + } + + /** + * Get the latest computed execution order + * + * @return the execution order + */ + public int getExecutionOrder() { + return executionOrder; + } + + /** + * Getter for the AMAS + * + * @return the amas + */ + public A getAmas() { + return amas; + } + + /** + * Remove the agent from the system + */ + public void destroy() { + getAmas()._removeAgent(this); + } + + /** + * Agent toString + */ + @Override + public String toString() { + return String.format("Agent #%d", id); + } + + /** + * Getter for the current phase of the agent + * + * @return the current phase + */ + public Phase getCurrentPhase() { + return currentPhase; + } + + /** + * Return the id of the agent + * + * @return the id of the agent + */ + public int getId() { + return id; + } + + /** + * Getter for the environment + * + * @return the environment + */ + public E getEnvironment() { + return getAmas().getEnvironment(); + } + + public boolean isSynchronous() { + return synchronous ; + } +} diff --git a/AMAKFX/src/fr/irit/smac/amak/Amas.java b/AMAKFX/src/fr/irit/smac/amak/Amas.java index a73d0e5107ef13fc638c7e17037858d93ae44c5d..a92400870625b739704f5ba05350fdf1c119fe35 100644 --- a/AMAKFX/src/fr/irit/smac/amak/Amas.java +++ b/AMAKFX/src/fr/irit/smac/amak/Amas.java @@ -13,6 +13,7 @@ import java.util.stream.Collectors; import fr.irit.smac.amak.tools.Log; import fr.irit.smac.amak.tools.RunLaterHelper; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; import fr.irit.smac.amak.ui.MainWindow; import fr.irit.smac.amak.ui.SchedulerToolbar; import fr.irit.smac.amak.ui.VUI; @@ -26,6 +27,9 @@ import fr.irit.smac.amak.ui.VUI; * The environment of the MAS */ public class Amas<E extends Environment> implements Schedulable { + + public AmasMultiUIWindow amasMultiUIWindow; + /** * List of agents present in the system */ @@ -155,6 +159,36 @@ public class Amas<E extends Environment> implements Schedulable { this.onRenderingInitialization(); this.scheduler.unlock(); } + + public Amas(AmasMultiUIWindow window, E environment, Scheduling scheduling, Object... params) { + + amasMultiUIWindow = window; + executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(Configuration.allowedSimultaneousAgentsExecution); + if (scheduling == Scheduling.DEFAULT) { + //MainWindow.instance(); + this.scheduler = Scheduler.getDefaultScheduler(window); + this.scheduler.add(this); + } else { + this.scheduler = new Scheduler(this); + if (scheduling == Scheduling.UI && !Configuration.commandLineMode) { + //MainWindow.instance(); + amasMultiUIWindow.addToolbar(new SchedulerToolbar("Amas #" + id, getScheduler())); + } + } + + this.scheduler.lock(); + this.params = params; + this.environment = environment; + this.onInitialConfiguration(); + executionPolicy = Configuration.executionPolicy; + this.onInitialAgentsCreation(); + + addPendingAgents(); + this.onReady(); + if (!Configuration.commandLineMode) + this.onRenderingInitialization(); + this.scheduler.unlock(); + } /** * The method in which the rendering initialization should be made. For example, @@ -371,7 +405,12 @@ public class Amas<E extends Environment> implements Schedulable { * {@link Amas#onRenderingInitialization} */ protected void onUpdateRender() { - VUI.get().updateCanvas(); + if(Configuration.multiUI) { + VUI.get(amasMultiUIWindow).updateCanvas(); + }else { + VUI.get().updateCanvas(); + } + } /** diff --git a/AMAKFX/src/fr/irit/smac/amak/Configuration.java b/AMAKFX/src/fr/irit/smac/amak/Configuration.java index bc8050f8b87e296f7497acf1932764c02508c4f2..34c301cc8ae3334a7c8f49476db23e17af8a6b65 100644 --- a/AMAKFX/src/fr/irit/smac/amak/Configuration.java +++ b/AMAKFX/src/fr/irit/smac/amak/Configuration.java @@ -47,4 +47,6 @@ public class Configuration { * By default AMAK will wait for 1 sec before updating the plots */ public static double plotMilliSecondsUpdate = 1000; + + public static boolean multiUI = false; } diff --git a/AMAKFX/src/fr/irit/smac/amak/Environment.java b/AMAKFX/src/fr/irit/smac/amak/Environment.java index 68433e456f0c7b95e24b646a47e8806d67dd4666..1d87b8eee2a96555e1660c6d1c96462832512f58 100644 --- a/AMAKFX/src/fr/irit/smac/amak/Environment.java +++ b/AMAKFX/src/fr/irit/smac/amak/Environment.java @@ -1,145 +1,168 @@ -package fr.irit.smac.amak; - -import java.util.Random; - -import fr.irit.smac.amak.ui.MainWindow; -import fr.irit.smac.amak.ui.SchedulerToolbar; - -/** - * This class must be overridden by environments - * - * @author Alexandre Perles - * - */ -public abstract class Environment implements Schedulable { - - /** - * Unique index to give unique id to each environment - */ - private static int uniqueIndex; - - /** - * The id of the environment - */ - private final int id = uniqueIndex++; - - /** - * The parameters that are passed to {@link Environment#onInitialization()} - */ - protected Object[] params; - /** - * Random object common to the amas - */ - private Random random = new Random(); - /** - * The scheduler of the environment - */ - private Scheduler scheduler; - - /** - * Constructor - * - * @param _scheduling - * The scheduling of the environment - * @param params - * The parameters to initialize the environment - */ - public Environment(Scheduling _scheduling, Object... params) { - if (_scheduling == Scheduling.DEFAULT) { - this.scheduler = Scheduler.getDefaultScheduler(); - this.scheduler.add(this); - } else { - this.scheduler = new Scheduler(this); - if (_scheduling == Scheduling.UI && !Configuration.commandLineMode) - MainWindow.addToolbar(new SchedulerToolbar("Environment #" + id, getScheduler())); - } - this.scheduler.lock(); - this.params = params; - onInitialization(); - onInitialEntitiesCreation(); - if (!Configuration.commandLineMode) - onRenderingInitialization(); - this.scheduler.unlock(); - } - - /** - * Override this method is you wish to render environment. For example, you can - * use this method to create a VUI drawable object. - */ - private void onRenderingInitialization() { - } - - /** - * Getter for the scheduler - * - * @return the scheduler - */ - public Scheduler getScheduler() { - return scheduler; - } - - /** - * Set the seed for the common random object. This method should be called at - * the very beginning of the initialization process - * - * @param _seed - * The seed to initialize the random object - */ - public void setSeed(long _seed) { - random = new Random(_seed); - } - - /** - * This method is called during the initialization process of the environment - */ - public void onInitialization() { - } - - /** - * This method is called after the initialization process of the environment to - * create entities - */ - public void onInitialEntitiesCreation() { - } - - /** - * This method is called at each cycle of the environment - */ - public void onCycle() { - } - - @Override - public boolean stopCondition() { - return false; - } - - @Override - public final void cycle() { - onCycle(); - if (!Configuration.commandLineMode) - onUpdateRender(); - } - - /** - * Override this method to update rendering related to the environment - */ - protected void onUpdateRender() { - } - - /** - * Getter for the random object - * - * @return the random object - */ - public Random getRandom() { - return random; - } - - @Override - public void onSchedulingStarts() { - } - - @Override - public void onSchedulingStops() { - } -} +package fr.irit.smac.amak; + +import java.util.Random; + +import fr.irit.smac.amak.ui.AmasMultiUIWindow; +import fr.irit.smac.amak.ui.MainWindow; +import fr.irit.smac.amak.ui.SchedulerToolbar; + +/** + * This class must be overridden by environments + * + * @author Alexandre Perles + * + */ +public abstract class Environment implements Schedulable { + + public AmasMultiUIWindow amasMultiUIWindow; + + /** + * Unique index to give unique id to each environment + */ + private static int uniqueIndex; + + /** + * The id of the environment + */ + private final int id = uniqueIndex++; + + /** + * The parameters that are passed to {@link Environment#onInitialization()} + */ + protected Object[] params; + /** + * Random object common to the amas + */ + private Random random = new Random(); + /** + * The scheduler of the environment + */ + private Scheduler scheduler; + + /** + * Constructor + * + * @param _scheduling + * The scheduling of the environment + * @param params + * The parameters to initialize the environment + */ + public Environment(Scheduling _scheduling, Object... params) { + if (_scheduling == Scheduling.DEFAULT) { + this.scheduler = Scheduler.getDefaultScheduler(); + this.scheduler.add(this); + } else { + this.scheduler = new Scheduler(this); + if (_scheduling == Scheduling.UI && !Configuration.commandLineMode) + MainWindow.addToolbar(new SchedulerToolbar("Environment #" + id, getScheduler())); + } + this.scheduler.lock(); + this.params = params; + onInitialization(); + onInitialEntitiesCreation(); + if (!Configuration.commandLineMode) + onRenderingInitialization(); + this.scheduler.unlock(); + } + + public Environment(AmasMultiUIWindow window, Scheduling _scheduling, Object... params) { + amasMultiUIWindow = window; + if (_scheduling == Scheduling.DEFAULT) { + this.scheduler = Scheduler.getDefaultScheduler(window); + this.scheduler.add(this); + } else { + this.scheduler = new Scheduler(this); + if (_scheduling == Scheduling.UI && !Configuration.commandLineMode) + amasMultiUIWindow.addToolbar(new SchedulerToolbar("Environment #" + id, getScheduler())); + } + + this.scheduler.lock(); + this.params = params; + onInitialization(); + onInitialEntitiesCreation(); + if (!Configuration.commandLineMode) + onRenderingInitialization(); + this.scheduler.unlock(); + } + + /** + * Override this method is you wish to render environment. For example, you can + * use this method to create a VUI drawable object. + */ + private void onRenderingInitialization() { + } + + /** + * Getter for the scheduler + * + * @return the scheduler + */ + public Scheduler getScheduler() { + return scheduler; + } + + /** + * Set the seed for the common random object. This method should be called at + * the very beginning of the initialization process + * + * @param _seed + * The seed to initialize the random object + */ + public void setSeed(long _seed) { + random = new Random(_seed); + } + + /** + * This method is called during the initialization process of the environment + */ + public void onInitialization() { + } + + /** + * This method is called after the initialization process of the environment to + * create entities + */ + public void onInitialEntitiesCreation() { + } + + /** + * This method is called at each cycle of the environment + */ + public void onCycle() { + } + + @Override + public boolean stopCondition() { + return false; + } + + @Override + public final void cycle() { + onCycle(); + if (!Configuration.commandLineMode) + onUpdateRender(); + } + + /** + * Override this method to update rendering related to the environment + */ + protected void onUpdateRender() { + } + + /** + * Getter for the random object + * + * @return the random object + */ + public Random getRandom() { + return random; + } + + @Override + public void onSchedulingStarts() { + } + + @Override + public void onSchedulingStops() { + } +} diff --git a/AMAKFX/src/fr/irit/smac/amak/Scheduler.java b/AMAKFX/src/fr/irit/smac/amak/Scheduler.java index bffeeb2ce6a8d7e7a2422d165b41817e2e8b44c6..f509d7f4d20d69a27f6f49ce719acdd71fe1683c 100644 --- a/AMAKFX/src/fr/irit/smac/amak/Scheduler.java +++ b/AMAKFX/src/fr/irit/smac/amak/Scheduler.java @@ -1,341 +1,354 @@ -package fr.irit.smac.amak; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.ConcurrentModificationException; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import java.util.Set; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Consumer; - -import fr.irit.smac.amak.ui.MainWindow; -import fr.irit.smac.amak.ui.SchedulerToolbar; - -/** - * A scheduler associated to a MAS - * - * @author Alexandre Perles - * - */ -public class Scheduler implements Runnable, Serializable { - /** - * Unique ID meant to handle serialization correctly - */ - private static final long serialVersionUID = -4765899565369100376L; - /** - * The schedulables object handled by the scheduler - */ - private final Set<Schedulable> schedulables = new LinkedHashSet<>(); - /** - * The state of the scheduler {@link State} - */ - private State state; - /** - * The sleep time in ms between each cycle - */ - private int sleep; - /** - * A lock to protect the state - */ - private final ReentrantLock stateLock = new ReentrantLock(); - /** - * Method that is called when the scheduler stops - */ - private Consumer<Scheduler> onStop; - /** - * The methods called when the speed is changed. Useful to change the value of - * the GUI slider of {@link SchedulerToolbar} - */ - private List<Consumer<Scheduler>> onChange = new ArrayList<>(); - /** - * The idea is to prevent scheduler from launching if the schedulables are not - * yet fully ready - */ - private int locked = 0; - /** - * The default scheduler - */ - private static Scheduler defaultScheduler; - /** - * The schedulables that must be added - */ - private Queue<Schedulable> pendingAdditionSchedulables = new LinkedList<>(); - /** - * The schedulables that must be removed - */ - private Queue<Schedulable> pendingRemovalSchedulables = new LinkedList<>(); - - /** - * State of the scheduler - * - */ - public enum State { - /** - * The scheduler is running - */ - RUNNING, - /** - * The scheduler is paused - */ - IDLE, - /** - * The scheduler is expected to stop at the end at the current cycle - */ - PENDING_STOP - - } - - /** - * Constructor which set the initial state and auto start if requested - * - * @param _schedulables - * the corresponding schedulables - */ - public Scheduler(Schedulable... _schedulables) { - - for (Schedulable schedulable : _schedulables) { - this.add(schedulable); - } - this.state = State.IDLE; - } - - /** - * Create or return the default scheduler - * - * @return The default scheduler - */ - public static Scheduler getDefaultScheduler() { - if (defaultScheduler == null) { - defaultScheduler = new Scheduler(); - if (!Configuration.commandLineMode) { - MainWindow.instance(); - SchedulerToolbar st = new SchedulerToolbar("Default", defaultScheduler); - MainWindow.addToolbar(st); - } - } - return defaultScheduler; - } - - /** - * Set the delay between two cycles and launch the scheduler if it is not - * running - * - * @param i - * the delay between two cycles - */ - public void startWithSleep(int i) { - if (locked > 0) { - - synchronized (onChange) { - onChange.forEach(c -> c.accept(this)); - } - return; - } - setSleep(i); - stateLock.lock(); - switch (state) { - case IDLE: - state = State.RUNNING; - new Thread(this).start(); - break; - default: - break; - } - stateLock.unlock(); - synchronized (onChange) { - onChange.forEach(c -> c.accept(this)); - } - } - - /** - * Start (or continue) with no delay between cycles - */ - public void start() { - startWithSleep(Schedulable.DEFAULT_SLEEP); - } - - /** - * Execute one cycle - */ - public void step() { - if (locked > 0) { - synchronized (onChange) { - onChange.forEach(c -> c.accept(this)); - } - return; - } - this.setSleep(0); - stateLock.lock(); - switch (state) { - case IDLE: - state = State.PENDING_STOP; - new Thread(this).start(); - break; - default: - break; - - } - stateLock.unlock(); - synchronized (onChange) { - onChange.forEach(c -> c.accept(this)); - } - } - - /** - * Stop the scheduler if it is running - */ - public void stop() { - stateLock.lock(); - switch (state) { - case RUNNING: - state = State.PENDING_STOP; - break; - default: - break; - - } - stateLock.unlock(); - synchronized (onChange) { - onChange.forEach(c -> c.accept(this)); - } - } - - /** - * Threaded run method - */ - @Override - public void run() { - treatPendingSchedulables(); - for (Schedulable schedulable : schedulables) { - schedulable.onSchedulingStarts(); - } - boolean mustStop; - do { - for (Schedulable schedulable : schedulables) { - schedulable.cycle(); - } - if (getSleep() != 0) { - try { - Thread.sleep(getSleep()); - } catch (final InterruptedException e) { - e.printStackTrace(); - } - } - mustStop = false; - for (Schedulable schedulable : schedulables) { - mustStop |= schedulable.stopCondition(); - } - } while (state == State.RUNNING && !mustStop); - stateLock.lock(); - state = State.IDLE; - stateLock.unlock(); - - for (Schedulable schedulable : schedulables) { - schedulable.onSchedulingStops(); - } - treatPendingSchedulables(); - if (onStop != null) - onStop.accept(this); - } - - /** - * Effectively Add or Remove the schedulables that were added or removed during - * a cycle to avoid {@link ConcurrentModificationException} - */ - private void treatPendingSchedulables() { - while (!pendingAdditionSchedulables.isEmpty()) - schedulables.add(pendingAdditionSchedulables.poll()); - while (!pendingRemovalSchedulables.isEmpty()) - schedulables.remove(pendingRemovalSchedulables.poll()); - - } - - /** - * Set the method that must be executed when the system is stopped - * - * @param _onStop - * Consumer method - */ - public final void setOnStop(Consumer<Scheduler> _onStop) { - this.onStop = _onStop; - } - - /** - * Add a method that must be executed when the scheduler speed is changed - * - * @param _onChange - * Consumer method - */ - public final void addOnChange(Consumer<Scheduler> _onChange) { - synchronized (onChange) { - this.onChange.add(_onChange); - } - } - - /** - * Is the scheduler running ? - * - * @return true if the scheduler is running - */ - public boolean isRunning() { - return state == State.RUNNING; - } - - /** - * Getter for the sleep time - * - * @return the sleep time - */ - - public int getSleep() { - return sleep; - } - - /** - * Setter for the sleep time - * - * @param sleep - * The time between each cycle - */ - public void setSleep(int sleep) { - this.sleep = sleep; - } - - /** - * Plan to add a schedulable - * - * @param _schedulable - * the schedulable to add - */ - public void add(Schedulable _schedulable) { - this.pendingAdditionSchedulables.add(_schedulable); - } - - /** - * Plan to remove a schedulable - * - * @param _schedulable - * the schedulable to remove - */ - public void remove(Schedulable _schedulable) { - this.pendingRemovalSchedulables.add(_schedulable); - } - - /** - * Soft lock the scheduler to avoid a too early running - */ - public void lock() { - locked++; - } - - /** - * Soft unlock the scheduler to avoid a too early running - */ - public void unlock() { - locked--; - } - -} +package fr.irit.smac.amak; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.ConcurrentModificationException; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; + +import fr.irit.smac.amak.ui.AmasMultiUIWindow; +import fr.irit.smac.amak.ui.MainWindow; +import fr.irit.smac.amak.ui.SchedulerToolbar; + +/** + * A scheduler associated to a MAS + * + * @author Alexandre Perles + * + */ +public class Scheduler implements Runnable, Serializable { + /** + * Unique ID meant to handle serialization correctly + */ + private static final long serialVersionUID = -4765899565369100376L; + /** + * The schedulables object handled by the scheduler + */ + private final Set<Schedulable> schedulables = new LinkedHashSet<>(); + /** + * The state of the scheduler {@link State} + */ + private State state; + /** + * The sleep time in ms between each cycle + */ + private int sleep; + /** + * A lock to protect the state + */ + private final ReentrantLock stateLock = new ReentrantLock(); + /** + * Method that is called when the scheduler stops + */ + private Consumer<Scheduler> onStop; + /** + * The methods called when the speed is changed. Useful to change the value of + * the GUI slider of {@link SchedulerToolbar} + */ + private List<Consumer<Scheduler>> onChange = new ArrayList<>(); + /** + * The idea is to prevent scheduler from launching if the schedulables are not + * yet fully ready + */ + private int locked = 0; + /** + * The default scheduler + */ + private static Scheduler defaultScheduler; + /** + * The schedulables that must be added + */ + private Queue<Schedulable> pendingAdditionSchedulables = new LinkedList<>(); + /** + * The schedulables that must be removed + */ + private Queue<Schedulable> pendingRemovalSchedulables = new LinkedList<>(); + + /** + * State of the scheduler + * + */ + public enum State { + /** + * The scheduler is running + */ + RUNNING, + /** + * The scheduler is paused + */ + IDLE, + /** + * The scheduler is expected to stop at the end at the current cycle + */ + PENDING_STOP + + } + + /** + * Constructor which set the initial state and auto start if requested + * + * @param _schedulables + * the corresponding schedulables + */ + public Scheduler(Schedulable... _schedulables) { + + for (Schedulable schedulable : _schedulables) { + this.add(schedulable); + } + this.state = State.IDLE; + } + + /** + * Create or return the default scheduler + * + * @return The default scheduler + */ + public static Scheduler getDefaultScheduler() { + if (defaultScheduler == null) { + defaultScheduler = new Scheduler(); + if (!Configuration.commandLineMode) { + MainWindow.instance(); + SchedulerToolbar st = new SchedulerToolbar("Default", defaultScheduler); + MainWindow.addToolbar(st); + } + } + return defaultScheduler; + } + + public static Scheduler getDefaultScheduler(AmasMultiUIWindow window) { + if (defaultScheduler == null) { + defaultScheduler = new Scheduler(); + if (!Configuration.commandLineMode) { + //MainWindow.instance(); + SchedulerToolbar st = new SchedulerToolbar("Default", defaultScheduler); + window.addToolbar(st); + } + } + return defaultScheduler; + } + + /** + * Set the delay between two cycles and launch the scheduler if it is not + * running + * + * @param i + * the delay between two cycles + */ + public void startWithSleep(int i) { + if (locked > 0) { + + synchronized (onChange) { + onChange.forEach(c -> c.accept(this)); + } + return; + } + setSleep(i); + stateLock.lock(); + switch (state) { + case IDLE: + state = State.RUNNING; + new Thread(this).start(); + break; + default: + break; + } + stateLock.unlock(); + synchronized (onChange) { + onChange.forEach(c -> c.accept(this)); + } + } + + /** + * Start (or continue) with no delay between cycles + */ + public void start() { + startWithSleep(Schedulable.DEFAULT_SLEEP); + } + + /** + * Execute one cycle + */ + public void step() { + if (locked > 0) { + synchronized (onChange) { + onChange.forEach(c -> c.accept(this)); + } + return; + } + this.setSleep(0); + stateLock.lock(); + switch (state) { + case IDLE: + state = State.PENDING_STOP; + new Thread(this).start(); + break; + default: + break; + + } + stateLock.unlock(); + synchronized (onChange) { + onChange.forEach(c -> c.accept(this)); + } + } + + /** + * Stop the scheduler if it is running + */ + public void stop() { + stateLock.lock(); + switch (state) { + case RUNNING: + state = State.PENDING_STOP; + break; + default: + break; + + } + stateLock.unlock(); + synchronized (onChange) { + onChange.forEach(c -> c.accept(this)); + } + } + + /** + * Threaded run method + */ + @Override + public void run() { + treatPendingSchedulables(); + for (Schedulable schedulable : schedulables) { + schedulable.onSchedulingStarts(); + } + boolean mustStop; + do { + for (Schedulable schedulable : schedulables) { + schedulable.cycle(); + } + if (getSleep() != 0) { + try { + Thread.sleep(getSleep()); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + } + mustStop = false; + for (Schedulable schedulable : schedulables) { + mustStop |= schedulable.stopCondition(); + } + } while (state == State.RUNNING && !mustStop); + stateLock.lock(); + state = State.IDLE; + stateLock.unlock(); + + for (Schedulable schedulable : schedulables) { + schedulable.onSchedulingStops(); + } + treatPendingSchedulables(); + if (onStop != null) + onStop.accept(this); + } + + /** + * Effectively Add or Remove the schedulables that were added or removed during + * a cycle to avoid {@link ConcurrentModificationException} + */ + private void treatPendingSchedulables() { + while (!pendingAdditionSchedulables.isEmpty()) + schedulables.add(pendingAdditionSchedulables.poll()); + while (!pendingRemovalSchedulables.isEmpty()) + schedulables.remove(pendingRemovalSchedulables.poll()); + + } + + /** + * Set the method that must be executed when the system is stopped + * + * @param _onStop + * Consumer method + */ + public final void setOnStop(Consumer<Scheduler> _onStop) { + this.onStop = _onStop; + } + + /** + * Add a method that must be executed when the scheduler speed is changed + * + * @param _onChange + * Consumer method + */ + public final void addOnChange(Consumer<Scheduler> _onChange) { + synchronized (onChange) { + this.onChange.add(_onChange); + } + } + + /** + * Is the scheduler running ? + * + * @return true if the scheduler is running + */ + public boolean isRunning() { + return state == State.RUNNING; + } + + /** + * Getter for the sleep time + * + * @return the sleep time + */ + + public int getSleep() { + return sleep; + } + + /** + * Setter for the sleep time + * + * @param sleep + * The time between each cycle + */ + public void setSleep(int sleep) { + this.sleep = sleep; + } + + /** + * Plan to add a schedulable + * + * @param _schedulable + * the schedulable to add + */ + public void add(Schedulable _schedulable) { + this.pendingAdditionSchedulables.add(_schedulable); + } + + /** + * Plan to remove a schedulable + * + * @param _schedulable + * the schedulable to remove + */ + public void remove(Schedulable _schedulable) { + this.pendingRemovalSchedulables.add(_schedulable); + } + + /** + * Soft lock the scheduler to avoid a too early running + */ + public void lock() { + locked++; + } + + /** + * Soft unlock the scheduler to avoid a too early running + */ + public void unlock() { + locked--; + } + +} diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/randomants/AntExampleMutliUI.java b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/AntExampleMutliUI.java new file mode 100644 index 0000000000000000000000000000000000000000..62c06ef392dc2ac841394a5403f56577efebc6a4 --- /dev/null +++ b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/AntExampleMutliUI.java @@ -0,0 +1,93 @@ +package fr.irit.smac.amak.examples.randomants; + +import fr.irit.smac.amak.Agent; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; +import fr.irit.smac.amak.ui.VUI; +import fr.irit.smac.amak.ui.drawables.DrawableImage; + +public class AntExampleMutliUI extends Agent<AntHillExampleMultiUI, WorldExampleMultiUI> { + + + + private boolean dead = false; + + /** + * X coordinate of the ant in the world + */ + public double dx; + /** + * Y coordinate of the ant in the world + */ + public double dy; + /** + * Angle in radians + */ + private double angle = Math.random() * Math.PI * 2; + private DrawableImage image; + + /** + * Constructor of the ant + * + * @param amas + * the amas the ant belongs to + * @param startX + * Initial X coordinate + * @param startY + * Initial Y coordinate + */ + public AntExampleMutliUI(AmasMultiUIWindow window, AntHillExampleMultiUI amas, double startX, double startY) { + super(window, amas, startX, startY); + System.out.println(amasMultiUIWindow + " ------------- AntExampleMutliUI 42"); + } + @Override + public void onInitialization() { + dx = (double) params[0]; + dy = (double) params[1]; + } + + @Override + protected void onRenderingInitialization() { + System.out.println(amasMultiUIWindow + " ------------- AntExampleMutliUI 52"); + image = VUI.get(amasMultiUIWindow).createAndAddImage(dx, dy, "file:resources/ant.png"); + image.setName("Ant "+getId()); + } + + /** + * Move in a random direction + */ + @Override + protected void onDecideAndAct() { + double random = amas.getEnvironment().getRandom().nextGaussian(); + angle += random * 0.1; + dx += Math.cos(angle); + dy += Math.sin(angle); + while (dx >= getAmas().getEnvironment().getWidth() / 2) + dx -= getAmas().getEnvironment().getWidth(); + while (dy >= getAmas().getEnvironment().getHeight() / 2) + dy -= getAmas().getEnvironment().getHeight(); + while (dx < -getAmas().getEnvironment().getWidth() / 2) + dx += getAmas().getEnvironment().getWidth(); + while (dy < -getAmas().getEnvironment().getHeight() / 2) + dy += getAmas().getEnvironment().getHeight(); + + if (amas.getEnvironment().getRandom().nextDouble() < 0.001) { + dead = true; + destroy(); + } + + if (amas.getEnvironment().getRandom().nextDouble() < 0.001) { + new AntExampleMutliUI(amasMultiUIWindow, getAmas(), dx, dy); + } + } + + @Override + public void onUpdateRender() { + image.move(dx, dy); + image.setAngle(angle); + image.setInfo("Ant "+getId()+"\nPosition "+dx+" "+dy+"\nAngle "+angle); + if(dead) { + image.setFilename("file:Resources/ant_dead.png"); + image.setInfo("Ant "+getId()+"\nPosition "+dx+" "+dy+"\nAngle "+angle+"\nDead"); + } + } +} diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/randomants/AntHillExampleMultiUI.java b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/AntHillExampleMultiUI.java new file mode 100644 index 0000000000000000000000000000000000000000..fe8564924cfa7fa2c1548c56387abfd8da69181b --- /dev/null +++ b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/AntHillExampleMultiUI.java @@ -0,0 +1,38 @@ +package fr.irit.smac.amak.examples.randomants; + +import fr.irit.smac.amak.Amas; +import fr.irit.smac.amak.Scheduling; +import fr.irit.smac.amak.tools.RunLaterHelper; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; +import fr.irit.smac.amak.ui.VUI; +import fr.irit.smac.amak.ui.drawables.DrawableString; + +public class AntHillExampleMultiUI extends Amas<WorldExampleMultiUI> { + + private DrawableString antsCountLabel; + + public AntHillExampleMultiUI(AmasMultiUIWindow window, WorldExampleMultiUI env) { + super(window, env, Scheduling.DEFAULT); + System.out.println(window + " ------------- AntHillExampleMultiUI 16"); + } + + @Override + protected void onRenderingInitialization() { + VUI.get(amasMultiUIWindow).createAndAddImage(20, 20, "file:Resources/ant.png").setFixed().setLayer(10).setShowInExplorer(false); + antsCountLabel = (DrawableString) VUI.get().createAndAddString(45, 25, "Ants count").setFixed().setLayer(10).setShowInExplorer(false); + } + + @Override + protected void onInitialAgentsCreation() { + for (int i = 0; i < 50; i++) { + System.out.println(amasMultiUIWindow + " ------------- AntHillExampleMultiUI 28"); + new AntExampleMutliUI(amasMultiUIWindow, this, 0, 0); + } + + } + + @Override + protected void onSystemCycleEnd() { + RunLaterHelper.runLater(()->antsCountLabel.setText("Ants count: " + getAgents().size())); + } +} diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/randomants/AntsLaunchExampleMultiUI.java b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/AntsLaunchExampleMultiUI.java new file mode 100644 index 0000000000000000000000000000000000000000..b592877357310be5f1eeaa46361e75acd60a7396 --- /dev/null +++ b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/AntsLaunchExampleMultiUI.java @@ -0,0 +1,58 @@ +package fr.irit.smac.amak.examples.randomants; + +import fr.irit.smac.amak.Configuration; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; +import fr.irit.smac.amak.ui.MainWindow; +import javafx.application.Application; +import javafx.scene.control.Label; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; + +public class AntsLaunchExampleMultiUI extends Application{ + + + + + + + + public static void main (String[] args) { + + + Application.launch(args); + + + + //MainWindow.instance(); + + + } + + @Override + public void start(Stage primaryStage) throws Exception { + + Configuration.multiUI=true; + + AmasMultiUIWindow window = new AmasMultiUIWindow(); + AmasMultiUIWindow window2 = new AmasMultiUIWindow(); + + WorldExampleMultiUI env = new WorldExampleMultiUI(window); + WorldExampleMultiUI env2 = new WorldExampleMultiUI(window2); + + + new AntHillExampleMultiUI(window, env); + new AntHillExampleMultiUI(window2, env2); + + Pane panel = new Pane(); + panel.getChildren().add(new Label("AntHill simulation\n" + + "Ants move randomly.\n" + + "This demo is here to show AMAK rendering capacities.\n")); + window.setLeftPanel(panel); + + Pane panel2 = new Pane(); + panel2.getChildren().add(new Label("AntHill simulation\n" + + "Ants move randomly.\n" + + "This demo is here to show AMAK rendering capacities.\n")); + window2.setLeftPanel(panel2); + } +} diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/randomants/HelloWorld.java b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/HelloWorld.java new file mode 100644 index 0000000000000000000000000000000000000000..a1a015c3cbba9b8dc9cf7ef322d8a70065dde946 --- /dev/null +++ b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/HelloWorld.java @@ -0,0 +1,37 @@ +package fr.irit.smac.amak.examples.randomants; + +import javafx.application.Application; +import javafx.scene.Group; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.stage.Stage; + +public class HelloWorld extends Application { + + @Override public void start(Stage stage) { + Text text = new Text(10, 40, "Hello World!"); + text.setFont(new Font(40)); + Scene scene = new Scene(new Group(text)); + + stage.setTitle("Welcome to JavaFX!"); + stage.setScene(scene); + stage.sizeToScene(); + + + + Stage stage2 = new Stage(); + stage2.setScene(new Scene(new Group(new Button("my second window")))); + stage2.show(); + + stage.show(); + } + + + + public static void main(String[] args) { + Application.launch(args); + } +} + diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/randomants/WorldExample.java b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/WorldExample.java index 2c6ea56a9ef181a9cf191d87669c578c3dd7e24b..88156754ae2aaa969ed1b24c99ad7143f3854987 100644 --- a/AMAKFX/src/fr/irit/smac/amak/examples/randomants/WorldExample.java +++ b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/WorldExample.java @@ -1,28 +1,28 @@ -package fr.irit.smac.amak.examples.randomants; - -import fr.irit.smac.amak.Environment; -import fr.irit.smac.amak.Scheduling; - -public class WorldExample extends Environment { - public WorldExample(Object...params) { - super(Scheduling.DEFAULT, params); - } - - private int width; - private int height; - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - - @Override - public void onInitialization() { - this.width = 800; - this.height = 600; - } - -} +package fr.irit.smac.amak.examples.randomants; + +import fr.irit.smac.amak.Environment; +import fr.irit.smac.amak.Scheduling; + +public class WorldExample extends Environment { + public WorldExample(Object...params) { + super(Scheduling.DEFAULT, params); + } + + private int width; + private int height; + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + @Override + public void onInitialization() { + this.width = 800; + this.height = 600; + } + +} diff --git a/AMAKFX/src/fr/irit/smac/amak/examples/randomants/WorldExampleMultiUI.java b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/WorldExampleMultiUI.java new file mode 100644 index 0000000000000000000000000000000000000000..22f9e6913fffcd74374b7274dc8fe25e1a9ffb21 --- /dev/null +++ b/AMAKFX/src/fr/irit/smac/amak/examples/randomants/WorldExampleMultiUI.java @@ -0,0 +1,29 @@ +package fr.irit.smac.amak.examples.randomants; + +import fr.irit.smac.amak.Environment; +import fr.irit.smac.amak.Scheduling; +import fr.irit.smac.amak.ui.AmasMultiUIWindow; + +public class WorldExampleMultiUI extends Environment { + public WorldExampleMultiUI(AmasMultiUIWindow window, Object...params) { + super(window, Scheduling.DEFAULT, params); + } + + private int width; + private int height; + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + @Override + public void onInitialization() { + this.width = 800; + this.height = 600; + } + +} diff --git a/AMAKFX/src/fr/irit/smac/amak/ui/AmakPlot.java b/AMAKFX/src/fr/irit/smac/amak/ui/AmakPlot.java index 60a58286244634a53702a0cea0126b6a792c29aa..a50dcea052955e308ffca135ce65c6d0998898a2 100644 --- a/AMAKFX/src/fr/irit/smac/amak/ui/AmakPlot.java +++ b/AMAKFX/src/fr/irit/smac/amak/ui/AmakPlot.java @@ -36,6 +36,10 @@ public class AmakPlot { public static void add(AmakPlot chart) { MainWindow.addTabbedPanel(chart.name, new ChartViewer(chart.chart)); } + + public static void add(AmasMultiUIWindow window, AmakPlot chart) { + window.addTabbedPanel(chart.name, new ChartViewer(chart.chart)); + } /* ----- */ private String name; @@ -119,6 +123,79 @@ public class AmakPlot { this(name, chart, true); } + + /** + * Create a chart + * @param name the name of the chart, used as the tab name. + * @param chartType {@link ChartType#LINE} or {@link ChartType#BAR} + * @param xAxisLabel label for the x (horizontal) axis + * @param yAxisLabel label for the y (vertical) axis + * @param autoAdd automatically make an {@link AmakPlot#add(AmakPlot)} call ? + */ + public AmakPlot(AmasMultiUIWindow window, String name, ChartType chartType, String xAxisLabel, String yAxisLabel, boolean autoAdd) { + this.name = name; + seriesCollection = new XYSeriesCollection(); + switch (chartType) { + case BAR: + chart = ChartFactory.createXYBarChart(name, xAxisLabel, false, yAxisLabel, seriesCollection); + break; + case LINE: + chart = ChartFactory.createXYLineChart(name, xAxisLabel, yAxisLabel, seriesCollection); + if(useSamplingRenderer) { + chart.getXYPlot().setRenderer(new SamplingXYLineRenderer()); + } + XYPlot plot = (XYPlot)chart.getPlot(); + plot.setDomainGridlinesVisible(true); + plot.setDomainGridlinePaint(Color.lightGray); + plot.setRangeGridlinePaint(Color.lightGray); + break; + default: + System.err.println("AmakPlot : unknow ChartType \""+chartType+"\"."); + break; + } + chart.setAntiAlias(false); + chart.getPlot().setBackgroundPaint(Color.WHITE); + if(autoAdd) { + add(window, this); + } + } + + /** + * Create a chart and add it to the main window. + * @param name the name of the chart, used as the tab name. + * @param chartType {@link ChartType#LINE} or {@link ChartType#BAR} + * @param xAxisLabel label for the x (horizontal) axis + * @param yAxisLabel label for the y (vertical) axis + */ + public AmakPlot(AmasMultiUIWindow window, String name, ChartType chartType, String xAxisLabel, String yAxisLabel) { + this(window, name, chartType, xAxisLabel, yAxisLabel, true); + } + + + /** + * Create a chart out of a JFreeChart. + * Make sure that your chart use an {@link XYSeriesCollection} as dataset. + * @param name the name of the chart, used as the tab name. + * @param chart the {@link JFreeChart} using a {@link XYSeriesCollection} for dataset. + * @param autoAdd automatically make an {@link AmakPlot#add(AmakPlot)} call ? + */ + public AmakPlot(AmasMultiUIWindow window, String name, JFreeChart chart, boolean autoAdd) { + this.name = name; + this.seriesCollection = (XYSeriesCollection) chart.getXYPlot().getDataset(); + this.chart = chart; + add(window, this); + } + + /** + * Create a chart out of a JFreeChart and add it to the main window. + * Make sure that your chart use an {@link XYSeriesCollection} as dataset. + * @param name the name of the chart, used as the tab name. + * @param chart the {@link JFreeChart} using a {@link XYSeriesCollection} for dataset. + */ + public AmakPlot(AmasMultiUIWindow window, String name, JFreeChart chart) { + this(window, name, chart, true); + } + public String getName() { return name; } diff --git a/AMAKFX/src/fr/irit/smac/amak/ui/AmasMultiUIWindow.java b/AMAKFX/src/fr/irit/smac/amak/ui/AmasMultiUIWindow.java new file mode 100644 index 0000000000000000000000000000000000000000..53ca4e46eeecf1df1b2ab7a0db9502cffcf173d0 --- /dev/null +++ b/AMAKFX/src/fr/irit/smac/amak/ui/AmasMultiUIWindow.java @@ -0,0 +1,251 @@ +package fr.irit.smac.amak.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +import javax.management.InstanceAlreadyExistsException; + +import fr.irit.smac.amak.Information; +import fr.irit.smac.amak.tools.RunLaterHelper; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuBar; +import javafx.scene.control.MenuItem; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.ToolBar; +import javafx.scene.image.Image; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import javafx.stage.WindowEvent; + +/** + * This window is the main one of an AMAS developed using AMAK. It contains a + * toolbar panel and various spaces for panels + * + * @author of the original version (the Swing one) Alexandre Perles, Marcillaud + * Guilhem + * + */ +public class AmasMultiUIWindow extends Stage{ +// /** +// * The window itself +// */ +// public Stage stage; + /** + * The panel which contains the toolbar + */ + public ToolBar toolbarPanel; + + /** + * The main pane of AMAK + */ + public BorderPane organizationPane; + + /** + * The menu bar of the window + */ + public MenuBar menuBar; + /** + * The menus + */ + public HashMap<String, Menu> menus = new HashMap<String, Menu>(); + /** + * The panel in which panels with tab can be added + */ + public TabPane tabbedPanel; + + + protected static Object startEnded = new Object(); + + /** + * Create the frame. + * + * @throws InstanceAlreadyExistsException + * if the MainWindow has already been instantiated. This constructor + * should be used by the Application of JavaFX only. + */ + public AmasMultiUIWindow() { + synchronized (startEnded) { + VBox root = new VBox(); + + // Creation of the menu bar (Top) + menuBar = new MenuBar(); + root.getChildren().add(menuBar); + + // Border organization + organizationPane = new BorderPane(); + organizationPane.setMinSize(200, 200); //that way we avoid 0 size, which can cause problems + root.getChildren().add(organizationPane); + VBox.setVgrow(organizationPane, Priority.ALWAYS); + + // Creation of scene + this.setTitle("AMAS"); + Scene scene = new Scene(root, 450, 300); + //stage = primaryStage; + this.setScene(scene); + this.setOnCloseRequest(new EventHandler<WindowEvent>() { + @Override + public void handle(WindowEvent event) { + Platform.exit(); + } + }); + + // Creation of the toolbar (Bottom) + toolbarPanel = new ToolBar(); + organizationPane.setBottom(toolbarPanel); + + // Creation of the right part of the split pane (Center Right) + tabbedPanel = new TabPane(); + organizationPane.setCenter(tabbedPanel); + + // Creation of the close menu item + MenuItem menuItem = new MenuItem("Close"); + menuItem.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + System.exit(0); + } + }); + addToMenu("Options", menuItem); + + menuBar.getMenus().add(new Menu("AMAKFX v" + Information.VERSION)); + + this.show(); + + startEnded.notify(); + } + } + +// @Override +// public void start(Stage primaryStage) throws Exception { +// +// } + + /** + * Add an action when the JavaFX app close. + * + * @param onClose + * The action to be executed when the window is closed + */ + public static void addOnCloseAction(Runnable onClose) { + Runtime.getRuntime().addShutdownHook(new Thread() { + public void run() { onClose.run(); } + }); + } + +// @Override +// public void stop() throws Exception { +// super.stop(); +// System.exit(0); +// } + + /** + * Change the icon of the window + * + * @param filename + * The filename of the icon + */ + public void setWindowIcon(String filename) { + RunLaterHelper.runLater(() -> this.getIcons().add(new Image(filename))); + } + + /** + * Change the title of the main window + * + * @param title + * The new title + */ + public void setWindowTitle(String title) { + RunLaterHelper.runLater(() -> this.setTitle(title)); + } + + /** + * Add a button in the menu options + * + * @param title + * The title of the button + * @param event + * The action to be executed + */ + public void addOptionsItem(String title, EventHandler<ActionEvent> event) { + MenuItem menuItem = new MenuItem(title); + menuItem.setOnAction(event); + RunLaterHelper.runLater(() -> addToMenu("Options", menuItem)); + } + + /** + * Add a tool in the toolbar. + * + * @param tool + */ + public void addToolbar(Node tool) { + RunLaterHelper.runLater(() -> toolbarPanel.getItems().add(tool)); + } + + /** + * Set a panel to the left + * + * @param panel + * The panel + */ + public void setLeftPanel(Node panel) { + RunLaterHelper.runLater(() -> organizationPane.setLeft(panel)); + } + + /** + * Set a panel to the right + * + * @param panel + * The panel + */ + public void setRightPanel(Node panel) { + RunLaterHelper.runLater(() -> organizationPane.setRight(panel)); + } + + /** + * Return the unique instance of MainWindow, may create it. + * + * @return instance + */ + + + + + /** + * Add a panel with a tab + * + * @param title + * The title of the tab + * @param panel + * The panel to add + */ + public void addTabbedPanel(String title, Node panel) { + Tab t = new DraggableTab(title, panel); + RunLaterHelper.runLater(() -> tabbedPanel.getTabs().add(t)); + } + + /** + * Add a {@link MenuItem} to a {@link Menu}. May create the menu and add it to the menu bar. + * @param menuName the name of the menu where the item will be added. + * @param item the item to be added. + */ + public void addToMenu(String menuName, MenuItem item) { + //instance(); + if( !menus.containsKey(menuName) ) { + Menu m = new Menu(menuName); + menus.put(menuName,m); + RunLaterHelper.runLater(() -> menuBar.getMenus().add(m)); + } + RunLaterHelper.runLater(() -> menus.get(menuName).getItems().add(item)); + } +} \ No newline at end of file diff --git a/AMAKFX/src/fr/irit/smac/amak/ui/VUI.java b/AMAKFX/src/fr/irit/smac/amak/ui/VUI.java index 136cb472ac33fbffb9e2f7c7ddda551fe2384d81..e396e9bb3deabfc2b3c05beef8826f5002258568 100644 --- a/AMAKFX/src/fr/irit/smac/amak/ui/VUI.java +++ b/AMAKFX/src/fr/irit/smac/amak/ui/VUI.java @@ -1,577 +1,588 @@ -package fr.irit.smac.amak.ui; - -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Semaphore; -import java.util.concurrent.locks.ReentrantLock; - -import fr.irit.smac.amak.tools.RunLaterHelper; -import fr.irit.smac.amak.ui.drawables.Drawable; -import fr.irit.smac.amak.ui.drawables.DrawableImage; -import fr.irit.smac.amak.ui.drawables.DrawablePoint; -import fr.irit.smac.amak.ui.drawables.DrawableRectangle; -import fr.irit.smac.amak.ui.drawables.DrawableString; -import javafx.event.ActionEvent; -import javafx.event.EventHandler; -import javafx.geometry.Insets; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.ToolBar; -import javafx.scene.control.Tooltip; -import javafx.scene.input.MouseEvent; -import javafx.scene.input.ScrollEvent; -import javafx.scene.layout.Background; -import javafx.scene.layout.BackgroundFill; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.CornerRadii; -import javafx.scene.layout.Pane; -import javafx.scene.paint.Color; -import javafx.scene.shape.Rectangle; -import javafx.scene.text.TextAlignment; - -/** - * - * Vectorial UI: This class allows to create dynamic rendering with zoom and - * move capacities - * - * @author of original version (the Swing one) perles - * - */ -public class VUI { - /** - * The toolbar of the VUI. - */ - public ToolBar toolbar; - - /** - * The VUI explorer. - * @see VuiExplorer - */ - private VuiExplorer vuiExplorer; - - /** - * List of objects currently being drawn by the VUI - */ - private List<Drawable> drawables = new LinkedList<>(); - /** - * Lock to avoid concurrent modification on the list {@link #drawables} - */ - private ReentrantLock drawablesLock = new ReentrantLock(); - - /** - * A static map to facilitate access to different instances of VUI - */ - private static Map<String, VUI> instances = new HashMap<>(); - - /** - * The horizontal offset of the drawing zone. Used to allow the user to move the - * view. - */ - private double worldOffsetX; - - /** - * The vertical offset of the drawing zone. Used to allow the user to move the - * view. - */ - private double worldOffsetY; - - /** - * The last horizontal position of the mouse when dragging - */ - protected Double lastDragX; - - /** - * The last vertical position of the mouse when dragging - */ - protected Double lastDragY; - - /** - * The main panel of the VUI - */ - private BorderPane panel; - - /** - * The canvas on which all is drawn - */ - private Pane canvas; - - /** - * Label aiming at showing information about the VUI (zoom and offset) - */ - private Label statusLabel; - - /** - * The default value of the {@link #zoom} - */ - private double defaultZoom = 100; - /** - * The default horizontal position of the view - */ - private double defaultWorldCenterX = 0; - /** - * The default vertical position of the view - */ - private double defaultWorldCenterY = 0; - /** - * The value of the zoom. 100 means 1/1 scale - */ - protected double zoom = defaultZoom; - - /** - * The horizontal position of the view - */ - private double worldCenterX = defaultWorldCenterX; - - /** - * The vertical position of the view - */ - private double worldCenterY = defaultWorldCenterY; - - /** - * Used to be sure that only one thread at the same time create a VUI - */ - private static ReentrantLock instanceLock = new ReentrantLock(); - - /** - * Get the default VUI - * - * @return the default VUI - */ - public static VUI get() { - if(!instances.containsKey("Default")) - MainWindow.addTabbedPanel("Default VUI", get("Default").getPanel()); - return get("Default"); - } - - /** - * Create or get a VUI.<br/> - * You have add its panel to the MainWindow yourself. - * - * @param id - * The unique id of the VUI - * @return The VUI with id "id" - */ - public static VUI get(String id) { - instanceLock.lock(); - if (!instances.containsKey(id)) { - VUI value = new VUI(id); - instances.put(id, value); - instanceLock.unlock(); - return value; - } - instanceLock.unlock(); - return instances.get(id); - } - - /** - * Constructor of the VUI. This one is private as it can only be created through - * static method. - * - * @param title - * The title used for the vui - */ - private VUI(String title) { - Semaphore done = new Semaphore(0); - RunLaterHelper.runLater(() -> { - panel = new BorderPane(); - - toolbar = new ToolBar(); - statusLabel = new Label("status"); - statusLabel.setTextAlignment(TextAlignment.LEFT); - toolbar.getItems().add(statusLabel); - panel.setBottom(toolbar); - - Button resetButton = new Button("Reset"); - resetButton.setOnAction(new EventHandler<ActionEvent>() { - @Override - public void handle(ActionEvent event) { - zoom = defaultZoom; - worldCenterX = defaultWorldCenterX; - worldCenterY = defaultWorldCenterY; - updateCanvas(); - } - }); - toolbar.getItems().add(resetButton); - - canvas = new Pane(); - canvas.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); - // clip the canvas (avoid drawing outside of it) - Rectangle clip = new Rectangle(0, 0, 0, 0); - clip.widthProperty().bind(canvas.widthProperty()); - clip.heightProperty().bind(canvas.heightProperty()); - canvas.setClip(clip); - - canvas.setOnMousePressed(new EventHandler<MouseEvent>() { - @Override - public void handle(MouseEvent event) { - lastDragX = event.getX(); - lastDragY = event.getY(); - } - }); - canvas.setOnMouseExited(new EventHandler<MouseEvent>() { - @Override - public void handle(MouseEvent event) { - lastDragX = null; - lastDragY = null; - } - }); - canvas.setOnMouseDragged(new EventHandler<MouseEvent>() { - @Override - public void handle(MouseEvent event) { - try { - double transX = screenToWorldDistance(event.getX() - lastDragX); - double transY = screenToWorldDistance(event.getY() - lastDragY); - worldCenterX += transX; - worldCenterY += transY; - worldOffsetX += transX; - worldOffsetY += transY; - lastDragX = event.getX(); - lastDragY = event.getY(); - updateCanvas(); - } catch (Exception ez) { - // Catch exception occurring when mouse is out of the canvas - } - } - }); - - canvas.setOnScroll(new EventHandler<ScrollEvent>() { - @Override - public void handle(ScrollEvent event) { - double wdx = screenToWorldDistance(canvas.getWidth() / 2 - event.getX()); - double wdy = screenToWorldDistance(canvas.getHeight() / 2 - event.getY()); - zoom += event.getDeltaY() / event.getMultiplierY() * 10; - if (zoom < 10) - zoom = 10; - - double wdx2 = screenToWorldDistance(canvas.getWidth() / 2 - event.getX()); - double wdy2 = screenToWorldDistance(canvas.getHeight() / 2 - event.getY()); - worldCenterX -= wdx2 - wdx; - worldCenterY -= wdy2 - wdy; - updateCanvas(); - } - }); - - panel.setCenter(canvas); - - //add VuiExplorer - vuiExplorer = new VuiExplorer(this); - panel.setLeft(vuiExplorer); - Button veButton = new Button("VUI explorer"); - veButton.setOnAction(new EventHandler<ActionEvent>() { - @Override - public void handle(ActionEvent event) { - panel.setLeft(vuiExplorer); - } - }); - veButton.setTooltip(new Tooltip("Show the VUI explorer if it was hidden.")); - toolbar.getItems().add(veButton); - - done.release(); - }); - try { - done.acquire(); - } catch (InterruptedException e) { - System.err.println("Failed to make sure that the VUI is correctly initialized."); - e.printStackTrace(); - } - } - - /** - * Convert a distance in the world to its equivalent on the screen - * - * @param d - * the in world distance - * @return the on screen distance - */ - public double worldToScreenDistance(double d) { - return d * getZoomFactor(); - } - - /** - * Convert a distance on the screen to its equivalent in the world - * - * @param d - * the on screen distance - * @return the in world distance - */ - public double screenToWorldDistance(double d) { - return d / getZoomFactor(); - } - - /** - * Convert a X in the world to its equivalent on the screen - * - * @param x - * the X in world - * - * @return the X on screen distance - */ - public double worldToScreenX(double x) { - return (x + getWorldOffsetX()) * getZoomFactor(); - } - - /** - * A value that must be multiplied to scale objects - * - * @return the zoom factor - */ - public double getZoomFactor() { - return zoom / 100; - } - - /** - * Convert a Y in the world to its equivalent on the screen - * - * @param y - * the Y in world - * - * @return the Y on screen distance - */ - public double worldToScreenY(double y) { - return (-y + getWorldOffsetY()) * getZoomFactor(); - } - - /** - * Convert a X on the screen to its equivalent in the world - * - * @param x - * the X on screen - * - * @return the X in the world distance - */ - public double screenToWorldX(double x) { - return x / getZoomFactor() - getWorldOffsetX(); - } - - /** - * Convert a Y on the screen to its equivalent in the world - * - * @param y - * the Y on screen - * - * @return the Y in the world distance - */ - public double screenToWorldY(double y) { - return -y / getZoomFactor() + getWorldOffsetY(); - } - - /** - * Add a drawable to the VUI. - * - * @param d - * the new drawable - */ - public void add(Drawable d) { - d.setVUI(this); - RunLaterHelper.runLater(()-> canvas.getChildren().add(d.getNode())); - drawablesLock.lock(); - drawables.add(d); - drawablesLock.unlock(); - updateCanvas(); - } - - /** - * Remove a drawable from the VUI. - * - * @param d - * the new drawable - */ - public void remove(Drawable d) { - drawablesLock.lock(); - drawables.remove(d); - drawablesLock.unlock(); - RunLaterHelper.runLater(()-> canvas.getChildren().remove(d.getNode())); - updateCanvas(); - } - - /** - * Remove all drawables from the VUI. - */ - public void clear() { - drawablesLock.lock(); - drawables.clear(); - RunLaterHelper.runLater(()->canvas.getChildren().clear()); - drawablesLock.unlock(); - } - - /** - * Refresh the canvas - */ - public void updateCanvas() { - final double w = canvas.getWidth(); - final double h = canvas.getHeight(); - - setWorldOffsetX(worldCenterX + screenToWorldDistance(w / 2)); - setWorldOffsetY(worldCenterY + screenToWorldDistance(h / 2)); - - drawablesLock.lock(); - Collections.sort(drawables, (o1, o2) -> o1.getLayer() - o2.getLayer()); - for (Drawable d : drawables) - RunLaterHelper.runLater(()-> d.onDraw()); - drawablesLock.unlock(); - - RunLaterHelper.runLater(() -> { - statusLabel.setText(String.format("Zoom: %.2f Center: (%.2f,%.2f)", zoom, worldCenterX, worldCenterY)); - }); - - RunLaterHelper.runLater(()-> vuiExplorer.update(true)); - } - - /** - * Get the width of the canvas - * - * @return the canvas width - */ - public double getCanvasWidth() { - return canvas.getWidth(); - } - - /** - * Get the height of the canvas - * - * @return the canvas height - */ - public double getCanvasHeight() { - return canvas.getHeight(); - } - - /** - * Get the value that must be added to the X coordinate of in world object - * - * @return the X offset - */ - public double getWorldOffsetX() { - return worldOffsetX; - } - - /** - * Set the value that must be added to the X coordinate of in world object - * - * @param offsetX - * the X offset - */ - public void setWorldOffsetX(double offsetX) { - this.worldOffsetX = offsetX; - } - - /** - * Get the value that must be added to the Y coordinate of in world object - * - * @return the Y offset - */ - public double getWorldOffsetY() { - return worldOffsetY; - } - - /** - * Set the value that must be added to the Y coordinate of in world object - * - * @param offsetY - * the Y offset - */ - public void setWorldOffsetY(double offsetY) { - this.worldOffsetY = offsetY; - } - - /** - * Create a point and start rendering it - * - * @param dx - * the x coordinate - * @param dy - * the y coordinate - * @return the point object - */ - public DrawablePoint createAndAddPoint(double dx, double dy) { - DrawablePoint drawablePoint = new DrawablePoint(dx, dy); - add(drawablePoint); - return drawablePoint; - } - - /** - * Create a rectangle and start rendering it - * - * @param x - * the x coordinate - * @param y - * the y coordinate - * @param w - * the width - * @param h - * the height - * @return the rectangle object - */ - public DrawableRectangle createAndAddRectangle(double x, double y, double w, double h) { - DrawableRectangle d = new DrawableRectangle(x, y, w, h); - add(d); - return d; - } - - /** - * Set the default configuration of the view - * - * @param zoom - * the initial zoom value - * @param worldCenterX - * the initial X center value - * @param worldCenterY - * the initial Y center value - */ - public void setDefaultView(double zoom, double worldCenterX, double worldCenterY) { - this.zoom = zoom; - this.worldCenterX = worldCenterX; - this.worldCenterY = worldCenterY; - this.defaultZoom = zoom; - this.defaultWorldCenterX = worldCenterX; - this.defaultWorldCenterY = worldCenterY; - } - - /** - * Create an image and start rendering it - * - * @param dx - * the x coordinate - * @param dy - * the y coordinate - * @param filename - * the filename of the image - * @return the created image - */ - public DrawableImage createAndAddImage(double dx, double dy, String filename) { - DrawableImage image = new DrawableImage(dx, dy, filename); - add(image); - return image; - } - - /** - * Create a string and start rendering it - * - * @param dx - * the x coordinate - * @param dy - * the y coordinate - * @param text - * the text to display - * @return the created string - */ - public DrawableString createAndAddString(int dx, int dy, String text) { - DrawableString ds = new DrawableString(dx, dy, text); - add(ds); - return ds; - } - - public Pane getCanvas() { - return canvas; - } - - public BorderPane getPanel() { - return panel; - } - - public List<Drawable> getDrawables() { - return drawables; - } -} +package fr.irit.smac.amak.ui; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Semaphore; +import java.util.concurrent.locks.ReentrantLock; + +import fr.irit.smac.amak.tools.RunLaterHelper; +import fr.irit.smac.amak.ui.drawables.Drawable; +import fr.irit.smac.amak.ui.drawables.DrawableImage; +import fr.irit.smac.amak.ui.drawables.DrawablePoint; +import fr.irit.smac.amak.ui.drawables.DrawableRectangle; +import fr.irit.smac.amak.ui.drawables.DrawableString; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ToolBar; +import javafx.scene.control.Tooltip; +import javafx.scene.input.MouseEvent; +import javafx.scene.input.ScrollEvent; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.CornerRadii; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; +import javafx.scene.text.TextAlignment; + +/** + * + * Vectorial UI: This class allows to create dynamic rendering with zoom and + * move capacities + * + * @author of original version (the Swing one) perles + * + */ +public class VUI { + /** + * The toolbar of the VUI. + */ + public ToolBar toolbar; + + /** + * The VUI explorer. + * @see VuiExplorer + */ + private VuiExplorer vuiExplorer; + + /** + * List of objects currently being drawn by the VUI + */ + private List<Drawable> drawables = new LinkedList<>(); + /** + * Lock to avoid concurrent modification on the list {@link #drawables} + */ + private ReentrantLock drawablesLock = new ReentrantLock(); + + /** + * A static map to facilitate access to different instances of VUI + */ + private static Map<String, VUI> instances = new HashMap<>(); + + /** + * The horizontal offset of the drawing zone. Used to allow the user to move the + * view. + */ + private double worldOffsetX; + + /** + * The vertical offset of the drawing zone. Used to allow the user to move the + * view. + */ + private double worldOffsetY; + + /** + * The last horizontal position of the mouse when dragging + */ + protected Double lastDragX; + + /** + * The last vertical position of the mouse when dragging + */ + protected Double lastDragY; + + /** + * The main panel of the VUI + */ + private BorderPane panel; + + /** + * The canvas on which all is drawn + */ + private Pane canvas; + + /** + * Label aiming at showing information about the VUI (zoom and offset) + */ + private Label statusLabel; + + /** + * The default value of the {@link #zoom} + */ + private double defaultZoom = 100; + /** + * The default horizontal position of the view + */ + private double defaultWorldCenterX = 0; + /** + * The default vertical position of the view + */ + private double defaultWorldCenterY = 0; + /** + * The value of the zoom. 100 means 1/1 scale + */ + protected double zoom = defaultZoom; + + /** + * The horizontal position of the view + */ + private double worldCenterX = defaultWorldCenterX; + + /** + * The vertical position of the view + */ + private double worldCenterY = defaultWorldCenterY; + + /** + * Used to be sure that only one thread at the same time create a VUI + */ + private static ReentrantLock instanceLock = new ReentrantLock(); + + /** + * Get the default VUI + * + * @return the default VUI + */ + public static VUI get() { + if(!instances.containsKey("Default")) + MainWindow.addTabbedPanel("Default VUI", get("Default").getPanel()); + return get("Default"); + } + + public static VUI get(AmasMultiUIWindow window) { + if(!instances.containsKey("Default")) { + System.out.println(window + " ------------- VUI 151"); + System.out.println(get("Default")); + System.out.println(get("Default").getPanel()); + window.addTabbedPanel("Default VUI", get("Default").getPanel()); + } + + return get("Default"); + } + + /** + * Create or get a VUI.<br/> + * You have add its panel to the MainWindow yourself. + * + * @param id + * The unique id of the VUI + * @return The VUI with id "id" + */ + public static VUI get(String id) { + instanceLock.lock(); + if (!instances.containsKey(id)) { + VUI value = new VUI(id); + instances.put(id, value); + instanceLock.unlock(); + return value; + } + instanceLock.unlock(); + return instances.get(id); + } + + /** + * Constructor of the VUI. This one is private as it can only be created through + * static method. + * + * @param title + * The title used for the vui + */ + private VUI(String title) { + Semaphore done = new Semaphore(0); + RunLaterHelper.runLater(() -> { + panel = new BorderPane(); + + toolbar = new ToolBar(); + statusLabel = new Label("status"); + statusLabel.setTextAlignment(TextAlignment.LEFT); + toolbar.getItems().add(statusLabel); + panel.setBottom(toolbar); + + Button resetButton = new Button("Reset"); + resetButton.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + zoom = defaultZoom; + worldCenterX = defaultWorldCenterX; + worldCenterY = defaultWorldCenterY; + updateCanvas(); + } + }); + toolbar.getItems().add(resetButton); + + canvas = new Pane(); + canvas.setBackground(new Background(new BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))); + // clip the canvas (avoid drawing outside of it) + Rectangle clip = new Rectangle(0, 0, 0, 0); + clip.widthProperty().bind(canvas.widthProperty()); + clip.heightProperty().bind(canvas.heightProperty()); + canvas.setClip(clip); + + canvas.setOnMousePressed(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + lastDragX = event.getX(); + lastDragY = event.getY(); + } + }); + canvas.setOnMouseExited(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + lastDragX = null; + lastDragY = null; + } + }); + canvas.setOnMouseDragged(new EventHandler<MouseEvent>() { + @Override + public void handle(MouseEvent event) { + try { + double transX = screenToWorldDistance(event.getX() - lastDragX); + double transY = screenToWorldDistance(event.getY() - lastDragY); + worldCenterX += transX; + worldCenterY += transY; + worldOffsetX += transX; + worldOffsetY += transY; + lastDragX = event.getX(); + lastDragY = event.getY(); + updateCanvas(); + } catch (Exception ez) { + // Catch exception occurring when mouse is out of the canvas + } + } + }); + + canvas.setOnScroll(new EventHandler<ScrollEvent>() { + @Override + public void handle(ScrollEvent event) { + double wdx = screenToWorldDistance(canvas.getWidth() / 2 - event.getX()); + double wdy = screenToWorldDistance(canvas.getHeight() / 2 - event.getY()); + zoom += event.getDeltaY() / event.getMultiplierY() * 10; + if (zoom < 10) + zoom = 10; + + double wdx2 = screenToWorldDistance(canvas.getWidth() / 2 - event.getX()); + double wdy2 = screenToWorldDistance(canvas.getHeight() / 2 - event.getY()); + worldCenterX -= wdx2 - wdx; + worldCenterY -= wdy2 - wdy; + updateCanvas(); + } + }); + + panel.setCenter(canvas); + + //add VuiExplorer + vuiExplorer = new VuiExplorer(this); + panel.setLeft(vuiExplorer); + Button veButton = new Button("VUI explorer"); + veButton.setOnAction(new EventHandler<ActionEvent>() { + @Override + public void handle(ActionEvent event) { + panel.setLeft(vuiExplorer); + } + }); + veButton.setTooltip(new Tooltip("Show the VUI explorer if it was hidden.")); + toolbar.getItems().add(veButton); + + done.release(); + }); + try { + done.acquire(); + } catch (InterruptedException e) { + System.err.println("Failed to make sure that the VUI is correctly initialized."); + e.printStackTrace(); + } + } + + /** + * Convert a distance in the world to its equivalent on the screen + * + * @param d + * the in world distance + * @return the on screen distance + */ + public double worldToScreenDistance(double d) { + return d * getZoomFactor(); + } + + /** + * Convert a distance on the screen to its equivalent in the world + * + * @param d + * the on screen distance + * @return the in world distance + */ + public double screenToWorldDistance(double d) { + return d / getZoomFactor(); + } + + /** + * Convert a X in the world to its equivalent on the screen + * + * @param x + * the X in world + * + * @return the X on screen distance + */ + public double worldToScreenX(double x) { + return (x + getWorldOffsetX()) * getZoomFactor(); + } + + /** + * A value that must be multiplied to scale objects + * + * @return the zoom factor + */ + public double getZoomFactor() { + return zoom / 100; + } + + /** + * Convert a Y in the world to its equivalent on the screen + * + * @param y + * the Y in world + * + * @return the Y on screen distance + */ + public double worldToScreenY(double y) { + return (-y + getWorldOffsetY()) * getZoomFactor(); + } + + /** + * Convert a X on the screen to its equivalent in the world + * + * @param x + * the X on screen + * + * @return the X in the world distance + */ + public double screenToWorldX(double x) { + return x / getZoomFactor() - getWorldOffsetX(); + } + + /** + * Convert a Y on the screen to its equivalent in the world + * + * @param y + * the Y on screen + * + * @return the Y in the world distance + */ + public double screenToWorldY(double y) { + return -y / getZoomFactor() + getWorldOffsetY(); + } + + /** + * Add a drawable to the VUI. + * + * @param d + * the new drawable + */ + public void add(Drawable d) { + d.setVUI(this); + RunLaterHelper.runLater(()-> canvas.getChildren().add(d.getNode())); + drawablesLock.lock(); + drawables.add(d); + drawablesLock.unlock(); + updateCanvas(); + } + + /** + * Remove a drawable from the VUI. + * + * @param d + * the new drawable + */ + public void remove(Drawable d) { + drawablesLock.lock(); + drawables.remove(d); + drawablesLock.unlock(); + RunLaterHelper.runLater(()-> canvas.getChildren().remove(d.getNode())); + updateCanvas(); + } + + /** + * Remove all drawables from the VUI. + */ + public void clear() { + drawablesLock.lock(); + drawables.clear(); + RunLaterHelper.runLater(()->canvas.getChildren().clear()); + drawablesLock.unlock(); + } + + /** + * Refresh the canvas + */ + public void updateCanvas() { + final double w = canvas.getWidth(); + final double h = canvas.getHeight(); + + setWorldOffsetX(worldCenterX + screenToWorldDistance(w / 2)); + setWorldOffsetY(worldCenterY + screenToWorldDistance(h / 2)); + + drawablesLock.lock(); + Collections.sort(drawables, (o1, o2) -> o1.getLayer() - o2.getLayer()); + for (Drawable d : drawables) + RunLaterHelper.runLater(()-> d.onDraw()); + drawablesLock.unlock(); + + RunLaterHelper.runLater(() -> { + statusLabel.setText(String.format("Zoom: %.2f Center: (%.2f,%.2f)", zoom, worldCenterX, worldCenterY)); + }); + + RunLaterHelper.runLater(()-> vuiExplorer.update(true)); + } + + /** + * Get the width of the canvas + * + * @return the canvas width + */ + public double getCanvasWidth() { + return canvas.getWidth(); + } + + /** + * Get the height of the canvas + * + * @return the canvas height + */ + public double getCanvasHeight() { + return canvas.getHeight(); + } + + /** + * Get the value that must be added to the X coordinate of in world object + * + * @return the X offset + */ + public double getWorldOffsetX() { + return worldOffsetX; + } + + /** + * Set the value that must be added to the X coordinate of in world object + * + * @param offsetX + * the X offset + */ + public void setWorldOffsetX(double offsetX) { + this.worldOffsetX = offsetX; + } + + /** + * Get the value that must be added to the Y coordinate of in world object + * + * @return the Y offset + */ + public double getWorldOffsetY() { + return worldOffsetY; + } + + /** + * Set the value that must be added to the Y coordinate of in world object + * + * @param offsetY + * the Y offset + */ + public void setWorldOffsetY(double offsetY) { + this.worldOffsetY = offsetY; + } + + /** + * Create a point and start rendering it + * + * @param dx + * the x coordinate + * @param dy + * the y coordinate + * @return the point object + */ + public DrawablePoint createAndAddPoint(double dx, double dy) { + DrawablePoint drawablePoint = new DrawablePoint(dx, dy); + add(drawablePoint); + return drawablePoint; + } + + /** + * Create a rectangle and start rendering it + * + * @param x + * the x coordinate + * @param y + * the y coordinate + * @param w + * the width + * @param h + * the height + * @return the rectangle object + */ + public DrawableRectangle createAndAddRectangle(double x, double y, double w, double h) { + DrawableRectangle d = new DrawableRectangle(x, y, w, h); + add(d); + return d; + } + + /** + * Set the default configuration of the view + * + * @param zoom + * the initial zoom value + * @param worldCenterX + * the initial X center value + * @param worldCenterY + * the initial Y center value + */ + public void setDefaultView(double zoom, double worldCenterX, double worldCenterY) { + this.zoom = zoom; + this.worldCenterX = worldCenterX; + this.worldCenterY = worldCenterY; + this.defaultZoom = zoom; + this.defaultWorldCenterX = worldCenterX; + this.defaultWorldCenterY = worldCenterY; + } + + /** + * Create an image and start rendering it + * + * @param dx + * the x coordinate + * @param dy + * the y coordinate + * @param filename + * the filename of the image + * @return the created image + */ + public DrawableImage createAndAddImage(double dx, double dy, String filename) { + DrawableImage image = new DrawableImage(dx, dy, filename); + add(image); + return image; + } + + /** + * Create a string and start rendering it + * + * @param dx + * the x coordinate + * @param dy + * the y coordinate + * @param text + * the text to display + * @return the created string + */ + public DrawableString createAndAddString(int dx, int dy, String text) { + DrawableString ds = new DrawableString(dx, dy, text); + add(ds); + return ds; + } + + public Pane getCanvas() { + return canvas; + } + + public BorderPane getPanel() { + return panel; + } + + public List<Drawable> getDrawables() { + return drawables; + } +}