component.py 35.8 KB
Newer Older
COULLON Helene's avatar
COULLON Helene committed
1 2 3 4 5 6 7 8
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
.. module:: component
   :synopsis: this file contains the Component class.
"""

Maverick Chardet's avatar
Maverick Chardet committed
9
import sys, time
Maverick Chardet's avatar
Juice  
Maverick Chardet committed
10
from queue import Queue
COULLON Helene's avatar
COULLON Helene committed
11
from abc import ABCMeta, abstractmethod
Maverick Chardet's avatar
Maverick Chardet committed
12
from typing import Dict, Tuple, List, Set, Callable
13

Maverick Chardet's avatar
Maverick Chardet committed
14 15 16 17 18
from concerto.place import Dock, Place
from concerto.dependency import DepType, Dependency
from concerto.transition import Transition
from concerto.gantt_chart import GanttChart
from concerto.utility import Messages, Printer
COULLON Helene's avatar
COULLON Helene committed
19

COULLON Helene's avatar
COULLON Helene committed
20 21 22 23 24 25 26
class Group(object):
    """
    This class is used to create a group object within a Component.
    A group is a set of places and transitions to which a service provide
    dependency is bound. This object facilitate the semantics and its
    efficiency.
    """
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
    
    class Operation():
        def __init__(self, delta : int):
            self.delta = delta
            
        def is_nothing(self) -> bool:
            return self.delta is 0
    
    def __init__(self, name : str):
        self.name : str = name
        self.elements : Set[str] = set()
        self.nb_tokens : int = 0
    
    def get_name(self) -> str:
        return self.name
    
    def add_places(self, places_names):
        self.elements.update(places_names)
    
    def add_transitions(self, transitions_names):
        self.elements.update(transitions_names)
    
    def add_state(self, state_name : str):
        self.elements.add(state_name)
    
    def add_transition(self, transition_name : str):
        self.elements.add(transition_name)
    
    def contains_place(self, place_name : str) -> bool:
        return place_name in self.elements
    
    def contains_transition(self, transition_name : str) -> bool:
        return transition_name in self.elements
    
    def contains_dock(self, dock : Dock) -> bool:
        return self.contains_transition(dock.get_transition().get_name())
    
    def enter_place_operation(self, input_docks : List[Dock]) -> Operation:
        delta : int = 0
        place_name : str = input_docks[0].get_place().get_name()
        if self.contains_place(place_name):
            delta += 1
        for dock in input_docks:
            if self.contains_dock(dock):
                delta -= 1
        return self.Operation(delta)
    
    def leave_place_operation(self, output_docks : List[Dock]) -> Operation:
        delta : int = 0
        place_name : str = output_docks[0].get_place().get_name()
        if self.contains_place(place_name):
            delta -= 1
        for dock in output_docks:
            if self.contains_dock(dock):
                delta += 1
        return self.Operation(delta)
    
    def is_activating(self, operation : Operation) -> bool:
        return (not self.is_active()) and (operation.delta > 0)
    
    def is_deactivating(self, operation : Operation) -> bool:
        return self.is_active() and (self.nb_tokens + operation.delta is 0)
    
    def is_active(self):
        return self.nb_tokens > 0
    
    def apply(self, operation : Operation):
        if self.nb_tokens + operation.delta < 0:
            raise Exception("Logic error: trying to remove %d tokens to group '%s' while its number of tokens is only %d."%(operation.delta,self.name,self.nb_tokens))
        self.nb_tokens += operation.delta

COULLON Helene's avatar
COULLON Helene committed
98

COULLON Helene's avatar
COULLON Helene committed
99
class Component (object, metaclass=ABCMeta):
COULLON Helene's avatar
COULLON Helene committed
100 101 102 103 104 105 106 107 108
    """This Component class is used to create a component.

        A component is a software module to deploy. It is composed of places,
        transitions between places, dependencies and bindings between
        dependencies and Places/transitions.

        This is an abstract class that need to be override.
    """

COULLON Helene's avatar
COULLON Helene committed
109 110 111 112
    @abstractmethod
    def create(self):
        pass

COULLON Helene's avatar
COULLON Helene committed
113 114

    def __init__(self):
115 116
        self.name : str = ""
        self.color : str = ''
Maverick Chardet's avatar
Maverick Chardet committed
117 118
        self._verbosity : int = 0
        self.forced_verobisty : int = None
119 120
        self.print_time : bool = False
        self.dryrun : bool = False
Maverick Chardet's avatar
Maverick Chardet committed
121 122
        self.gantt : GanttChart = None
        self.hidden_from_gantt_chart = False
123 124
        
        self.places : List[str] = []
Maverick Chardet's avatar
Maverick Chardet committed
125
        self.switches : List[Tuple[str,Callable[[Place,str],List[int]]]] = []
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
        self.transitions : Dict[str,Tuple] = {}
        self.groups : Dict[str,List[str]] = {}
        self.dependencies : Dict[str,Tuple] = {}
        self.initial_place : str = None
        
        self.st_places : Dict[str, Place] = {}
        self.st_transitions : Dict[str,Transition] = {}
        self.st_dependencies : Dict[str, Dependency] = {}
        self.st_groups : Dict[str, Group] = {}
        self.st_behaviors : Set[str] = set()
        
        self.trans_dependencies : Dict[str,List[Dependency]] = {}
        self.place_dependencies : Dict[str,List[Dependency]] = {}
        self.group_dependencies : Dict[str,List[Dependency]] = {}
        self.place_groups : Dict[str,List[Group]] = {}
        
        self.act_places : Set[Place] = set()
        self.act_transitions : Set[Transition] = set()
        self.act_odocks : Set[Dock] = set()
        self.act_idocks : Set[Dock] = set()
146
        self.act_behavior : str = "_init"
Maverick Chardet's avatar
Juice  
Maverick Chardet committed
147
        self.queued_behaviors : Queue = Queue()
148
        self.visited_places : List[Place] = set()
149 150 151
        
        self.initialized : bool = False
        
COULLON Helene's avatar
COULLON Helene committed
152
        self.create()
153
        self.add_places(self.places)
154
        self.add_groups(self.groups)
155 156
        self.add_transitions(self.transitions)
        self.add_dependencies(self.dependencies)
157 158 159
    
    
    def set_verbosity(self, level : int):
Maverick Chardet's avatar
Maverick Chardet committed
160
        self._verbosity = level
161 162 163 164 165 166
        
    def set_print_time(self, value : bool):
        self.print_time = value
        
    def set_dryrun(self, value : bool):
        self.dryrun = value
Maverick Chardet's avatar
Maverick Chardet committed
167 168 169 170 171 172 173 174
        
    def set_gantt_chart(self, gc : GanttChart):
        if not self.hidden_from_gantt_chart:
            self.gantt = gc
    
    def force_hide_from_gantt_chart(self):
        self.hidden_from_gantt_chart = True
        self.gantt = None
Maverick Chardet's avatar
Maverick Chardet committed
175 176 177 178 179 180 181 182 183
    
    def force_vebosity(self, forced_verobisty : int):
        self.forced_verobisty = forced_verobisty
    
    def get_verbosity(self):
        if self.forced_verobisty is None:
            return self._verbosity
        else:
            return self.forced_verobisty
184

COULLON Helene's avatar
COULLON Helene committed
185

186
    def add_places(self, places : List[str], initial=None):
COULLON Helene's avatar
COULLON Helene committed
187 188 189 190 191 192 193
        """
        This method add all places declared in the user component class as a
        dictionary associating the name of a place to its number of input and
        output docks.

        :param places: dictionary of places
        """
194
        for key in places:
195
            self.add_place(key)
196 197
        if initial is not None:
            self.set_initial_place(initial)
COULLON Helene's avatar
COULLON Helene committed
198

199 200 201 202 203 204 205 206 207
    def add_place(self, name : str, initial=False):
        """
        This method offers the possibility to add a single place to an
        already existing dictionary of places.

        :param name: the name of the place to add
        :param initial: whether the place is the initial place of the component (default: False)
        """
        if name in self.st_places:
208
            raise Exception("Trying to add '%s' as a place while it is already a place"%name)
209
        elif name in self.st_transitions:
210
            raise Exception("Trying to add '%s' as a place while it is already a transition"%name)
211
        elif name in self.st_groups:
212
            raise Exception("Trying to add '%s' as a place while it is already a group"%name)
213 214 215 216 217 218
        self.st_places[name] = Place(name)
        self.place_dependencies[name] = []
        self.place_groups[name] = []
        
        if initial:
            self.set_initial_place(initial)
Maverick Chardet's avatar
Maverick Chardet committed
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247


    def add_switches(self, switches : List[Tuple[str,Callable[[Place,str],List[int]]]], initial=None):
        for key in switches:
            self.add_switch(key)
        if initial is not None:
            self.set_initial_place(initial)

    def add_switch(self, tuple : Tuple[str,Callable[[Place,str],List[int]]], initial=False):
        """
        This method offers the possibility to add a single place to an
        already existing dictionary of places.

        :param name: the name of the place to add
        :param initial: whether the place is the initial place of the component (default: False)
        """
        (name, override_f) = tuple
        if name in self.st_places:
            raise Exception("Trying to add '%s' as a place while it is already a place"%name)
        elif name in self.st_transitions:
            raise Exception("Trying to add '%s' as a place while it is already a transition"%name)
        elif name in self.st_groups:
            raise Exception("Trying to add '%s' as a place while it is already a group"%name)
        self.st_places[name] = Place(name, override_f, cp=self) # TODO: Remove cp
        self.place_dependencies[name] = []
        self.place_groups[name] = []
        
        if initial:
            self.set_initial_place(initial)
248 249 250 251 252 253 254 255
    
    
    def add_groups(self, groups : Dict[str,List[str]]):
        for name in groups:
            self.add_group(name, groups[name])
    
    def add_group(self, name : str, places : List[str]):
        if name in self.st_places:
256
            raise Exception("Trying to add '%s' as a group while it is already a place"%name)
257
        elif name in self.st_transitions:
258
            raise Exception("Trying to add '%s' as a group while it is already a transition"%name)
259
        elif name in self.st_groups:
260
            raise Exception("Trying to add '%s' as a group while it is already a group"%name)
Maverick Chardet's avatar
Maverick Chardet committed
261 262 263 264 265 266 267
        
        for place_name in places:
            if place_name not in self.st_places:
                raise Exception("Error: trying to add non-existing place '%s' to group '%s'"%(place_name, name))
            elif place_name is self.initial_place:
                raise Exception("Error: trying to add initial place '%s' to group '%s' (framework limitation: initial place cannot be in a group, for now...)"%(place_name, name))
        
268 269 270 271 272 273 274 275
        self.st_groups[name] = Group(name)
        self.group_dependencies[name] = []
        self.st_groups[name].add_places(places)
        for place_name in places:
            self.place_groups[place_name].append(self.st_groups[name])


    def add_transitions(self, transitions : Dict[str,Tuple]):
COULLON Helene's avatar
COULLON Helene committed
276 277 278 279
        """
        This method add all transitions declared in the user component class
        as a dictionary associating the name of a transition to a transition
        object created by the user too.
