-
Notifications
You must be signed in to change notification settings - Fork 0
/
petrinet.h
733 lines (698 loc) · 24.6 KB
/
petrinet.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
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
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
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
729
730
731
732
733
#ifndef _PETRINET_H
#define _PETRINET_H
// 1. The MTPetriNet class implements a high performance mode, with no control
// over transition sequence. This class is suitable for basic behavioral
// simulations, but not useful for verification by randomization.
//
// 2. The STPetriNet class implements a stochastic timed petri net simulation
// which offers control over randomization of event sequences. This class may
// be useful for property verifications by randomized simulations. The
// randomization strategy depends on the compile time flag passed as follows:
//
// SIMU_MODE_RANDOMPRIO : Transitions are assigned a uniform random priority
// as soon as they get enabled. Most useful mode for
// property verification by randomized simulation. The
// probability of hitting corner case sequences is
// higher in this mode.
//
// SIMU_MODE_RANDOMPICK : Transitions are picked uniformly randomly from
// among enabled transitions. At present this is just
// an experimental mode.
//
// SIMU_MODE_STPN : This is just like the Default mode explained below,
// just that the simulation time tracked and advanced
// after processing every transition. The transitions
// enabled first tend to get a higher priority than a
// transition enabled later. Hence this mode may not
// be useful for uncovering rarely occurring
// transition sequences as compared to
// SIMU_MODE_RANDOMPICK. But, this mode provides
// classical STPN simulations. Consider attaching
// probability distribution functions with the
// transition in your application. This functionality
// is not included in this simulator.
//
// Default : Transitions are assigned with a delay using
// callback function set using setDelayFn. Default
// delay (if setDelayFn isn't called) is 0.
#include <iostream>
#include <string>
#include <list>
#include <queue>
#include <set>
#if defined( SIMU_MODE_RANDOMPICK ) || defined( SIMU_MODE_RANDOMPRIO )
# include <random>
#endif
#ifdef USESEQNO
# include <atomic>
#endif
#include "dot.h"
#include "mtengine.h"
#include "jsonprinter.h"
#ifdef PNDBG
# define PNLOG(ARGS) \
_pn->pnlogmutex.lock(); \
_pn->pnlog << ARGS << endl; \
_pn->pnlogmutex.unlock();
#else
# define PNLOG(ARGS)
#endif
// Design Note: Some methods that should ideally belong to PNPlace /
// PNTransition are hosted in respective PetriNet impelementation, because
// they are specific to respective Petri net algorithm. So the behavioral
// aspects, no matter of Place / Transition are implemented in PN variants and
// rest of the classes have only structural and aspects that are agnostic to
// Petri net approach. This allows us to keep multiple Petri net
// implementation options available.
class PNArc;
class PNPlace;
class PNQuitPlace;
class PNTransition;
class PNNode;
typedef vector<PNArc*> Arcs; // a vector to aid filtering by indices
typedef set<PNPlace*> Places;
typedef set<PNTransition*> Transitions;
// Interfaces to resolve inter-dependencies
class IPetriNet : public MTEngine
{
function<void(unsigned, unsigned long)> _eventListener;
void setLogfile(string logfile)
{
#ifdef PNDBG
pnlog.open(logfile);
#endif
}
public:
string _netname;
#ifdef PNDBG
ofstream pnlog;
mutex pnlogmutex;
#endif
unsigned _idcntr = 0;
# ifdef USESEQNO
atomic<unsigned long> _eseqno = 0;
# endif
virtual PNTransition* createTransition(string name)=0;
virtual PNPlace* createPlace(string name, unsigned marking=0, unsigned capacity=1)=0;
virtual PNQuitPlace* createQuitPlace(string name, unsigned marking=0, unsigned capacity=1)=0;
virtual PNNode* createArc(PNNode *n1, PNNode *n2, string name = "", unsigned wt = 1)=0;
virtual void printdot(string filename="petri.dot")=0;
virtual void printpnml(string filename="petri.pnml")=0;
virtual void deleteElems()=0;
virtual void addtokens(PNPlace* place, unsigned newtokens)=0;
void tellListener(unsigned e, unsigned long eseqno) { _eventListener(e, eseqno); }
IPetriNet(string netname, function<void(unsigned,unsigned long)> eventListener) : _netname(netname), _eventListener(eventListener)
{
setLogfile(netname+".petri.log");
}
};
class PNElement
{
public:
typedef enum {PLACE,TRANSITION,ARC} Etyp;
virtual Etyp typ()=0;
};
class PNArc : public PNElement
{
public:
PNPlace* _place;
PNTransition* _transition;
unsigned _wt;
virtual PNNode* source()=0;
virtual PNNode* target()=0;
virtual DEdge dedge() = 0;
Etyp typ() { return ARC; }
PNArc(PNPlace* p, PNTransition* t, unsigned wt) : _place(p), _transition(t), _wt(wt) {}
virtual ~PNArc() {}
};
class PNNode : public PNElement
{
protected:
IPetriNet* _pn;
public:
const unsigned _nodeid;
Arcs _iarcs;
Arcs _oarcs;
const string _name;
void addiarc(PNArc* a) { _iarcs.push_back(a); }
void addoarc(PNArc* a) { _oarcs.push_back(a); }
string idlabel() { return idstr() + ":" + _name; }
string idstr() { return to_string(_nodeid); }
PNNode(string name, IPetriNet* pn) : _name(name), _nodeid(pn->_idcntr++), _pn(pn) {}
};
class PNPlace : public PNNode
{
function<list<int>()> _arcchooser = NULL;
unsigned _marking;
unsigned _capacity;
mutex _tokenmutex;
protected:
function<void()> _addactions = [](){};
public:
virtual void addactions(unsigned newtokens)
{
PNLOG("p:" << idstr() << ":+" << newtokens << ":" << _tokens << ":" << _name)
_addactions();
}
Arcs eligibleArcs()
{
if ( _arcchooser != NULL )
{
Arcs retarcs;
list<int> arcindices = _arcchooser();
for(auto i:arcindices)
retarcs.push_back(_oarcs[i]);
return retarcs;
}
else return _oarcs;
}
void setMarking(unsigned marking) { _marking = marking; }
void setCapacity(unsigned capacity) { _capacity = capacity; }
// This can be put on queue by adding a wrapper that does addwork, for granularity reason it wasn't
unsigned marking() { return _marking; }
unsigned capacity() { return _capacity; }
unsigned _tokens = 0;
Etyp typ() { return PLACE; }
void setArcChooser(function<list<int>()> f) { _arcchooser = f; }
void setAddActions(function<void()> af) { _addactions = af; }
virtual void deductactions(unsigned dedtokens)
{
PNLOG("p:" << idstr() << ":-" << dedtokens << ":" << _tokens << ":" << _name)
}
void lock() { _tokenmutex.lock(); }
bool lockIfEnough(unsigned mintokens)
{
lock();
if(_tokens >= mintokens) return true;
else
{
unlock();
return false;
}
}
void unlock() { _tokenmutex.unlock(); }
DNode dnode() { return DNode(idstr(),(Proplist){{"label","p:"+idlabel()}}); }
// capacity 0 means place can hold unlimited tokens
PNPlace(string name, IPetriNet* pn, unsigned marking=0,unsigned capacity=1) : PNNode(name, pn), _capacity(capacity), _marking(marking) {}
virtual ~PNPlace() {}
};
class PNTransition : public PNNode
{
function<void(unsigned long)> _enabledactions = [](unsigned long){};
function<unsigned long()> _delayfn = [](){ return 0; };
public:
virtual void notEnoughTokensActions()
{
PNLOG("wait:" << idlabel())
}
void enabledactions(unsigned long eseqno)
{
// the event is delivered to a listener first and then the actions attached, if any, are carried out
// Usually it is expected that the listener is some event sequence analyzer (such as a CEP tool)
// while actions may trigger state changes under the main system under simulation. This sequence ensures
// that the state changes caused by this event happen only after the listener sees the event.
# ifdef PN_USE_EVENT_LISTENER
_pn->tellListener(_nodeid, eseqno);
# endif
PNLOG("t:" << idlabel() << ":" << eseqno)
_enabledactions(eseqno);
}
// Although tryTrigger is called only when preceding places have enough
// tokens, there can be other contenders for those tokens who may consume
// them, hence we need to check again whether this transition can fire by
// holding all predecessor places' counts under a lock
unsigned _enabledPlaceCnt = 0;
mutex _enabledPlaceCntMutex;
bool hasEnabledPlaces() { return _enabledPlaceCnt == _iarcs.size(); }
bool mayFire()
{
for(auto ia:_iarcs)
if ( ia->_place->_tokens < ia->_wt ) return false;
return true;
}
void setEnabledActions(function<void(unsigned long)> af) { _enabledactions = af; }
void setDelayFn( function<unsigned long()> df ) { _delayfn = df; }
unsigned long delay() { return _delayfn(); }
Etyp typ() { return TRANSITION; }
DNode dnode() { return DNode(idstr(),(Proplist){{"shape","rectangle"},{"label","t:"+idlabel()}}); }
PNTransition(string name, IPetriNet *pn): PNNode(name, pn) {}
virtual ~PNTransition() {}
};
class PNPTArc : public PNArc
{
public:
PNNode* source() { return _place; }
PNNode* target() { return _transition; }
DEdge dedge() { return DEdge(_place->idstr(),_transition->idstr()); }
PNPTArc(PNPlace* p, PNTransition* t, unsigned wt=1) : PNArc(p,t,wt)
{
_place->addoarc(this);
_transition->addiarc(this);
}
};
class PNTPArc : public PNArc
{
public:
PNNode* source() { return _transition; }
PNNode* target() { return _place; }
DEdge dedge() { return DEdge(_transition->idstr(),_place->idstr()); }
PNTPArc(PNTransition* t, PNPlace* p, unsigned wt=1) : PNArc(p,t,wt)
{
_transition->addoarc(this);
_place->addiarc(this);
}
};
// When QuitPlace gets a token the simulation ends
class PNQuitPlace : public PNPlace
{
using PNPlace::PNPlace;
public:
void addactions(unsigned) { _pn->quit(); }
};
class PetriNetBase : public IPetriNet
{
void assertPlacePresent(PNPlace* n)
{
if ( _places.find(n) == _places.end() )
{
cout << "Place not found: " << n->_name << endl;
exit(1);
}
}
void assertTransitionPresent(PNTransition* n)
{
if ( _transitions.find(n) == _transitions.end() )
{
cout << "Transition not found: " << n->_name << endl;
exit(1);
}
}
protected:
Places _places;
Transitions _transitions;
Arcs _arcs;
virtual void _postinit() {}
void checkPlaceCapacityException(PNPlace *p)
{
if ( p->capacity() > 0 and p->_tokens > p->capacity() )
{
cout << "PN_PLACE_CAPACITY_EXCEPTION:" << p->idlabel() << ":"
<< p->capacity() << ":" << p->_tokens << endl;
}
}
// Note: Fire is to be called after deducting tokens from sources
// it will add tokens to destinations
void fire(PNTransition* t)
{
for(auto iarc:t->_iarcs) iarc->_place->deductactions(iarc->_wt);
# ifdef USESEQNO
t->enabledactions ( _eseqno++ );
# else
t->enabledactions ( 0 );
# endif
for(auto oarc:t->_oarcs) addtokens(oarc->_place, oarc->_wt);
}
// Does json conversion actions that are common to places and transitions
JsonMap* node2json(JsonFactory& jf, JsonMap& nodemap, PNNode* n, JsonKey& label_key)
{
auto idval = jf.createJsonAtom<string>(n->idstr());
auto thisnodemap = jf.createJsonMap();
nodemap.push_back({idval,thisnodemap});
auto labelval = jf.createJsonAtom<string>(n->_name);
thisnodemap->push_back({&label_key,labelval});
return thisnodemap;
}
public:
PNTransition* createTransition(string name)
{
auto t = new PNTransition(name, this);
_transitions.insert(t);
return t;
}
PNPlace* createPlace(string name, unsigned marking=0, unsigned capacity=1)
{
auto p = new PNPlace(name, this, marking, capacity);
_places.insert(p);
return p;
}
PNQuitPlace* createQuitPlace(string name, unsigned marking=0, unsigned capacity=1)
{
auto p = new PNQuitPlace(name, this, marking, capacity);
_places.insert(p);
return p;
}
// returns intermediate node if it was inserted between PP/TT else NULL
// adds the created arc(s) and intermediate node (if any) to Elements pnes
// For intermediate node (if any i.e. for PP/TT arguments) optional
// argument 'name' can be passed
// Optional argument 'wt' is used to assign weight to arc only for PT/TP
// arguments i.e. only when a single arc is created
PNNode* createArc(PNNode *n1, PNNode *n2, string name = "", unsigned wt = 1)
{
if ( n1->typ() == PNElement::TRANSITION )
{
assertTransitionPresent((PNTransition*)n1);
if ( n2->typ() == PNElement::TRANSITION )
{
assertTransitionPresent((PNTransition*)n2);
auto *dummy = createPlace(name);
_arcs.push_back(new PNTPArc((PNTransition*)n1,dummy));
_arcs.push_back(new PNPTArc(dummy,(PNTransition*)n2));
return dummy;
}
else
{
assertPlacePresent((PNPlace*)n2);
_arcs.push_back(new PNTPArc((PNTransition*)n1,(PNPlace*)n2,wt));
return NULL;
}
}
else
{
assertPlacePresent((PNPlace*)n1);
if ( n2->typ() == PNElement::TRANSITION )
{
assertTransitionPresent((PNTransition*)n2);
_arcs.push_back(new PNPTArc((PNPlace*)n1,(PNTransition*)n2,wt));
return NULL;
}
else
{
assertPlacePresent((PNPlace*)n2);
auto *dummy = createTransition(name);
_arcs.push_back(new PNPTArc((PNPlace*)n1,dummy));
_arcs.push_back(new PNTPArc(dummy,(PNPlace*)n2));
return dummy;
}
}
}
void printpnml(string filename="petri.pnml")
{
ofstream ofs;
ofs.open(filename);
ofs << "<?xml version=\"1.0\"?>" << endl;
ofs << "<pnml xmlns=\"http://www.pnml.org/version-2009/grammar/pnml\">" << endl;
for(auto p:_places)
{
ofs << "<place id=\"" << p->idstr() << "\">";
ofs << "<name><text>" << p->_name << "</text></name>";
auto marking = p->marking();
if( marking )
ofs << "<initialMarking><text>" << marking << "</text></initialMarking>";
ofs << "</place>" << endl;
}
for(auto t:_transitions)
{
ofs << "<transition id=\"" << t->idstr() << "\">";
ofs << "<name><text>" << t->_name << "</text></name>";
ofs << "</transition>" << endl;
}
unsigned tmparcid=0;
for(auto e:_arcs)
ofs << "<arc id=\"" << tmparcid++ << "\" source=\"" << e->source()->idstr() << "\" target=\"" << e->target()->idstr() << "\"/>" << endl;
ofs << "</pnml>" << endl;
ofs.close();
}
void printjson(ofstream& ofs)
{
JSONSTR(label)
JSONSTR(places)
JSONSTR(transitions)
JSONSTR(arcs)
JSONSTR(marking)
JSONSTR(capacity)
JSONSTR(src)
JSONSTR(tgt)
JSONSTR(wt)
JsonFactory jf;
JsonMap placemap, transitionmap;
JsonList arclist;
JsonMap top {
{ &places_key, &placemap },
{ &transitions_key, &transitionmap },
{ &arcs_key, &arclist },
};
for(auto p:_places)
{
auto thisplacemap = node2json(jf, placemap, p, label_key);
auto markingval = jf.createJsonAtom<unsigned>(p->marking());
thisplacemap->push_back( { &marking_key, markingval } );
auto capacityval = jf.createJsonAtom<unsigned>(p->capacity());
thisplacemap->push_back( { &capacity_key, capacityval } );
}
for(auto t:_transitions) node2json(jf, transitionmap, t, label_key);
for(auto a:_arcs)
{
auto thisarcmap = jf.createJsonMap();
arclist.push_back(thisarcmap);
auto srcval = jf.createJsonAtom<unsigned>(a->source()->_nodeid);
thisarcmap->push_back( { &src_key, srcval } );
auto tgtval = jf.createJsonAtom<unsigned>(a->target()->_nodeid);
thisarcmap->push_back( { &tgt_key, tgtval } );
auto wtval = jf.createJsonAtom<unsigned>(a->_wt);
thisarcmap->push_back( { &wt_key, wtval } );
}
top.print(ofs);
}
void printjson(string filename="petri.json")
{
ofstream ofs(filename);
printjson(ofs);
ofs.close();
}
void printdot(string filename="petri.dot")
{
DNodeList nl;
for(auto n:_places) nl.push_back(n->dnode());
for(auto n:_transitions) nl.push_back(n->dnode());
DEdgeList el;
for(auto e:_arcs) el.push_back(e->dedge());
DGraph g(nl,el);
g.printdot(filename);
}
void printMarkings()
{
for(auto p:_places) if ( p->_tokens )
cout << "MARKING:" << p->idlabel() << ":" << p->_tokens << endl;
}
// Convenience API to find and delete all elements (places, transitions,
// arcs). Responsibility to delete is with one who does new (all
// destructors are virtual)
void deleteElems()
{
for(auto n:_places) delete n;
for(auto n:_transitions) delete n;
for(auto e:_arcs) delete e;
}
void init()
{
for(auto p:_places)
if ( p->marking() )
addtokens(p, p->marking());
_postinit();
}
PetriNetBase(string netname = "system", function<void(unsigned, unsigned long)> eventListener = [](unsigned, unsigned long){})
: IPetriNet(netname, eventListener)
{}
};
class MTPetriNet : public PetriNetBase
{
using PetriNetBase::PetriNetBase;
// Transition methods (See Design note)
void tryTrigger(PNTransition *transition)
{
Arcs::iterator it = transition->_iarcs.begin();
while(transition->hasEnabledPlaces())
if(tryTransferTokens(transition,it)) fire(transition);
}
// Recursive walk helps keep it simple to avoid locking input places in
// case previous ones do not meet the criteria
bool tryTransferTokens(PNTransition* transition, Arcs::iterator it)
{
if(it==transition->_iarcs.end()) return true;
auto ptarc = *it;
if(ptarc->_place->lockIfEnough(ptarc->_wt))
{
if(tryTransferTokens(transition,++it))
{
deducttokens(ptarc->_place, ptarc->_wt);
ptarc->_place->unlock();
return true;
}
else
{
ptarc->_place->unlock();
return false;
}
}
else return false;
}
void gotEnoughTokens(PNTransition* transition)
{
transition->_enabledPlaceCntMutex.lock();
transition->_enabledPlaceCnt++;
Work tryTriggerWrok = bind(&MTPetriNet::tryTrigger,this,transition);
if(transition->hasEnabledPlaces()) addwork(tryTriggerWrok);
else transition->notEnoughTokensActions();
transition->_enabledPlaceCntMutex.unlock();
}
void notEnoughTokens(PNTransition* transition)
{
transition->_enabledPlaceCntMutex.lock();
if( transition->_enabledPlaceCnt > 0 ) transition->_enabledPlaceCnt--;
transition->_enabledPlaceCntMutex.unlock();
}
// Place methods (See Design note)
// deducttokens Expects caller to have taken care of locking
void deducttokens(PNPlace* place, unsigned tokens)
{
unsigned oldcnt = place->_tokens;
place->_tokens -= tokens;
for(auto oarc:place->_oarcs)
// inform the transition only if we went below the threshold now
if( place->_tokens < oarc->_wt && oldcnt >= oarc->_wt )
notEnoughTokens((PNTransition*)oarc->_transition);
}
public:
void addtokens(PNPlace* place, unsigned newtokens)
{
Arcs eligibleArcs = place->eligibleArcs();
place->lock();
unsigned oldcnt = place->_tokens;
place->_tokens += newtokens;
# ifdef PN_PLACE_CAPACITY_EXCEPTION
checkPlaceCapacityException(place);
# endif
for(auto oarc:eligibleArcs)
// inform the transition only if we crossed the threshold now
// Think, whether we want to randomize the sequence of oarc for non
// determinism Of course, without it also the behavior is correct,
// since a deterministic sequence is a subset of possible non
// deterministic behaviors anyway.
if( place->_tokens >= oarc->_wt && oldcnt < oarc->_wt )
gotEnoughTokens((PNTransition*)oarc->_transition);
place->unlock();
place->addactions(newtokens);
}
};
// TODO: Decide how to use multiple cores for STPN. Either start multiple
// simulations over threads or let the user start multiple processes. Log
// clashes should be avoided when doing so.
class STPetriNet : public PetriNetBase
{
using PetriNetBase::PetriNetBase;
using t_pair = pair<double, PNTransition*>;
// Saves the overhead of comparing 2nd member of the pair
class PriorityLT
{
public:
// Since delay is opposite of priority, we use >
bool operator() (t_pair& l, t_pair& r) { return l.first > r.first; }
};
using t_queue = priority_queue<t_pair, vector<t_pair>, PriorityLT>;
#ifdef SIMU_MODE_RANDOMPICK
list<PNTransition*> _tq;
random_device _rng;
uniform_int_distribution<unsigned> _udistr;
#elif defined ( SIMU_MODE_RANDOMPRIO )
t_queue _tq;
random_device _rng;
uniform_real_distribution<double> _udistr {-1,1};
#else
t_queue _tq;
#endif
#ifdef SIMU_MODE_STPN
double _offset = 0;
#endif
mutex _tqmutex;
condition_variable _tq_cvar;
void _simuloop()
{
while ( not _quit )
{
_tqmutex.lock();
if ( _tq.empty() )
{
_tqmutex.unlock();
break;
}
#ifdef SIMU_MODE_STPN
_offset += _tq.top().first;
#endif
#ifdef SIMU_MODE_RANDOMPICK
int pick = _udistr(_rng) % _tq.size();
auto it = _tq.begin();
advance(it,pick);
auto t = *it;
_tq.erase(it);
#else
auto t = _tq.top().second;
_tq.pop();
#endif
_tqmutex.unlock();
// this was checked when adding to _tq, but marking may change
// till its turn comes, so check again
if ( t->mayFire() )
{
for(auto ia:t->_iarcs)
{
auto p = ia->_place;
p->lock();
p->_tokens -= ia->_wt;
p->unlock();
}
fire(t);
}
}
}
void simuloop()
{
// TODO: this needs to be a do-while loop, otherwise and early quit will exit
// TODO: wait call lambda should be not _tq.empty()
// TODO: unique_lock and wait should be in a scope (braces)
while ( not _quit )
{
_simuloop();
unique_lock<mutex> ulockq(_tqmutex);
_tq_cvar.wait(ulockq, [](){return true;});
}
}
void _postinit()
{
Work w = bind(&STPetriNet::simuloop,this);
addwork(w);
}
public:
void addtokens(PNPlace* place, unsigned newtokens)
{
place->lock();
place->_tokens += newtokens;
# ifdef PN_PLACE_CAPACITY_EXCEPTION
checkPlaceCapacityException(place);
# endif
place->unlock();
place->addactions(newtokens);
Arcs eligibleArcs = place->eligibleArcs();
for(auto oarc:eligibleArcs)
{
auto t = oarc->_transition;
if ( t->mayFire() )
{
_tqmutex.lock();
#if defined( SIMU_MODE_RANDOMPICK )
_tq.push_back(t);
#elif defined( SIMU_MODE_RANDOMPRIO )
_tq.push( { _udistr(_rng), t } );
#elif defined( SIMU_MODE_STPN )
_tq.push( { t->delay() + _offset, t } );
#else
_tq.push( { t->delay(), t } );
#endif
_tqmutex.unlock();
}
}
unique_lock<mutex> ulockq(_tqmutex);
_tq_cvar.notify_one();
}
};
#endif