Pārlūkot izejas kodu

allow execute terminal as main process

Bastien Sevajol 7 gadus atpakaļ
vecāks
revīzija
eddd6a3010
5 mainītis faili ar 119 papildinājumiem un 6 dzēšanām
  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 Parādīt failu

@@ -27,7 +27,7 @@ if sys.version_info.major == 3 and sys.version_info.minor == 4:
27 27
 
28 28
 setup(
29 29
     name='synergine2',
30
-    version='1.0.1',
30
+    version='1.0.2',
31 31
     description='Subject focus simulation framework',
32 32
     author='Bastien Sevajol',
33 33
     author_email='sevajol.bastien@gmail.com',

+ 33 - 2
synergine2/core.py Parādīt failu

@@ -1,12 +1,15 @@
1 1
 # coding: utf-8
2 2
 import time
3 3
 
4
+from multiprocessing import Queue
5
+
4 6
 from synergine2.base import BaseObject
5 7
 from synergine2.config import Config
6 8
 from synergine2.cycle import CycleManager
7 9
 from synergine2.log import SynergineLogger
8 10
 from synergine2.simulation import Simulation
9 11
 from synergine2.terminals import TerminalManager
12
+from synergine2.terminals import Terminal
10 13
 from synergine2.terminals import TerminalPackage
11 14
 from synergine2.utils import time_it
12 15
 
@@ -28,10 +31,38 @@ class Core(BaseObject):
28 31
         self.terminal_manager = terminal_manager or TerminalManager(config, logger, [])
29 32
         self._loop_delta = 1./cycles_per_seconds
30 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 43
         self.logger.info('Run core')
34 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 66
             self.terminal_manager.start()
36 67
 
37 68
             start_package = TerminalPackage(
@@ -40,7 +71,7 @@ class Core(BaseObject):
40 71
             self.logger.info('Send start package to terminals')
41 72
             self.terminal_manager.send(start_package)
42 73
 
43
-            while True:
74
+            while self._continue:
44 75
                 self._start_cycle()
45 76
 
46 77
                 events = []

+ 39 - 2
synergine2/terminals.py Parādīt failu

@@ -1,5 +1,6 @@
1 1
 # coding: utf-8
2 2
 import collections
3
+import typing
3 4
 from copy import copy
4 5
 from multiprocessing import Queue
5 6
 from multiprocessing import Process
@@ -7,12 +8,16 @@ from queue import Empty
7 8
 import time
8 9
 
9 10
 from synergine2.base import BaseObject
11
+from synergine2.exceptions import SynergineException
10 12
 from synergine2.share import shared
11 13
 from synergine2.config import Config
12 14
 from synergine2.log import SynergineLogger
13 15
 from synergine2.simulation import Subject
14 16
 from synergine2.simulation import Event
15 17
 
18
+if typing.TYPE_CHECKING:
19
+    from synergine2.core import Core
20
+
16 21
 STOP_SIGNAL = '__STOP_SIGNAL__'
17 22
 
18 23
 
@@ -60,6 +65,8 @@ class Terminal(BaseObject):
60 65
     # List of subscribed Event classes. Terminal will not receive events
61 66
     # who are not instance of listed classes
62 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 71
     def __init__(
65 72
         self,
@@ -76,6 +83,7 @@ class Terminal(BaseObject):
76 83
         self.cycle_events = []
77 84
         self.event_handlers = collections.defaultdict(list)
78 85
         self.asynchronous = asynchronous
86
+        self.core_process = None  # type: Process
79 87
 
80 88
     def accept_event(self, event: Event) -> bool:
81 89
         for event_class in self.subscribed_events:
@@ -88,6 +96,26 @@ class Terminal(BaseObject):
88 96
         self._output_queue = output_queue
89 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 119
     def run(self):
92 120
         """
93 121
         Override this method to create your daemon terminal
@@ -161,10 +189,19 @@ class TerminalManager(BaseObject):
161 189
         self.outputs_queues = {}
162 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 200
     def start(self) -> None:
165 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 205
             output_queue = Queue()
169 206
             self.outputs_queues[terminal] = output_queue
170 207
 

+ 2 - 1
synergine2_cocos2d/gui.py Parādīt failu

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

+ 44 - 0
tests/test_terminals.py Parādīt failu

@@ -1,9 +1,15 @@
1 1
 # coding: utf-8
2 2
 import time
3 3
 
4
+import pytest
5
+
4 6
 from synergine2.config import Config
7
+from synergine2.core import Core
8
+from synergine2.cycle import CycleManager
5 9
 from synergine2.log import SynergineLogger
6 10
 from synergine2.simulation import Event
11
+from synergine2.simulation import Simulation
12
+from synergine2.simulation import Subjects
7 13
 from synergine2.terminals import Terminal
8 14
 from synergine2.terminals import TerminalPackage
9 15
 from synergine2.terminals import TerminalManager
@@ -156,3 +162,41 @@ class TestTerminals(BaseTest):
156 162
         assert AnOtherEvent == type(packages[1].events[0])
157 163
 
158 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