280
        Requires add_states and add_groups to have been executed
COULLON Helene's avatar
COULLON Helene committed
281 282 283

        :param transitions: dictionary of transitions
        """
284
        for key in transitions:
COULLON Helene's avatar
COULLON Helene committed
285
            # add docks to places and bind docks
Maverick Chardet's avatar
Maverick Chardet committed
286
            if len(transitions[key])==6:
287
                self.add_transition(key, transitions[key][0], transitions[key][
Maverick Chardet's avatar
Maverick Chardet committed
288
                    1], transitions[key][2], transitions[key][3], transitions[key][4], transitions[key][5])
289 290
            else:
                self.add_transition(key, transitions[key][0], transitions[key][
Maverick Chardet's avatar
Maverick Chardet committed
291
                    1], transitions[key][2], transitions[key][3], transitions[key][4])
292 293 294 295 296 297 298 299 300 301 302
        
    def _force_add_transition(self, name : str, src_name : str, dst_name : str, bhv : str, idset : int, func, args=()):
        src = None
        if src_name is not None:
            src = self.st_places[src_name]
        self.st_transitions[name] = Transition(name, src, self.st_places[dst_name], bhv, idset, func, args)
        self.trans_dependencies[name] = []
        self.st_behaviors.add(bhv)
        for group in self.st_groups:
            if self.st_groups[group].contains_place(src_name) and self.st_groups[group].contains_place(dst_name):
                self.st_groups[group].add_transition(name)
303

304
    def add_transition(self, name : str, src_name : str, dst_name : str, bhv : str, idset : int, func, args=()):
305 306 307 308 309 310 311 312 313 314 315 316
        """
        This method offers the possibility to add a single transition to an
        already existing dictionary of transitions.

        :param name: the name of the transition to add
        :param src: the name of the source place of the transition
        :param dst: the name of the destination place of the transition
        :param bhv: the name of the behavior associated to the transition
        :param func: a functor created by the user
        :param args: optional tuple of arguments to give to the functor
        """
        if name in self.st_places:
317 318 319 320 321 322 323 324 325 326
            raise Exception("Trying to add '%s' as a transition while it is already a place"%name)
        if name in self.st_transitions:
            raise Exception("Trying to add '%s' as a transition while it is already a transition"%name)
        if name in self.st_groups:
            raise Exception("Trying to add '%s' as a transition while it is already a group"%name)
        if name is "_init":
            raise Exception("Cannot name a transition '_init' (used internally)")
        if bhv is "_init":
            raise Exception("Cannot name a behavior '_init' (used internally)")
        if src_name not in self.st_places:
Maverick Chardet's avatar
Maverick Chardet committed
327
            raise Exception("Trying to add transition '%s' starting from unexisting place '%s'"%(name, src_name))
328
        if dst_name not in self.st_places:
Maverick Chardet's avatar
Maverick Chardet committed
329
            raise Exception("Trying to add transition '%s' going to unexisting place '%s'"%(name, dst_name))
330
        
331
        self._force_add_transition(name,src_name,dst_name,bhv,idset,func,args)
332 333 334
    

    def add_dependencies(self, dep : Dict[str,Tuple[DepType, List[str]]]):
COULLON Helene's avatar
COULLON Helene committed
335 336 337 338 339 340 341 342 343 344 345 346
        """
        This method add all dependencies declared in the user component class
        as a dictionary associating the name of a dependency to both a type
        and the name of the transition or the place to which it is bound.

        - a 'use' or 'data-use' dependency can be bound to a transition

        - a 'provide' or 'data-provide' dependency can be bound to a place

        :param dep: dictionary of dependencies

        """
347
        for key in dep:
COULLON Helene's avatar
COULLON Helene committed
348
            if len(dep[key])==2:
349 350
                type = dep[key][0]
                bname = dep[key][1] # list of places or transitions bounded to
351
                self.add_dependency(key, type, bname)
COULLON Helene's avatar
COULLON Helene committed
352 353

            else:
354 355 356
                raise Exception("ERROR dependency %s - two arguments should be given for construction, "
                        "a type enum DepType and the name of the place, the transition "
                        "or the group to which the dependency is bound."%key)
COULLON Helene's avatar
COULLON Helene committed
357

358
    def add_dependency(self, name : str, type : DepType, bindings : List[str]):
COULLON Helene's avatar
COULLON Helene committed
359
        """
