# NAME
MooX::Role::POE::Emitter - Pluggable POE event emitter role for cows
# SYNOPSIS
## A POE::Session that can broadcast events to listeners:
package My::EventEmitter;
use POE;
use Moo;
with 'MooX::Role::POE::Emitter';
sub spawn {
my ($self, %args) = @_;
$self->set_object_states(
[
$self => {
## Add some extra handlers to our Emitter:
'emitter_started' => '_emitter_started',
'emitter_stopped' => '_emitter_stopped',
},
## Include any object_states we had previously
## (e.g. states added at construction time):
(
$self->has_object_states ?
@{ $self->object_states } : ()
),
## Maybe include from named arguments, for example:
(
ref $args{object_states} eq 'ARRAY' ?
@{ $args{object_states} } : ()
),
],
);
## Start our Emitter's POE::Session:
$self->_start_emitter;
}
sub shutdown {
my ($self) = @_;
## .. do some cleanup, whatever ..
$self->_shutdown_emitter;
}
sub _emitter_started {
my ($kernel, $self) = @_[KERNEL, OBJECT];
## A POE state called when the emitter's session starts.
## (Analogous to a normal '_start' handler)
## Could load plugins, do initialization, etc.
}
sub _emitter_stopped {
## Opposite of 'emitter_started'
}
sub do_something {
my ($self, @things) = @_;
# ... do some work ...
# ... emit an event:
$self->emit( did_stuff => @things )
}
## A listening POE::Session:
package My::Listener;
use POE;
sub spawn {
# This spawn() takes an alias/session to subscribe to:
my ($self, $alias_or_sessionID) = @_;
POE::Session->create(
## Set up a Session, etc
object_states => [
$self => [
'emitted_did_stuff',
# ...
],
],
);
## Subscribe to all events from $alias_or_sessionID:
$poe_kernel->call(
$alias_or_sessionID => subscribe => 'all'
);
}
sub emitted_did_stuff {
my ($kernel, $self) = @_[KERNEL, OBJECT];
## Received 'did_stuff' from Emitter
my @things = @_[ARG0 .. $#_];
# ...
}
# DESCRIPTION
Consuming this [Moo::Role](
https://metacpan.org/pod/Moo::Role) gives your class a [POE::Session](
https://metacpan.org/pod/POE::Session) capable of
processing events via loaded plugins and/or emitting them to registered
"listener" sessions.
It is derived from [POE::Component::Syndicator](
https://metacpan.org/pod/POE::Component::Syndicator) by BINGOS, HINRIK, APOCAL
et al, but with more cows ;-) and a few extra features (such as anonymous
coderef callbacks; see ["yield"](#yield)), as well as the
faster plugin dispatch system that comes with [MooX::Role::Pluggable](
https://metacpan.org/pod/MooX::Role::Pluggable).
The Emitter role consumes [MooX::Role::Pluggable](
https://metacpan.org/pod/MooX::Role::Pluggable),
making your emitter pluggable (see the
[MooX::Role::Pluggable](
https://metacpan.org/pod/MooX::Role::Pluggable) documentation for plugin-related details).
You do not need to create your own [POE::Session](
https://metacpan.org/pod/POE::Session); calling
["\_start\_emitter"](#_start_emitter) will spawn one for you.
You also get some useful sugar over POE event dispatch; see ["Methods"](#methods).
## Creating an Emitter
["SYNOPSIS"](#synopsis) contains an emitter that uses **set\_$attrib** methods to
configure itself when `spawn()` is called; attributes can, of course,
be set when your Emitter is constructed:
my $emitter = MyEmitter->new(
alias => 'my_emitter',
pluggable_type_prefixes => {
NOTIFY => 'Notify',
PROCESS => 'Proc',
},
# . . .
);
### Attributes
Most of these can be altered via **set\_$attrib** methods at any time before
["\_start\_emitter"](#_start_emitter) is called. Changing an emitter's configuration after it has
been started may result in undesirable behavior ;-)
Public attributes provide **has\_** prefixed predicates; e.g.
**has\_event\_prefix**.
#### alias
**alias** specifies the POE::Kernel alias used for our [POE::Session](
https://metacpan.org/pod/POE::Session);
defaults to the stringified object.
Set via **set\_alias**. If the emitter is running, a prefixed **alias\_set**
event is emitted to notify listeners that need to know where to reach the emitter.
#### event\_prefix
**event\_prefix** is prepended to notification events before they are
dispatched to listening sessions. It is also used for the plugin
pipeline's internal events; see ["\_pluggable\_event" in MooX::Role::Pluggable](
https://metacpan.org/pod/MooX::Role::Pluggable#pluggable_event)
for details.
Defaults to `emitted_`
Set via **set\_event\_prefix**
#### pluggable\_type\_prefixes
**pluggable\_type\_prefixes** is a hash reference that can optionally be set
to change the default [MooX::Role::Pluggable](
https://metacpan.org/pod/MooX::Role::Pluggable) plugin handler prefixes for
`PROCESS` and `NOTIFY` (which default to `P` and `N`, respectively):
my $emitter = $class->new(
pluggable_type_prefixes => {
PROCESS => 'P',
NOTIFY => 'N',
},
);
Set via **set\_pluggable\_type\_prefixes**
#### object\_states
**object\_states** is an array reference suitable for passing to
[POE::Session](
https://metacpan.org/pod/POE::Session); the subclasses own handlers should be added to
**object\_states** prior to calling ["\_start\_emitter"](#_start_emitter).
Set via **set\_object\_states**
#### register\_prefix
**register\_prefix** is prepended to 'register' and 'unregister' methods
called on plugins at load time (see [MooX::Role::Pluggable](
https://metacpan.org/pod/MooX::Role::Pluggable)).
Defaults to _Emitter\__
Set via **set\_register\_prefix**
#### session\_id
**session\_id** is our emitter's [POE::Session](
https://metacpan.org/pod/POE::Session) ID, set when our Session is
started via ["\_start\_emitter"](#_start_emitter).
#### shutdown\_signal
**shutdown\_signal** is the name of the [POE](
https://metacpan.org/pod/POE) signal that will trigger a
shutdown (used to shut down multiple Emitters). See ["Signals"](#signals)
### \_start\_emitter
**\_start\_emitter()** should be called on our object to spawn the actual
[POE::Session](
https://metacpan.org/pod/POE::Session). It takes no arguments and should be called after the
object has been configured.
### \_shutdown\_emitter
**\_shutdown\_emitter()** must be called to terminate the Emitter's
[POE::Session](
https://metacpan.org/pod/POE::Session)
A 'shutdown' event will be emitted before sessions are dropped.
## Listening sessions
### Session event subscription
An external [POE::Session](
https://metacpan.org/pod/POE::Session) can subscribe to receive events via
normal POE event dispatch by sending a `subscribe`:
$poe_kernel->post( $emitter->session_id,
'subscribe',
@events
);
Listening sessions are consumers; they cannot modify event arguments in
any meaningful way, and will receive arguments as-normal (in @\_\[ARG0 ..
$#\_\] like any other POE state). Plugins operate differently and receive
references to arguments that can be modified -- see
[MooX::Role::Pluggable](
https://metacpan.org/pod/MooX::Role::Pluggable) for details.
### Session event unregistration
An external Session can unregister subscribed events using the same syntax
as above:
$poe_kernel->post( $emitter->session_id,
'unsubscribe',
@events
);
If no events are specified, then any previously subscribed events are
unregistered.
Note that unsubscribing from 'all' does not carry the same behavior; that
is to say, a subscriber can subscribe/unsubscribe for 'all' separately from
some set of specifically named events.
## Receiving events
### Events delivered to listeners
Events are delivered to subscribed listener sessions as normal POE events,
with the configured ["event\_prefix"](#event_prefix) prepended and arguments available via
` @_[ARG0 .. $#_] ` as normal.
sub emitted_my_event {
my ($kernel, $self) = @_[KERNEL, OBJECT];
my @args = @_[ARG0 .. $#_];
# . . .
}
See ["Session event subscription"](#session-event-subscription) and ["emit"](#emit)
### Events delivered to this session
The emitter's [POE::Session](
https://metacpan.org/pod/POE::Session) provides a '\_default' handler that
redispatches unknown POE-delivered events to ["process"](#process)
(except for events prefixed with '\_', which are reserved).
You can change this behavior by overriding '\_emitter\_default' -- here's a
direct adaption of the example from [POE::Component::Syndicator](
https://metacpan.org/pod/POE::Component::Syndicator):
use Moo;
use POE;
with 'MooX::Role::POE::Emitter';
around '_emitter_default' => sub {
my $orig = shift;
my ($kernel, $self) = @_[KERNEL, OBJECT];
my ($event, $args) = @_[ARG0, ARG1];
## process(), then do something else, for example
return if $self->process( $event, @$args ) == EAT_ALL;
. . .
};
(Note that due to internal redispatch $\_\[SENDER\] will be the Emitter's
Session.)
## EAT values
[MooX::Role::Pluggable](
https://metacpan.org/pod/MooX::Role::Pluggable) uses `EAT_*` constants to indicate event
lifetime.
If a plugin in the pipeline returns EAT\_CLIENT or EAT\_ALL, events
are not dispatched to subscribed listening sessions; a dispatched NOTIFY
event goes to your emitter's Session if it is subscribed to receive it,
then to the plugin pipeline, and finally to other subscribed listener
Sessions **unless** a plugin returned EAT\_CLIENT or EAT\_ALL.
See ["emit"](#emit) for more on dispatch behavior and event lifetime. See
[MooX::Role::Pluggable](
https://metacpan.org/pod/MooX::Role::Pluggable) for details regarding plugins.
### NOTIFY events
**NOTIFY** events are intended to be dispatched asynchronously to our own
session, any loaded plugins in the pipeline, and subscribed listening
sessions, respectively.
See ["emit"](#emit).
### PROCESS events
**PROCESS** events are intended to be processed by the plugin pipeline
immediately; these are intended for message processing and similar
synchronous action handled by plugins.
Handlers for **PROCESS** events are prefixed with `P_`
See ["process"](#process).
## Sending events
### emit
$self->emit( $event, @args );
**emit()** dispatches ["NOTIFY events"](#notify-events) -- these events are dispatched
first to our own session (with ["event\_prefix"](#event_prefix) prepended), then any
loaded plugins in the pipeline (with `N_` prepended), then registered
sessions (with ["event\_prefix"](#event_prefix) prepended):
## With default event_prefix:
$self->emit( 'my_event', @args )
# -> Dispatched to own session as 'emitted_my_event'
# -> Dispatched to plugin pipeline as 'N_my_event'
# -> Dispatched to registered sessions as 'emitted_my_event'
# *unless* a plugin returned EAT_CLIENT or EAT_ALL
See ["Receiving events"](#receiving-events), ["EAT values"](#eat-values)
### emit\_now
$self->emit_now( $event, @args );
**emit\_now()** synchronously dispatches ["NOTIFY events"](#notify-events) -- see
["emit"](#emit).
### process
$self->process( $event, @args );
**process()** calls registered plugin handlers for ["PROCESS events"](#process-events)
immediately; these are **not** dispatched to listening sessions.
Returns the same value as ["\_pluggable\_process" in MooX::Role::Pluggable](
https://metacpan.org/pod/MooX::Role::Pluggable#pluggable_process).
See [MooX::Role::Pluggable](
https://metacpan.org/pod/MooX::Role::Pluggable) for details on pluggable
event dispatch.
## Methods
These methods provide easy proxy mechanisms for issuing POE events and
managing timers within the context of the emitter's [POE::Session](
https://metacpan.org/pod/POE::Session).
### yield
$self->yield( $poe_event, @args );
Provides an interface to [POE::Kernel](
https://metacpan.org/pod/POE::Kernel)'s yield/post() method, dispatching
POE events within the context of the emitter's session.
The event can be either a named event/state dispatched to your Emitter's
[POE::Session](
https://metacpan.org/pod/POE::Session):
$emitter->yield( 'some_event', @args );
.. or an anonymous coderef, which is executed as if it were a named
POE state belonging to your Emitter:
$emitter->yield( sub {
## $_[OBJECT] is the Emitter's object:
my ($kernel, $self) = @_[KERNEL, OBJECT];
my @params = @_[ARG0 .. $#_];
## $_[STATE] is the current coderef
## Yield ourselves again, for example:
$self->yield( $_[STATE], @new_args )
if $some_condition;
}, $some, $args );
Inside an anonymous coderef callback such as shown above, `$_[OBJECT]` is
the Emitter's `$self` object and `$_[STATE]` contains the callback
coderef itself.
### call
$self->call( $poe_event, @args );
The synchronous counterpart to ["yield"](#yield).
### timer
my $alarm_id = $self->timer(
$delayed_seconds,
$event,
@args
);
Set a timer in the context of the emitter's [POE::Session](
https://metacpan.org/pod/POE::Session). Returns the
POE alarm ID.
The event can be either a named event/state or an anonymous coderef (see
["yield"](#yield)).
A prefixed (["event\_prefix"](#event_prefix)) 'timer\_set' event is emitted when a timer is
set. Arguments are the alarm ID, the event name or coderef, the delay time,
and any event parameters, respectively.
### timer\_del
$self->timer_del( $alarm_id );
Clears a pending ["timer"](#timer).
A prefixed (["event\_prefix"](#event_prefix)) 'timer\_deleted' event is emitted when a timer
is deleted. Arguments are the removed alarm ID, the event name or coderef,
and any event parameters, respectively.
## Signals
### Shutdown Signal
The attribute ["shutdown\_signal"](#shutdown_signal) defines a POE signal that will trigger a
shutdown; it defaults to `SHUTDOWN_EMITTER`:
## Shutdown *all* emitters (with a default shutdown_signal()):
$poe_kernel->signal( $poe_kernel, 'SHUTDOWN_EMITTER' );
See ["Signal Watcher Methods" in POE::Kernel](
https://metacpan.org/pod/POE::Kernel#Signal-Watcher-Methods) for details on [POE](
https://metacpan.org/pod/POE) signals.
# SEE ALSO
For details regarding POE, see [POE](
https://metacpan.org/pod/POE), [POE::Kernel](
https://metacpan.org/pod/POE::Kernel), [POE::Session](
https://metacpan.org/pod/POE::Session)
For details regarding Moo classes and Roles, see [Moo](
https://metacpan.org/pod/Moo), [Moo::Role](
https://metacpan.org/pod/Moo::Role),
[Role::Tiny](
https://metacpan.org/pod/Role::Tiny)
# AUTHOR
Jon Portnoy <
[email protected]>
Written from the ground up, but conceptually derived from
[POE::Component::Syndicator](
https://metacpan.org/pod/POE::Component::Syndicator)-0.06 copyright Hinrik Orn Sigurosson (HINRIK),
Chris Williams (BINGOS), APOCAL et al -- that will probably do you for
non-Moo(se) use cases; I needed something cow-like that worked with
[MooX::Role::Pluggable](
https://metacpan.org/pod/MooX::Role::Pluggable).
Licensed under the same terms as Perl 5; see the license that came with your
Perl distribution for details.