Speed, speed, speed

Lately I spend a lot of my time wrestling with performance issues under Glulx.

This is partly because most of what I’m working on is, in one way or another, a test of the big conversation library I have in progress, and this is necessarily very code-heavy. But if I’m wrestling with it, that’s not good, because it means other authors using it are likely going to have to do the same — and they may have less patience with the problem, or just find that the whole thing is too irritating. For small projects, the system works fine, but for Alabaster (ca. 250 quips now) even after a fair amount of work, there is a 1-3 second delay on a lot of the moves. That is Just Not Okay.

There are three points of attack here, as I understand the problem:

Optimizing Inform so that it compiles code that will run faster. Several things have come out of my (or other people’s) performance problems in the past — Graham has sped up pathfinding and handling of large “Instead…” type rulebooks, and our recent conversations might conceivably lead to speeding up the performance of object-based rulebooks (which are one of the key pieces of the conversation library, since every piece of conversation has to be consulted to see whether it can be said at the moment).

Optimizing my own source. I’m working on that: finding more ways to cache needed information, minimize the scope of loops, etc. (And, since I mean this to be a released extension down the road, figuring out how to give authors of other work help optimizing their stuff, where those optimizing tricks cannot be folded into the core library but require some per-game customization.)

Speeding up interpreters so that any speed problems that remain are minimized for the end-user. Here Git seems to have a clear advantage over its competitors, but I haven’t had luck so far building it for OS X. (That might be because I have no ****ing idea what I’m doing. I am not exactly an old hand with gcc.) Has anyone else had luck with this? Is there an already-compiled OS X Git (using the latest, January 2008 version of the code) out there that I just don’t know about?

It looks as though Git might be what’s under the hood in Spatterlight (at least, Spatterlight seems to play faster than Zoom, at least a bit) — but if I actually open the Spatterlight package, it doesn’t list Git, only Glulxe. And I don’t see anywhere within the Spatterlight application to find out what version of interpreter it’s using. Which I guess doesn’t matter too much if it does, in fact, play faster… but this is all kind of mystifying.