360 361
        This method offers the possibility to add a single dependency to an
        already existing dictionary of dependencies.
COULLON Helene's avatar
COULLON Helene committed
362

363 364 365
        :param name: the name of the dependency to add
        :param type: the type DepType of the dependency
        :param binding: the name of the binding of the dependency (place or transition)
COULLON Helene's avatar
COULLON Helene committed
366
        """
367 368 369
        if type == DepType.DATA_USE:
            for bind in bindings:
                if bind not in self.st_transitions:
370
                    raise Exception("Trying to bind dependency %s (of type %s) to something else than a transition"%(name,str(type)))
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
            
            self.st_dependencies[name] = Dependency(self, name, type)
            for transition_name in bindings:
                self.trans_dependencies[transition_name].append(self.st_dependencies[name])
                
                
        elif type == DepType.USE:
            places = []
            transitions = []
            groups = []
            for bind in bindings:
                if bind in self.st_transitions:
                    transitions.append(bind)
                elif bind in self.st_places:
                    places.append(bind)
                elif bind in self.st_groups:
                    groups.append(bind)
                else:
389
                    raise Exception("Trying to bind dependency %s (of type %s) to something else than a place, a transition or a group"%(name,str(type)))
390 391 392 393 394 395 396 397 398 399 400 401
                
            self.st_dependencies[name] = Dependency(self, name, type)
            for place_name in places:
                self.place_dependencies[place_name].append(self.st_dependencies[name])
            for transition_name in transitions:
                self.trans_dependencies[transition_name].append(self.st_dependencies[name])
            for group_name in groups:
                self.group_dependencies[group_name].append(self.st_dependencies[name])

        elif type == DepType.DATA_PROVIDE:
            for bind in bindings:
                if bind not in self.st_places:
402
                    raise Exception("Trying to bind dependency %s (of type %s) to something else than a place"%(name,str(type)))
403 404 405 406 407 408 409 410 411 412 413 414 415 416
            
            self.st_dependencies[name] = Dependency(self, name, type)
            for place_name in bindings:
                self.place_dependencies[place_name].append(self.st_dependencies[name])

        elif type == DepType.PROVIDE:
            places = []
            groups = []
            for bind in bindings:
                if bind in self.st_places:
                    places.append(bind)
                elif bind in self.st_groups:
                    groups.append(bind)
                else:
417
                    raise Exception("Trying to bind dependency %s (of type %s) to something else than a place or a group"%(name,str(type)))
418 419 420 421 422 423
                
            self.st_dependencies[name] = Dependency(self, name, type)
            for place_name in places:
                self.place_dependencies[place_name].append(self.st_dependencies[name])
            for group_name in groups:
                self.group_dependencies[group_name].append(self.st_dependencies[name])
424 425


426
    def set_initial_place(self, name : str):
427 428 429 430 431 432 433 434 435 436 437 438
        """
        This method allows to set the (unique) initial place of the component, if not already done
        using the parameter of add_place and add_places.
        
        :param name: the name of the place to mark initial
        """
        
        if name not in self.st_places:
            raise Exception("Trying to set non-existant place %s as intial place of component %s." % (name, self.get_name()))
        if self.initial_place is not None:
            raise Exception("Trying to set place %s as intial place of component %s while %s is already the intial place." % (name, self.get_name(), self.initial_place))
        self.initial_place = name
439
        
COULLON Helene's avatar
COULLON Helene committed
440 441 442 443 444 445 446 447 448

    def get_places(self):
        """
        This method returns the dictionary of places of the component

        :return: self.st_places the dictionary of places
        """
        return self.st_places

449
    def get_dependency(self, name : str) -> Dependency:
COULLON Helene's avatar
COULLON Helene committed
450 451 452 453 454 455 456 457
        """
        This method returns the dependencies object associated to a given
        name

        :param name: the name (string) of the dependency to get
        :return: the dependency object associated to the name
        """
        return self.st_dependencies[name]
458 459 460
    
    def get_dependency_type(self, name : str) -> DepType:
        return self.get_dependency(name).get_type()
COULLON Helene's avatar
COULLON Helene committed
461

462
    def set_name(self, name : str):
COULLON Helene's avatar
COULLON Helene committed
463 464 465 466 467 468 469
        """
        This method sets the name of the current component

        :param name: the name (string) of the component
        """
        self.name = name

470
    def get_name(self):
COULLON Helene's avatar
COULLON Helene committed
471 472 473 474 475 476 477
        """
        This method returns the name of the component

        :return: the name (string) of the component
        """
        return self.name

478
    def set_color(self, c):
COULLON Helene's avatar
COULLON Helene committed
479 480 481 482 483 484 485
        """
        This method set a printing color to the current component

        :param c: the color to set
        """
        self.color = c

486
    def get_color(self):
COULLON Helene's avatar
COULLON Helene committed
487 488 489 490 491 492
        """
        This method returns the color associated to the current component

        :return: the printing color of the component
        """
        return self.color
493 494
    
    def print_color(self, string : str):
Maverick Chardet's avatar
Maverick Chardet committed
495
        if self.get_verbosity() < 0:
496 497 498 499 500 501
            return
        message : str = "%s[%s] %s%s"%(self.get_color(), self.get_name(), string, Messages.endc())
        if self.print_time:
            Printer.st_tprint(message)
        else:
            print(message)
COULLON Helene's avatar
COULLON Helene committed
502

503
    def is_connected(self, name : str):
COULLON Helene's avatar
COULLON Helene committed
504 505 506 507 508
        """
        This method is used to know if a given dependency is connected or not
        :param name: name of the dependency
        :return: True if connected, False otherwise
        """
509
        return not self.st_dependencies[name].is_free()
COULLON Helene's avatar
COULLON Helene committed
510

COULLON Helene's avatar
COULLON Helene committed
511 512 513 514
    """
    READ / WRITE DEPENDENCIES
    """

515 516
    def read(self, name : str):
        return self.st_dependencies[name].read()
COULLON Helene's avatar
COULLON Helene committed
517

518
    def write(self, name : str, val):
COULLON Helene's avatar
COULLON Helene committed
519 520 521 522 523 524 525
        # keep trace of the line below to check wether the calling method has
        #  the right to acess thes dependency
        # this is not portable according to Python implementations
        # moreover, the write is associated to a transition while the data
        # provide is associated to a place in the model. This has to be
        # corrected somewhere.
        # print(sys._getframe().f_back.f_code.co_name)
526
        self.st_dependencies[name].write(val)
COULLON Helene's avatar
COULLON Helene committed
527

COULLON Helene's avatar
COULLON Helene committed
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553
    """
    CHECK COMPONENT
    """

    def check_warnings(self):
        """
        This method check WARNINGS in the structure of the component.

        :return: False if some WARNINGS have been detected, True otherwise.
        """
        check = True

        return check

    def check_connections(self):
        """
        This method check connections once the component has been
        instanciated and connected in an assembly. This method is called by
        the engine -> assembly

        :return: True if all dependencies of a component are connected, False otherwise
        """

        result = True

        for dep in self.st_dependencies:
554
            if self.st_dependencies[dep].is_free():
COULLON Helene's avatar
COULLON Helene committed
555 556 557
                result = False

        return result
558 559 560 561 562 563 564 565
    
    
    """
    RECONFIGURATION
    """
    
    # reconfiguration of the component by changing its current behavior
    
566
    def set_behavior(self, behavior : str):
567 568 569 570
        if behavior not in self.st_behaviors and behavior is not None:
            raise Exception("Trying to set behavior %s in component %s while this behavior does not exist in this component." % (behavior, self.get_name()))
        # TODO warn if no transition with the behavior is fireable from the current state
        self.act_behavior = behavior
Maverick Chardet's avatar
Maverick Chardet committed
571
        if self.gantt is not None:
Maverick Chardet's avatar
Maverick Chardet committed
572
            self.gantt.push_b(self.get_name(), behavior, time.perf_counter())
573
        self.visited_places = set()
Maverick Chardet's avatar
Maverick Chardet committed
574
        if self.get_verbosity() >= 1:
575
            self.print_color("Changing behavior to '%s'"%behavior)
576 577 578 579 580 581
        
    def get_behaviors(self):
        return self.st_behaviors
        
    def get_active_behavior(self):
        return self.act_behavior
Maverick Chardet's avatar
Juice  
Maverick Chardet committed
582 583 584 585 586 587 588 589
    
    def queue_behavior(self, behavior : str):
        if self.get_active_behavior() is None:
            self.set_behavior(behavior)
        else:
            if behavior not in self.st_behaviors and behavior is not None:
                raise Exception("Trying to queue behavior %s in component %s while this behavior does not exist in this component." % (behavior, self.get_name()))
            self.queued_behaviors.put(behavior)
Maverick Chardet's avatar
Maverick Chardet committed
590
            if self.get_verbosity() >= 1:
Maverick Chardet's avatar
Juice  
Maverick Chardet committed
591 592
                self.print_color("Queing behavior '%s'"%behavior)
            
593 594
            
            
COULLON Helene's avatar
COULLON Helene committed
595 596 597 598 599

    """
    OPERATIONAL SEMANTICS
    """

600 601 602 603 604 605 606
    # these four lists represents the configuration at the component level
    # they are used within the semantics parts, ie the runtime
    # act_places the set of active places of the component
    # act_transitions the set of active transitions of the component
    # act_idocks the set of active input docks of the component
    # act_odocks the set of active output docks of the component

COULLON Helene's avatar
COULLON Helene committed
607 608 609
    # trans_connections a dictionary associating one transition to its
    # associated use connections

COULLON Helene's avatar
COULLON Helene committed
610 611 612 613 614
    # old_places the set of previous iteration active places of the component
    # old_transitions the set of previous iteration active transitions of the component
    # old_idocks the set of previous iteration active input docks of the component
    # old_odocks the set of previous iteration active output docks of the component
    # old_my_connections
615 616 617 618 619 620
    
    def is_idle(self):
        """
        This method returns a boolean stating if the component is idle.
        :return: a boolean stating if the component is idle
        """
621
        return self.get_active_behavior() is None
622
    
623 624
    
    def init(self):
Maverick Chardet's avatar
Maverick Chardet committed
625
        from concerto.utility import empty_transition
626 627 628
        """
        This method initializes the component and returns the set of active places
        """
629 630 631
        if self.initialized:
            raise Exception("Trying to initialize component '%s' a second time"%self.get_name())
        
632 633
        self._force_add_transition("_init",None,self.initial_place,"_init",0,empty_transition)
        self.act_transitions.add(self.st_transitions["_init"])
634
        
635
        self.initialized = True
Maverick Chardet's avatar
DEBUG  
Maverick Chardet committed
636
    
637
    def semantics(self) -> bool:
COULLON Helene's avatar
COULLON Helene committed
638 639
        """
        This method apply the operational semantics at the component level.
