Maybe a weird corner case not worth worrying about, but is there any way to safely interact (test, modify) a Thing’s desc() in a PreinitObject?
Specific case I’m actually worried about is some lightweight procgen-ish stuff that depends on compile-time preprocessor flags, but here’s a barebones example:
#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>
PreinitObject
execute() {
forEachInstance(Thing, function(o) {
// DON'T DO THIS IT WILL A SPLODE
if(dataType(o.desc) == TypeNil)
return;
});
}
;
startRoom: Room 'Void'
"This is a featureless void. "
;
+me: Person;
+pebble: Thing 'small round pebble' 'pebble' "A small, round pebble. ";
versionInfo: GameID;
gameMain: GameMainDef initialPlayerChar = me;
This will throw an error during during compilation (if compiled without the -d flag) or at runtime (if compiled with the -d flag) because:
Thing.desc() uses defaultDescReport()
defaultDescReport() is a macro that adds a report to gTranscript
Adding the report creates a MessageResult instance
MessageResult.construct() will call gAction.getCurrentObjects() if the message isn’t an object or property (which a double-quoted string will never be)
And gAction won’t be defined during preinit
Just wondering if there’s a known solution/approach to this.
But this seems a very messy way to approach it if you want to twiddle a lot of objects at preinit.
It’s also possible to handle this sort of thing “externally”…just have a build script that generates the T3 code, instead of relying on the T3 code to do it itself. But that seems like kinda a kludge as well, since doing this general kind of thing is more or less what preinit objects are for.
Mostly I’m just trying to feel around to figure how (or I guess if) I can update properties on objects during preinit. For most things, it’s straightforward enough:
Does anyone know enough about the lifecycle of mainOutputStream to know why something like local txt = mainOutputStream.captureOutput({: "Foo" }); fails (in the sense the output isn’t captured, just displayed) during preinit? For example:
-----start capture-----
Foo
-----end capture-----
txt = ""
Void
This is a featureless void.
You see a pebble here.
>foozle
---start capture---
---end capture---
txt = "Foo"
That is, during preinit captureOutput() doesn’t prevent the display of the passed function and it doesn’t return the output.
Edit: And for clarity: the above doesn’t include execBeforeMe = [ mainOutputStream ] on the anonymous PreinitObject, because it doesn’t appear to make any difference. Just calling it out because all OutputStream instances are PreinitObjects as well.
It’s true that TypeDStrings are methods under the hood, but I don’t think that’s where the “gotcha” is. Or at least I don’t think it’s where I’m currently getting hung up (no idea if there are additional, hidden gotchas that I just haven’t run into yet because I haven’t gotten that far).
…more or less works as expected. There is breakage possible if the return value of pebble.foozle() is different at preinit than it would be at runtime (for example). And the fact that the preinit twiddling is replacing a method with a single-quoted string. But it “works” in that it compiles and runs and produces the expected output.
And I can’t figure out a way to do something similar with double-quoted strings, specifically worrying about Thing.desc() because it’s the most common place double-quoted strings are used.
So say I’m writing a T3 module that does procgen stuff, and one of the things I want to be able to do is append some stuff to room descriptions if the game is compiled with the module. One way to do that would be to require authors to write descriptions in some entirely different way (use some other property instead of desc, using single-quoted strings instead of double-quoted strings, append some macro or <<>> tag, or something like that). But then it’s a pain to use.
Or say I’ve got a bunch of stuff I want to precompute at compile time and then bake into object/room descriptions so that the expensive stuff doesn’t get called when looking at things. Again, something that can be handled via the methods described above (that is, requiring special care in writing double-quoted stuff), but that seems ugly considering pre-baking stuff like this is more or less what preinit is for in the first place.
If that all makes sense. It’s certainly possible that all of this is just me thinking about things wrong or missing something obvious.
I don’t think twiddling the “examine” methods get me where I want to be because they all use desc under the hood.
And now that I’m onto it, I’m looking more into something like kludging together a preinit-safe (or preinit-specific) OutputStream so captureOutput() works, because there are plenty of other things that habitually use double-quoted strings.
Yeah, that’s an alternative (that’s what I meant in my post above about “use some other property instead of desc”).
The downside is it requires writing code in an ideosyncratic/non-standard way and/or re-writing existing code. What I want to be able to do (beyond solving the abstract technical problem, now that I’ve started thinking about it) is to implement things as a drop-in module that doesn’t require writers to learn a special way to write objects in order to use. If that makes sense.
getMethod()/setMethod() are hauntingly close to what I want, but they break if the double-quoted string contains any fancy stuff (conditionals, <<>> expressions, and so on).
So something like:
#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>
PreinitObject
execute() {
local txt;
txt = pebble.getMethod(&desc);
if(txt == 'A small, round pebble.') {
"Matched desc()\n ";
pebble.setMethod(&desc, 'A small, round rock.');
}
}
;
startRoom: Room 'Void' "This is a featureless void. ";
+me: Person;
+pebble: Thing 'small round pebble' 'pebble' "A small, round pebble.";
versionInfo: GameID;
gameMain: GameMainDef initialPlayerChar = me;
Will appear to work (that is, at preinit it will report matching the desc and at runtime >X PEBBLE will output A small, round rock.). But then if you try to define the pebble as:
+pebble: Thing 'small round pebble' 'pebble' "A small, round <<name>>.";
It won’t match.
That’s why I was trying things like local txt = mainOutputStream.captureOutput({: "Foo" }) Because Section 4.2 of Learning TADS 3 suggests using captureOutput() for precisely this sort of thing…but it doesn’t work at preinit time.
Well, I’ve tried adding execBeforeMe = [ mainOutputStream ] to the declaration and it doesn’t appear to have any effect, which I mentioned above. I’ve also tried adding outputManager, and then eventually every transient object in output.t, and none seemed to have any effect. I’d certainly be willing to try something else if you’ve got any ideas.
Having done some debug-via-printf, it looks like output during preinit doesn’t use aioSay(), which the same methods/functions do at runtime. I assume this is because the assumption is that preinit stuff will usually be happening at compile time and only infrequently in the interpreter, but I haven’t done enough source diving through the compiler to see what’s actually happening under the hood.
Perhaps I’m missing something here, but the way your examples are written (capture output at preinit time, then rewrite the descriptions based on the captured text) you’re trampling all over any ‘fancy stuff’ anyways.
If you don’t plan to account for change anyways, then requiring descriptions to be plain double-quoted strings with no embedded expressions might be reasonable?
If you do want to account for change, I don’t see how you can avoid doing the entire substitution thing at runtime.
I’m away from the computer, so you might need to double-check this spelling. execBeforeMe should be adv3libPreinit. It’s a PreinitObject that sets the output filters (and a whole lot of other stuff). IF your object is getting executed before adv3, that would be why the output filters don’t work.