<html><body>Well, it's been, what, 5 months since the last post here?  And most of the <br />problems we were thinking about then are the same ones we're thinking about now.<br /><br />Today radix and I had a chat about how events ought to work, again.  I started<br />off thinking that we needed some comprehensive refactoring to do this, but I'm<br />glad to say I was disuaded.  The code is pretty close to how it should work<br />already.  Everything happens in roughly the right order and all the right<br />objects exist.<br /><br />First, we framed the problem:<br /><br />    The hiragana mouse was too hard to write, hard to test, and has lots of<br />    code which is prone to transaction errors (ie, reactor interaction) and<br />    does not cooperate sanely with other code.<br /><br />Our goal was to come up with an "event system" design that diverged as little<br />as possible from the current design but resolved these issues:<br /><br />    1. There should be no need to interact with the reactor or the persistent<br />       scheduler directly.  All repeating event setup and scheduling should be<br />       done by framework code, so as to deal with errors and guaranteeing<br />       execution across restarts in a uniform way.<br /><br />    2. There should be less boilerplate.  The amount of code necessary to<br />       implement such a simple object when there is so much infrastructure<br />       already is bordering on ridiculous.<br /><br />    3. Correct timing interaction should be supported directly by the<br />       framework.  This is similar to point #1: the calls to callLater should<br />       be eliminated and replaced with something reactor-aware, so that other<br />       things like the mouse listening for speech can be ordered properly based<br />       on their responsiveness stats, rather than ad-hoc timed event ordering.<br /><br />All of these issues are aided by reducing the amount of procedural code and<br />introducing some procedural statements.  We also agreed early in the<br />conversation that "magic" (automatic generation of interfaces, especially)<br />needed be avoided, since the savings that such implicit behavior generate are<br />often misleading, since you end up paying for the complexity they add in other<br />ways.<br /><br />We eventually decided that our ideal interface would look something like this:<br /><br />    class Bite(TargetAction):<br />        ...<br /><br />    class Salute(TargetAction):<br />        ...<br /><br />    class HiraganaMouse(Item, IntelligenceMixin):<br />        currentChallenge = attributes.text(default=None)<br />        actor = attributes.reference()<br /><br />        def vetteChallengeResponse(self, romajiResponse): ...<br /><br />        def challenge(self):<br />            if self.currentChallenge is not None:<br />                Bite.perform(actor=self.actor, target=random.choice(<br />                        Actor.getAllPresentNear(self.actor)))<br />            self.currentChallenge = random.choice(japanese.hiragana.keys())<br />            Say.perform(actor=self.actor, string=self.currentChallenge)<br />        events.whenPlayersPresentRepeat(10.0, challenge)<br /><br />        def responseReceived(self, sayAction):<br />            if self.vetteChallengeResponse(sayAction.string):<br />                sayAction.consequentlyDo(Salute(actor=self, target=sayAction.actor))<br />            else:<br />                sayAction.consequentlyDo(Bite(actor=self, target=sayAction.actor))<br />        Say.whenSucceded(responseReceived)<br /><br />This is still kind of vague, but I think it communicates the general idea.  It<br />is, as it should be, FAR shorter and less error-prone than the current hiragana<br />mouse implementation.<br /><br />There are a couple of things going on here.  I'm going to list them in order of<br />most to least important, to get the things which are probably contentious and I<br />don't really care about out of the way first:<br /><br />    - The "IntelligenceMixin" class is just there to say "maybe we should do<br />      something to help with the drudgery associated with setting up a<br />      connection to an actor".  A mixin like this is not necessarily the right<br />      thing.<br /><br />    - The "perform" classmethod immediately performs an action, with no<br />      parsing.  This is really just a utility class method, which reads a bit<br />      more nicely than the current form, "Bite().do(self.actor, None, ...)"<br /><br />    - You'll notice a few actions being constructed with arguments, but not<br />      performed.  This is actually an important change.  Actions, really, are<br />      supposed to be stateful.  While this particular API is not necessarily<br />      the right way to go (although it would certainly be useful) the notion of<br />      action objects holding state is important, and I'll get to that in a<br />      minute...<br /><br />    - There is no repeating timer manually managed in this code.  The use case<br />      it proposes, "do something every X seconds when I am present in a<br />      player's sensory field", is extremely common.  Even in our meager set of<br />      examples I can think of several off the top of my head: the mouse, the<br />      cockroach, the cloud teleporter in Divunal, the elevator in tenth's<br />      mansion, the mummy's moaning in Inheritance. I am therefore proposing an<br />      actual built-in API for doing that.  The<br />      'events.whenPlayersPresentRepeat' API is declarative: it says "when<br />      players are present, call the mentioned method every X seconds".  This<br />      may require a bit more context be passed to it, for example perhaps the<br />      'actor' attribute (since raw Items do not have location), but I think the<br />      idea is basically sound.<br /><br />    - Say.whenSucceded is similarly a declaration of intent: it says "when a<br />      'Say' event succeeds, execute that method with this event".  The argument<br />      given is the "Say" event.<br /><br />    - Notice I said the "Say" event and not the "SpeechEvent" event.  The idea<br />      there is, to reduce boilerplate on the event side of things (and, for<br />      example, to allow for the use of the handy 'Bite' and 'Salute' events,<br />      rather than a generic message), we take advantage of the fact that<br />      actions are objects (with appropriate state) and use them as the<br />      success-response event for their own execution.<br /><br />    - The 'consequentlyDo' method of Action is the implementation of ordering<br />      referred to above.  Actions have Consequences.  In this example (and I<br />      think for the first implementation) consequences will be non-persistent<br />      and will execute at the end of the transaction that the subject Action is<br />      executing in.  An action's lifecycle is like this:<br /><br />        parse -> do -> perform checks, broadcast test events -> broadcast<br />        success event -> execute consequences -> send network notifications to<br />        clients<br /><br />      radix and I discussed a potential need for transactional (future, timed)<br />      consequences, and for some things that will definitely eventualy be<br />      necessary, but for the time being we can just have methods which accept<br />      actions and possibly arbitrary Python callables.  Consequences SHOULD NOT<br />      raise exceptions, and if they do, it's a catastrophic failure of the<br />      whole event - for example, consider that one might in some cases<br />      implement a "consequence" of buying something being the decrement of your<br />      account.  Consequences have to be inexorably tied to their causes.<br /><br />It looks to me like the code is already in a shape fairly amenable to most of<br />these changes, and with a little bit of fiddling with argument lists, all the<br />existing actions should be kept in working shape without trouble.<br /><br />The major amount of work is going to be in the particulars of the<br />implementation of the event-responder decorators.  While I'm not completely<br />sure how the implementation is going to work, I don't forsee any major<br />difficulties.  The basic idea is just to leverage the existing code, perhaps<br />changing the signature of 'prepare' to take a specific IEvent type rather than<br />a IConcept (although in practice it would always be a more specific interface,<br />since you'd be registering by asking the type of event involved).  The changes<br />to the interaction between parsing and instantiation of items seems entirely<br />straightforward.<br /><br />What this does NOT involve:<br /><br />    - no changes to proxies<br />    - nothing to do with IWaveform<br />    - no illumination- or tethering-based use-cases<br /><br />I think that the dynamic proxies implementation is mostly OK and we can happily<br />address those areas once we have something as concrete as the hiragana mouse<br />(i.e. an actual game mechanic use for darkness) to base some design discussions<br />off of.  The mouse made for a remarkably focused discussion of this area of the<br />code: thanks JP and Chris.<br /><br />If I'm lucky I'll have some time this weekend to actually attack this, after<br />having finished off the tagbox branch and my taxes this week.<br /></body></html>