package Evo::MDN;
use Evo::Base -base;
use Hash::Util::FieldHash qw(register id id_2obj);
use Evo::Guard;

Hash::Util::FieldHash::fieldhashes \my (%SENDERS, %STORE);

sub broadcast ($self, $sender, $msg) {
  my $hash = $SENDERS{$sender};
  $hash->{$_}->(id_2obj($_), $msg, $sender) for keys $hash->%*;
}

# don't try to register subscriptions.
# hashes works fast enought and in most
# keyses shoud work much faster because you don't need
# to register a second entity

sub _when_message_cb {
  my $self  = shift;
  my $guard = Evo::Guard->new(
    error => sub {
      __PACKAGE__->unsubscribe_from_all($self);
    }
  );
  $self->when_message(@_);
}

sub subscribe ($self, $me, $to, $store=1, $cb=undef) {
  unless ($cb) {
    Carp::croak "implement when_message in $me"
      unless $me->can('when_message');
    $cb = \&_when_message_cb;
  }
  my $hash = $SENDERS{$to} //= {};
  my $id = id(register $me, $hash);

  # here could be unsubscribed obj with the same addr
  Carp::croak "$me has been subscribed to $to before" if $hash->{$id};

  $hash->{$id} = $cb;
  $STORE{$to}->{$id} = $me if $store;
}

sub unsubscribe ($self, $me, $from) { _unsubscribe_id(id($me), $from) }

sub _unsubscribe_id ($me_id, $from) {
  delete $SENDERS{$from}->{$me_id};
  delete $STORE{$from}->{$me_id};
}

sub unsubscribe_from_all ($self, $me) { _unsubscribe_from_all_id(id($me)); }

sub _unsubscribe_from_all_id ($me_id) {
  _unsubscribe_id($me_id, $_) for keys %SENDERS;
}

sub _all_senders { return \%SENDERS; }
sub _all_stores  { return \%STORE; }

1;

# ABSTRACT: A message delivery network

__END__

=pod

=encoding UTF-8

=head1 NAME

Evo::MDN - A message delivery network

=head1 VERSION

version 0.0158

=head1 SYNOPSIS

  use Evo::Base -strict;
  use Evo::MDN;
  my $mdn = Evo::MDN->new;

  my $sender = Evo::Base->new;
  do {
    my $foo  = Evo::Base->new;
    my $file = IO::File->new;
    $mdn->subscribe($foo,  $sender, 1, sub($me, $msg, @) { say "$me got $msg" });
    $mdn->subscribe($file, $sender, 0, sub($me, $msg, @) { say "$me got $msg" });
    $mdn->broadcast($sender, "hello");
  };

  # only Foo is alive
  $mdn->broadcast($sender, "alive");

=head1 DESCRIPTION

Message delivery network. Allows to send messages from one object to another.
It do the right things in most cases.

The benefit to use it that almost any object can send and any object can
receive messages without modification. So you can use your existing code
or write new modules and build your app using independent components that
communicate.

You can expect about 500K-1M messages/s perfomance per 1 process/processor core

=head1 METHODS

=head2 broadcast

Sends a message to all subscribers. when_message will be invoked with
the sender and message for the subscribers

=head2 subscribe

Subscribe one object to another
If the third passed arbument is true, stores an object and prevent
it from destruction while sender exists. Default values are

  $mdn->subscribe($foo, $sender, 1, sub { shift->when_message(@_) });

But if callback is not provided, an object will be checked that method
C<when_message> exists in object's class, or an exception will be thrown

=head2 unsubscribe

Unsubscribe one object from another and deletet itself, if it was stored

=head2 unsubscribe_from_all

Unsubscribe me from all senders

=head1 AUTHOR

alexbyk.com

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2015 by alexbyk.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
