File Coverage

blib/lib/VS/RuleEngine/Runloop.pm
Criterion Covered Total %
statement 126 127 99.2
branch 27 32 84.3
condition 8 9 88.8
subroutine 18 18 100.0
pod 5 5 100.0
total 184 191 96.3


line stmt bran cond sub pod time code
1             package VS::RuleEngine::Runloop;
2              
3 32     32   40620 use strict;
  32         60  
  32         1481  
4 32     32   161 use warnings;
  32         54  
  32         997  
5              
6 32     32   169 use Carp qw(croak);
  32         52  
  32         1904  
7 32     32   231 use List::Util qw(first);
  32         54  
  32         3081  
8 32     32   234 use Scalar::Util qw(refaddr blessed);
  32         53  
  32         2077  
9              
10 32     32   727 use VS::RuleEngine::Constants;
  32         57  
  32         53879  
11              
12             my %Engine;
13             my %Runloop;
14             my %Initialized;
15              
16             sub new {
17 25     25 1 86 my ($pkg) = @_;
18 25         37 my $self = bless \do { my $v; }, $pkg;
  25         97  
19 25         114 $$self = refaddr $self;
20              
21 25         85 $Engine{$$self} = [];
22 25         59 $Runloop{$$self} = [];
23 25         49 $Initialized{$$self} = 0;
24              
25 25         66 return $self;
26             }
27              
28             sub DESTROY {
29 25     25   855 my $self = shift;
30 25         105 delete $Engine{$$self};
31 25         97 delete $Initialized{$$self};
32 25         319 delete $Runloop{$$self};
33             }
34              
35             sub add_engine {
36 30     30 1 4158 my ($self, $engine, $global) = @_;
37            
38 30 100       127 croak "Engine is undefined" if !defined $engine;
39 29 100 100     336 croak "Engine is not a VS::RuleEngine::Engine instance" if !(blessed $engine && $engine->isa("VS::RuleEngine::Engine"));
40            
41 27         61 my $engines = $Engine{$$self};
42 27 100   2   262 if (!first { $_->[0] == $engine } @$engines) {
  2         13  
43 26         103 push @$engines, [$engine, $global];
44             }
45             else {
46 1         10 croak "Engine already exists";
47             }
48             }
49              
50             sub init {
51 24     24 1 39 my ($self) = @_;
52            
53 24 50       70 return if $Initialized{$$self};
54            
55 24         34 for my $engine (@{$Engine{$$self}}) {
  24         64  
56 25         84 my $runloop = _mk_runloop($engine->[0], $engine->[1]);
57 25         73 $self->_register_runloop($runloop);
58             }
59             }
60              
61             sub _mk_runloop {
62 34     34   81 my $engine = shift;
63 34         43 my $global = shift;
64            
65 34         54 my @rules = @{$engine->_rule_order};
  34         818  
66              
67 48         141 my %action_map = map {
68 34         195 my $actions = $engine->_get_rule_actions($_);
69 48         84 $_ => [@{$actions}];
  48         205  
70             } @rules;
71            
72 48         147 my %rules = map {
73 34         71 my $rule = $engine->_get_rule($_);
74 48         171 my $rule_obj = $rule->instantiate($engine);
75 48         150 $_ => $rule_obj;
76             } @rules;
77            
78 43         139 my %actions = map {
79 34         149 my $action = $engine->_get_action($_);
80 43         175 my $action_obj = $action->instantiate($engine);
81 43         133 $_ => $action_obj;
82             } $engine->actions;
83            
84 34         65 my @pre_hooks = map { $_->instantiate($engine); } map { $engine->_get_hook($_) } @{$engine->_pre_hooks};
  7         26  
  7         42  
  34         809  
85 34         160 my @post_hooks = map { $_->instantiate($engine); } map { $engine->_get_hook($_) } @{$engine->_post_hooks};
  24         71  
  24         146  
  34         759  
86              
87 34         201 my $inputs = $engine->_input_handler;
88 34         209 my @outputs = map { $_->instantiate($engine); } map { $engine->_get_output($_) } sort $engine->outputs;
  3         15  
  3         13  
89              
90 34   66     264 $global = $global || VS::RuleEngine::Data->new();
91            
92 34         143 $inputs->set_global($global);
93              
94             my $runloop = sub {
95 34     34   127 $inputs->_clear();
96            
97 34         121 my $local = VS::RuleEngine::Data->new();
98            
99 34         119 $inputs->set_local($local);
100              
101 34         63 my $skip = 0;
102            
103             # Process all pre hooks
104 34         78 for my $hook (@pre_hooks) {
105 7         22 my $result = $hook->invoke($inputs, $global, $local);
106 7 50       4204 $skip = 1 if $result == KV_SKIP;
107 7 100       37 return KV_ABORT if $result == KV_ABORT;
108             }
109            
110             # Run rules until we find a matching rule
111 30 50       100 if (!$skip) {
112 30         36 my $match;
113            
114 30         64 PROCESS_RULES: for (@rules) {
115 37         71 my $rule = $rules{$_};
116 37         148 my $result = $rule->evaluate($inputs, $global, $local);
117 37 100       3411 $skip = 1, last PROCESS_RULES if $result == KV_SKIP;
118 36 100       122 $match = $_, last PROCESS_RULES if $result == KV_MATCH;
119             }
120            
121             # Run all actions
122 30 100 100     156 if (!$skip && $match) {
123 24         90 $local->set('VS::RuleEngine/matchingRule' => $match);
124 24         43 my $actions = $action_map{$match};
125 24         125 for (@$actions) {
126 23         775 my $action = $actions{$_};
127 23         87 $action->perform($inputs, $global, $local);
128             }
129             }
130             }
131            
132             # All outputs are always called if somethings decides not to skip
133 30 100       8173 if (!$skip) {
134 29         66 PROCESS_OUTPUT: for my $output (@outputs) {
135 2         8 $output->process($inputs, $global, $local);
136             }
137             }
138            
139             # Process all post hooks
140 30         3147 for my $hook (@post_hooks) {
141 24         101 my $result = $hook->invoke($inputs, $global, $local);
142 24 100       2899 return KV_ABORT if $result == KV_ABORT;
143             }
144            
145 9         74 return KV_CONTINUE;
146 34         249 };
147            
148 34         143 return $runloop;
149             }
150              
151             sub _register_runloop {
152 25     25   40 my ($self, $runloop) = @_;
153            
154 25         45 my $runloops = $Runloop{$$self};
155 25 50   1   150 if (!first { $_ == $runloop } @$runloops) {
  1         5  
156 25         159 push @$runloops, $runloop;
157             }
158             else {
159 0         0 croak "Runloop already registered";
160             }
161             }
162              
163             sub _unregister_runloop {
164 25     25   72 my ($self, $runloop) = @_;
165            
166 25         48 my $runloops = $Runloop{$$self};
167 25         45 my @runloops = grep { $_ != $runloop } @$runloops;
  26         95  
168 25         96 $Runloop{$$self} = \@runloops;
169            
170             }
171              
172             sub step {
173 48     48 1 142 my $self = shift;
174            
175 48         75 my $runloops = $Runloop{$$self};
176            
177 48 100       236 if (@$runloops) {
178 24         61 for my $runloop (@$runloops) {
179 25         51 my $status = $runloop->();
180 25 50       69 if ($status == KV_ABORT) {
181 25         72 $self->_unregister_runloop($runloop);
182             }
183             }
184             }
185            
186 48         539 return scalar @$runloops;
187             }
188              
189             sub run {
190 24     24 1 41 my $self = shift;
191            
192 24         98 $self->init();
193            
194 24         42 RUNLOOP: while(1) {
195 48 100       117 last RUNLOOP if $self->step() == 0;
196             }
197             }
198              
199             1;
200             __END__