Browse Source

Simplification of shared data design and usage

Bastien Sevajol 6 years ago
parent
commit
82add56815

+ 1 - 5
perf.py View File

@@ -21,12 +21,8 @@ from synergine2.simulation import Simulation, Subjects
21 21
 def simulate(complexity, subject_count, cycle_count, cores):
22 22
     config = Config(dict(complexity=complexity, core=dict(use_x_cores=cores)))
23 23
     simulation = Simulation(config)
24
-
25
-    simulation.add_to_index(ComputeSubject)
26
-    simulation.add_to_index(ComputeBehaviour)
27
-    simulation.add_to_index(ComputeMechanism)
28
-
29 24
     subjects = Subjects(simulation=simulation)
25
+
30 26
     for i in range(subject_count):
31 27
         subject = ComputeSubject(
32 28
             config=config,

+ 1 - 1
sandbox/perf/simulation.py View File

@@ -55,7 +55,7 @@ class ComputeBehaviour(SubjectBehaviour):
55 55
 
56 56
 class ComputeSubject(Subject):
57 57
     behaviours_classes = [ComputeBehaviour]
58
-    data = shared.create(['{id}', 'data'], [])
58
+    data = shared.create_self('data', lambda: [])
59 59
 
60 60
     def __init__(
61 61
         self,

+ 0 - 2
sandbox/tile/run.py View File

@@ -30,8 +30,6 @@ def main(map_dir_path: str, seed_value: int=42):
30 30
     map_file_path = 'sandbox/tile/{}.tmx'.format(os.path.join(map_dir_path, os.path.basename(map_dir_path)))
31 31
 
32 32
     simulation = TileStrategySimulation(config, map_file_path=map_file_path)
33
-    simulation.add_to_index(Man, MoveToBehaviour, MoveToMechanism)
34
-
35 33
     subjects = TileStrategySubjects(simulation=simulation)
36 34
 
37 35
     for position in ((0, 2),):

+ 1 - 1
sandbox/tile/simulation/subject.py View File

@@ -5,7 +5,7 @@ from synergine2_xyz.move import MoveToBehaviour
5 5
 
6 6
 
7 7
 class Man(BaseSubject):
8
-    collections = [
8
+    start_collections = [
9 9
         COLLECTION_ALIVE,
10 10
     ]
11 11
     behaviours_classes = [

+ 9 - 0
synergine2/base.py View File

@@ -4,3 +4,12 @@
4 4
 class BaseObject(object):
5 5
     def repr_debug(self) -> str:
6 6
         return type(self).__name__
7
+
8
+
9
+class IdentifiedObject(BaseObject):
10
+    def __init__(self, *args, **kwargs):
11
+        self._id = id(self)
12
+
13
+    @property
14
+    def id(self) -> int:
15
+        return self._id

+ 16 - 56
synergine2/cycle.py View File

@@ -35,9 +35,6 @@ class CycleManager(BaseObject):
35 35
         self.current_cycle = -1
36 36
         self.first_cycle = True
37 37
 
38
-        self.subject_mechanisms_cache = {}  # type: typing.Dict[int, typing.Dict[str, SubjectMechanism]]
39
-        self.subject_behaviours_cache = {}  # type: typing.Dict[int, typing.Dict[str, SubjectBehaviour]]
40
-
41 38
         # TODO NOW: Les processes devront maintenir une liste des subjects qui sont nouveaux.ne connaissent pas
42 39
         # Attention a ce qu'in ne soient pas "expose" quand on créer ces subjects au sein du process.
43 40
         # Ces subjects ont vocation à adopter l'id du vrau subject tout de suite après leur instanciation
@@ -107,7 +104,7 @@ class CycleManager(BaseObject):
107 104
                     str(mechanism_data),
108 105
                 ))
109 106
 
110
-            mechanisms_data[mechanism.__class__.__name__] = mechanism_data
107
+            mechanisms_data[mechanism.__class__] = mechanism_data
111 108
 
112 109
         behaviours = self.simulation.behaviours.values()
113 110
         self.logger.info('{} behaviours to compute'.format(str(len(behaviours))))
@@ -126,7 +123,7 @@ class CycleManager(BaseObject):
126 123
                 ))
127 124
 
128 125
             if behaviour_data:
129
-                behaviours_data[id(behaviour.__class__)] = behaviour_data
126
+                behaviours_data[behaviour.__class__] = behaviour_data
130 127
 
131 128
         return behaviours_data
132 129
 
@@ -168,8 +165,7 @@ class CycleManager(BaseObject):
168 165
         results_by_processes = self.process_manager.make_them_work(JOB_TYPE_SIMULATION)
169 166
 
170 167
         for process_result in results_by_processes:
