Inform 7's (lack of) Speed

Thanks!

I notice that you record concealment after reading a command. That’s when I was caching scope as well, but I recently ran into some weirdness. I think it had to do with actions by NPCs that have an “[any known thing]” token in the grammar line. I thought I made myself a note somewhere, but for now all I can offer is a vague warning…

I remembered where the bug comes in. If a rule uses the phrase:

if the player's command includes "[any thing]"

… then the scope context will be set to that list of things, and it will stay that way during the “after reading a command” rulebook on the next turn. Here’s an example:

[code]include Scope Caching by Mike Ciul.

Test is a room. There is a rock.

last after reading a command: showme the list of marked visible things.

For printing a parser error when the latest parser error is the can’t see any such thing error:
if the player’s command includes “[any off-stage thing]”:
say “You can’t see any of the following: [the list of off-stage things].”;
otherwise:
make no decision.

Test me with “get rock/z”[/code]

On the second turn, exactly one thing is visible during the after reading a command rulebook: the rock. If we record what items are visible at that time, scope caching gets messed up.

Can anyone explain this? How can I fix it?

Under the hood, [any off-stage thing] works by asking the parser to match [thing] at various word offsets after changing scope to be the set of off-stage things. It’s reset only when a different scope is needed, which SearchScope decides by consulting scope_token. But Scope Caching doesn’t touch scope_token before calling LoopOverScope, so the most recent routine is used, which in this case thinks that no reset is necessary. Change your inline I6 to[code]To iterate scope with (func - phrase thing -> nothing): (- scope_token=0;LoopOverScope({func}–>1); -).

To iterate scope for (actor - thing) with (func - phrase thing -> nothing): (- scope_token=0;LoopOverScope({func}–>1, {actor}); -).[/code] and you should be good.

A naive question: is there a theoretical reason to expect Inform to be slow? Or it the problem that the specific implementation of the Inform 6/7 compiler is generating egregiously inefficient byte code?

I would think that since the typical game has only on the order of 1,000 objects, even the most straightforward O(n^2) algorithms would be plenty fast…

Perfect. Thanks!

Well, if you look back through this thread, you’ll see that Inform isn’t inherently slow. Inform games can be fast or slow, depending on what they do. The problem is it’s hard to know what’s slow unless you’re a serious I6 maven. Most game designers pick some tools from the manual, get the effect they want, and don’t worry about efficiency. That’s what they should be doing, but unfortunately the platform isn’t as forgiving as it should be.

The games run on a virtual machine, and the interpreters aren’t as aggressively optimized as big-label VMs like Javascript. That’s one thing. Then the interpreter might be built on top of Javascript, or on top of a low-power mobile CPU. That’s another thing. Result: an N^2 loop over 1000 objects is undesirable.

At the low level, I6 generates efficient byte code, because I6 is a low-level language and is easy to compile. (Again, it doesn’t have giant compiler teams working on optimizing it, so it does miss tricks. But hand-crafting efficient I6 is straightforward.)

At the next level up, I7’s libraries – which are hand-crafted I6 – are a bit of a mix. Some of that code is tight; some could use some work.

The I6 code that I7 generates, from user code, is fairly good. It’s also a minority of the problem; i.e., it’s generally a thin layer that calls down to the library level. (Of course, if you write nested “repeat through everything” loops, I7 will generate nested repeat-through-everything loops, and then it’s not so thin.)

But at this point there are enough layers that the whole system has some slack in it. A simple-appearing construct like “try taking the foo” invokes a complex action mechanism, which invokes a complex scope mechanism, which checks a lot of objects, and each object check is a property read, and the property read does some type-checking… so low-level code gets invoked a whole lot.

The good news is that this problem is amenable to improvements at every level. We talk about optimizing low-level functions, because they invoked thousands of times. We also talk about ways for scope-checking to cache its results. And about ways for rulebook invocations to be compiled down to more efficient code. And, since all of that is hard work, we wind up going back to the author and saying “Could you not check scope so many times per turn? I know it’s hard to see where it’s happening. But it’s kind of important.”

1 Like

Jimmy,

I came back to this because I’ve decided to release Scope Caching officially. Do you plan to release concealment as an extension? Did you already do that and I missed it? I think I’d rather give people the option to include it alongside scope caching than try to duplicate its functions.

So sorry I missed this. Afraid it came while I was on holiday. As of now, I’ve just wrapped the concealed possessions stuff into a larger (private) extension which helps me to move titles onto the Kindle. You’re welcome to use my concealed possessions code in an extension of your own, if you like.

Thanks for the update. It’ll probably take me a while to get to this, but I might eventually.