File Coverage

blib/lib/Bot/Backbone/Bot.pm
Criterion Covered Total %
statement 45 52 86.5
branch 5 8 62.5
condition n/a
subroutine 9 10 90.0
pod 5 5 100.0
total 64 75 85.3


line stmt bran cond sub pod time code
1             package Bot::Backbone::Bot;
2             $Bot::Backbone::Bot::VERSION = '0.161950';
3 4     4   5325 use v5.10;
  4         12  
4 4     4   18 use Moose;
  4         5  
  4         23  
5              
6 4     4   16873 use Bot::Backbone::Types qw( EventLoop ServiceList );
  4         5  
  4         35  
7 4     4   6736 use POE qw( Loop::AnyEvent );
  4         94010  
  4         27  
8              
9             # ABSTRACT: Provides backbone services to your bot
10              
11              
12             has event_loop => (
13             is => 'ro',
14             isa => EventLoop,
15             required => 1,
16             default => 'POE::Kernel',
17             );
18              
19              
20             has services => (
21             is => 'ro',
22             isa => ServiceList,
23             required => 1,
24             default => sub { +{} },
25             traits => [ 'Hash' ],
26             handles => {
27             add_service => 'set',
28             service_names => 'keys',
29             list_services => 'values',
30             destroy_services => 'clear',
31             has_service => 'defined',
32             get_service => 'get',
33             },
34             );
35              
36              
37             has initialized_services => (
38             is => 'ro',
39             isa => 'HashRef[Bool]',
40             required => 1,
41             default => sub { +{} },
42             );
43              
44              
45 0     0 1 0 sub bot { $_[0] }
46              
47              
48             sub _ordered_services {
49 10     10   16 my $self = shift;
50              
51 10         17 my %forest;
52 10         24 for my $pair ($self->meta->services_kv) {
53 24         34 my ($name, $config) = @$pair;
54              
55 24 50       105 if ($config->{service}->does('Bot::Backbone::Service::Role::ChatConsumer')) {
56 0         0 $forest{ $name } = $config->{chat};
57             }
58             else {
59 24         4371 $forest{ $name } = undef;
60             }
61             }
62              
63 10         22 my @names;
64 10         24 for my $name (keys %forest) {
65 24 50       29 if (defined $forest{ $name }) {
66 0         0 my $depth = 1;
67 0         0 my $next_name = $forest{ $name };
68 0         0 while (defined($next_name = $forest{ $next_name })) {
69 0         0 $depth++;
70             }
71              
72 0         0 $forest{ $name } = $depth;
73             }
74             else {
75 24         27 $forest{ $name } = 0;
76             }
77             }
78              
79 10         33 return sort { $forest{ $a } <=> $forest{ $b } } keys %forest;
  20         34  
80             }
81              
82             sub construct_services {
83 5     5 1 360 my $self = shift;
84              
85 5         21 my $my_name = $self->meta->name;
86              
87 5         121 my @names = $self->_ordered_services;
88              
89 5         13 for my $name ($self->_ordered_services) {
90 12         32 my $service_config = $self->meta->services->{$name};
91 12 100       272 next if defined $self->services->{$name};
92              
93 9         12 my $class_name = $service_config->{service};
94 9         164 my $service = $class_name->new(
95             %$service_config,
96             name => $name,
97             bot => $self,
98             );
99              
100 9         298 $self->add_service($name, $service);
101             }
102             }
103              
104              
105             sub initialize_services {
106 4     4 1 8 my $self = shift;
107              
108 4         156 for my $name ($self->service_names) {
109 9 50       558 next if $self->initialized_services->{ $name };
110              
111 9         212 $self->initialized_services->{ $name }++;
112              
113 9         269 my $service = $self->get_service($name);
114 9         40 $service->initialize;
115             }
116             }
117              
118              
119             sub run {
120 4     4 1 1186 my $self = shift;
121              
122 4         22 $self->construct_services;
123 4         26 $self->initialize_services;
124              
125 4         137 $self->event_loop->run;
126             }
127              
128              
129             sub shutdown {
130 1     1 1 905175 my $self = shift;
131              
132 1         62 $_->shutdown for ($self->list_services);
133 1         50 $self->destroy_services;
134             }
135              
136              
137             __PACKAGE__->meta->make_immutable;
138              
139             __END__
140              
141             =pod
142              
143             =encoding UTF-8
144              
145             =head1 NAME
146              
147             Bot::Backbone::Bot - Provides backbone services to your bot
148              
149             =head1 VERSION
150              
151             version 0.161950
152              
153             =head1 SYNOPSIS
154              
155             my $bot = My::Bot->new;
156             $bot->run;
157              
158             =head1 DESCRIPTION
159              
160             When you use L<Bot::Backbone> in your code, you get a bot implementing this
161             role. It provides tools for constructing, executing, and shutting down services.
162              
163             =head1 ATTRIBUTES
164              
165             =head2 event_loop
166              
167             Bots do all their work using an event loop. Usually, this is either L<POE> or
168             L<AnyEvent>. Fortunately, these event loops tend to work well together in case
169             you need both. Just in case you need specialized startup for your bot's event
170             loop, though, this is attribute is provided to allow the event loop startup to
171             be customized.
172              
173             This is an object or class on which you may call a C<run> with no arguments. It
174             will be called to start the event loop. By default, this is just
175             "L<POE::Kernel>". It is expected that this method will block until the bot is
176             shutdown.
177              
178             =head2 services
179              
180             This is a hash of constructed services used by this bot. There should be a key
181             in this hash matching every key in the same attribute in
182             L<Bot::Backbone::Meta::Class>, once L</run> has been called.
183              
184             =head2 initialized_services
185              
186             This is a set containing the names of all the services that have been
187             constructed and initialized.
188              
189             =head1 METHODS
190              
191             =head2 bot
192              
193             Returns itself.
194              
195             =head2 construct_services
196              
197             $bot->construct_services;
198              
199             This method iterates through the service configurations of the meta class and constructs each service from that configuration.
200              
201             You may run this prior to L</run> to construct your services prior to running. Normally, though, this method is called within L</run>.
202              
203             =head2 initialize_services
204              
205             $bot->initialize_services;
206              
207             If more services are added to the bot later, this method may be called to initialize services after the new services have been constructed.
208              
209             =head2 run
210              
211             $bot->run;
212              
213             This starts your bot running. It constructs the services if they have not yet been constructed. Then, it initializes each service. Finally, it starts the L<POE> event loop. This last part really isn't it's business and might go away in the future.
214              
215             This method will not return until the event loop terminates. The usual way to do this is to call L</shutdown>.
216              
217             =head2 shutdown
218              
219             $bot->shutdown;
220              
221             You may call this at any time while your bot is running to shutdown all the services. This notifies each service that it should shutdown (i.e., finish or terminate any pending jobs in the event loop). It then clears the L</services> hash, which should cause all services to be destroyed.
222              
223             =head1 CAVEATS
224              
225             This thing sort of kind of needs L<POE> to be any kind of useful. However, L<POE> seems to have weird drawbacks. I have some planned work-arounds for this being an explicit and required dependency, but it's there for now.
226              
227             Second, if you use the Jabber chat service, you need L<AnyEvent>. Mostly, L<AnyEvent> and L<POE> seem to get along, but it's slow and I've found that timers, in particular, just plain don't work quite right.
228              
229             =head1 AUTHOR
230              
231             Andrew Sterling Hanenkamp <hanenkamp@cpan.org>
232              
233             =head1 COPYRIGHT AND LICENSE
234              
235             This software is copyright (c) 2016 by Qubling Software LLC.
236              
237             This is free software; you can redistribute it and/or modify it under
238             the same terms as the Perl 5 programming language system itself.
239              
240             =cut