171
-            for behaviour_class_id, behaviour_result in process_result.items():
172
-                behaviour_class = self.simulation.index[behaviour_class_id]
168
+            for behaviour_class, behaviour_result in process_result.items():
173 169
                 results[behaviour_class] = behaviour_class.merge_data(
174 170
                     behaviour_result,
175 171
                     results.get(behaviour_class),
@@ -179,7 +175,7 @@ class CycleManager(BaseObject):
179 175
 
180 176
         # Make events
181 177
         for behaviour_class, behaviour_data in results.items():
182
-            behaviour_events = self.simulation.behaviours[behaviour_class.__name__].action(behaviour_data)
178
+            behaviour_events = self.simulation.behaviours[behaviour_class].action(behaviour_data)
183 179
             self.logger.info('{} behaviour generate {} events'.format(
184 180
                 str(behaviour_class),
185 181
                 str(len(behaviour_events)),
@@ -215,21 +211,21 @@ class CycleManager(BaseObject):
215 211
                     subject_behaviours_results,
216 212
                 ))
217 213
 
218
-            subject_behaviours = self.get_subject_behaviours(subject)
219
-            for behaviour_class_name, behaviour_data in subject_behaviours_results.items():
214
+            subject_behaviours = subject.behaviours
215
+            for behaviour_class, behaviour_data in subject_behaviours_results.items():
220 216
                 # TODO: Ajouter une etape de selection des actions a faire (genre neuronnal)
221 217
                 # (genre se cacher et fuir son pas compatibles)
222
-                behaviour_events = subject_behaviours[behaviour_class_name].action(behaviour_data)
218
+                behaviour_events = subject_behaviours[behaviour_class].action(behaviour_data)
223 219
 
224 220
                 self.logger.info('{} behaviour for subject {} generate {} events'.format(
225
-                    str(behaviour_class_name),
221
+                    str(behaviour_class.__name__),
226 222
                     str(subject.id),
227 223
                     str(len(behaviour_events)),
228 224
                 ))
229 225
 
230 226
                 if self.logger.is_debug:
231 227
                     self.logger.debug('{} behaviour for subject {} generated events: {}'.format(
232
-                        str(behaviour_class_name),
228
+                        str(behaviour_class.__name__),
233 229
                         str(subject.id),
234 230
                         str([e.repr_debug() for e in behaviour_events]),
235 231
                     ))
@@ -249,7 +245,7 @@ class CycleManager(BaseObject):
249 245
         self.logger.info('Subjects computing: {} subjects to compute'.format(str(len(subjects))))
250 246
 
251 247
         for subject in subjects:
252
-            mechanisms = self.get_subject_mechanisms(subject)
248
+            mechanisms = subject.mechanisms.values()
253 249
 
254 250
             if mechanisms:
255 251
                 self.logger.info('Subject {}: {} mechanisms'.format(
@@ -260,13 +256,13 @@ class CycleManager(BaseObject):
260 256
                 if self.logger.is_debug:
261 257
                     self.logger.info('Subject {}: mechanisms are: {}'.format(
262 258
                         str(subject.id),
263
-                        str([m.repr_debug for n, m in mechanisms.items()])
259
+                        str([m.repr_debug for m in mechanisms])
264 260
                     ))
265 261
 
266 262
             mechanisms_data = {}
267 263
             behaviours_data = {}
268 264
 
269
-            for mechanism_class_name, mechanism in mechanisms.items():
265
+            for mechanism in mechanisms:
270 266
                 with time_it() as elapsed_time:
271 267
                     mechanism_data = mechanism.run()
272 268
                 if self.logger.is_debug:
@@ -277,7 +273,7 @@ class CycleManager(BaseObject):
277 273
                         elapsed_time.get_final_time(),
278 274
                     ))
279 275
 
280
-                mechanisms_data[mechanism_class_name] = mechanism_data
276
+                mechanisms_data[mechanism.__class__] = mechanism_data
281 277
 
282 278
             if mechanisms:
283 279
                 if self.logger.is_debug:
@@ -286,7 +282,7 @@ class CycleManager(BaseObject):
286 282
                         str(mechanisms_data),
287 283
                     ))
288 284
 
289
-            subject_behaviours = self.get_subject_behaviours(subject)
285
+            subject_behaviours = subject.behaviours
290 286
             if not subject_behaviours:
291 287
                 break
292 288
 
@@ -295,7 +291,7 @@ class CycleManager(BaseObject):
295 291
                 str(len(subject_behaviours)),
296 292
             ))
297 293
 
298
-            for behaviour_class_name, behaviour in subject_behaviours.items():
294
+            for behaviour in subject_behaviours.values():
299 295
                 self.logger.info('Subject {}: run {} behaviour'.format(
300 296
                     str(subject.id),
301 297
                     str(type(behaviour)),
@@ -314,47 +310,11 @@ class CycleManager(BaseObject):
314 310
                     ))
315 311
 
316 312
                 if behaviour_data:
317
-                    behaviours_data[behaviour_class_name] = behaviour_data
313
+                    behaviours_data[behaviour.__class__] = behaviour_data
318 314
 
319 315
             results[subject.id] = behaviours_data
320 316
         return results
321 317
 
