Preinit Transience (Also How Does Preinit Even Work) [TADS 3]

Hey, so I’m looking over the transient keyword in the docs, and wondering how it interacts with the PreinitObject stuff.

If I have a property in a PreinitObject—and this property is marked transient—then will it not be saved to cache after compile, and instead be represented with a nil value on game load?

To be clear: I want these transient properties to be thrown out after the preinit process is fully complete. I’m just wanting to clarify that using the transient keyword for these references would do this, because the documentation is kinda fuzzy about this case in particular.

Thanks!

Without consulting the docs… can properties be transient? I thought it was just whole objects…

1 Like

The transient modifier works in the same places as the static modifier. I think if a property is storing an object reference, you can mark that specific reference as transient, instead of condemning the whole class to that status. I don’t think this works if you’re storing something like an integer, tho.

Again, I’m quite competent at TADS 3, but I do not claim to be an expert. These are just observations.

Yeah, I getcha… within a property you can write “transient new SomeClass”, because you’re defining a transient object. I thought you meant just making particular properties transient and others not…

1 Like

My guess would be that any transient new… defined in a prop of a preinitobj would behave like other transients, because that property is just a reference to an autonomous object, it’s not like the transient is necessarily “contained” in the preinit…

1 Like

Can you just set those props to nil at the end of preinit?

1 Like

Yeah I could just set it to nil. Just wanted to be certain that excess data isn’t being saved after compile is all. Idk when and how garbage collection kicks in during this process. I only learned about the whole preinit thing a few days ago.

Can you just create all those objects with new() on the stack during preinit? Seems like they’d be gone without a trace by game load.

Maybe…? I’m not really sure how preinit stuff works at all, or how it interacts with anything else. The documentation doesn’t really clear much up.

EDIT: Might need to run some experiments.

EDIT: I’ve figured out the problem. I’m just keeping this here as a reminder of how frayed I was becoming, before I finally figured it out. Details on the solution are here, just a few posts down!


A record of my manic, confused state

Okay, I’ve run some tests, and I’m reaching a rather frustrated level of abject confusion here. I feel like I’m completely misunderstanding something.

What I am trying to do is set up a bunch of pre-computated data to my gameMain.t3 game file with a PreinitObject, but selectively remove certain unnecessary data at the end of the process, so it’s not also included in that file. Namely, these are things like temporary objects and variables that hold mid-process information, but are ultimately unneeded once the whole process is complete, and we have our final values.

However, it seems I might be having difficulties saving any data to gameMain.t3 at all.

Let me begin, by stating the assumptions I’m currently working on:

List of Assumptions
  1. By defining an object which inherits from PreinitObject, anything in its execute() method will be run at the end of game compilation.
  2. The aforementioned game compilation stage is when your code files get compiled into the final gameMain.t3 file, which is used for either debugging or shipping to the player.
  3. As a result, these data are already formatted and ready to dump into interpreter memory when a player loads the gameMain.t3 file, eliminating time spent performing pre-computation. This is effectively similar to packing your game with a built-in game save that is loaded to start play.
  4. objects marked as transient are not written to game saves. As a result, when a game save is loaded later, anything that was once referring to a transient object is now nil.
  5. My expectation here is that if any PreinitObject is working with objects that are marked as transient, they will not be saved to the final gameMain.t3 file, because of point number 4 (above).

However—my dear artists, players, enthusiasts, future visitors, and post-anthropocene data-scrapers—I am gathering some evidence and hints from my short experiments that seem to really throw a wrench into this pocket watch of gears. Namely:

  1. There is quite a delay before my current project loads, compared to previous projects. This leads me to suspect that the execute() method of my PreinitObject is happening when the interpreter loads the gameMain.t3 file, and not before the creation of the gameMain.t3 file has concluded, as I had assumed. An alternative explanation is that the data dump into memory takes a lot longer than I had anticipated, but I also have not run into the memory ceiling problem, which our dear @jbg has warned me of. Because my computer is rather beefy on the CPU side of the tech spectrum, I’m a little perplexed by this delay (in what should be a simple SSD-to-RAM load procedure).
  2. The transient objects that are created during the execute() method are still there once the game starts. I have tried every combination of approaches for this, and I get the same result. I have defined a transient object reference as an initial property, I have assigned a new transient instance to a initially-nil property within the execute() method, etc.
  3. The only thing which finally worked was instantiating the object as just a normal reference, and then making the reference nil by the end of the execute() method of my PreinitObject.

