[Scummvm-devel] ATTN Porters: SaveFileManager API Adjustments
Johannes Schickel
lordhoto at gmail.com
Sun Mar 20 18:47:10 CET 2016
On 03/20/2016 04:54 PM, Marcus Comstedt wrote:
> Johannes Schickel <lordhoto at scummvm.org> writes:
>
>> This is behavior you suggest for listSavefiles is exactly the reason
>> why I looked into this. Our default implementation thought this is a
>> good idea and did it. And that is a real problem, because it results
>> in discovered save files which can not be loaded.
> Huh? How is that possible? listSavefiles should only return names
> of files which actually exist. If the file exists it should be
> possible to load it, if you use its correct filename (which is the one
> returned by listSavefiles).
*If* you use its correct casing in filename. And here is the issue how
to do that in practice.
> If there is a file "FnorD.99" and listSavefiles is called with the
> glob "fnord.##", then listSavefiles should return "FnorD.99", because
> that is the name of the file which matches the glob, and the engine
> should pass the name "FnorD.99" to the openForLoading function, becase
> that is the save file that was discovered. If the name of the file is
> "FnorD.99" and you call openForLoading with the name "FnorD.99" it
> should work regardless of whether the filesystem is case insensitive
> or not.
Let's look at the reality of things. Engine's usually have a load/write
operation based on slot numbers. For opening the apprioriate save file
they generate the save file name without caring about case issues.
This is arguably bad behavior on case sensitive file systems. According
to you (and I agree here) good engine behavior is to use exactly the
case returned by listSavefiles to avoid problems.
How would engines go on for implementing this? Naively: Use just
listSavefiles to determine the casing for the specific slot number and
then use it for openFor*. And this is exactly what you argue against
because this can lead to bad performance.
>> So, how big would the penalty be for your system?
> Well, let's try to math it out. Let's say that reading the file list
> and reading a small save takes the same amount of time T. Let's also
> say that you have N saves which match the current target. To present
> the save game dialog, the engine has to call listSavegames once,
> and openForLoading N times. listSavegames only needs to fetch the
> list of files, to it takes time T. openForLoading will open the file,
> which internally needs to fetch the file list to get the start block
> number, and then the file can be read, so T+T=2T time for that.
>
> So with current (case sensitive) implementation this takes T(2N+1),
> which amounts to maybe 5 seconds for a reasonable N. Incidentally, it
> used to take T(2N+100-N) because the engine would war-dial all
> possible savegame names, which was horrible of course. Hence the
> existence of listSavegames.
>
> Now, if each call to openForLoading needs to perform an extra file
> listing so it can figure out the case before it can call the real open
> function, the time for reading the file becomes 3T instead of 2T. So
> the savegame dialog would now need T(3N+1) time to pop up, which is an
> increase of approximately 50%. When it already takes several seconds,
> this is pretty bad.
>
I am a bit confused by your maths to be honest, openForLoading (with
system casing) should only take T because it only lists the files once?
So in total, for system casing it should be (N+1)T and for case
insensitive (2N+1)T?
But in general what you argue against is exactly what you end up with if
you leave the case underspecified?
Let's look at it again. What is our main goal? Be user friendly and have
a smooth save/load experience for the user.
1) What can affect this experience? Messed up casings on case sensitive
file systems.
2) Where can this come from? (In random order:) 1. Users copy over save
files from friends and ignore case differences. 2. Copying over betweens
systems which have different cases. 3. ScummVM messing up cases in
previous ScummVM versions (i.e. trying to start a game with a different
case variation of a target name or the user messing up target name case
in the Edit Game dialog).
Result) To be on the safe side, every engine will need to implement case
insensitive save file operations.
Please be aware that just because some issues can't happen on your
system, doesn't mean the engine side doesn't won't have to worry about them.
Let's look at this specific use case of save/load dialogs again (which
is not the only use case for save/load, since there is, for example,
also quick saves via hotkeys). Based on this I will present what I
personally learned from thinking about it.
An engine implementing case insensitive access for the dialogs on a
unspecified (and thus potentially case sensitive) system will need to do
the following:
1) List all available save files via listSavefiles, this is a single
operation taking time T.
2) For N save files, it will need to read the description to present it
to the user.
A system file opening operation takes time T to obtain the start
block (according to you, let's just go with it). And some time D to
extract the description. As I described above a naive implementation
would do another listSavefiles to figure out the case to use beforehand.
This is takes again time T.
In total that is 2T+D for extracting the description for every save
file, i.e. N(2T+D) in total.
In total this gives me T+N(2T+D) = (2N+1)T+ND for a naive implementation.
What is the really important point we learn here? That opening a file in
a naive way will take 2T time for every file opening operation.
Everywhere in the engine, in other use cases too. For example, hot key
based save loading, load from launcher, etc.
Now, an engine can be smart, it can try to cache the save file list to
prevent another listSavefile for every file opening operation in the
engine, reducing openFor* again to T instead of 2T (This will bring ups
back to (N+1)T+ND for the dialogs).
This work we need to do in every relevant engine and in all places,
which are all if we want to strive for the best user experience possible.
So in total we learned:
1) All engines will most likely do case insensitive save file operations
for maximum user friendlyness.
2) Engines can improve performance by caching save file names and bring
it back to the level of case sensitive access.
From 2 we know that we can feature fine performance on case sensitive
systems when putting effort in this. However, because of 1, this might
be logic spread across all engines. A better idea is to have all this
logic concentrated in one place: SaveFileManager. This makes it a good
idea to simply make SaveFileManager case insensitive. For implementors
of a backend it seems easy to use caching of the file list to improve
speed of operations if necessary.
// Johannes
More information about the Scummvm-devel
mailing list