322
-    def get_subject_mechanisms(self, subject: Subject) -> typing.Dict[str, SubjectMechanism]:
323
-        # TODO: Implementer un systeme qui inhibe des mechanisme (ex. someil inhibe l'ouie)
324
-        # Attention: c'est utilisé dans le main process aussi, pertinent de la faire là ?
325
-        try:
326
-            return self.subject_mechanisms_cache[subject.id]
327
-        except KeyError:
328
-            mechanisms = {}
329
-            for mechanism_class_id in shared.get('subject_mechanisms_index')[subject.id]:
330
-                mechanism_class = self.simulation.index[mechanism_class_id]
331
-                mechanism = mechanism_class(
332
-                    self.config,
333
-                    self.simulation,
334
-                    subject,
335
-                )
336
-                mechanisms[mechanism_class.__name__] = mechanism
337
-            self.subject_mechanisms_cache[subject.id] = mechanisms
338
-            return mechanisms
339
-
340
-    def get_subject_behaviours(self, subject: Subject) -> typing.Dict[str, SubjectBehaviour]:
341
-        # TODO: Implementer un systeme qui inhibe des behaviours (ex. someil inhibe avoir faim)
342
-        # Attention: c'est utilisé dans le main process aussi, pertinent de la faire là ?
343
-        try:
344
-            return self.subject_behaviours_cache[subject.id]
345
-        except KeyError:
346
-            behaviours = {}
347
-            for behaviour_class_id in shared.get('subject_behaviours_index')[subject.id]:
348
-                behaviour_class = self.simulation.index[behaviour_class_id]
349
-                behaviour = behaviour_class(
350
-                    self.config,
351
-                    self.simulation,
352
-                    subject,
353
-                )
354
-                behaviours[behaviour_class.__name__] = behaviour
355
-            self.subject_behaviours_cache[subject.id] = behaviours
356
-            return behaviours
357
-
358 318
     def apply_actions(
359 319
             self,
360 320
             simulation_actions: [tuple]=None,

+ 103 - 78
synergine2/share.py View File

@@ -2,13 +2,17 @@
2 2
 import pickle
3 3
 import typing
4 4
 
5
-import collections
6 5
 import redis
7 6
 
7
+from synergine2.base import IdentifiedObject
8 8
 from synergine2.exceptions import SynergineException
9 9
 from synergine2.exceptions import UnknownSharedData
10 10
 
11 11
 
12
+class NoSharedDataInstance(SynergineException):
13
+    pass
14
+
15
+
12 16
 class SharedDataIndex(object):
13 17
     def __init__(
14 18
         self,
@@ -25,22 +29,58 @@ class SharedDataIndex(object):
25 29
         raise NotImplementedError()
26 30
 
27 31
 
32
+class SharedData(object):
33
+    def __init__(
34
+        self, key: str,
35
+        self_type: bool=False,
36
+        default: typing.Any=None,
37
+    ) -> None:
38
+        """
39
+        :param key: shared data key
40
+        :param self_type: if it is a magic shared data where real key is association of key and instance id
41
+        :param default: default/initial value to shared data. Can be a callable to return list or dict
42
+        """
43
+        self._key = key
44
+        self.self_type = self_type
45
+        self._default = default
46
+        self.is_special_type = isinstance(self.default_value, (list, dict))
47
+        if self.is_special_type:
48
+            if isinstance(self.default_value, list):
49
+                self.special_type = TrackedList
50
+            elif isinstance(self.default_value, dict):
51
+                self.special_type = TrackedDict
52
+            else:
53
+                raise NotImplementedError()
54
+
55
+    def get_final_key(self, instance: IdentifiedObject) -> str:
56
+        if self.self_type:
57
+            return '{}_{}'.format(instance.id, self._key)
58
+        return self._key
59
+
60
+    @property
61
+    def default_value(self) -> typing.Any:
62
+        if callable(self._default):
63
+            return self._default()
64
+
65
+        return self._default
66
+
67
+
28 68
 class TrackedDict(dict):
29 69
     base = dict
30 70
 
31 71
     def __init__(self, seq=None, **kwargs):
32
-        self.key = kwargs.pop('key')
33
-        self.original_key = kwargs.pop('original_key')
72
+        self.shared_data = kwargs.pop('shared_data')
34 73
         self.shared = kwargs.pop('shared')
74
+        self.instance = kwargs.pop('instance')
35 75
         super().__init__(seq, **kwargs)
36 76
 
37 77
     def __setitem__(self, key, value):
38 78
         super().__setitem__(key, value)
39
-        self.shared.set(self.key, dict(self), original_key=self.original_key)
79
+        self.shared.set(self.shared_data.get_final_key(self.instance), dict(self))
40 80
 
41 81
     def setdefault(self, k, d=None):
42 82
         v = super().setdefault(k, d)
43
-        self.shared.set(self.key, dict(self), original_key=self.original_key)
83
+        self.shared.set(self.shared_data.get_final_key(self.instance), dict(self))
44 84
         return v
45 85
     # TODO: Cover all methods
46 86
 
@@ -49,14 +89,14 @@ class TrackedList(list):
49 89
     base = list
50 90
 
51 91
     def __init__(self, seq=(), **kwargs):
52
-        self.key = kwargs.pop('key')
53
-        self.original_key = kwargs.pop('original_key')
92
+        self.shared_data = kwargs.pop('shared_data')
54 93
         self.shared = kwargs.pop('shared')
94
+        self.instance = kwargs.pop('instance')
55 95
         super().__init__(seq)
56 96
 
57 97
     def append(self, p_object):
58 98
         super().append(p_object)
59
-        self.shared.set(self.key, list(self), original_key=self.original_key)
99
+        self.shared.set(self.shared_data.get_final_key(self.instance), list(self))
60 100
 
61 101
     # TODO: Cover all methods
62 102
 
@@ -69,6 +109,8 @@ class SharedDataManager(object):
69 109
     def __init__(self, clear: bool=True):
70 110
         self._r = redis.StrictRedis(host='localhost', port=6379, db=0)  # TODO: configs
71 111
 
112
+        self._shared_data_list = []  # type: typing.List[SharedData]
113
+
72 114
         self._data = {}
73 115
         self._modified_keys = set()
74 116
         self._default_values = {}
@@ -88,61 +130,31 @@ class SharedDataManager(object):
88 130
         self.commit()
89 131
         self._data = {}
90 132
 
91
-    def set(self, key: str, value: typing.Any, original_key: str=None) -> None:
92
-        try:
93
-            special_type, original_key_ = self._special_types[key]
94
-            value = special_type(value, key=key, shared=self, original_key=original_key)
95
-        except KeyError:
96
-            try:
97
-                # TODO: Code degeu pour gerer les {id}_truc
98
-                special_type, original_key_ = self._special_types[original_key]
99
-                value = special_type(value, key=key, shared=self, original_key=original_key)
100
-            except KeyError:
101
-                pass
133
+    def purge_data(self):
134
+        self._data = {}
102 135
 
136
+    def set(self, key: str, value: typing.Any) -> None:
103 137
         self._data[key] = value
104
-        self._modified_keys.add((key, original_key))
105
-
106
-    def get(self, *key_args: typing.Union[str, float, int]) -> typing.Any:
107
-        key = '_'.join([str(v) for v in key_args])
138
+        self._modified_keys.add(key)
108 139
 
140
+    def get(self, key: str) -> typing.Any:
109 141
         try:
110 142
             return self._data[key]
111 143
         except KeyError:
112
-            b_value = self._r.get(key)
113
-            if b_value is None:
144
+            database_value = self._r.get(key)
145
+            if database_value is None:
114 146
                 # We not allow None value storage
115 147
                 raise UnknownSharedData('No shared data for key "{}"'.format(key))
116
-
117
-            value = pickle.loads(b_value)
118
-            special_type = None
119
-
120
-            try:
121
-                special_type, original_key = self._special_types[key]
122
-            except KeyError:
123
-                pass
124
-
125
-            if special_type:
126
-                self._data[key] = special_type(value, key=key, shared=self, original_key=original_key)
127
-            else:
128
-                self._data[key] = value
148
+            value = pickle.loads(database_value)
149
+            self._data[key] = value
129 150
 
130 151
         return self._data[key]
131 152
 
132 153
     def commit(self) -> None:
133
-        for key, original_key in self._modified_keys:
134
-            try:
135
-                special_type, original_key = self._special_types[key]
136
-                value = special_type.base(self.get(key))
137
-                self._r.set(key, pickle.dumps(value))
138
-            except KeyError:
139
-                # Code degeu pour gerer les {id}_truc
140
-                try:
141
-                    special_type, original_key = self._special_types[original_key]
142
-                    value = special_type.base(self.get(key))
143
-                    self._r.set(key, pickle.dumps(value))
144
-                except KeyError:
145
-                    self._r.set(key, pickle.dumps(self.get(key)))
154
+        for key in self._modified_keys:
155
+            value = self.get(key)
156
+            self._r.set(key, pickle.dumps(value))
157
+
146 158
         self._modified_keys = set()
147 159
 
148 160
     def refresh(self) -> None:
@@ -157,55 +169,63 @@ class SharedDataManager(object):
157 169
     ) -> SharedDataIndex:
158 170
         return shared_data_index_class(self, key, *args, **kwargs)
159 171
 
172
+    def create_self(
173
+        self,
174
+        key: str,
175
+        default: typing.Any,
176
+        indexes: typing.List[SharedDataIndex]=None,
177
+    ):
178
+        return self.create(key, self_type=True, value=default, indexes=indexes)
179
+
160 180
     def create(
161 181
         self,
162
-        key_args: typing.Union[str, typing.List[typing.Union[str, int, float]]],
182
+        key: str,
163 183
         value: typing.Any,
184
+        self_type: bool=False,
164 185
         indexes: typing.List[SharedDataIndex]=None,
165 186
     ):
166
-        key = key_args
167
-        if not isinstance(key, str):
168
-            key = '_'.join(key_args)
187
+        # TODO: Store all keys and forbid re-use one
169 188
         indexes = indexes or []
189
+        shared_data = SharedData(
190
+            key=key,
191
+            self_type=self_type,
192
+            default=value,
193
+        )
194
+        self._shared_data_list.append(shared_data)
170 195
 
171
-        if type(value) is dict:
172
-            value = TrackedDict(value, key=key, shared=shared, original_key=key)
173
-            self._special_types[key] = TrackedDict, key
174
-        elif type(value) is list:
175
-            value = TrackedList(value, key=key, shared=shared, original_key=key)
176
-            self._special_types[key] = TrackedList, key
177
-
178
-        def get_key(obj):
179
-            return key
196
+        def fget(instance):
197
+            final_key = shared_data.get_final_key(instance)
180 198
 
181
-        def get_key_with_id(obj):
182
-            return key.format(id=obj.id)
199
+            try:
200
+                value_ = self.get(final_key)
201
+                if not shared_data.is_special_type:
202
+                    return value_
203
+                else:
204
+                    return shared_data.special_type(value_, shared_data=shared_data, shared=self, instance=instance)
183 205
 
184
-        if '{id}' in key:
185
-            key_formatter = get_key_with_id
186
-        else:
187
-            self.set(key, value)
188
-            self._default_values[key] = value
189
-            key_formatter = get_key
206
+            except UnknownSharedData:
207
+                # If no data in database, value for this shared_data have been never set
208
+                self.set(final_key, shared_data.default_value)
209
+                self._default_values[final_key] = shared_data.default_value
210
+                return self.get(final_key)
190 211
 
191
-        def fget(self_):
192
-            return self.get(key_formatter(self_))
212
+        def fset(instance, value_):
213
+            final_key = shared_data.get_final_key(instance)
193 214
 
194
-        def fset(self_, value_):
195 215
             try:
196
-                previous_value = self.get(key_formatter(self_))
216
+                previous_value = self.get(final_key)
197 217
                 for index in indexes:
198 218
                     index.remove(previous_value)
199 219
             except UnknownSharedData:
200 220
                 pass  # If no shared data, no previous value to remove
201 221
 
202
-            self.set(key_formatter(self_), value_, original_key=key)
222
+            self.set(final_key, value_)
203 223
 
204 224
             for index in indexes:
205 225
                 index.add(value_)
206 226
 
207 227
         def fdel(self_):
208
-            raise SynergineException('You cannot delete a shared data')
228
+            raise SynergineException('You cannot delete a shared data: not implemented yet')
209 229
 
210 230
         shared_property = property(
211 231
             fget=fget,
@@ -213,6 +233,11 @@ class SharedDataManager(object):
213 233
             fdel=fdel,
214 234
         )
215 235
 
236
+        # A simple shared data can be set now because no need to build key with instance id
237
+        if not self_type:
238
+            self.set(key, shared_data.default_value)
239
+            self._default_values[key] = shared_data.default_value
240
+
216 241
         return shared_property
217 242
 
218 243
 # TODO: Does exist a way to permit overload of SharedDataManager class ?

+ 42 - 41
synergine2/simulation.py View File

@@ -1,8 +1,8 @@
1 1
 # coding: utf-8
2
-import collections
3 2
 import typing
4 3
 
5 4
 from synergine2.base import BaseObject
5
+from synergine2.base import IdentifiedObject
6 6
 from synergine2.config import Config
7 7
 from synergine2.share import shared
8 8
 from synergine2.utils import get_mechanisms_classes
@@ -13,11 +13,10 @@ class Intention(object):
13 13
 
14 14
 
15 15
 class IntentionManager(object):
16
-    intentions = shared.create(['{id}', 'intentions'], {})  # type: typing.Dict[typing.Type[Intention], Intention]
16
+    intentions = shared.create_self('intentions', lambda: {})  # type: typing.Dict[typing.Type[Intention], Intention]
17 17
 
18 18
     def __init__(self):
19 19
         self._id = id(self)
20
-        self.intentions = {}
21 20
 
22 21
     @property
23 22
     def id(self) -> int:
@@ -31,8 +30,8 @@ class IntentionManager(object):
31 30
         return self.intentions[intention_type]
32 31
 
33 32
 
34
-class Subject(BaseObject):
35
-    collections = []
33
+class Subject(IdentifiedObject):
34
+    start_collections = []
36 35
     behaviours_classes = []
37 36
     behaviour_selector_class = None  # type: typing.Type[SubjectBehaviourSelector]
38 37
     intention_manager_class = None  # type: typing.Type[IntentionManager]
@@ -42,8 +41,9 @@ class Subject(BaseObject):
42 41
         config: Config,
43 42
         simulation: 'Simulation',
44 43
     ):