640
        Returns whether the component is IDLE.
COULLON Helene's avatar
COULLON Helene committed
641
        """
642 643
        
        if not self.initialized:
644
            self.init()
Maverick Chardet's avatar
Maverick Chardet committed
645
        
646
        #done = False
647
        
648 649 650 651 652 653 654 655 656 657 658 659
        #if self.act_places:
            #done = self._place_to_odocks()
        #if (not done) and self.act_odocks:
            #done = self._start_transition()
        #if (not done) and self.act_transitions:
            #done = self._end_transition()
        #if (not done) and self.act_idocks:
            #done = self._idocks_to_place()
        
        #TODO: Discuss if best alternative: doing the 4 if possible (starting by idocks to place so that if a provide is not stable it doesn't get activated)
        if self.act_idocks:
            self._idocks_to_place()
660
        if self.act_places:
661 662 663 664 665
            self._place_to_odocks()
        if self.act_odocks:
            self._start_transition()
        if self.act_transitions:
            self._end_transition()
666
        
667 668 669
        # Checks if the component is IDLE
        idle = not self.act_transitions and not self.act_odocks and not self.act_idocks
        if idle:
670
            for place in self.act_places:
671
                if place not in self.visited_places and len(place.get_output_docks(self.act_behavior)) > 0:
672
                    idle = False
673
                    break
674
        
Maverick Chardet's avatar
Juice  
Maverick Chardet committed
675 676 677 678 679
        if idle:
            if not self.queued_behaviors.empty():
                idle = False
                self.set_behavior(self.queued_behaviors.get())
        
680 681
        if idle:
            self.set_behavior(None)
Maverick Chardet's avatar
Maverick Chardet committed
682
            if self.get_verbosity() >= 1:
683 684 685
                self.print_color("Going IDLE")
        
        return idle
COULLON Helene's avatar
COULLON Helene committed
686 687


688
    def _place_to_odocks(self) -> bool:
COULLON Helene's avatar
COULLON Helene committed
689
        """
