File Coverage

blib/lib/MarpaX/Languages/C/AST/Callback.pm
Criterion Covered Total %
statement 322 395 81.5
branch 108 210 51.4
condition 30 86 34.8
subroutine 22 26 84.6
pod 0 9 0.0
total 482 726 66.3


line stmt bran cond sub pod time code
1 2     2   943 use strict;
  2         4  
  2         52  
2 2     2   7 use warnings FATAL => 'all';
  2         3  
  2         66  
3              
4             package MarpaX::Languages::C::AST::Callback;
5 2     2   6 use MarpaX::Languages::C::AST::Util qw/whoami/;
  2         2  
  2         71  
6 2     2   740 use MarpaX::Languages::C::AST::Callback::Method;
  2         4  
  2         94  
7              
8             use Class::Struct
9             #
10             # External attributes
11             #
12 2         9 log_prefix => '$', # Prepended to every log
13             hscratchpad => '%', # User working area
14             ascratchpad => '@', # User working area
15             sscratchpad => '$', # User working area
16             #
17             # Internal attributes
18             #
19             cb => '@', # List of methods.
20             cb_unregistered => '@', # List of unregistered methods, post-processed if done during fire()
21             topic_fired => '%', # Remember what are the eligible cb's topics.
22             topic_fired_data => '%', # Remember what are the eligible cb's topics data.
23             topic_fired_persistence => '%', # Remember what are the eligible cb's topics persistence.
24             topic_level => '@', # Topic levels
25             ncb => '$', # Number of methods.
26             prioritized_cb => '@', # Prioritized list of methods, for efficiency.
27             prioritized_cb_tofire => '@', # Remember what cb are eligible.
28             prioritized_cb_fired => '@', # Remember what cb were fired
29             arguments => '@', # List of arguments to the exec method.
30             firing => '$'
31 2     2   8 ;
  2         2  
32              
33             # ABSTRACT: Simple but powerful callback generic framework that depend on nothing else but core modules.
34              
35 2     2   3455 use Carp qw/croak/;
  2         4  
  2         6097  