45
-        # TODO: Bannir les attribut de classe passé en reference ! Et meme virer les attr de classe tout court.
46
-        self.collections = self.collections[:]
44
+        super().__init__()
45
+        # FIXME: use shared data to permit dynamic start_collections
46
+        self.collections = self.start_collections[:]
47 47
 
48 48
         self.config = config
49 49
         self._id = id(self)  # We store object id because it's lost between process
@@ -60,27 +60,43 @@ class Subject(BaseObject):
60 60
         else:
61 61
             self.intentions = IntentionManager()
62 62
 
63
+        self._mechanisms = None  # type: typing.Dict[typing.Type['SubjectMechanism'], 'SubjectMechanism']
64
+        self._behaviours = None  # type: typing.Dict[typing.Type['SubjectBehaviour'], 'SubjectBehaviour']
65
+
63 66
     @property
64
-    def id(self) -> int:
65
-        try:
66
-            return self._id
67
-        except AttributeError:
68
-            pass
67
+    def mechanisms(self) -> typing.Dict[typing.Type['SubjectMechanism'], 'SubjectMechanism']:
68
+        if self._mechanisms is None:
69
+            self._mechanisms = {}
70
+            for behaviour_class in self.behaviours_classes:
71
+                for mechanism_class in behaviour_class.use:
72
+                    mechanism = mechanism_class(
73
+                        self.config,
74
+                        self.simulation,
75
+                        self,
76
+                    )
77
+                    self._mechanisms[mechanism_class] = mechanism
78
+        return self._mechanisms
79
+
80
+    @property
81
+    def behaviours(self) -> typing.Dict[typing.Type['SubjectBehaviour'], 'SubjectBehaviour']:
82
+        if self._behaviours is None:
83
+            self._behaviours = {}
84
+            for behaviour_class in self.behaviours_classes:
85
+                behaviour = behaviour_class(
86
+                    self.config,
87
+                    self.simulation,
88
+                    self,
89
+                )
90
+                self._behaviours[behaviour_class] = behaviour
91
+        return self._behaviours
69 92
 