690 691
        This method represents the one moving the token of a place to its
        output docks.
COULLON Helene's avatar
COULLON Helene committed
692
        """
693 694
        did_something = False
        places_to_remove : Set[Place] = set()
COULLON Helene's avatar
COULLON Helene committed
695

696
        for place in self.act_places:
697 698
            if place in self.visited_places:
                continue
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728
            odocks = place.get_output_docks(self.act_behavior)
            if len(odocks) is 0:
                continue
            
            can_leave : bool = True
            # Checking place dependencies
            for dep in self.place_dependencies[place.get_name()]:
                if dep.get_type() is DepType.PROVIDE:
                    if dep.is_locked():
                        can_leave = False
                        break
            if not can_leave:
                continue
            
            # Checking group dependencies if in a group
            deactivating_groups_operation : Dict[Group,Group.Operation] = {}
            for group in self.place_groups[place.get_name()]:
                if not can_leave:
                    break
                group_operation = group.leave_place_operation(odocks)
                if group.is_deactivating(group_operation):
                    for dep in self.group_dependencies[group.get_name()]:
                        if (dep.get_type() is DepType.PROVIDE) and dep.is_locked():
                            can_leave = False
                            break
                    deactivating_groups_operation[group] = group_operation
            if not can_leave:
                continue
            
            did_something = True
Maverick Chardet's avatar
Maverick Chardet committed
729
            if self.get_verbosity() >= 1:
730 731 732 733
                self.print_color("Leaving place '%s'"%(place.get_name()))
            for dep in self.place_dependencies[place.get_name()]:
                if dep.get_type() is not DepType.DATA_PROVIDE:
                    dep.stop_using()
Maverick Chardet's avatar
Maverick Chardet committed
734
                    if self.get_verbosity() >= 2:
735 736 737 738 739
                        self.print_color("Stopping to use place dependency '%s'"%dep.get_name())
            for group in deactivating_groups_operation:
                group.apply(deactivating_groups_operation[group])
                for dep in self.group_dependencies[group.get_name()]:
                    dep.stop_using()
Maverick Chardet's avatar
Maverick Chardet committed
740
                    if self.get_verbosity() >= 2:
741
                        self.print_color("Stopping to use group dependency '%s'"%dep.get_name())
Maverick Chardet's avatar
Maverick Chardet committed
742
                if self.get_verbosity() >= 2:
743 744 745
                    self.print_color("Deactivating group '%s'"%( group.get_name()))
            self.act_odocks.update(odocks)
            places_to_remove.add(place)
746
            self.visited_places.add(place)
747 748 749 750
        
        self.act_places.difference_update(places_to_remove)
        
        return did_something
COULLON Helene's avatar
COULLON Helene committed
751 752


753
    def _start_transition(self) -> bool:
COULLON Helene's avatar
COULLON Helene committed
754
        """
