diff --git a/src/example/philosophes/Dechets.java b/src/example/philosophes/Dechets.java new file mode 100644 index 0000000000000000000000000000000000000000..465901d39d1ff2230c736c945573842291613ec9 --- /dev/null +++ b/src/example/philosophes/Dechets.java @@ -0,0 +1,37 @@ +package example.philosophes; + +import mas.core.Cyclable; +import mas.core.Schedulable; + +import java.util.concurrent.atomic.AtomicInteger; + +public class Dechets implements Cyclable { + + private int id; + + Schedulable scheduleur = null; + + public Dechets(int _id){ + id = _id; + + } + @Override + public void cycle() { + System.out.println("je suis le dechet n°" + id); + } + + @Override + public boolean terminate() { + return true; + } + + @Override + public void setScheduleur(Schedulable _scheduleur) { + scheduleur = _scheduleur; + } + + @Override + public String toString() { + return "Dechet " + id; + } +} diff --git a/src/example/philosophes/Fork.java b/src/example/philosophes/Fork.java new file mode 100644 index 0000000000000000000000000000000000000000..3e150ea5c2f1723c9276a0afb1a1bcb9d1ce38e0 --- /dev/null +++ b/src/example/philosophes/Fork.java @@ -0,0 +1,30 @@ +package example.philosophes; + +public class Fork { + + private Philosophe takenBy; + + public synchronized boolean tryTake(Philosophe asker){ + if(takenBy != null){ + return false; + } + takenBy = asker; + return true; + } + + public synchronized void release(Philosophe asker){ + if(takenBy == asker){ + takenBy = null; + } + } + + public synchronized boolean owned(Philosophe asker){ + return takenBy == asker; + } + + public Philosophe getTakenBy() { + if (takenBy == null) + return new Philosophe(999, null, null, null, null); + return takenBy; + } +} diff --git a/src/example/philosophes/MainPhilosophe.java b/src/example/philosophes/MainPhilosophe.java new file mode 100644 index 0000000000000000000000000000000000000000..c1b976fda9ae4396f2dc692eb0f6ccdfd1f19360 --- /dev/null +++ b/src/example/philosophes/MainPhilosophe.java @@ -0,0 +1,63 @@ +package example.philosophes; + +import mas.core.Schedulable; +import mas.implementation.base.schedulers.TwoDCycling; +import mas.ui.MainWindow; +import mas.ui.SchedulerToolbar; + +public class MainPhilosophe { + + public static void main(String[] args) { + + final long startTime = System.nanoTime(); + + int nAgents = 6; + + Philosophe[] philosophes = new Philosophe[nAgents]; + Fork[] forks = new Fork[nAgents]; + + for (int i = 0; i<nAgents ; i++){ + forks[i] = new Fork(); + } + + for(int i = 0; i<nAgents ; i++){ + philosophes[i] = new Philosophe(i, forks[(((i-1) % nAgents) + nAgents) % nAgents], forks[i], null, null); + } + + for (int i = 0; i<nAgents ; i++){ + philosophes[i].setLeftPhilosophe(philosophes[(((i-1) % nAgents) + nAgents) % nAgents]); + philosophes[i].setRightPhilosophe(philosophes[(i+1) % nAgents]); + } + + Schedulable scheduleur = new FairCycling(philosophes); + //scheduleur.setSleep(500); + scheduleur.start(); + + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + scheduleur.pause(); + + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + scheduleur.resume(); + + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + scheduleur.stop(); + + + final long endTime = System.nanoTime(); + System.out.println("Total execution time: " + (endTime / 1000000 - startTime / 1000000) + " microseconds");*/ + } +} diff --git a/src/example/philosophes/Philosophe.java b/src/example/philosophes/Philosophe.java new file mode 100644 index 0000000000000000000000000000000000000000..761f71765995663e71cf643f878c40179f35b9c3 --- /dev/null +++ b/src/example/philosophes/Philosophe.java @@ -0,0 +1,210 @@ +package example.philosophes; + +import mas.core.Agent; +import mas.core.Schedulable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class Philosophe extends Agent { + + private Fork leftFork; + + private Fork rightFork; + + private Philosophe rightPhilosophe; + + private Philosophe leftPhilosophe; + + /** + * The amount of time (in cycle) the philosopher haven't ate (while in state + * hungry) + */ + private double hungerDuration; + /** + * The amount of eaten pastas + */ + private double eatenPastas; + + /** + * The id of the philosopher + */ + private int id; + + private Schedulable scheduleur; + + /** + * States philosophers can be in + */ + public enum State { + /** + * The philosopher is thinking. It essentially means that they are not hungry + * and not eating + */ + THINK, + /** + * The philosopher is hungry. He wants to be in the state eating. + */ + HUNGRY, + /** + * The philosopher has obtained the two forks and eat. + */ + EATING + } + + private State state = State.THINK; + + private double criticallity; + + public Philosophe(int _id, Fork _leftFork, Fork _rightFork, Philosophe _rightPhilosophe, Philosophe _leftPhilosophe) { + id = _id; + leftFork = _leftFork; + rightFork = _rightFork; + rightPhilosophe = _rightPhilosophe; + leftPhilosophe = _leftPhilosophe; + } + + @Override + public void perceive() { + //System.out.println("Philosopher num " + id + " perceive"); + criticallity = computeCriticallity(); + } + + @Override + public void decide() { + //System.out.println("Philosopher num " + id + " decide"); + + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + State nextState = state; + switch (state) { + + case EATING ->{ + eatenPastas++; + if (new Random().nextInt(101) > 50) { + leftFork.release(this); + rightFork.release(this); + nextState = State.THINK; + } + } + + case HUNGRY -> { + hungerDuration++; + if (getMostCriticalNeighbor() == this) { + leftFork.tryTake(this); + rightFork.tryTake(this); + if (leftFork.owned(this) && rightFork.owned(this)) + nextState = State.EATING; + + } else { + leftFork.release(this); + rightFork.release(this); + } + } + case THINK->{ + if (new Random().nextInt(101) > 50) { + hungerDuration = 0; + nextState = State.HUNGRY; + } + } + + default -> {} + } + + state = nextState; + //System.out.println("Philospher n°" + id + " / State " + state); + System.out.println( + "\tPhilosopher num " + id + " : " + state + " / " + criticallity + " / " + eatenPastas + /*+ "\n\t\t Right Fk : " + rightFork.getTakenBy().getId() + " / Left Fk : " + leftFork.getTakenBy().getId()*/ + ); + } + + private Philosophe getMostCriticalNeighbor() { + if(leftPhilosophe.getCriticallity() < rightPhilosophe.getCriticallity()){ + if (rightPhilosophe.getCriticallity() < this.getCriticallity()) + return this; + return rightPhilosophe; + } else { + if (leftPhilosophe.getCriticallity() < this.getCriticallity()) + return this; + return leftPhilosophe; + } + } + + private double computeCriticallity(){ + + if (state == State.HUNGRY){ + return hungerDuration; + } + return -1; + } + + @Override + public void act() { + //System.out.println("Philosopher num " + id + " act"); + scheduleur.addCyclable(new Dechets(id)); + } + + @Override + public boolean terminate() { + return false; + } + + public int getId() { + return id; + } + + public double getEatenPastas() { + return eatenPastas; + } + + public double getHungerDuration() { + return hungerDuration; + } + + public Fork getLeftFork() { + return leftFork; + } + + public Fork getRightFork() { + return rightFork; + } + + public Philosophe getLeftPhilosophe() { + return leftPhilosophe; + } + + public Philosophe getRightPhilosophe() { + return rightPhilosophe; + } + + public double getCriticallity() { + return criticallity; + } + + public void setLeftPhilosophe(Philosophe leftPhilosophe) { + this.leftPhilosophe = leftPhilosophe; + } + + public void setRightPhilosophe(Philosophe rightPhilosophe) { + this.rightPhilosophe = rightPhilosophe; + } + + public void setScheduleur(Schedulable scheduleur) { + this.scheduleur = scheduleur; + } + + public Schedulable getScheduleur() { + return scheduleur; + } + + @Override + public String toString() { + return "Philosophe " + id; + } +} diff --git a/src/mas/core/Agent.java b/src/mas/core/Agent.java new file mode 100644 index 0000000000000000000000000000000000000000..d4b955b3308628c5c1625b3674b8eb2ef433c10e --- /dev/null +++ b/src/mas/core/Agent.java @@ -0,0 +1,31 @@ +package mas.core; + +public class Agent implements ThreeStepCyclable{ + + private Schedulable scheduleur; + + @Override + public void perceive() { + + } + + @Override + public void decide() { + + } + + @Override + public void act() { + + } + + @Override + public boolean terminate() { + return false; + } + + @Override + public void setScheduleur(Schedulable _scheduleur) { + scheduleur = _scheduleur; + } +} diff --git a/src/mas/core/Cyclable.java b/src/mas/core/Cyclable.java new file mode 100644 index 0000000000000000000000000000000000000000..c272b066b98e525ab4f2190f4720eb4ea3896fe6 --- /dev/null +++ b/src/mas/core/Cyclable.java @@ -0,0 +1,23 @@ +package mas.core; + +/** + * A cyclable objet + */ +public interface Cyclable { + + /** + * TODO + */ + void cycle(); + + /** + * TODO + * @return + */ + boolean terminate(); + + /** + * TODO + */ + void setScheduleur(Schedulable _scheduleur); +} diff --git a/src/mas/core/Schedulable.java b/src/mas/core/Schedulable.java new file mode 100644 index 0000000000000000000000000000000000000000..a868b93c20b35a570b98dbf399f6456272150c7f --- /dev/null +++ b/src/mas/core/Schedulable.java @@ -0,0 +1,53 @@ +package mas.core; + +/** + * A schedulable object can be controlled by a scheduler + * + */ +public interface Schedulable { + + public static final int DEFAULT_SLEEP = 0; + + /** + * Launch the scheduler if it is not running + */ + void start(); + + /** + * Stops the scheduler if it is running + */ + void stop(); + + /** + * Pause the scheduler at the end of the current cycle + */ + void pause(); + + /** + * Resumes the scheduler in his current state + */ + void resume(); + + /** + * TODO + */ + int getSleep(); + + /** + * TODO + * @param sleep + */ + void setSleep(int sleep); + + /** + * + */ + void addCyclable(Cyclable cyclable); + + + /** + * TODO + * @return + */ + boolean stopCondition(); +} diff --git a/src/mas/core/ThreeStepCyclable.java b/src/mas/core/ThreeStepCyclable.java new file mode 100644 index 0000000000000000000000000000000000000000..99a01727b03ac903efed80a4d7c33a1c3259d4b7 --- /dev/null +++ b/src/mas/core/ThreeStepCyclable.java @@ -0,0 +1,31 @@ +package mas.core; + +import mas.core.Cyclable; + +/** + * TODO + */ +public interface ThreeStepCyclable extends Cyclable { + + @Override + default void cycle(){ + perceive(); + decide(); + act(); + }; + + /** + * TODO + */ + void perceive(); + + /** + * TODO + */ + void decide(); + + /** + * TODO + */ + void act(); +} diff --git a/src/mas/core/TwoStepCyclable.java b/src/mas/core/TwoStepCyclable.java new file mode 100644 index 0000000000000000000000000000000000000000..6324ccd5f2b9f12d5aa354d3e12ebebc097b3614 --- /dev/null +++ b/src/mas/core/TwoStepCyclable.java @@ -0,0 +1,22 @@ +package mas.core; + +import mas.core.Cyclable; + +public interface TwoStepCyclable extends Cyclable { + + @Override + default void cycle(){ + perceive(); + decideAndAct(); + }; + + /** + * TODO + */ + void perceive(); + + /** + * TODO + */ + void decideAndAct(); +} diff --git a/src/mas/environment/TwoDContinuosGrid.java b/src/mas/environment/TwoDContinuosGrid.java new file mode 100644 index 0000000000000000000000000000000000000000..4ae7f991c0e1ea0a73e91d8bcc034ceafde4815d --- /dev/null +++ b/src/mas/environment/TwoDContinuosGrid.java @@ -0,0 +1,5 @@ +package mas.environment; + +public class TwoDContinuosGrid { + +} diff --git a/src/mas/implementation/base/schedulers/AsyncCycling.java b/src/mas/implementation/base/schedulers/AsyncCycling.java new file mode 100644 index 0000000000000000000000000000000000000000..99c821b41f5e0bc580e8715d4fc3feafbf412b46 --- /dev/null +++ b/src/mas/implementation/base/schedulers/AsyncCycling.java @@ -0,0 +1,109 @@ +package mas.implementation.base.schedulers; + +import mas.core.Cyclable; +import mas.core.Schedulable; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.*; + +/** + * Zero administration + */ +public class AsyncCycling implements Schedulable { + + private final Set<Cyclable> cyclables = new LinkedHashSet<>(); + + private int sleep = DEFAULT_SLEEP; + + boolean mustStop = false; + private PausableThreadPoolExecutor executor = new PausableThreadPoolExecutor(); + + public AsyncCycling(Cyclable... _cyclables){ + + for(Cyclable cyclable : _cyclables){ + cyclables.add(cyclable); + cyclable.setScheduleur(this); + } + } + + @Override + public void start() { + System.out.println("Je fait start"); + for (Cyclable cyclable : cyclables){ + executor.execute(() -> { + manageCyclable(cyclable); + }); + } + } + + @Override + public void stop() { + System.out.println("Je fait stop"); + mustStop = true; + executor.shutdown(); + try { + executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + @Override + public void pause() { + System.out.println("Je fait pause"); + executor.pause(); + } + + @Override + public void resume() { + System.out.println("Je fait resume"); + executor.resume(); + } + + @Override + public void addCyclable(Cyclable cyclable) { + cyclables.add(cyclable); + cyclable.setScheduleur(this); + + if(!mustStop){ + executor.execute(() -> { + manageCyclable(cyclable); + }); + } + } + + @Override + public boolean stopCondition() { + return false; + } + + @Override + public int getSleep() { + return sleep; + } + + @Override + public void setSleep(int sleep) { + this.sleep = sleep; + } + + private void manageCyclable(Cyclable cyclable){ + cyclable.cycle(); + + try { + Thread.sleep(sleep); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + if(!cyclable.terminate() && !mustStop){ + executor.execute(() -> manageCyclable(cyclable)); + } else { + cyclables.remove(cyclable); + } + } + + + +} diff --git a/src/mas/implementation/base/schedulers/FairCycling.java b/src/mas/implementation/base/schedulers/FairCycling.java new file mode 100644 index 0000000000000000000000000000000000000000..aeb42f01d4ddecc62f7d69740c01c5cdf944bd41 --- /dev/null +++ b/src/mas/implementation/base/schedulers/FairCycling.java @@ -0,0 +1,154 @@ +package mas.implementation.base.schedulers; + +import mas.core.Cyclable; +import mas.core.Schedulable; + +import java.util.LinkedHashSet; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.*; + +/** + * Chaque agent execute exactement 1 cycle pour chaque cycle systeme + */ +public class FairCycling implements Schedulable { + + private Set<Cyclable> cyclables = new LinkedHashSet<>(); + private Queue<Cyclable> pendingToAddCyclables = new ConcurrentLinkedQueue<>(); + + //private Queue<Cyclable> pendingToRemoveCyclables = new ConcurrentLinkedQueue<>(); + + private int sleep = DEFAULT_SLEEP; + + private int nbOfCycles = 0; + + boolean mustStop = false; + + boolean mustPause = false; + + ExecutorService executor = Executors.newCachedThreadPool(); + + CountDownLatch pauseLatch; + CountDownLatch latch; + + public FairCycling(Cyclable... _cyclables) { + + for (Cyclable cyclable : _cyclables) { + addCyclable(cyclable); + } + } + + @Override + public void start() { + System.out.println("Je fait start"); + new Thread(() -> doCycle()).start(); + } + + @Override + public void stop() { + System.out.println("Je fait stop"); + mustStop = true; + executor.shutdown(); + } + + @Override + public void pause() { + System.out.println("Je fait pause"); + pauseLatch = new CountDownLatch(1); + mustPause = true; + } + + @Override + public void resume() { + System.out.println("Je fait resume"); + pauseLatch.countDown(); + } + + @Override + public boolean stopCondition(){ + return false; + } + + @Override + public void addCyclable(Cyclable cyclable){ + //System.out.println("Je fait addCyclebles : " + cyclable.toString()); + cyclable.setScheduleur(this); + pendingToAddCyclables.add(cyclable); + } + + @Override + public int getSleep() { + return sleep; + } + + @Override + public void setSleep(int sleep) { + this.sleep = sleep; + } + + protected void step() { + System.out.println("Je fait step"); + nbOfCycles++; + System.out.println("cycle systeme " + nbOfCycles); + + //System.out.println(pendingToAddCyclables.size() + " : " + pendingToAddCyclables); + + treatPendingCyclables(); + + latch = new CountDownLatch(cyclables.size()); + + for (Cyclable cyclable : cyclables) { + + executor.execute(() -> { + cyclable.cycle(); + if(!cyclable.terminate()){ + //System.out.println("\t\t" + cyclable.toString() + " je fait pendingToAdd"); + pendingToAddCyclables.add(cyclable); + } + + latch.countDown(); + }); + + } + + if (getSleep() != 0) { + try { + Thread.sleep(getSleep()); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + } + + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + cyclables.clear(); + } + + protected void doCycle() { + System.out.println("Je fait doCycle"); + step(); + if(stopCondition()){ + this.stop(); + } + + if (!mustStop) { + if (mustPause) { + try { + pauseLatch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + executor.execute(() -> doCycle()); + } + } + + private void treatPendingCyclables() { + while (!pendingToAddCyclables.isEmpty()) + cyclables.add(pendingToAddCyclables.poll()); + } +} diff --git a/src/mas/implementation/base/schedulers/FairPosCycling.java b/src/mas/implementation/base/schedulers/FairPosCycling.java new file mode 100644 index 0000000000000000000000000000000000000000..3100732f64f8c7331620d3eb46889b4d1939a05c --- /dev/null +++ b/src/mas/implementation/base/schedulers/FairPosCycling.java @@ -0,0 +1,49 @@ +package mas.implementation.base.schedulers; + +import mas.core.Cyclable; +import mas.core.Schedulable; + +/** + * Même que fair + equitable sur position des Round Robin + */ +public class FairPosCycling implements Schedulable { + @Override + public void start() { + + } + + @Override + public void stop() { + + } + + @Override + public void pause() { + + } + + @Override + public void resume() { + + } + + @Override + public int getSleep() { + return 0; + } + + @Override + public void setSleep(int sleep) { + + } + + @Override + public void addCyclable(Cyclable cyclable) { + + } + + @Override + public boolean stopCondition() { + return false; + } +} diff --git a/src/mas/implementation/base/schedulers/PausableThreadPoolExecutor.java b/src/mas/implementation/base/schedulers/PausableThreadPoolExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..85299435cb680dd7270d1d803fa3d5abadd17c06 --- /dev/null +++ b/src/mas/implementation/base/schedulers/PausableThreadPoolExecutor.java @@ -0,0 +1,77 @@ +package mas.implementation.base.schedulers; + +import java.util.concurrent.*; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * A light wrapper around the {@link ThreadPoolExecutor}. It allows for you to pause execution and + * resume execution when ready. It is very handy for games that need to pause. + * + * @author Matthew A. Johnston (warmwaffles) + */ +public class PausableThreadPoolExecutor extends ThreadPoolExecutor { + private boolean isPaused; + private ReentrantLock lock; + private Condition condition; + + /** + * @see {@link ThreadPoolExecutor#ThreadPoolExecutor(int, int, long, TimeUnit, BlockingQueue)} + */ + public PausableThreadPoolExecutor() { + super(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); + lock = new ReentrantLock(); + condition = lock.newCondition(); + } + + /** + * @param thread The thread being executed + * @param runnable The runnable task + * @see {@link ThreadPoolExecutor#beforeExecute(Thread, Runnable)} + */ + @Override + protected void beforeExecute(Thread thread, Runnable runnable) { + super.beforeExecute(thread, runnable); + lock.lock(); + try { + while (isPaused) condition.await(); + } catch (InterruptedException ie) { + thread.interrupt(); + } finally { + lock.unlock(); + } + } + + public boolean isRunning() { + return !isPaused; + } + + public boolean isPaused() { + return isPaused; + } + + /** + * Pause the execution + */ + public void pause() { + lock.lock(); + try { + isPaused = true; + } finally { + lock.unlock(); + } + } + + /** + * Resume pool execution + */ + public void resume() { + lock.lock(); + try { + isPaused = false; + condition.signalAll(); + } finally { + lock.unlock(); + } + } +} \ No newline at end of file diff --git a/src/mas/implementation/base/schedulers/TwoDCycling.java b/src/mas/implementation/base/schedulers/TwoDCycling.java new file mode 100644 index 0000000000000000000000000000000000000000..0c9067e8653e0506601e9af239ae533373d0f1f6 --- /dev/null +++ b/src/mas/implementation/base/schedulers/TwoDCycling.java @@ -0,0 +1,114 @@ +package mas.implementation.base.schedulers; + +import mas.core.Cyclable; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; + +public class TwoDCycling extends FairCycling{ + + /** + * The state of the scheduler {@link State} + */ + private State state; + + /** + * Method that is called when the scheduler stops + */ + private Consumer<TwoDCycling> onStop; + + /** + * The methods called when the speed is changed. Useful to change the value of + * the GUI slider of + */ + + 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 + + } + private List<Consumer<TwoDCycling>> onChange = new ArrayList<>(); + + public TwoDCycling(Cyclable... _cyclables){ + super(_cyclables); + } + + /** + * Set the method that must be executed when the system is stopped + * + * @param _onStop + * Consumer method + */ + public final void setOnStop(Consumer<TwoDCycling> _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<TwoDCycling> _onChange) { + synchronized (onChange) { + this.onChange.add(_onChange); + } + } + + public void doOneCycle() { + System.out.println("Je fait doOneCycle"); + step(); + if(stopCondition()){ + this.stop(); + } + + if (!mustStop) { + this.pause(); + } + } + public void startWithSleep(int _sleep){ + System.out.println("Je commence startWithSleep(" + _sleep + ")"); + setSleep(_sleep); + state = State.RUNNING; + resume(); + executor.execute(() -> doCycle()); + } + + + public boolean isRunning() { + return state == State.RUNNING; + } + + @Override + public void start() { + state = State.RUNNING; + new Thread(() -> doCycle()).start(); + } + + @Override + public void pause() { + state = State.IDLE; + pauseLatch = new CountDownLatch(1); + mustPause = true; + } + + @Override + public void resume() { + if(pauseLatch != null){ + pauseLatch.countDown(); + } + state = State.RUNNING; + } +} diff --git a/src/mas/ui/MainWindow.java b/src/mas/ui/MainWindow.java new file mode 100644 index 0000000000000000000000000000000000000000..a30f45386dbb2ccef3a10dc99fd53305a07e76f5 --- /dev/null +++ b/src/mas/ui/MainWindow.java @@ -0,0 +1,209 @@ +package mas.ui; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.event.ActionListener; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; + +import javax.swing.ImageIcon; +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JSplitPane; +import javax.swing.JTabbedPane; +import javax.swing.JToolBar; + +/** + * This window is the main one of an AMAS developed using AMAK. It contains a + * toolbar panel and various spaces for panels + * + * @author Alexandre Perles, Marcillaud Guilhem + * + */ +public class MainWindow extends JFrame { + + /** + * Unique ID meant to handle serialization correctly + */ + private static final long serialVersionUID = 2607956693857748227L; + /** + * The panel which contains the toolbar + */ + private JPanel toolbarPanel; + /** + * The main panel is split in two panels. This allows to dynamically resize + * these two panels. + */ + private JSplitPane splitPane; + /** + * The menu bar of the window + */ + private JMenuBar menuBar; + /** + * The option menu + */ + private JMenu optionsMenu; + /** + * The panel in which panels with tab can be added + */ + private JTabbedPane tabbedPanel; + /** + * The listener of the window here to detect the closing of the window and + * execute specific actions. + */ + private final MainWindowListener mainWindowListener; + /** + * For an AMAK process it can only be one instance of MainWindow + */ + private static MainWindow instance; + /** + * Lock present to avoid the creation of a MainWindow while another is creating + */ + private static ReentrantLock instanceLock = new ReentrantLock(); + + /** + * Create the frame. + */ + private MainWindow() { + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + mainWindowListener = new MainWindowListener(this); + addWindowListener(mainWindowListener); + setBounds(100, 100, 450, 300); + toolbarPanel = new JPanel(); + getContentPane().add(toolbarPanel, BorderLayout.SOUTH); + toolbarPanel.setLayout(new FlowLayout(FlowLayout.LEFT, 5, 5)); + + splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + getContentPane().add(splitPane, BorderLayout.CENTER); + + tabbedPanel = new JTabbedPane(); + splitPane.setRightComponent(tabbedPanel); + + menuBar = new JMenuBar(); + optionsMenu = new JMenu("Options"); + menuBar.add(optionsMenu); + getContentPane().add(menuBar, BorderLayout.NORTH); + + JMenuItem menuItem = new JMenuItem("Close"); + menuItem.addActionListener(l -> System.exit(0)); + optionsMenu.add(menuItem); + + menuBar.add(new JMenu("AMAK v")); + + setVisible(true); + + } + + /** + * Add a close action to the listener + * + * @param onClose + * The action to be executed when the window is closed + */ + public static void addOnCloseAction(Consumer<MainWindow> onClose) { + instance().mainWindowListener.addOnCloseAction(onClose); + } + + /** + * Change the icon of the window + * + * @param filename + * The filename of the icon + */ + public static void setWindowIcon(String filename) { + ImageIcon img = new ImageIcon(filename); + instance().setIconImage(img.getImage()); + } + + /** + * Change the title of the main window + * + * @param title + * The new title + */ + public static void setWindowTitle(String title) { + instance().setTitle(title); + } + + /** + * Add a button in the menu options + * + * @param title + * The title of the button + * @param listener + * The action to be executed + */ + public static void addMenuItem(String title, ActionListener listener) { + JMenuItem menuItem = new JMenuItem(title); + menuItem.addActionListener(listener); + instance().optionsMenu.add(menuItem); + } + + /** + * Add a toolBar + * + * @param toolbar + * The ToolBar. + */ + public static void addToolbar(JToolBar toolbar) { + instance().toolbarPanel.add(toolbar); + instance().pack(); + instance().setVisible(true); + } + + /** + * Set a panel to the left + * + * @param panel + * The panel + */ + + public static void setLeftPanel(JPanel panel) { + instance().splitPane.setLeftComponent(panel); + instance().pack(); + instance().setVisible(true); + } + + /** + * Set a panel to the right + * + * @param panel + * The panel + */ + public static void setRightPanel(JPanel panel) { + instance().splitPane.setRightComponent(panel); + instance().pack(); + instance().setVisible(true); + } + + /** + * Return the unique instance of MainWindow, may create it. + * + * @return instance + */ + public static MainWindow instance() { + instanceLock.lock(); + if (instance == null) { + instance = new MainWindow(); + } + instanceLock.unlock(); + return instance; + } + + /** + * Add a panel with a tab + * + * @param title + * The title of the tab + * @param panel + * The panel to add + */ + public static void addTabbedPanel(String title, JPanel panel) { + instance().tabbedPanel.addTab(title, panel); + instance().pack(); + instance().setVisible(true); + } +} diff --git a/src/mas/ui/MainWindowListener.java b/src/mas/ui/MainWindowListener.java new file mode 100644 index 0000000000000000000000000000000000000000..181f62c850de9b1b586f85949a37b88527f15b88 --- /dev/null +++ b/src/mas/ui/MainWindowListener.java @@ -0,0 +1,91 @@ +package mas.ui; + +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * Listener for the {@link MainWindow} + * + * @author Perles Alexandre + * + */ +public class MainWindowListener implements WindowListener { + /** + * Actions that must be executed when the MainWindow is closed + */ + private final List<Consumer<MainWindow>> onCloseActions = new ArrayList<>(); + /** + * The MainWindow linked to this listener + */ + private MainWindow mainWindow; + + /** + * The constructor of the listener + * + * @param mainWindow + * The MainWindow linked to this listener + */ + public MainWindowListener(MainWindow mainWindow) { + this.mainWindow = mainWindow; + } + + @Override + public void windowOpened(WindowEvent e) { + // At the time of the creation of this listener no need has been expressed for + // this method + } + + /** + * Add the action "action" in the list of actions that must be executed when the + * window is closed + * + * @param action + * The action to be executed + */ + public void addOnCloseAction(Consumer<MainWindow> action) { + onCloseActions.add(action); + } + + @Override + public void windowClosing(WindowEvent e) { + for (Consumer<MainWindow> consumer : onCloseActions) { + consumer.accept(this.mainWindow); + } + System.exit(0); + } + + @Override + public void windowClosed(WindowEvent e) { + // At the time of the creation of this listener no need has been expressed for + // this method + } + + @Override + public void windowIconified(WindowEvent e) { + // At the time of the creation of this listener no need has been expressed for + // this method + } + + @Override + public void windowDeiconified(WindowEvent e) { + // At the time of the creation of this listener no need has been expressed for + // this method + } + + @Override + public void windowActivated(WindowEvent e) { + // At the time of the creation of this listener no need has been expressed for + // this method + } + + @Override + public void windowDeactivated(WindowEvent e) { + // At the time of the creation of this listener no need has been expressed for + // this method + } + +} + diff --git a/src/mas/ui/SchedulerToolbar.java b/src/mas/ui/SchedulerToolbar.java new file mode 100644 index 0000000000000000000000000000000000000000..69ea3523befc110b5b2376beff25302dbce83116 --- /dev/null +++ b/src/mas/ui/SchedulerToolbar.java @@ -0,0 +1,137 @@ +package mas.ui; + +import mas.implementation.base.schedulers.TwoDCycling; + +import javax.swing.*; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.util.Hashtable; + +public class SchedulerToolbar extends JToolBar { + + /** + * The slider which controls the speed + */ + private JSlider runController; + + /** + * The scheduler to which the toolbar is associated + */ + private TwoDCycling scheduler; + + /** + * The title of the toolbar + */ + private String title; + + /** + * Constructor of the toolbar + * + * @param title + * The title of the toolbar + * @param scheduler + * The scheduler to which the toolbar is associated + * + */ + public SchedulerToolbar(String title, TwoDCycling scheduler) { + this.title = title; + this.scheduler = scheduler; + this.scheduler.setOnStop(s -> getSlider().setValue(1)); + this.scheduler.addOnChange(s -> { + if (s.isRunning()) { + switch (s.getSleep()) { + case 1000: + getSlider().setValue(2); + break; + case 100: + getSlider().setValue(3); + break; + case 20: + getSlider().setValue(4); + break; + case 10: + getSlider().setValue(5); + break; + case 2: + getSlider().setValue(6); + break; + case 0: + getSlider().setValue(7); + break; + default: + getSlider().setValue(1); + } + } else { + getSlider().setValue(1); + } + }); + add(getSlider()); + setPreferredSize(new Dimension(300, 100)); + } + + /** + * Get or create the slider component + * + * @return the slider + */ + public JSlider getSlider() { + if (runController == null) { + runController = new JSlider(SwingConstants.HORIZONTAL, 0, 7, 1); + runController.setBorder(BorderFactory.createTitledBorder(this.title)); + + // Hashtable is not recommended anymore and should be replaced by an HashMap but + // JSlider requires Hashtable so Hashtable it will have. + final Hashtable<Integer, JLabel> labelTable = new Hashtable<>(); + labelTable.put(0, new JLabel("Step")); + labelTable.put(1, new JLabel("Stop")); + labelTable.put(2, new JLabel("x1")); + labelTable.put(3, new JLabel("x10")); + labelTable.put(4, new JLabel("x50")); + labelTable.put(5, new JLabel("x100")); + labelTable.put(6, new JLabel("x500")); + labelTable.put(7, new JLabel("MAX")); + + runController.setLabelTable(labelTable); + + runController.setPaintLabels(true); + + runController.addChangeListener(l -> { + JSlider source = (JSlider) l.getSource(); + if(!source.getValueIsAdjusting()){ + switch (runController.getValue()) { + case 0 -> { + System.out.println("Je commence doOneCycle()"); + scheduler.doOneCycle(); + System.out.println("Je termine doOneCycle()"); + } + case 2 -> { + scheduler.startWithSleep(1000); + } + case 3 -> { + scheduler.startWithSleep(100); + } + case 4 -> { + scheduler.startWithSleep(20); + } + case 5 -> { + scheduler.startWithSleep(10); + } + case 6 -> { + scheduler.startWithSleep(2); + } + case 7 -> { + scheduler.startWithSleep(0); + } + default -> { + scheduler.pause(); + } + } + } + + }); + } + return runController; + } + + +}