Mainly what I'd call 'stutter'. Unexpected and unpredictable 'freezes' in moving from one interaction to another. Kind of like the autosave issue, but smaller-scale.
What kind of device do you play on ? The autosave is supposed to be threaded for all games, therefore it do not stutter, it can not stutter. And I don't just assume it, I know it for sure, just verified it right now.
I took my variable viewer, enabled all the option, and opened it to fill the dict with the previous value.
What mean that I had a save that contained a dict with an entry for each variables, functions and classes available in the 15 stores, and most of the ones present in the 143 modules of the core ; said otherwise, it had thousands of entries (more 30 000 for this game), for an average length by entry that should be around 40 Bytes.
The autosave file was near to 13 000 KB, for a log file (where the pickled variables are stored) near to 55 000 KB. It's something
really big (the average save is around 500 KB at max), that needed exactly 34.9102740288 seconds to be proceeded (pickling of the variables then compression of the file).
There's just no way I could have missed it, right ? If the autosave was effectively impacting the game, making it stutter, there's no way I could have missed it... But the truth is that I didn't noticed it, because as I said, the part effectively happening in the main process is atomic. Everything is performed in a thread and have absolutely no impact for the player.
Choose whatever game that supposedly stutter because of the autosave. Put this in a rpy file:
Code:
init python:
renpy.config.autosave_frequency = 10
config.console = True
then put the said file in the "/game" directory and play the game.
The game will perform the autosave every 10 interactions. So, advance a dozen of dialog lines, and verify in the "/game/saves" directory that an autosave have been performed.
Once you've the confirmation that an autosave happened, open the console (
shift[icode] + [icode]o
) then type:
renpy.loadsave.autosave_thread
.
If it return you "None", for some reason the autosave is not threaded for this game/OS/Device. But if it return you something like
<Thread([...])>
then it's not the autosave that make the game stutter because it happen in a thread and is totally cost free for the player.
[Note: The thread is created when the first autosave is performed, reason why you need to pass through it before you can know]
If effectively the autosave is threaded, and that the stuttering effectively happen everytime a autosave is performed, then my guess goes for the hard drive queue, or its cache (either hardware or the one of the OS).
All this being said, by default the autosave happen every 200 interactions. The average reading speed being around 5 words second, and the average dialog line being around 15 words (I hate this actual trend), it mean that on average there's an autosave every 10 minutes.
With an average save taking less than 0.2 seconds to be performed, it's already difficult to notice the stuttering. But it become even more difficult when it happen every 10 minutes. Especially since it happen while you are reading ; what mean that, for you to notice it, it need to take more times that you'll pass reading the dialog line.
I believe you, there's something that make games stutter for you. But I really doubt that it's due to the autosave.
Also, screens in general, because there's an unholy amount of UI cycles going on and every little bit helps.
Hmm...
Quantify "unholy". Right now I have a screen opened that have a
vpgrid
of 1521 lines, each line having 10
ui
. It's not totally smooth of course, but I didn't expected it to be ; it's around 8 000 that it stop to be smooth. Yet I wonder to what extend removing the hovering style on the button wouldn't solve this issue.
So, is "unholy" more than those 15 210 ui, without counting the screen language part of the screen ? Or at least more than the 8 000 ?
[...] but said mess is already conceptually so damn complex it's very nearly answer #1.
Ren'Py is basic engine, build with a basic language. Keep your code basic, and it will works perfectly with Ren'Py. Overdo it, and you'll struggle.
The only games I've seen suffer from Ren'Py are the ones who try to tame it by force instead of trying to understand it. I don't know if it's still the case, but years ago Summertime Saga had a bunch of around 100 Python lines to do something that Ren'Py can do natively with a 5 lines screen. And off course it's this second version that was faster.
Excessive predicting of dynamically generated displayables is exactly the issue that might crop up. Or not. As I said, my evidence is really not conclusive that it helps, mainly that I have no crashes with and had occasional crashes without WebP. Might entirely be grasping for straws here.
It depend of what you call "dynamically generated displayable".
If it's built up image like the layered images offer, the prediction have no consequences. More or less the same if it's dynamism by text substitution (
image whatever = "images/[girlName]/nude.png"
). If it's the
show expression [...]
kind, it will depend of the complexity of the expression.
And if it's dynamically drawn displayable, well, it works fine:
Mostly because it's not predicted unless you explicitly define the
predict
method.
[side note: I really hope that one day I'll have the time to write the game that goes with this effect]
Sure, that's what it's intended to do. But with a ludicrous 'config.image_cache_size = 4096' I've ever seen the game take ~8Gb once, and it crashed soon after. Usually, it stays entirely under 3Gb.
For my test regarding the autosave, Ren'Py goes to 10GB use without problems. But I must say that I have 32GB of native RAM, so the OS haven't had to rely on its cache.
So, my guess is that it's an OS problem more than a Ren'Py one.
Said objects are actual, pretty complex Python objects. Above about 30-50 of those in a viewport or similar tends to be too much clutter from both a design and screen refresh POV.
You absolutely don't need this, really.
As I said above, as long as you stay basic, everything will be fine ; it's when you try to overdo it that Ren'Py will start to rebel.
And yes, you can do really complex thing with the most basic code.
In fact, I think I ran into
viewport size limits before I did the paging.
No, you haven't. I said it above, I have 15 210 ui in front of my eyes right now and Ren'Py can clearly go further. Not smoothly, but it will not crash.
Because I have to recalculate paging info on the fly, removing and reordering the objects dynamically to reflect changed game state.
If you have a suggestion for avoiding that, would be much appreciated.
Python:
init python:
def reset():
for o in object_list:
o.reset()
class Object( renpy.python.RevertableObject ):
def __init__( self ):
self.__x = None
self.__y = None
self.__visible = None
def reset( self ):
self.__x = None
self.__y = None
self.__visible = None
def display( self ):
[...]
@property
def x( self ):
if self.__x: return self.__x
[compute the value]
self.__x = the computed value
@property
def y( self ):
if self.__y: return self.__y
[compute the value]
self.__y = the computed value
@property
def visible( self ):
if self.__visible: return self.__visible
[compute the value]
self.__visible = the computed value
screen whatever:
for o in object_list:
if o.visible:
add o.visual pos( o.x, o.y )
textbutton "close":
action [ Hide( "whatever" ), Function( reset ) ]
And it's done, your objects are ordered on the screen and only visible if needed. Optimize the computation of the coordinate and visibility, make all your object inherit from this base class, and you are good.
If you fear that the screen look messing for an instant at the opening, then it's this:
Python:
init python:
def preLoad():
for o in object_list:
o.compute() # replace the computation in /x/, /y/ and /visible/ properties
label whatever:
$ preLoad()
call screen whatever
But unless you change the state right before opening the screen, Ren'Py prediction should do its magic here, and the value should already be computed when you'll open the screen. What should also apply for
display
, that would also benefit from a cache system ; like the
__x
/
__y
/
__visible
being computed only once by screen opening.
If effectively the screen prediction do its magic (it's largely past 4AM here, so I'm not totally sure), but the computations take time, then try to design the game for it to not change its state during the three/four interaction preceding the opening of the screen. Like prediction are also threaded, it will not impact the game, while still letting the time for Ren'Py to compute all the values.
Given that I can control my window size and resolution (and also tell others to do so),
Expecting them to agree to do so and to have a screen that reach this resolution.
this is not as inapplicable as it seems from a general dev POV.
From a general dev point of view, and I talk as a professional, as well as a general player point of view, it's the worse approach. I don't count the number of Unity games I haven't been able to play, because their author didn't cared to make them 4/3 compatible at the time where I still had only 4/3 screens.
Also, Ren'Py can totally leave an image at its original size and cut off anything that goes over the edge.
Oh, but it also do. The author is assumed to know what he do, therefore any image is displayed accordingly to its size, cropped in order to fit the size gave in the configuration. Then the result is resized to fit the effective window size.
And I must say that it's how it have to be done.
It's probably not a real benefit, though. I guess the biggest problem would be loss of image quality from downscaling too much.
I fear to ask the size of your images...
Not the game I have.
Saves take several
seconds. And it's
very noticeable when autosaves are cut out.
What's the size of a save file ? If it's below 1 000 KB and it take several seconds, it's the hardware or OS the problem. If it's above 1 000 KB, then it's the game design the problem.
But in both case, it's not Ren'Py and the autosave is noticable only if Ren'Py can not thread them, what again send back to an OS issue.
There's a lot of semi-procedural content and event reuse. So these 0.1 seconds can be important when there's lots of skipping.
If the player encounter some annoyance because he decided to skip, it's his problem, not yours. And I can assure you that he'll complain less about those 0.1 seconds, than about the lack of rollback.
Of course it has. If it didn't, I wouldn't be here.
Then as I said, your game have a bigger problem.
The best solution would be to not generate so much trash, and obviously manually triggering the collection is never the solution. You should change the threshold and let Python/Ren'Py (depending of the configuration) deal with it when the time come:
Code:
init python:
config.gc_thresholds = (10000, 10, 10) #Initially 25000
Mine is decidedly in the 90%. Maybe even 99%.
Then you're using Ren'Py wrongly.
I'm not blaming Ren'Py. It's obviously not a good fit for the game, but it's what I've got.
There's a RPG Maker emulator entirely made with Ren'Py... Therefore I wonder what your game can be for Ren'Py not being a good fit for it.
Of course there's its limited performances, but as long as you don't try to do real time rendering, it's not really a problem. At least as long as you code it following Ren'Py flow.
The biggest bottleneck of Ren'Py is the 'Py, i.e. using Python (and even pygame). Python is among the slowest of mainstream languages. The truly critical stuff is in C even in Ren'Py (or pygame, really).
Python is the second smallest language. Yet look at the two videos I provided... It's real time processing through a Python object. The hologram effect take a picture, apply transparency on it, tint it in blue, then deform it, on average 40 time by second... Ren'Py is fast enough for any game that don't need full real time.
There's indeed a lot of activity attached to buttons.
More than 2 400, that correspond to the 800 entries, I talked above, before Ren'Py stop to respond smoothly ?
Caching some values into variables helps, but this is really not applicable to everything.
It
is applicable to everything. By default the screens are refreshed 5 times by seconds, and by "refreshed" I don't talk about the frame rate, I talk about the number of time the screens are fully proceeded, recomputing each value in them. If really you are in need for speed, then even the screen actions should be cached.
The only thing that effectively can't be cached are the ui call. Yet, I'm almost sure that you can do something by playing with
ui.Warper
and
Curry
.
I guess that should have been on the list: use variables instead of functions in UI-related parts as much as possible. Easier said than done, though.
You don't need to use variable, you need to use none embedded code, and to cache the values (or declare the function as
[USER=42055]Pure[/USER]
). There's no need to redo all the computation 5 times by seconds when the result will always be the same.