The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Loop::Flow::Object;

use 5.006;
use strict;
use warnings;

use POSIX ':sys_wait_h'; # для waitpid -1, WNOHANG;

=encoding utf8

=head1 ПРИВЕТСТВИЕ SALUTE

Доброго всем! Доброго здоровья! Доброго духа!

Hello all! Nice health! Good thinks!

=head1 NAME

Loop::Flow::Object - запуск цикла для объекта с контролем и переключением ветвления, выполнение кода в указанных методах объекта.

Loop::Flow::Object - looping code of one object with forking on/off. Simple switch and count of forks.

Executing code, control count and exit from loop by the object methods.


=head1 VERSION

Version 0.01

=cut

our $VERSION = '0.01';


=head1 SYNOPSIS

    use Loop::Flow::Object;
    use Some::My::Module;
    
    my $obj = Some::My::Module->new(...);
    
    # no-no this is on Some/My/Module.pm !!
    sub Some::My::Module::one {# main code in loop
        my $self = shift;
        my @data = @_;
        ...
    }
    # no-no this is on Some/My/Module.pm !!
    sub Some::My::Module::data {# data for main code in loop
        my $self = shift;
        my $count = shift;
        ...
    }
    my $loop = Loop::Flow::Object->new(max_count=>..., forks=>..., debug=>...);
    $loop->start($obj, main=>'one', data=>'data', end=>'...',);
    ...



=head1 EXPORT

None.

=head1 METHODS

=cut

=head2 new(max_count=>..., forks=>..., debug=>...)
Options:

=over 4

=item * max_count => <integer> (optional)
    infinitely looping if max_count => 0 || undef (default)

=item * forks => <integer> (optional)
    No forking, sequentially if forks => 0 || undef (default)
    Limit of forks

=item * debug => 0|1 (optional)
    0 - no print msg (default)

=back
=cut

sub new {
    my $class = shift;
    my $self = {
        max_count=>undef,
        forks => undef,
        @_,
    };
    bless $self, $class;
    return $self;
}

=head2 start($obj, main=>'<main_method>', data=>'<data_method>', end=>'<end_method>',)
Looping for $obj which have methods:

=over 4

=item * string '<main_method>' - main code which execute in loop (as child process if forks) (mandatory)

=item * string '<data_method>' - hook which get/return data for '<main_method>'
B<Attention>. If you define this method and it's return B<empty list> - WILL STOPS THE LOOP!

=item * string '<end_method>' - hook which execute when end the '<main_method>' (child process exit if forks)

=cut

sub start {
    my $self = shift;
    my $obj = shift;
    my %meths = (@_);
    my %stack = ();# для $self->{forks} = undef останется пустой
    my $count = 0;
    #~ while ( %stack != 0 || !$self->{max_count} || $count < $self->{max_count} ) {# ПОЕХАЛИ
    until ( %stack == 0 && $self->{max_count} && $count == $self->{max_count} ) {# ПОЕХАЛИ (с)
        print "START: ", (map {"[$_], "} (%stack != 0, !$self->{max_count},  $count < $self->{max_count})),"\n",;
        if ((!$self->{max_count} || $count < $self->{max_count}) && (!$self->{forks} || scalar keys %stack < $self->{forks})) {
            my @data = $self->data($obj, $meths{data}, $count);# данные, отправляемые в основной метод
            last unless @data;
            my $pid = $self->start_main($obj, $meths{main}, @data,);
            $stack{$pid}++ if $pid;
            $count++;
        }
        
        if ($self->{forks} && (my @pids = $self->check_child()) ) {
            delete @stack{ @pids };
        }
        
    }
}

sub data {
    my $self = shift;
    my $obj = shift;
    my $meth_str = shift;
    my $count = shift;
    
    if ($meth_str) {
        my $meth = $obj->can($meth_str);
        die "Не найден метод [$meth_str] объекта/модуля [$obj]" unless $meth;
        return $obj->$meth(@_);
    } else {
        return $count;
    }
}

sub start_main {#  может не форк
    my $self = shift;
    my $obj = shift;
    my $meth_str = shift;
    
    my $meth = $obj->can($meth_str);
    die "Не найден метод [$meth_str] объекта/модуля [$obj]" unless $meth;

    my $pid = $self->{forks} ? fork() : 0;#
    if( $pid ) {# parent
        #~ print "{$$} PARENT: running child pid={$pid}\n" if $self->{debug};
        return $pid;
    } elsif ($pid == 0) {# child or sequential
        #~ print "make_child: ", Dumper(\@_),
        $obj->$meth(@_);
        
        if ($self->{forks}) {
            print "{$$} CHILD: iam done!\n" if $self->{debug};
            exit 0;
        } else {
            return undef;
        }
    } else {
        die "couldnt fork: $!\n";
    }

}

sub check_child {# просто проверить и вернуть иды завершенных процессов для delete from %stack
	my $self = shift;
	my $pid;
	my @pids = ();
	while (1) {#$pid > 0do
		$pid = waitpid(-1, WNOHANG);
		if ($pid > 0) {
			print "Parent: done child pid=$pid \$?=[$?];\n";
			push(@pids, $pid);
		} else {last;}
	}
	return @pids;
}


=head1 AUTHOR

Mikhail Che, C<< <m.che at aukama.dyndns.org> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-loop-flow at rt.cpan.org>, or through
the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Loop-Flow>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.




=head1 SUPPORT

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

    perldoc Loop::Flow::Object


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker (report bugs here)

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Loop-Flow>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/Loop-Flow>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/Loop-Flow>

=item * Search CPAN

L<http://search.cpan.org/dist/Loop-Flow/>

=back


=head1 ACKNOWLEDGEMENTS


=head1 LICENSE AND COPYRIGHT

Copyright 2012 Mikhail Che.

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.


=cut

1; # End of Loop::Flow::Object