Any way to safely access Thing.desc() during preinit?

I just want you to know that this part of TADS 3 is way out of my depth, but I have been excitedly following this thread the whole time.

2 Likes

I find it odd that there seems to be a disparity in the documentation about setMethod… the libref specifically says it can’t take an anonymous function, which obviously isn’t the case. I thought I had remembered Eric Eve using strings with setMethod, so I was surprised to read that paragraph in the libref.
As far as say, that was just an arbitrary choice to fill out the example; I was just drawing attention to the fact that you could use anonymous methods with setMethod in preinit, and get the transcript you were looking for.

2 Likes

I’ve found errors and discrepancies in the TADS 3 documentation before, too. Really throws me for a loop, but they seems to be super rare.

This thread will likely be a valuable resource for anyone else who runs into something like this in the future.

1 Like

I don’t know the full history, but it’s my impression that getMethod() and setMethod() were fairly late additions. It wouldn’t surprise me if they were added in a major release and then updated to be more flexible in a minor release and the documentation just never got updated.

Yeah, that makes sense. It turns out it’s surprisingly finicky, though. Most things in the library use aioSay() when they’re directly calling a “say” function, and it (outside of the web UI) just calls tadsSay(). But none of pebble.setMethod(&desc, {: aioSay(txt0) }), pebble.setMethod(&desc, {: tadsSay(txt0) }), or pebble.setMethod(&desc, {: say(txt0) }) work (when used during preinit).

2 Likes

How are you dealing with side-effects when executing the double-quoted string?

me: Actor {
    frobEffect = 0;

    frobDesc()
    {
        if (frobEffect < 1) {
            "It makes you look kinda blue. ";
            ++frobEffect;
        } else if (frobEffect < 2) {
            "Hm, now you look kinda red. ";
            ++frobEffect;
        } else {
            "You see yourself in black and white. ";
            frobEffect = 0;
        }
    }
}

frob: Thing 'frob' 'frob' "You look through the frob. <<me.frobDesc()>>";

It seems to me your code will in effect still require desc to not call nor evaluate anything, since doing so can change state. At which point the requirement might as well just be a new property that should never be a DQ string.

3 Likes

Maybe?

But it’s syntactically valid in T3 for literally any property to be a method, and (afaik) there’s no way to enforce use of explicit getters and setters (that could do sanity checking of values).

Is there any way to pull the string literal out of a double-quoted string?

1 Like

Yeah. And even single-quoted strings can contain << >> code.

No idea how well this will work, but you could try to manually create an UNDO savepoint right before you start going through the properties, and restore that savepoint after you finish. tadsgen.h provides the savepoint() and undo() functions for that. Obviously, the object you store the results in must be transient, otherwise it itself will be affected by undo().

I don’t know of a way, unfortunately.

2 Likes

That still has the problem that if the contents of a double-quoted string are dynamic, then any capture-and-replace mechanism will lose the dynamic part.

The way I was thinking of handling this is to capture the value twice (maybe once at preinit and once at init?) and compare them and throw a warning if the values are different. This is based on the assumption that the number of cases where there’s a bunch of “fancy stuff” like this that needs to be handled is small compared to the total number of descriptions (or double-quoted strings, or whatever) that might be affected.

1 Like

What if someone has a large amount of randomization?

Depends on where the randomness is.

My motivation for all of this is the implementation of some lightweight procgen stuff. Some of it is runtime, in which case most of the randomness is handled by selecting between a couple of different options. So a random room description would involve a layout tool rolling dice and picking a random room, rather than a method on a room object (that remains the same across iterations) rolling the dice in a method and selecting a description (or whatever). If that makes sense.

That’s for the runtime stuff, which is for roguelike/soulslike kind of things: every day the player can go to the Hall of Mirrors at the carnival, and every day it’s subtly different. That kind of thing.

The other thing is at compile time, as an authoring/prototyping tool: write rooms/objects more or less normally, but have the relationships enunciated in a separate configuration/specification (think of something like a directed graph) that is used at compile-time and/or preinit to generate the “concrete” layout…and do basic consistency checking (are there any keys stuck behind the doors they unlock, that kind of thing). This all to make it easier for you (as the game designer) to shuffle things around during development, without having to “manually” twiddle all the exits every time you add/remove/move a room. And so on. For this kind of stuff, my assumption is that the number of “random” things that still need to be random at runtime (as opposed to being a choice made at compile-time that is then static at runtime) is small.

But it’s certainly true that there are going to be some number of corner cases that this kind of thing absolutely can’t handle outside of handling it via special logic/overriding the default behavior/whatever. I’m just trying to keep the number of places where that’s true small compared to the number of places where things work by default (such that it’s a net win in terms of the amount of effort needed to obtain the same result).

And, I mean, cards on the table here, at some point I was just interested in fiddling with the problem because it’s something that seems like it ought to have a straightforward solution but which was apparently a bit of a mess. So it was interesting to investigate purely as a technical puzzle.

1 Like

:100:

I was more asking to clarify the boundaries/scope of the puzzle. Wasn’t meaning to shut down the idea or anything.

Mostly the bounds on the problem I’m thinking about (specifically with capturing and re-writing stuff during preinit) is for baking a result at compile time that’ll thereafter be static at runtime.

For objects/rooms/whatever that are “fully dynamic” at runtime, then my assumption is that a) they’re less numerous than “static” objects, and b) they’re already going to be full of bespoke code, so adding additional hooks to tie into a module (or whatever) is less onerous than having to touch everything would be.

I’m also considering the possibility of having this be purely a development tool…so maybe the output isn’t the compiled game, but a source file (or source files) that have the changes baked into them, which can then be customized/edited/whatever and then they’re compiled into the game that gets shipped.

2 Likes

A TADS 2/3 preprocessor can be an interesting project by itself, but on your need, I’m working on the exact opposite (even the PC object is fully dynamic…) that is, the static object (mostly mere decos) is the exception and not the rule.

Hence, I tentatively suggest that you exploit the ratio, giving to your objects a “bakeable” (or “not bakeable” flag, to be checked during the preinit, marking the “safely accessable” Things to the preinit “oven”.

I fear that isn’t much of help, but anyway, are my proverbial 2/100 :wink:

Best regards from Italy,
dott. Piergiorgio.

1 Like