Ren'Py Ren'Py optimization tips?

Jman9

Engaged Member
Jul 17, 2019
2,295
957
renpy.python.store_dicts["store"].ever_been_changed.remove( n )
Oh, that's good info.

Unfortunately... I went and did that for ~150 of the temp variables and that saved me 15kB at best, out of what's now over 6Mb (compared to the <550kB starter save). I'll keep going when I can muster the courage again, but it looks like unless there's a massive one somewhere, that isn't it. There's another 350 left in renpy.python.store_dicts["store"].ever_been_changed, and I'd guess less than 300 can be safely removed.

But look at it that way:
...
So, all depend of the nature of those temporary variables.
Most are just strings or numbers. Some are lists or other objects. But virtually all of them are overwritten, so even if they're mutable, it doesn't matter.

Labels are at global level, there's no reason for their variables to be seen as garbage.
Okay, yeah, that's my bad. It looks like moving all calculations to functions wherever possible is the idea.

And like you can see, it pass through the exact same process than a regular autosave ; test if it apply, then call the function that will start/call the autosave thread.
The point being it will likely pass far, far more frequently than the interaction-count-based autosave. Unless I'm misunderstanding what 'choice' refers to here. It is the menus, right?

It's not just about the time needed to do the processing, but also when this processing happen.
Yeah, okay, that is true.

What I have doesn't play out quite like that, though, because Slut equivalents don't change while the screen itself is open. Rather, I change screens, do stuff with them, possibly including running labels, then come back to the original screen. I can also change their state before opening the screen.

I could possibly go and change the calculations around so that there actually is a flag 'show_Big_Red_Button' and not a calculation... But that'd require a lot of changes everywhere.

I settled for just disabling the button when returning, since it's actually not likely be used after the initial opening.

You pass from an algorithm that correspond to a "hey, girls, can you tell me what is your state ?" performed every round
No, just before showing the screen, after my initial post about it.

Well, it obviously have a bigger influence than a textbutton, that do the same but in a purely optimized way. And here it can effectively be noticeable, because there's more to proceed.
Purely out of curiosity, what's the difference between a textbutton and a button with text?

I just looked, and fortunately the only actual buttons I have are in the save/load screen.

What is a killing point. It mean that Ren'Py will built and display the whole screen... but most of it will be not seen because outside of the window.
Which is why I paged the thing. It used to show 20 and half of the next row, now it's just 20 which is only ever so slightly sluggish. I mean, I can't speedrun my cursor across buttons and get the hovers to light up, but everything else is okay-ish.

Perhaps I should ditch the whole viewport now.

A vpgrid would still take time, but for each entity in it, Ren'Py will first look if it's inside the window or not ; and if it's not it will just ignore it.
That's good to know, thanks.



Edit: But, well, my biggest issue is still there. Despite turning off autosave, despite removing all renpy.free_memory() and garbage collection calls, turning off rollback and trying different renderers... I still get mini-freezes every minute or so. It's not really limited to any one place, either, although I can't get it to reliably trigger without actually playing and changing game state.

The only thing I can guess at is that it might be related to loading images... But the game and thus the images are on an SSD, and general RAM use doesn't seem to be a bottleneck. The game will happily triple it's footprint just before crashing (and possibly cause system instability if other memory-intensive programs are open).
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,302
15,172
Unfortunately... I went and did that for ~150 of the temp variables and that saved me 15kB at best, out of what's now over 6Mb (compared to the <550kB starter save). I'll keep going when I can muster the courage again, but it looks like unless there's a massive one somewhere, that isn't it. There's another 350 left in renpy.python.store_dicts["store"].ever_been_changed, and I'd guess less than 300 can be safely removed.
The problem isn't necessarily in the importance of the variable, but in the relevance of, at least a part of, their values.

The game I used the most to test my variable viewer have some objects that host both values that are important, and values that aren't at all.
By example there's one used for one of the interactive sex scene. It have the values describing the actual state of the girl... and the image object related to the different pose she can have ; of course the images themselves are not included in the save file, only the value of the image object is. While the first values are important, the second ones are totally irrelevant. They are in fact constants and not saving those information wouldn't change a thing.

This doesn't mean that you should separate the information and not mix them in a single object. But if you do mix them, you should then use __getstate__ and __setstate__ magic methods. The first one to only exports the values that are relevant when the object will be pickled (and therefore where it will be saved). The second one to rebuild the constant values when the object is unpickled (therefore when it's loaded).


Okay, yeah, that's my bad. It looks like moving all calculations to functions wherever possible is the idea.
Not necessarily. Ren'Py have a function for this, renpy.dynamic(). It will declare some variables as local to the label:
Code:
label whatever:
    $ renpy.dynamic( "i", "temp" )
    $ i = 0
    while i < len( list ):
        $ temp = list[i]
        [do whatever]
