FileAccess.StoreLine not working randomly

Godot Version

4.4.1

Question

I’m having trouble writing my Player’s inventory to a save file, but only when I am accessing the Player using a list of the current players in the game. When I access the same Player using a static localPlayer variable, it works as expected.

    public void SaveGame () {
        NonArraySave();
        // ArraySave();

        if (Player.localPlayer == Player.players[0]) {
            GD.Print("THEY'RE THE SAME PERSON");
        }

        if (Json.Stringify(Player.localPlayer.equipmentManager.SaveCurrentEquipment()).Equals(Json.Stringify(Player.players[0].equipmentManager.SaveCurrentEquipment()))) {
            GD.Print("THEY'RE THE SAME STRING");
        }
    }

    void NonArraySave () {
        using FileAccess saveFile = FileAccess.Open(fileName, FileAccess.ModeFlags.Write);

        string saveString = "NonArrSave\n";
        saveString += Json.Stringify(Player.localPlayer.equipmentManager.SaveCurrentEquipment());
        saveFile.StoreLine(saveString);
    }

    void ArraySave () {
        using FileAccess saveFile = FileAccess.Open(fileName, FileAccess.ModeFlags.Write);

        string saveString = "ArrSave\n";
        saveString += Json.Stringify(Player.players[0].equipmentManager.SaveCurrentEquipment());
        saveFile.StoreLine(saveString);
    }

The only difference between the 2 methods is that the NonArraySave method uses Player.localPlayer instead of Player.players[0], but somehow ArraySave doesn’t work; the save file is just completely empty.

The player is the same and the Json strings being stored are identical but 1 method works and 1 doesn’t. Does anyone know what I could be doing wrong?

I am using Linux Mint if that changes anything.

I’d be inclined to merge your NonArraySave and ArraySave functions into a single function that takes the player as an argument, and then see if you get the same results between handing in Player.players[0] and Player.localPlayer.

What I’m wondering is if there’s some shenanigans going on where they aren’t the same type somehow, but one can be downcast to the type of other so they appear to be under some circumstances.

1 Like

So I did some more testing and I think the issue has something to do with how I’ve been calling the SaveGame method. Before, I only tried autosaving when the game closes; I added this override to my SaveManager class based on this documentation:

    public override void _Notification (int what) {
        if (what == NotificationWMCloseRequest) {
            SaveGame();
        }
    }

This reliably calls the SaveGame method whenever I close the game, but for some reason, it isn’t writing to the file correctly in this case. I’ve tried closing the game with Alt-F4, clicking the X on the window, and a Quit button in the pause menu, but the issue is present every time.

When I save without closing the game, however, it works as expected; I added an autosave every time the pause menu is opened and it works whether I am using the array or not.

I didn’t think that could matter since the player isn’t null and saveString was always the expected value so I didn’t mention that part.

When I have more time I will try to rewrite SaveGame to use System.IO’s FileStream class instead of Godot’s FileAccess just to see if there is a difference.

In that case you should try calling FileAccess.flush() or FileAcess.close() as you may risk the file not being flushed to disk before the process exits.

2 Likes

Unfortunately, calling Close on the FileAccess didn’t have an effect and passing the player to a separate function didn’t either.

I tried using System.IO.FileWriter and it has the same problem, but then I tried System.IO.FileStream, and it works as expected:

    public void SaveGame () {
        string saveString = "";
        saveString += "Save Name\n";
        saveString += Json.Stringify(Player.players[0].equipmentManager.SaveCurrentEquipment());

        GD.Print(saveString);

        using (System.IO.FileStream saveFile = File.Open(Path.Join(OS.GetUserDataDir(), "save.sav"), FileMode.Truncate)) {
            WriteText(saveFile, saveString);
        }
    }

    private void WriteText (FileStream fs, string value) {
        byte[] info = new UTF8Encoding(true).GetBytes(value);
        fs.Write(info, 0, info.Length);
    }

So something is breaking in FileAccess and FileWriter but not FileStream? Or there’s a race condition somewhere and I got lucky? Idk. Maybe I’ll try making an MRP at some point, but for now this method is working.

In that case then the problem is probably that it may not even reach the close() or flush() function before the process exits. You’ll need to disable Auto Accept Quit in Project Settings -> Application -> Config and manually call SceneTree.quit() after the game is saved.

I’ll give that a shot thanks! I can also try delaying Quit() until a frame later to see if that helps, but in my first example I was printing to the console after the ArraySave was called so Close() was definitely being called before the process exited.

I found the problem: it’s me :sweat_smile:

The save was always working correctly, but then in ExitTree it was trying to save again and the player would remove itself from the players array by then. The 2nd save would then either completely overwrite the previous file or would simply save an empty player inventory.

The odd part is that there was never a NullReferenceException or a print to the console to show that it was trying and failing the 2nd time.

Thank you everyone for your help!

1 Like