Browse Source

allow execute terminal as main process

Bastien Sevajol 7 years ago
parent
commit
eddd6a3010
5 changed files with 119 additions and 6 deletions
  1. 1 1
      setup.py
  2. 33 2
      synergine2/core.py
  3. 39 2
      synergine2/terminals.py
  4. 2 1
      synergine2_cocos2d/gui.py
  5. 44 0
      tests/test_terminals.py

+ 1 - 1
setup.py View File

27
 
27
 
28
 setup(
28
 setup(
29
     name='synergine2',
29
     name='synergine2',
30
-    version='1.0.1',
30
+    version='1.0.2',
31
     description='Subject focus simulation framework',
31
     description='Subject focus simulation framework',
32
     author='Bastien Sevajol',
32
     author='Bastien Sevajol',
33
     author_email='sevajol.bastien@gmail.com',
33
     author_email='sevajol.bastien@gmail.com',

+ 33 - 2
synergine2/core.py View File

1
 # coding: utf-8
1
 # coding: utf-8
2
 import time
2
 import time
3
 
3
 
4
+from multiprocessing import Queue
5
+
4
 from synergine2.base import BaseObject
6
 from synergine2.base import BaseObject
5
 from synergine2.config import Config
7
 from synergine2.config import Config
6
 from synergine2.cycle import CycleManager
8
 from synergine2.cycle import CycleManager
7
 from synergine2.log import SynergineLogger
9
 from synergine2.log import SynergineLogger
8
 from synergine2.simulation import Simulation
10
 from synergine2.simulation import Simulation
9
 from synergine2.terminals import TerminalManager
11
 from synergine2.terminals import TerminalManager
12
+from synergine2.terminals import Terminal
10
 from synergine2.terminals import TerminalPackage
13
 from synergine2.terminals import TerminalPackage
11
 from synergine2.utils import time_it
14
 from synergine2.utils import time_it
12
 
15
 
28
         self.terminal_manager = terminal_manager or TerminalManager(config, logger, [])
31
         self.terminal_manager = terminal_manager or TerminalManager(config, logger, [])
29
         self._loop_delta = 1./cycles_per_seconds
32
         self._loop_delta = 1./cycles_per_seconds
30
         self._current_cycle_start_time = None
33
         self._current_cycle_start_time = None
34
+        self._continue = True
35
+        self.main_process_terminal = None  # type: Terminal
31
 
36
 
32
-    def run(self):
37
+    def run(
38
+        self,
39
+        from_terminal: Terminal=None,
40
+        from_terminal_input_queue: Queue=None,
41
+        from_terminal_output_queue: Queue=None,
42
+    ):
33
         self.logger.info('Run core')
43
         self.logger.info('Run core')
34
         try:
44
         try:
45
+            # Execute terminal in main process if needed
46
+            if not from_terminal:
47
+                self.main_process_terminal \
48
+                    = self.terminal_manager.get_main_process_terminal()
49
+                if self.main_process_terminal:
50
+                    self.logger.info(
51
+                        'The "{}" terminal have to be the main process'
52
+                        ', start it now'.format(
53
+                            self.main_process_terminal.__class__.__name__,
54
+                        ),
55
+                    )
56
+                    self.main_process_terminal.execute_as_main_process(self)
57
+                    return
58
+            else:
59
+                # A terminal is main process, so we have to add it's queues to terminal
60
+                # manager
61
+                self.terminal_manager.inputs_queues[from_terminal] \
62
+                    = from_terminal_input_queue
63
+                self.terminal_manager.outputs_queues[from_terminal] \
64
+                    = from_terminal_output_queue
65
+
35
             self.terminal_manager.start()
66
             self.terminal_manager.start()
36
 
67
 
37
             start_package = TerminalPackage(
68
             start_package = TerminalPackage(
40
             self.logger.info('Send start package to terminals')
71
             self.logger.info('Send start package to terminals')
41
             self.terminal_manager.send(start_package)
72
             self.terminal_manager.send(start_package)
42
 
73
 
43
-            while True:
74
+            while self._continue:
44
                 self._start_cycle()
75
                 self._start_cycle()
45
 
76
 
46
                 events = []
77
                 events = []

+ 39 - 2
synergine2/terminals.py View File

1
 # coding: utf-8
1
 # coding: utf-8
2
 import collections
2
 import collections
3
+import typing
3
 from copy import copy
4
 from copy import copy
4
 from multiprocessing import Queue
5
 from multiprocessing import Queue
5
 from multiprocessing import Process
6
 from multiprocessing import Process
7
 import time
8
 import time
8
 
9
 
9
 from synergine2.base import BaseObject
10
 from synergine2.base import BaseObject
11
+from synergine2.exceptions import SynergineException
10
 from synergine2.share import shared
12
 from synergine2.share import shared
11
 from synergine2.config import Config
13
 from synergine2.config import Config
12
 from synergine2.log import SynergineLogger
14
 from synergine2.log import SynergineLogger
13
 from synergine2.simulation import Subject
15
 from synergine2.simulation import Subject
14
 from synergine2.simulation import Event
16
 from synergine2.simulation import Event
15
 
17
 
18
+if typing.TYPE_CHECKING:
19
+    from synergine2.core import Core
20
+
16
 STOP_SIGNAL = '__STOP_SIGNAL__'
21
 STOP_SIGNAL = '__STOP_SIGNAL__'
17
 
22
 
18
 
23
 
60
     # List of subscribed Event classes. Terminal will not receive events
65
     # List of subscribed Event classes. Terminal will not receive events
61
     # who are not instance of listed classes
66
     # who are not instance of listed classes
62
     subscribed_events = [Event]
67
     subscribed_events = [Event]
68
+    # Permit to execute terminal in main process, only one terminal can use this
69
+    main_process = False
63
 
70
 
64
     def __init__(
71
     def __init__(
65
         self,
72
         self,
76
         self.cycle_events = []
83
         self.cycle_events = []
77
         self.event_handlers = collections.defaultdict(list)
84
         self.event_handlers = collections.defaultdict(list)
78
         self.asynchronous = asynchronous
85
         self.asynchronous = asynchronous
86
+        self.core_process = None  # type: Process
79
 
87
 
80
     def accept_event(self, event: Event) -> bool:
88
     def accept_event(self, event: Event) -> bool:
81
         for event_class in self.subscribed_events:
89
         for event_class in self.subscribed_events:
88
         self._output_queue = output_queue
96
         self._output_queue = output_queue
89
         self.run()
97
         self.run()
90
 
98
 
99
+    def execute_as_main_process(self, core: 'Core') -> None:
100
+        """
101
+        This method is called when the terminal have to be the main process. It will
102
+        create a process with the run of core and make it's job here.
103
+        """
104
+        output_queue = Queue()
105
+        input_queue = Queue()
106
+
107
+        self.logger.info('Start core in a process')
108
+        self.core_process = Process(target=core.run, kwargs=dict(
109
+            from_terminal=self,
110
+            from_terminal_input_queue=output_queue,
111
+            from_terminal_output_queue=input_queue,
112
+        ))
113
+        self.core_process.start()
114
+
115
+        # Core is started, continue this terminal job
116
+        self.logger.info('Core started, continue terminal job')
117
+        self.start(input_queue=input_queue, output_queue=output_queue)
118
+
91
     def run(self):
119
     def run(self):
92
         """
120
         """
93
         Override this method to create your daemon terminal
121
         Override this method to create your daemon terminal
161
         self.outputs_queues = {}
189
         self.outputs_queues = {}
162
         self.inputs_queues = {}
190
         self.inputs_queues = {}
163
 
191
 
192
+    def get_main_process_terminal(self) -> typing.Optional[Terminal]:
193
+        main_process_terminals = [t for t in self.terminals if t.main_process]
194
+        if main_process_terminals:
195
+            if len(main_process_terminals) > 1:
196
+                raise SynergineException('There is more one main process terminal !')
197
+            return main_process_terminals[0]
198
+        return None
199
+
164
     def start(self) -> None:
200
     def start(self) -> None:
165
         self.logger.info('Start terminals')
201
         self.logger.info('Start terminals')
166
-        for terminal in self.terminals:
167
-            # TODO: logs
202
+        # We exclude here terminal who is run from main process
203
+        terminals = [t for t in self.terminals if not t.main_process]
204
+        for terminal in terminals:
168
             output_queue = Queue()
205
             output_queue = Queue()
169
             self.outputs_queues[terminal] = output_queue
206
             self.outputs_queues[terminal] = output_queue
170
 
207
 

+ 2 - 1
synergine2_cocos2d/gui.py View File

734
         self.terminal = terminal
734
         self.terminal = terminal
735
         self.cycle_duration = self.config.resolve('core.cycle_duration')
735
         self.cycle_duration = self.config.resolve('core.cycle_duration')
736
 
736
 
737
-        cocos.director.director.init(
737
+        cocos.director.\
738
+            director.init(
738
             width=640,
739
             width=640,
739
             height=480,
740
             height=480,
740
             vsync=True,
741
             vsync=True,

+ 44 - 0
tests/test_terminals.py View File

1
 # coding: utf-8
1
 # coding: utf-8
2
 import time
2
 import time
3
 
3
 
4
+import pytest
5
+
4
 from synergine2.config import Config
6
 from synergine2.config import Config
7
+from synergine2.core import Core
8
+from synergine2.cycle import CycleManager
5
 from synergine2.log import SynergineLogger
9
 from synergine2.log import SynergineLogger
6
 from synergine2.simulation import Event
10
 from synergine2.simulation import Event
11
+from synergine2.simulation import Simulation
12
+from synergine2.simulation import Subjects
7
 from synergine2.terminals import Terminal
13
 from synergine2.terminals import Terminal
8
 from synergine2.terminals import TerminalPackage
14
 from synergine2.terminals import TerminalPackage
9
 from synergine2.terminals import TerminalManager
15
 from synergine2.terminals import TerminalManager
156
         assert AnOtherEvent == type(packages[1].events[0])
162
         assert AnOtherEvent == type(packages[1].events[0])
157
 
163
 
158
         terminals_manager.stop()  # TODO pytest must execute this if have fail
164
         terminals_manager.stop()  # TODO pytest must execute this if have fail
165
+
166
+    @pytest.mark.skip(reason="Buggy ! Never terminate, all processes closed ?")
167
+    def test_terminal_as_main_process(self):
168
+        config = Config()
169
+        logger = SynergineLogger('test')
170
+        simulation = Simulation(config)
171
+        simulation.subjects = Subjects(simulation=simulation)
172
+        cycle_manager = CycleManager(
173
+            config=config,
174
+            logger=logger,
175
+            simulation=simulation,
176
+        )
177
+
178
+        class MyMainTerminal(Terminal):
179
+            main_process = True
180
+
181
+        terminal = MyMainTerminal(config, logger)
182
+
183
+        class Terminated(Exception):
184
+            pass
185
+
186
+        class MyCore(Core):
187
+            def _end_cycle(self):
188
+                self._continue = False
189
+
190
+        core = MyCore(
191
+            config=config,
192
+            logger=logger,
193
+            simulation=simulation,
194
+            cycle_manager=cycle_manager,
195
+            terminal_manager=TerminalManager(
196
+                config=config,
197
+                logger=logger,
198
+                terminals=[terminal],
199
+            ),
200
+        )
201
+        core.run()
202
+        pass