755
        This method starts the transitions which are ready to run:
COULLON Helene's avatar
COULLON Helene committed
756
        """
757 758
        did_something = False
        docks_to_remove : Set[Dock] = set()
COULLON Helene's avatar
COULLON Helene committed
759

760 761
        for od in self.act_odocks:
            trans = od.get_transition()
COULLON Helene's avatar
COULLON Helene committed
762

763 764 765 766 767 768 769
            enabled = True
            
            for dep in self.trans_dependencies[trans.get_name()]:
                # Necessarily USE or DATA_USE
                if not dep.is_served():
                    enabled = False
                    break
COULLON Helene's avatar
COULLON Helene committed
770

771 772 773 774
            if not enabled:
                continue
            
            did_something = True
Maverick Chardet's avatar
Maverick Chardet committed
775
            if self.get_verbosity() >= 1:
776 777 778
                self.print_color("Starting transition '%s'"%(trans.get_name()))
            for dep in self.trans_dependencies[trans.get_name()]:
                dep.start_using()
Maverick Chardet's avatar
Maverick Chardet committed
779
                if self.get_verbosity() >= 2:
780
                    self.print_color("Starting to use transition dependency '%s'"%dep.get_name())
Maverick Chardet's avatar
Maverick Chardet committed
781 782 783 784 785
            if self.gantt is None:
                gantt_tuple = None
            else:
                gantt_tuple = (self.gantt,(self.name, self.act_behavior, trans.get_name(), time.perf_counter()))
            trans.start_thread(gantt_tuple, self.dryrun)
786 787
            self.act_transitions.add(trans)
            docks_to_remove.add(od)
COULLON Helene's avatar
COULLON Helene committed
788

789 790
        self.act_odocks.difference_update(docks_to_remove)
        return did_something
COULLON Helene's avatar
COULLON Helene committed
791 792


793
    def _end_transition(self) -> bool:
COULLON Helene's avatar
COULLON Helene committed
794
        """
795
        This method try to join threads from currently running transitions.
COULLON Helene's avatar
COULLON Helene committed
796
        """
797 798
        did_something = False
        transitions_to_remove : Set[Transition] = set()
COULLON Helene's avatar
COULLON Helene committed
799

800 801
        # check if some of these running transitions are finished
        for trans in self.act_transitions:
802 803 804 805 806 807 808 809 810
            if trans.get_name() is not "_init":
                if self.gantt is None:
                    gantt_tuple = None
                else:
                    gantt_tuple = (self.gantt,(self.name, self.act_behavior, trans.get_name(), time.perf_counter()))
                joined = trans.join_thread(gantt_tuple, self.dryrun)
                # get the new set of activated input docks
                if not joined:
                    continue
811 812 813
            
            for dep in self.trans_dependencies[trans.get_name()]:
                dep.stop_using()
Maverick Chardet's avatar
Maverick Chardet committed
814
                if self.get_verbosity() >= 2:
815
                    self.print_color("Stopping to use transition dependency '%s'"%dep.get_name())
Maverick Chardet's avatar
Maverick Chardet committed
816
            if self.get_verbosity() >= 1:
817 818 819
                self.print_color("Ending transition '%s'"%(trans.get_name()))
            self.act_idocks.add(trans.get_dst_dock())
            transitions_to_remove.add(trans)
COULLON Helene's avatar
COULLON Helene committed
820

821 822
        self.act_transitions.difference_update(transitions_to_remove)
        return did_something
COULLON Helene's avatar
COULLON Helene committed
823 824


825
    def _idocks_to_place(self):
COULLON Helene's avatar
COULLON Helene committed
826
        """