70 93
     def change_id(self, id_: int) -> None:
71 94
         self._id = id_
72 95
 
73 96
     def expose(self) -> None:
74
-        subject_behaviours_index = shared.get('subject_behaviours_index').setdefault(self._id, [])
75
-        subject_mechanisms_index = shared.get('subject_mechanisms_index').setdefault(self._id, [])
76 97
         subject_classes = shared.get('subject_classes')
77
-
78
-        for behaviour_class in self.behaviours_classes:
79
-            subject_behaviours_index.append(id(behaviour_class))
80
-            for mechanism_class in behaviour_class.use:
81
-                subject_mechanisms_index.append(id(mechanism_class))
82
-
83
-        subject_classes[self._id] = id(type(self))
98
+        subject_classes[self._id] = type(self)
99
+        shared.set('subject_classes', subject_classes)
84 100
 
85 101
         for collection in self.collections:
86 102
             self.simulation.collections.setdefault(collection, []).append(self.id)
@@ -129,7 +145,7 @@ class Subjects(list):
129 145
         self.subject_ids.remove(value.id)
130 146
         # Remove from subjects list
131 147
         super().remove(value)
132
-        # Remove from collections
148
+        # Remove from start_collections
133 149
         for collection_name in value.collections:
134 150
             self.simulation.collections[collection_name].remove(value)
135 151
         # Add to removed listing
@@ -158,10 +174,8 @@ class Simulation(BaseObject):
158 174
     accepted_subject_class = Subjects
159 175
     behaviours_classes = []
160 176
 
161
-    subject_behaviours_index = shared.create('subject_behaviours_index', {})
162
-    subject_mechanisms_index = shared.create('subject_mechanisms_index', {})
163 177
     subject_classes = shared.create('subject_classes', {})
164
-    collections = shared.create('collections', {})
178
+    collections = shared.create('start_collections', {})
165 179
 
