From 40d531b4aa2e65e6daea12a25bae8a55967288bc Mon Sep 17 00:00:00 2001
From: shinedday <shinedday@gmail.com>
Date: Thu, 20 May 2021 14:21:49 +0200
Subject: [PATCH] Add : scheduler mono threading

---
 pyAmakCore/classes/scheduler.py               | 134 ++++--------------
 .../classes/scheduler_mono_threading.py       |  99 +++++++++++++
 pyAmakCore/classes/scheduler_tool/callable.py |  41 ++++++
 pyAmakCore/classes/scheduler_tool/savable.py  |  41 ++++++
 .../classes/scheduler_tool/scheduler_core.py  |  57 ++++++++
 pyAmakCore/classes/thread_tool/__init__.py    |   0
 .../agent_thread.py                           |   0
 .../amas_thread.py                            |   4 +-
 .../schedulable_thread.py                     |   0
 pyAmakCore/classes/tools/scheduler_monoIHM.py |  31 ++++
 pyAmakCore/tests/memory_leak/main.py          |   8 +-
 11 files changed, 306 insertions(+), 109 deletions(-)
 create mode 100644 pyAmakCore/classes/scheduler_mono_threading.py
 create mode 100644 pyAmakCore/classes/scheduler_tool/callable.py
 create mode 100644 pyAmakCore/classes/scheduler_tool/savable.py
 create mode 100644 pyAmakCore/classes/scheduler_tool/scheduler_core.py
 create mode 100644 pyAmakCore/classes/thread_tool/__init__.py
 rename pyAmakCore/classes/{scheduler_tool => thread_tool}/agent_thread.py (100%)
 rename pyAmakCore/classes/{scheduler_tool => thread_tool}/amas_thread.py (95%)
 rename pyAmakCore/classes/{scheduler_tool => thread_tool}/schedulable_thread.py (100%)
 create mode 100644 pyAmakCore/classes/tools/scheduler_monoIHM.py

diff --git a/pyAmakCore/classes/scheduler.py b/pyAmakCore/classes/scheduler.py
index a7d486c..2326a7d 100644
--- a/pyAmakCore/classes/scheduler.py
+++ b/pyAmakCore/classes/scheduler.py
@@ -5,168 +5,92 @@ from typing import List
 
 import sys
 import pathlib
-import pickle
-from threading import Semaphore, Thread
-
-from time import sleep
+from threading import Thread
 
 sys.path.insert(0, str(pathlib.Path(__file__).parent))
 
 from pyAmakCore.classes.amas import Amas
-from pyAmakCore.classes.scheduler_tool.schedulable_thread import SchedulableThread
-from pyAmakCore.classes.scheduler_tool.amas_thread import AmasThread
+from pyAmakCore.classes.thread_tool.schedulable_thread import SchedulableThread
+from pyAmakCore.classes.thread_tool.amas_thread import AmasThread
 from pyAmakCore.classes.tools.schedulable import Schedulable
+from pyAmakCore.classes.scheduler_tool.scheduler_core import SchedulerCore
 
 
-class Scheduler:
+class Scheduler(SchedulerCore):
     """
     Scheduler class, to make sure that environment and amas are always sync together
     """
 
     def __init__(self, amas: Amas) -> None:
-        self.amas: Amas = amas
-
-        self.exit_bool: bool = False
-
-        self.semaphore_start_stop: Semaphore = Semaphore(0)
+        SchedulerCore.__init__(self, amas)
 
-        self.schedulables: List[SchedulableThread] = []
-        self.schedulables_threads: List[Thread] = []
+        self.__schedulables: List[SchedulableThread] = []
+        self.__schedulables_threads: List[Thread] = []
 
-        self.add_schedulables(amas, AmasThread)
-        self.add_schedulables(amas.get_environment(), SchedulableThread)
+        self.__add_schedulables(amas, AmasThread)
+        self.__add_schedulables(amas.get_environment(), SchedulableThread)
 