36              
37             our $VERSION = '0.47'; # VERSION
38              
39              
40             sub _sort_by_option_priority_desc {
41 281     281   13034 return $b->option->priority <=> $a->option->priority
42             }
43              
44             sub _sort_by_numeric_desc {
45 0     0   0 return $b <=> $a
46             }
47              
48             sub register {
49 37     37 0 3389 my ($self, $cb) = @_;
50              
51 37 50       78 if (ref($cb) ne 'MarpaX::Languages::C::AST::Callback::Method') {
52 0         0 croak 'argument bust be a reference to a MarpaX::Languages::C::AST::Callback::Method object'
53             }
54             #
55             # Sanitize self
56             #
57 37 100       499 if (! defined($self->log_prefix)) {
58 1         18 $self->log_prefix('')
59             }
60              
61             #
62             # Sanitize cb
63             #
64 37 50 33     585 if (defined($cb->method) && ref($cb->method) ne 'ARRAY') {
65 0         0 croak 'method must be an ARRAY ref'
66             }
67 37 50       1097 if (defined($cb->method)) {
68 37 50       151 if (! @{$cb->method}) {
  37         403  
69 0         0 croak 'method is a reference to an empty array'
70             }
71 37 0 0     539 if (ref(($cb->method)->[0]) ne 'CODE' && (! ref($cb->method) && $cb->method eq 'auto')) {
      33        
72 0         0 croak 'method must be an ARRAY ref starting with a CODE reference, or the string \'auto\''
73             }
74             }
75 37 100       561 if (! defined($cb->method_mode)) {
76 22         322 $cb->method_mode('push')
77             }
78 37 50 66     537 if ($cb->method_mode ne 'push' && $cb->method_mode ne 'replace') {
79 0         0 croak 'method_mode must be \'push\' or \'replace\''
80             }
81             #
82             # Sanitize $cb->option
83             #
84 37 50       691 if (! defined($cb->option)) {
85 0         0 $cb->option(MarpaX::Languages::C::AST::Callback::Option->new())
86             }
87 37         562 my $option = $cb->option;
88 37         153 foreach (@{$option->condition}) {
  37         414  
89 38 50 33     376 if (! defined($_) || (! (ref($_) eq 'ARRAY')) || (! (ref($_->[0]) eq 'CODE' || (! ref($_->[0]) && $_->[0] eq 'auto')))) {
      66        
      33        
90 0         0 croak 'A condition is not an ARRAY reference, that must start with a CODE reference or the "auto" keyword"'
91             }
92             }
93              
94 37 50       439 if (! defined($option->conditionMode)) {
95 37         556 $option->conditionMode('and')
96             }
97 37 50       151 if (! grep {$option->conditionMode eq $_} qw/and or/) {
  74         923  
98 0         0 croak 'condition mode must be "and" or "or"'
99             }
100              
101 37 50       575 if (! defined($option->subscriptionMode)) {
102 37         564 $option->subscriptionMode('required')
103             }
104 37 50       140 if (! grep {$option->subscriptionMode eq $_} qw/required optional/) {
  74         916  
105 0         0 croak 'condition mode must be "and" or "or"'
106             }
107              
108 37 100       569 if (! defined($option->topic_persistence)) {
109 11         179 $option->topic_persistence('none')
110             }
111 37 50       174 if (! grep {$option->topic_persistence eq $_} qw/none any level/) {
  111         1473  
112 0         0 croak 'topic persistence mode must be "none", "any" or "level"'
113             }
114              
115 37 100       573 if (! defined($option->priority)) {
116 6         93 $option->priority(0)
117             }
118 37         541 my $priority = $option->priority;
119 37 50       209 if (! ("$priority" =~ /^[+-]?\d+$/)) {
120 0         0 croak 'priority must be a number'
121             }
122              
123 37 100       433 $self->ncb(0) if (! defined($self->ncb));
124 37         608 $self->cb($self->ncb, $cb);
125 37         706 $self->ncb($self->ncb + 1);
126 37         233 $self->prioritized_cb([sort _sort_by_option_priority_desc @{$self->cb}]);
  37         410  
127              
128             #
129             # Invalid cache if any
130             #
131 37         2297 $self->hscratchpad('_cache', 0);
132              
133             #
134             # We return the indice within Callback
135             #
136 37         615 return $self->ncb - 1
137             }
138              
139             sub _unregister {
140 20     20   83 my $self = shift;
141              
142 20         46 foreach (sort _sort_by_numeric_desc @_) {
143              
144 0         0 my $cb = $self->cb($_);
145 0 0       0 croak "Unknown callback indice $_" if (! defined($cb));
146              
147 0         0 splice(@{$self->cb}, $_, 1);
  0         0  
148 0         0 $self->ncb($self->ncb - 1);
149 0         0 $self->prioritized_cb([sort _sort_by_option_priority_desc @{$self->cb}])
  0         0  
150              
151             }
152              
153             return
154 20         22 }
155              
156             sub unregister {
157 0     0 0 0 my $self = shift;
158              
159 0   0     0 my $firing = $self->firing() || 0;
160 0 0       0 if (! $firing) {
161 0         0 $self->_unregister(@_)
162             } else {
163 0         0 push(@{$self->cb_unregistered}, @_)
  0         0  
164             }
165              
166             return
167 0         0 }
168              
169             sub exec {
170 20     20 0 19 my $self = shift;
171             #
172             # Remember our arguments, if the callback need it
173             #
174 20         21 my $argumentsp = \@_;
175 20         286 $self->arguments($argumentsp);
176             #
177             # Localize cache mode for faster lookup
178             #
179 20   50     376 my $cache = $self->hscratchpad('_cache') || 0;
180 20 50       398 local $__PACKAGE__::_cacheNcb = $cache ? $self->hscratchpad('_cacheNcb') : undef;
181 20 50       133 local $__PACKAGE__::_cacheArgumentsp = $cache ? $argumentsp : undef;
182 20 50       242 local $__PACKAGE__::_cachePrioritized_cbp = $cache ? $self->hscratchpad('_cachePrioritized_cb') : undef;
183 20 50       343 local $__PACKAGE__::_cachePrioritized_cb_tofirep = $cache ? $self->hscratchpad('_cachePrioritized_cb_tofire') : undef;
184 20 50       337 local $__PACKAGE__::_cachePrioritized_cb_firedp = $cache ? $self->hscratchpad('_cachePrioritized_cb_fired') : undef;
185 20 50       348 local $__PACKAGE__::_cacheOptionp = $cache ? $self->hscratchpad('_cacheOption') : undef;
186 20 50       335 local $__PACKAGE__::_cacheOptionConditionModep = $cache ? $self->hscratchpad('_cacheOptionConditionMode') : undef;
187 20 50       338 local $__PACKAGE__::_cacheOptionConditionp = $cache ? $self->hscratchpad('_cacheOptionCondition') : undef;
188 20 50       335 local $__PACKAGE__::_cacheOptionSubscriptionp = $cache ? $self->hscratchpad('_cacheOptionSubscription') : undef;
189 20 50       331 local $__PACKAGE__::_cacheOptionSubscriptionModep = $cache ? $self->hscratchpad('_cacheOptionSubscriptionMode') : undef;
190 20 50       351 local $__PACKAGE__::_cacheOptionTopicp = $cache ? $self->hscratchpad('_cacheOptionTopic') : undef;
191 20 50       331 local $__PACKAGE__::_cacheOptionTopic_persistencep = $cache ? $self->hscratchpad('_cacheOptionTopic_persistence') : undef;
192 20 50       333 local $__PACKAGE__::_cacheCbDescriptionp = $cache ? $self->hscratchpad('_cacheCbDescription') : undef;
193 20 50       329 local $__PACKAGE__::_cacheCbMethodp = $cache ? $self->hscratchpad('_cacheCbMethod') : undef;
194 20 50       335 local $__PACKAGE__::_cacheCbMethod_voidp = $cache ? $self->hscratchpad('_cacheCbMethod_void') : undef;
195              
196             #
197             # Do an inventory of eligible callbacks and topics
198             #
199 20         131 $self->_inventory_fire();
200             #
201             # Fire everything that is eligible
202             #
203 20         38 $self->_fire();
204             #
205             # And post-process eventual unregistrations
206             #
207 20         16 $self->_unregister(@{$self->cb_unregistered});
  20         246  
208 20         237 $self->cb_unregistered([]);
209              
210             return
211 20         200 }
212              
213             sub _inventory_condition_tofire {
214             # my $self = shift;
215              
216 20     20   17 my $nbNewTopics = 0;
217 20   33     36 my $ncb = $__PACKAGE__::_cacheNcb // $_[0]->ncb;
218 20   33     29 my $prioritized_cbp = $__PACKAGE__::_cachePrioritized_cbp // $_[0]->prioritized_cb;
219 20   33     30 my $prioritized_cb_tofirep = $__PACKAGE__::_cachePrioritized_cb_tofirep // $_[0]->prioritized_cb_tofire;
220 20   33     34 my $argumentsp = $__PACKAGE__::_cacheArgumentsp // $_[0]->arguments;
221 20         244 my $topic_firedp = $_[0]->topic_fired;
222 20         274 my $topic_fired_datap = $_[0]->topic_fired_data;
223 20         273 my $topic_fired_persistencep = $_[0]->topic_fired_persistence;
224              
225 20         96 foreach (my $i = 0; $i < $ncb; $i++) {
226 155         163 my $cb = $prioritized_cbp->[$i];
227 155 50       191 my $option = defined($__PACKAGE__::_cacheOptionp) ? $__PACKAGE__::_cacheOptionp->[$i] : $cb->option;
228 155 50       267 my $conditionMode = ((defined($__PACKAGE__::_cacheOptionConditionModep) ? $__PACKAGE__::_cacheOptionConditionModep->[$i] : $option->conditionMode) eq 'and') ? 1 : 0;
    50          
229              
230 155         114 my @condition = ();
231 155 50       212 my $description = defined($__PACKAGE__::_cacheCbDescriptionp) ? $__PACKAGE__::_cacheCbDescriptionp->[$i] : $cb->description;
232 155 50       166 foreach my $condition (defined($__PACKAGE__::_cacheOptionConditionp) ? @{$__PACKAGE__::_cacheOptionConditionp->[$i]} : @{$option->condition}) {
  155         194  
  0         0  
233 160         94 my ($coderef, @arguments) = @{$condition};
  160         178  
234 160 100       278 if (ref($coderef) eq 'CODE') {
    50          
235 45 100       96 push(@condition, &$coderef($cb, $_[0], $argumentsp, @arguments) ? 1 :0)
236             } elsif (defined($description)) {
237             #
238             # Per def condition is the string 'auto'
239             #
240 115 100       92 push(@condition, (grep {$_ eq $description} @{$argumentsp}) ? 1 :0)
  151         293  
  115         100  
241             }
242             }
243             #
244             ## Apply conditionMethod. If none, then the callback will never be
245             ## executed. Only the subscription methods can make it eligible.
246             #
247 155         116 my $condition = 0;
248 155 50       215 if (@condition) {
249 155         106 $condition = shift(@condition);
250 155 50       162 if ($conditionMode) {
251             #
252             # Per def, this is 'and'
253             #
254 155         161 foreach (@condition) {
255 5   100     18 $condition &&= $_
256             }
257             } else {
258             #
259             # Per def, this is 'or'
260             #
261 0         0 foreach (@condition) {
262 0   0     0 $condition ||= $_
263             }
264             }
265             }
266 155 100       154 if ($condition) {
267 28         28 $prioritized_cb_tofirep->[$i] = 1;
268             #
269             # Initialize the associated topics if needed
270             #
271 28 50       21 foreach my $topic (keys %{defined($__PACKAGE__::_cacheOptionTopicp) ? $__PACKAGE__::_cacheOptionTopicp->[$i] : $option->topic}) {
  28         88  
272 29 50       92 next if (! defined(defined($__PACKAGE__::_cacheOptionTopicp) ? $__PACKAGE__::_cacheOptionTopicp->[$i]->{$topic} : $option->topic($topic)));
    50          
273 29 50       61 next if (! (defined($__PACKAGE__::_cacheOptionTopicp) ? $__PACKAGE__::_cacheOptionTopicp->[$i]->{$topic} : $option->topic($topic)));
    50          
274 29 100       62 if (! defined($topic_firedp->{$topic})) {
275 17         22 $topic_firedp->{$topic} = 1;
276 17 50       29 $topic_fired_persistencep->{$topic} = defined($__PACKAGE__::_cacheOptionTopic_persistencep) ? $__PACKAGE__::_cacheOptionTopic_persistencep->[$i] : $option->topic_persistence;
277 17 50       34 if (! defined($topic_fired_datap->{$topic})) {
278 17         21 $topic_fired_datap->{$topic} = [];
279 17         38 ++$nbNewTopics
280             }
281             }
282             }
283             } else {
284 127 100       356 if (@condition) {
285 4         10 $prioritized_cb_tofirep->[$i] = -1
286             }
287             }
288             }
289              
290 20         27 return $nbNewTopics
291             }
292              
293             #
294             # Class::Struct is great but introduces overhead
295             # The most critical accesses, identified using
296             # Devel::NYTProf are cached here.
297             #
298             sub cache {
299             # my $self = shift;
300              
301 5     5 0 9 my @cacheOption = ();
302 5         5 my @cacheOptionConditionMode = ();
303 5         7 my @cacheOptionCondition = ();
304 5         6 my @cacheOptionSubscription = ();
305 5         6 my @cacheOptionSubscriptionMode = ();
306 5         3 my @cacheOptionTopic = ();
307 5         6 my @cacheOptionTopic_persistence = ();
308 5         6 my @cacheCbDescription = ();
309 5         4 my @cacheCbMethod = ();
310 5         5 my @cacheCbMethod_void = ();
311 5         67 my $prioritized_cbp = $_[0]->prioritized_cb;
312 5         71 my $prioritized_cb_tofirep = $_[0]->prioritized_cb_tofire;
313 5         71 my $prioritized_cb_firedp = $_[0]->prioritized_cb_fired;
314 5         71 my $ncb = $_[0]->ncb;
315 5         29 foreach (my $i = 0; $i < $ncb; $i++) {
316 37         169 my $cb = $prioritized_cbp->[$i];
317 37         407 my $option = $cb->option;
318 37         170 push(@cacheOption, $option);
319 37         426 push(@cacheOptionConditionMode, $option->conditionMode);
320 37         530 push(@cacheOptionCondition, $option->condition);
321 37         498 push(@cacheOptionSubscription, $option->subscription);
322 37         500 push(@cacheOptionSubscriptionMode, $option->subscriptionMode);
323 37         540 push(@cacheOptionTopic, $option->topic);
324 37         490 push(@cacheOptionTopic_persistence, $option->topic_persistence);
325 37         522 push(@cacheCbDescription, $cb->description);
326 37         522 push(@cacheCbMethod, $cb->method);
327 37         501 push(@cacheCbMethod_void, $cb->method_void);
328             }
329 5         85 $_[0]->hscratchpad('_cacheNcb', $ncb);
330 5         88 $_[0]->hscratchpad('_cachePrioritized_cb', $prioritized_cbp);
331 5         82 $_[0]->hscratchpad('_cachePrioritized_cb_tofire', $prioritized_cb_tofirep);
332 5         80 $_[0]->hscratchpad('_cachePrioritized_cb_fired', $prioritized_cb_firedp);
333 5         87 $_[0]->hscratchpad('_cacheOption', \@cacheOption);
334 5         80 $_[0]->hscratchpad('_cacheOptionConditionMode', \@cacheOptionConditionMode);
335 5         81 $_[0]->hscratchpad('_cacheOptionCondition', \@cacheOptionCondition);
336 5         82 $_[0]->hscratchpad('_cacheOptionSubscription', \@cacheOptionSubscription);
337 5         82 $_[0]->hscratchpad('_cacheOptionSubscriptionMode', \@cacheOptionSubscriptionMode);
338 5         81 $_[0]->hscratchpad('_cacheOptionTopic', \@cacheOptionTopic);
339 5         82 $_[0]->hscratchpad('_cacheOptionTopic_persistence', \@cacheOptionTopic_persistence);
340 5         81 $_[0]->hscratchpad('_cacheCbDescription', \@cacheCbDescription);
341 5         86 $_[0]->hscratchpad('_cacheCbMethod', \@cacheCbMethod);
342 5         80 $_[0]->hscratchpad('_cacheCbMethod_void', \@cacheCbMethod_void);
343              
344 5         98 $_[0]->hscratchpad('_cache', 1);
345              
346             return
347 5         43 }
348              
349             sub _fire {
350             # my $self = shift;
351              
352 32     32   430 $_[0]->firing(1);
353              
354             #
355             # Make sure the raised topic data always exist.
356             # It is very important that this routine is safe v.s. any on-the-fly registration
357             # or unregistration. Thus all dependencies are expressed in the beginning.
358             # This mean that nay on-the-flu registration/unregistration will happend at NEXT round.
359             #
360 32   33     148 my $ncb = $__PACKAGE__::_cacheNcb // $_[0]->ncb;
361 32   33     53 my $prioritized_cb_tofirep = $__PACKAGE__::_cachePrioritized_cb_tofirep // $_[0]->prioritized_cb_tofire;
362 32   33     50 my $prioritized_cb_firedp = $__PACKAGE__::_cachePrioritized_cb_firedp // $_[0]->prioritized_cb_fired;
363 32   33     44 my $prioritized_cbp = $__PACKAGE__::_cachePrioritized_cbp // $_[0]->prioritized_cb;
364 32   33     49 my $argumentsp = $__PACKAGE__::_cacheArgumentsp // $_[0]->arguments;
365 32         376 my $topic_fired_datap = $_[0]->topic_fired_data;
366              
367 32         147 foreach (my $i = 0; $i < $ncb; $i++) {
368 237 100       291 if ($prioritized_cb_tofirep->[$i] <= 0) {
369             # -1: Condition KO
370             # -2: Condition NA and Subscription NA
371             # -3: Subscription KO
372             next
373 194         254 }
374 43         41 my $cb = $prioritized_cbp->[$i];
375 43 100       66 if ($prioritized_cb_firedp->[$i]) {
376             # already fired
377             next
378 15         23 }
379             #
380             # Fire the callback (if there is a method)
381             #
382 28         25 $prioritized_cb_firedp->[$i] = 1;
383 28 50       50 my $method = defined($__PACKAGE__::_cacheCbMethodp) ? $__PACKAGE__::_cacheCbMethodp->[$i] : $cb->method;
384 28 50       39 if (defined($method)) {
385 28         27 my @rc;
386 28 50       49 if (ref($method) eq 'ARRAY') {
387 28         20 my ($method, @arguments) = @{$method};
  28         56  
388 28 50       40 if (ref($method) eq 'CODE') {
389 28         80 @rc = &$method($cb, $_[0], $argumentsp, @arguments)
390             } else {
391             #
392             # Per def method is the string 'auto'
393             #
394 0   0     0 @rc = $topic_fired_datap->{$cb->description} || []
395             }
396             }
397             #
398             # Push result to data attached to every topic of this callback
399             #
400 28         372 my $option = $cb->option;
401 28 50       161 my $method_void = defined($__PACKAGE__::_cacheCbMethod_voidp) ? $__PACKAGE__::_cacheCbMethod_voidp->[$i] : $cb->method_void;
402 28 100       51 if (! $method_void) {
403 27 50       20 foreach my $topic (keys %{defined($__PACKAGE__::_cacheOptionTopicp) ? $__PACKAGE__::_cacheOptionTopicp->[$i] : $option->topic}) {
  27         76  
404 27 50       63 next if (! defined(defined($__PACKAGE__::_cacheOptionTopicp) ? $__PACKAGE__::_cacheOptionTopicp->[$i]->{$topic} : $option->topic($topic)));
    50          
405 27 50       67 next if ((defined($__PACKAGE__::_cacheOptionTopicp) ? $__PACKAGE__::_cacheOptionTopicp->[$i]->{$topic} : $option->topic($topic)) != 1);
    100          
406 21   50     38 my $topic_fired_data = $topic_fired_datap->{$topic} || [];
407 21 50       257 if (ref($cb->method) eq 'ARRAY') {
408 21 100       316 if ($cb->method_mode eq 'push') {
409 7         32 push(@{$topic_fired_data}, @rc)
  7         11  
410             } else {
411 14         66 @{$topic_fired_data} = @rc
  14         17  
412             }
413             } else {
414 0 0       0 if ($cb->method_mode eq 'push') {
415 0         0 push(@{$topic_fired_data}, @rc)
  0         0  
416             } else {
417 0         0 @{$topic_fired_data} = @rc
  0         0  
418             }
419             }
420 21         53 $topic_fired_datap->{$topic} = $topic_fired_data
421             }
422             }
423             }
424             }
425              
426 32         407 $_[0]->firing(0);
427              
428             return
429 32         126 }
430              
431             sub topic_level_fired_data {
432 1     1 0 3 my ($self, $topic, $level) = (shift, shift, shift);
433              
434 1   50     3 $level //= 0;
435              
436             #
437             # Level MUST be 0 for current or a negative number
438             #
439 1         2 $level = int($level);
440 1 50       3 if ($level > 0) {
441 0         0 croak 'int(level) must be 0 or a negative number'
442             }
443 1 50       2 if ($level == 0) {
444 0 0       0 if (@_) {
445 0         0 $self->topic_fired_data($topic, shift)
446             }
447 0         0 return $self->topic_fired_data($topic);
448             } else {
449 1         3 my ($old_topic_firedp, $old_topic_persistencep, $old_topic_datap) = @{$self->topic_level($level)};
  1         16  
450 1 50       13 if (@_) {
451 0         0 $old_topic_datap->{$topic} = shift
452             }
453 1         4 return $old_topic_datap->{$topic}
454             }
455             }
456              
457             sub _inventory_initialize_topic {
458             # my ($self) = @_;
459              
460             #
461             # For topics, we want to keep those that have a persistence of 'level' or 'any'
462             #
463 20     20   539 my $topic_firedp = $_[0]->topic_fired;
464 20         309 my $topic_fired_datap = $_[0]->topic_fired_data;
465 20         285 my $topic_fired_persistencep = $_[0]->topic_fired_persistence;
466              
467 20         77 my $keep_topic_firedp = {};
468 20         18 my $keep_topic_fired_persistencep = {};
469 20         20 my $keep_topic_fired_datap = {};
470              
471 20         18 foreach my $topic (keys %{$topic_firedp}) {
  20         41  
472 26         29 my $persistence = $topic_fired_persistencep->{$topic};
473 26 50       24 if (grep {$_ eq $persistence} qw/any level/) {
  52         75  
474 26         29 $keep_topic_firedp->{$topic} = $topic_firedp->{$topic};
475 26         26 $keep_topic_fired_persistencep->{$topic} = $topic_fired_persistencep->{$topic};
476 26         36 $keep_topic_fired_datap->{$topic} = $topic_fired_datap->{$topic}
477             }
478             }
479 20         247 $_[0]->topic_fired($keep_topic_firedp);
480 20         334 $_[0]->topic_fired_persistence($keep_topic_fired_persistencep);
481 20         346 $_[0]->topic_fired_data($keep_topic_fired_datap);
482              
483             return
484 20         141 }
485              
486             sub _inventory_initialize_tofire {
487             # my ($self) = @_;
488              
489 20   33 20   38 my $ncb = $__PACKAGE__::_cacheNcb // $_[0]->ncb;
490 20         35 my $prioritized_cb_tofirep = [ (0) x $ncb ];
491 20         249 $_[0]->prioritized_cb_tofire($prioritized_cb_tofirep);
492 20 50       138 if (defined($__PACKAGE__::_cachePrioritized_cb_tofirep)) {
493 20         21 $__PACKAGE__::_cachePrioritized_cb_tofirep = $prioritized_cb_tofirep
494             }
495             return
496 20         17 }
497              
498             sub _inventory_initialize_fired {
499             # my ($self) = @_;
500              
501 20   33 20   31 my $ncb = $__PACKAGE__::_cacheNcb // $_[0]->ncb;
502 20         31 my $prioritized_cb_firedp = [ (0) x $ncb ];
503 20         240 $_[0]->prioritized_cb_fired($prioritized_cb_firedp);
504 20 50       142 if (defined($__PACKAGE__::_cachePrioritized_cb_firedp)) {
505 20         19 $__PACKAGE__::_cachePrioritized_cb_firedp = $prioritized_cb_firedp
506             }
507             return
508 20         22 }
509              
510             sub _inventory_fire {
511             # my ($self) = @_;
512              
513             #
514             # Inventory
515             #
516 20     20   35 $_[0]->_inventory_initialize_topic();
517 20         35 $_[0]->_inventory();
518             return
519 20         19 }
520              
521             sub _inventory {
522             # my ($self) = @_;
523              
524 20     20   20 my $nbTopicsCreated = 0;
525 20         17 do {
526 20         29 $_[0]->_inventory_initialize_tofire();
527 20         33 $_[0]->_inventory_initialize_fired();
528 20         34 $nbTopicsCreated += $_[0]->_inventory_condition_tofire();
529 20         40 $nbTopicsCreated += $_[0]->_inventory_subscription_tofire();
530 20 100       40 if ($nbTopicsCreated > 0) {
531 12         32 $_[0]->_fire();
532 12         27 $nbTopicsCreated = 0
533             }
534             } while ($nbTopicsCreated > 0);
535              
536             return
537 20         18 }
538              
539             sub _inventory_subscription_tofire {
540             # my ($self) = @_;
541             #
542             # This is a loop because when a new callback is eligible there might be new topics
543             #
544 20     20   19 my $nbNewTopics = 0;
545 20         19 my $nbSubscriptionOK = 0;
546 20   33     35 my $ncb = $__PACKAGE__::_cacheNcb // $_[0]->ncb;
547 20         321 my $prioritized_cbp = $_[0]->prioritized_cb;
548 20         289 my $prioritized_cb_tofirep = $_[0]->prioritized_cb_tofire;
549 20         280 my $topic_firedp = $_[0]->topic_fired;
550 20         273 my $topic_fired_datap = $_[0]->topic_fired_data;
551 20         296 my $topic_fired_persistencep = $_[0]->topic_fired_persistence;
552 20         68 my @keys_topic_fired = keys %{$topic_firedp};
  20         53  
553              
554 20         41 foreach (my $i = 0; $i < $ncb; $i++) {
555 155         120 my $cb = $prioritized_cbp->[$i];
556 155 50       207 my $option = defined($__PACKAGE__::_cacheOptionp) ? $__PACKAGE__::_cacheOptionp->[$i] : $cb->option;
557             #
558             # Here the values can be:
559             # -1: condition KO
560             # 0: no condition applied
561             # 1: condition OK
562 155 100       202 next if ($prioritized_cb_tofirep->[$i] < 0);
563              
564 151         124 my %subscribed = ();
565 151         98 my $nbSubscription = 0;
566 151 50       103 foreach my $subscription (keys %{defined($__PACKAGE__::_cacheOptionSubscriptionp) ? $__PACKAGE__::_cacheOptionSubscriptionp->[$i] : $option->subscription}) {
  151         268  
567 0 0       0 next if (! defined(defined($__PACKAGE__::_cacheOptionSubscriptionp) ? $__PACKAGE__::_cacheOptionSubscriptionp->[$i]->{$subscription} : $option->subscription($subscription)));
    0          
568 0 0       0 next if (! (defined($__PACKAGE__::_cacheOptionSubscriptionp) ? $__PACKAGE__::_cacheOptionSubscriptionp->[$i]->{$subscription} : $option->subscription($subscription)));
    0          
569 0         0 ++$nbSubscription;
570 0 0       0 if (ref($subscription) eq 'Regexp') {
571 0         0 foreach (@keys_topic_fired) {
572 0 0       0 if ($_ =~ $subscription) {
573 0         0 $subscribed{$_} = 1
574             }
575             }
576             } else {
577 0         0 foreach (@keys_topic_fired) {
578 0 0       0 if ("$_" eq "$subscription") {
579 0         0 $subscribed{$_} = 1
580             }
581             }
582             }
583             }
584              
585 151 100 66     399 if ($prioritized_cb_tofirep->[$i] == 0 && ! %subscribed) {
586             #
587             # no condition was setted and no subscription is raised
588             #
589 123         83 $prioritized_cb_tofirep->[$i] = -2;
590             next
591 123         212 }
592              
593 28 0 33     51 if ($nbSubscription > 0 && (defined($__PACKAGE__::_cacheOptionSubscriptionModep) ? $__PACKAGE__::_cacheOptionSubscriptionModep->[$i] : $option->subscriptionMode) eq 'required' && $nbSubscription != keys %subscribed) {
    0 33        
594             #
595             # There are active subscription not raised, and subscriptionMode is 'required'
596             #
597 0         0 $prioritized_cb_tofirep->[$i] = -3;
598             next
599 0         0 }
600              
601 28 50       49 if ($prioritized_cb_tofirep->[$i] == 0) {
602             #
603             # There must have been topic subscription being raised
604             #
605 0         0 $prioritized_cb_tofirep->[$i] = 1;
606 0         0 ++$nbSubscriptionOK
607             }
608              
609 28 50       14 foreach my $topic (keys %{defined($__PACKAGE__::_cacheOptionTopicp) ? $__PACKAGE__::_cacheOptionTopicp->[$i] : $option->topic}) {
  28         72  
610 29 50       69 next if (! defined(defined($__PACKAGE__::_cacheOptionTopicp) ? $__PACKAGE__::_cacheOptionTopicp->[$i]->{$topic} : $option->topic($topic)));
    50          
611 29 50       61 next if (! (defined($__PACKAGE__::_cacheOptionTopicp) ? $__PACKAGE__::_cacheOptionTopicp->[$i]->{$topic} : $option->topic($topic)));
    50          
612 29 50       73 if (! defined($topic_firedp->{$topic})) {
613 0         0 $topic_firedp->{$topic} = 1;
614 0         0 $topic_fired_persistencep->{$topic} = $option->topic_persistence;
615 0         0 $topic_fired_datap->{$topic} = [];
616 0         0 ++$nbNewTopics
617             }
618             }
619             }
620              
621 20         32 return $nbNewTopics
622             }
623              
624             sub currentTopicLevel {
625             # my ($self) = @_;
626              
627 0     0 0 0 return scalar(@{$_[0]->topic_level})
  0         0  
628             }
629              
630             sub pushTopicLevel {
631             # my ($self) = @_;
632              
633 5     5 0 61 my $topic_firedp = $_[0]->topic_fired;
634 5         68 my $topic_fired_datap = $_[0]->topic_fired_data;
635 5         96 my $topic_fired_persistencep = $_[0]->topic_fired_persistence;
636              
637             #
638             # Since we are going to replace the entire hash, keeping a copy of them
639             # in @{$self->topic_level} is enough
640             #
641 5         18 push(@{$_[0]->topic_level}, [ $topic_firedp, $topic_fired_persistencep, $topic_fired_datap ]);
  5         58  
642             #
643             # We remove from current topics those that do not have the 'any' persistence
644             #
645 5         26 my $new_topic_firedp = {};
646 5         4 my $new_topic_fired_persistencep = {};
647 5         6 my $new_topic_fired_datap = {};
648 5         6 foreach my $topic (keys %{$topic_firedp}) {
  5         9  
649 14         9 my $persistence = $topic_fired_persistencep->{$topic};
650 14 100       14 if (grep {$_ eq $persistence} qw/any/) {
  14         35  
651 2         3 $new_topic_firedp->{$topic} = $topic_firedp->{$topic};
652 2         4 $new_topic_fired_persistencep->{$topic} = $topic_fired_persistencep->{$topic};
653 2         4 $new_topic_fired_datap->{$topic} = $topic_fired_datap->{$topic}
654             }
655             }
656             #
657             # These lines guarantee that what we have pushed will not be touched using $self->topic_fired() etc... accessors
658             # because we replace the entire hash.
659             #
660 5         64 $_[0]->topic_fired($new_topic_firedp);
661 5         83 $_[0]->topic_fired_persistence($new_topic_fired_persistencep);
662 5         78 $_[0]->topic_fired_data($new_topic_fired_datap);
663              
664             return
665              
666 5         36 }
667              
668             sub popTopicLevel {
669             # my ($self) = @_;
670              
671             #
672             # We pop current topics and their persistence from the topic_level
673             #
674 5     5 0 4 my ($old_topic_firedp, $old_topic_persistencep, $old_topic_datap) = @{$_[0]->topic_level(-1)};
  5         60  
675 5         35 pop(@{$_[0]->topic_level});
  5         57  
676 5         72 $_[0]->topic_fired($old_topic_firedp);
677 5         81 $_[0]->topic_fired_persistence($old_topic_persistencep);
678 5         83 $_[0]->topic_fired_data($old_topic_datap);
679              
680             return
681              
682 5         34 }
683              
684             sub reset_topic_fired_data {
685             # my ($self, $topic, $value, $level) = @_;
686              
687             # $value //= [];
688 0   0 0 0   $_[2] //= [];
689             # $level //= 0;
690 0   0       $_[3] //= 0;
691              
692 0 0         if (ref($_[2]) ne 'ARRAY') {
693 0           croak 'Topic fired data must be an ARRAY reference'
694             }
695              
696             #
697             # Level MUST be 0 or a negative number
698             # It is okay if $_[2] is undef
699             #
700 0           $_[3] = int($_[3]);
701 0 0         if ($_[3] > 0) {
702 0           croak 'int(level) must be 0 or a negative number'
703             }
704 0 0         if ($_[3] == 0) {
705 0           $_[0]->topic_fired_data($_[1], $_[2]);
706             } else {
707 0           my ($old_topic_fired, $old_topic_persistence, $old_topic_data) = @{$_[0]->topic_level($_[3])};
  0            
708 0           $old_topic_data->{$_[1]} = $_[2];
709             }
710              
711             return
712 0           }
713              
714             1;
715              
716             __END__