<html><body>Well, it's been, what, 5 months since the last post here? &#160;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. &#160;I started<br />off thinking that we needed some comprehensive refactoring to do this, but I'm<br />glad to say I was disuaded. &#160;The code is pretty close to how it should work<br />already. &#160;Everything happens in roughly the right order and all the right<br />objects exist.<br /><br />First, we framed the problem:<br /><br />&#160; &#160; The hiragana mouse was too hard to write, hard to test, and has lots of<br />&#160; &#160; code which is prone to transaction errors (ie, reactor interaction) and<br />&#160; &#160; 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 />&#160; &#160; 1. There should be no need to interact with the reactor or the persistent<br />&#160; &#160; &#160; &#160;scheduler directly. &#160;All repeating event setup and scheduling should be<br />&#160; &#160; &#160; &#160;done by framework code, so as to deal with errors and guaranteeing<br />&#160; &#160; &#160; &#160;execution across restarts in a uniform way.<br /><br />&#160; &#160; 2. There should be less boilerplate. &#160;The amount of code necessary to<br />&#160; &#160; &#160; &#160;implement such a simple object when there is so much infrastructure<br />&#160; &#160; &#160; &#160;already is bordering on ridiculous.<br /><br />&#160; &#160; 3. Correct timing interaction should be supported directly by the<br />&#160; &#160; &#160; &#160;framework. &#160;This is similar to point #1: the calls to callLater should<br />&#160; &#160; &#160; &#160;be eliminated and replaced with something reactor-aware, so that other<br />&#160; &#160; &#160; &#160;things like the mouse listening for speech can be ordered properly based<br />&#160; &#160; &#160; &#160;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. &#160;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 />&#160; &#160; class Bite(TargetAction):<br />&#160; &#160; &#160; &#160; ...<br /><br />&#160; &#160; class Salute(TargetAction):<br />&#160; &#160; &#160; &#160; ...<br /><br />&#160; &#160; class HiraganaMouse(Item, IntelligenceMixin):<br />&#160; &#160; &#160; &#160; currentChallenge = attributes.text(default=None)<br />&#160; &#160; &#160; &#160; actor = attributes.reference()<br /><br />&#160; &#160; &#160; &#160; def vetteChallengeResponse(self, romajiResponse): ...<br /><br />&#160; &#160; &#160; &#160; def challenge(self):<br />&#160; &#160; &#160; &#160; &#160; &#160; if self.currentChallenge is not None:<br />&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; Bite.perform(actor=self.actor, target=random.choice(<br />&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; Actor.getAllPresentNear(self.actor)))<br />&#160; &#160; &#160; &#160; &#160; &#160; self.currentChallenge = random.choice(japanese.hiragana.keys())<br />&#160; &#160; &#160; &#160; &#160; &#160; Say.perform(actor=self.actor, string=self.currentChallenge)<br />&#160; &#160; &#160; &#160; events.whenPlayersPresentRepeat(10.0, challenge)<br /><br />&#160; &#160; &#160; &#160; def responseReceived(self, sayAction):<br />&#160; &#160; &#160; &#160; &#160; &#160; if self.vetteChallengeResponse(sayAction.string):<br />&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; sayAction.consequentlyDo(Salute(actor=self, target=sayAction.actor))<br />&#160; &#160; &#160; &#160; &#160; &#160; else:<br />&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; sayAction.consequentlyDo(Bite(actor=self, target=sayAction.actor))<br />&#160; &#160; &#160; &#160; Say.whenSucceded(responseReceived)<br /><br />This is still kind of vague, but I think it communicates the general idea. &#160;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. &#160;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 />&#160; &#160; - The "IntelligenceMixin" class is just there to say "maybe we should do<br />&#160; &#160; &#160; something to help with the drudgery associated with setting up a<br />&#160; &#160; &#160; connection to an actor". &#160;A mixin like this is not necessarily the right<br />&#160; &#160; &#160; thing.<br /><br />&#160; &#160; - The "perform" classmethod immediately performs an action, with no<br />&#160; &#160; &#160; parsing. &#160;This is really just a utility class method, which reads a bit<br />&#160; &#160; &#160; more nicely than the current form, "Bite().do(self.actor, None, ...)"<br /><br />&#160; &#160; - You'll notice a few actions being constructed with arguments, but not<br />&#160; &#160; &#160; performed. &#160;This is actually an important change. &#160;Actions, really, are<br />&#160; &#160; &#160; supposed to be stateful. &#160;While this particular API is not necessarily<br />&#160; &#160; &#160; the right way to go (although it would certainly be useful) the notion of<br />&#160; &#160; &#160; action objects holding state is important, and I'll get to that in a<br />&#160; &#160; &#160; minute...<br /><br />&#160; &#160; - There is no repeating timer manually managed in this code. &#160;The use case<br />&#160; &#160; &#160; it proposes, "do something every X seconds when I am present in a<br />&#160; &#160; &#160; player's sensory field", is extremely common. &#160;Even in our meager set of<br />&#160; &#160; &#160; examples I can think of several off the top of my head: the mouse, the<br />&#160; &#160; &#160; cockroach, the cloud teleporter in Divunal, the elevator in tenth's<br />&#160; &#160; &#160; mansion, the mummy's moaning in Inheritance. I am therefore proposing an<br />&#160; &#160; &#160; actual built-in API for doing that. &#160;The<br />&#160; &#160; &#160; 'events.whenPlayersPresentRepeat' API is declarative: it says "when<br />&#160; &#160; &#160; players are present, call the mentioned method every X seconds". &#160;This<br />&#160; &#160; &#160; may require a bit more context be passed to it, for example perhaps the<br />&#160; &#160; &#160; 'actor' attribute (since raw Items do not have location), but I think the<br />&#160; &#160; &#160; idea is basically sound.<br /><br />&#160; &#160; - Say.whenSucceded is similarly a declaration of intent: it says "when a<br />&#160; &#160; &#160; 'Say' event succeeds, execute that method with this event". &#160;The argument<br />&#160; &#160; &#160; given is the "Say" event.<br /><br />&#160; &#160; - Notice I said the "Say" event and not the "SpeechEvent" event. &#160;The idea<br />&#160; &#160; &#160; there is, to reduce boilerplate on the event side of things (and, for<br />&#160; &#160; &#160; example, to allow for the use of the handy 'Bite' and 'Salute' events,<br />&#160; &#160; &#160; rather than a generic message), we take advantage of the fact that<br />&#160; &#160; &#160; actions are objects (with appropriate state) and use them as the<br />&#160; &#160; &#160; success-response event for their own execution.<br /><br />&#160; &#160; - The 'consequentlyDo' method of Action is the implementation of ordering<br />&#160; &#160; &#160; referred to above. &#160;Actions have Consequences. &#160;In this example (and I<br />&#160; &#160; &#160; think for the first implementation) consequences will be non-persistent<br />&#160; &#160; &#160; and will execute at the end of the transaction that the subject Action is<br />&#160; &#160; &#160; executing in. &#160;An action's lifecycle is like this:<br /><br />&#160; &#160; &#160; &#160; parse -&gt; do -&gt; perform checks, broadcast test events -&gt; broadcast<br />&#160; &#160; &#160; &#160; success event -&gt; execute consequences -&gt; send network notifications to<br />&#160; &#160; &#160; &#160; clients<br /><br />&#160; &#160; &#160; radix and I discussed a potential need for transactional (future, timed)<br />&#160; &#160; &#160; consequences, and for some things that will definitely eventualy be<br />&#160; &#160; &#160; necessary, but for the time being we can just have methods which accept<br />&#160; &#160; &#160; actions and possibly arbitrary Python callables. &#160;Consequences SHOULD NOT<br />&#160; &#160; &#160; raise exceptions, and if they do, it's a catastrophic failure of the<br />&#160; &#160; &#160; whole event - for example, consider that one might in some cases<br />&#160; &#160; &#160; implement a "consequence" of buying something being the decrement of your<br />&#160; &#160; &#160; account. &#160;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. &#160;While I'm not completely<br />sure how the implementation is going to work, I don't forsee any major<br />difficulties. &#160;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). &#160;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 />&#160; &#160; - no changes to proxies<br />&#160; &#160; - nothing to do with IWaveform<br />&#160; &#160; - 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. &#160;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>