-        self.sleep_time: float = 0
-
-    def get_amas(self) -> Amas:
-        """
-        return amas pointer
-        """
-        return self.amas
-
-    def add_schedulables(self, schedulable: Schedulable, cls) -> None:
+    def __add_schedulables(self, schedulable: Schedulable, cls) -> None:
         """
         add a schedulable in scheduler
         """
         schedulable_thread = cls(schedulable)
-        self.schedulables.append(schedulable_thread)
+        self.__schedulables.append(schedulable_thread)
         current_thread = Thread(target=schedulable_thread.run)
-        self.schedulables_threads.append(current_thread)
+        self.__schedulables_threads.append(current_thread)
         current_thread.start()
 
-    def wait_schedulables(self) -> None:
+    def __wait_schedulables(self) -> None:
         """
         wait for all schedulable to release a token
         """
-        for schedulable in self.schedulables:
+        for schedulable in self.__schedulables:
             schedulable.action_done.acquire()
 
-    def start_schedulables(self) -> None:
+    def __start_schedulables(self) -> None:
         """
         wait for all schedulable to release a token
         """
-        for schedulable in self.schedulables:
+        for schedulable in self.__schedulables:
             schedulable.is_waiting.release()
 
     def first_part(self) -> None:
         """
         first part of a cycle
         """
-        self.start_schedulables()
+        self.__start_schedulables()
         # on cycle begin
-        self.wait_schedulables()
+        self.__wait_schedulables()
 
     def main_part(self) -> None:
         """
         main part of a cycle
         """
-        self.start_schedulables()
+        self.__start_schedulables()
         # agent cycle
-        self.wait_schedulables()
+        self.__wait_schedulables()
 
     def last_part(self) -> None:
         """
         last part of a cycle
         """
-        self.start_schedulables()
+        self.__start_schedulables()
         # on cycle end
-        self.wait_schedulables()
+        self.__wait_schedulables()
 
     def run(self) -> None:
         """
-        main part of amak core
+        override super run to close child thread before stopping
         """
-        while not self.exit_bool:
-            print("Cycle : ", self.amas.get_cycle())
-
-            self.semaphore_start_stop.acquire()
-            if self.exit_bool:
-                break
-            self.semaphore_start_stop.release()
-
-            self.first_part()
-            self.main_part()
-            self.last_part()
+        super().run()
+        self.__close_child()
 
-            sleep(self.sleep_time)
-        self.close_child()
-
-    def close_child(self) -> None:
+    def __close_child(self) -> None:
         """
         tell all child to shut down
         """
-        for schedulable in self.schedulables:
+        for schedulable in self.__schedulables:
             schedulable.exit_bool = True
             schedulable.is_waiting.release()
-        for thread in self.schedulables_threads:
+        for thread in self.__schedulables_threads:
             thread.join(0)
