1"""tasking.py weightless thread scheduling
2
3
4"""
5#print("module {0}".format(__name__))
6
7from ..aid.sixing import *
8from .globaling import *
9from ..aid.odicting import odict
10from . import excepting
11from . import registering
12from . import storing
13
14from ..aid.consoling import getConsole
15console = getConsole()
16
17
18def CreateInstances(store):
19    """Create server instances which automatically get registered on object creation
20       must be function so can recreate after clear registry
21    """
22    tasker = Tasker(name = 'tasker', store = store)
23
24#Class definitions
25
26
27class Tasker(registering.StoriedRegistrar):
28    """Task class, Base class for weightless threads
29
30    """
31    Counter = 0
32    Names = {}
33
34    def __init__(self, period = 0.0, schedule=INACTIVE, **kw):
35        """
36        Initialize instance.
37
38        Inherited instance attributes
39            .name = unique name for tasker
40            .store = data store
41
42        Instance attributes
43            .period = desired time in seconds between runs,non negative, zero means asap
44            .stamp = depends on subclass default is time tasker last RUN
45            .status = operational status of tasker
46            .desire = desired control asked by this or other taskers
47            .done = tasker completion state True or False
48            .schedule = initial scheduling context for this tasker vis a vis skedder
49            .runner = generator to run tasker
50
51         The registry class will supply unique name when name is empty by using
52         the .__class__.__name__ as the default preface to the name.
53         To use a different default preface add this to the .__init__ method
54         before the super call
55
56         if 'preface' not in kw:
57             kw['preface'] = 'MyDefaultPreface'
58
59        """
60        super(Tasker,self).__init__(**kw)
61
62        self.period = float(abs(period)) #desired time between runs, 0.0 means asap
63        self.stamp = 0.0 #time last run
64        self.presolved = False  # tasker has been presolved
65        self.resolved = False  # tasker has been resolved
66        self.status = STOPPED #operational status of tasker
67        self.desire = STOP #desired control next time Task is iterated
68        self.done = True # tasker completion state reset on restart
69        self.schedule = schedule #initial scheduling context vis a vis skedder
70        self.runner = None #reference to runner generator
71        self.remake() #make generator assign to .runner and advance to yield
72
73    def reinit(self, period=None, schedule=None, **kw):
74        if period is not None:
75            self.period = period
76
77        if schedule is not None:
78            self.schedule = schedule
79
80    def remake(self):
81        """Re make runner generator
82
83           .send(None) same as .next()
84        """
85        self.runner = self.makeRunner() #make generator
86        status = self.runner.send(None) #advance to first yield to allow send(cmd) on next iteration
87
88        if console._verbosity >= console.Wordage.profuse:
89            self.expose()
90
91    def expose(self):
92        """
93
94        """
95        print("     Task %s status = %s" % (self.name, StatusNames[self.status]))
96
97    def presolve(self, **kwa):
98        """Presolves any links to aux clones"""
99        self.presolved = True
100
101    def resolve(self, **kwa):
102        """Resolves any by name links to other objects   """
103        self.resolved = True
104
105    def ready(self):
106        """ready runner
107
108        """
109        return self.runner.send(READY)
110
111    def start(self):
112        """start runner
113
114        """
115        return self.runner.send(START)
116
117    def run(self):
118        """run runner
119
120        """
121        return self.runner.send(RUN)
122
123    def stop(self):
124        """stop runner
125
126        """
127        return self.runner.send(STOP)
128
129    def abort(self):
130        """abort runner
131
132        """
133        return self.runner.send(ABORT)
134
135    def makeRunner(self):
136        """generator factory function to create generator to run this tasker
137
138           Should be overridden in sub class
139        """
140        #do any on creation initialization here
141        console.profuse("     Making Task Runner {0}\n".format(self.name))
142
143        self.status = STOPPED #operational status of tasker
144        self.desire = STOP #default what to do next time, override below
145        self.done = True
146
147        count = 0
148
149        try:
150            while (True):
151                control = (yield (self.status)) #accept control and yield status
152                console.profuse("\n     Iterate Tasker {0} with control = {1} status = {2}\n".format(
153                    self.name,
154                    ControlNames.get(control, 'Unknown'),
155                    StatusNames.get(self.status, 'Unknown')))
156
157                if control == RUN:
158                    if self.status == STARTED or self.status == RUNNING:
159                        console.profuse("     Running Tasker {0} ...\n".format(self.name))
160                        self.status = RUNNING
161                    else:
162                        console.profuse("     Need to Start Tasker {0}\n".format(self.name))
163                        self.desire = START
164
165                elif control == READY:
166                    console.profuse("     Readying Tasker {0} ...\n".format(self.name))
167                    self.desire = START
168                    self.status = READIED
169
170                elif control == START:
171                    console.terse("     Starting Tasker {0} ...\n".format(self.name))
172                    self.desire = RUN
173                    self.status = STARTED
174                    self.done = False
175
176                elif control == STOP:
177                    if self.status == RUNNING or self.status == STARTED:
178                        console.terse("     Stopping Tasker {0} ...\n".format(self.name))
179                        self.desire = STOP
180                        self.status = STOPPED
181                        self.done = True
182                    else:
183                        console.terse("     Tasker {0} not started or running.\n".format(self.name))
184
185                elif control == ABORT:
186                    console.profuse("     Aborting Tasker {0} ...\n".format(self.name))
187                    self.desire = ABORT
188                    self.status = ABORTED
189                    self.done = True #only done if complete successfully
190
191                else: #control == unknown error condition bad control
192                    self.desire = ABORT
193                    self.status = ABORTED
194                    console.profuse("     Aborting Tasker {0}, bad control = {1}\n".format(
195                        self.name,  CommandNames[control]))
196                    break #break out of while loop. this will cause stopIteration
197
198                self.stamp = self.store.stamp
199
200        finally: #in case uncaught exception
201            console.profuse("     Exception causing Abort Tasker {0} ...\n".format(self.name))
202            self.desire = ABORT
203            self.status = ABORTED
204
205
206def resolveTasker(tasker, who='', desc='tasker', contexts=None, human='', count=None):
207    """ Returns resolved tasker instance from tasker
208        tasker may be name of tasker or instance
209        who is optional name of object owning the link
210        such as framer or frame or actor
211        desc is string description of tasker link such as 'aux' or 'framer'
212        contexts is list of allowed schedule contexts, None or empty means any.
213        Taskers.Names registry must already be setup
214    """
215    if not isinstance(tasker, Tasker): # not instance so name
216        if tasker not in Tasker.Names:
217            raise excepting.ResolveError("ResolveError: Bad {0} link name".format(desc),
218                                         tasker,
219                                         who,
220                                         human,
221                                         count)
222        tasker = Tasker.Names[tasker]
223        if contexts and tasker.schedule not in contexts:
224            raise excepting.ResolveError("ResolveError: Bad {0} link not scheduled"
225                                         " as one of {1}".format(desc, contexts),
226                                         tasker.name,
227                                         who,
228                                         human,
229                                         count)
230        console.concise("         Resolved {0} Tasker '{1}' in '{2}'\n"
231                      "".format(desc, tasker.name, who))
232    return tasker
233
234ResolveTasker = resolveTasker
235