From 7cd8bc9314215e51d931604be4b41e1bc2c0b267 Mon Sep 17 00:00:00 2001
From: unknown <david.antunes-da-silva@irit.fr>
Date: Mon, 20 Jun 2022 13:59:53 +0200
Subject: [PATCH] Amak first version for test

---
 src/example/philosophes/Dechets.java          |  37 +++
 src/example/philosophes/Fork.java             |  30 +++
 src/example/philosophes/MainPhilosophe.java   |  63 ++++++
 src/example/philosophes/Philosophe.java       | 210 ++++++++++++++++++
 src/mas/core/Agent.java                       |  31 +++
 src/mas/core/Cyclable.java                    |  23 ++
 src/mas/core/Schedulable.java                 |  53 +++++
 src/mas/core/ThreeStepCyclable.java           |  31 +++
 src/mas/core/TwoStepCyclable.java             |  22 ++
 src/mas/environment/TwoDContinuosGrid.java    |   5 +
 .../base/schedulers/AsyncCycling.java         | 109 +++++++++
 .../base/schedulers/FairCycling.java          | 154 +++++++++++++
 .../base/schedulers/FairPosCycling.java       |  49 ++++
 .../PausableThreadPoolExecutor.java           |  77 +++++++
 .../base/schedulers/TwoDCycling.java          | 114 ++++++++++
 src/mas/ui/MainWindow.java                    | 209 +++++++++++++++++
 src/mas/ui/MainWindowListener.java            |  91 ++++++++
 src/mas/ui/SchedulerToolbar.java              | 137 ++++++++++++
 18 files changed, 1445 insertions(+)
 create mode 100644 src/example/philosophes/Dechets.java
 create mode 100644 src/example/philosophes/Fork.java
 create mode 100644 src/example/philosophes/MainPhilosophe.java
 create mode 100644 src/example/philosophes/Philosophe.java
 create mode 100644 src/mas/core/Agent.java
 create mode 100644 src/mas/core/Cyclable.java
 create mode 100644 src/mas/core/Schedulable.java
 create mode 100644 src/mas/core/ThreeStepCyclable.java
 create mode 100644 src/mas/core/TwoStepCyclable.java
 create mode 100644 src/mas/environment/TwoDContinuosGrid.java
 create mode 100644 src/mas/implementation/base/schedulers/AsyncCycling.java
 create mode 100644 src/mas/implementation/base/schedulers/FairCycling.java
 create mode 100644 src/mas/implementation/base/schedulers/FairPosCycling.java
 create mode 100644 src/mas/implementation/base/schedulers/PausableThreadPoolExecutor.java
 create mode 100644 src/mas/implementation/base/schedulers/TwoDCycling.java
 create mode 100644 src/mas/ui/MainWindow.java
 create mode 100644 src/mas/ui/MainWindowListener.java
 create mode 100644 src/mas/ui/SchedulerToolbar.java

diff --git a/src/example/philosophes/Dechets.java b/src/example/philosophes/Dechets.java
new file mode 100644
index 0000000..465901d
--- /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 0000000..3e150ea
--- /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 0000000..c1b976f
--- /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 0000000..761f717
--- /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 0000000..d4b955b
--- /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 0000000..c272b06
--- /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 0000000..a868b93
--- /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 0000000..99a0172
--- /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 0000000..6324ccd
--- /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 0000000..4ae7f99
--- /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 0000000..99c821b
--- /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 0000000..aeb42f0
--- /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 0000000..3100732
--- /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 0000000..8529943
--- /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 0000000..0c9067e
--- /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 0000000..a30f453
--- /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 0000000..181f62c
--- /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 0000000..69ea352
--- /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;
+    }
+
+
+}
-- 
GitLab