I learned of point number 3’s method of removing temporary mid-calculation objects from @johnnywz00 here:

He also included this, but I freely admit that I don’t quite follow:

My questions for clarification: When you define a new object within execute() during preinit, are these not saved as a result? Do I need to store these values and object references in a specific place for them to be included in the final gameMain.t3 file…? Do you mean creating a new object assigned to a local variable only, and not to a property of the PreinitObject?

At this point, it’s becoming increasingly-clear that I have no clue about:

  1. What’s going on
  2. How to create the data which needs saving
  3. Where to store it so that it makes it to gameMain.t3
  4. How to prevent mid-computation temporary data from being saved as well

I’m wondering if I should be running t3RunGC() at the end of the execute() method, too. I’m not sure if gameMain.t3 is having the post-compile memory dumped as-is, or if I need to do some explicit spring-cleaning of memory allocation before execute() concludes, in order to keep gameMain.t3 from having a bloated size. Alternatively, it’s possible that the data needs to be dropped into a specific place, in order for it to be save with the game file.

I had first learned of the PreinitObject business from @jnelson here:

Full disclosure: I am doing all of my TADS 3 hilarity on an Ubuntu machine. I am using VSCode, with the absolutely brilliant TADS 3 extension, created by our esteemed @Tomas.

Maybe I am not using the correct compiler arguments, when I save and test my project?

I’m not sure! Maybe I’m not even writing the data in the correct place!

Either way, I have included some sample code below, which demonstrates my current level of understanding:

My code files and compiler settings

My (stripped-down) code file (gameMain.t) is below:

#charset "us-ascii"
#include <tads.h>
#include "advlite.h"

#define CLOCK_COUNT 3600

orbitSystem: PreinitObject {
    radiansPerClock = static new BigNumber('0.00174532925')

    // The values loaded into this vector are what I'm trying to precompute,
    // to prevent slow loads on the player's side.
    // I want this vector to ship with the game.t3 file.
    sineVector = static new Vector(CLOCK_COUNT)

    execute() {
        // Set up our sine vector
        local bigInterpolation = new BigNumber(360);
        for (local i = 0; i < CLOCK_COUNT; i++) {
            local theta = radiansPerClock * i;
            local interSine = theta.sine() * bigInterpolation;
            sineVector.append(toInteger(interSine));
        }
    }
}

gameMain: GameMainDef {
    initialPlayerChar = me

    showIntro() {
        say('Test value: ' + orbitSystem.sineVector[3]);
    }
}

testRoom: Room { 'Test Room'
    "Oh wow, a test room!!"
}

+me: Actor {
    person = 2
}

Makefile.t3m is as follows:

## Makefile for an adv3Lite library game template

-I liblite
-D LANGUAGE=english
-Fy obj
-Fo obj
-FI /usr/local/share/frobtads/tads3/include
-FL /usr/local/share/frobtads/tads3/lib
-we
-v
-d

##sources
-lib system
-lib adv3Lite/adv3Lite
-source gameMain.t

-res
GameInfo.txt

Compiler: t3make
I don’t see any other settings in VSCode that might hint at extra compiler options; I think it’s all defined in Makefile.t3m, shown above.

Core Question:
With the above code files, will I successfully compile gameMain.t3 to include an orbitSystem object, with an attached sineVector, already full of values, both in the debug-version game and the final shipped-version game?

I am lost, confused, and vulnerable in the software engineering sense. I’m having difficulty finding answers in the documentation, and the results perplex me.

Send help.

Thank you all!

I just did some more searching online, in case I missed something that wasn’t clear until my fourth re-read, and I’ve discovered this lil’ nugget:

