NAME

Async::Selector - level-triggered resource observer like select(2)

VERSION

1.03

SYNOPSIS

use Async::Selector;

my $selector = Async::Selector->new();

## Register resource
my $resource = "some text.";  ## 10 bytes

$selector->register(resource_A => sub {
    ## If length of $resource is more than or equal to $threshold bytes, provide it.
    my $threshold = shift;
    return length($resource) >= $threshold ? $resource : undef;
});


## Watch the resource with a callback.
$selector->watch(
    resource_A => 20,  ## When the resource gets more than or equal to 20 bytes...
    sub {              ## ... execute this callback.
        my ($watcher, %resource) = @_;
        print "$resource{resource_A}\n";
        $watcher->cancel();
    }
);


## Append data to the resource
$resource .= "data";  ## 14 bytes
$selector->trigger('resource_A'); ## Nothing happens

$resource .= "more data";  ## 23 bytes
$selector->trigger('resource_A'); ## The callback prints 'some text.datamore data'

DESCRIPTION

Async::Selector is an object that watches registered resources and executes callbacks when some of the resources are available. Thus it is an implementation of the Observer pattern like Event::Notify, but the important difference is that Async::Selector is level-triggered like select(2) system call.

Basic usage of Async::Selector is as follows:

  1. Register as many resources as you like by register() method.

    A resource has its name and resource provider. A resource provier is a subroutine reference that returns some data (or undef if it's not available).

  2. Watch as many resources as you like by watch() method.

    When any of the watched resources gets available, a callback function is executed with the available resource data.

    Note that if some of the watched resources is already available when calling watch() method, it executes the callback function immediately. That's because Async::Selector is level-triggered.

  3. Notify the Async::Selector object by trigger() method that some of the registered resources have changed.

    The Async::Selector object then checks if any of the triggered resources gets available. If some resources become available, the callback function given by watch() method is executed.

CLASS METHODS

$selector = Async::Selector->new();

Creates an Async::Selector object. It takes no parameters.

OBJECT METHODS

$selector->register($name => $provider->($condition_input), ...);

Registers resources with the object. A resource is described as a pair of resource name and resource provider. You can register as many resources as you like.

The resource name ($name) is an arbitrary string. It is used to select the resource in watch() method. If $name is already registered with $selector, the resource provider is updated with $provider and the old one is discarded.

The resource provider ($provider) is a subroutine reference. Its return value is supposed to be a scalar data of the resource if it's available, or undef if it's NOT available.

$provider subroutine takes a scalar argument ($condition_input), which is given by the user in arguments of watch() method. $provider can decide whether to provide the resource according to $condition_input.

register() method returns $selector object itself.

$selector->unregister($name, ...);

Unregister resources from $selector object.

$name is the name of the resource you want to unregister. You can unregister as many resources as you like.

unregister() returns $selector object itself.

$watcher = $selector->watch($name => $condition_input, ..., $callback->($watcher, %resources));

Starts to watch resources. A watch is described as pairs of resource names and condition inputs for the resources.

$name is the resource name that you want to watch. It is the name given in register() method.

$condition_input describes the condition the resource has to meet to be considered as "available". $condition_input is an arbitrary scalar, and its interpretation is up to the resource provider.

You can list as many $name => condition_input pairs as you like.

$callback is a subroutine reference that is executed when any of the watched resources gets available. Its first argument $watcher is an object of Async::Selector::Watcher which represents the watch you just made by watch() method. This object is the same instance as the return value of watch() method. The other argument (%resources) is a hash whose keys are the available resource names and values are the corresponding resource data. Note that $callback is executed before watch() method returns if some of the watched resources is already available.

The return value of $callback is just ignored by Async::Selector.

watch() method returns an object of Async::Selector::Watcher ($watcher) which represents the watch you just made by watch() method. $watcher gives you various information such as the list of watched resources and whether the watcher is active or not. See Async::Selector::Watcher for detail.

The watcher created by watch() method is persistent in nature, i.e., it remains in the Async::Selector object and $callback can be executed repeatedly. To cancel the watcher and release the $callback, call $watcher->cancel() method.

If no resource selection ($name => $condition_input pair) is specified, watch() method silently ignores it. As a result, it returns a $watcher object which is already canceled and inactive.

$watcher = $selector->watch_lt(...);

watch_lt() method is an alias for watch() method.

$watcher = $selector->watch_et(...);

This method is just like watch() method but it emulates edge-triggered watch.

To emulate edge-triggered behavior, watch_et() won't execute the $callback immediately even if some of the watched resources are available. The $callback is executed only when trigger() method is called on resources that are watched and available.

$selector->trigger($name, ...);

Notify $selector that the resources specified by $names may be changed.

$name is the name of the resource that might have been changed. You can specify as many $names as you like.

Note that you may call trigger() on resources that are not actually changed. It is up to the resource provider to decide whether to provide the resource to watchers.

trigger() method returns $selector object itself.

@resouce_names = $selector->resources();

Returns the list of registered resource names.

$is_registered = $selector->registered($resource_name);

Returns true if $resource_name is registered with the Async::Selector object. Returns false otherwise.

@watchers = $selector->watchers([@resource_names]);

Returns the list of active watchers (Async::Selector::Watcher objects) from the Async::Selector object.

If watchers() method is called without argument, it returns all of the active watchers.

If watchers() method is called with some arguments (@resource_names), it returns active watchers that watch ANY resource out of @resource_names.

If you want watchers that watch ALL of @resource_names, try filtering the result (@watchers) with Async::Selector::Watcher's resources() method.

EXAMPLES

Level-triggered vs. edge-triggered

Watchers created by watch() and watch_lt() methods are level-triggered. This means their callbacks can be immediately executed if some of the watched resources are already available.

Watchers created by watch_et() method are edge-triggered. This means their callbacks are never executed at the moment watch_et() is called.

Both level-triggered and edge-triggered watcher callbacks are executed when some of the watched resources are trigger()-ed AND available.

my $selector = Async::Selector->new();
my $a = 10;
$selector->register(a => sub { my $t = shift; return $a >= $t ? $a : undef });

## Level-triggered watch
$selector->watch_lt(a => 5, sub { ## => LT: 10
    my ($watcher, %res) = @_;
    print "LT: $res{a}\n";
});
$selector->trigger('a');          ## => LT: 10
$a = 12;
$selector->trigger('a');          ## => LT: 12
$a = 3;
$selector->trigger('a');          ## Nothing happens because $a == 3 < 5.

## Edge-triggered watch
$selector->watch_et(a => 2, sub { ## Nothing happens because it's edge-triggered
    my ($watcher, %res) = @_;
    print "ET: $res{a}\n";
});
$selector->trigger('a');          ## => ET: 3
$a = 0;
$selector->trigger('a');          ## Nothing happens.
$a = 10;
$selector->trigger('a');          ## => LT: 10
                                  ## => ET: 10

Multiple resources, multiple watches

You can register multiple resources with a single Async::Selector object. You can watch multiple resources with a single call of watch() method. If you watch multiple resources, the callback is executed when any of the watched resources is available.

my $selector = Async::Selector->new();
my $a = 5;
my $b = 6;
my $c = 7;
$selector->register(
    a => sub { my $t = shift; return $a >= $t ? $a : undef },
    b => sub { my $t = shift; return $b >= $t ? $b : undef },
    c => sub { my $t = shift; return $c >= $t ? $c : undef },
);
$selector->watch(a => 10, sub {
    my ($watcher, %res) = @_;
    print "Select 1: a is $res{a}\n";
    $watcher->cancel();
});
$selector->watch(
    a => 12, b => 15, c => 15,
    sub {
        my ($watcher, %res) = @_;
        foreach my $key (sort keys %res) {
            print "Select 2: $key is $res{$key}\n";
        }
        $watcher->cancel();
    }
);

($a, $b, $c) = (11, 14, 14);
$selector->trigger(qw(a b c));  ## -> Select 1: a is 11
print "---------\n";
($a, $b, $c) = (12, 14, 20);
$selector->trigger(qw(a b c));  ## -> Select 2: a is 12
                                ## -> Select 2: c is 20

One-shot and persistent watches

The watchers are persistent by default, that is, they remain in the Async::Selector object no matter how many times their callbacks are executed.

If you want to execute your callback just one time, call $watcher->cancel() in the callback.

my $selector = Async::Selector->new();
my $A = "";
my $B = "";
$selector->register(
    A => sub { my $in = shift; return length($A) >= $in ? $A : undef },
    B => sub { my $in = shift; return length($B) >= $in ? $B : undef },
);

my $watcher_a = $selector->watch(A => 5, sub {
    my ($watcher, %res) = @_;
    print "A: $res{A}\n";
    $watcher->cancel(); ## one-shot callback
});
my $watcher_b = $selector->watch(B => 5, sub {
    my ($watcher, %res) = @_;
    print "B: $res{B}\n";
    ## persistent callback
});

## Trigger the resources.
## Execution order of watcher callbacks is not guaranteed.
($A, $B) = ('aaaaa', 'bbbbb');
$selector->trigger('A', 'B');   ## -> A: aaaaa
                                ## -> B: bbbbb
print "--------\n";
## $watcher_a is already canceled.
($A, $B) = ('AAAAA', 'BBBBB');
$selector->trigger('A', 'B');   ## -> B: BBBBB
print "--------\n";

$B = "CCCCCCC";
$selector->trigger('A', 'B');   ## -> B: CCCCCCC
print "--------\n";

$watcher_b->cancel();
$selector->trigger('A', 'B');   ## Nothing happens.

Watcher aggregator

Sometimes you might want to use multiple Async::Selector objects and watch their resources simultaneously. In this case, you can use Async::Selector::Aggregator to aggregate watchers produced by Async::Selector objects. See Async::Selector::Aggregator for details.

my $selector_a = Async::Selector->new();
my $selector_b = Async::Selector->new();
my $A = "";
my $B = "";
$selector_a->register(resource => sub { my $in = shift; return length($A) >= $in ? $A : undef });
$selector_b->register(resource => sub { my $in = shift; return length($B) >= $in ? $B : undef });

my $watcher_a = $selector_a->watch(resource => 5, sub {
    my ($watcher, %res) = @_;
    print "A: $res{resource}\n";
});
my $watcher_b = $selector_b->watch(resource => 5, sub {
    my ($watcher, %res) = @_;
    print "B: $res{resource}\n";
});

## Aggregates the two watchers into $aggregator
my $aggregator = Async::Selector::Aggregator->new();
$aggregator->add($watcher_a);
$aggregator->add($watcher_b);

## This cancels both $watcher_a and $watcher_b
$aggregator->cancel();

print("watcher_a: " . ($watcher_a->active ? "active" : "inactive") . "\n"); ## -> watcher_a: inactive
print("watcher_b: " . ($watcher_b->active ? "active" : "inactive") . "\n"); ## -> watcher_b: inactive

Real-time Web: Comet (long-polling) and WebSocket

Async::Selector can be used for foundation of so-called real-time Web. Resource registered with an Async::Selector object can be pushed to Web browsers via Comet (long-polling) and/or WebSocket.

See Async::Selector::Example::Mojo for detail.

COMPATIBILITY

The following methods that existed in Async::Selector v0.02 or older are supported but not recommended in this version.

  • select()

  • select_lt()

  • select_et()

  • selections()

  • cancel()

Currently the watch methods are substituted for the select methods.

The differences between watch and select methods are as follows.

  • watch methods take the watcher callback from the last argument, while select methods take it from the first argument.

  • watch methods return Async::Selector::Watcher objects, while select methods return selection IDs, which are strings.

  • The callback function for watch receives Async::Selector::Watcher object from the first argument, while the callback for select receives the selection ID.

  • The second argument for the callback function is also different. For watch methods, it is a hash of resources that are watched, triggered and available. For select methods, it is a hash of all the watched resources with values for unavailable resources being undef.

  • Return values from the callback function for watch methods are ignored, while those for select methods are used to automatically cancel the selection.

  • trigger() method executes the callback for watch methods when it triggers resources that are watched and available. On the other hand, trigger() method executes the callback for select when it triggers resources that are watched, and some of the watched resources are available. So if you trigger an unavailable watched resource and don't trigger any available watched resource, the select callback is executed with available resources even though they are not triggered.

SEE ALSO

Event::Notify, Notification::Center

AUTHOR

Toshio Ito, <toshioito at cpan.org>

BUGS

Please report any bugs or feature requests to bug-async-selector at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Async-Selector. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc Async::Selector

You can also look for information at:

LICENSE AND COPYRIGHT

Copyright 2012-2013 Toshio Ito.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.