| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Schedule::Activity; |
|
2
|
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
5853
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
35
|
|
|
4
|
1
|
|
|
1
|
|
4
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
57
|
|
|
5
|
1
|
|
|
1
|
|
5
|
use List::Util qw/any/; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
136
|
|
|
6
|
1
|
|
|
1
|
|
614
|
use Ref::Util qw/is_arrayref is_hashref is_plain_hashref is_ref/; |
|
|
1
|
|
|
|
|
2932
|
|
|
|
1
|
|
|
|
|
143
|
|
|
7
|
1
|
|
|
1
|
|
8
|
use Scalar::Util qw/blessed/; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
106
|
|
|
8
|
1
|
|
|
1
|
|
502
|
use Schedule::Activity::Annotation; |
|
|
1
|
|
|
|
|
4
|
|
|
|
1
|
|
|
|
|
45
|
|
|
9
|
1
|
|
|
1
|
|
587
|
use Schedule::Activity::Attributes; |
|
|
1
|
|
|
|
|
4
|
|
|
|
1
|
|
|
|
|
38
|
|
|
10
|
1
|
|
|
1
|
|
559
|
use Schedule::Activity::Message; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
49
|
|
|
11
|
1
|
|
|
1
|
|
594
|
use Schedule::Activity::Node; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
48
|
|
|
12
|
1
|
|
|
1
|
|
636
|
use Schedule::Activity::NodeFilter; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
8705
|
|
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
our $VERSION='0.3.0'; |
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
sub new { |
|
17
|
55
|
|
|
55
|
0
|
555326
|
my ($ref,%opt)=@_; |
|
18
|
55
|
|
33
|
|
|
321
|
my $class=is_ref($ref)||$ref; |
|
19
|
|
|
|
|
|
|
my %self=( |
|
20
|
|
|
|
|
|
|
config =>$opt{configuration}//{}, |
|
21
|
|
|
|
|
|
|
attr =>undef, |
|
22
|
|
|
|
|
|
|
valid =>0, |
|
23
|
|
|
|
|
|
|
built =>undef, |
|
24
|
|
|
|
|
|
|
reach =>undef, |
|
25
|
55
|
|
50
|
|
|
855
|
unsafe =>$opt{unsafe}//0, |
|
|
|
|
50
|
|
|
|
|
|
26
|
|
|
|
|
|
|
PNA =>undef, # per node attribute prefix, loaded via config |
|
27
|
|
|
|
|
|
|
); |
|
28
|
55
|
|
|
|
|
715
|
return bless(\%self,$class); |
|
29
|
|
|
|
|
|
|
} |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# validate() |
|
32
|
|
|
|
|
|
|
# compile() |
|
33
|
|
|
|
|
|
|
# schedule(activities=>[...]) |
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
sub _attr { |
|
36
|
131909
|
|
|
131909
|
|
251087
|
my ($self)=@_; |
|
37
|
131909
|
|
66
|
|
|
293761
|
$$self{attr}//=Schedule::Activity::Attributes->new(); |
|
38
|
131909
|
|
|
|
|
556480
|
return $$self{attr}; |
|
39
|
|
|
|
|
|
|
} |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
sub validate { |
|
42
|
55
|
|
|
55
|
0
|
151
|
my ($self,$force)=@_; |
|
43
|
55
|
50
|
33
|
|
|
329
|
if($$self{valid}&&!$force) { return } |
|
|
0
|
|
|
|
|
0
|
|
|
44
|
55
|
50
|
50
|
|
|
153
|
$$self{config}//={}; if(!is_hashref($$self{config})) { return ('Configuration must be a hash') } |
|
|
55
|
|
|
|
|
230
|
|
|
|
0
|
|
|
|
|
0
|
|
|
45
|
55
|
|
|
|
|
218
|
my @errors=$self->_validateConfig(); |
|
46
|
55
|
100
|
|
|
|
146
|
if(!@errors) { $$self{valid}=1 } |
|
|
52
|
|
|
|
|
138
|
|
|
47
|
55
|
|
|
|
|
135
|
return @errors; |
|
48
|
|
|
|
|
|
|
} |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
sub _validateConfig { |
|
51
|
55
|
|
|
55
|
|
121
|
my ($self)=@_; |
|
52
|
55
|
|
|
|
|
155
|
my $attr=$self->_attr(); |
|
53
|
55
|
|
|
|
|
113
|
my %config=%{$$self{config}}; |
|
|
55
|
|
|
|
|
262
|
|
|
54
|
55
|
|
|
|
|
140
|
my (@errors,@invalids); |
|
55
|
55
|
50
|
|
|
|
154
|
if(!is_hashref($config{node})) { push @errors,'Config is missing: node'; $config{node}={} } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
56
|
55
|
100
|
|
|
|
183
|
if($config{attributes}) { |
|
57
|
3
|
50
|
|
|
|
9
|
if(!is_hashref($config{attributes})) { push @errors,'Attributes invalid structure' } |
|
|
0
|
|
|
|
|
0
|
|
|
58
|
3
|
|
|
|
|
6
|
else { while(my ($k,$v)=each %{$config{attributes}}) { push @errors,$attr->register($k,%$v) } } |
|
|
7
|
|
|
|
|
24
|
|
|
|
10
|
|
|
|
|
29
|
|
|
59
|
|
|
|
|
|
|
} |
|
60
|
55
|
100
|
|
|
|
159
|
if($config{messages}) { |
|
61
|
2
|
50
|
|
|
|
8
|
if(!is_hashref($config{messages})) { push @errors,'Messages invalid structure' } |
|
|
0
|
|
|
|
|
0
|
|
|
62
|
|
|
|
|
|
|
else { |
|
63
|
2
|
|
|
|
|
4
|
while(my ($namea,$msga)=each %{$config{messages}}) { |
|
|
5
|
|
|
|
|
22
|
|
|
64
|
3
|
50
|
66
|
|
|
20
|
if(!is_hashref($msga)) { push @errors,"Messages $namea invalid structure"; next } |
|
|
0
|
50
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
65
|
0
|
|
|
|
|
0
|
elsif(defined($$msga{attributes})&&!is_hashref($$msga{attributes})) { push @errors,"Messages $namea invalid attributes"; delete($$msga{attributes}) } |
|
|
0
|
|
|
|
|
0
|
|
|
66
|
3
|
|
|
|
|
13
|
else { foreach my $kv (Schedule::Activity::Message::attributesFromConf($msga)) { push @errors,$attr->register($$kv[0],%{$$kv[1]}) } } |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
10
|
|
|
67
|
3
|
100
|
|
|
|
16
|
if(is_hashref($$msga{message})) { |
|
68
|
1
|
50
|
33
|
|
|
10
|
if(defined($$msga{message}{alternates})&&!is_arrayref($$msga{message}{alternates})) { push @errors,"Messages $namea invalid alternates" } |
|
|
0
|
|
|
|
|
0
|
|
|
69
|
1
|
|
|
|
|
4
|
else { foreach my $kv (Schedule::Activity::Message::attributesFromConf($$msga{message})) { push @errors,$attr->register($$kv[0],%{$$kv[1]}) } } |
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
10
|
|
|
70
|
|
|
|
|
|
|
} |
|
71
|
|
|
|
|
|
|
} |
|
72
|
|
|
|
|
|
|
} } # messages |
|
73
|
55
|
|
|
|
|
121
|
while(my ($k,$node)=each %{$config{node}}) { |
|
|
462
|
|
|
|
|
1647
|
|
|
74
|
407
|
50
|
|
|
|
806
|
if(!is_hashref($node)) { push @errors,"Node $k, Invalid structure"; next } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
75
|
407
|
|
|
|
|
1307
|
Schedule::Activity::Node::defaulting($node); |
|
76
|
407
|
|
|
|
|
1499
|
my @nerrors=Schedule::Activity::Node::validate(%$node); |
|
77
|
407
|
100
|
|
|
|
1249
|
if($$node{attributes}) { |
|
78
|
179
|
50
|
|
|
|
490
|
if(!is_hashref($$node{attributes})) { push @nerrors,"attributes, Invalid structure" } |
|
|
0
|
|
|
|
|
0
|
|
|
79
|
179
|
|
|
|
|
306
|
else { while(my ($k,$v)=each %{$$node{attributes}}) { push @nerrors,$attr->register($k,%$v) } } |
|
|
200
|
|
|
|
|
944
|
|
|
|
379
|
|
|
|
|
1313
|
|
|
80
|
|
|
|
|
|
|
} |
|
81
|
407
|
|
|
|
|
1762
|
push @nerrors,Schedule::Activity::Message::validate($$node{message},names=>$config{messages}); |
|
82
|
407
|
|
|
|
|
1412
|
foreach my $kv (Schedule::Activity::Message::attributesFromConf($$node{message})) { push @nerrors,$attr->register($$kv[0],%{$$kv[1]}) } |
|
|
14
|
|
|
|
|
18
|
|
|
|
14
|
|
|
|
|
30
|
|
|
83
|
407
|
100
|
|
|
|
910
|
if(@nerrors) { push @errors,map {"Node $k, $_"} @nerrors; next } |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
7
|
|
|
|
2
|
|
|
|
|
5
|
|
|
84
|
405
|
|
100
|
|
|
1747
|
@invalids=grep {!defined($config{node}{$_})} Schedule::Activity::Node->nextnames(0,$$node{next}//[]); |
|
|
846
|
|
|
|
|
2015
|
|
|
85
|
405
|
50
|
|
|
|
853
|
if(@invalids) { push @errors,"Node $k, Undefined name in array: next" } |
|
|
0
|
|
|
|
|
0
|
|
|
86
|
405
|
100
|
100
|
|
|
1512
|
if(defined($$node{finish})&&!defined($config{node}{$$node{finish}})) { push @errors,"Node $k, Undefined name: finish" } |
|
|
1
|
|
|
|
|
4
|
|
|
87
|
|
|
|
|
|
|
} |
|
88
|
55
|
|
100
|
|
|
272
|
$config{annotations}//={}; |
|
89
|
55
|
50
|
|
|
|
175
|
if(!is_hashref($config{annotations})) { push @errors,'Annotations must be a hash' } |
|
|
0
|
|
|
|
|
0
|
|
|
90
|
55
|
|
|
|
|
117
|
else { while(my ($k,$notes)=each %{$config{annotations}}) { |
|
|
60
|
|
|
|
|
225
|
|
|
91
|
5
|
|
|
|
|
18
|
push @errors,map {"Annotation $k: $_"} map {( |
|
|
0
|
|
|
|
|
0
|
|
|
92
|
|
|
|
|
|
|
Schedule::Activity::Annotation::validate(%$_), |
|
93
|
|
|
|
|
|
|
Schedule::Activity::Message::validate($$_{message},names=>$config{messages}) |
|
94
|
6
|
|
|
|
|
44
|
)} @$notes } } |
|
95
|
55
|
|
|
|
|
177
|
return @errors; |
|
96
|
|
|
|
|
|
|
} |
|
97
|
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
sub _reachability { |
|
99
|
54
|
|
|
54
|
|
13532
|
my ($self)=@_; |
|
100
|
54
|
|
|
|
|
79
|
my $changed; |
|
101
|
54
|
|
|
|
|
230
|
my %reach=(min=>{},max=>{}); |
|
102
|
54
|
|
|
|
|
98
|
foreach my $namea (keys %{$$self{built}{node}}) { |
|
|
54
|
|
|
|
|
287
|
|
|
103
|
411
|
|
|
|
|
782
|
my $nodea=$$self{built}{node}{$namea}; |
|
104
|
411
|
|
|
|
|
540
|
my @nodes; |
|
105
|
411
|
100
|
|
|
|
1091
|
if (is_arrayref($$nodea{next})) { @nodes=@{$$nodea{next}} } |
|
|
306
|
100
|
|
|
|
386
|
|
|
|
306
|
|
|
|
|
857
|
|
|
106
|
5
|
|
|
|
|
12
|
elsif(is_hashref($$nodea{next})) { @nodes=map {$$_{node}} values %{$$nodea{next}} } |
|
|
24
|
|
|
|
|
49
|
|
|
|
5
|
|
|
|
|
15
|
|
|
107
|
411
|
|
|
|
|
830
|
foreach my $nodeb (@nodes) { |
|
108
|
853
|
|
|
|
|
2418
|
$reach{min}{$nodea}{$nodeb}=$$nodea{tmmin}; |
|
109
|
853
|
100
|
|
|
|
3183
|
$reach{max}{$nodea}{$nodeb}=(($nodea eq $nodeb)?'+':$$nodea{tmmax}); |
|
110
|
|
|
|
|
|
|
} |
|
111
|
|
|
|
|
|
|
} |
|
112
|
54
|
|
|
|
|
152
|
$changed=1; |
|
113
|
54
|
|
|
|
|
167
|
while($changed) { $changed=0; |
|
|
110
|
|
|
|
|
208
|
|
|
114
|
110
|
|
|
|
|
213
|
foreach my $nodea (keys %{$reach{min}}) { |
|
|
110
|
|
|
|
|
371
|
|
|
115
|
730
|
|
|
|
|
1070
|
foreach my $nodeb (keys %{$reach{min}{$nodea}}) { |
|
|
730
|
|
|
|
|
1734
|
|
|
116
|
1669
|
|
|
|
|
2243
|
foreach my $nodec (keys %{$reach{min}{$nodeb}}) { |
|
|
1669
|
|
|
|
|
3884
|
|
|
117
|
3460
|
|
|
|
|
5651
|
my $x=$reach{min}{$nodea}{$nodec}; |
|
118
|
3460
|
|
|
|
|
6361
|
my $y=$reach{min}{$nodea}{$nodeb}+$reach{min}{$nodeb}{$nodec}; |
|
119
|
3460
|
100
|
100
|
|
|
11845
|
if(!defined($x)||($x>$y)) { |
|
120
|
152
|
|
|
|
|
311
|
$reach{min}{$nodea}{$nodec}=$y; |
|
121
|
152
|
|
|
|
|
287
|
$changed=1; |
|
122
|
|
|
|
|
|
|
} |
|
123
|
|
|
|
|
|
|
} } } |
|
124
|
|
|
|
|
|
|
} |
|
125
|
|
|
|
|
|
|
my $triadd=sub { |
|
126
|
828
|
|
|
828
|
|
1533
|
my ($x,$y)=@_; |
|
127
|
828
|
100
|
|
|
|
2531
|
if($x eq '+') { return '+' } |
|
|
361
|
|
|
|
|
707
|
|
|
128
|
467
|
100
|
|
|
|
936
|
if($y eq '+') { return '+' } |
|
|
279
|
|
|
|
|
670
|
|
|
129
|
188
|
|
|
|
|
331
|
return $x+$y; |
|
130
|
54
|
|
|
|
|
401
|
}; |
|
131
|
54
|
|
|
|
|
112
|
$changed=1; |
|
132
|
54
|
|
|
|
|
143
|
while($changed) { $changed=0; |
|
|
116
|
|
|
|
|
173
|
|
|
133
|
116
|
|
|
|
|
193
|
foreach my $nodea (keys %{$reach{max}}) { |
|
|
116
|
|
|
|
|
422
|
|
|
134
|
776
|
|
|
|
|
1053
|
foreach my $nodeb (keys %{$reach{max}{$nodea}}) { |
|
|
776
|
|
|
|
|
1907
|
|
|
135
|
1789
|
|
|
|
|
2382
|
foreach my $nodec (keys %{$reach{max}{$nodeb}}) { |
|
|
1789
|
|
|
|
|
4089
|
|
|
136
|
3749
|
100
|
|
|
|
6835
|
if($nodea eq $nodec) { $reach{max}{$nodea}{$nodec}='+'; next } |
|
|
784
|
|
|
|
|
1461
|
|
|
|
784
|
|
|
|
|
1463
|
|
|
137
|
2965
|
|
|
|
|
12033
|
my $x=$reach{max}{$nodea}{$nodec}; |
|
138
|
2965
|
100
|
100
|
|
|
8734
|
if(defined($x)&&($x eq '+')) { next } |
|
|
2137
|
|
|
|
|
3802
|
|
|
139
|
828
|
|
|
|
|
1824
|
my $y=&$triadd($reach{max}{$nodea}{$nodeb},$reach{max}{$nodeb}{$nodec}); |
|
140
|
828
|
100
|
100
|
|
|
2934
|
if(!defined($x)||($y eq '+')||($x<$y)) { |
|
|
|
|
100
|
|
|
|
|
|
141
|
778
|
|
|
|
|
1713
|
$reach{max}{$nodea}{$nodec}=$y; |
|
142
|
778
|
|
|
|
|
1397
|
$changed=1; |
|
143
|
|
|
|
|
|
|
} |
|
144
|
|
|
|
|
|
|
} } } |
|
145
|
|
|
|
|
|
|
} |
|
146
|
54
|
|
|
|
|
149
|
$$self{reach}=\%reach; |
|
147
|
54
|
|
|
|
|
398
|
return $self; |
|
148
|
|
|
|
|
|
|
} |
|
149
|
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
# These checks ignore any filtering that might be active during construction; these are only sanity checks. |
|
151
|
|
|
|
|
|
|
# Recommend stashing the reachability results in $self for later. |
|
152
|
|
|
|
|
|
|
# |
|
153
|
|
|
|
|
|
|
# Here are the tests and their defined orders. |
|
154
|
|
|
|
|
|
|
# 1. Activity that cannot reach finish |
|
155
|
|
|
|
|
|
|
# 2. Orphaned actions (no activity reaches them) |
|
156
|
|
|
|
|
|
|
# 3. Dual-parent action nodes (with more than a single root activity) NOT(item2) |
|
157
|
|
|
|
|
|
|
# 4. Dual-finish action nodes NOT(item3) |
|
158
|
|
|
|
|
|
|
# 5. Dangling actions (cannot reach their finish node) NOT(item1||item4) |
|
159
|
|
|
|
|
|
|
# 6. Action nodes with tmavg=0 NOT(activity|finish) (this is only a problem if there's a cycle) |
|
160
|
|
|
|
|
|
|
# |
|
161
|
|
|
|
|
|
|
sub safetyChecks { |
|
162
|
51
|
|
|
51
|
0
|
131
|
my ($self)=@_; |
|
163
|
51
|
|
|
|
|
96
|
my (@errors,$changed); |
|
164
|
51
|
|
|
|
|
176
|
$self->_reachability(); |
|
165
|
51
|
|
|
|
|
105
|
my %reach=%{$$self{reach}}; |
|
|
51
|
|
|
|
|
269
|
|
|
166
|
|
|
|
|
|
|
# |
|
167
|
|
|
|
|
|
|
# Be very cautious about names versus stringified references. |
|
168
|
51
|
|
|
|
|
176
|
my $builtNode=$$self{built}{node}; |
|
169
|
51
|
|
|
|
|
281
|
my %activities=map {$$builtNode{$_}=>$$builtNode{$_}{finish}} grep {defined($$builtNode{$_}{finish})} keys(%$builtNode); |
|
|
99
|
|
|
|
|
408
|
|
|
|
401
|
|
|
|
|
921
|
|
|
170
|
51
|
|
|
|
|
186
|
my %finishes=map {$_=>1} values(%activities); |
|
|
99
|
|
|
|
|
308
|
|
|
171
|
51
|
|
100
|
|
|
180
|
my %actions=map {$_=>$$builtNode{$_}} grep {!exists($activities{$$builtNode{$_}})&&!exists($finishes{$$builtNode{$_}})} keys(%$builtNode); |
|
|
203
|
|
|
|
|
518
|
|
|
|
401
|
|
|
|
|
1589
|
|
|
172
|
51
|
|
|
|
|
221
|
my %incompleteActivities=map {$_=>1} grep{!defined($reach{min}{$$builtNode{$_}}{$activities{$$builtNode{$_}}})} grep {defined($$builtNode{$_}{finish})} keys(%$builtNode); |
|
|
2
|
|
|
|
|
8
|
|
|
|
99
|
|
|
|
|
487
|
|
|
|
401
|
|
|
|
|
831
|
|
|
173
|
|
|
|
|
|
|
# |
|
174
|
51
|
|
|
|
|
161
|
push @errors,map {"Finish for activity $_ is unreachable"} keys(%incompleteActivities); |
|
|
2
|
|
|
|
|
7
|
|
|
175
|
|
|
|
|
|
|
# |
|
176
|
51
|
|
|
|
|
133
|
my (%orphans,%dualParent,%dualFinish,%dangling,%infiniteCycle); |
|
177
|
51
|
|
|
|
|
151
|
foreach my $action (keys %actions) { |
|
178
|
203
|
|
|
|
|
353
|
my $parents=0; |
|
179
|
203
|
|
|
|
|
311
|
my $terminals=0; |
|
180
|
203
|
100
|
|
|
|
425
|
foreach my $activity (keys %activities) { if(defined($reach{min}{$activity}{$actions{$action}})) { $parents++ } } |
|
|
493
|
|
|
|
|
1418
|
|
|
|
203
|
|
|
|
|
374
|
|
|
181
|
203
|
100
|
|
|
|
455
|
foreach my $finish (keys %finishes) { if(defined($reach{min}{$actions{$action}}{$finish})) { $terminals++ } } |
|
|
493
|
|
|
|
|
1502
|
|
|
|
200
|
|
|
|
|
371
|
|
|
182
|
203
|
100
|
|
|
|
672
|
if($parents==0) { $orphans{$action}=1 } |
|
|
2
|
100
|
|
|
|
4
|
|
|
183
|
2
|
|
|
|
|
4
|
elsif($parents>1) { $dualParent{$action}=1 } |
|
184
|
203
|
100
|
|
|
|
519
|
if($terminals>1) { $dualFinish{$action}=1 } |
|
|
2
|
100
|
|
|
|
6
|
|
|
185
|
5
|
|
|
|
|
12
|
elsif(!$terminals) { $dangling{$action}=1 } |
|
186
|
203
|
100
|
100
|
|
|
684
|
if(($actions{$action}{tmavg}==0)&&(defined($reach{min}{$actions{$action}}{$actions{$action}}))) { $infiniteCycle{$action}=1 } |
|
|
3
|
|
|
|
|
10
|
|
|
187
|
|
|
|
|
|
|
} |
|
188
|
51
|
|
|
|
|
131
|
push @errors,map {"Action $_ belongs to no activity"} keys(%orphans); |
|
|
2
|
|
|
|
|
6
|
|
|
189
|
51
|
|
|
|
|
128
|
push @errors,map {"Action $_ belongs to multiple activities"} keys(%dualParent); |
|
|
2
|
|
|
|
|
11
|
|
|
190
|
51
|
|
|
|
|
154
|
push @errors,map {"Action $_ reaches multiple finish nodes"} keys(%dualFinish); |
|
|
2
|
|
|
|
|
6
|
|
|
191
|
51
|
|
|
|
|
109
|
push @errors,map {"Dangling action $_"} keys(%dangling); |
|
|
5
|
|
|
|
|
16
|
|
|
192
|
51
|
|
|
|
|
110
|
push @errors,map {"No progress will be made for action $_"} keys(%infiniteCycle); |
|
|
3
|
|
|
|
|
8
|
|
|
193
|
51
|
|
|
|
|
374
|
return @errors; |
|
194
|
|
|
|
|
|
|
} |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
sub _buildConfig { |
|
197
|
52
|
|
|
52
|
|
194
|
my ($self)=@_; |
|
198
|
52
|
|
|
|
|
88
|
my %base=%{$$self{config}}; |
|
|
52
|
|
|
|
|
220
|
|
|
199
|
52
|
|
|
|
|
163
|
my $attr=$self->_attr(); |
|
200
|
52
|
|
|
|
|
114
|
my %res; |
|
201
|
52
|
100
|
|
|
|
167
|
if($base{PNA}) { $$self{PNA}=$base{PNA} } |
|
|
2
|
|
|
|
|
7
|
|
|
202
|
52
|
|
|
|
|
90
|
while(my ($k,$node)=each %{$base{node}}) { |
|
|
452
|
|
|
|
|
1305
|
|
|
203
|
400
|
50
|
|
|
|
700
|
if(is_plain_hashref($node)) { $res{node}{$k}=Schedule::Activity::Node->new(%$node) } |
|
|
400
|
0
|
|
|
|
1297
|
|
|
204
|
0
|
|
|
|
|
0
|
elsif(blessed($node)) { $res{node}{$k}=$node } |
|
205
|
0
|
|
|
|
|
0
|
else { die "Invalid node $k when building config" } |
|
206
|
400
|
|
|
|
|
1075
|
$res{node}{$k}{keyname}=$k; |
|
207
|
400
|
100
|
|
|
|
902
|
if($$self{PNA}) { |
|
208
|
8
|
|
|
|
|
31
|
$res{node}{$k}{attributes}{"$$self{PNA}$k"}={incr=>1}; |
|
209
|
8
|
|
|
|
|
28
|
$attr->register("$$self{PNA}$k",type=>'int',value=>0); |
|
210
|
|
|
|
|
|
|
} |
|
211
|
|
|
|
|
|
|
} |
|
212
|
52
|
|
100
|
|
|
254
|
my $msgNames=$base{messages}//{}; |
|
213
|
52
|
|
|
|
|
113
|
while(my ($k,$node)=each %{$res{node}}) { |
|
|
452
|
|
|
|
|
1387
|
|
|
214
|
400
|
|
|
|
|
1122
|
$node->nextremap($res{node}); |
|
215
|
400
|
100
|
|
|
|
867
|
if(defined($$node{finish})) { $$node{finish}=$res{node}{$$node{finish}} } |
|
|
99
|
|
|
|
|
247
|
|
|
216
|
400
|
|
|
|
|
1214
|
$$node{msg}=Schedule::Activity::Message->new(message=>$$node{message},names=>$msgNames); |
|
217
|
400
|
100
|
|
|
|
1788
|
if(is_plain_hashref($$node{require})) { $$node{require}=Schedule::Activity::NodeFilter->new(%{$$node{require}}) } |
|
|
7
|
|
|
|
|
16
|
|
|
|
7
|
|
|
|
|
52
|
|
|
218
|
|
|
|
|
|
|
} |
|
219
|
52
|
|
|
|
|
159
|
$$self{built}=\%res; |
|
220
|
52
|
|
|
|
|
161
|
return $self; |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
sub compile { |
|
224
|
17161
|
|
|
17161
|
0
|
53993
|
my ($self,%opt)=@_; |
|
225
|
17161
|
100
|
|
|
|
54460
|
if($$self{built}) { return } |
|
|
17106
|
|
|
|
|
51144
|
|
|
226
|
55
|
|
|
|
|
248
|
my @errors=$self->validate(); |
|
227
|
55
|
100
|
|
|
|
135
|
if(@errors) { return (error=>\@errors) } |
|
|
3
|
|
|
|
|
11
|
|
|
228
|
52
|
|
|
|
|
254
|
$self->_buildConfig(); |
|
229
|
52
|
100
|
|
|
|
175
|
if(!$opt{unsafe}) { @errors=$self->safetyChecks(); if(@errors) { return (error=>\@errors) } } |
|
|
50
|
100
|
|
|
|
169
|
|
|
|
50
|
|
|
|
|
174
|
|
|
|
2
|
|
|
|
|
8
|
|
|
230
|
50
|
|
|
|
|
158
|
return; |
|
231
|
|
|
|
|
|
|
} |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
sub _nodeMessage { |
|
234
|
185980
|
|
|
185980
|
|
404115
|
my ($optattr,$tm,$node)=@_; |
|
235
|
185980
|
|
|
|
|
599092
|
my ($message,$msg)=$$node{msg}->random(); |
|
236
|
185980
|
100
|
|
|
|
583516
|
if($$node{attributes}) { |
|
237
|
114659
|
|
|
|
|
187432
|
while(my ($k,$v)=each %{$$node{attributes}}) { |
|
|
311435
|
|
|
|
|
1091470
|
|
|
238
|
196776
|
|
|
|
|
595449
|
$optattr->change($k,%$v,tm=>$tm) } } |
|
239
|
185980
|
50
|
|
|
|
431644
|
if(is_hashref($msg)) { while(my ($k,$v)=each %{$$msg{attributes}}) { |
|
|
185980
|
|
|
|
|
288087
|
|
|
|
186046
|
|
|
|
|
561218
|
|
|
240
|
66
|
|
|
|
|
277
|
$optattr->change($k,%$v,tm=>$tm); |
|
241
|
|
|
|
|
|
|
} } |
|
242
|
|
|
|
|
|
|
# |
|
243
|
185980
|
|
50
|
|
|
730695
|
return Schedule::Activity::Message->new(message=>$message,attributes=>$$msg{attributes}//{}); |
|
244
|
|
|
|
|
|
|
} |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
sub findpath { |
|
247
|
18839
|
|
|
18839
|
0
|
108851
|
my (%opt)=@_; |
|
248
|
18839
|
|
|
|
|
58236
|
my ($tm,$slack,$buffer,@res)=(0,0,0); |
|
249
|
|
|
|
|
|
|
my %tension=( |
|
250
|
|
|
|
|
|
|
slack =>1-($opt{tensionslack} //$opt{tension}//0.5), |
|
251
|
18839
|
|
66
|
|
|
103045
|
buffer=>1-($opt{tensionbuffer}//$opt{tension}//0.85659008), |
|
|
|
|
50
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
252
|
|
|
|
|
|
|
); |
|
253
|
18839
|
50
|
|
|
|
41751
|
foreach my $k (qw/slack buffer/) { if($tension{$k}>1){$tension{$k}=1}; if($tension{$k}<0){$tension{$k}=0} } |
|
|
37678
|
50
|
|
|
|
97774
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
37678
|
|
|
|
|
93010
|
|
|
|
0
|
|
|
|
|
0
|
|
|
254
|
18839
|
|
|
|
|
45095
|
my ($node,$conclusion)=($opt{start},$opt{finish}); |
|
255
|
18839
|
|
|
|
|
72011
|
$opt{attr}->push(); |
|
256
|
18839
|
|
66
|
|
|
110174
|
while($node&&($node ne $conclusion)) { |
|
257
|
167141
|
|
|
|
|
489869
|
push @res,[$tm,$node]; |
|
258
|
167141
|
|
|
|
|
288417
|
push @{$res[-1]},_nodeMessage($opt{attr},$tm+$opt{tmoffset},$node); |
|
|
167141
|
|
|
|
|
612283
|
|
|
259
|
167141
|
|
|
|
|
729079
|
$node->increment(\$tm,\$slack,\$buffer); |
|
260
|
167141
|
|
|
|
|
621877
|
$opt{attr}->push(); |
|
261
|
167141
|
|
|
|
|
573017
|
$opt{attr}->log($tm); |
|
262
|
167141
|
100
|
|
|
|
806783
|
if($tm-$tension{slack}*$slack+rand($tension{buffer}*$buffer+$tension{slack}*$slack)<=$opt{goal}) { |
|
|
|
100
|
|
|
|
|
|
|
263
|
147508
|
|
66
|
|
|
571475
|
$node=$node->nextrandom(not=>$conclusion,tm=>$tm,attr=>$opt{attr}{attr})//$node->nextrandom(tm=>$tm,attr=>$opt{attr}{attr}) } |
|
264
|
18729
|
|
|
|
|
35532
|
elsif($node->hasnext($conclusion)) { $node=$conclusion } |
|
265
|
904
|
|
33
|
|
|
3587
|
else { $node=$node->nextrandom(not=>$conclusion,tm=>$tm,attr=>$opt{attr}{attr})//$node->nextrandom(tm=>$tm,attr=>$opt{attr}{attr}) } |
|
266
|
167141
|
|
|
|
|
515563
|
$opt{attr}->pop(); |
|
267
|
|
|
|
|
|
|
} |
|
268
|
18839
|
50
|
33
|
|
|
81620
|
if($node&&($node eq $conclusion)) { |
|
269
|
18839
|
|
|
|
|
57830
|
push @res,[$tm,$conclusion]; |
|
270
|
18839
|
|
|
|
|
38245
|
push @{$res[-1]},_nodeMessage($opt{attr},$tm+$opt{tmoffset},$conclusion); |
|
|
18839
|
|
|
|
|
76592
|
|
|
271
|
18839
|
|
|
|
|
80324
|
$conclusion->increment(\$tm,\$slack,\$buffer); |
|
272
|
18839
|
|
|
|
|
36873
|
$node=undef; |
|
273
|
|
|
|
|
|
|
} |
|
274
|
18839
|
|
|
|
|
69778
|
$opt{attr}->pop(); |
|
275
|
|
|
|
|
|
|
return ( |
|
276
|
18839
|
|
|
|
|
160088
|
steps =>\@res, |
|
277
|
|
|
|
|
|
|
tm =>$tm, |
|
278
|
|
|
|
|
|
|
slack =>$slack, |
|
279
|
|
|
|
|
|
|
buffer=>$buffer, |
|
280
|
|
|
|
|
|
|
); |
|
281
|
|
|
|
|
|
|
} |
|
282
|
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
sub scheduler { |
|
284
|
18875
|
|
|
18875
|
0
|
107289
|
my (%opt)=@_; # goal,node,config |
|
285
|
18875
|
50
|
|
|
|
55829
|
if(!is_hashref($opt{node})) { die 'scheduler called with invalid node' } |
|
|
0
|
|
|
|
|
0
|
|
|
286
|
18875
|
50
|
|
|
|
54923
|
if(!defined($opt{node}{finish})) { die 'scheduler called with non-activity node' } |
|
|
0
|
|
|
|
|
0
|
|
|
287
|
18875
|
|
100
|
|
|
89383
|
$opt{retries}//=10; $opt{retries}--; |
|
|
18875
|
|
|
|
|
36984
|
|
|
288
|
18875
|
100
|
50
|
|
|
45299
|
if($opt{retries}<0) { die $opt{error}//'scheduling retries exhausted' } |
|
|
36
|
|
|
|
|
22925
|
|
|
289
|
|
|
|
|
|
|
# |
|
290
|
|
|
|
|
|
|
my %path=findpath( |
|
291
|
|
|
|
|
|
|
start =>$opt{node}, |
|
292
|
|
|
|
|
|
|
finish =>$opt{node}{finish}, |
|
293
|
|
|
|
|
|
|
goal =>$opt{goal}, |
|
294
|
|
|
|
|
|
|
retries =>$opt{retries}, |
|
295
|
|
|
|
|
|
|
backtracks=>2*$opt{retries}, |
|
296
|
|
|
|
|
|
|
attr =>$opt{attr}, |
|
297
|
|
|
|
|
|
|
tmoffset =>$opt{tmoffset}, |
|
298
|
|
|
|
|
|
|
tensionslack =>$opt{tensionslack} //$opt{tension}, |
|
299
|
|
|
|
|
|
|
tensionbuffer=>$opt{tensionbuffer}//$opt{tension}, |
|
300
|
18839
|
|
66
|
|
|
149452
|
); |
|
|
|
|
66
|
|
|
|
|
|
301
|
18839
|
50
|
0
|
|
|
56735
|
if($path{retry}) { return scheduler(%opt,retries=>$opt{retries},error=>$path{error}//'Retries exhausted') } |
|
|
0
|
|
|
|
|
0
|
|
|
302
|
18839
|
|
|
|
|
34714
|
my @res=@{$path{steps}}; |
|
|
18839
|
|
|
|
|
61872
|
|
|
303
|
18839
|
|
|
|
|
63648
|
my ($tm,$slack,$buffer)=@path{qw/tm slack buffer/}; |
|
304
|
18839
|
50
|
|
|
|
86014
|
if($res[-1][1] ne $opt{node}{finish}) { return scheduler(%opt,retries=>$opt{retries},error=>q|Didn't reach finish node|) } |
|
|
0
|
|
|
|
|
0
|
|
|
305
|
|
|
|
|
|
|
# |
|
306
|
18839
|
|
|
|
|
45715
|
my $excess=$tm-$opt{goal}; |
|
307
|
18839
|
100
|
|
|
|
52798
|
if(abs($excess)>0.5) { |
|
308
|
996
|
100
|
100
|
|
|
4088
|
if(($excess>0)&&($excess>$slack)) { return scheduler(%opt,retries=>$opt{retries},error=>"Excess exceeds slack ($excess>$slack)") } |
|
|
391
|
|
|
|
|
3529
|
|
|
309
|
605
|
100
|
100
|
|
|
2127
|
if(($excess<0)&&(-$excess>$buffer)) { return scheduler(%opt,retries=>$opt{retries},error=>'Shortage exceeds buffer ('.(-$excess).">$buffer)") } |
|
|
10
|
|
|
|
|
51
|
|
|
310
|
595
|
|
|
|
|
1438
|
my ($reduction,$rate)=(0); |
|
311
|
595
|
100
|
|
|
|
1316
|
if($excess>0) { $rate=$excess/$slack } |
|
|
438
|
|
|
|
|
897
|
|
|
312
|
157
|
|
|
|
|
370
|
else { $rate=$excess/$buffer } |
|
313
|
595
|
|
|
|
|
2278
|
foreach my $entry (@res[0..$#res]) { |
|
314
|
7170
|
|
|
|
|
12922
|
$$entry[0]=$$entry[0]-$reduction; |
|
315
|
7170
|
|
|
|
|
10328
|
my $dt; |
|
316
|
7170
|
100
|
|
|
|
12173
|
if($excess>0) { $dt=$rate*($$entry[1]->slack()) } |
|
|
5511
|
|
|
|
|
12122
|
|
|
317
|
1659
|
|
|
|
|
3297
|
else { $dt=$rate*($$entry[1]->buffer()) } |
|
318
|
7170
|
|
|
|
|
14252
|
$reduction+=$dt; |
|
319
|
|
|
|
|
|
|
} |
|
320
|
|
|
|
|
|
|
} |
|
321
|
18438
|
|
|
|
|
65926
|
foreach my $i (0..$#res) { |
|
322
|
173489
|
|
|
|
|
237932
|
my $dt; |
|
323
|
173489
|
100
|
|
|
|
300954
|
if($i<$#res) { $dt=$res[$i+1][0]-$res[$i][0] } |
|
|
155051
|
|
|
|
|
286715
|
|
|
324
|
18438
|
|
|
|
|
36841
|
else { $dt=$opt{goal}-$res[$i][0] } |
|
325
|
173489
|
|
|
|
|
379523
|
$res[$i][3]=$res[$i][4]=0; |
|
326
|
173489
|
|
50
|
|
|
375162
|
$dt-=$res[$i][1]{tmavg}//0; |
|
327
|
173489
|
100
|
|
|
|
324120
|
if($dt>0) { $res[$i][4]=$dt } |
|
|
1350
|
|
|
|
|
2347
|
|
|
328
|
172139
|
|
|
|
|
321116
|
else { $res[$i][3]=-$dt } |
|
329
|
|
|
|
|
|
|
} |
|
330
|
|
|
|
|
|
|
# |
|
331
|
|
|
|
|
|
|
# Message selection occurs in _nodeMessage during path construction. |
|
332
|
|
|
|
|
|
|
# Message attributes apply during path construction to permit node |
|
333
|
|
|
|
|
|
|
# filtering, but final materialization occurs after slack/buffer |
|
334
|
|
|
|
|
|
|
# adjustments have been made. |
|
335
|
|
|
|
|
|
|
# |
|
336
|
|
|
|
|
|
|
# Both nodes and their messages may change attributes, but node |
|
337
|
|
|
|
|
|
|
# attributes are applied first, so message attributes will "win" if |
|
338
|
|
|
|
|
|
|
# both contain 'set' operations. Documented in "/Precedence". |
|
339
|
|
|
|
|
|
|
# |
|
340
|
18438
|
|
|
|
|
50164
|
foreach my $i (0..$#res) { |
|
341
|
173489
|
|
|
|
|
319224
|
my $node=$res[$i][1]; |
|
342
|
173489
|
100
|
|
|
|
374618
|
if($$node{attributes}) { |
|
343
|
112701
|
|
|
|
|
172499
|
while(my ($k,$v)=each %{$$node{attributes}}) { |
|
|
307453
|
|
|
|
|
953767
|
|
|
344
|
194752
|
|
|
|
|
735518
|
$opt{attr}->change($k,%$v,tm=>$res[$i][0]+$opt{tmoffset}) } } |
|
345
|
173489
|
|
|
|
|
482518
|
my ($message,$msg)=$res[$i][2]->random(); |
|
346
|
173489
|
|
|
|
|
804013
|
$res[$i][1]=Schedule::Activity::Node->new(%$node,message=>$message); |
|
347
|
173489
|
50
|
|
|
|
463507
|
if(is_hashref($msg)) { while(my ($k,$v)=each %{$$msg{attributes}}) { |
|
|
173489
|
|
|
|
|
280243
|
|
|
|
173555
|
|
|
|
|
588212
|
|
|
348
|
66
|
|
|
|
|
276
|
$opt{attr}->change($k,%$v,tm=>$res[$i][0]+$opt{tmoffset}); |
|
349
|
|
|
|
|
|
|
} } |
|
350
|
|
|
|
|
|
|
} |
|
351
|
18438
|
|
|
|
|
168752
|
return @res; |
|
352
|
|
|
|
|
|
|
} |
|
353
|
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
sub goalScheduling { |
|
355
|
146
|
|
|
146
|
0
|
530
|
my ($self,%opt)=@_; |
|
356
|
146
|
|
|
|
|
290
|
my %goal=%{delete($opt{goal})}; |
|
|
146
|
|
|
|
|
728
|
|
|
357
|
146
|
50
|
|
|
|
587
|
if(!is_hashref($goal{attribute})) { return (error=>'goal{attribute} must be hash') } |
|
|
0
|
|
|
|
|
0
|
|
|
358
|
146
|
|
|
|
|
321
|
{ my $attr=$self->_attr(); |
|
|
146
|
|
|
|
|
475
|
|
|
359
|
146
|
|
|
|
|
452
|
my %validOp=map {$_=>undef} (qw/min max eq ne/); |
|
|
584
|
|
|
|
|
1717
|
|
|
360
|
146
|
|
|
|
|
453
|
my %valueOp=map {$_=>undef} (qw/eq ne/); |
|
|
292
|
|
|
|
|
829
|
|
|
361
|
146
|
|
|
|
|
325
|
foreach my $k (keys %{$goal{attribute}}) { |
|
|
146
|
|
|
|
|
625
|
|
|
362
|
176
|
50
|
|
|
|
704
|
if(!defined($$attr{attr}{$k})) { return (error=>"goal-requested attribute does not exist: $k") } |
|
|
0
|
|
|
|
|
0
|
|
|
363
|
176
|
50
|
|
|
|
628
|
if(!is_hashref($goal{attribute}{$k})) { return (error=>"goal attribute $k must be a hash") } |
|
|
0
|
|
|
|
|
0
|
|
|
364
|
176
|
50
|
|
|
|
635
|
if(!defined($goal{attribute}{$k}{op})) { return (error=>"missing operator in goal $k") } |
|
|
0
|
|
|
|
|
0
|
|
|
365
|
176
|
50
|
50
|
|
|
843
|
if(!exists($validOp{$goal{attribute}{$k}{op}//''})) { return (error=>"invalid operator in goal $k") } |
|
|
0
|
|
|
|
|
0
|
|
|
366
|
176
|
50
|
66
|
|
|
1247
|
if(exists($valueOp{$goal{attribute}{$k}{op}})&&!defined($goal{attribute}{$k}{value})) { return (error=>"missing value in goal $k") } |
|
|
0
|
|
|
|
|
0
|
|
|
367
|
|
|
|
|
|
|
} |
|
368
|
|
|
|
|
|
|
} |
|
369
|
146
|
|
50
|
|
|
533
|
my $cycles=$goal{cycles}//10; |
|
370
|
146
|
|
|
|
|
296
|
my %schedule; |
|
371
|
146
|
|
|
|
|
308
|
eval { %schedule=$self->schedule(%opt) }; |
|
|
146
|
|
|
|
|
584
|
|
|
372
|
146
|
|
|
|
|
394
|
my ($bestscore,%best); |
|
373
|
|
|
|
|
|
|
my $notemerge=sub { |
|
374
|
16048
|
100
|
|
16048
|
|
65069
|
if(!defined($schedule{annotations})) { return } |
|
|
13210
|
|
|
|
|
30500
|
|
|
375
|
2838
|
|
|
|
|
5391
|
my %seen; |
|
376
|
2838
|
|
|
|
|
4893
|
my @activities=@{$schedule{activities}}; |
|
|
2838
|
|
|
|
|
11399
|
|
|
377
|
2838
|
|
|
|
|
5759
|
foreach my $group (sort {$a cmp $b} keys(%{$schedule{annotations}})) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
2838
|
|
|
|
|
11515
|
|
|
378
|
2838
|
50
|
|
|
|
9490
|
if($seen{$group}) { next } |
|
|
0
|
|
|
|
|
0
|
|
|
379
|
2838
|
50
|
|
|
|
9538
|
if(!defined($schedule{annotations}{$group})) { next } |
|
|
0
|
|
|
|
|
0
|
|
|
380
|
2838
|
|
|
|
|
5839
|
push @activities,@{$schedule{annotations}{$group}{events}}; |
|
|
2838
|
|
|
|
|
8324
|
|
|
381
|
2838
|
|
|
|
|
8069
|
$seen{$group}=1; |
|
382
|
|
|
|
|
|
|
} |
|
383
|
2838
|
50
|
|
|
|
8796
|
if(%seen) { |
|
384
|
2838
|
|
|
|
|
12737
|
@activities=sort {$$a[0]<=>$$b[0]} @activities; |
|
|
115411
|
|
|
|
|
188856
|
|
|
385
|
2838
|
|
|
|
|
12402
|
%{$schedule{attributes}}=$self->computeAttributes(@activities); |
|
|
2838
|
|
|
|
|
21849
|
|
|
386
|
|
|
|
|
|
|
} |
|
387
|
146
|
|
|
|
|
1160
|
}; |
|
388
|
|
|
|
|
|
|
my $score=sub { |
|
389
|
16048
|
|
|
16048
|
|
34583
|
my $res=-1e6; |
|
390
|
16048
|
100
|
|
|
|
51203
|
if(!defined($schedule{attributes})) { return $res } |
|
|
1
|
|
|
|
|
3
|
|
|
391
|
16047
|
|
|
|
|
29136
|
$res=0; |
|
392
|
16047
|
|
|
|
|
27699
|
foreach my $k (keys %{$goal{attribute}}) { |
|
|
16047
|
|
|
|
|
72006
|
|
|
393
|
31195
|
|
|
|
|
50016
|
my %cmp=%{$goal{attribute}{$k}}; |
|
|
31195
|
|
|
|
|
113897
|
|
|
394
|
31195
|
|
50
|
|
|
57742
|
my %attr=%{$schedule{attributes}{$k}//{}}; |
|
|
31195
|
|
|
|
|
139798
|
|
|
395
|
31195
|
|
|
|
|
61391
|
my $avg; |
|
396
|
31195
|
50
|
33
|
|
|
104934
|
if($$self{PNA}&&($k=~/^\Q$$self{PNA}\E/)) { $avg=$attr{y}//0 } |
|
|
0
|
|
0
|
|
|
0
|
|
|
397
|
31195
|
|
50
|
|
|
81881
|
else { $avg=$attr{avg}//0 } |
|
398
|
31195
|
|
100
|
|
|
118069
|
my $weight=$cmp{weight}//1; |
|
399
|
31195
|
100
|
|
|
|
94664
|
if ($cmp{op} eq 'max') { $res+=$avg*$weight } |
|
|
26916
|
100
|
|
|
|
95214
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
400
|
466
|
|
|
|
|
2097
|
elsif($cmp{op} eq 'min') { $res-=$avg*$weight } |
|
401
|
1159
|
|
|
|
|
7770
|
elsif($cmp{op} eq 'eq') { $res-=abs($avg-$cmp{value})*$weight } |
|
402
|
2654
|
|
|
|
|
15634
|
elsif($cmp{op} eq 'ne') { $res+=abs($avg-$cmp{value})*$weight } |
|
403
|
|
|
|
|
|
|
elsif($cmp{op} eq 'XX') { |
|
404
|
0
|
|
0
|
|
|
0
|
my $xy=$attr{xy}//[]; |
|
405
|
0
|
|
|
|
|
0
|
foreach my $i (0..$#$xy-1) { |
|
406
|
|
|
|
|
|
|
$res-=($$xy[1+$i][0]-$$xy[$i][0])*abs(0.5*$$xy[$i][1]+0.5*$$xy[1+$i][1]-$cmp{value}) |
|
407
|
0
|
|
|
|
|
0
|
} |
|
408
|
|
|
|
|
|
|
} |
|
409
|
|
|
|
|
|
|
} |
|
410
|
16047
|
|
|
|
|
42196
|
return $res; |
|
411
|
146
|
|
|
|
|
1098
|
}; |
|
412
|
146
|
|
|
|
|
488
|
&$notemerge(); |
|
413
|
146
|
|
|
|
|
360
|
$bestscore=&$score(); %best=%schedule; |
|
|
146
|
|
|
|
|
728
|
|
|
414
|
146
|
|
|
|
|
448
|
my $lasterr; |
|
415
|
|
|
|
|
|
|
# |
|
416
|
146
|
|
|
|
|
504
|
while(--$cycles) { |
|
417
|
15921
|
|
|
|
|
33839
|
eval { %schedule=$self->schedule(%opt) }; |
|
|
15921
|
|
|
|
|
77561
|
|
|
418
|
15921
|
100
|
|
|
|
59487
|
if($@) { $lasterr=$@; next } |
|
|
19
|
|
|
|
|
51
|
|
|
|
19
|
|
|
|
|
78
|
|
|
419
|
15902
|
|
|
|
|
60256
|
&$notemerge(); |
|
420
|
15902
|
|
|
|
|
53033
|
my $s=&$score(); |
|
421
|
15902
|
100
|
|
|
|
69350
|
if($s>$bestscore) { |
|
422
|
525
|
|
|
|
|
1049
|
$bestscore=$s; |
|
423
|
525
|
|
|
|
|
38408
|
%best=%schedule; |
|
424
|
|
|
|
|
|
|
} |
|
425
|
|
|
|
|
|
|
} |
|
426
|
146
|
100
|
66
|
|
|
843
|
if(!%best&&$lasterr) { die $lasterr } |
|
|
1
|
|
|
|
|
52
|
|
|
427
|
145
|
|
|
|
|
20140
|
return %best; |
|
428
|
|
|
|
|
|
|
} |
|
429
|
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
sub incrementalScheduling { |
|
431
|
22
|
|
|
22
|
0
|
110
|
my ($self,%opt)=@_; |
|
432
|
22
|
|
|
|
|
57
|
my $activities=$opt{activities}; |
|
433
|
22
|
|
|
|
|
42
|
my $i=0; |
|
434
|
22
|
|
|
|
|
48
|
my %after=(); |
|
435
|
22
|
|
|
|
|
40
|
my %schedule; |
|
436
|
22
|
|
|
|
|
123
|
while($i<=$#$activities) { |
|
437
|
44
|
|
|
|
|
106
|
my $j=$i; |
|
438
|
44
|
|
66
|
|
|
303
|
while(($j<$#$activities)&&(!is_hashref($$activities[$j][2])||!defined($$activities[$j][2]{goal}))) { $j++ } |
|
|
22
|
|
100
|
|
|
173
|
|
|
439
|
44
|
|
|
|
|
84
|
my @acts; |
|
440
|
44
|
|
|
|
|
166
|
foreach my $activity (@$activities[$i..$j]) { |
|
441
|
66
|
|
|
|
|
226
|
push @acts,[@$activity[0,1]]; |
|
442
|
66
|
100
|
|
|
|
319
|
if(is_hashref($$activity[2])) { push @{$acts[-1]},{map {$_=>$$activity[2]{$_}} grep {$_ ne 'goal'} keys(%{$$activity[2]})} } |
|
|
22
|
|
|
|
|
48
|
|
|
|
22
|
|
|
|
|
72
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
22
|
|
|
|
|
130
|
|
|
|
22
|
|
|
|
|
83
|
|
|
443
|
|
|
|
|
|
|
} |
|
444
|
44
|
|
|
|
|
319
|
%schedule=$self->schedule(%opt,%after,activities=>\@acts,goal=>$$activities[$j][2]{goal}); |
|
445
|
44
|
100
|
|
|
|
335
|
if($i<$#$activities) { %after=(after=>{%schedule}) } |
|
|
22
|
|
|
|
|
121
|
|
|
446
|
44
|
|
|
|
|
289
|
$i=1+$j; |
|
447
|
|
|
|
|
|
|
} |
|
448
|
22
|
|
|
|
|
952
|
return %schedule; |
|
449
|
|
|
|
|
|
|
} |
|
450
|
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
sub schedule { |
|
452
|
17160
|
|
|
17160
|
0
|
126603
|
my ($self,%opt)=@_; |
|
453
|
17160
|
|
66
|
|
|
112134
|
my %check=$self->compile(unsafe=>$opt{unsafe}//$$self{unsafe}); |
|
454
|
17160
|
100
|
|
|
|
45391
|
if($check{error}) { return (error=>$check{error}) } |
|
|
4
|
|
|
|
|
23
|
|
|
455
|
17156
|
50
|
|
|
|
49358
|
if(!is_arrayref($opt{activities})) { return (error=>'Activities must be an array') } |
|
|
0
|
|
|
|
|
0
|
|
|
456
|
17156
|
100
|
|
18686
|
|
95733
|
if(any {is_hashref($$_[2])&&defined($$_[2]{goal})} @{$opt{activities}}) { return $self->incrementalScheduling(%opt) } |
|
|
18686
|
100
|
|
|
|
113651
|
|
|
|
17156
|
|
|
|
|
89230
|
|
|
|
22
|
|
|
|
|
157
|
|
|
457
|
17134
|
100
|
66
|
|
|
103850
|
if($opt{goal}&&%{$opt{goal}}) { return $self->goalScheduling(%opt) } |
|
|
146
|
|
|
|
|
782
|
|
|
|
146
|
|
|
|
|
687
|
|
|
458
|
16988
|
|
50
|
|
|
60709
|
my $tmoffset=$opt{tmoffset}//0; |
|
459
|
16988
|
|
|
|
|
68930
|
my %res=(stat=>{slack=>0,buffer=>0}); |
|
460
|
16988
|
100
|
|
|
|
51584
|
if($opt{after}) { |
|
461
|
24
|
|
|
|
|
252
|
delete($$self{attr}); |
|
462
|
24
|
|
|
|
|
79
|
my $attr=$self->_attr(); |
|
463
|
24
|
|
|
|
|
55
|
push @{$$attr{stack}},$opt{after}{_attr}; |
|
|
24
|
|
|
|
|
105
|
|
|
464
|
24
|
|
|
|
|
88
|
$attr->pop(); |
|
465
|
24
|
|
|
|
|
61
|
$tmoffset=$opt{after}{_tmmax}; |
|
466
|
24
|
|
|
|
|
47
|
%{$res{stat}}=(%{$res{stat}},%{$opt{after}{stat}}); |
|
|
24
|
|
|
|
|
122
|
|
|
|
24
|
|
|
|
|
71
|
|
|
|
24
|
|
|
|
|
85
|
|
|
467
|
|
|
|
|
|
|
} |
|
468
|
16988
|
|
|
|
|
47588
|
$self->_attr()->push(); |
|
469
|
16988
|
|
|
|
|
28907
|
foreach my $activity (@{$opt{activities}}) { |
|
|
16988
|
|
|
|
|
46127
|
|
|
470
|
18474
|
|
|
|
|
109163
|
foreach my $entry (scheduler(goal=>$$activity[0],node=>$$self{built}{node}{$$activity[1]},config=>$$self{built},attr=>$self->_attr(),tmoffset=>$tmoffset,tensionslack=>$opt{tensionslack},tensionbuffer=>$opt{tensionbuffer})) { |
|
471
|
173489
|
|
|
|
|
245590
|
push @{$res{activities}},[$$entry[0]+$tmoffset,@$entry[1..$#$entry]]; |
|
|
173489
|
|
|
|
|
648307
|
|
|
472
|
173489
|
|
|
|
|
343671
|
$res{stat}{slack}+=$$entry[3]; $res{stat}{buffer}+=$$entry[4]; |
|
|
173489
|
|
|
|
|
278899
|
|
|
473
|
173489
|
|
|
|
|
378261
|
$res{stat}{slackttl}+=$$entry[1]{tmavg}-$$entry[1]{tmmin}; |
|
474
|
173489
|
|
|
|
|
378739
|
$res{stat}{bufferttl}+=$$entry[1]{tmmax}-$$entry[1]{tmavg}; |
|
475
|
|
|
|
|
|
|
} |
|
476
|
18438
|
|
|
|
|
112135
|
$tmoffset+=$$activity[0]; |
|
477
|
|
|
|
|
|
|
} |
|
478
|
16952
|
|
|
|
|
71820
|
$self->_attr()->log($tmoffset); |
|
479
|
16952
|
100
|
|
|
|
54517
|
if($opt{after}) { unshift @{$res{activities}},@{$opt{after}{activities}} } |
|
|
24
|
|
|
|
|
61
|
|
|
|
24
|
|
|
|
|
60
|
|
|
|
24
|
|
|
|
|
183
|
|
|
480
|
16952
|
|
|
|
|
46125
|
%{$res{attributes}}=$self->_attr()->report(); |
|
|
16952
|
|
|
|
|
54504
|
|
|
481
|
16952
|
100
|
|
|
|
57016
|
if(!$opt{nonote}) { while(my ($group,$notes)=each %{$$self{config}{annotations}}) { |
|
|
16941
|
|
|
|
|
29034
|
|
|
|
19783
|
|
|
|
|
92347
|
|
|
482
|
2842
|
|
|
|
|
5733
|
my @schedule; |
|
483
|
2842
|
|
|
|
|
7527
|
foreach my $note (@$notes) { |
|
484
|
2843
|
|
|
|
|
16665
|
my $annotation=Schedule::Activity::Annotation->new(%$note); |
|
485
|
2843
|
|
|
|
|
6470
|
foreach my $note ($annotation->annotate(@{$res{activities}})) { |
|
|
2843
|
|
|
|
|
10636
|
|
|
486
|
14169
|
|
50
|
|
|
82177
|
my ($message,$mobj)=Schedule::Activity::Message->new(message=>$$note[1]{message},names=>$$self{config}{messages}//{})->random(); |
|
487
|
14169
|
|
50
|
|
|
45950
|
my %node=(%{$mobj//{}},message=>$message); |
|
|
14169
|
|
|
|
|
62568
|
|
|
488
|
14169
|
50
|
|
|
|
43656
|
if($$note[1]{annotations}) { $node{annotations}=$$note[1]{annotations} } |
|
|
0
|
|
|
|
|
0
|
|
|
489
|
14169
|
|
|
|
|
68995
|
push @schedule,[$$note[0],\%node,@$note[2..$#$note]]; |
|
490
|
|
|
|
|
|
|
} |
|
491
|
|
|
|
|
|
|
} |
|
492
|
2842
|
|
|
|
|
13527
|
@schedule=sort {$$a[0]<=>$$b[0]} @schedule; |
|
|
19912
|
|
|
|
|
37515
|
|
|
493
|
2842
|
|
|
|
|
9070
|
for(my $i=0;$i<$#schedule;$i++) { ## no critic (CStyleForLoops) |
|
494
|
11330
|
100
|
|
|
|
35955
|
if($schedule[$i+1][0]==$schedule[$i][0]) { |
|
495
|
1
|
|
|
|
|
3
|
splice(@schedule,$i+1,1); $i-- } } |
|
|
1
|
|
|
|
|
6
|
|
|
496
|
2842
|
|
|
|
|
14065
|
$res{annotations}{$group}{events}=\@schedule; |
|
497
|
|
|
|
|
|
|
} } |
|
498
|
16952
|
|
|
|
|
46280
|
$self->_attr()->push(); $res{_attr}=pop(@{$$self{attr}{stack}}); # store a copy in {_attr} |
|
|
16952
|
|
|
|
|
28599
|
|
|
|
16952
|
|
|
|
|
69357
|
|
|
499
|
16952
|
|
|
|
|
52373
|
$self->_attr()->pop(); |
|
500
|
16952
|
|
|
|
|
50036
|
$res{_tmmax}=$tmoffset; |
|
501
|
16952
|
|
|
|
|
924359
|
return %res; |
|
502
|
|
|
|
|
|
|
} |
|
503
|
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
sub computeAttributes { |
|
505
|
2839
|
|
|
2839
|
0
|
9796
|
my ($self,@activities)=@_; |
|
506
|
2839
|
|
|
|
|
9031
|
$self->_attr()->push(); |
|
507
|
2839
|
|
|
|
|
7607
|
$self->_attr()->reset(); |
|
508
|
2839
|
|
|
|
|
11188
|
foreach my $event (sort {$$a[0]<=>$$b[0]} @activities) { |
|
|
79762
|
|
|
|
|
128205
|
|
|
509
|
48224
|
|
|
|
|
96573
|
my ($tm,$node,$msg)=@$event; |
|
510
|
48224
|
100
|
|
|
|
106610
|
if($$node{attributes}) { |
|
511
|
17003
|
|
|
|
|
27030
|
while(my ($k,$v)=each %{$$node{attributes}}) { |
|
|
34006
|
|
|
|
|
106130
|
|
|
512
|
17003
|
|
|
|
|
40296
|
$self->_attr()->change($k,%$v,tm=>$tm) } } |
|
513
|
48224
|
100
|
|
|
|
103072
|
if(is_hashref($msg)) { while(my ($k,$v)=each %{$$msg{attributes}}) { |
|
|
34059
|
|
|
|
|
51411
|
|
|
|
34062
|
|
|
|
|
104858
|
|
|
514
|
3
|
|
|
|
|
5
|
$self->_attr()->change($k,%$v,tm=>$tm); |
|
515
|
|
|
|
|
|
|
} } |
|
516
|
|
|
|
|
|
|
} |
|
517
|
2839
|
|
|
|
|
7546
|
my %res=$self->_attr()->report(); |
|
518
|
2839
|
|
|
|
|
8203
|
$self->_attr()->pop(); |
|
519
|
2839
|
|
|
|
|
11857
|
return %res; |
|
520
|
|
|
|
|
|
|
} |
|
521
|
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
sub loadMarkdown { |
|
523
|
1
|
|
|
1
|
0
|
3641
|
my ($text)=@_; |
|
524
|
1
|
|
|
|
|
16
|
my $list=qr/(?:\d+\.|[-*])/; |
|
525
|
1
|
|
|
|
|
3
|
my (%config,@activities,@siblings,$activity,$tm); |
|
526
|
1
|
|
|
|
|
12
|
foreach my $line (split(/\n/,$text)) { |
|
527
|
14
|
100
|
|
|
|
30
|
if($line=~/^\s*$/) { next } |
|
|
2
|
|
|
|
|
2
|
|
|
528
|
12
|
100
|
|
|
|
184
|
if($line=~/^$list\s*(.*)$/) { |
|
|
|
50
|
|
|
|
|
|
|
529
|
3
|
|
|
|
|
6
|
$activity=$1; $tm=0; |
|
|
3
|
|
|
|
|
4
|
|
|
530
|
3
|
50
|
|
|
|
12
|
if($activity=~/(?.*?),\s*(?\d+)(?min|sec)\s*$/) { |
|
531
|
3
|
50
|
|
|
|
12
|
$activity=$+{name}; $tm=$+{tm}; if($+{unit} eq 'min') { $tm*=60 } } |
|
|
3
|
|
|
|
|
9
|
|
|
|
3
|
|
|
|
|
10
|
|
|
|
3
|
|
|
|
|
5
|
|
|
532
|
3
|
50
|
|
|
|
8
|
if(defined($config{node}{$activity})) { die "Name conflict: $activity" } |
|
|
0
|
|
|
|
|
0
|
|
|
533
|
3
|
|
|
|
|
5
|
push @activities,[$tm,$activity]; |
|
534
|
3
|
|
|
|
|
4
|
@siblings=(); |
|
535
|
3
|
|
|
|
|
12
|
$config{node}{$activity}={ |
|
536
|
|
|
|
|
|
|
message=>$activity, |
|
537
|
|
|
|
|
|
|
next=>[], |
|
538
|
|
|
|
|
|
|
tmavg=>0, |
|
539
|
|
|
|
|
|
|
finish=>"$activity, conclude", |
|
540
|
|
|
|
|
|
|
}; |
|
541
|
3
|
|
|
|
|
12
|
$config{node}{"$activity, conclude"}={tmavg=>0}; |
|
542
|
|
|
|
|
|
|
} |
|
543
|
|
|
|
|
|
|
elsif($line=~/^\s+$list\s*(.*)$/) { |
|
544
|
9
|
50
|
|
|
|
13
|
if(!$activity) { die 'Action without activity' } |
|
|
0
|
|
|
|
|
0
|
|
|
545
|
9
|
|
|
|
|
12
|
my $action=$1; $tm=60; |
|
|
9
|
|
|
|
|
32
|
|
|
546
|
9
|
50
|
|
|
|
24
|
if($action=~/(?.*?),\s*(?\d+)(?min|sec)\s*$/) { |
|
547
|
9
|
50
|
|
|
|
23
|
$action=$+{name}; $tm=$+{tm}; if($+{unit} eq 'min') { $tm*=60 } } |
|
|
9
|
|
|
|
|
20
|
|
|
|
9
|
|
|
|
|
24
|
|
|
|
9
|
|
|
|
|
11
|
|
|
548
|
9
|
|
|
|
|
16
|
$action="$activity, $action"; |
|
549
|
9
|
|
|
|
|
8
|
push @{$config{node}{$activity}{next}},$action; |
|
|
9
|
|
|
|
|
16
|
|
|
550
|
9
|
|
33
|
|
|
15
|
$activities[-1][0]||=$tm; |
|
551
|
9
|
50
|
|
|
|
16
|
if(defined($config{node}{$action})) { die "Name conflict: $action" } |
|
|
0
|
|
|
|
|
0
|
|
|
552
|
9
|
|
|
|
|
24
|
$config{node}{$action}={ |
|
553
|
|
|
|
|
|
|
message=>$action, |
|
554
|
|
|
|
|
|
|
next=>[@siblings,"$activity, conclude"], |
|
555
|
|
|
|
|
|
|
tmavg=>$tm, |
|
556
|
|
|
|
|
|
|
}; |
|
557
|
9
|
|
|
|
|
13
|
foreach my $sibling (@siblings) { push @{$config{node}{$sibling}{next}},$action } |
|
|
9
|
|
|
|
|
8
|
|
|
|
9
|
|
|
|
|
17
|
|
|
558
|
9
|
|
|
|
|
12
|
push @siblings,$action; |
|
559
|
|
|
|
|
|
|
} |
|
560
|
|
|
|
|
|
|
} |
|
561
|
1
|
|
|
|
|
9
|
return (configuration=>\%config,activities=>\@activities); |
|
562
|
|
|
|
|
|
|
} |
|
563
|
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
1; |
|
565
|
|
|
|
|
|
|
|
|
566
|
|
|
|
|
|
|
__END__ |