827 828 829 830
        Input docks to place.
        """
        did_something = False
        docks_to_remove : Set[Dock] = set()
COULLON Helene's avatar
COULLON Helene committed
831

832 833 834
        # if not all input docks are enabled for a place, the place will not
        # be activated.
        still_idocks = []
COULLON Helene's avatar
COULLON Helene committed
835

836 837 838 839 840 841 842 843 844 845 846 847 848 849
        for dock in self.act_idocks:
            place : Place = dock.get_place()
            if place in self.act_places:
                continue
            
            grp_inp_docks = place.get_groups_of_input_docks(self.act_behavior)
            for inp_docks in grp_inp_docks:
                if len(inp_docks) is 0:
                    continue
                
                ready = True
                for dock2 in inp_docks:
                    if dock2 not in self.act_idocks:
                        ready = False
COULLON Helene's avatar
COULLON Helene committed
850
                        break
851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879
                if not ready:
                    continue
                
                # Checking place dependencies
                for dep in self.place_dependencies[place.get_name()]:
                    if dep.get_type() is DepType.USE or dep.get_type() is DepType.DATA_USE:
                        if not dep.is_served():
                            ready = False
                            break
                if not ready:
                    continue
                
                # Checking group dependencies
                activating_groups_operation : Dict[Group,Group.Operation] = {}
                for group in self.place_groups[place.get_name()]:
                    if not ready:
                        break
                    group_operation = group.enter_place_operation(inp_docks)
                    if group.is_activating(group_operation):
                        for dep in self.group_dependencies[group.get_name()]:
                            if dep.get_type() is DepType.USE and (not dep.is_served()):
                                ready = False
                                break
                        activating_groups_operation[group] = group_operation
                if not ready:
                    continue
                
                did_something = True
                for group in activating_groups_operation:
Maverick Chardet's avatar
Maverick Chardet committed
880
                    if self.get_verbosity() >= 2:
881 882 883 884
                        self.print_color("Activating group '%s'"%( group.get_name()))
                    group.apply(activating_groups_operation[group])
                    for dep in self.group_dependencies[group.get_name()]:
                        dep.start_using()
Maverick Chardet's avatar
Maverick Chardet committed
885
                        if self.get_verbosity() >= 2:
886
                            self.print_color("Starting to use group dependency '%s'"%dep.get_name())
Maverick Chardet's avatar
Maverick Chardet committed
887
                if self.get_verbosity() >= 1:
888 889 890
                    self.print_color("Entering place '%s'"%( place.get_name()))
                for dep in self.place_dependencies[place.get_name()]:
                    dep.start_using()
Maverick Chardet's avatar
Maverick Chardet committed
891
                    if self.get_verbosity() >= 2:
892 893 894 895 896 897
                        self.print_color("Starting to use place dependency '%s'"%dep.get_name())
                self.act_places.add(place)
                docks_to_remove.update(inp_docks)
        
        self.act_idocks.difference_update(docks_to_remove)
        return did_something
Maverick Chardet's avatar
Maverick Chardet committed
898
    
899 900 901
    def get_active_places(self):
        return list([p.get_name() for p in self.act_places])
    
Maverick Chardet's avatar
Maverick Chardet committed
902 903
    def get_debug_info(self) -> str:
        debug_string = "== Component '%s' status ==\n" % self.get_name()
904
        # TODO: remove access to internal variable queue of queued_behaviors, not in API
Maverick Chardet's avatar
DEBUG  
Maverick Chardet committed
905
        debug_string += ("  active behaviors: %s + %s\n" % (self.act_behavior, self.queued_behaviors.queue))
Maverick Chardet's avatar
Maverick Chardet committed
906 907 908 909 910
        debug_string += ("  active places: %s\n" % ','.join([p.get_name() for p in self.act_places]))
        debug_string += ("  active transitions: %s\n" % ','.join([t.get_name() for t in self.act_transitions]))
        debug_string += ("  active odocks (transition): %s\n" % ','.join([d.get_transition().get_name() for d in self.act_odocks]))
        debug_string += ("  active idocks (transition): %s\n" % ','.join([d.get_transition().get_name() for d in self.act_idocks]))
        return debug_string