Skip to content
Snippets Groups Projects
Commit 7e782d0e authored by BrunoDatoMeneses's avatar BrunoDatoMeneses
Browse files

Merge branch 'multiUiAmakFX' into expRein

parents 342586ba d02ecd5c
Branches
No related tags found
1 merge request!4Exp rein
Showing
with 3664 additions and 2335 deletions
# This is the README from the old version of AMAK, some part may be outdated ! #
# This is a fork of AMAK, now using JavaFX instead of Swing #
[The original repo](https://bitbucket.org/perlesa/amak/overview)
This repository contains a framework made to facilitate the development of multi-agent system.
Examples are available in the package fr.irit.smac.amak.examples.
# [Click here to download the latest standalone version](https://bitbucket.org/perlesa/amak/raw/master/Release/AmakFramework-standalone.jar?at=master) #
## AMAKFX changes (2019-07-25) :
These changes were made for a particular project, without much care for backward compatibility. But the overall philosophy should be the same.
# Recent changes #
### Possible breaking changes :
+ Anything related with the GUI in Swing.
+ Support for asynchronous agent has been dropped, you may still use then, but no promise are made for the visualization.
### Changes :
+ Build system is now maven.
+ Use JavaFX instead of Swing. JavaFX run on a different thread than the simulation, when updating the GUI from the simulation thread you must be extra careful for :
+ Running JavaFX code on the JavaFX thread, by only using AMAKFX methods, or by learning how to use JavaFX's RunLater (see AMAKFX's [RunLaterHelper](src/fr/irit/smac/amak/tools/RunLaterHelper.java))
+ Not overload JavaFX call queue with draw call, leading to unpredictable result. For that purpose a new option has been added to Configuration : waitForGui, default at true. Each simulation cycle, AMAK will wait for JavaFX call queue to be empty. But queue overload can still happen inside a cycle. Symptoms are : long freeze, part of GUI not updating, or becoming white.
+ The VUI has been overhauled, it now provide extra features :
+ Drawable can detect events, and dispatch these events to linked drawables.
+ VUI Explorer, a sided bar that show a list of visible Drawable in its VUI. Features : hovering an element in the VUI Explorer highlight it in the VUI. Search for element in the VUI Explorer with regex. Click an element to display additional info (when available).
+ Plotting is now done with JFreeChart, AMAKFX provide an helper class : AmakPlot.
+ Tabs can be drag-n-dropped to rearrange them. Dropping a tab outside the tab bar will open it in a new window (despite the mouse cursor showing it's impossible)
+ Changes on how logging work, the a Log object now accept multiple action. Added the Loggable interface,with default methods, allowing a class to easily log to a file.
# Old AMAK README #
## 1.5.3 (11/28/2018) ##
### New features: none
......
This diff is collapsed.
......@@ -13,9 +13,11 @@ 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;
import fr.irit.smac.amak.ui.VUIMulti;
/**
* This class must be overridden by multi-agent systems
......@@ -26,6 +28,10 @@ import fr.irit.smac.amak.ui.VUI;
* The environment of the MAS
*/
public class Amas<E extends Environment> implements Schedulable {
public AmasMultiUIWindow amasMultiUIWindow;
public VUIMulti vuiMulti;
/**
* List of agents present in the system
*/
......@@ -155,6 +161,43 @@ public class Amas<E extends Environment> implements Schedulable {
this.onRenderingInitialization();
this.scheduler.unlock();
}
public Amas(AmasMultiUIWindow window, VUIMulti vui, E environment, Scheduling scheduling, Object... params) {
if(!Configuration.commandLineMode) {
amasMultiUIWindow = window;
vuiMulti = vui;
amasMultiUIWindow.addTabbedPanel(vuiMulti.title, vuiMulti.getPanel());
}
executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(Configuration.allowedSimultaneousAgentsExecution);
//this.scheduler = environment.getScheduler();
if (scheduling == Scheduling.DEFAULT) {
this.scheduler = Scheduler.getDefaultMultiUIScheduler(window);
this.scheduler.add(this);
} else {
this.scheduler = new Scheduler(this);
if (scheduling == Scheduling.UI && !Configuration.commandLineMode) {
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 +414,19 @@ public class Amas<E extends Environment> implements Schedulable {
* {@link Amas#onRenderingInitialization}
*/
protected void onUpdateRender() {
VUI.get().updateCanvas();
if(Configuration.multiUI) {
vuiMulti.updateCanvas();
}else {
VUI.get().updateCanvas();
}
}
public VUIMulti getVUIMulti() {
return vuiMulti;
}
/**
......
......@@ -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;
}
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.getDefaultMultiUIScheduler(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() {
}
}
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 getDefaultMultiUIScheduler(AmasMultiUIWindow window) {
Scheduler multiUIScheduler = new Scheduler();
if (!Configuration.commandLineMode) {
SchedulerToolbar st = new SchedulerToolbar("Default", multiUIScheduler);
window.addToolbar(st);
}
return multiUIScheduler;
}
/**
* 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.examples.asyncrandomants;
import fr.irit.smac.amak.Configuration;
import fr.irit.smac.amak.examples.randomants.AntExample;
import fr.irit.smac.amak.examples.randomants.AntHillExample;
import fr.irit.smac.amak.examples.randomants.WorldExample;
import fr.irit.smac.amak.ui.MainWindow;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
/**
* Class aiming at starting the mas-less ants system
*
* @author perles
*
*/
public class AsyncAntsLaunchExample {
/**
* Launch method
*
* @param args
* Main arguments
*/
public static void main(String[] args) {
Configuration.allowedSimultaneousAgentsExecution = 4;
WorldExample env = new WorldExample();
AntHillExample amas = new AntHillExample(env);
for (int i = 0; i < 50; i++)
new AntExample(amas, 0, 0);
Pane panel = new Pane();
String content = "Async AntHill simulation\n\n" + "Ants move randomly.\n"
+ "This demo is here to show AMAK asynchronous agent capacities.";
Label label = new Label(content);
label.setStyle("-fx-font-weight: bold;");
panel.getChildren().add(label);
MainWindow.setLeftPanel(panel);
}
}
/**
* This package contains an example showing ants moving randomly.
* In this example, agents are fully asynchronous.
*/
package fr.irit.smac.amak.examples.asyncrandomants;
package fr.irit.smac.amak.examples.randomants;
import fr.irit.smac.amak.Agent;
import fr.irit.smac.amak.ui.VUI;
import fr.irit.smac.amak.ui.drawables.DrawableImage;
public class AntExample extends Agent<AntHillExample, WorldExample> {
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 AntExample(AntHillExample amas, double startX, double startY) {
super(amas, startX, startY);
}
@Override
public void onInitialization() {
dx = (double) params[0];
dy = (double) params[1];
}
@Override
protected void onRenderingInitialization() {
image = VUI.get().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 AntExample(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");
}
}
}
package fr.irit.smac.amak.examples.randomants;
import fr.irit.smac.amak.Agent;
import fr.irit.smac.amak.ui.VUI;
import fr.irit.smac.amak.ui.drawables.DrawableImage;
public class AntExample extends Agent<AntHillExample, WorldExample> {
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 AntExample(AntHillExample amas, double startX, double startY) {
super(amas, startX, startY);
}
@Override
public void onInitialization() {
dx = (double) params[0];
dy = (double) params[1];
}
@Override
protected void onRenderingInitialization() {
image = VUI.get().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 AntExample(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");
}
}
}
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;
}
}
package fr.irit.smac.amak.examples.randomantsMultiUi;
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);
}
@Override
public void onInitialization() {
dx = (double) params[0];
dy = (double) params[1];
}
@Override
protected void onRenderingInitialization() {
image = getAmas().getVUIMulti().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(getAmas().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");
}
}
}
package fr.irit.smac.amak.examples.randomantsMultiUi;
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.VUIMulti;
import fr.irit.smac.amak.ui.drawables.DrawableString;
public class AntHillExampleMultiUI extends Amas<WorldExampleMultiUI> {
private DrawableString antsCountLabel;
public AntHillExampleMultiUI(AmasMultiUIWindow window, VUIMulti vui, WorldExampleMultiUI env) {
super(window, vui, env, Scheduling.DEFAULT);
}
@Override
protected void onRenderingInitialization() {
vuiMulti.createAndAddImage(20, 20, "file:Resources/ant.png").setFixed().setLayer(10).setShowInExplorer(false);
antsCountLabel = (DrawableString) vuiMulti.createAndAddString(45, 25, "Ants count").setFixed().setLayer(10).setShowInExplorer(false);
}
@Override
protected void onInitialAgentsCreation() {
for (int i = 0; i < 50; i++) {
new AntExampleMutliUI(amasMultiUIWindow, this, 0, 0);
}
}
@Override
protected void onSystemCycleEnd() {
RunLaterHelper.runLater(()->antsCountLabel.setText("Ants count: " + getAgents().size()));
}
}
package fr.irit.smac.amak.examples.randomantsMultiUi;
import fr.irit.smac.amak.Configuration;
import fr.irit.smac.amak.ui.AmasMultiUIWindow;
import fr.irit.smac.amak.ui.MainWindow;
import fr.irit.smac.amak.ui.VUIMulti;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class AntsLaunchExampleMultiUI extends Application{
public static void main (String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
Configuration.multiUI=true;
Configuration.commandLineMode =false;
AmasMultiUIWindow window = new AmasMultiUIWindow("Random Ants Multi UI 1");
AmasMultiUIWindow window2 = new AmasMultiUIWindow("Random Ants Multi UI 2");
WorldExampleMultiUI env = new WorldExampleMultiUI(window);
WorldExampleMultiUI env2 = new WorldExampleMultiUI(window2);
AntHillExampleMultiUI ants = new AntHillExampleMultiUI(window, new VUIMulti("Ants VUI 1"), env);
AntHillExampleMultiUI ants2 = new AntHillExampleMultiUI(window2, new VUIMulti("Ants VUI 2"), env2);
startTask(ants, 500, 10);
startTask(ants2, 250, 30);
}
public void startTask(AntHillExampleMultiUI amas, long wait, int cycles)
{
// Create a Runnable
Runnable task = new Runnable()
{
public void run()
{
runTask(amas, wait, cycles);
}
};
// Run the task in a background thread
Thread backgroundThread = new Thread(task);
// Terminate the running thread if the application exits
backgroundThread.setDaemon(true);
// Start the thread
backgroundThread.start();
}
public void runTask(AntHillExampleMultiUI amas, long wait, int cycles)
{
for(int i = 0; i < cycles; i++)
{
try
{
// Get the Status
final String status = "Processing " + i + " of " + cycles;
// Update the Label on the JavaFx Application Thread
Platform.runLater(new Runnable()
{
@Override
public void run()
{
amas.cycle();
System.out.println(status);
}
});
Thread.sleep(wait);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
@Override
public void stop() throws Exception {
super.stop();
System.exit(0);
}
}
package fr.irit.smac.amak.examples.randomantsMultiUi;
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;
}
}
......@@ -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;
}
......
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;
/**
* 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(String title) {
RunLaterHelper.runLater(() -> {
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(title);
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();
});
}
// @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
This diff is collapsed.
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 VUIMulti {
public String title;
/**
* 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, VUIMulti> 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;
/**
* 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
*/
public VUIMulti(String titleValue) {
RunLaterHelper.runLater(() -> {
this.title = titleValue;
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);
});
}
/**
* 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.setVUIMulti(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;
}
}
......@@ -23,13 +23,14 @@ import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
/**
* A piece of GUI allowing to see and look for contexts.
* A piece of GUI allowing to see and look for agents.
* @author Hugo
*
*/
public class VuiExplorer extends ScrollPane {
private VUI vui;
private VUI vui = null;
private VUIMulti vuiMulti = null;
private VBox vbox;
private TitledPane contextsPane;
......@@ -102,6 +103,74 @@ public class VuiExplorer extends ScrollPane {
RunLaterHelper.runLater(()->vui.getPanel().setLeft(this));
}
public VuiExplorer(VUIMulti vuiMlt) {
this.vuiMulti = vuiMlt;
this.setMaxWidth(Double.MAX_VALUE);
this.setMaxHeight(Double.MAX_VALUE);
vbox = new VBox();
vbox.setFillWidth(true);
this.setContent(vbox);
// refresh, close, and collapseAll button
HBox hboxButtons = new HBox();
Button refresh = new Button("Refresh");
refresh.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
update();
}
});
Button close = new Button("Close");
close.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
vuiMulti.getPanel().setLeft(null);
}
});
Button collapseAll = new Button("Collapse all");
collapseAll.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
collapseAll();
}
});
hboxButtons.getChildren().addAll(refresh, close, collapseAll);
// check box
autoRefresh = new CheckBox("Auto refresh");
autoRefresh.setTooltip(new Tooltip("Try to automatically refresh the VUI explorer when the VUI is updated."));
// search bar
search = new TextField();
search.setPromptText("regular expression");
// update list on change
search.textProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
search.setStyle(null);
try {
update();
} catch (PatternSyntaxException ex) {
search.setStyle("-fx-border-color: red;");
}
}
});
cpVBox = new VBox();
contextsPane = new TitledPane("Drawables", cpVBox);
vbox.getChildren().addAll(hboxButtons, autoRefresh, search, contextsPane);
update();
// Add to vui
RunLaterHelper.runLater(()->vuiMulti.getPanel().setLeft(this));
}
public void update(boolean auto) {
if(auto && autoRefresh.isSelected()) {
......@@ -113,7 +182,13 @@ public class VuiExplorer extends ScrollPane {
* Update the list of context
*/
public void update() {
List<Drawable> drawableList = vui.getDrawables();
List<Drawable> drawableList = null;
if(vui != null) {
drawableList = vui.getDrawables();
}
if(vuiMulti != null) {
drawableList = vuiMulti.getDrawables();
}
// crude color sort
drawableList.sort(new Comparator<Drawable>() {
@Override
......@@ -142,7 +217,13 @@ public class VuiExplorer extends ScrollPane {
}
private void collapseAll() {
List<Drawable> drawableList = vui.getDrawables();
List<Drawable> drawableList = null;
if(vui != null) {
drawableList = vui.getDrawables();
}
if(vuiMulti != null) {
drawableList = vuiMulti.getDrawables();
}
for(Drawable d : drawableList) {
if(d.showInExplorer && d.isVisible()) {
Drawable mini = d.getLinkedDrawable("mini");
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment