Reducing UNDO State Size

This is sorta inspired by this topic.

In my preinit stage, I have a large data set being included in the compiled game as a cache.

I’m wondering if this data set is being deep-copied into every UNDO state, which might seriously reduce the number of states available to the player.

If I can guarantee no changes will occur in this data set during gameplay, is there a way to force the interpreter to only keep one copy of the data, and refer to this one copy in all UNDO states?

Is this something the interpreter already does? (Am I worried about a problem that doesn’t exist?)

2 Likes

Have you tested how many undos are available once your game is well underway?

2 Likes

Not yet. Will it vary by machine? Because mine is kind of a statistical outlier, and would have a lot more memory available for UNDO states than most. I could probably test it on my phone, I suppose…

It’s more a desire to maximize this for the player, than the number of UNDO states being a present problem. I can see there is waste somewhere, and I want to reduce it. If it only offers 20 states but could offer 100, then I would like to push for 100 by reducing copies of unchanging data in each state. (These are fictitious numbers. Idk what the average is for a game.)

EDIT: Specifically, I’m asking if there’s a way to force certain data to be shallow-copied instead of deep-copied, per UNDO state, and/or how these states are made, mostly for the sake of knowledge itself. It’s not so much “I need a solution for a specific problem in one game in particular”.

1 Like

There is an explanation of the VM’s undo mechanism in the source here: tads-runner/vmparam.h at master · tads-intfic/tads-runner · GitHub. Changes to properties are recorded as they occur, so data that never changes shouldn’t contribute.

4 Likes

Ah! :grin: Excellent!!

I remember originally thinking this was how it worked, but I guess I seriously misunderstood something I had read a few months ago, and found myself under the false impression that it copied all the data, per state.

What you’ve posted makes a lot more sense, though, given historical limitations of computer memory.

Thank you for posting this! :smiley: I am happily corrected!

1 Like

@ArdiMaster , are you able to verify that my big game is stuck with its two undos unless I release the game in a custom interpreter?

2 Likes

Um… I’m not sure what there is to verify. If you only get two UNDOs then it looks like that’s what you’re going to be stuck with. (With the notable exception of frobTADS, the Unix console-mode interpreter, which makes the undo limit configurable on the command line.)

The interpreter normally allows 4096 undo records, with one being used every time a property changes. The code comments say that a simple game with the adv3 library uses about 200 per turn, although I suspect that number has probably gone up as the library evolved — the frobTADS source code here says that games typically get only five or six UNDOs with the default value. So I guess it’s not unthinkable that a very large game could end up having two or even just a single UNDO state available.

(Although that does give me pause about my own large game, should I ever get around to writing it…)

3 Likes

If the cache never changes, then you can initialize it as a preinit object and also make it transient, which excludes it from both UNDO as well as SAVE. See https://www.tads.org/t3doc/doc/sysman/objdef.htm#transient

4 Likes

Ohhhh okay, so transient still gets included during preinit? I thought transient stuff contributed to preinit calculations, but didn’t get saved in the game file.

(I am unlearning a lot of false ideas today! :grin: )

2 Likes

4096 records is only 64KB of RAM. I think we could double it without issue. And if anyone was working on a TADS interpreter on a really memory-strapped system they could lower it again (but I suspect TADS wouldn’t fit on such a system regardless of the UNDO size.)

Really the question is if we should only double it, or whether it should be made even bigger than that.

Edit: Based on @RealNC’s advice I’ve increased the undo limit by 16 times. But it will take some time for the other interpreters to include the increase.

4 Likes

What does “we” mean? Gargoyle and/or Parchment specifically?

2 Likes

We meaning everyone :wink:. If we change it in the tads-runner repo then it will filter down to Parchment and Gargoyle. But we could also coordinate to update QTADS and other interpreters at the same time.

3 Likes

Follow-up question: If there are pointers to objects in the cache when the game saves, would these pointers be restored correctly, because the data is cached in the original game file? Do transient references only become nil when the transient object was created after the game began? Or do all transient references become nil, regardless of where the data is stored, or when the data was created?

EDIT: Mostly asking because I’m not sure if my AI needs to hold onto any references to the cache in a save file, should I choose to make the cache transient, lol…

1 Like

That’s true. There’s nothing special about transients here.

Everything is saved in the game file. Transient objects as well. But they are excluded from UNDO, SAVE and RESTART. That’s all there is to them.

No. That counts as modifying the cache, which you said never happens :stuck_out_tongue:

2 Likes

An object outside of the cache storing a reference to a cache object counts as modifying the cache…?

1 Like

You said: “If there are pointers to objects in the cache when the game saves, would these pointers be restored correctly”

That sounds like the cache is storing references to other objects. If it’s the other way around (other, non-transient objects are pointing to the cache) then it’s fine.

2 Likes

That’s entirely on me for not clarifying. Sorry about that, lol.

Okay, so these specific references would restore correctly from a game save file? A non-transient non-cache object storing a reference to a never-modified, preinit-cached, transient object?

(Sorry if this should be obvious. I ask for clarification a lot; side-effect of my neurotype.)

1 Like

I was wrong. It’s not fine since the properties that point to anything transient will be set to nil after a RESTORE.

So since you have properties that refer to the cache, don’t make the cache transient. Unless you can manually restore those properties in port-restore or post-restart code.

But you should probably do a simple test to see if the amount of undo steps changes with a transient cache or not. If not (or if the difference is small) then I’d say it’s not worth the extra effort.

2 Likes

Will do. Thank you for your time, help, and patience! :grin:

1 Like

Oh cool, I didn’t know there was an update all terps at once option like that! That’d be great!

2 Likes