-
-    """
-    program interface
-    """
-
-    def exit_program(self) -> None:
-        """
-        Exit the system as soon as possible
-        """
-        self.exit_bool = True
-        self.semaphore_start_stop.release()
-
-    def start(self) -> None:
-        """
-        Unlock the scheduler
-        """
-        self.semaphore_start_stop.release()
-
-    def stop(self) -> None:
-        """
-        Lock the scheduler
-        """
-        self.semaphore_start_stop.acquire()
-
-    def set_sleep(self, sleep_time: int) -> None:
-        """
-        Set sleep value between 2 cycles
-        """
-        self.sleep_time = sleep_time
-
-    """
-    load & save program
-    """
-
-    def save(self) -> None:
-        """
-        Save the current state of the system
-        """
-        with open('filename.pickle', 'wb') as handle:
-            pickle.dump(self.amas, handle, protocol=pickle.HIGHEST_PROTOCOL)
-
-    @classmethod
-    def load(cls) -> 'Scheduler':
-        """
-        Load the last save of the system
-        """
-        with open('filename.pickle', 'rb') as handle:
-            amas_object = pickle.load(handle)
-
-        return cls(amas_object)
diff --git a/pyAmakCore/classes/scheduler_mono_threading.py b/pyAmakCore/classes/scheduler_mono_threading.py
new file mode 100644
index 0000000..1f3bbc3
--- /dev/null
+++ b/pyAmakCore/classes/scheduler_mono_threading.py
@@ -0,0 +1,99 @@
+"""
+SchedulerMono class
+"""
+from random import shuffle
+from typing import List
+
+import sys
+import pathlib
+
+
+sys.path.insert(0, str(pathlib.Path(__file__).parent))
+
+from pyAmakCore.classes.scheduler_tool.scheduler_core import SchedulerCore
+from pyAmakCore.classes.amas import Amas
+from pyAmakCore.classes.agent import Agent
+from pyAmakCore.classes.tools.schedulable import Schedulable
+from pyAmakCore.classes.communicating_agent import CommunicatingAgent
+from pyAmakCore.enumeration.executionPolicy import ExecutionPolicy
+
+
+class SchedulerMono(SchedulerCore):
+    """
+    Scheduler class, without threading
+    """
+
+    def __init__(self, amas: Amas) -> None:
+        SchedulerCore.__init__(self, amas)
+
+        self.schedulables: List[Schedulable] = []
+
+        self.schedulables.append(self.amas)
+        self.amas.add_pending_agent()
+        self.schedulables.append(self.amas.get_environment())
+
+    def first_part(self) -> None:
+        """
+        first part of a cycle
+        """
+        self.amas.add_pending_agent()
+
+        for schedulable in self.schedulables:
+            schedulable.on_cycle_begin()
+
+    def main_part(self) -> None:
+        """
+        main part of a cycle
+        """
+
+        def phase1(current_agent: Agent) -> None:
+            """
+            this is the first phase of a cycle
+            """
+            current_agent.next_phase()
+            current_agent.on_cycle_begin()
+            if isinstance(current_agent, CommunicatingAgent):
+                current_agent.read_mails()
+
+            current_agent.on_perceive()
+            current_agent.set_criticality(current_agent.compute_criticality())
+            current_agent.next_phase()
+
+        def phase2(current_agent: Agent) -> None:
+            """
+            this is the second phase of a cycle
+            """
+            current_agent.next_phase()
+            current_agent.on_decide()
+            current_agent.on_act()
+            current_agent.set_criticality(current_agent.compute_criticality())
+            current_agent.next_phase()
+            current_agent.on_cycle_end()
+
+        agents: List[Agent] = self.amas.get_agents()
+
+        if self.amas.get_execution_policy() == ExecutionPolicy.ONE_PHASE:
+            shuffle(agents)
+            for agent in agents:
+                phase1(agent)
+                phase2(agent)
+        else:
+            shuffle(agents)
+            for agent in agents:
+                phase1(agent)
+            shuffle(agents)
+            for agent in agents:
+                phase2(agent)
+
+    def last_part(self) -> None:
+        """
+        last part of a cycle
+        """
+        for schedulable in self.schedulables:
+            schedulable.on_cycle_end()
+
+        self.amas.remove_pending_agent()
+        self.amas.to_csv(self.amas.get_cycle(), self.amas.get_agents())
+
+        for schedulable in self.schedulables:
+            schedulable.cycle()
diff --git a/pyAmakCore/classes/scheduler_tool/callable.py b/pyAmakCore/classes/scheduler_tool/callable.py
new file mode 100644
index 0000000..f6f4a51
--- /dev/null
+++ b/pyAmakCore/classes/scheduler_tool/callable.py
@@ -0,0 +1,41 @@
+"""
+class Callable
+"""
+from threading import Semaphore
+
+
+class Callable:
+    """
+    Class that implement useful method to interact with the program
+    """
+
+    def __init__(self) -> None:
+        self.exit_bool: bool = False
+        self.sleep_time: float = 0
+
+        self.semaphore_start_stop: Semaphore = Semaphore(0)
+
+    def exit_program(self) -> None:
+        """
+        Exit the system as soon as possible
+        """
+        self.exit_bool = True
+        self.semaphore_start_stop.release()
+
+    def start(self) -> None:
+        """
+        Unlock the scheduler
+        """
+        self.semaphore_start_stop.release()
+
+    def stop(self) -> None:
+        """
+        Lock the scheduler
+        """
+        self.semaphore_start_stop.acquire()
+
+    def set_sleep(self, sleep_time: int) -> None:
+        """
+        Set sleep value between 2 cycles
+        """
+        self.sleep_time = sleep_time
diff --git a/pyAmakCore/classes/scheduler_tool/savable.py b/pyAmakCore/classes/scheduler_tool/savable.py
new file mode 100644
index 0000000..fcffd31
--- /dev/null
+++ b/pyAmakCore/classes/scheduler_tool/savable.py
@@ -0,0 +1,41 @@
+"""
+Savable Class
+"""
+import pickle
+import sys
+import pathlib
+
+sys.path.insert(0, str(pathlib.Path(__file__).parent))
+
+from pyAmakCore.classes.amas import Amas
+
+
+class Savable:
+    """
+    Class that implement convenient method to save and load an amas
+    """
+    def __init__(self, amas: Amas) -> None:
+        self.amas = amas
+
+    def get_amas(self):
+        """
+        return amas
+        """
+        return self.amas
+
+    def save(self) -> None:
+        """
+        Save the current state of the system
+        """
+        with open('filename.pickle', 'wb') as handle:
+            pickle.dump(self.amas, handle, protocol=pickle.HIGHEST_PROTOCOL)
+
+    @classmethod
+    def load(cls) -> 'Savable':
+        """
+        Load the last save of the system
+        """
+        with open('filename.pickle', 'rb') as handle:
+            amas_object = pickle.load(handle)
+
+        return cls(amas_object)
\ No newline at end of file
diff --git a/pyAmakCore/classes/scheduler_tool/scheduler_core.py b/pyAmakCore/classes/scheduler_tool/scheduler_core.py
new file mode 100644
index 0000000..15ef702
--- /dev/null
+++ b/pyAmakCore/classes/scheduler_tool/scheduler_core.py
@@ -0,0 +1,57 @@
+"""
+Scheduler class
+"""
+from time import sleep
+
+import sys
+import pathlib
+
+sys.path.insert(0, str(pathlib.Path(__file__).parent))
+
+from pyAmakCore.classes.scheduler_tool.callable import Callable
+from pyAmakCore.classes.scheduler_tool.savable import Savable
+from pyAmakCore.classes.amas import Amas
+
+
+class SchedulerCore(Callable, Savable):
+    """
+    Core part of Scheduler
+    """
+
+    def __init__(self, amas: Amas) -> None:
+
+        Callable.__init__(self)
+        Savable.__init__(self, amas)
+
+    def first_part(self) -> None:
+        """
+        first part of a cycle
+        """
+
+    def main_part(self) -> None:
+        """
+        main part of a cycle
+        """
+
+    def last_part(self) -> None:
+        """
+        last part of a cycle
+        """
+
+    def run(self) -> None:
+        """
+        main part of amak core
+        """
+        while not self.exit_bool:
+            print("Cycle : ", self.amas.get_cycle())
+
+            self.semaphore_start_stop.acquire()
+            if self.exit_bool:
+                break
+            self.semaphore_start_stop.release()
+
+            self.first_part()
+            self.main_part()
+            self.last_part()
+
+            sleep(self.sleep_time)
diff --git a/pyAmakCore/classes/thread_tool/__init__.py b/pyAmakCore/classes/thread_tool/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pyAmakCore/classes/scheduler_tool/agent_thread.py b/pyAmakCore/classes/thread_tool/agent_thread.py
similarity index 100%
rename from pyAmakCore/classes/scheduler_tool/agent_thread.py
rename to pyAmakCore/classes/thread_tool/agent_thread.py
diff --git a/pyAmakCore/classes/scheduler_tool/amas_thread.py b/pyAmakCore/classes/thread_tool/amas_thread.py
similarity index 95%
rename from pyAmakCore/classes/scheduler_tool/amas_thread.py
rename to pyAmakCore/classes/thread_tool/amas_thread.py
index dad5c74..e251638 100644
--- a/pyAmakCore/classes/scheduler_tool/amas_thread.py
+++ b/pyAmakCore/classes/thread_tool/amas_thread.py
@@ -11,10 +11,10 @@ import pathlib
 sys.path.insert(0, str(pathlib.Path(__file__).parent.parent))
 
 from pyAmakCore.enumeration.executionPolicy import ExecutionPolicy