166 180
     def __init__(
167 181
         self,
@@ -170,35 +184,23 @@ class Simulation(BaseObject):
170 184
         self.config = config
171 185
         self._subjects = None  # type: Subjects
172 186
 
173
-        # Should contain all usable class of Behaviors, Mechanisms, SubjectBehaviourSelectors,
174
-        # IntentionManagers, Subject
175
-        self._index = {}  # type: typing.Dict[int, type]
176 187
         self._index_locked = False
177 188
 
178 189
         self.behaviours = {}
179 190
         self.mechanisms = {}
180 191
 
181 192
         for mechanism_class in get_mechanisms_classes(self):
182
-            self.mechanisms[mechanism_class.__name__] = mechanism_class(
193
+            self.mechanisms[mechanism_class] = mechanism_class(
183 194
                 config=self.config,
184 195
                 simulation=self,
185 196
             )
186 197
 
187 198
         for behaviour_class in self.behaviours_classes:
188
-            self.behaviours[behaviour_class.__name__] = behaviour_class(
199
+            self.behaviours[behaviour_class] = behaviour_class(
189 200
                 config=self.config,
190 201
                 simulation=self,
191 202
             )
192 203
 
193
-    def add_to_index(self, *classes: type) -> None:
194
-        assert not self._index_locked
195
-        for class_ in classes:
196
-            self._index[id(class_)] = class_
197
-
198
-    @property
199
-    def index(self) -> typing.Dict[int, type]:
200
-        return self._index
201
-
202 204
     def lock_index(self) -> None:
203 205
         self._index_locked = True
204 206
 
@@ -219,8 +221,7 @@ class Simulation(BaseObject):
219 221
             return self._subjects.index[subject_id]
220 222
         except KeyError:
221 223
             # We should be in process context and subject have to been created
222
-            subject_class_id = shared.get('subject_classes')[subject_id]
223
-            subject_class = self.index[subject_class_id]
224
+            subject_class = shared.get('subject_classes')[subject_id]
224 225
             subject = subject_class(self.config, self)
225 226
             subject.change_id(subject_id)
226 227
             self.subjects.append(subject)

+ 1 - 1
synergine2_xyz/mechanism.py View File

@@ -9,4 +9,4 @@ class ProximitySubjectMechanism(ProximityMixin, SubjectMechanism):
9 9
             position=self.subject.position,
10 10
             simulation=self.simulation,
11 11
             exclude_subject=self.subject,
12
-        )
12
+        )

+ 1 - 1
synergine2_xyz/move.py View File

@@ -127,7 +127,7 @@ class MoveToBehaviour(SubjectBehaviour):
127 127
     def run(self, data):
128 128
         # TODO: on fait vraiment rien ici ? Note: meme si il n'y a pas de new_path, l'action doit s'effectuer
129 129
         # du moment qu'il y a une intention de move
130
-        move_to_data = data[self.move_to_mechanism.__name__]
130
+        move_to_data = data[self.move_to_mechanism]
131 131
         if move_to_data:
132 132
             return move_to_data
133 133
         return False

+ 2 - 2
synergine2_xyz/xyz.py View File

@@ -110,13 +110,13 @@ def get_degree_from_north(a, b):
110 110
 class XYZSubjectMixinMetaClass(type):
111 111
     def __init__(cls, name, parents, attribs):
112 112
         super().__init__(name, parents, attribs)
113
-        collections = getattr(cls, "collections", [])
113
+        collections = getattr(cls, "start_collections", [])
114 114
         if COLLECTION_XYZ not in collections:
115 115
             collections.append(COLLECTION_XYZ)
116 116
 
117 117
 
118 118
 class XYZSubjectMixin(object, metaclass=XYZSubjectMixinMetaClass):
119
-    position = shared.create(['{id}', 'counter'], (0, 0, 0))
119
+    position = shared.create_self('position', lambda: (0, 0, 0))
120 120
 
121 121
     def __init__(self, *args, **kwargs):
122 122
         """

+ 2 - 16
tests/test_cycle.py View File

@@ -28,8 +28,7 @@ class MySubjectBehavior(SubjectBehaviour):
28 28
     use = [MySubjectMechanism]
29 29
 
30 30
     def run(self, data):
31
-        class_name = MySubjectMechanism.__name__
32
-        if class_name in data and data[class_name] == 42:
31
+        if MySubjectMechanism in data and data[MySubjectMechanism] == 42:
33 32
             return self.subject.id
34 33
 
35 34
     def action(self, data) -> [Event]:
@@ -58,7 +57,7 @@ class MySimulationBehaviour(SimulationBehaviour):
58 57
         return start_data + new_data
59 58
 
60 59
     def run(self, data):
61
-        return data['MySimulationMechanism'] * 2
60
+        return data[MySimulationMechanism] * 2
62 61
 
63 62
     def action(self, data) -> [Event]:
64 63
         return [MyEvent(data)]
@@ -78,11 +77,6 @@ class TestCycle(BaseTest):
78 77
         subjects = MySubjects(simulation=simulation)
79 78
         simulation.subjects = subjects
80 79
 
81
-        # Prepare simulation class index
82
-        simulation.add_to_index(MySubjectBehavior)
83
-        simulation.add_to_index(MySubjectMechanism)
84
-        simulation.add_to_index(MySubject)
85
-
86 80
         for i in range(3):
87 81
             subjects.append(MySubject(config, simulation=simulation))
88 82
 
@@ -108,11 +102,6 @@ class TestCycle(BaseTest):
108 102
         subjects = MySubjects(simulation=simulation)
109 103
         simulation.subjects = subjects
110 104
 
111
-        # Prepare simulation class index
112
-        simulation.add_to_index(MySubjectBehavior)
113
-        simulation.add_to_index(MySubjectMechanism)
114
-        simulation.add_to_index(MySubject)
115
-
116 105
         for i in range(3):
117 106
             subjects.append(MySubject(config, simulation=simulation))
118 107
 
@@ -143,9 +132,6 @@ class TestCycle(BaseTest):
143 132
 
144 133
         simulation = MySimulation(config)
145 134
 
146
-        # Prepare simulation class index
147
-        simulation.add_to_index(MySimulationBehaviour, MySimulationMechanism)
148
-
149 135
         subjects = MySubjects(simulation=simulation)
150 136
         simulation.subjects = subjects
151 137
 

+ 121 - 149
tests/test_share.py View File

@@ -3,6 +3,7 @@ import pickle
3 3
 
4 4
 import pytest
5 5
 
6
+from synergine2.base import IdentifiedObject
6 7
 from synergine2.exceptions import UnknownSharedData
7 8
 from synergine2 import share
8 9
 from tests import BaseTest
@@ -13,21 +14,6 @@ class TestShare(BaseTest):
13 14
         shared = share.SharedDataManager()
14 15
 
15 16
         class Foo(object):
16
-            counter = shared.create('counter', value=0)
17
-
18
-        foo = Foo()
19
-        foo.counter = 42
20
-
21
-        assert shared.get('counter') == 42
22
-
23
-        foo.counter = 48
24
-
25
-        assert shared.get('counter') == 48
26
-
27
-    def test_default_value(self):
28
-        shared = share.SharedDataManager()
29
-
30
-        class Foo(object):
31 17
             counter = shared.create('counter', 0)
32 18
 
33 19
         foo = Foo()
@@ -39,126 +25,6 @@ class TestShare(BaseTest):
39 25
 
40 26
         assert shared.get('counter') == 48
41 27
 
42
-    def test_dynamic_key(self):
43
-        shared = share.SharedDataManager()
44
-
45
-        class Foo(object):
46
-            counter = shared.create(
47
-                ['{id}', 'counter'],
48
-                value=0,
49
-                indexes=[],
50
-            )
51
-
52
-            @property
53
-            def id(self):
54
-                return id(self)
55
-
56
-        foo = Foo()
57
-        foo.counter = 42
58
-
59
-        assert shared.get(foo.id, 'counter') == 42
60
-
61
-        foo.counter = 48
62
-
63
-        assert shared.get(foo.id, 'counter') == 48
64
-
65
-    def test_multiple_uses(self):
66
-        shared = share.SharedDataManager()
67
-
68
-        class Foo(object):
69
-            position = shared.create(
70
-                '{id}_position',
71
-                (0, 0, 0),
72
-                indexes=[],
73
-            )
74
-
75
-            @property
76
-            def id(self):
77
-                return id(self)
78
-
79
-        foo = Foo()
80
-        foo.position = (0, 1, 2)
81
-
82
-        assert shared.get('{}_position'.format(foo.id)) == (0, 1, 2)
83
-
84
-        foo2 = Foo()
85
-        foo2.position = (3, 4, 5)
86
-
87
-        assert shared.get('{}_position'.format(foo.id)) == (0, 1, 2)
88
-        assert shared.get('{}_position'.format(foo2.id)) == (3, 4, 5)
89
-
90
-    def test_update_dict_with_pointer(self):
91
-        shared = share.SharedDataManager()
92
-
93
-        class Foo(object):
94
-            data = shared.create('data', {})
95
-
96
-        foo = Foo()
97
-        foo.data = {'foo': 'bar'}
98
-
99
-        assert shared.get('data') == {'foo': 'bar'}
100
-
101
-        foo.data['foo'] = 'buz'
102
-        assert shared.get('data') == {'foo': 'buz'}
103
-
104
-        shared.commit()
105
-        assert shared.get('data') == {'foo': 'buz'}
106
-        assert pickle.loads(shared._r.get('data')) == {'foo': 'buz'}
107
-
108
-        foo.data['foo'] = 'bAz'
109
-        shared.commit()
110
-        assert shared.get('data') == {'foo': 'bAz'}
111
-        assert pickle.loads(shared._r.get('data')) == {'foo': 'bAz'}
112
-
113
-    def test_update_list_with_pointer(self):
114
-        shared = share.SharedDataManager()
115
-
116
-        class Foo(object):
117
-            data = shared.create('data', [])
118
-
119
-        foo = Foo()
120
-        foo.data = ['foo']
121
-
122
-        assert shared.get('data') == ['foo']
123
-
124
-        foo.data.append('bar')
125
-        assert shared.get('data') == ['foo', 'bar']
126
-
127
-        shared.commit()
128
-        assert shared.get('data') == ['foo', 'bar']
129
-        assert pickle.loads(shared._r.get('data')) == ['foo', 'bar']
130
-
131
-        foo.data.append('bAr')
132
-        shared.commit()
133
-        assert shared.get('data') == ['foo', 'bar', 'bAr']
134
-        assert pickle.loads(shared._r.get('data')) == ['foo', 'bar', 'bAr']
135
-
136
-    def test_update_list_with_pointer__composite_key(self):
137
-        shared = share.SharedDataManager()
138
-
139
-        class Foo(object):
140
-            data = shared.create(['{id}', 'data'], [])
141
-
142
-            def __init__(self):
143
-                self.id = id(self)
144
-
145
-        foo = Foo()
146
-        foo.data = ['foo']
147
-
148
-        assert shared.get(str(id(foo)) + '_data') == ['foo']
149
-
150
-        foo.data.append('bar')
151
-        assert shared.get(str(id(foo)) + '_data') == ['foo', 'bar']
152
-
153
-        shared.commit()
154
-        assert shared.get(str(id(foo)) + '_data') == ['foo', 'bar']
155
-        assert pickle.loads(shared._r.get(str(id(foo)) + '_data')) == ['foo', 'bar']
156
-
157
-        foo.data.append('bAr')
158
-        shared.commit()
159
-        assert shared.get(str(id(foo)) + '_data') == ['foo', 'bar', 'bAr']
160
-        assert pickle.loads(shared._r.get(str(id(foo)) + '_data')) == ['foo', 'bar', 'bAr']
161
-
162 28
     def test_refresh_without_commit(self):
163 29
         shared = share.SharedDataManager()
164 30
 
@@ -221,17 +87,13 @@ class TestShare(BaseTest):
221 87
 
222 88
         shared = share.SharedDataManager()
223 89
 
224
-        class Foo(object):
225
-            position = shared.create(
226
-                '{id}_position',
90
+        class Foo(IdentifiedObject):
91
+            position = shared.create_self(
92
+                'position',
227 93
                 (0, 0, 0),
228 94
                 indexes=[shared.make_index(ListIndex, 'positions')],
229 95
             )
230 96
 
231
-            @property
232
-            def id(self):
233
-                return id(self)
234
-
235 97
         with pytest.raises(UnknownSharedData):
236 98
             shared.get('positions')
237 99
 
@@ -252,22 +114,132 @@ class TestShare(BaseTest):
252 114
         assert shared.get('{}_position'.format(foo2.id)) == (6, 7, 8)
253 115
         assert shared.get('positions') == [(0, 1, 2), (6, 7, 8)]
254 116
 
117
+    def test_auto_self_shared_data(self):
118
+        shared = share.SharedDataManager()
119
+
120
+        class MySubject(IdentifiedObject):
121
+            age = shared.create_self('age', 0)
122
+
123
+            @property
124
+            def id(self):
125
+                return id(self)
126
+
127
+        subject = MySubject()
128
+        assert subject.age == 0
129
+
130
+    def test_auto_self_shared_data_with_callable(self):
131
+        shared = share.SharedDataManager()
132
+
133
+        class MySubject(IdentifiedObject):
134
+            colors = shared.create_self('colors', lambda: [])
135
+
136
+            @property
137
+            def id(self):
138
+                return id(self)
139
+
140
+        subjecta = MySubject()
141
+        subjectb = MySubject()
142
+
143
+        assert subjecta.colors == []
144
+        assert subjectb.colors == []
145
+
146
+        subjecta.colors = ['toto']
147
+        subjectb.colors = ['tata']
148
+
149
+        assert subjecta.colors == ['toto']
150
+        assert subjectb.colors == ['tata']
151
+
152
+    def test_update_self_dict_with_pointer(self):
153
+        shared = share.SharedDataManager()
154
+
155
+        class Foo(IdentifiedObject):
156
+            data = shared.create_self('data', lambda: {})
157
+
158
+        foo = Foo()
159
+        foo.data = {'foo': 'bar'}
160
+
161
+        assert foo.data == {'foo': 'bar'}
162
+
163
+        foo.data['foo'] = 'buz'
164
+        # In this case local data is used
165
+        assert foo.data == {'foo': 'buz'}
166
+
167
+        shared.commit()
168
+        shared.purge_data()
169
+
170
+        # In this case, "data" key was pending and has been commit
171
+        assert foo.data == {'foo': 'buz'}
172
+
173
+        # In this case "data" key was NOT pending and has not been commit
174
+        foo.data['foo'] = 'bAz'
175
+        shared.commit()
176
+        shared.purge_data()
177
+
178
+        assert foo.data == {'foo': 'bAz'}
179
+
180
+    def test_update_self_list_with_pointer(self):
181
+        shared = share.SharedDataManager()
182
+
183
+        class Foo(IdentifiedObject):
184
+            data = shared.create_self('data', [])
185
+
186
+        foo = Foo()
187
+        foo.data = ['foo']
188
+
189
+        assert foo.data == ['foo']
190
+
191
+        foo.data.append('bar')
192
+        assert foo.data == ['foo', 'bar']
193
+
194
+        shared.commit()
195
+        assert foo.data == ['foo', 'bar']
196
+
197
+        foo.data.append('bAr')
198
+        shared.commit()
199
+        assert foo.data == ['foo', 'bar', 'bAr']
200
+
201
+    def test_update_list_with_pointer(self):
202
+        shared = share.SharedDataManager()
203
+
204
+        class Foo(IdentifiedObject):
205
+            data = shared.create('data', [])
206
+
207
+        foo = Foo()
208
+        foo.data = ['foo']
209
+
210
+        assert foo.data == ['foo']
211
+        assert shared.get('data') == ['foo']
212
+
213
+        foo.data.append('bar')
214
+        assert shared.get('data') == ['foo', 'bar']
215
+
216
+    def test_update_dict_with_pointer(self):
217
+        shared = share.SharedDataManager()
218
+
219
+        class Foo(IdentifiedObject):
220
+            data = shared.create('data', lambda: {})
221
+
222
+        foo = Foo()
223
+        foo.data = {'foo': 'bar'}
224
+
225
+        assert shared.get('data') == {'foo': 'bar'}
226
+
227
+        foo.data['foo'] = 'buz'
228
+        # In this case local data is used
229
+        assert shared.get('data') == {'foo': 'buz'}
230
+
255 231
 
256 232
 class TestIndexes(BaseTest):
257 233
     def test_list_index(self):
258 234
         shared = share.SharedDataManager()
259 235
 
260
-        class Foo(object):
261
-            position = shared.create(
262
-                '{id}_position',
236
+        class Foo(IdentifiedObject):
237
+            position = shared.create_self(
238
+                'position',
263 239
                 (0, 0, 0),
264 240
                 indexes=[shared.make_index(share.ListIndex, 'positions')],
265 241
             )
266 242
 
267
-            @property
268
-            def id(self):
269
-                return id(self)
270
-
271 243
         with pytest.raises(UnknownSharedData):
272 244
             shared.get('positions')
273 245
 

+ 0 - 13
tests/test_xyz.py View File

@@ -25,11 +25,6 @@ class TestXYZ(BaseTest):
25 25
     def test_proximity_mechanism_with_one(self):
26 26
         shared.reset()
27 27
         simulation = XYZSimulation(Config())
28
-        simulation.add_to_index(
29
-            MyProximityMechanism,
30
-            MySubject,
31
-        )
32
-
33 28
         subject = MySubject(Config(), simulation, position=(0, 0, 0))
34 29
         other_subject = MySubject(Config(), simulation, position=(5, 0, 0))
35 30
 
@@ -58,10 +53,6 @@ class TestXYZ(BaseTest):
58 53
     def test_proximity_mechanism_excluding(self):
59 54
         shared.reset()
60 55
         simulation = XYZSimulation(Config())
61
-        simulation.add_to_index(
62
-            MyProximityMechanism,
63
-            MySubject,
64
-        )
65 56
 
66 57
         subject = MySubject(Config(), simulation, position=(0, 0, 0))
67 58
         other_subject = MySubject(Config(), simulation, position=(11, 0, 0))
@@ -88,10 +79,6 @@ class TestXYZ(BaseTest):
88 79
     def test_proximity_mechanism_with_multiple(self):
89 80
         shared.reset()
90 81
         simulation = XYZSimulation(Config())
91
-        simulation.add_to_index(
92
-            MyProximityMechanism,
93
-            MySubject,
94
-        )
95 82
 
96 83
         subject = MySubject(Config(), simulation, position=(0, 0, 0))
97 84
         other_subjects = []