As long as you stay in this label (I'm not 100% sure of my memory, but it should include called label and inner labels), the variables will exist. Once you jump outside of the label, or return from it, they'll cease to exist.


The point being it will likely pass far, far more frequently than the interaction-count-based autosave. Unless I'm misunderstanding what 'choice' refers to here. It is the menus, right?
Yes it's menus, but since autosaves are threaded, and the autosave on choice function is two lines long, it's purely atomic. You'll not notice it if Ren'Py proceed less than a dozen lines of Python, or if it don't.


Purely out of curiosity, what's the difference between a textbutton and a button with text?
The same difference than between a car, and a board to which you put four wheels, some seats and a motor. They'll both do the exact same thing, but one being designed especially for this will do it way better and faster.


Which is why I paged the thing. It used to show 20 and half of the next row, now it's just 20 which is only ever so slightly sluggish. I mean, I can't speedrun my cursor across buttons and get the hovers to light up, but everything else is okay-ish.
Hovers are the weak point of Ren'Py screens. They consume way too much time and can radically slow down Ren'Py if there's too many of them.
Using a vpgrid can partly solve this issue, but just partly alas.


Edit: But, well, my biggest issue is still there. Despite turning off autosave, despite removing all renpy.free_memory() and garbage collection calls, turning off rollback and trying different renderers... I still get mini-freezes every minute or so. It's not really limited to any one place, either, although I can't get it to reliably trigger without actually playing and changing game state.
Did the game use config.periodic_callbacks, or screens with timer ?
Since they both are way to include an automated repetitive process to the game, the slowness can possibly come from a process triggered that way.


The only thing I can guess at is that it might be related to loading images... But the game and thus the images are on an SSD, and general RAM use doesn't seem to be a bottleneck.
And the pre-loading process is threaded. It can still lead to some slowdown time to time, if there's many image to load and the thread haven't finished yet, but I don't see why it would be so frequent and this regularly.
 
  • Love
Reactions: Jman9

Jman9

Engaged Member
Jul 17, 2019
2,295
957
...you should then use __getstate__ and __setstate__ magic methods.
Good idea, but that presumes I actually know which objects are problematic. Although...

It have the values describing the actual state of the girl... and the image object related to the different pose she can have ; of course the images themselves are not included in the save file, only the value of the image object is.
... I may be misunderstanding this? Are you saying this girl is a fixed character who always has the exact same poses? Then that won't really work, most of data/object properties are dynamically generated, vary during the game, or both, and can't be guessed at from the start.

Now, there are a bunch of relatively constant data structures that might be worth declaring in an init block. But seeing which ones actually are totally unchanging will take some doing.

I'm also not quite clear how different init blocks in different files interact. Because init 1 python should be able to use a class defined in an init 0 python block somewhere else, right? But it doesn't seem to.

renpy.dynamic().
Hmm, good to know.

Yes it's menus, but since autosaves are threaded, and the autosave on choice function is two lines long, it's purely atomic. You'll not notice it if Ren'Py proceed less than a dozen lines of Python, or if it don't.
But I do tend to notice when autosave is processing, which it will be (more often) when this is set to true. Right?

The same difference than between a car...
No, I meant what does it actually do to achieve this?

Hovers are the weak point of Ren'Py screens. They consume way too much time and can radically slow down Ren'Py if there's too many of them.
Yeah, I'm counting up to 180+ buttons with hovers in that screen. Most of which are kinda useful to have. I don't suppose there's an alternative that accomplishes roughly the same thing (notes which button I'm about to press) without the specific visuals?

Did the game use config.periodic_callbacks, or screens with timer ?
Both config.periodic_callbacks and config.periodic_callback have nothing.

The only major screen with timer is 'notify'. It's... not very frequent, but not uncommon, either.

And the pre-loading process is threaded. It can still lead to some slowdown time to time, if there's many image to load and the thread haven't finished yet, but I don't see why it would be so frequent and this regularly.
Yeah, I was grasping at straws there. :(



Edit: Another thing this threading business reminds me of: I tend to savescum. A lot. So this might be sabotaging autosave and image-loading threads quite a bit.

It's not manifestly all of it, because I can play for a while without reloading and still get the small freezes... But I think more than a proportional share of these are be happening when I jump into something straight after loading.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,302
15,172
Good idea, but that presumes I actually know which objects are problematic. Although...
Well, it need times, but it's partly what my Extended Variable Viewer (link in signature) is for. Once you found one object that clearly have some attributes that shouldn't be saved, you can just look at the code to see its class and then track the object using the same.


... I may be misunderstanding this? Are you saying this girl is a fixed character who always has the exact same poses?
It's partly puppeter approach. And each element that can be used to build the puppet are stored in the object.


Then that won't really work, most of data/object properties are dynamically generated, vary during the game, or both, and can't be guessed at from the start.
Whatever how complex are the computations, at anytime the data used are just, and always just, the result of a process that can be reproduced as long as you kept trace of the path followed.

Even if everything was randomly generated, you could still rebuild everything identically after a load, as long as you kept the randomized values. Then the only thing you have to do is to provide those values, as if they had been randomly picked one instant before.
And like I guess that it's more along the line of "this state is equal to that, so the image is proceeded that way", whatever how many states apply at the same time, then there's absolutely nothing preventing you to retrieve the actual data.


Now, there are a bunch of relatively constant data structures that might be worth declaring in an init block. But seeing which ones actually are totally unchanging will take some doing.
Once again my Extended Variable Viewer can help with this. It would still need time (at least one play) but it have all the features to make this relatively easy to do.


I'm also not quite clear how different init blocks in different files interact. Because init 1 python should be able to use a class defined in an init 0 python block somewhere else, right? But it doesn't seem to.
I assure you that it do. It's only at equal level that there's possibly some issue, that can be fixed by renaming the files, since they are proceeded in alphabetical order.


But I do tend to notice when autosave is processing, which it will be (more often) when this is set to true. Right?
Have you played with config.autosave_frequency ? Put its value at 1000 and see if you feel it less often. Put its value at 1 and see if you feel it everytime.


No, I meant what does it actually do to achieve this?
And it's what I answered to.
A textbutton build a button and then add a text to it. Exactly what you did by building a button, then adding a text to it. It's just that, like it do it natively, it also do it better, faster and in a more optimized way.


Yeah, I'm counting up to 180+ buttons with hovers in that screen. Most of which are kinda useful to have. I don't suppose there's an alternative that accomplishes roughly the same thing (notes which button I'm about to press) without the specific visuals?
There's no alternative, but I wonder to which extend they are effectively this useful.
The player should already know what will happen when he click here or there, and what will be the difference if he do this when the button looks like this, and when it looks like that. And at worse there's always the possibility to add a global help screen as reminder.
Hover are effectively useful for actions that aren't performed often, or as an alternate way to label two mostly identical items (like doors in a free roaming section by example). Generally, outside of those context, they are more the symptom of a bad designed User Interface, than anything else.
They can also be used to add some depth through visual effects. But then they aren't really "useful". It's more a nice addition, but an addition that stop to be nice if it imply a slowdown ; and therefore an addition to remove.


The only major screen with timer is 'notify'. It's... not very frequent, but not uncommon, either.
And it's an internal screen that come with Ren'Py. Nothing effectively impact full. The timer is just here to hide the screen (and so also stop the timer), once the notification have to disappear.


Edit: Another thing this threading business reminds me of: I tend to savescum. A lot. So this might be sabotaging autosave and image-loading threads quite a bit.
Not really. Regular saves happen in the game thread, therefore they don't have an impact on autosave and image loading. Especially since you said that the hard drive is a SDD one.

It's if you where reloading regularly that it would be a problem, since effectively the image cache could start with totally outdated data and have to be totally purged then rebuilt. But the impact on the autosave should be at the opposite be benefit, since it should normally restart with a counter set at 0 ; so you would have another 200 interactions before the next autosave.
 
  • Love
Reactions: Jman9

Jman9

Engaged Member
Jul 17, 2019
2,295
957
...Extended Variable Viewer (link in signature) is for. Once you found one object that clearly have some attributes that shouldn't be saved, you can just look at the code to see its class and then track the object using the same.
I am not sure how this is supposed to work. What tells me that these are the 'attributes that shouldn't be saved'?

I am familiar enough with the code now that I'm likely to know the class just from variable names anyway. The question is, is a particular one too big? Frankly, having a hundred useless integers hanging around shouldn't really be a particularly bad problem save size-wise. And, AFAIK, telling how much memory (or disk space when pickled) something takes in Python is not easy.

Whatever how complex are the computations, at anytime the data used are just, and always just, the result of a process that can be reproduced as long as you kept trace of the path followed.
Given that a whole lot of it is either bookkeeping that depends on the state the player has put themselves into, things that are random by design, or direct input from the player, there is not too much determinism here. They could have 2 Slut equivalents, or 20, or 200. They might never interact with one, or all the time, flipping all sorts of flags and changing numeric values. The changes can be random, even. They could well reload and get a new number. That isn't limited to Slut equivalents, either.

Even if everything was randomly generated, you could still rebuild everything identically after a load, as long as you kept the randomized values. Then the only thing you have to do is to provide those values, as if they had been randomly picked one instant before.
Well, that would basically require me to keep a giant log of player inputs, random seeds, and rewrite the game to support that. Not to mention removing savescumming. :cry: I kinda doubt the end result would be smaller or more efficient.

Exaggerating a bit, it'd basically writing down all the intermediate results when you're only ever going to use the final answer. Save games exist for a reason.

Once again my Extended Variable Viewer can help with this. It would still need time (at least one play) but it have all the features to make this relatively easy to do.
I've never even got a quarter through the game. And it can basically continue forever, if the player so desires.

I'll try looking at the code and figuring it out myself. There seems to be a bunch of low-hanging fruit, at the very least.

I assure you that it do.
Okay, then why do I get
Code:
NameError: name 'ClassName' is not defined
from having these in three different script files?
Code:
init -10 python:
    class Structure(object):
        ...
Code:
init python:
    class ClassName(Structure):
        def __init__(self,classname_id):
            if some_data.has_key(classname_id):
                ...
Code:
init 1 python:
    some_data = dict_from_config_file('some.yaml')

    some_dict = {}
    for key in some_data:
        some_dict[key] = ClassName(key)

Have you played with config.autosave_frequency ?
Well, I tried again. config.autosave_frequency doesn't seem to have much effect.
Code:
config.has_autosave = False
config.autosave_frequency = 10000
config.autosave_on_choice = False
and
Code:
config.has_autosave = True
config.autosave_frequency = 1
config.autosave_on_choice = False
are virtually indistinguishable, the first being my default setting now, and the mini-freezes still happen. But...

Code:
config.has_autosave = True
config.autosave_frequency = 1
config.autosave_on_choice = True
Now that is starting to edge into unplayable slideshow territory.

There's no alternative, but I wonder to which extend they are effectively this useful.
The player should already know what will happen when he click here or there, and what will be the difference if he do this when the button looks like this, and when it looks like that.
Well, the reason I like them (and I was the person who added most of them) is that I know really quickly what I'm about to press.

I do admit they're a luxury.

OTOH, I just now went and disabled all of them for that screen and, honestly, any speed difference was marginal. The reason I mentioned them in the first place is that they're the most visible way I have to measure, well, 'responsiveness'.

And it's an internal screen that come with Ren'Py. Nothing effectively impact full.
That particular one was customised by the original dev, adding a frame and an appear/fade transform. I still doubt it's all that notable performance-wise.

It's if you where reloading regularly that it would be a problem, since effectively the image cache could start with totally outdated data and have to be totally purged then rebuilt. But the impact on the autosave should be at the opposite be benefit...
So I guess the conclusion is that any impact from autosave really shouldn't be there anymore after config.autosave_on_choice was turned off, and I might be causing some of the slowdown myself via frequent reloading and resetting the image cache.

It still doesn't seem to be the full answer since I can reload, play for five minutes, and get another 2-3 mini-freezes.

Now, one might be induced to think that maybe it's a system issue of some sort and not the game or even Ren'Py. But I have another mod for another Ren'Py game that's chock full of the most inefficient and overcomplicated code, and that just hangs for a bit (as in, the whole program becomes unresponsive) when it's chugging through the zillion calculations. Or, alternatively, it's sluggish all over if there's a screen that does a ton of stuff every refresh. It never does these small sub-second freezes.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,302
15,172
I am not sure how this is supposed to work. What tells me that these are the 'attributes that shouldn't be saved'?
Thinking, time, errors, and experience. It's a reverse process, but it's not different than when you build your software and have to decide if this variable will be saved or not.
Is the value relevant outside of the local context ? Can you save in the middle of the current context ? Can you rebuild the value with what is actually saved ? And all.


I am familiar enough with the code now that I'm likely to know the class just from variable names anyway. The question is, is a particular one too big? Frankly, having a hundred useless integers hanging around shouldn't really be a particularly bad problem save size-wise. And, AFAIK, telling how much memory (or disk space when pickled) something takes in Python is not easy.
Globally speaking, for pickle, it's one Byte, plus len of the variable name, plus the size of the value. Time to time you've one or two extra Bytes for structuration.
So, still globally speaking, for an object like Sluts (as I initially wrote it) it's:
1 Byte to mark the start of an object
X Bytes for the name of the object
1 Byte to mark that the class is following
5 Bytes for the name of the class
1 Byte to mark a string attribute
4 Bytes ("name" attribute)
Len of the string + 1
1 Byte to mark an integer attribute
5 Bytes ("meter" attribute)
DWord for the value
1 Byte to mark a list attribute
for 1..len list
1 Byte to mark a string value
len string
1 Byte to mark the end of the object

Except for strings and lists, the name of a variable will always take more place in the save file than its value. Objects and dictionaries will also take more place, but technically their value is limited to the Byte that say that it's a dictionary, and the Byte saying that it's and object + the name of the class, for and object. The rest will be the keys, that will take more place than the value, and the name of the attributes, that will take more place than their value.
In the game I used in example for the size of a save file, it's not the thousands variables that make the save file goes over 500KB, but the fact that it have more than 50 characters longs variables names that just host a boolean value. Would the author have limited his variables names to 20 characters max, the size of a save file would have been divided by two ; the compression already help to reduce a part of the impact of such long names.


They could well reload and get a new number.
If one save the game, it's to find it as it was when he will load it. The value MUST NOT be rerolled when he load.
Imagine that you're in the middle of the final boss battle, and you've to save for whatever reason. Then when you load again, the boss have retrieved all his HP, but you don't get yours back, nor the ammo you fired... It's not what saves are for and absolutely not how they should works.


Well, that would basically require me to keep a giant log of player inputs, random seeds, and rewrite the game to support that. Not to mention removing savescumming. :cry: I kinda doubt the end result would be smaller or more efficient.
It's why I also said that what matter is the actual state. You only have to save the path when it don't lead to a finite state ; what is not this frequent and generally apply only to drawing.
The path is what permit you to get the geometrical figure drawn on the screen ; this angle for x pixels, then turn Y° to the left and start a circle...
But here the result of the computation is a finite state. To keep my previous example, the girl with have that skin, those hairs, this size, and all. Those are finite values. You don't need to know how the game reached those value, just to know that they are the ones to use.


Okay, then why do I get
Code:
NameError: name 'ClassName' is not defined
Case one: Because you aren't attentive enough and not seen a difference between the name shown in the error message, and the effective name of the class.
Case two: Because there's a structural error in "ClassName" and Python can't build it, but for some reason haven't complain about this.
Because it works perfectly fine:
You don't have permission to view the spoiler content. Log in or register now.


Well, I tried again. config.autosave_frequency doesn't seem to have much effect.
Code:
config.has_autosave = False
config.autosave_frequency = 10000
config.autosave_on_choice = False
[...]
are virtually indistinguishable, the first being my default setting now, and the mini-freezes still happen. But...
What is the value you get for config.autosave_frequency, from the console and when the game is started ?
Because here, you're saying that you've mini-freezes due to the autosave... when Ren'Py do not use the autosave.
 
  • Like
Reactions: Jman9

Jman9

Engaged Member
Jul 17, 2019
2,295
957
Thinking, time, errors, and experience.
Yeah, but the question was, what does the EVV do to help me there? I'm sure it helps, I just am not able to figure out how to proceed with it. Seeing which classes are accumulating more crap than they're supposed to?

Objects and dictionaries will also take more place, but technically their value is limited to the Byte that say that it's a dictionary, and the Byte saying that it's and object + the name of the class, for and object. The rest will be the keys, that will take more place than the value, and the name of the attributes, that will take more place than their value.
That's exactly where I think the problems are, and identifying how bad a dictionary, or custom object, or even a list is in this respect, that does not seem to have any easy tools available.

In the game I used in example for the size of a save file, it's not the thousands variables that make the save file goes over 500KB, but the fact that it have more than 50 characters longs variables names that just host a boolean value.
Hmm, interesting and good to know.

If one save the game, it's to find it as it was when he will load it. The value MUST NOT be rerolled when he load.
No, I meant they could reload and get a new random number, then proceed with the next step using that. The resulting save will be different depending on whether it was save scummed or not, and I have no way to rebuild the resulting game state short of storing all random numbers and player actions. That, or disabling savescumming altogether and storing just the initial random seed. Which all looks really excessive.

Come to think of it, I also disagree with your thesis on an abstract level. A game or a few games that rejiggered the save state with every reload would be kinda interesting. All or most games being like that would be a show-stopper.

Case one: Because you aren't attentive enough and not seen a difference between the name shown in the error message, and the effective name of the class.

Case two: Because there's a structural error in "ClassName" and Python can't build it, but for some reason haven't complain about this.
Actually, it was case 3: there was a freaking init offset. :mad:

What is the value you get for config.autosave_frequency, from the console and when the game is started ?
Because here, you're saying that you've mini-freezes due to the autosave... when Ren'Py do not use the autosave.
'None', '1' and '1', same as defined.

