1
2
3 """SimulationStep 2.0 Supports stepping through SimPy simulation event - by - event.
4 Based on generators (Python 2.3 and later; not 3.0)
5
6 LICENSE:
7 Copyright (C) 2002, 2005, 2006, 2007, 2008 Klaus G. Muller, Tony Vignaux
8 mailto: kgmuller@xs4all.nl and Tony.Vignaux@vuw.ac.nz
9
10 This library is free software; you can redistribute it and / or
11 modify it under the terms of the GNU Lesser General Public
12 License as published by the Free Software Foundation; either
13 version 2.1 of the License, or (at your option) any later version.
14
15 This library is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 Lesser General Public License for more details.
19
20 You should have received a copy of the GNU Lesser General Public
21 License along with this library; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 - 1307 USA
23 END OF LICENSE
24
25 **Change history:**
26
27 Started out as SiPy 0.9
28
29 5 / 9/2002: SiPy 0.9.1
30
31 - Addition of '_cancel' method in class Process and supporting '_unpost' method in
32 class __Evlist.
33
34 - Removal of redundant 'Action' method in class Process.
35
36 12 / 9/2002:
37
38 - Addition of resource class
39
40 - Addition of '_request' and '_release' coroutine calls
41
42 15 / 9/2002: moved into SimPy package
43
44 16 / 9/2002:
45 - Resource attributes fully implemented (resources can now have more
46 than 1 shareable resource units)
47
48 17 / 9/2002:
49
50 - corrected removal from waitQ (Vignaux)
51
52 17 / 9/2002:
53
54 - added test for queue discipline in 'test_demo()'. Must be FIFO
55
56 26 / 9/02: Version 0.2.0
57
58 - cleaned up code; more consistent naming
59
60 - prefixed all Simulation - private variable names with '_'.
61
62 - prefixed all class - private variable names with '__'.
63
64 - made normal exit quiet (but return message from scheduler()
65
66 28 / 9/02:
67
68 - included stopSimulation()
69
70 15 / 10 / 02: Simulation version 0.3
71
72 - Version printout now only if __TESTING
73
74 - '_stop' initialized to True by module load, and set to False in
75 initialize()
76
77 - Introduced 'simulate(until = 0)' instead of 'scheduler(till = 0)'.
78 Left 'scheduler()' in for backward compatibility, but marked
79 as deprecated.
80
81 - Added attribute 'name' to class Process; default == 'a_process'
82
83 - Changed Resource constructor to
84 'def __init__(self, capacity = 1, name = 'a_resource', unitName = 'units''.
85
86 13 / 11 / 02: Simulation version 0.6
87
88 - Major changes to class Resource:
89
90 - Added two queue types for resources, FIFO (default) and PriorityQ
91
92 - Changed constructor to allow selection of queue type.
93
94 - Introduced preemption of resources (to be used with PriorityQ
95 queue type)
96
97 - Changed constructor of class Resource to allow selection of preemption
98
99 - Changes to class Process to support preemption of service
100
101 - Cleaned up 'simulate' by replacing series of if - statements by dispatch table.
102
103 19 / 11 / 02: Simulation version 0.6.1
104 - Changed priority schemes so that higher values of Process
105 attribute 'priority' represent higher priority.
106
107 20 / 11 / 02: Simulation version 0.7
108 - Major change of priority approach:
109
110 - Priority set by 'yield request, self, res, priority'
111
112 - Priority of a Process instance associated with a specific
113 resource
114
115 25 / 11 / 02: Simulation version 0.7.1
116
117 - Code cleanup and optimization
118
119 - Made process attributes remainService and preempted private
120 (_remainService and _preempted)
121
122 11 / 12 / 2002: First process interrupt implementation
123
124 - Addition of user methods 'interrupt' and 'resume'
125
126 - Significant code cleanup to maintain process state
127
128 20 / 12 / 2002: Changes to 'interrupt'; addition of boolean methods to show
129 process states
130
131 16 / 3/2003: Changed hold (allowing posting events past _endtime)
132
133 18 / 3/2003: Changed _nextev to prevent _t going past _endtime
134
135 23 / 3/2003: Introduced new interrupt construct; deleted 'resume' method
136
137 25 / 3/2003: Expanded interrupt construct:
138
139 - Made 'interrupt' a method of Process
140
141 - Added 'interruptCause' as an attribute of an interrupted process
142
143 - Changed definition of 'active' to
144 'self._nextTime <> None and not self._inInterrupt'
145
146 - Cleaned up test_interrupt function
147
148 30 / 3/2003: Modification of 'simulate':
149
150 - error message if 'initialize' not called (fatal)
151
152 - error message if no process scheduled (warning)
153
154 - Ensured that upon exit from 'simulate', now() == _endtime is
155 always valid
156
157 2 / 04 / 2003:
158
159 - Modification of 'simulate': leave _endtime alone (undid change
160 of 30 Mar 03)
161
162 - faster '_unpost'
163
164 3 / 04 / 2003: Made 'priority' private ('_priority')
165
166 4 / 04 / 2003: Catch activation of non - generator error
167
168 5 / 04 / 2003: Added 'interruptReset()' function to Process.
169
170 7 / 04 / 2003: Changed '_unpost' to ensure that process has
171 _nextTime == None (is passive) afterwards.
172
173 8 / 04 / 2003: Changed _hold to allow for 'yield hold, self'
174 (equiv to 'yield hold, self, 0')
175
176 10 / 04 / 2003: Changed 'cancel' syntax to 'Process().cancel(victim)'
177
178 12 / 5/2003: Changed eventlist handling from dictionary to bisect
179
180 9 / 6/2003: - Changed eventlist handling from pure dictionary to bisect-
181 sorted 'timestamps' list of keys, resulting in greatly
182 improved performance for models with large
183 numbers of event notices with differing event times.
184 =========================================================
185 This great change was suggested by Prof. Simon Frost.
186 Thank you, Simon! This version 1.3 is dedicated to you!
187 =========================================================
188 - Added import of Lister which supports well - structured
189 printing of all attributes of Process and Resource instances.
190
191 Oct 2003: Added monitored Resource instances (Monitors for activeQ and waitQ)
192
193 13 Dec 2003: Merged in Monitor and Histogram
194
195 27 Feb 2004: Repaired bug in activeQ monitor of class Resource. Now actMon
196 correctly records departures from activeQ.
197
198 19 May 2004: Added erroneously omitted Histogram class.
199
200 5 Sep 2004: Added SimEvents synchronization constructs
201
202 17 Sep 2004: Added waituntil synchronization construct
203
204 01 Dec 2004: SimPy version 1.5
205 Changes in this module: Repaired SimEvents bug re proc.eventsFired
206
207 12 Jan 2005: SimPy version 1.5.1
208 Changes in this module: Monitor objects now have a default name
209 'a_Monitor'
210
211 29 Mar 2005: Start SimPy 1.6: compound 'yield request' statements
212
213 05 Jun 2005: Fixed bug in _request method -- waitMon did not work properly in
214 preemption case
215
216 09 Jun 2005: Added test in 'activate' to see whether 'initialize()' was called first.
217
218 23 Aug 2005: - Added Tally data collection class
219 - Adjusted Resource to work with Tally
220 - Redid function allEventNotices() (returns prettyprinted string with event
221 times and names of process instances
222 - Added function allEventTimes (returns event times of all scheduled events)
223
224 16 Mar 2006: - Added Store and Level classes
225 - Added 'yield get' and 'yield put'
226
227 10 May 2006: - Repaired bug in Store._get method
228 - Repaired Level to allow initialBuffered have float value
229 - Added type test for Level get parameter 'nrToGet'
230
231 06 Jun 2006: - To improve pretty - printed output of 'Level' objects, changed attribute
232 _nrBuffered to nrBuffered (synonym for amount property)
233 - To improve pretty - printed output of 'Store' objects, added attribute
234 buffered (which refers to _theBuffer)
235
236 25 Aug 2006: - Start of version 1.8
237 - made 'version' public
238 - corrected condQ initialization bug
239
240 30 Sep 2006: - Introduced checks to ensure capacity of a Buffer > 0
241 - Removed from __future__ import (so Python 2.3 or later needed)
242
243 15 Oct 2006: - Added code to register all Monitors and all Tallies in variables
244 'allMonitors' and 'allTallies'
245 - Added function 'startCollection' to activate Monitors and Tallies at a
246 specified time (e.g. after warmup period)
247 - Moved all test / demo programs to after 'if __name__ == '__main__':'.
248
249 17 Oct 2006: - Added compound 'put' and 'get' statements for Level and Store.
250
251 18 Oct 2006: - Repaired bug: self.eventsFired now gets set after an event fires
252 in a compound yield get / put with a waitevent clause (reneging case).
253
254 21 Oct 2006: - Introduced Store 'yield get' with a filter function.
255
256 22 Oct 2006: - Repaired bug in prettyprinting of Store objects (the buffer
257 content==._theBuffer was not shown) by changing ._theBuffer
258 to .theBuffer.
259
260 04 Dec 2006: - Added printHistogram method to Tally and Monitor (generates
261 table - form histogram)
262
263 07 Dec 2006: - Changed the __str__ method of Histogram to print a table
264 (like printHistogram).
265
266 18 Dec 2006: - Added trace printing of Buffers' 'unitName' for yield get and put.
267
268 09 Jun 2007: - Cleaned out all uses of 'object' to prevent name clash.
269
270 18 Nov 2007: - Start of 1.9 development
271 - Added 'start' method (alternative to activate) to Process
272
273 22 Nov 2007: - Major change to event list handling to speed up larger models:
274 * Drop dictionary
275 * Replace bisect by heapq
276 * Mark cancelled event notices in unpost and skip them in
277 nextev (great idea of Tony Vignaux))
278
279 4 Dec 2007: - Added twVariance calculation for both Monitor and Tally (gav)
280
281 5 Dec 2007: - Changed name back to timeVariance (gav)
282
283 1 Mar 2008: - Start of 1.9.1 bugfix release
284 - Delete circular reference in Process instances when event
285 notice has been processed (caused much circular garbage)
286 - Added capability for multiple preempts of a process
287
288 10 Aug 2008: - Removed most classes / methods and imported them from
289 Simulation.py instead (Stefan Scherfke)
290 - Moved remaining functions to SimulationStep and added some
291 methods for backward compatibility
292
293 """
294 from SimPy.Simulation import *
295
296 __TESTING = False
297 version = __version__ = '2.0 $Revision: 163 $ $Date: 2008-12-15 12:47:44 +0100 (Mo, 15 Dez 2008) $'
298 if __TESTING:
299 print 'SimPy.SimulationStep %s' %__version__,
300 if __debug__:
301 print '__debug__ on'
302 else:
303 print
304
305 _step = False
306
308
312
316
318 """Application function to start stepping through simulation."""
319 self._step = True
320
322 """Application function to stop stepping through simulation."""
323 self._step = False
324
325 - def simulate(self, callback = lambda :None, until = 0):
326 """Schedules Processes / semi - coroutines until time 'until'"""
327
328 """Gets called once. Afterwards, co - routines (generators) return by
329 'yield' with a cargo:
330 yield hold, self, <delay>: schedules the 'self' process for activation
331 after < delay > time units.If <,delay > missing,
332 same as 'yield hold, self, 0'
333
334 yield passivate, self : makes the 'self' process wait to be re - activated
335
336 yield request, self,<Resource > [,<priority>]: request 1 unit from < Resource>
337 with < priority > pos integer (default = 0)
338
339 yield release, self,<Resource> : release 1 unit to < Resource>
340
341 yield waitevent, self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
342 wait for one or more of several events
343
344
345 yield queueevent, self,<SimEvent>|[<Evt1>,<Evt2>,<Evt3), . . . ]:
346 queue for one or more of several events
347
348 yield waituntil, self, cond : wait for arbitrary condition
349
350 yield get, self,<buffer > [,<WhatToGet > [,<priority>]]
351 get < WhatToGet > items from buffer (default = 1);
352 <WhatToGet > can be a pos integer or a filter function
353 (Store only)
354
355 yield put, self,<buffer > [,<WhatToPut > [,priority]]
356 put < WhatToPut > items into buffer (default = 1);
357 <WhatToPut > can be a pos integer (Level) or a list of objects
358 (Store)
359
360 EXTENSIONS:
361 Request with timeout reneging:
362 yield (request, self,<Resource>),(hold, self,<patience>) :
363 requests 1 unit from < Resource>. If unit not acquired in time period
364 <patience>, self leaves waitQ (reneges).
365
366 Request with event - based reneging:
367 yield (request, self,<Resource>),(waitevent, self,<eventlist>):
368 requests 1 unit from < Resource>. If one of the events in < eventlist > occurs before unit
369 acquired, self leaves waitQ (reneges).
370
371 Get with timeout reneging (for Store and Level):
372 yield (get, self,<buffer>,nrToGet etc.),(hold, self,<patience>)
373 requests < nrToGet > items / units from < buffer>. If not acquired < nrToGet > in time period
374 <patience>, self leaves < buffer>.getQ (reneges).
375
376 Get with event - based reneging (for Store and Level):
377 yield (get, self,<buffer>,nrToGet etc.),(waitevent, self,<eventlist>)
378 requests < nrToGet > items / units from < buffer>. If not acquired < nrToGet > before one of
379 the events in < eventlist > occurs, self leaves < buffer>.getQ (reneges).
380
381
382
383 Event notices get posted in event - list by scheduler after 'yield' or by
384 'activate' / 'reactivate' functions.
385
386 Nov 9, 2003: Added capability to step through simulation event by event if
387 step == True. 'callback' gets called after every event. It can
388 cancel stepping or end run. API and semantics backwards
389 compatible with previous versions of simulate().
390
391 """
392 paused = False
393 self._stop = False
394
395 if self._e is None:
396 raise FatalSimerror('Simulation not initialized')
397 if self._e._isEmpty():
398 message = 'SimPy: No activities scheduled'
399 return message
400
401 self._endtime = until
402 message = 'SimPy: Normal exit'
403 dispatch={hold:holdfunc, request:requestfunc, release:releasefunc,
404 passivate:passivatefunc, waitevent:waitevfunc, queueevent:queueevfunc,
405 waituntil:waituntilfunc, get:getfunc, put:putfunc}
406 commandcodes = dispatch.keys()
407 commandwords={hold:'hold', request:'request', release:'release', passivate:'passivate',
408 waitevent:'waitevent', queueevent:'queueevent', waituntil:'waituntil',
409 get:'get', put:'put'}
410 nextev = self._e._nextev
411 while not self._stop and self._t <= self._endtime:
412 try:
413 a = nextev()
414 if not a[0] is None:
415
416 if type(a[0][0]) == tuple:
417
418 command = a[0][0][0]
419 else:
420 command = a[0][0]
421 if __debug__:
422 if not command in commandcodes:
423 raise FatalSimerror('Illegal command: yield %s'%command)
424 dispatch[command](a)
425 except FatalSimerror, error:
426 print 'SimPy: ' + error.value
427 sys.exit(1)
428 except Simerror, error:
429 message = 'SimPy: ' + error.value
430 self._stop = True
431 if self._step:
432 callback()
433 if self._wustep:
434 self._test()
435 self._stopWUStepping()
436 self.stopStepping()
437 self._e = None
438 return message
439
440 - def simulateStep(self, callback = lambda :None, until = 0):
441 """Schedules Processes / semi - coroutines until next event
442
443 Can be called repeatedly.
444 Behaves like 'simulate', but does execute only one event per call.
445
446
447
448 """
449 status = 'resumable'
450
451 if self._e == None:
452 raise Simerror('Fatal SimPy error: Simulation not initialized')
453 if self._e._isEmpty():
454 message = 'SimPy: No activities scheduled'
455 status = 'notResumable'
456 return message, status
457
458 self._endtime = until
459 message = 'SimPy: Normal exit'
460 dispatch={hold:holdfunc, request:requestfunc, release:releasefunc,
461 passivate:passivatefunc, waitevent:waitevfunc, queueevent:queueevfunc,
462 waituntil:waituntilfunc, get:getfunc, put:putfunc}
463 commandcodes = dispatch.keys()
464 commandwords={hold:'hold', request:'request', release:'release', passivate:'passivate',
465 waitevent:'waitevent', queueevent:'queueevent', waituntil:'waituntil',
466 get:'get', put:'put'}
467 if not self._stop and self._t <= self._endtime:
468 try:
469 a = self._e._nextev()
470 if not a[0] == None:
471
472 if type(a[0][0]) == tuple:
473
474 command = a[0][0][0]
475 else:
476 command = a[0][0]
477 if __debug__:
478 if not command in commandcodes:
479 raise FatalSimerror('Fatal error: illegal command: yield %s'%command)
480 dispatch[command](a)
481 except FatalSimerror, error:
482 print 'SimPy: ' + error.value
483 sys.exit(1)
484 except Simerror, error:
485 message = 'SimPy: ' + error.value
486 self._stop = True
487 status = 'notResumable'
488 if self._step:
489 callback()
490 return message, status
491
492
493 Globals.sim = SimulationStep()
496
499
500 -def simulate(callback = lambda :None, until = 0):
502
505
506
507
508
509
510 if __name__ == '__main__':
511 print 'SimPy.SimulationStep %s' %__version__
512
513
515 a = raw_input('[Time=%s] End run (e), Continue stepping (s), Run to end (r)'%now())
516 if a == 'e':
517 stopSimulation()
518 elif a == 's':
519 return
520 else:
521 stopStepping()
522
524 class Aa(Process):
525 sequIn = []
526 sequOut = []
527 def __init__(self, holdtime, name):
528 Process.__init__(self, name)
529 self.holdtime = holdtime
530
531 def life(self, priority):
532 for i in range(1):
533 Aa.sequIn.append(self.name)
534 print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
535 len(rrr.activeQ)
536 print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ]
537 print 'activeQ: ',[(k.name, k._priority[rrr]) \
538 for k in rrr.activeQ]
539 assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
540 'Inconsistent resource unit numbers'
541 print now(),self.name, 'requests 1 ', rrr.unitName
542 yield request, self, rrr, priority
543 print now(),self.name, 'has 1 ', rrr.unitName
544 print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
545 len(rrr.activeQ)
546 print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
547 len(rrr.activeQ)
548 assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
549 'Inconsistent resource unit numbers'
550 yield hold, self, self.holdtime
551 print now(),self.name, 'gives up 1', rrr.unitName
552 yield release, self, rrr
553 Aa.sequOut.append(self.name)
554 print now(),self.name, 'has released 1 ', rrr.unitName
555 print 'waitQ: ',[(k.name, k._priority[rrr]) for k in rrr.waitQ]
556 print now(),rrr.name, 'waitQ:', len(rrr.waitQ),'activeQ:',\
557 len(rrr.activeQ)
558 assert rrr.n + len(rrr.activeQ) == rrr.capacity, \
559 'Inconsistent resource unit numbers'
560
561 class Destroyer(Process):
562 def __init__(self):
563 Process.__init__(self)
564
565 def destroy(self, whichProcesses):
566 for i in whichProcesses:
567 Process().cancel(i)
568 yield hold, self, 0
569
570 class Observer(Process):
571 def __init__(self):
572 Process.__init__(self)
573
574 def observe(self, step, processes, res):
575 while now() < 11:
576 for i in processes:
577 print ' %s %s: act:%s, pass:%s, term: %s, interr:%s, qu:%s'\
578 %(now(),i.name, i.active(),i.passive(),i.terminated()\
579 ,i.interrupted(),i.queuing(res))
580 print
581 yield hold, self, step
582
583 class UserControl(Process):
584 def letUserInteract(self, when):
585 yield hold, self, when
586 startStepping()
587
588 print '****First case == priority queue, resource service not preemptable'
589 initialize()
590 rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ,
591 preemptable = 0)
592 procs = []
593 for i in range(10):
594 z = Aa(holdtime = i, name = 'Car ' + str(i))
595 procs.append(z)
596 activate(z, z.life(priority = i))
597 o = Observer()
598 activate(o, o.observe(1, procs, rrr))
599 startStepping()
600 a = simulate(until = 10000, callback = askCancel)
601 print 'Input sequence: ', Aa.sequIn
602 print 'Output sequence: ', Aa.sequOut
603
604 print '\n****Second case == priority queue, resource service preemptable'
605 initialize()
606 rrr = Resource(5, name = 'Parking', unitName = 'space(s)', qType = PriorityQ,
607 preemptable = 1)
608 procs = []
609 for i in range(10):
610 z = Aa(holdtime = i, name = 'Car ' + str(i))
611 procs.append(z)
612 activate(z, z.life(priority = i))
613 o = Observer()
614 activate(o, o.observe(1, procs, rrr))
615 u = UserControl()
616 activate(u, u.letUserInteract(4))
617 Aa.sequIn = []
618 Aa.sequOut = []
619 a = simulate(askCancel, until = 10000)
620 print a
621 print 'Input sequence: ', Aa.sequIn
622 print 'Output sequence: ', Aa.sequOut
623
625 class Bus(Process):
626 def __init__(self, name):
627 Process.__init__(self, name)
628
629 def operate(self, repairduration = 0):
630 print now(),'>> %s starts' % (self.name)
631 tripleft = 1000
632 while tripleft > 0:
633 yield hold, self, tripleft
634 if self.interrupted():
635 print 'interrupted by %s' %self.interruptCause.name
636 print '%s: %s breaks down ' %(now(),self.name)
637 tripleft = self.interruptLeft
638 self.interruptReset()
639 print 'tripleft ', tripleft
640 reactivate(br, delay = repairduration)
641 yield hold, self, repairduration
642 print now(),' repaired'
643 else:
644 break
645 print now(),'<< %s done' % (self.name)
646
647 class Breakdown(Process):
648 def __init__(self, myBus):
649 Process.__init__(self, name = 'Breakdown ' + myBus.name)
650 self.bus = myBus
651
652 def breakBus(self, interval):
653
654 while True:
655 yield hold, self, interval
656 if self.bus.terminated(): break
657 self.interrupt(self.bus)
658
659 print'\n\n+++test_interrupt'
660 initialize()
661 b = Bus('Bus 1')
662 activate(b, b.operate(repairduration = 20))
663 br = Breakdown(b)
664 activate(br, br.breakBus(200))
665 startStepping()
666 simulate(until = 4000)
667
668
670 class Waiter(Process):
671 def waiting(self, theSignal):
672 while True:
673 yield waitevent, self, theSignal
674 print '%s: process \'%s\' continued after waiting for %s' % (now(),self.name, theSignal.name)
675 yield queueevent, self, theSignal
676 print '%s: process \'%s\' continued after queueing for %s' % (now(),self.name, theSignal.name)
677
678 class ORWaiter(Process):
679 def waiting(self, signals):
680 while True:
681 yield waitevent, self, signals
682 print now(),'one of %s signals occurred' % [x.name for x in signals]
683 print '\t%s (fired / param)'%[(x.name, x.signalparam) for x in self.eventsFired]
684 yield hold, self, 1
685
686 class Caller(Process):
687 def calling(self):
688 while True:
689 signal1.signal('wake up!')
690 print '%s: signal 1 has occurred'%now()
691 yield hold, self, 10
692 signal2.signal('and again')
693 signal2.signal('sig 2 again')
694 print '%s: signal1, signal2 have occurred'%now()
695 yield hold, self, 10
696 print'\n+++testSimEvents output'
697 initialize()
698 signal1 = SimEvent('signal 1')
699 signal2 = SimEvent('signal 2')
700 signal1.signal('startup1')
701 signal2.signal('startup2')
702 w1 = Waiter('waiting for signal 1')
703 activate(w1, w1.waiting(signal1))
704 w2 = Waiter('waiting for signal 2')
705 activate(w2, w2.waiting(signal2))
706 w3 = Waiter('also waiting for signal 2')
707 activate(w3, w3.waiting(signal2))
708 w4 = ORWaiter('waiting for either signal 1 or signal 2')
709 activate(w4, w4.waiting([signal1, signal2]),prior = True)
710 c = Caller('Caller')
711 activate(c, c.calling())
712 print simulate(until = 100)
713
715 """
716 Demo of waitUntil capability.
717
718 Scenario:
719 Three workers require sets of tools to do their jobs. Tools are shared, scarce
720 resources for which they compete.
721 """
722
723
724 class Worker(Process):
725 def __init__(self, name, heNeeds = []):
726 Process.__init__(self, name)
727 self.heNeeds = heNeeds
728 def work(self):
729
730 def workerNeeds():
731 for item in self.heNeeds:
732 if item.n == 0:
733 return False
734 return True
735
736 while now() < 8 * 60:
737 yield waituntil, self, workerNeeds
738 for item in self.heNeeds:
739 yield request, self, item
740 print '%s %s has %s and starts job' % (now(),self.name,
741 [x.name for x in self.heNeeds])
742 yield hold, self, random.uniform(10, 30)
743 for item in self.heNeeds:
744 yield release, self, item
745 yield hold, self, 2
746
747 print '\n+++ nwaituntil demo output'
748 initialize()
749 brush = Resource(capacity = 1, name = 'brush')
750 ladder = Resource(capacity = 2, name = 'ladder')
751 hammer = Resource(capacity = 1, name = 'hammer')
752 saw = Resource(capacity = 1, name = 'saw')
753 painter = Worker('painter',[brush, ladder])
754 activate(painter, painter.work())
755 roofer = Worker('roofer',[hammer, ladder, ladder])
756 activate(roofer, roofer.work())
757 treeguy = Worker('treeguy',[saw, ladder])
758 activate(treeguy, treeguy.work())
759 for who in (painter, roofer, treeguy):
760 print '%s needs %s for his job' % (who.name,[x.name for x in who.heNeeds])
761 print
762 print simulate(until = 9 * 60)
763
764
765
766
767 test_demo()
768 test_interrupt()
769 testSimEvents()
770 testwaituntil()
771