-from pyAmakCore.classes.scheduler_tool.agent_thread import AgentThread
+from pyAmakCore.classes.thread_tool.agent_thread import AgentThread
 from pyAmakCore.classes.amas import Amas
 from pyAmakCore.classes.agent import Agent
-from pyAmakCore.classes.scheduler_tool.schedulable_thread import SchedulableThread
+from pyAmakCore.classes.thread_tool.schedulable_thread import SchedulableThread
 
 
 class AmasThread(SchedulableThread):
diff --git a/pyAmakCore/classes/scheduler_tool/schedulable_thread.py b/pyAmakCore/classes/thread_tool/schedulable_thread.py
similarity index 100%
rename from pyAmakCore/classes/scheduler_tool/schedulable_thread.py
rename to pyAmakCore/classes/thread_tool/schedulable_thread.py
diff --git a/pyAmakCore/classes/tools/scheduler_monoIHM.py b/pyAmakCore/classes/tools/scheduler_monoIHM.py
new file mode 100644
index 0000000..827b998
--- /dev/null
+++ b/pyAmakCore/classes/tools/scheduler_monoIHM.py
@@ -0,0 +1,31 @@
+"""
+Scheduler class that need to be used for pyAmakIhm
+"""
+import pathlib
+import sys
+
+
+sys.path.insert(0, str(pathlib.Path(__file__).parent.parent))
+
+from pyAmakCore.classes.amas import Amas
+from pyAmakCore.classes.scheduler_mono_threading import SchedulerMono
+
+
+class SchedulerMonoIHM(SchedulerMono):
+    """
+    Convenient class to override while using pyAmakIHM
+    """
+
+    def __init__(self, amas: Amas):
+        self.__observer = None
+        super().__init__(amas)
+
+    def last_part(self) -> None:
+        super().last_part()
+        self.__observer.updateCycle()
+
+    def attach(self, observer: 'Controleur') -> None:
+        """
+        set observer pointer to observer
+        """
+        self.__observer = observer
diff --git a/pyAmakCore/tests/memory_leak/main.py b/pyAmakCore/tests/memory_leak/main.py
index a1a804f..398af44 100644
--- a/pyAmakCore/tests/memory_leak/main.py
+++ b/pyAmakCore/tests/memory_leak/main.py
@@ -1,3 +1,5 @@
+from pyAmakCore.classes.scheduler_mono_threading import SchedulerMono
+
 from pyAmakCore.exception.override import ToOverrideWarning
 from pyAmakCore.classes.agent import Agent
 from pyAmakCore.classes.amas import Amas
@@ -11,7 +13,8 @@ class SimpleAgent(Agent):
 
 class SimpleAmas(Amas):
     def on_initialization(self) -> None:
-        self.set_do_log(True)
+        # self.set_do_log(True)
+        pass
 
     def on_initial_agents_creation(self) -> None:
         for i in range(10):
@@ -27,7 +30,8 @@ ToOverrideWarning.enable_warning(False)
 env = SimpleEnv()
 amas = SimpleAmas(env)
 
-scheduler = Scheduler(amas)
+#scheduler = Scheduler(amas)
+scheduler = SchedulerMono(amas)
 
 scheduler.start()
 scheduler.run()
-- 
GitLab