Deferred preinit in debug builds

Note that if when you compile in “debug” mode, the compiler defers preinit until you run the program normally. It does this so that you have a chance to step through the preinit process using the debugger. If the compiler ran preinit even in a debug build, there’d be no way for you to step through the process in the debugger, since the whole process would already have come and gone by the time you got into the debugger.

Note that preinit does still occur even in debug builds - it just gets deferred until you run the program, rather than running during compilation. This doesn’t change anything except the timing. In particular, everything still happens in the same order. You don’t have to worry about writing conditional code to deal with any differences; as far as your program is concerned, it shouldn’t matter.

There’s one small additional detail worth mentioning: the link between the debug build mode and the timing of the pre-initialization step is only the default, and you can override it if you want using a compiler option. The t3make option “-nopre” tells the compiler not to run pre-initialization (thereby deferring it until run-time), even for a non-debug build; the “-pre” option tells the compiler to run pre-initialization as part of the compilation process, even for a debug build.

Now, my dear forum-goers…let me review my compiler settings.

## Makefile for an adv3Lite library game template

-I liblite
-D LANGUAGE=english
-Fy obj
-Fo obj
-FI /usr/local/share/frobtads/tads3/include
-FL /usr/local/share/frobtads/tads3/lib
-we
-v
-d

##sources
-lib system
-lib adv3Lite/adv3Lite
-source gameMain.t

-res
GameInfo.txt

Do you see the “-d” option? If my reckless experimentation with TADS 3 compilers tells me anything…that’s the debug option.

Now, do you notice the stark, blatant, naked lack of a-preoption???

Perhaps we have found the problem. Now, don’t mind me while I excuse myself from the family dinner table and manically scramble to my computer like an energetic dog.

Will post results soon!

NOBODY PANIC; I HAVE FIGURED IT OUT!

Behold, the new set of compiler options:

## Makefile for an adv3Lite library game template

-I liblite
-D LANGUAGE=english
-Fy obj
-Fo obj
-FI /usr/local/share/frobtads/tads3/include
-FL /usr/local/share/frobtads/tads3/lib
-we
-v
-d
-pre

##sources
-lib system
-lib adv3Lite/adv3Lite
-source gameMain.t

-res
GameInfo.txt

I needed to include -pre if I was using -d, which means “use pre-initialization in a debug build, and this is a debug build”.

The reason why my project was taking so long to load was because, by default, debug builds (-d) skip pre-initialization, and instead perform it when the game normally loads, within the interpreter of your choice. This means that I was right in suspecting that pre-init was happening during the testing phase.

The reason for this is because you are able to log debug output during pre-init, as long as you do not include the -pre option. If you do include the -pre option, then pre-init does happen for your debug build, during compile. This means you effectively forfeit your opportunity to get logs during pre-init, as the compiler does not let you hear your program scream during this stage.

So, I am now able to confirm that I am correctly utilizing pre-init, and the data loads just fine! I also have some other funny observations:

  1. Transience does not matter during pre-init. If your data is transient, it’s welcome to come along for the ride; it’s just not allowed in save files, specifically. If you instantiate transient objects, they are saved into the pre-init result, just like everything else.
  2. It seems that @johnnywz00 was quite correct: If you do not want something saved during pre-init, just set it to nil when you’re done. Thank you, John!

Also, thanks again to @jnelson and @jbg for all the pointers so far!

Only one question remains:
Do I need to manually perform garbage collection with t3RunGC() before the end of execute() in a PreinitObject, or is garbage collection performed automatically before writing to the final gameMain.t3 file, after pre-init?

I just don’t want to accidentally save garbage memory to a game file during pre-init.


In software engineering, they say that the rubber duck method works wonders.

Clearly, it does.

However, I seem to have made the whole forum into a giant, collective, rubber duck, at least for a moment there. Hopefully, making myself a public mess in a manic state was informative, if not slightly entertaining. Thanks to everyone for allowing me space to do so, lol.

3 Likes