[Reality] still in effect, the mock you made

glyph at divmod.com glyph at divmod.com
Wed Oct 11 23:33:01 CDT 2006


Well, it's been, what, 5 months since the last post here?  And most of the 
problems we were thinking about then are the same ones we're thinking about now.

Today radix and I had a chat about how events ought to work, again.  I started
off thinking that we needed some comprehensive refactoring to do this, but I'm
glad to say I was disuaded.  The code is pretty close to how it should work
already.  Everything happens in roughly the right order and all the right
objects exist.

First, we framed the problem:

    The hiragana mouse was too hard to write, hard to test, and has lots of
    code which is prone to transaction errors (ie, reactor interaction) and
    does not cooperate sanely with other code.

Our goal was to come up with an "event system" design that diverged as little
as possible from the current design but resolved these issues:

    1. There should be no need to interact with the reactor or the persistent
       scheduler directly.  All repeating event setup and scheduling should be
       done by framework code, so as to deal with errors and guaranteeing
       execution across restarts in a uniform way.

    2. There should be less boilerplate.  The amount of code necessary to
       implement such a simple object when there is so much infrastructure
       already is bordering on ridiculous.

    3. Correct timing interaction should be supported directly by the
       framework.  This is similar to point #1: the calls to callLater should
       be eliminated and replaced with something reactor-aware, so that other
       things like the mouse listening for speech can be ordered properly based
       on their responsiveness stats, rather than ad-hoc timed event ordering.

All of these issues are aided by reducing the amount of procedural code and
introducing some procedural statements.  We also agreed early in the
conversation that "magic" (automatic generation of interfaces, especially)
needed be avoided, since the savings that such implicit behavior generate are
often misleading, since you end up paying for the complexity they add in other
ways.

We eventually decided that our ideal interface would look something like this:

    class Bite(TargetAction):
        ...

    class Salute(TargetAction):
        ...

    class HiraganaMouse(Item, IntelligenceMixin):
        currentChallenge = attributes.text(default=None)
        actor = attributes.reference()

        def vetteChallengeResponse(self, romajiResponse): ...

        def challenge(self):
            if self.currentChallenge is not None:
                Bite.perform(actor=self.actor, target=random.choice(
                        Actor.getAllPresentNear(self.actor)))
            self.currentChallenge = random.choice(japanese.hiragana.keys())
            Say.perform(actor=self.actor, string=self.currentChallenge)
        events.whenPlayersPresentRepeat(10.0, challenge)

        def responseReceived(self, sayAction):
            if self.vetteChallengeResponse(sayAction.string):
                sayAction.consequentlyDo(Salute(actor=self, target=sayAction.actor))
            else:
                sayAction.consequentlyDo(Bite(actor=self, target=sayAction.actor))
        Say.whenSucceded(responseReceived)

This is still kind of vague, but I think it communicates the general idea.  It
is, as it should be, FAR shorter and less error-prone than the current hiragana
mouse implementation.

There are a couple of things going on here.  I'm going to list them in order of
most to least important, to get the things which are probably contentious and I
don't really care about out of the way first:

    - The "IntelligenceMixin" class is just there to say "maybe we should do
      something to help with the drudgery associated with setting up a
      connection to an actor".  A mixin like this is not necessarily the right
      thing.

    - The "perform" classmethod immediately performs an action, with no
      parsing.  This is really just a utility class method, which reads a bit
      more nicely than the current form, "Bite().do(self.actor, None, ...)"

    - You'll notice a few actions being constructed with arguments, but not
      performed.  This is actually an important change.  Actions, really, are
      supposed to be stateful.  While this particular API is not necessarily
      the right way to go (although it would certainly be useful) the notion of
      action objects holding state is important, and I'll get to that in a
      minute...

    - There is no repeating timer manually managed in this code.  The use case
      it proposes, "do something every X seconds when I am present in a
      player's sensory field", is extremely common.  Even in our meager set of
      examples I can think of several off the top of my head: the mouse, the
      cockroach, the cloud teleporter in Divunal, the elevator in tenth's
      mansion, the mummy's moaning in Inheritance. I am therefore proposing an
      actual built-in API for doing that.  The
      'events.whenPlayersPresentRepeat' API is declarative: it says "when
      players are present, call the mentioned method every X seconds".  This
      may require a bit more context be passed to it, for example perhaps the
      'actor' attribute (since raw Items do not have location), but I think the
      idea is basically sound.

    - Say.whenSucceded is similarly a declaration of intent: it says "when a
      'Say' event succeeds, execute that method with this event".  The argument
      given is the "Say" event.

    - Notice I said the "Say" event and not the "SpeechEvent" event.  The idea
      there is, to reduce boilerplate on the event side of things (and, for
      example, to allow for the use of the handy 'Bite' and 'Salute' events,
      rather than a generic message), we take advantage of the fact that
      actions are objects (with appropriate state) and use them as the
      success-response event for their own execution.

    - The 'consequentlyDo' method of Action is the implementation of ordering
      referred to above.  Actions have Consequences.  In this example (and I
      think for the first implementation) consequences will be non-persistent
      and will execute at the end of the transaction that the subject Action is
      executing in.  An action's lifecycle is like this:

        parse -> do -> perform checks, broadcast test events -> broadcast
        success event -> execute consequences -> send network notifications to
        clients

      radix and I discussed a potential need for transactional (future, timed)
      consequences, and for some things that will definitely eventualy be
      necessary, but for the time being we can just have methods which accept
      actions and possibly arbitrary Python callables.  Consequences SHOULD NOT
      raise exceptions, and if they do, it's a catastrophic failure of the
      whole event - for example, consider that one might in some cases
      implement a "consequence" of buying something being the decrement of your
      account.  Consequences have to be inexorably tied to their causes.

It looks to me like the code is already in a shape fairly amenable to most of
these changes, and with a little bit of fiddling with argument lists, all the
existing actions should be kept in working shape without trouble.

The major amount of work is going to be in the particulars of the
implementation of the event-responder decorators.  While I'm not completely
sure how the implementation is going to work, I don't forsee any major
difficulties.  The basic idea is just to leverage the existing code, perhaps
changing the signature of 'prepare' to take a specific IEvent type rather than
a IConcept (although in practice it would always be a more specific interface,
since you'd be registering by asking the type of event involved).  The changes
to the interaction between parsing and instantiation of items seems entirely
straightforward.

What this does NOT involve:

    - no changes to proxies
    - nothing to do with IWaveform
    - no illumination- or tethering-based use-cases

I think that the dynamic proxies implementation is mostly OK and we can happily
address those areas once we have something as concrete as the hiragana mouse
(i.e. an actual game mechanic use for darkness) to base some design discussions
off of.  The mouse made for a remarkably focused discussion of this area of the
code: thanks JP and Chris.

If I'm lucky I'll have some time this weekend to actually attack this, after
having finished off the tagbox branch and my taxes this week.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://twistedmatrix.com/pipermail/reality/attachments/20061012/6429b7c3/attachment.htm


More information about the Reality mailing list