I'm not saying I get mini-freezes only due to autosave. I'm saying config.autosave_on_choice contributes, but I still get them due to some other reason when autosave is completely disabled. And that other reason seems specific to this game.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,302
15,172
Yeah, but the question was, what does the EVV do to help me there? I'm sure it helps, I just am not able to figure out how to proceed with it. Seeing which classes are accumulating more crap than they're supposed to?
Well, since it show you the value of every single attributes in an object, even if the object is in a list of dict that is hidden in a object that is in an object inside a list of list of dict of list of object, you just have to look at them and see if, from your point of view, this or that value are crap or not.
By example:
You don't have permission to view the spoiler content. Log in or register now.
Not that it can also just present you the value that changed, what can help you, but negation, notice those that stay constants.


That's exactly where I think the problems are, and identifying how bad a dictionary, or custom object, or even a list is in this respect, that does not seem to have any easy tools available.
I say it since few days, there's one that can do this relatively well. You'll still need to understand what the values are for, but anyway no tool would be able to do this unless they include an AI.


No, I meant they could reload and get a new random number, then proceed with the next step using that.
And so, who care how the values have been generated ? They have a perfectly defined and stable state when the save is done. It's this state that have to be saved, and there's nothing preventing this to happen.
Like I said, save what is relevant to the game state right now.


The resulting save will be different depending on whether it was save scummed or not, and I have no way to rebuild the resulting game state short of storing all random numbers and player actions.
I said that "at worse" you save the process, but also said that only the actual state matter. You're really making your life more complex that it have do be.
Outside of a pure neuronal networks, there's a very limited and small number of cases where you can have a software that isn't constantly in a perfectly finite state. It can be temporarily in a not finite state, but this happen only in the middle of a computation, and you don't save in the middle of a computation.

Take the example in the screenshot above. Each entry in the list represent the different visual elements composing the body. You don't need to save them, just their state, and you don't need to save all those states.

Where are the legs is an information that matter, yet you can summarize it. You don't need to know the exact position, just what are their states right now ; by example "closed", "spread", "widely spread", because it's the option that the player have. With this sole information you can totally rebuild the image identically to what it was when the save was made. The position of the legs is always the same relatively to the body, and only depend of their state right now ; if the body is in X,Y, then the legs will always be at the x1, y1 position when they are "spread". As for the image, it's always the same for this body, therefore once again the value haven't to be saved for the legs, just for the body.
This mean that, just for the legs, you pass from two objects (one by leg) that host two other objects, to a single 3 states semaphore. Therefore, you pass from around 400 Bytes saved, to less than 20 (something like: BYTE[integer value]"legs"QWORD[the value]).
And the same apply for the head and arms if they can move. As for the rest (boobs, navel, sex, whatever) you don't need to know their position, they are fixed relatively to the body ; is the body is in x,y, the boobs will always be at x+X, y+Y.
Globally speaking, you'll pass from something like 1KB saved, to something that will be around 200 Bytes (body family for the images, expression for the face, position of the body, semaphore for the legs, arms and head).
And obviously you don't need to know why the girls have her legs spreads, not why she is at this position and have this body family.
 
  • Like
Reactions: Jman9

Jman9

Engaged Member
Jul 17, 2019
2,295
957
Well, since it show you the value of every single attributes in an object...
Well, that's what I figured. Useful, but not massively so. Edit: Or, for the purpose of identifying what's causing my issues. It'd be tremendously useful for rewriting the game, of course, but I'm not going to do that. /Edit

Is there another common use case I'm not thinking of?

...but negation, notice those that stay constants.
Negation of what? Booleans?

You'll still need to understand what the values are for, but anyway no tool would be able to do this unless they include an AI.
I mean, I'd like some sort of 'sizeof()' function that actually showed 'real' sizes. Then I wouldn't need to know anything about values before identifying where the big problem is. Right now, I'm kinda shooting in the dark. I might be getting tiny improvements in some places, but so far, nothing is touching the core issues of inflated save size or mini-freezes.

I might be looking at entirely the wrong problem, even, like searching for superfluous variables while missing config.periodic_callbacks could have been.

Maybe I should go and explore the saves themselves. Is there a tool for that?

I said that "at worse" you save the process, but also said that only the actual state matter. You're really making your life more complex that it have do be.
No, I'm telling you I don't see the point in doing so, for this game. Pretty much all images are static 2D art that's not combined in any way. The most I do is resize and alpha mask, and I put those in that dictionary that gets built on the go. Not sure if that was the right call but, eh.

I did have a bunch of stuff that were better off in an init block. I don't think moving made much of a difference to save sizes or anything, though.
 
