I'm realizing just how limited Ren'py is the more I'm working with it.
Sorry, but If you say this, then you don't know much about Ren'Py, nor even about Python.
A set only stores values.
I took
set only as comparison point, because it's what I used above in the thread. But the issue isn't that
set would be better, it's that your approach solve nothing.
This being said, it's perfectly possible to store tuples in a set. Like it's not too difficult to mimic your key/value approach with a set, as long as the value is scalar
(by definition only since with Python everything is an object).
Python:
def add( key, value ):
def getType( value ):
if isinstance( value, int ):
return 'I'
elif isinstance( value, float ):
return 'F'
else:
return 'S'
for atom in mySet:
if atom.startswith( key+'|' ):
mySet.remove( atom )
break
mySet.add( "{}|{}{}".format( key, getType( value ), value ) )
def get( key ):
for atom in mySet:
if atom.startswith( key+'|' ):
idx = atom.index('|')
t = atom[idx+1]
if t == 'I':
return int( atom[idx+2:] )
elif t == 'F':
return float( atom[idx+2:] )
else:
return atom[idx+2:]
Your first example would use 76 bytes in a set instead of 94 in a dict. The second would use 88 bytes instead of 106. 18 bytes of differences, therefore a bit more than 0.5KB counting the rollback.
For those who are curious, using a set of tuples would increase the size by 72 bytes by entries.
Not that I advocate for this. Using a dict in such case is obviously a better approach since natural. But you said that it's not possible, I just explain that it is.
That’s fine if you just want a boolean "present or not" flag, but it’s useless if you actually want to store key-value pairs, which is necessary if you want to support arbitrary types, or more complex data, as part of migration or future-proofing.
And creating an object that will rely on a dictionary to store them is nothing else than recreating Ren'Py native stores, in a more complex way. This for no actual benefit since it's harder to operate, use more RAM, and take more space in the save file.
The point was to persist key-values for migration purposes, using `save_json_callback`, and not saving them through the store.
As I said, the first can be done with the
MultiPersistent
class. And, being designed especially for this, this class do not need that you keep the save directory name constant.
As for the second, the question is still the same: what is the interest?
In memory, every single variables created in a RPY file
is stacked in the store. So, it change nothing at this level. At most it would use less RAM, but at the price of the none rollback compliance.
Like you include it in the save, it make no difference with it being saved through the store. Except that you need one more operation to save it, and one more to load it.
There's possibly also issues with the autosave and quicksave, but being at works I can't check if the callback also apply for both.
Like it's in the JSON file and not the pickled copy off the store, it need more space to be saved.
Worse, it's not saved through the store
only because it's not rollback compliant. The instant it will become rollback compliant, it would be twice in the save file; once in the pickled file, once in the JSON one.
And if you keep it none rollback compliant, then it raise other issues due to it's persistence, like by example if the player restart the game. And obviously what I previously shown, with both values being saved because the player did a rollback to make another choice.
I could make the dictionary MultiRevertable to make it rollback-compliant, but as you noted, it saves it in the store and defeats the point of it being meant for cold storage.
And this didn't triggered something in your mind? Like by example that "cold storage" is totally useless here because it give absolutely no benefits?
[...] it's not possible to intercept or filter data before serialization because of the lack of callbacks provided by Ren'py, [...]
It's totally possible to do it. It would be dirty
(so I'll not give the code), but it would works.
Methods are just attributes pointing to a code instead of a value, what mean that they can perfectly be rewrote without having to create a dedicated class inheriting from the original. And the name of the variables to are in a set.
But the question stay the same, what is the interest?
It's saved with the store, and so? As I said, it would take an insignificant amount of space, both in the RAM and in the save file. So it's not a big deal.
And it can not be passed to another game, but there's already a class dedicated for this. So it's not the ideal.
The only possible use I see is if an update isn't save compliant. But creating a harness to keep the values, while you've no guaranties that they'll still be significants, isn't the way to deal with such issues.
the only way to do what I was trying to do (cold storage and rollback-compliant) is to create a parallel rollback system but that is honestly too much work for optimizing [...]
I stop you right now, it would optimize absolutely nothing at all, it's the opposite.
Writing and reading the JSON file take more time than loading the same data from the pickled file. And it would also take more space in the RAM.
It doesn't need to be loaded, that's the point of a cold storage. It's only stored in memory for the session so it can be migrated later on when it's actually needed instead of polluting across sessions.
It
need to be loaded... because what happen is the strict opposite of what you are saying.
None rollback compliant data
are fucking persistent...
I have two playthroughs for your game.
I play a bit of the first one, you store my choices in your
migration_helper object.
I load a save from the second playthrough. Your
migration_helper object is not updated, all the choice I made are stored in it, even the ones regarding choices that I haven't encoutered yet in that route.
I play a bit of that route, my choices are added to your
migration_helper object, in top of the ones that were already there.
I load the save from the first playthrough. Your
migration_helper object now have the choices I made for both routes...
I return to the second playthrough. Your
migration_helper object is now fully polluted for both routes.
Not gonna do that because Ren’Py is about as flexible as a brick with concrete shoes, so I’m not gonna lose sleep over it.
Sorry but









As I’ve explained, the interest is for future-proofing and migration, especially for games with lots of custom or evolving data.
And as I explained, you are doing the opposite, and doing it for absolutely no reasons.
If you hit the scale where it matters, it’s a sign that Ren’Py is not the right engine for the job, and that’s a design tradeoff to make early.
No, if you hit the scale where it matters, it's the sign that you don't know Ren'Py enough.
Not that Ren'Py is the engine for everything, but it can do everything. And when you know it enough, it can do most of it relatively easily.
TL;DR: Yeah, don't use my migration helper, it's incomplete, and Ren'py is like a toaster, great for bread and bagels, but if you need anything fancier or a more evenly toasted bread, you're shit out of luck.
Well,
Lust Madness isn't out of luck for his map system and dressing doll systems in
Lust Hunter. Like
Winged Cloud weren't out of luck for their old school 3D maze in
Sakura Dungeon, 9 years ago. And those are only two examples.
TL

R: You waste your time complicating your code, for no actual benefits and a lot of risks to hit a wall that would force players to restarts the game from scratch. And the worse is that it teach you nothing regarding Ren'Py.