On Sat, Jun 09, 2001 at 11:15:19AM -0400, Drake Wilson wrote:
> Greetings.  Note first that I am relatively new to POE and am probably 
> missing something obvious.  Note second that the (nearly) full code can be 
> found at <http://www.perlmonks.org/index.pl?node_id=87139>.
> 
> I am attempting to write a simple pluggable core for interactive fiction in 
> Perl, using POE to keep track of the events passed between game objects. 
> However, I am running into a problem where an event is not being triggered 
> correctly.
> 
> The problem is that when a line of text is typed, the wheel sends it to the 
> input handler, which supposedly triggers an event in the parser object, 
> which then calls the verb as necessary. Right now I have one verb 
> implemented, just for a test of the read-parse-compute-output cycle: quit. 
> However, typing "quit" doesn't work. In my log file, it gets as far having 
> the POE kernel post an event to the parser; the event in the parser, 
> however, is never actually called, as that line never appears in the log.

The code you posted to perlmonks contains a few oddities:

You're storing a reference to a POE session in the next line.  This is
almost never necessary since POE's Kernel manages them for you.  There
are other ways to find a particular session if you need to refer to it
later.

>      my $p =
>          POE::Session->create
>              (
>               # {{{ 1.1.1. Actual parsing routines.
>               inline_states =>
>               {
>                # {{{ 1.1.1.1. _start
>                _start => sub {
>                    w("Parser starting");

You're storing another reference to the session.  Instead, you could
invoke:

>  $_[KERNEL]->alias_set('parser');

This registers a symbolic name, "parser", which becomes a synonym for
this session.  From that point onward, you can refer to the session as
"parser" in most calls that expect a session reference:

>  $_[KERNEL]->post( parser => parse => $this );

Aliases can also make pluggable systems easier.  Whichever parser you
decide to use can set itself up as "parser".  Likewise, whichever
input module you load can register "input".  As long as the parser and
input sessions follow a common interface, you can just
$kernel->post( parser => parse => $this ) and things will work.

>                    $parser = $_[SESSION];
>                },
>  ...

The new _start state might look like this:

              # {{{ 1.1.1.1. _start
              _start => sub {
                  w("Parser starting");
                  $_[KERNEL]->alias_set( 'parser' );
              },

# The same goes for here: $i isn't doing anything.

>  ...
>    my $i;
>    $i = POE::Session->create
>        (
>         # {{{ 4.1.1. Actual input subroutines.
>         inline_states => {
>                           # {{{ 4.1.1.1. _start
>                           _start => sub {
>                               w("Input handler starting");
>                               my $heap = $_[HEAP];

Likewise here with $input.  If other sessions need to post messages to
the input session later on, consider setting an alias ("input" or
something) and posting to the alias.  By the way, aliases also avoid
circular references that can confound garbage collection.

>                               $input = $_[SESSION];
>                               $heap->{wheel} = POE::Wheel::ReadLine->new
>                                   ( InputEvent => 'got_line' );
>                               w("Wheel created: ",$heap->{wheel});
>                               $heap->{wheel}->get('> ');
>                           },
>                           # }}}
>                           # {{{ 4.1.1.2. got_line
>                           got_line => sub {
>                               my ($heap, $line) = @_[HEAP, ARG0];
>                               if (defined $line) {
>                                   w("Line input: $line");
>                                   $heap->{wheel}->addhistory($line);

Here you can say $_[KERNEL]->post( parser => parse_line => $line );

>                                   $_[KERNEL]->call($parser, 'parse_line', $line);
>                                   w("Event posted");
>                                   $heap->{wheel}->get('> ');
>                               } elsif ($_[ARG1] eq 'interrupt') {

delete $heap->{wheel} instead.  This removes the input session's
reason to live, and POE doesn't let sessions live without a reason.

>                                   exit 0;
>                               }
>                           },
>  ...

So anyway, why didn't your code work?

The parser session has nothing to do: no events to handle, no alarms
to wait for, no filehandles to watch, etc.  Part of POE::Kernel's job
is to keep track of what sessions need to do.  As sessions become
idle, the Kernel recognizes that and stops/deallocates them.

Back to the parser: it has nothing to do, so it's stopped right away.
By the time the player has entered her first command into the input
session, your parser is already dead.  You can see it happen by adding
a _stop state to the parser:

  _stop => sub { print "Stopped you say?!\n" },

Input tries to call "parse_line" on a session that no longer exists.
By design-- mainly to facilitate pluggable systems-- these events are
quietly dropped.  If you'd rather them not be discarded without a peep
(perhaps while developing), you can turn on some debugging behavior
within POE::Kernel.  Before using POE, define this constant function:

  sub POE::Kernel::ASSERT_SESSIONS () { 1 }
  use POE;
  ...

See the POE::Kernel manpage for the other ASSERT_XYZ things you can
do (ASSERT_DEFAULT turns them all on at once).

Anyway, aliases are a weak form of inter-session communication.
They're like named sockets or something.  They promise to POE::Kernel
that their sessions will receive events from the great beyond: from
some other session besides itself.  The Kernel recognizes this and
keeps aliased sessions alive until every other session becomes
dormant.

When that happens, POE::Kernel recognizes that the enitre program has
gone idle, and it kills off everything left over (including aliased
sessions).  This way zombie POE programs don't eat your machine's
brains.  A quick rundown of the events which lead to your program's
demise:

1. The player presses C-c, generating an "interrupt" in the input
   session.

2. The input session deletes its wheel.

3. POE::Kernel sees that every session is alive by virtue of their
   aliases alone.  Nothing further will happen in this program, so it
   kills off the remaining sessions.

4. The sessions stop and are garbage collected.

5. POE::Kernel sees that there are no more sessions, so
   $poe_kernel->run() finally returns, and the main program exits.

By the way, I have some perl code for an interactive fiction parser
based on Inform's.  It's really old code, and I never used it in an
actual game (let alone one based on POE), but maybe you can use it for
something.

-- Rocco Caputo / [EMAIL PROTECTED] / poe.perl.org / poe.sourceforge.net

Reply via email to