Last edited:

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,302
15,172
Negation of what? Booleans?
It will highlight the variables that have had a value change (even it's in an attribute in an object inside a dict of list). Therefore, anything that have never been highlighted have a pure constant value and don't needed to be saved.


I mean, I'd like some sort of 'sizeof()' function that actually showed 'real' sizes.
renpy.sys.getsizeof() will return you the size of an object in Bytes. But for none native/built-in objects, it will need a little more, because it only refer to the size used in RAM by the object, not including the size of its attributes ; unless they defined __sizeof__ meta method.


Then I wouldn't need to know anything about values before identifying where the big problem is. Right now, I'm kinda shooting in the dark. I might be getting tiny improvements in some places, but so far, nothing is touching the core issues of inflated save size or mini-freezes.
It would change absolutely nothing.
The size of a 4 characters long string in RAM is 58 Bytes. The size of this same string once pickled in the save file will be 7 (or perhaps 8, don't remember exactly) Bytes plus the length of the variable name where it's stored.
Fr an integer it's 24 Bytes in RAM, and 24 Bytes + length of the name when pickled. But for a boolean value, it's 24 Bytes in RAM (because bool inherit from int), but once pickled it's, from memory, the length of the variable name where it's stored + 1 Byte.


Maybe I should go and explore the saves themselves. Is there a tool for that?
By default both variables viewers (mine and the native one) only show you the value that will be saved ; and mine apply for all stores, so it give a better overview. But none include the extra data coming directly from the core. And, there's not really a tool for that. Just a script Rich wrote once to have a quick glance inside the persistent file. I attached a small variation I made that works with regular save files, but not directly:
You firstly need to open the ".sav" file as an archive, then extract the "log" file. Then type pyhon dump-SAVE.py log (from the directory itself).



No, I'm telling you I don't see the point in doing so, for this game. Pretty much all images are static 2D art that's not combined in any way. The most I do is resize and alpha mask, and I put those in that dictionary that gets built on the go. Not sure if that was the right call but, eh.
Then just get rid of any information regarding the image (except path+name) that are stored in the objects. Or at least verify that there's none ; but from what you said, I expect that the resized image are stored in the objects.
 

Jman9

Engaged Member
Jul 17, 2019
2,295
957
It will highlight the variables that have had a value change (even it's in an attribute in an object inside a dict of list). Therefore, anything that have never been highlighted have a pure constant value and don't needed to be saved.
Oh, yeah, that is good. It doesn't show anything that's default-ed or define-d, right?

renpy.sys.getsizeof() will return you the size of an object in Bytes. But for none native/built-in objects...
Exactly my problem. And the same general problem also exists for Python, and probably most languages?

It would change absolutely nothing.
The size of a 4 characters long string...
It would, if I could apply it to arbitrary objects. If there's a runaway log, cycle or whatever that produces a ton of pointless crap and hides it somewhere, I could sorta search for it.

Then type pyhon dump-SAVE.py log (from the directory itself).
Code:
    File "dump-SAVE.py", line 65, in <module>
        result = unpickler.load()
    File "dump-SAVE.py", line 50, in run
        result = unpickler.load()
TypeError: defaultdict() takes no arguments
WTF is this even? Do I have some bogus version of defaultdict?

Edit: In other news, moving some stuff to init reduced starter saves from 550kB to 420kB. A win, but not a major one.
 

anne O'nymous

I'm not grumpy, I'm just coded that way.
Modder
Donor
Respected User
Jun 10, 2017
10,302
15,172
Oh, yeah, that is good. It doesn't show anything that's default-ed or define-d, right?
It show everything that is defaulted, since it's how you declare variables as savable ; unless their value haven't changed since last time and you asked to see only the value that changed. But it show nothing that is defined unless that had a value change that made them savable.
After there's many different filters, so you can see everything, or nothing, it totally depend of what you ask for.


It would, if I could apply it to arbitrary objects. If there's a runaway log, cycle or whatever that produces a ton of pointless crap and hides it somewhere, I could sorta search for it.
But this crap can be purely none picklable data or, but I doubt that it's the case, there can be a __setargs__/__getargs__ that will reduce what is effectively pickled.


Code:
    File "dump-SAVE.py", line 65, in <module>
        result = unpickler.load()
    File "dump-SAVE.py", line 50, in run
        result = unpickler.load()
TypeError: defaultdict() takes no arguments
WTF is this even? Do I have some bogus version of defaultdict?
Hmm, perhaps forgot to say that it need Python 3.


Edit: In other news, moving some stuff to init reduced starter saves from 550kB to 420kB. A win, but not a major one.
But are the save effectively consistent ?
You gain some space because the objects aren't saved anymore, but were their value effectively none significant ?
 

Jman9

Engaged Member
Jul 17, 2019
2,295
957
It show everything that is defaulted... But it show nothing that is defined...
Ah, yeah, true. :oops:

But this crap can be purely none picklable data or, but I doubt that it's the case, there can be a __setargs__/__getargs__ that will reduce what is effectively pickled.
Weal, yeah, but something is inflating the saves. Which means it's got to be pickleable.

I tried a Ren'Py save editor, but than one's effectively useless for exploring.

Hmm, perhaps forgot to say that it need Python 3.
I have, lemme check, 3.7.4. I can manually open up the interpreter and define defaultdict objects, with or without arguments, no problem. Puzzling.

But are the save effectively consistent ?
You gain some space because the objects aren't saved anymore, but were their value effectively none significant ?
Restarted and saved a few times. 312 kB, 305 kB, 310 kB, 313 kB, 316 kB. Goes to about 340-350 kB if I start playing and get some Slut equivalents, pass a turn and see an event.

This is better than before because I moved another couple constant data structures to init.

I'd say, overall, these did have 'significant' values, compared to the rest. Of course, it's nearly nothing when contrasted against the fucked up test game I've been playing that's grown to over 7Mb now. :cry:
 
Last edited:

EricGrey

Member
Nov 11, 2020
436
441
Jman9

After reading through the majority of this thread, there is one thing that keeps coming back, and that is your save times. This could be caused by a bad disk drive.

Have you run a full disk scan for errors? If you are seeing a lot of errors, it could be on its way out. A dying disk drive can take much longer to save and read from disk than a properly working one.

I've had recent experience with this on my 3 year-old laptop (fuckin Dell) where the mechanical (!) hard drive was dying just after the two-year mark (less than a month after I let the extended warranty lapse, of course).

Luckily, I was able to back it up, and replace it with an NVME drive that improved performance.

Just a suggestion. I'm not anywhere near as knowledgeable on the programming side of things as the rest here, but I do have a fair amount of experience with hardware.


:cool: Eric Grey
 
  • Like
Reactions: Jman9

Jman9

Engaged Member
Jul 17, 2019
2,295
957
Nope, I have a variety of disks in my machine and moved the game from one disk to the SSD just prior to creating the thread. The issues persisted on both. (Of course, both could be faulty, but then my whole system ought to be slowing down and starting to explode).

Save times also scale with save sizes, so that's working as expected.

I also don't get these issues in another similarly complex Ren'Py game/mod, nor other games in general. Well, I did get to play PF: Kingmaker as 'Loading Screen: The Game', but I don't think I'm the only person to have that issue. :p

I do suspect my habit of leaving the computer on 24/7, its age and Windows memory (mis)management might be contributing a little. I have no hard data to support or refute that, however.

I'm kinda resigned to the fact that saving is fucked up and to unfuck it I'd need to go and examine the saves in more detail, which I kinda failed at. But I still get these small ~1 second freezes every once in a while in completely distinct parts of the game. Some of this can certainly be explained due to save-scumming, but not all of it.
 

EricGrey

Member
Nov 11, 2020
436
441
Nope, I have a variety of disks in my machine and moved the game from one disk to the SSD just prior to creating the thread. The issues persisted on both. (Of course, both could be faulty, but then my whole system ought to be slowing down and starting to explode).
Whelp, it was a thought.

EDIT: I just had a thought. System slowdown on disk access such as saving the game would more than likely be your system drive. Ren'Py saves to the game directory AND the user profile directory (i.e. C:\Users\<userID>\AppData\Roaming\RenPy. Either disk (assuming you have the games on a secondary drive) could be the problem.

Although I have not seen this type of behavior on an SSD, only mechanical drives. That may be in part due to the fact that my current machine is the first I've had with an SSD installed, so there is that. :p

I do suspect my habit of leaving the computer on 24/7, its age and Windows memory (mis)management might be contributing a little. I have no hard data to support or refute that, however.
Rebooting is a good thing for any OS, Windows especially, but even Mac and Linux computers need it on occasion. That is just a general rule to follow.

Good luck regardless.


:cool: Eric Grey
 
Last edited:

dyho

Newbie
Jan 31, 2020
87
243
renpy if I have in a nv images with resolutions of 1920x1080 and animations in 1280x720 do you know how to make the resolution adjust automatically I ask you since there are some nv it happens like it is DMD in gui.rpy even if it appears 1920x1080 and the resolution of the images is reduced videos on the screen are adjusted automatically
 

Jman9

Engaged Member
Jul 17, 2019
2,295
957
I do not understand the question. Ren'Py automatically scales everything to window size. renpy.movie_cutscene is automatically fullscreen.
 

dyho

Newbie
Jan 31, 2020
87
243
I do not understand the question. Ren'Py automatically scales everything to window size. renpy.movie_cutscene is automatically fullscreen.
It happens that I have a gui in 1920x1080, which is the resolution of the images, but the videos are in 1280x720 and they do not appear on the entire screen, only a small box
 

Jman9

Engaged Member
Jul 17, 2019
2,295
957
What exactly are you using to display videos?

More on topic, why are you asking about it in this thread?