26 thoughts on “Speed, speed, speed”

  1. There’s also the idea that maybe Inform will just suck at this sort of thing and ultimately there’s nothing you can really do. Even if you fix this current crop of speed problems, the z-machine’s history just suggests they will come back as games get even more aggressive in what they attempt to do.

    Glulx, while freeing up some of the limitations, is really just a bigger z-machine and suggests that same history of problems. Plus, as you indicate the development and inclusion of Glulx has been mystifying at best. I think the lesson here is don’t build new platforms on top of clunky old technology that should have been retired decades ago.

  2. Can you explain in more detail how the Glulx architecture guarantees that this is the case?

    If not, I’m disinclined to throw out years’ worth of work and (perhaps more to the point) give up on the kind of IF I want to write.

  3. The “kind of IF you want to write” shouldn’t be predicated on just one system. That’s like saying you can only write an academic paper if you have a Selectric typewriter instead of a word processor.

    As far as the Glulx architecture, I don’t say it guarantees anything necessarily. That’s part of its problem. It doesn’t strike me as any more future-driven than the z-machine. You’re talking about a piece of technology here that’s predicated upon playing text games but has a cumbersome (and limited) system for applying text styles to such games. That’s just one element. Beyond that it’s the nature of Inform 7 itself, which you seem to be writing in. *That’s* where your architecture overall is flawed. Rules-based systems *ALWAYS* tap out on performance.

    My point was that the z-machine has always been more about its limitations than it strengths. I’m not sure Glulx has done much to really offset that too much. Now factor in Inform 7 with its focus on a rules-based achitecture and you’ve basically crafted a situation where you have to constantly hope interpreter authors will be able to create better and faster interpreters or hope that more compiler optimization will save the day.

    Does that sound like the type of thing upon which to base the “kind of IF you want to write”?

  4. It’s the type of thing upon which the Web is based. That’s not a bad example to follow. (Javascript is Javascript, but recent work has produced ferocious speed improvements for the JS engines in web browsers.)

    Really, the limitations of Glulx should be *least* visible when doing internal computational work. At that level, it’s just a byte-oriented VM — it’s not architecturally different from the real CPU. Nothing about the problem of selecting conversation topics is architecturally horrible for it.

    Compiling the high-level structures of I7 down to efficient code seems to me to be the — not the hardest problem, but the problem where there is most to be gained. I’ve always wanted the compiler to *really* optimize rulebooks — down to a single routine full of branch opcodes, if possible. This is logically possible (in the absence of procedural rules) but requires a lot of futzy pattern-matching logic.

    (I do not understand enough of what Git does to imagine importing its improvements into (portable) Glulxe source.)

  5. I’ve haven’t played Alabaster, but I have read your posts about it, and I don’t totally get it. e.g. it doesn’t sound like *that* computationally intensive of a concept. That said, I also don’t get why you need to test every possible quip on every turn; surely the state of the game rules certain ones out? If so, can’t you remove those from consideration as the state of the game changes?

    Ideally you’d want a profiling tool to help you figure out what’s going on, but I’m going to guess such a thing doesn’t exist for glulx. In the absence of a tool like that, you can hand-roll something with simple timers in the code; I think the z-machine wouldn’t let you do that easily, but perhaps the glulx api would?

  6. @ James: Erm, I have the sense of being trolled at this point. In the event that that’s not your intention, though:

    (a) I’m not at all convinced that rule-based systems are especially inadequate for IF (and simple assertion doesn’t get very far with convincing me).

    (b) These decisions aren’t made in a vacuum. If I could snap my fingers and bring into being a beautiful fast new multimedia VM with elegant text-handling and cross-platform support and the ability to run via browser at need, I’d do so — but getting a new virtual machine designed and implemented, and the bugs worked out, and the interpreters written for all the appropriate platforms, and authors comfortable enough with the VM to use it, and players comfortable enough with the VM to play the results, is a project of many years. (And I’d first have to acquire the requisite skills, which would also be a project of more years.) The stable stuff is mostly a decade or more old, or based on pre-existing architecture, because that is how long it takes for the hobbyist community to generate something stable and earn it widespread acceptance.

    As things stand at the moment, the only other VM remotely in contention is T3, and it has cross-platform interpretation issues of its own (and occasionally speed issues, too, depending on just what the author has done). Switching languages and VMs wouldn’t solve my problems, just hand me a new and less-familiar version of them — plus the learning curve of learning a new language and porting my code.

    So it seems to the point to at least try to improve the existing tools than to start over from scratch whenever they start to show strain.

    @Chris: Perhaps I put that badly; some quips can be moved out of consideration depending on the state of play, and I use that information to optimize a bit. I do also weed out unrepeatable quips as they’re spoken, so that the game has fewer and fewer things to worry about as it goes along.

    At the same time, I don’t want to spend too much time moving unspeakable quips out of consideration, because at that point I’m essentially redoing the “can this be said?” work… just in a different part of the turn.

    Currently, the system has to do the following each turn:

    1) parse the player’s command for whether it refers to an available quip; the parsing has multiple stages because quips can be identified not just by their own particular name but by the name of the subject they pertain to. (So certain quips would know that they are “about” the evil queen, and the player asking about the evil queen without being more specific will get the option of saying any of those).

    2) having said what the player wants to say, check what quips are available *next*, and tag them appropriately.

    3) given what it knows about available quips, choose which of those quips are sufficiently appropriate next steps in the conversation that they should be recommended to the player; write that recommendation (by way of a certain amount of filtering that lets the author customize how quips are recommended).

    The design could run faster if I were willing to make the author hard-code more; for instance, we could make the author specify by hand what follow-up suggestions should be offered to the player at each turn, which would eliminate one pass through the available quips. But this is a lot more intensive for the author (and could miss some interesting possibilities).

    As far as I can tell, the single greatest problem in getting more conversationally-rich IF out there is the tedium of production, so my goal is to transfer some of the more dull and fiddly aspects of authorship to the computer.

  7. Git illustrates that an optimized interpreter can do a lot to speed up games. However, I think profile-based optimizations on the entire game+interpreter system will be more profitable than simply speeding up the interpreter. I’ve made vague references to FyreVM’s “veneer acceleration” feature here and there; this is how it came about.

    I profiled the interpreter itself, optimized the key paths, and got some minor improvements. But the dramatic improvements came from profiling the game we were developing: I discovered that something like two-thirds of the game’s execution time was being spent in a handful of veneer routines. Remember, Glulx doesn’t have built-in opcodes for reading properties or testing object relationships like Z does, and Inform does a lot of that. So I moved those routines into the interpreter, and suddenly performance wasn’t an issue anymore.

    There are principled reasons for the interpreter to remain unaware of how the game is organizing its memory. But in practice, nearly everything a Glulx interpreter ever runs is going to be an Inform game, incorporating the same veneer and library that have been part of Inform for years, and so it can expect to spend most of its time interpreting the same few routines. It’s possible, though not trivial, for an interpreter to detect when one of these routines is being called and substitute its own code.

    On a related note, it’d be nice to have a profiling version of Glulxe.

  8. Emily, I agree with you completely about moving the burden from the author to the computer — I just wonder if there is a clever way of precomputing things at compile time or even when the game is initially loaded, or splitting the work among turns somehow so the delays aren’t so painful for the player.

  9. I just wonder if there is a clever way of precomputing things at compile time or even when the game is initially loaded, or splitting the work among turns somehow so the delays aren’t so painful for the player.

    I’m doing some computation at start-up, and that shifts some of the burden away from individual turns. I haven’t had any brilliant ideas about how to force anything to precompute at compile-time, though.

    But in practice, nearly everything a Glulx interpreter ever runs is going to be an Inform game, incorporating the same veneer and library that have been part of Inform for years, and so it can expect to spend most of its time interpreting the same few routines.

    This sounds extremely promising.

  10. As things stand at the moment, the only other VM remotely in contention is T3, and it has cross-platform interpretation issues of its own (and occasionally speed issues, too, depending on just what the author has done).

    This has piqued my curiosity: I’m now wondering what speed issues you’ve encountered in TADS 3 games! Of course it’s always possible for an author to write code that will make a game run really slow in just about any system; creating a large number of dynamic objects in TADS 3, for example, seems to be a fairly reliable way of making a T3 game virtually grind to a halt. But I can’t think of many instances where I’ve come across speed issues in practice working with TADS 3. The nearest real example that springs to mind is the pathfinding used in the GO TO command in Blighted Isle, which might cause a noticeable lag under some circumstances (although in TADS 3 I can cache the path in a list for use in CONTINUE, which helps speed things up a little; it would be nice to be able to the same in I7). Did you have any other examples in mind?

    Switching languages and VMs wouldn’t solve my problems, just hand me a new and less-familiar version of them — plus the learning curve of learning a new language and porting my code.

    I certainly wouldn’t expect you to switch languages, but you have set me idly pondering how one might go about implementing your conversation system in TADS 3, or whether one would in fact simply take a completely different approach. Off the top of my head I think it would be perfectly possible to write a TADS 3 application (or part of a TADS 3 extension) that generated some conversational code and thus reduced some of the authorial busywork. What’s less clear to me is whether one would even try to copy your conversational system all that closely or whether one would build on what’s already there in TADS 3 (and existing extensions) to produce something with approximate functional equivalence. What I also don’t know is whether such a system would encounter the same speed problems (conversation in TADS 3 normally doesn’t exhibit speed problems, but then I guess it isn’t normally trying to do quite so much work).

    I suppose the tangent I’m going off on here is wondering how much the design of something like an IF conversation extension is shaped by the system it’s written in. I’m perhaps speculating that had you been a TADS 3 author you might have come up with a rather different design!

    None of which is remotely helpful to the actual problem you’re trying to tackle, of course!

    The other thing at the back of my mind here is that it might be an interesting experiment to try to implement your conversation system in TADS 3 and then see if it hit the same speed problems; but I don’t really have the time for that right now, and I suspect that if I did I might well end up with a different system anyway, for the reasons already suggested.

  11. I haven’t had any brilliant ideas about how to force anything to precompute at compile-time, though.

    You could hack something like that by having the game save a copy of itself after doing the initial computations:

    Home is a room.

    An apple is a kind of thing. Six apples are in Home.

    Initial setup:
    repeat with X running from 1 to 10000000:
    let Y be X;
    now the player carries a random apple.

    [====================================================]

    Initial setup is a rulebook.

    The initial setup flag is a truth state that varies.

    First startup rule (this is the perform initial setup rule):
    if the initial setup rulebook is not empty and the initial setup flag is false:
    now the initial setup flag is true;
    consider the initial setup rulebook;
    save the story file.

    To save the story file: (- SaveStoryFile(); -).

    Include (-
    [ SaveStoryFile fref stream len;
    ! open a file stream
    fref = glk_fileref_create_by_prompt(fileusage_Data | fileusage_BinaryMode, filemode_Write, 0);
    if (~~fref) rfalse;
    stream = glk_stream_open_file(fref, filemode_Write, 0);
    if (~~stream) rfalse;

    ! write the entire contents of memory. we have to write the first byte separately, since
    ! Glk interprets a buffer pointer of zero as NULL instead of the first byte in memory.
    glk_put_char_stream(stream, 0->0);
    @getmemsize len;
    glk_put_buffer_stream(stream, 1, len);
    glk_stream_close(stream, 0);
    ];
    -).

    This may need some additional code to repair the checksum. Since Glk file functions silently fail in the I7 IDE (at least on Windows), you can test normally, then just run the game once after you release it, and distribute the file that it produces.

  12. @Emily: “(a) I’m not at all convinced that rule-based systems are especially inadequate for IF (and simple assertion doesn’t get very far with convincing me).”

    I don’t think this is specific to IF. In many cases, rules-based systems run into performance problems. This doesn’t have to be in IF. That was my point, really, which is that you’re building IF (at least in Inform) on top of an architecture design (rules) that is known to have performance problems and then saying you are spending a lot of time fighting performance problems. This isn’t surprising.

    Like Eric, I would be curious what speed issues you’ve found with the T3 VM. As you said, “a simple assertion doesn’t get very far with convincing me.”

    I’d agree on the cross-platform aspect of TADS 3 but that’s more because people haven’t taken the time to do it as opposed to an inherent limitation. There’s also the idea that a system that can be ported everywhere often has to make certain tradeoffs compared to one that isn’t so portable.

  13. Re. speed issues in T3: I recall coming across some with pathfinding, and more so with goal-seeking characters; possibly other things as well which I wasn’t able to diagnose because I didn’t know what was going on under the hood. The most significant slowdown in a TADS 3 game that I can recall was in Battle of Walcot Keep, which was ambitiously doing a whole load of goal-seeking per turn; it’s hardly surprising that it should have done so.

    On the speed issue and rules in general: “rules-based systems can be slow therefore it is the rule-based architecture that is slowing you down” has the same logical flaw as “dogs have short life-spans, so your dog died of old age”. It sounds, especially given what vaporware has said, as though the problem may not be primarily located in the architecture of I7 (though it would be cool if it were possible to compile something highly optimized, as zarf suggests) but in the need to access properties through slow VM features. My conversation system as implemented in I7 does make it necessary to access properties a lot — possibly more often than I would need to given an object-oriented architecture — but I think the difference is probably an integer factor. (As a corroborating piece of evidence, I had speed issues under Glulx with City of Secrets, which had a not-identical but similarly-conceived conversation system running under I6. This is not proof that they derived from the same flaws, but it is to my mind suggestive.)

    Re. my conversation system revised to go with TADS 3, or in TADS 3, or the like: I am sure you’re right. If I were a TADS 3 user, I would have written something quite different; I’d probably have been inclined to build something that went with the existing conversation system in TADS 3. As far as I can tell (from reading the TADS 3 conversation docs but never actually using the system for myself) that would mean the following:

    — The TADS 3 implementation by default doesn’t seem to have hooks for stopping a dialogue snippet between the player’s statement and the NPC’s answer. This could be faked up (it looks like) through AltTopics for the conversations I wanted to be able to interrupt, but it would work differently; I couldn’t do something (as I mean to do in Alabaster) that had an NPC categorically refuse to answer all questions after they were asked but before they were answered under certain conditions when the NPC was annoyed.

    I have also done some limited preliminary testing with having third-party NPCs interrupt conversation at that point if they find the thing you say inflammatory — this seems to work, but I haven’t done anything with it in a large WIP yet. In my very-plotty WIP, I am instead modeling dialogue flow by acting as though everyone in the room is a single interlocutor, since within any given scene people don’t go in and out. But I’d like my system to work if the author wants to have a game where, e.g., it matters not only whether you say something to X but also whether you say it in front of Y.

    — Along the same lines, my system assumes a greater degree of “threading” (in just the sense Mike means) than the TADS 3 system does; it’s not necessary to create separate ConvNode-like objects in order to keep track of special contexts, since the assumption is that the context of the last spoken quips always has the potential to be important. (The system currently keeps track of the last three things said, though this could be expanded if one really wanted.)

    This means that it was relatively easy to build in some sensitivity to violations of conversational expectation — i.e., if you leave one thread and start another, the system considers that to be “changing the subject”, and this is a hook which the NPC can use to comment, if appropriate. This is not an effect I want to use very frequently, but it is occasionally conducive to cool outcomes, especially in scenes where you and the NPC are working at cross-purposes.

    It also means that it’s small effort to write rules giving special responses to actions at any point in the conversation (for example, a SMILE command that produces generic results most of the time but is construed as insulting in response to one specific quip). One of my design goals was to make elements like this very easy to add: an author will go out of his way to create an effect if the game needs it, but may shrug off smaller polishing touches if they’re going to be a lot of work. But a rich collection of exceptional behavior is especially important for NPC writing: the accumulation of many small touches is what makes a character seem aware and alive.

    — In TADS 3, it is not clear to me how much the author can modify the presentation of the topic inventory on a case-by-case basis; I don’t think I’ve seen it done much, if it is allowed. I’m pretty sure this is something that someone could build into the existing TADS 3 implementation, though, so it’s probably not an important difference given an author who thinks it’s important in the first place. (In other words, if I had been implementing my system on top of what TADS 3 has, that is probably something I would have worked out.)

    — Conversely, TADS 3 does a lot with actor states making conversation available, and my system really doesn’t concern itself with that at all. It’s certainly possible to write rules checking what an actor is doing and revising (or refusing) his responses accordingly, but since there’s no built-in actor state to work with, the I7 implementation doesn’t look for such information. (On the other hand, the I7 implementation is aware of scenes, for the purpose of creating a game rich in both plot and dialogue, and I’ve found that very useful in another WIP.)

    — I think (and here again I’m not sure) that the I7 implementation has a more flexible handling of queued conversation for NPCs. I’m aware that in TADS 3 one can write AgendaItems to make people plan to say things, but part of what emerges from my implementation is the ability to schedule an NPC’s next comment for “whenever the existing line of conversation dries up”. That, again, depends on a strong degree of threadedness, because it checks for the point where the player has either run out of natural options or has paused for a turn and failed to pursue any of those options.

    This one is fairly important to me, though it may seem like a gimmick: a lot of experimentation with conversation flow suggests that people don’t like NPCs to talk proactively on every turn, especially if there are still options they wanted to pursue themselves (see the response to Best of Three), but that they also don’t like to be stuck in conversation (see the response to Galatea and numerous others). The sense that the NPC waits its turn, isn’t pushy, but does continue the conversation where appropriate, strikes me as essential to creating a plausible balance of dialogue. It also makes it pretty easy to set up scenes where an NPC has casual follow-up thoughts that are discarded if the player changes the subject before they’re spoken, or scenes where an NPC is following a semi-rigid script. (Both of those are things I’ve found myself wanting fairly frequently when writing new material.)

    It might be possible to rig this onto the TADS 3 system, but I think it would require some re-thinking of the convnode stuff. (Again, not having used that extensively, I may be speculating incorrectly.)

    Overall I think the largest differences have to do with the greater threadedness of the I7 version (which partly grows out of I7’s relations, though the City of Secrets conversation system was highly-threaded as well — it just expressed the fact less elegantly) and more fluid approach to assessing context that comes out of that (whereas TADS 3 stacks conversation elements inside one another to indicate priorities and special cases). And it is (again maybe not surprisingly) the threadedness that causes the most processing, because there are a large number of properties relating conversation items to one another, which need to be re-examined when assessing the current state of play.

    The automated conversation-assembly stuff could probably done (with appropriate revisions) in TADS 3.

  14. Okay, veneer acceleration:

    I went a little way down this road at the beginning of Glulx, putting the @linkedsearch (etc) opcodes in. I6 properties are linked lists, but I don’t think the I6 compiler ever got tweaked to use those opcodes. Did it?

    Putting routines wholesale into native code sounds like it could be worthwhile. It wouldn’t be too hard to set this up in a safe and backwards-compatible way: add a single new opcode @acclerate F N, where F is the address of a VM function and N is the index number of the optimized native function. If the terp has that native function built in, all future calls to F are optimized. If not, nothing happens. New native functions can be added to the terp but never changed.

    (It’s reasonable to implement this by actually patching the VM image in memory, changing the VM function header. That’d be a little faster than a hash table lookup on every VM function call.)

    It would be unwise to start this without the profiling Glulxe that you asked for, so let me take a look at that first.

  15. The most significant slowdown in a TADS 3 game that I can recall was in Battle of Walcot Keep, which was ambitiously doing a whole load of goal-seeking per turn; it’s hardly surprising that it should have done so.

    The Battle of Walcot Keep was also trying to do a lot of real-time stuff, which may make it an even more atypical example.

    I am sure you’re right. If I were a TADS 3 user, I would have written something quite different;

    Thanks for a very interesting and detailed response here. I’ll just offer a couple of observations.

    The TADS 3 implementation by default doesn’t seem to have hooks for stopping a dialogue snippet between the player’s statement and the NPC’s answer. This could be faked up (it looks like) through AltTopics

    There’s probably a neater way to do it if you wanted to do this on a regular basis. If it were me I’d probably override the topicResponse() method on TopicEntry to call three separate methods, one giving the PC’s question, one allowing a hook for interventions, and one giving the NPC’s response if nothing has intervened.

    In TADS 3, it is not clear to me how much the author can modify the presentation of the topic inventory on a case-by-case basis; I don’t think I’ve seen it done much, if it is allowed.

    Blighted Isle does it quite a bit. You may recall the player has the option of three levels of topic suggestions. The way I did that could in principle be adapted to just about any desired scheme for filtering the options offered.

    But perhaps you were thinking not so much of varying the list of topics offered as varying the way they’re introduced (varying the wording from “You could…”). You’re right that this hasn’t been done much if at all in TADS 3 games, but it’s doable in principle and something I’m experimenting with a bit in my current TADS 3 WIP.

    I think (and here again I’m not sure) that the I7 implementation has a more flexible handling of queued conversation for NPCs. I’m aware that in TADS 3 one can write AgendaItems to make people plan to say things, but part of what emerges from my implementation is the ability to schedule an NPC’s next comment for “whenever the existing line of conversation dries up”.

    That ought to be equally possible in TADS 3. Provided that there’s some way the game can test whether the existing line of conversation has dried up it can be part of the condition that triggers the AgendaItem.

    Along the same lines, my system assumes a greater degree of “threading” (in just the sense Mike means) than the TADS 3 system does; it’s not necessary to create separate ConvNode-like objects in order to keep track of special contexts, since the assumption is that the context of the last spoken quips always has the potential to be important. (The system currently keeps track of the last three things said, though this could be expanded if one really wanted.)

    I think this is the aspect that might require the greatest thought in any TADS 3 implementation. It might be that one wouldn’t want to use the ConvNode mechanism at all, but find some other way to thread conversations. Although TADS 3 doesn’t offer relations (apart from built-in ones like containment) as standard, one could probably design a data structure that does the same job (as TADS 3 in fact does with containment, which is internally implemented with lists). One might then base one’s threading on that. And although SpecialTopics only work inside ConvNodes my SayQuery extension could offer a way round that limitation.

    The main issue might be not so much whether you could design a functionally similar system in TADS 3, but whether or not it would end up as author-friendly as you’d like. Macros and templates might help a little here, as might a system for auto-generating some of the code.

  16. There’s probably a neater way to do it if you wanted to do this on a regular basis. If it were me I’d probably override the topicResponse() method on TopicEntry to call three separate methods, one giving the PC’s question, one allowing a hook for interventions, and one giving the NPC’s response if nothing has intervened.

    What I was wondering about here was whether doing something like that would ruin the ability to make topics also be shuffled or stopped lists (which seemed to be another rather clever key element of the TADS 3 approach).

    Tangent: variable answers to repeated questioning is something I mostly handle in I7 with text variations, though the latest version of Alabaster goes a bit further: for some topics that the player might ask about repeatedly, I have a list of clauses (each with several variant phrasings) that might make up the paragraph of the NPC’s answer. That looks like this:

    Mom-comments is a list of text that varies. Mom-comments is {
    “[one of]she was from the north, where they have fair skin[or]she looked like me[or]she came from away in the north[or]she was from a petty noble family in the north[or]she had very fair skin, like mine, because she came from the north[at random]”,
    “[one of]she liked music[or]she liked embroidery[or]I have a few of her embroidery pieces[or]she left some instruments behind[or]she was good at needlework[or]her talents were for music and embroidery[or]she played a few instruments[at random]”,
    “[one of]she died when I was very young[or]I was young when she died[or]I don’t remember her because she died before I was very old[at random]”
    }.

    Then each time the player asks the question, the game assembles a new paragraph by gluing the clauses together into sentences (and capitalizing and punctuating as necessary); sometimes it also prefaces the paragraph with a comment on the fact that you’ve asked before and she has no new information. (I also have it set up so that I can either present the clauses in the order given or shuffle them up for greater randomness.)

    This is probably massive overkill, to be honest — there’s nothing wrong from an implementation point of view with just running through three or four random variations, and certainly the effect of even a light randomization is much better than having the NPC repeat the same thing verbatim every time. Mostly I did this because it seemed like a fun little thing to code up. But I sort of like the effect — one result is that it’s possible to eke out a few tiny extra plot-irrelevant details by pestering the NPC about something repeatedly, just as in real life if you asked someone over and over again to tell you about their grandmother they’d probably come up with a few extra characteristics occasionally.

    That ought to be equally possible in TADS 3. Provided that there’s some way the game can test whether the existing line of conversation has dried up it can be part of the condition that triggers the AgendaItem.

    Yeah, I really saw this not as a limitation on AgendaItems (my impression from a brief examination is that they can take arbitrarily complex conditions), but as a limitation on the information currently available from the underlying model. Which comes back to the threading issue.

    But perhaps you were thinking not so much of varying the list of topics offered as varying the way they’re introduced (varying the wording from “You could…”). You’re right that this hasn’t been done much if at all in TADS 3 games, but it’s doable in principle and something I’m experimenting with a bit in my current TADS 3 WIP.

    I was thinking of both issues — both changing the filtering, and revising the wording of specific hints.

    I find that when I’m playing a game with a lot of “(You could…” lines, the redundancy of phrasing can get a bit spammy. And sometimes I specifically want to frame this as the PC’s impulse in some more vivid way, or throw the emphasis to one response while implying that the others are less desirable. Like: “You are dying to ask Lord Spackleworthy whether he likes to dance, but that might seem forward. Your mother would tell you to comment on the weather instead.” Revising the topic list like that makes it (IMO, for some games) more worth reading, and also may hint to the player about the risks and rewards that might attach to various options.

    Naturally, one doesn’t want to do this all the time (it goes back to being the kind of endless handcrafting that can become so onerous for the author, and the customized pieces stand out more when they only turn up now and then). Nonetheless, it’s an ability I find myself using a lot both in Alabaster and in other WIPs.

  17. Putting routines wholesale into native code sounds like it could be worthwhile. It wouldn’t be too hard to set this up in a safe and backwards-compatible way: add a single new opcode @acclerate F N, where F is the address of a VM function and N is the index number of the optimized native function.

    Indeed, that’s how FyreVM does it: @fyrecall FY_SETVENEER N F. Note that the terp’s veneer implementations also need to know about some constant values such as NUM_ATTR_BYTES, INDIV_PROP_START, #classes_table, #cpv__start, etc.; I pass these out using the same mechanism.

    The approach I’m playing with for Twisty/zplet is to have the terp match the body of each function against a set of known veneer routine bodies, using a hash table to keep track of which functions have been checked. The patterns used for matching routine bodies contain holes for the important constants, which can then be read directly out of the code.

    Pros: requires no library updates, so it works on already-released games; no danger of mapping a routine to the wrong implementation if the veneer changes without notice.

    Cons: overhead from matching on the first call to any address, and hash lookup on subsequent calls; making the veneer body patterns is tedious and is complicated by Inform’s branch optimization (the -t assembly output includes bytes that aren’t present in the game file) and by the fact that some routine lengths can vary based on compile-time factors.

  18. Meh, this whole “virtual machine” idea has proven itself to be fundamentally slow. The Z-Machine is decades old, and still too slow for cutting edge work. Heck, Java is mature, has all these smart people working on it, and still is slow. Screw all these “high level” languages. Real IF programmers can write their game in any language without any loss of productivity. That’s why I’m with Paul Panks in writing IF in assembly.

  19. What I was wondering about here was whether doing something like that would ruin the ability to make topics also be shuffled or stopped lists (which seemed to be another rather clever key element of the TADS 3 approach).

    Right. I was thinking in terms of one-off responses. If you wanted to incorporate shuffled lists and the like you might implement this in a different way. Once coding pattern might be to use list elements that look like:

    [
    {: “What the PC says.<.p>
    << response(‘NPC\’s reply.’) >> ” },

    {: “More stuff like this.<.p>
    << response(‘Another reply.’) >> ” }
    ]

    With this, each item in the list displays the PC’s quip, then passes the NPC’s response to the response() method of the TopicEntry. The response() method can then contain the hook to decide whether something intervenes between the question and the response, and so decide whether or not to display the response. (There are doubtless other ways to achieve the same effect, but this seemed to me to be probably the neatest, or at least, the least messy).

    One could then use the same coding pattern for one off responses as well.

    If, however, you just wanted the kind of variations in your subsequent example, you could use the rand() function in TADS 3 to get much the same results as the [one of] … [at random] construct in I7; e.g.:

    “<< rand(‘she was from the north, where they have fair skin’, ‘she looked like me’, ‘she came from away in the north’, she was from a petty noble family in the north’, ‘she had very fair skin, like mine, because she came from the north’) >>”

    Yeah, I really saw this not as a limitation on AgendaItems (my impression from a brief examination is that they can take arbitrarily complex conditions), but as a limitation on the information currently available from the underlying model. Which comes back to the threading issue.

    Indeed so; my response assumed that the threading issue would otherwise have been dealt with, and that there would therefore some way of determining whether the thread had changed. For example, if one were using a series of ConvNodes to implement a thread, one could give all these ConvNodes a property value that showed they all belonged to the same thread, and the AgendaItem could look for a change in the corresponding property value of the current ConvNode. Some other strategy could probably be devised if one went about implementing threads in some other way.

    I suspect I’m now going rather too far into the details of a purely hypothetical conversational extension; my point is that I suspect most of the issues you raise would have a solution in TADS 3 (though it might take a bit of work to find it, and it might not end up quite as neat as one would ideally wish).

  20. Has anyone used Apple’s profiler Shark on an if interpreter, to see if there are opportunities for performance enhancements?

  21. Some exciting stuff going on here!

    I’ve struggled with speed issues a lot on Blue Lacuna (which is days away from final beta, hurrah!) and I would make the point that the single biggest issue is *not knowing what’s causing the speed issues.* As soon as you *know* that, say, pathfinding is culprit, you can take a look at your code and realize you don’t really need it, or don’t need it running every turn, or whatever, and take steps. But you’re really just whistling in the dark as far as determining what’s causing slowdowns. I did *days* of painstakingly turning systems, functions, or individual lines of code off one by one, timing test script runs, and comparing results; or looking through the I6 veneer routines for things that perhaps weren’t performing well with my particular game. It would be marvelous if there was some tool that could say “In that last move, 60% of the computation time was spent in the ChooseObjects routine” or whatever…

    Meanwhile, if any of you smart people fiddling with speed improvements need a large, slow game for testing purposes, drop me a line. :)

  22. @Eric: Yeah, I’m sure that TADS 3 is capable of handling all of the things I described, in some way or other (sorry if I seemed to be questioning that!). I was more trying to dig into the more nebulous question of how I would have imagined the design differently given the different styles of the languages. It might be that I would have done more or less the same things (emulating what I had already constructed in Inform 6 for City of Secrets), but it’s also possible that TADS 3’s pre-existing structure would have tipped me in the direction of a less-threaded structure. I’m not sure.

    @Jon: I think Graham mentioned doing some things with Shark at some point, but I could be misremembering that. And that may have been work on the compiler itself.

  23. Yeah, I’m sure that TADS 3 is capable of handling all of the things I described, in some way or other (sorry if I seemed to be questioning that!). I was more trying to dig into the more nebulous question of how I would have imagined the design differently given the different styles of the languages.

    Sure; which is how I introduced this branch of the discussion in the first place, so you were staying on-topic while I was getting carried away with the challenge of trying to imagine how one might try to implement these things in TADS 3!

    It might be that I would have done more or less the same things (emulating what I had already constructed in Inform 6 for City of Secrets), but it’s also possible that TADS 3’s pre-existing structure would have tipped me in the direction of a less-threaded structure. I’m not sure.

    My guess is that the differences between TADS 3 and Inform 7 would very likely have led to a difference in design. Two differences in particular may be relevant here:

    (1) With one or two exceptions, one tends to have to name every object in I7, whereas TADS 3 encourages the creation of anonymous objects (not least for conversational responses). Moreover, since in I7 the (internal programming) name of an object is also its display name and the name the parser will match, having a system using objects with names like “whether Fred seems annoyed” is a relatively natural move; the existing TADS 3 conversation system using anonymous objects matched in a completely different way would not lead one to think in this direction.

    (2) Since the concept of relations is built into I7 but not into TADS 3, the notion of using relations to link game objects/subjects to quip objects would be much less likely to occur to someone working with TADS 3, especially since the TADS 3 parser doesn’t come armed with a straightforward way (if any way at all!) to understand things by relations.

    Again, as you suggest, the very existence of the built-in TADS 3 system would naturally lead one to try to leverage what was already there, rather than thinking outside the box (though I suspect you may be better at that than I am, hence my cautious ‘one’!).

    To get back to the main point of this thread, I can’t rid myself of the suspicion that a TADS 3 version of this might run faster, and I’m wondering what that’s based on. It may partly be based on the fact that I’ve never actually come across a TADS 3 game that has to deal with quite so many quips per interlocutor as Alabaster is trying to do, so I simply haven’t seen TADS 3 trying to cope with processing quite such a large database. Again, what the TADS 3 library tends to be doing in this sort of operation is a single pass through a pre-built list (or a small number of pre-built lists), either till it finds what it’s looking for, or to enable it to choose the highest scoring available item for its purpose; this may mean it’s simply doing less work overall than you’re trying to do with your conversational extension (which I think you said has to make multiple passes).

    But I don’t think these musings are heading anywhere helpful unless they happen to inspire some optimization strategy which hadn’t already occurred to you, which I rather doubt they’ll do!

  24. I’ve posted a quick hack to Glulxe which generates profiling information. See RAIF for announcement post.

  25. The sources for Spatterlight include an older port of Git, from February 2007, but it looks like only Glulxe is referenced in the application launcher code. Git is not in the binary download.

    Also, Git 1.2.1 was released in September 2008; the home page is out of date but IF Archive has the most recent version.

Leave a Reply to Emily Short Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: