"construct" within a program so that an "x thing" command will list all the components / children.

If I put the above line of code in the (descr) body, it only displays the first child / component.

Code:

#thing1(descr) This is the thing1 description, here are the parts: (line) *($Part has parent #thing1) $Part is part of #thing1

Yes, that should probably be elaborated some more in the manual. Thanks for bringing it up!

I'm assuming you mean to use either #thing or #thing1 throughout; I'll use #thing1. This is what happens: The library makes a normal query to (descr #thing1). In the rule for that, you make a multi-query. Although that multi-query has the potential to return more than once, it never needs to do so, because there is no failure condition later. Backtracking usually only happens on failure. However, we can force Dialog to backtrack over every possible option using the '(exhaust)' keyword:

Code:

#thing1(descr *) This is the thing1 description, here are the parts: (line) (exhaust) { *($Part has parent #thing1) $Part is part of #thing1 }

Advanced:

Of course, you can now move the entire exhaust-expression into a '(showAll)' predicate, if you like. But it is also possible (though not necessarily a good idea in this situation) to move the inner part of the exhaust-expression to a separate predicate, and then exhaust every possible option of a multi-query to that predicate:

Code:

(showAll) *($Part has parent #thing1) $Part is part of #thing1

#thing1(descr *) This is the thing1 description, here are the parts: (line) (exhaust) { *(showAll) }

Hope this helps!

Perfect !

What are the pros / cons of the two different approaches ?

Please elaborate on "(though not necessarily a good idea in this situation)".

So I tried some variations of Cloak of Darkness (z5) on my Amiga. I'd expected the ones produced by I6 to be slower and bulkier, but was surprised to see Dialog and I6 neck-and-neck in terms of both file size (bit over 80 kB) and execution speed. More puzzling was that the original CoD file found at Roger Firth's page (built under 6.21 with the 6/10 library) was 50 kB in size and ran slightly but noticeably faster.

This intrigues me because I was always annoyed by the sluggish pace of IF on the Amiga, and I'm trying to see where I can shorten the response loop. Dialog is promising, and I'd expected the turn loop to run quicker. Is I6 really that efficient, or does a minimal compile (such as Cloak) in Dialog really encode much more functionality than the I6 version? Note that I'm not throwing shade on either compiler; I'm merely looking for points where optimization can be done in a more trivial manner than rolling my own z-machine terp in Asm.

Please elaborate on "(though not necessarily a good idea in this situation)".

It's mostly a matter of readability. Moving a chunk of code into a separate predicate can sometimes be helpful, if the name of the predicate helps to clarify the purpose of the code. But the downside is that the reader has to jump to a different part of the file to see what the predicate does. In this case, especially with a placeholder name such as '(showAll)', I just couldn't see the benefit.

However: When you've got identical or very similar code in multiple places, it can be a good idea to put that in a separate, well-named predicate, just to avoid having to maintain multiple copies of the same thing. Your '(ShowAnyX $Obj)' is a potentially much more useful predicate, although it probably needs a more descriptive name.

All of this is subjective. From a technical point of view, having multiple copies of the same code can make the story file bigger, but in this particular case it would be negligible.

So I tried some variations of Cloak of Darkness (z5) on my Amiga. I'd expected the ones produced by I6 to be slower and bulkier, but was surprised to see Dialog and I6 neck-and-neck in terms of both file size (bit over 80 kB) and execution speed. More puzzling was that the original CoD file found at Roger Firth's page (built under 6.21 with the 6/10 library) was 50 kB in size and ran slightly but noticeably faster.

This intrigues me because I was always annoyed by the sluggish pace of IF on the Amiga, and I'm trying to see where I can shorten the response loop. Dialog is promising, and I'd expected the turn loop to run quicker. Is I6 really that efficient, or does a minimal compile (such as Cloak) in Dialog really encode much more functionality than the I6 version? Note that I'm not throwing shade on either compiler; I'm merely looking for points where optimization can be done in a more trivial manner than rolling my own z-machine terp in Asm.

Thanks for this benchmark! I've been meaning to check how Dialog code performs on the Amiga, but so far I've been using Tethered on C64 as my go-to measurement, so I've really only looked at relative improvements across compiler versions.

A couple of points:

First of all, yes, I6 is really efficient. It's a low-level language in a number of senses: What you write is structurally pretty close to what the Z-machine needs to do at runtime. There is no type-safety, array bounds aren't enforced, so you can mess up the memory in ways that lead to mysterious bugs much later. When you are working this close to the machine, you get to decide how the various story elements are represented internally, and representations that are faster at runtime also tend to be more straightforward (easier) to implement.

On the flip side, the development process becomes less flexible. For instance, once you've decided to represent a property as an object flag, it might be difficult to adapt the code to some other representation, such as a three-way property, or a value that's computed on the fly with a function call. In terms of its level of abstraction, Dialog is more like I7, and the goal is to achieve a good performance while allowing the author to focus more on the story, and less on the implementation details.

With that in mind, I'm quite happy to be on par with the performance of I6. But I'm not done yet. There have been performance improvements over the last half-year, and I'm sure there will be plenty more. Writing an optimizing compiler is a never-ending job, but it can be really fun! I think much can be gained by implementing a more thorough type inference, for instance.

Since your benchmark is based on a very small game, a large proportion of the result will be due to differences in the standard libraries. The parser in the Dialog library is more flexible than the parser of I6/I7. Not necessarily more powerful in a given situation, but more flexible for the programmer, and capable of disambiguation at the verb level, for instance. As for the jump from 50 kB to 80 kB you mention, I suspect that it's mainly due to changes from I6 lib 6/10 to lib 6/12. A quick glance at the ChangeLog for 6/11 shows that a number of extra features were added.

One final possibility, and this is somewhat fuzzy and speculative: The Z-machine interpreter you're using on the Amiga was presumably developed in the 90s or early 00s, when nearly every game released for the Z-machine was coded in I6 or ZIL. As a result, the interpreter may have been optimized (intentionally or not) to deal particularly well with I6- or ZIL-generated Z-code. When I was creating Zeugma for the C64, I really wanted to be able to run recent I7 games. One of the things I did was to have the interpreter check for a particular, often-called routine that's present in every I7 game, and replace it with a native version coded in 6502 assembler. Eventually I had to admit defeat and give up on I7 support, but that feature is still in there. Now, I'm not saying that your Amiga interpreter contains something like that, tailored to a particular version of the I6 compiler, but I'm saying that its developers must have been fueled by the same desire to run recent games well. Hence, they would have spent a lot of effort on speeding up the kind of operations that dominate I6 code, while leaving the rest of the interpreter more or less unoptimized.

That's quite interesting. I have to admit I discounted the idea that a more flexible structure might force certain design choices. I'm excited to see where Dialog is going. Would types be exposed as a syntactic part of the language, or would it be an under-the-hood thing?

lft wrote:

Thanks for this benchmark! I've been meaning to check how Dialog code performs on the Amiga, but so far I've been using Tethered on C64 as my go-to measurement, so I've really only looked at relative improvements across compiler versions.

My pleasure. Honestly, though, I'd hardly dignify the comparison as a "benchmark": I only attempted it on an A1200, had no concrete metrics, and the environment I used was not really what you'd call pristine or controlled. I'd call it an indication, no more.

As for optimization, I've been peeking through the Frotz sources, and while no obvious signs of fine-tuning leaped out (it was mainly platform-agnostic C, zero Asm includes so far), that only means the bottleneck might be due to the stack-passing overhead typical of C on the Amiga (which seems possible, given that each opcode is a function pointer). Conversely, it could also be down to expensive OS 1.x system calls, or the multitasking environment, or any of a dozen other factors. I may just have to bite the bullet and measure properly.

_________________~Björn Paulsen

Last edited by Eleas on Tue Feb 12, 2019 12:52 pm, edited 1 time in total.

First of all, yes, I6 is really efficient. It's a low-level language in a number of senses: What you write is structurally pretty close to what the Z-machine needs to do at runtime. There is no type-safety, array bounds aren't enforced

That's all true. It's also true, and I think more relevant here, that the I6 parser and world model are *also* written very close to the metal. They're built to rely on Z-machine data structures and avoid any operation which is expensive (like managing dynamic lists).

The cost is that the I6 authoring environment is sharply constrained. Some authoring features just aren't on the table.

When Graham went to I7, he consciously *didn't* take "I7 should be as efficient as I6" as a goal. I don't mean there's no optimization in I7; there's plenty. But I7 offers a much more flexible authoring model, and it runs slower than I6 in the best case. These facts are connected.

Would types be exposed as a syntactic part of the language, or would it be an under-the-hood thing?

Oh, I was referring to the built-in types (object, number, dictionary word, list, reference). It's nice to be able to use any value (mostly) anywhere, so that, for instance, there can be a rule for '(the $)' that takes a list and prints a description of the entire collection. But it's probably the case that a lot of local variables only take on values of one particular kind, and the compiler might be able to deduce that. In those cases, it should be possible to eliminate certain runtime checks, and this in turn could lead to a speedup.

Objects around the perimeter of a room, as defined by '(from $Room go $Dir to $Obj)', are now automatically attracted into the room; they become floating objects. This used to be the case for doors, but now it is also true for any non-room, non-direction object mentioned in such a rule.

Library change: Scope

Objects around the perimeter of a room, including neighbouring rooms, are now only in scope if the player can see them. The current room is still always in scope (and can be referred to as dark/darkness when the player can't see).

Library change: Disambiguation

Object-based disambiguation—when the library asks if the player meant to do a thing to any of a given list of objects—is handled differently: The answer from the player is now matched against the output from '(the full $)', i.e. the actual words from the printed list. Furthermore, the answer is regarded as an elaboration of the previous, incomplete action, rather than as a new action. This makes UNDO behave more intuitively.

Library change: Internal performance tweaks

The library no longer maintains a list of objects in scope. The earlier approach had a performance advantage until 0e/01 introduced '(determine object $)', but now it is faster to compute '($ is in scope)' on the fly.

In contrast, the current visibility ceiling is now tracked using a global variable, and the '(player can see)' predicate is a global flag. The library updates them as required.

Hej! So I only heard of this project yesterday and gave it a try last night after skimming the documentation. Looks very promising. Compiling in Windows 10 was a breeze, using the built-in linux sub-system with Ubuntu installed (essentially just sudo apt install gcc make, make;even apt-installed mingw and compiled the windows EXE... development in Windows have never been better than now).

The uuid warning was a bit annoying, but again apt came to the rescue with a quick "apt install uuid" and a one-line make-rule to create a uuid-file for each dg-file if it did not exist and automatically put that on the command-line for my %.z8 make-rule. 10 minutes and I think I have a reasonable Makefile already. No issues with the command-line interface so far.

My memories of Prolog are mixed. I took an undergraduate course in Prolog ~20 years ago. I thought it was really neat at first, but then when actually trying to implement anything it kind of turned into backwards LISP and having to constantly worry about exactly what the resolver was up to and adding cuts in the correct places. Eew. Was looking at some logic programming library for Clojure more recently (core.logic or something? based on some similar scheme library) and it was much more declarative, but probably with some other downsides. It looks from all the examples I saw so far that the higher-level Dialog code looks more like I would hope that logic programming would be, but the lower-level language stuff (that I am sure there is more of inside of the library) are more like my bad Prolog memories with carefully backwards-written code to get intended results (rather than "declare what you want to be true and let the computer resolve things" ideal)? It looks much more programmer-friendly than any of the Inform-versions I tried either way.

An emacs-mode would be nice. Anyone already have something like that, or know of some language that is similar enough that it would be a good starting point? I guess some automatic handling of indenting blocks somewhat similar to python-mode (but simpler) would be useful, but other than that not much would be needed? Maybe enable paredit or some similar minor mode.

One thing that immediately came to mind that looks like it could be built-in in some nice way is automatic testing. Ages ago when I last had an idea to write parser-based IF I wrote a perl-script wrapping frotz to play back walkthrough files with some lines being regular expressions that were matched against interpreter output in that location. (I think Inform 7 has something somewhat similar built-in?) It would be very neat if it was somehow possible to write tests that would execute lines of text and either just parse each line or (with some syntax) apply a line as a predicate to check the state of the game (or/and output). Maybe combine that with the debugger somehow. I saw that the debugger can already play-back saved scripts, but not sure if it can combine that with batch-running scripts and checking the results?

EDIT: I should add that I am not sure I want to go back to experiment with parser-based if, so maybe do not listen too much to my opinions, but I have looked for a sane way to compile choice-based games to the z-machine, and it looks like what would be needed to make that is already available in the Dialog language and that could be a fun project.

EDIT2: And now, a few minutes later, I have confirmed that it compiles and runs on my mac in OSX and on my raspberry pi in Raspbian as well. Very good work making it so portable and easy on the dependencies etc. No obvious issues making a runnable helloworld.z8 on either platform.

Last edited by pelle on Sat Feb 16, 2019 6:59 am, edited 1 time in total.

Who is online

Users browsing this forum: No registered users and 2 guests

You cannot post new topics in this forumYou cannot reply to topics in this forumYou cannot edit your posts in this forumYou cannot delete your posts in this forumYou cannot post attachments in this forum